doc: add some documentation on platform-independent modules

This commit is contained in:
2025-03-18 13:09:02 +02:00
parent 500a99832c
commit 32b324b132
14 changed files with 270 additions and 23 deletions
+11 -14
View File
@@ -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
View File
@@ -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]"
:
+11
View File
@@ -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)});
+18
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
+4
View File
@@ -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;
+7
View File
@@ -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);
}
+36
View File
@@ -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();
}
+2
View File
@@ -0,0 +1,2 @@
pub const dtb = @import("util/dtb.zig");
pub const range = @import("util/range.zig");
+70 -1
View File
@@ -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);
}
View File
+7
View File
@@ -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;