doc: add some documentation on platform-independent modules
This commit is contained in:
+11
-14
@@ -1,15 +1,12 @@
|
||||
pub fn arch() type {
|
||||
const builtin = @import("builtin");
|
||||
//! Helper module to select architecture-specific modules depending on what platform is
|
||||
//! being targeted.
|
||||
|
||||
switch (comptime builtin.cpu.arch) {
|
||||
.riscv64 => {
|
||||
return @import("arch/riscv64.zig");
|
||||
},
|
||||
.aarch64 => {
|
||||
return @import("arch/aarch64.zig");
|
||||
},
|
||||
else => {
|
||||
@panic("Architecture is not supported");
|
||||
},
|
||||
}
|
||||
}
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const impl = switch (builtin.cpu.arch) {
|
||||
.riscv64 => @import("arch/riscv64.zig"),
|
||||
.aarch64 => @import("arch/aarch64.zig"),
|
||||
else => @compileError("Unsupported architecture"),
|
||||
};
|
||||
|
||||
pub usingnamespace impl;
|
||||
|
||||
+17
-1
@@ -1,3 +1,5 @@
|
||||
//! RISC-V 64-bit platform-specific implementations.
|
||||
|
||||
const boot = @import("riscv64/boot.zig");
|
||||
const regs = @import("riscv64/regs.zig");
|
||||
const thread = @import("../thread.zig");
|
||||
@@ -16,19 +18,23 @@ fn idleFunction() callconv(.naked) noreturn {
|
||||
asm volatile ("j .");
|
||||
}
|
||||
|
||||
/// This CPU's HART (HARdware Thread) ID.
|
||||
pub threadlocal var tHartId: u32 = 0;
|
||||
|
||||
/// RISC-V task context
|
||||
pub const Context = extern struct {
|
||||
const STACK_SIZE: usize = 8192;
|
||||
|
||||
// Has to be exactly at offset 0x00, used in assembly
|
||||
// Has to be exactly at offset 0x00, used in assembly.
|
||||
kstack: thread.KStack(STACK_SIZE),
|
||||
|
||||
/// Constructs an idle context struct.
|
||||
pub fn idle() @This() {
|
||||
const entry = @intFromPtr(&idleFunction);
|
||||
return Context.kernel(entry, 0);
|
||||
}
|
||||
|
||||
/// Constructs a kernel task context with entry point in `pc` and an `arg`ument.
|
||||
pub fn kernel(pc: usize, arg: usize) @This() {
|
||||
var ks = thread.KStack(STACK_SIZE).create();
|
||||
const entry = @intFromPtr(&__rv64_task_enter_kernel);
|
||||
@@ -54,15 +60,18 @@ pub const Context = extern struct {
|
||||
return .{ .kstack = ks };
|
||||
}
|
||||
|
||||
/// Low-level task context entry function.
|
||||
pub fn enter(self: *@This()) noreturn {
|
||||
__rv64_enter_task(self);
|
||||
}
|
||||
|
||||
/// Low-level task context switch function.
|
||||
pub fn switchFrom(self: *@This(), from: *@This()) void {
|
||||
__rv64_switch_task(self, from);
|
||||
}
|
||||
};
|
||||
|
||||
/// Halts the CPU execution indefinitely, without ever returning.
|
||||
pub inline fn halt() noreturn {
|
||||
while (true) {
|
||||
_ = setInterruptMask(true);
|
||||
@@ -70,6 +79,8 @@ pub inline fn halt() noreturn {
|
||||
}
|
||||
}
|
||||
|
||||
/// Modifies the interrupt mask to either allow or block IRQs from being delivered to the CPU.
|
||||
/// Returns the old IRQ mask.
|
||||
pub inline fn setInterruptMask(mask: bool) bool {
|
||||
const old = interruptMask();
|
||||
if (mask) {
|
||||
@@ -80,19 +91,23 @@ pub inline fn setInterruptMask(mask: bool) bool {
|
||||
return old;
|
||||
}
|
||||
|
||||
/// Returns the current state of IRQ masking.
|
||||
pub fn interruptMask() bool {
|
||||
return regs.SSTATUS.read().SIE;
|
||||
}
|
||||
|
||||
/// Suspends the CPU until an interrupt is signalled.
|
||||
pub inline fn waitForInterrupt() void {
|
||||
asm volatile ("wfi");
|
||||
}
|
||||
|
||||
/// Hint to the CPU that the code is executing a "busy-wait" or a "spin-wait" loop.
|
||||
pub inline fn spinHint() void {
|
||||
// Don't want to explicitly enable Zihintpause ext, so just paste this as raw opcode
|
||||
asm volatile (".word 0x0100000f");
|
||||
}
|
||||
|
||||
/// Combined memory/compiler fence to ensure specific ordering of instructions and memory accesses.
|
||||
pub inline fn barrier(comptime ordering: std.builtin.AtomicOrder) void {
|
||||
switch (ordering) {
|
||||
.acquire => {
|
||||
@@ -109,6 +124,7 @@ pub inline fn barrier(comptime ordering: std.builtin.AtomicOrder) void {
|
||||
asm volatile ("" ::: "memory");
|
||||
}
|
||||
|
||||
/// Set the CPU's thread pointer to some value.
|
||||
pub inline fn setThreadPointer(tp: usize) void {
|
||||
asm volatile ("mv tp, %[tp]"
|
||||
:
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
//! Simple bump allocator arena.
|
||||
|
||||
const physMemory = @import("mem/phys.zig");
|
||||
const log = @import("debug.zig").log;
|
||||
const mem = @import("mem.zig");
|
||||
|
||||
/// Bump allocator implementation.
|
||||
pub const Arena = struct {
|
||||
physBase: mem.PhysicalAddress,
|
||||
capacity: usize,
|
||||
len: usize,
|
||||
|
||||
/// Creates a new `Arena` of given `cap`acity.
|
||||
///
|
||||
/// Requires initialized physical memory management.
|
||||
pub fn init(cap: usize) ?Arena {
|
||||
const physBase = physMemory.alloc_pages(cap / mem.vmm.PAGE_SIZE) orelse return null;
|
||||
return .{ .physBase = physBase, .capacity = cap, .len = 0 };
|
||||
}
|
||||
|
||||
/// Allocates an object of type `T` within this arena.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the arena runs out of memory.
|
||||
pub fn create(self: *@This(), comptime T: type) *T {
|
||||
if (self.len + @sizeOf(T) > self.capacity) {
|
||||
log.panic("Out of memory. Cannot allocate {} bytes", .{@sizeOf(T)});
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
//! Kernel debug output and logging functions.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
fn dummyWrite(_: u8) void {}
|
||||
|
||||
/// The main method of kernel logging.
|
||||
pub const log = struct {
|
||||
/// Level to emit log records at.
|
||||
pub const Level = enum {
|
||||
/// Debug.
|
||||
debug,
|
||||
/// Info.
|
||||
info,
|
||||
/// Warning.
|
||||
warn,
|
||||
/// Error/Fatal.
|
||||
err,
|
||||
};
|
||||
|
||||
@@ -21,45 +29,55 @@ pub const log = struct {
|
||||
return data.len;
|
||||
}
|
||||
|
||||
/// Replaces the function to print debug bytes with a new one.
|
||||
pub fn setWriteFn(f: *const fn (u8) void) void {
|
||||
writeFn = f;
|
||||
}
|
||||
|
||||
/// Emit an `info`-level log record.
|
||||
pub inline fn info(comptime format: []const u8, args: anytype) void {
|
||||
writeln(.info, format, args);
|
||||
}
|
||||
|
||||
/// Emit a `debug`-level log record.
|
||||
pub inline fn debug(comptime format: []const u8, args: anytype) void {
|
||||
writeln(.debug, format, args);
|
||||
}
|
||||
|
||||
/// Emit a `warn`-level log record.
|
||||
pub inline fn warn(comptime format: []const u8, args: anytype) void {
|
||||
writeln(.warn, format, args);
|
||||
}
|
||||
|
||||
/// Emit a `err`-level log record.
|
||||
pub inline fn err(comptime format: []const u8, args: anytype) void {
|
||||
writeln(.err, format, args);
|
||||
}
|
||||
|
||||
/// Write raw byte data into the debugging output.
|
||||
pub fn writeRaw(data: []const u8) void {
|
||||
_ = writeWrapperFn(0, data) catch return;
|
||||
}
|
||||
|
||||
/// Write a formatted string (without logging prefix/suffix/newline) into the debugging output.
|
||||
pub fn write(comptime format: []const u8, args: anytype) void {
|
||||
writer.print(format, args) catch return;
|
||||
}
|
||||
|
||||
/// Write a formatted log record into the debugging output.
|
||||
pub fn writeln(comptime level: Level, comptime format: []const u8, args: anytype) void {
|
||||
const prefix = comptime logPrefix(level);
|
||||
const suffix = comptime logSuffix(level);
|
||||
writer.print(prefix ++ format ++ suffix ++ "\r\n", args) catch return;
|
||||
}
|
||||
|
||||
/// Helper function to emit a `Not yet implemented` message and panic.
|
||||
pub fn todo(comptime msg: []const u8, args: anytype) noreturn {
|
||||
err("Not yet implemented: " ++ msg, args);
|
||||
@panic("Not yet implemented");
|
||||
}
|
||||
|
||||
/// Helper function to emit a `PANIC` message and panic.
|
||||
pub fn panic(comptime msg: []const u8, args: anytype) noreturn {
|
||||
err("PANIC: " ++ msg, args);
|
||||
@panic("Explicit kernel panic");
|
||||
|
||||
+15
-2
@@ -1,13 +1,17 @@
|
||||
// export const _ = @import("arch/riscv64/boot.zig").rv64BspLowerEntry;
|
||||
pub const arch = @import("arch.zig").arch();
|
||||
//! zing microkernel.
|
||||
|
||||
pub const arch = @import("arch.zig");
|
||||
pub const mem = @import("mem.zig");
|
||||
pub const debug = @import("debug.zig");
|
||||
pub const arena = @import("arena.zig");
|
||||
pub const thread = @import("thread.zig");
|
||||
pub const util = @import("util.zig");
|
||||
pub const sync = @import("sync.zig");
|
||||
|
||||
pub const log = debug.log;
|
||||
pub const vmm = mem.vmm;
|
||||
|
||||
/// If set to `true`, will emit log messages for physical page allocations/deallocations.
|
||||
pub const TRACE_PHYSICAL_ALLOCATOR: bool = false;
|
||||
|
||||
const std = @import("std");
|
||||
@@ -25,6 +29,14 @@ noinline fn f1(arg: usize, c: usize) void {
|
||||
log.write("\x1B[1;{}H{}", .{ arg + 1, (c + arg) % 10 });
|
||||
}
|
||||
|
||||
/// Platform-independent entry point for the kernel.
|
||||
///
|
||||
/// # Invariants
|
||||
///
|
||||
/// The following preconditions must be met to invoke this function:
|
||||
///
|
||||
/// * Physical memory must be initialized.
|
||||
/// * (optional) Logging should be set up.
|
||||
pub export fn kernel_main() callconv(.C) noreturn {
|
||||
log.write("\x1B[2J", .{});
|
||||
var a = arena.Arena.init(256 * 0x1000) orelse @panic("Could not setup kernel arena");
|
||||
@@ -39,6 +51,7 @@ pub export fn kernel_main() callconv(.C) noreturn {
|
||||
thread.enter();
|
||||
}
|
||||
|
||||
/// Kernel's panic handler
|
||||
pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, return_address: ?usize) noreturn {
|
||||
_ = error_return_trace;
|
||||
|
||||
|
||||
+22
@@ -1,3 +1,5 @@
|
||||
//! Platform-independent memory management functions.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const vmm = @import("mem/vmm.zig");
|
||||
@@ -5,18 +7,30 @@ pub const phys = @import("mem/phys.zig");
|
||||
|
||||
pub const TranslationLevel = vmm.TranslationLevel;
|
||||
|
||||
/// A representation for physical address.
|
||||
pub const PhysicalAddress = packed struct(u64) {
|
||||
raw: u64,
|
||||
|
||||
/// NULL/zero physical address constant.
|
||||
pub const NULL: @This() = .{ .raw = 0 };
|
||||
|
||||
/// Base address to add to a given `PhysicalAddress` in order to "virtualize" it.
|
||||
pub var gVirtualizeBase: usize = 0;
|
||||
/// Maximum `PhysicalAddress` that can be represented as a virtual address.
|
||||
pub var gVirtualizeSize: usize = 0;
|
||||
|
||||
/// Adds an `offset` to this `PhysicalAddress`
|
||||
pub fn add(self: @This(), offset: usize) @This() {
|
||||
return .{ .raw = self.raw + @as(u64, @intCast(offset)) };
|
||||
}
|
||||
|
||||
/// "Virtualizes" a `PhysicalAddress` by turning it into a virtual address representation of
|
||||
/// the memory pointed to by this physical address.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the physical address points to a memory that cannot be represented by a virtual
|
||||
/// address.
|
||||
pub fn virtualize(self: @This()) usize {
|
||||
if (self.raw > gVirtualizeSize) {
|
||||
@panic("Physical address out of virtualize bounds");
|
||||
@@ -25,6 +39,12 @@ pub const PhysicalAddress = packed struct(u64) {
|
||||
return self.raw + gVirtualizeBase;
|
||||
}
|
||||
|
||||
/// "De-virtualizes" a previously "virtualized" physical address by mapping it back into its
|
||||
/// physical form.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the virtual address provided is outside of virtualizable memory range.
|
||||
pub fn from_virtualized(virt: usize) @This() {
|
||||
if ((virt < gVirtualizeBase) || (virt - gVirtualizeBase > gVirtualizeSize)) {
|
||||
@panic("Invalid virtualized physical address");
|
||||
@@ -34,6 +54,8 @@ pub const PhysicalAddress = packed struct(u64) {
|
||||
}
|
||||
};
|
||||
|
||||
/// Helper function to format a byte quantity into a human-readable size.
|
||||
/// Writes its result to the `buffer` provided and returns a pointer to it.
|
||||
pub fn formatSize(buffer: []u8, size: u64) []const u8 {
|
||||
const KIBI: u64 = 1024;
|
||||
const MIBI: u64 = KIBI * 1024;
|
||||
|
||||
+50
-5
@@ -1,3 +1,5 @@
|
||||
//! Physical memory management implementation.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const kernel = @import("../kernel.zig");
|
||||
@@ -9,13 +11,22 @@ const sync = @import("../sync.zig");
|
||||
const Range = @import("../util/range.zig").Range;
|
||||
const Spinlock = sync.IrqSafeSpinlock;
|
||||
|
||||
pub const MemoryRegion = struct { name: []const u8, range: Range(u64) };
|
||||
/// Represents a single region of physical memory (reserved or available).
|
||||
pub const MemoryRegion = struct {
|
||||
/// Name string, used to represent where the memory comes from.
|
||||
name: []const u8,
|
||||
/// Byte range of the memory region.
|
||||
range: Range(u64),
|
||||
};
|
||||
|
||||
const Page = extern struct {
|
||||
/// Represents information about a single managed physical memory page.
|
||||
pub const Page = extern struct {
|
||||
/// Reference count of the page. Zero means the page is not allocated.
|
||||
refcount: u32 = 0,
|
||||
unused: [3]u32 = undefined,
|
||||
|
||||
fn isUsed(self: *const @This()) bool {
|
||||
/// Returns `true` if the page is allocated/used.
|
||||
pub fn isUsed(self: *const @This()) bool {
|
||||
return self.refcount != 0;
|
||||
}
|
||||
|
||||
@@ -116,11 +127,21 @@ var gReservedRegions: std.BoundedArray(MemoryRegion, 16) = .{};
|
||||
var gPhysicalMemoryLock = Spinlock{};
|
||||
var gPhysicalMemory = PhysicalMemoryManager{ .pageArray = undefined };
|
||||
|
||||
/// Adds an available memory region to the list.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Only meaningful to call before calling `init()`.
|
||||
pub fn addMemoryRegion(name: []const u8, base: u64, size: u64) void {
|
||||
log.info("Memory: '{s}', base 0x{x}, size 0x{x}", .{ name, base, size });
|
||||
gMemoryRegions.append(.{ .name = name, .range = .{ .start = base, .len = size } }) catch @panic("memory regions overflow");
|
||||
}
|
||||
|
||||
/// Adds an reserved memory region to the list.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Only meaningful to call before calling `init()`.
|
||||
pub fn addReservedRegion(name: []const u8, base: u64, size: u64) void {
|
||||
log.info("Reserved: '{s}', base 0x{x}, size 0x{x}", .{ name, base, size });
|
||||
gReservedRegions.append(.{ .name = name, .range = .{ .start = base, .len = size } }) catch @panic("reserved regions overflow");
|
||||
@@ -175,6 +196,12 @@ fn allocPageArray(pageCount: usize) []Page {
|
||||
@panic("TODO");
|
||||
}
|
||||
|
||||
/// Initializes the physical memory management.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// Calls to `add***Region()` functions have no meaning past this point, so all the memory
|
||||
/// present in the system, along with memory reservations, should be added **prior** to this point.
|
||||
pub fn init() void {
|
||||
var memoryStart: u64 = std.math.maxInt(u64);
|
||||
var memoryEnd: u64 = std.math.minInt(u64);
|
||||
@@ -189,7 +216,8 @@ pub fn init() void {
|
||||
}
|
||||
|
||||
const memoryPages = (memoryEnd - memoryStart) / vmm.PAGE_SIZE; // == bitmap bits required
|
||||
const pageArrayPages = (memoryPages + PhysicalMemoryManager.RECORDS_PER_PAGE - 1) / PhysicalMemoryManager.RECORDS_PER_PAGE;
|
||||
const pageArrayPages = (memoryPages + PhysicalMemoryManager.RECORDS_PER_PAGE - 1) //
|
||||
/ PhysicalMemoryManager.RECORDS_PER_PAGE;
|
||||
|
||||
const pageArray = allocPageArray(pageArrayPages);
|
||||
var availablePages: usize = 0;
|
||||
@@ -232,6 +260,7 @@ fn trace_free(page: mem.PhysicalAddress) void {
|
||||
log.debug("free 0x{x}", .{page.raw});
|
||||
}
|
||||
|
||||
/// Allocates a single 4KiB physical memory page.
|
||||
pub fn alloc_page() ?mem.PhysicalAddress {
|
||||
gPhysicalMemoryLock.lock();
|
||||
defer gPhysicalMemoryLock.release();
|
||||
@@ -242,6 +271,7 @@ pub fn alloc_page() ?mem.PhysicalAddress {
|
||||
return page;
|
||||
}
|
||||
|
||||
/// Allocates a set of `count` contiguous 4KiB pages.
|
||||
pub fn alloc_pages(count: usize) ?mem.PhysicalAddress {
|
||||
gPhysicalMemoryLock.lock();
|
||||
defer gPhysicalMemoryLock.release();
|
||||
@@ -252,6 +282,13 @@ pub fn alloc_pages(count: usize) ?mem.PhysicalAddress {
|
||||
return pages;
|
||||
}
|
||||
|
||||
/// Deallocates a single page of physical memory.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// * If the `page` does not represent a valid managed page.
|
||||
/// * If the `page` was not previously allocated.
|
||||
/// * If a double free is detected.
|
||||
pub fn free_page(page: mem.PhysicalAddress) void {
|
||||
if (comptime kernel.TRACE_PHYSICAL_ALLOCATOR) {
|
||||
trace_free(page);
|
||||
@@ -261,7 +298,15 @@ pub fn free_page(page: mem.PhysicalAddress) void {
|
||||
gPhysicalMemory.free_page(page);
|
||||
}
|
||||
|
||||
// NOTE: Physical memory lock must be held
|
||||
/// Returns a `Page` struct representing the given `page`.
|
||||
///
|
||||
/// # Invariants
|
||||
///
|
||||
/// The physical memory lock must be held.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the `page` does not represent a valid managed page.
|
||||
pub fn get_page(page: mem.PhysicalAddress) *Page {
|
||||
return gPhysicalMemory.get_page(page);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
//! Platform-independent virtual memory management definitions.
|
||||
|
||||
/// Page size is 4KiB on all platforms.
|
||||
pub const PAGE_SIZE: usize = 0x1000;
|
||||
|
||||
/// Helper function to construct a "Translation Level" struct type from a bit shift.
|
||||
pub fn TranslationLevel(comptime shift: usize) type {
|
||||
return struct {
|
||||
pub const SHIFT: usize = shift;
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
//! Kernel synchronization primitives.
|
||||
|
||||
const std = @import("std");
|
||||
const arch = @import("kernel.zig").arch;
|
||||
|
||||
/// Basic spinlock implementation
|
||||
// TODO not actually IRQ safe, lol.
|
||||
pub const IrqSafeSpinlock = struct {
|
||||
state: std.atomic.Value(bool) = .{ .raw = false },
|
||||
|
||||
/// Acquires a lock over `self`. Returns `false` if the lock is already held by someone else.
|
||||
pub fn tryLock(self: *@This()) bool {
|
||||
return self.state.cmpxchgStrong(false, true, .acquire, .monotonic) orelse false;
|
||||
}
|
||||
|
||||
/// Acquires a lock over `self`. Will block until a lock can be acquired.
|
||||
pub fn lock(self: *@This()) void {
|
||||
while (!self.tryLock()) {
|
||||
arch.spinHint();
|
||||
}
|
||||
}
|
||||
|
||||
/// Releases a lock over `self`.
|
||||
pub fn release(self: *@This()) void {
|
||||
self.state.store(false, .release);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Thread management utilities and data structures.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const arena = @import("arena.zig");
|
||||
@@ -5,13 +7,19 @@ const arch = @import("kernel.zig").arch;
|
||||
const log = @import("debug.zig").log;
|
||||
const mem = @import("mem.zig");
|
||||
|
||||
/// Per-CPU thread queue structure.
|
||||
pub const Queue = struct {
|
||||
/// Idle task context. Used when there are no other tasks running.
|
||||
idle: arch.Context,
|
||||
/// Current thread pointer.
|
||||
current: ?*Thread = null,
|
||||
/// Thread queue head pointer.
|
||||
head: ?*Thread = null,
|
||||
|
||||
/// Pointer to this CPU's thread queue.
|
||||
pub threadlocal var thisCpu: ?*Queue = null;
|
||||
|
||||
/// Sets up a thread queue for the current CPU.
|
||||
pub fn initThisCpu(a: *arena.Arena) void {
|
||||
const idle = arch.Context.idle();
|
||||
const q = a.create(Queue);
|
||||
@@ -19,6 +27,7 @@ pub const Queue = struct {
|
||||
thisCpu = q;
|
||||
}
|
||||
|
||||
/// Enters a task on this CPU.
|
||||
pub fn enter(self: *@This()) noreturn {
|
||||
if (self.head) |gt| {
|
||||
self.current = gt;
|
||||
@@ -29,6 +38,7 @@ pub const Queue = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Yields CPU to the next available task.
|
||||
pub fn yield(self: *@This()) void {
|
||||
if (self.current) |curr| {
|
||||
// Switching from thread
|
||||
@@ -55,6 +65,7 @@ pub const Queue = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an available task to this queue.
|
||||
pub fn enqueue(self: *@This(), t: *Thread) void {
|
||||
if (self.head) |gt| {
|
||||
t.next = gt;
|
||||
@@ -69,13 +80,19 @@ pub const Queue = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// Represents a single execution thread.
|
||||
pub const Thread = struct {
|
||||
/// Arena.
|
||||
allocator: *arena.Arena,
|
||||
/// Architecture-specific task context.
|
||||
archContext: arch.Context,
|
||||
|
||||
/// Next thread in the queue.
|
||||
next: ?*Thread = null,
|
||||
/// Previous thread in the queue.
|
||||
prev: ?*Thread = null,
|
||||
|
||||
/// Creates a new (kernel) thread with given `pc` (entry point) and `arg`ument.
|
||||
pub fn create(a: *arena.Arena, pc: usize, arg: usize) *Thread {
|
||||
const thread = a.create(Thread);
|
||||
thread.* = .{
|
||||
@@ -85,23 +102,34 @@ pub const Thread = struct {
|
||||
return thread;
|
||||
}
|
||||
|
||||
/// Enters the thread, does not return.
|
||||
pub fn enter(self: *@This()) noreturn {
|
||||
self.archContext.enter();
|
||||
}
|
||||
|
||||
/// Switches from `from` to `self` thread.
|
||||
pub fn switchFrom(self: *@This(), from: *@This()) void {
|
||||
self.archContext.switchFrom(&from.archContext);
|
||||
}
|
||||
};
|
||||
|
||||
/// Helper data structure to represent kernel stacks in task contexts.
|
||||
pub fn KStack(comptime SIZE: usize) type {
|
||||
return extern struct {
|
||||
// Has to be at exactly offset 0x00, used in assembly
|
||||
/// Stack pointer. Aliases `data`.
|
||||
sp: *usize,
|
||||
|
||||
/// Stack data represented as a slice of `SIZE` machine-sized words.
|
||||
data: *[SIZE]usize,
|
||||
/// Physical base address at which the stack is allocated.
|
||||
physicalBase: mem.PhysicalAddress,
|
||||
|
||||
/// Allocates a new kernel stack.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics on Out-of-Memory condition. TODO Fix this.
|
||||
pub fn create() @This() {
|
||||
const physicalBase = mem.phys.alloc_pages(SIZE * @sizeOf(usize) / 0x1000) orelse @panic("OOM");
|
||||
const ptr = @as(*[SIZE]usize, @ptrFromInt(physicalBase.virtualize()));
|
||||
@@ -109,6 +137,11 @@ pub fn KStack(comptime SIZE: usize) type {
|
||||
return .{ .data = ptr, .physicalBase = physicalBase, .sp = @ptrFromInt(@intFromPtr(&ptr[0]) + SIZE * @sizeOf(usize)) };
|
||||
}
|
||||
|
||||
/// Pushes a machine-sized word onto the stack.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if a push would overflow the stack.
|
||||
pub fn push(self: *@This(), value: usize) void {
|
||||
if (self.sp == &self.data[0]) {
|
||||
@panic("KStack overflow");
|
||||
@@ -119,14 +152,17 @@ pub fn KStack(comptime SIZE: usize) type {
|
||||
};
|
||||
}
|
||||
|
||||
/// Adds a thread to some CPU queue for execution.
|
||||
pub fn enqueue(t: *Thread) void {
|
||||
Queue.thisCpu.?.enqueue(t);
|
||||
}
|
||||
|
||||
/// Enters thread execution on the current CPU.
|
||||
pub fn enter() noreturn {
|
||||
Queue.thisCpu.?.enter();
|
||||
}
|
||||
|
||||
/// Yields this CPU's execution to a next thread.
|
||||
pub fn yield() void {
|
||||
Queue.thisCpu.?.yield();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
pub const dtb = @import("util/dtb.zig");
|
||||
pub const range = @import("util/range.zig");
|
||||
+70
-1
@@ -1,3 +1,5 @@
|
||||
//! Flattened Device Tree manipulation utilities
|
||||
|
||||
const mem = @import("../mem.zig");
|
||||
const log = @import("../debug.zig").log;
|
||||
const std = @import("std");
|
||||
@@ -17,7 +19,14 @@ const fdt_header = extern struct {
|
||||
size_dt_struct: u32,
|
||||
};
|
||||
|
||||
const fdt_op = enum(u32) { FDT_BEGIN_NODE = 1, FDT_END_NODE = 2, FDT_PROP = 3, FDT_NOP = 4, FDT_END = 9, _ };
|
||||
const fdt_op = enum(u32) {
|
||||
FDT_BEGIN_NODE = 1,
|
||||
FDT_END_NODE = 2,
|
||||
FDT_PROP = 3,
|
||||
FDT_NOP = 4,
|
||||
FDT_END = 9,
|
||||
_,
|
||||
};
|
||||
|
||||
const fdt_prop = extern struct {
|
||||
len: u32,
|
||||
@@ -38,26 +47,37 @@ const FdtTag = union(enum) {
|
||||
end,
|
||||
};
|
||||
|
||||
/// Represents a memory region described by some device tree node.
|
||||
/// Can either mean a range of reserved memory or a range of available memory depending on the
|
||||
/// function used to obtain it.
|
||||
pub const FdtMemoryRegion = struct {
|
||||
name: []const u8,
|
||||
base: u64,
|
||||
size: u64,
|
||||
};
|
||||
|
||||
/// Represents a single node within a device tree.
|
||||
pub const FdtNode = struct {
|
||||
/// Parent device tree pointer.
|
||||
fdt: *const Fdt,
|
||||
/// Offset within the device tree raw data blob.
|
||||
off: usize,
|
||||
/// Name string of the node.
|
||||
name: []const u8,
|
||||
/// Depth of the node within the tree.
|
||||
depth: usize,
|
||||
|
||||
/// Returns an iterator over the node's properties.
|
||||
pub fn propIterator(self: *const @This()) FdtNodePropIterator {
|
||||
return .{ .node = self, .tagIter = self.fdt.tagIteratorAt(self.off) };
|
||||
}
|
||||
|
||||
/// Returns an iterator over the node's children.
|
||||
pub fn children(self: *const @This()) FdtNodeIterator {
|
||||
return .{ .tagIter = self.fdt.tagIteratorAt(self.off), .depth = self.depth + 1, .depthLower = self.depth };
|
||||
}
|
||||
|
||||
/// Looks up a child with given `name` within the node.
|
||||
pub fn child(self: *const @This(), name: []const u8) ?FdtNode {
|
||||
var iter = self.children();
|
||||
while (iter.next()) |c| {
|
||||
@@ -68,6 +88,7 @@ pub const FdtNode = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Looks up a property with given `name` within the node.
|
||||
pub fn property(self: *const @This(), name: []const u8) ?FdtNodeProp {
|
||||
var propIter = self.propIterator();
|
||||
while (propIter.next()) |prop| {
|
||||
@@ -79,24 +100,33 @@ pub const FdtNode = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// Represents a property of some node within a device tree.
|
||||
pub const FdtNodeProp = struct {
|
||||
/// Node to which this property belongs.
|
||||
node: *const FdtNode,
|
||||
/// Name string.
|
||||
name: []const u8,
|
||||
/// Value of the property represented as a raw byte slice.
|
||||
value: []const u8,
|
||||
|
||||
/// Interprets the property's value as a list of strings.
|
||||
pub inline fn getStringArray(self: *const @This()) FdtStringArrayIterator {
|
||||
return .{ .prop = self };
|
||||
}
|
||||
|
||||
/// Interprets the property's value as a single string.
|
||||
pub inline fn getString(self: *const @This()) []const u8 {
|
||||
var sa = self.getStringArray();
|
||||
return sa.next() orelse "";
|
||||
}
|
||||
|
||||
/// Returns the length of the property in full 32-bit cells.
|
||||
pub inline fn lenU32(self: *const @This()) usize {
|
||||
return self.value.len / @sizeOf(u32);
|
||||
}
|
||||
|
||||
/// Interprets the property's value as an array of 32-bit cells and returns a cell at a given
|
||||
/// index.
|
||||
pub fn getU32(self: *const @This(), index: usize) ?u32 {
|
||||
if (index >= self.lenU32()) {
|
||||
return null;
|
||||
@@ -108,6 +138,15 @@ pub const FdtNodeProp = struct {
|
||||
return std.mem.bigToNative(u32, @as(*const u32, @ptrCast(@alignCast(&self.value[index * 4]))).*);
|
||||
}
|
||||
|
||||
/// Interprets the property's value as an array of tuples of cells with sizes described by
|
||||
/// `sizes` and reads a single such tuple into the `output`.
|
||||
///
|
||||
/// Returns `true` if a whole tuple can be read at given `index`.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// * `index` parameter means a 32-bit cell index, not a tuple index.
|
||||
/// * Tuple length is assumed to be `@min(output.len, sizes.len)`.
|
||||
pub fn readCells(self: *const @This(), index: usize, output: []u64, sizes: []const usize) bool {
|
||||
const count = @min(output.len, sizes.len);
|
||||
const len = self.lenU32();
|
||||
@@ -138,6 +177,7 @@ pub const FdtNodeProp = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// An iterator over a string list property value.
|
||||
pub const FdtStringArrayIterator = struct {
|
||||
prop: *const FdtNodeProp,
|
||||
off: usize = 0,
|
||||
@@ -154,6 +194,7 @@ pub const FdtStringArrayIterator = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// An iterator over available memory regions described by a device tree.
|
||||
pub const FdtMemoryRegionIterator = struct {
|
||||
nodeIter: FdtNodeIterator,
|
||||
cellSizes: [2]usize,
|
||||
@@ -178,6 +219,7 @@ pub const FdtMemoryRegionIterator = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// An iterator over a device tree's node properties.
|
||||
pub const FdtNodePropIterator = struct {
|
||||
node: *const FdtNode,
|
||||
tagIter: FdtTagIterator,
|
||||
@@ -213,6 +255,7 @@ pub const FdtNodePropIterator = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// An iterator over a device tree's nodes.
|
||||
pub const FdtNodeIterator = struct {
|
||||
tagIter: FdtTagIterator,
|
||||
depth: usize = 0,
|
||||
@@ -246,6 +289,7 @@ pub const FdtNodeIterator = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// An iterator over a device tree's raw tags.
|
||||
pub const FdtTagIterator = struct {
|
||||
fdt: *const Fdt,
|
||||
raw: []const u8,
|
||||
@@ -295,13 +339,22 @@ pub const FdtTagIterator = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// An error returned by the FDT manipulation functions.
|
||||
pub const FdtError = error{invalid_magic};
|
||||
|
||||
/// Magic number describing a valid device tree (Big-Endian).
|
||||
pub const FDT_MAGIC: u32 = 0xD00DFEED;
|
||||
|
||||
/// Represents a Flattened Device Tree.
|
||||
pub const Fdt = struct {
|
||||
/// Raw device tree bytes, including the header.
|
||||
bytes: []const u8,
|
||||
|
||||
/// Constructs a FDT struct from a physical address.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * `invalid_magic` if the address provided does not have a valid magic number in its header.
|
||||
pub fn fromPhysicalAddress(phys: mem.PhysicalAddress) FdtError!@This() {
|
||||
const virt = phys.virtualize();
|
||||
const hdr = @as(*const fdt_header, @ptrFromInt(virt));
|
||||
@@ -313,6 +366,7 @@ pub const Fdt = struct {
|
||||
return .{ .bytes = x[0..totalsize] };
|
||||
}
|
||||
|
||||
/// Returns the header pointer of this device tree.
|
||||
pub fn header(self: *const @This()) *const fdt_header {
|
||||
return @ptrCast(@alignCast(&self.bytes[0]));
|
||||
}
|
||||
@@ -322,18 +376,27 @@ pub const Fdt = struct {
|
||||
return self.bytes[off..];
|
||||
}
|
||||
|
||||
/// Returns an iterator over the device tree's raw tags.
|
||||
pub fn tagIterator(self: *const @This()) FdtTagIterator {
|
||||
return self.tagIteratorAt(0);
|
||||
}
|
||||
|
||||
/// Returns an iterator over the device tree's raw tags at specific byte offset.
|
||||
pub fn tagIteratorAt(self: *const @This(), off: usize) FdtTagIterator {
|
||||
return .{ .raw = self.data(), .fdt = self, .off = off };
|
||||
}
|
||||
|
||||
/// Returns an iterator over the device tree's nodes.
|
||||
pub fn nodeIterator(self: *const @This()) FdtNodeIterator {
|
||||
return .{ .tagIter = self.tagIterator() };
|
||||
}
|
||||
|
||||
/// Returns the root node of this device tree.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the device tree does not have a root node (which means the device tree blob is
|
||||
/// malformed and the OS shouldn't be running anyway).
|
||||
pub fn rootNode(self: *const @This()) FdtNode {
|
||||
var nodeIter = self.nodeIterator();
|
||||
while (nodeIter.next()) |node| {
|
||||
@@ -344,6 +407,7 @@ pub const Fdt = struct {
|
||||
@panic("Unreachable code");
|
||||
}
|
||||
|
||||
/// Returns an iterator over available memory regions described by the device tree.
|
||||
pub fn memoryRegionIterator(self: *const @This()) FdtMemoryRegionIterator {
|
||||
const r = self.rootNode();
|
||||
const addressCells = if (r.property("#address-cells")) |o| (if (o.getU32(0)) |p| p else 1) else 1;
|
||||
@@ -360,12 +424,15 @@ pub const Fdt = struct {
|
||||
return @ptrCast(self.bytes[off .. off + len]);
|
||||
}
|
||||
|
||||
/// Returns a string slice at given byte offset into the device tree's strings section.
|
||||
pub fn stringAt(self: *const @This(), off: usize) []const u8 {
|
||||
const raw = self.stringData()[off..];
|
||||
const len = std.mem.len(raw);
|
||||
return @ptrCast(raw[0..len]);
|
||||
}
|
||||
|
||||
/// Adds information about the available and reserved memory regions described in this device
|
||||
/// tree into the physical memory management structures.
|
||||
pub fn addPhysicalMemoryToSystem(self: *const @This()) void {
|
||||
var memoryRegions = self.memoryRegionIterator();
|
||||
var cells: [2]u64 = undefined;
|
||||
@@ -387,6 +454,7 @@ pub const Fdt = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Looks up a `/slash/separated/path` inside the device tree.
|
||||
pub fn find(self: *const @This(), path: []const u8) ?FdtNode {
|
||||
const trimmedPath = std.mem.trimLeft(u8, path, "/");
|
||||
var pathElements = std.mem.splitScalar(u8, trimmedPath, '/');
|
||||
@@ -492,6 +560,7 @@ pub const Fdt = struct {
|
||||
log.write("}},\r\n", .{});
|
||||
}
|
||||
|
||||
/// Dumps the structured device tree into the log output.
|
||||
pub fn dump(self: *const @This()) void {
|
||||
dump_node(&self.rootNode(), 0);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
//! Utilities for manipulating ranges.
|
||||
|
||||
/// Non-inclusive range type over `T`.
|
||||
pub fn Range(comptime T: type) type {
|
||||
return struct {
|
||||
/// Range start.
|
||||
start: T,
|
||||
/// Range length.
|
||||
len: T,
|
||||
|
||||
/// Returns `start + len` of the range.
|
||||
pub fn end(self: *const @This()) T {
|
||||
return self.start + self.len;
|
||||
}
|
||||
|
||||
/// Returns a range representing an intersections of `self` with an`other` range.
|
||||
pub fn intersect(self: *const @This(), other: *const @This()) ?Range(T) {
|
||||
if (self.start < other.start) {
|
||||
const p = other.start - self.start;
|
||||
|
||||
Reference in New Issue
Block a user