diff --git a/build.zig b/build.zig index e4723b1..0d5dc97 100644 --- a/build.zig +++ b/build.zig @@ -1,72 +1,86 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const optimize = .Debug; - const target = b.standardTargetOptions(.{ - .default_target = .{ - .cpu_arch = .riscv64, - .os_tag = .freestanding, - .abi = .none, +fn insertFakeLinuxImageHeader(step: *std.Build.Step, opts: std.Build.Step.MakeOptions) anyerror!void { + const RISCV_MAGIC1 = "RISCV\x00\x00\x00"; + const RISCV_MAGIC2 = "RSC\x05"; + + const elf = std.elf; + + var ehdr: elf.Ehdr = undefined; + + var file = try std.fs.cwd().openFile("zig-out/bin/kernel", .{ .mode = .read_write }); + const stat = try file.stat(); + _ = try file.readAll(std.mem.asBytes(&ehdr)); + + for (0..ehdr.e_phnum) |i| { + var phdr: elf.Phdr = undefined; + var data: [64]u8 = undefined; + + _ = try file.preadAll(std.mem.asBytes(&phdr), ehdr.e_phoff + i * ehdr.e_phentsize); + + if (phdr.p_type != elf.PT_LOAD) { + continue; } - }); - const kernel_module = b.addModule("kernel", .{ - .optimize = optimize, - .target = target, - .pic = true, - .red_zone = false, - .code_model = .medium, - .root_source_file = b.path("src/kernel.zig") - }); + _ = try file.preadAll(&data, phdr.p_offset); - const kernel = b.addExecutable(.{ - .name = "kernel", - .root_module = kernel_module, - .pic = true - }); + if (std.mem.eql(u8, RISCV_MAGIC1, data[48..56]) and std.mem.eql(u8, RISCV_MAGIC2, data[56..60])) { + const size: u64 = stat.size; + try file.pwriteAll(std.mem.asBytes(&size), phdr.p_offset + 16); + break; + } + } + + _ = step; + _ = opts; +} + +pub fn build(b: *std.Build) anyerror!void { + const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast }); + const target = b.standardTargetOptions(.{ .default_target = .{ + .cpu_arch = .riscv64, + .os_tag = .freestanding, + .abi = .none, + } }); + + const kernel_module = b.addModule("kernel", .{ .optimize = optimize, .target = target, .pic = true, .red_zone = false, .code_model = .medium, .root_source_file = b.path("src/kernel.zig") }); + + const kernel = b.addExecutable(.{ .name = "kernel", .root_module = kernel_module, .pic = true }); kernel.pie = true; - kernel.entry = .{.symbol_name = "__rv64_entry"}; + kernel.entry = .{ .symbol_name = "__rv64_entry" }; kernel.setLinkerScript(b.path("etc/riscv64-unknown-none.ld")); kernel.addCSourceFiles(.{ - .files = &.{ - "src/arch/riscv64/entry.S" - }, + .files = &.{"src/arch/riscv64/entry.S"}, .flags = &.{}, }); b.installArtifact(kernel); - const elf2bin = b.addSystemCommand(&.{ - "llvm-objcopy", - "-O", "binary", - "zig-out/bin/kernel", - "zig-out/bin/kernel.bin" + const fakeLinuxHeader: *std.Build.Step = try b.allocator.create(std.Build.Step); + fakeLinuxHeader.* = std.Build.Step.init(.{ + .id = std.Build.Step.Id.custom, + .name = "insert fake linux header", + .owner = kernel.step.owner, + .makeFn = insertFakeLinuxImageHeader, }); + const elf2bin = b.addSystemCommand(&.{ "llvm-objcopy", "-O", "binary", "zig-out/bin/kernel", "zig-out/bin/kernel.bin" }); + // TODO QEMU binary override const qemu_info = switch (target.result.cpu.arch) { .riscv64 => .{ "qemu-system-riscv64", "rv64" }, else => unreachable, }; - const qemu_cmd = b.addSystemCommand(&.{ - qemu_info[0], - "-M", "virt", - "-kernel", "zig-out/bin/kernel.bin", - "-m", "256M", - "-cpu", qemu_info[1], - "-serial", "mon:stdio", - "-display", "none" - }); + const qemu_cmd = b.addSystemCommand(&.{ qemu_info[0], "-M", "virt", "-kernel", "zig-out/bin/kernel.bin", "-m", "256M", "-cpu", qemu_info[1], "-serial", "mon:stdio", "-display", "none" }); if (target.result.cpu.arch == .riscv64) { - qemu_cmd.addArgs(&.{ - "-bios", "etc/boot/rv64_fw_jump.bin" - }); + qemu_cmd.addArgs(&.{ "-bios", "etc/boot/rv64_fw_jump.bin" }); } - elf2bin.step.dependOn(b.getInstallStep()); + fakeLinuxHeader.dependOn(b.getInstallStep()); + elf2bin.step.dependOn(fakeLinuxHeader); qemu_cmd.step.dependOn(&elf2bin.step); if (b.args) |args| qemu_cmd.addArgs(args); const run_step = b.step("run", "Start the OS in qemu"); diff --git a/src/arch/riscv64.zig b/src/arch/riscv64.zig index 08d0289..4ef7940 100644 --- a/src/arch/riscv64.zig +++ b/src/arch/riscv64.zig @@ -1,4 +1,7 @@ const boot = @import("riscv64/boot.zig"); +const regs = @import("riscv64/regs.zig"); +const std = @import("std"); +const builtin = @import("builtin"); export const _ = boot.rv64BspLowerEntry; pub fn arch() type { @@ -6,18 +9,47 @@ pub fn arch() type { pub inline fn halt() noreturn { while (true) { _ = setInterruptMask(true); - pause(); + waitForInterrupt(); } } pub inline fn setInterruptMask(mask: bool) bool { - // TODO - _ = mask; - return true; + const old = interruptMask(); + if (mask) { + regs.SSTATUS.modify(.{}, .{ .SIE = true }); + } else { + regs.SSTATUS.modify(.{ .SIE = true }, .{}); + } + return old; } - pub inline fn pause() void { + pub fn interruptMask() bool { + return regs.SSTATUS.read().SIE; + } + + pub inline fn waitForInterrupt() void { asm volatile ("wfi"); } + + pub inline fn spinHint() void { + // Don't want to explicitly enable Zihintpause ext, so just paste this as raw opcode + asm volatile (".word 0x0100000f"); + } + + pub inline fn barrier(comptime ordering: std.builtin.AtomicOrder) void { + switch (ordering) { + .acquire => { + asm volatile ("fence rw, w"); + }, + .release => { + asm volatile ("fence w, rw"); + }, + .acq_rel, .seq_cst => { + asm volatile ("fence rw, rw"); + }, + .unordered, .monotonic => {}, + } + asm volatile ("":::"memory"); + } }; } diff --git a/src/arch/riscv64/boot.zig b/src/arch/riscv64/boot.zig index 75b12b3..4bcb28c 100644 --- a/src/arch/riscv64/boot.zig +++ b/src/arch/riscv64/boot.zig @@ -1,15 +1,20 @@ const sbi = @import("sbi.zig"); const debug = @import("../../debug.zig"); -const arch = @import("../../kernel.zig").arch; +const kernel = @import("../../kernel.zig"); const vmm = @import("vmm.zig"); const regs = @import("regs.zig"); +const dtb = @import("../../util/dtb.zig"); const log = debug.log; +const arch = kernel.arch; extern const __rela_start: u8; extern const __rela_end: u8; extern const __rv64_bsp_stack_top: u8; +var gDtbAddress: usize = 0; +var gBspHartId: usize = 0; + pub export fn rv64RelocateKernel(imageBase: usize, relaStart: usize, relaEnd: usize) void { const elf = @import("std").elf; @@ -26,78 +31,54 @@ pub export fn rv64RelocateKernel(imageBase: usize, relaStart: usize, relaEnd: us } } -fn bspUpperEntry(a0: usize, a1: usize) callconv(.C) noreturn { - asm volatile ("":::"memory"); +fn bspUpperEntry(realAddress: usize, unused: usize) callconv(.C) noreturn { + _ = unused; + + arch.barrier(.acq_rel); - _ = a1; // Relocate the kernel yet again, this time to another base const relaStart = @intFromPtr(&__rela_start); const relaEnd = @intFromPtr(&__rela_end); - const relOffset = vmm.KERNEL_VIRTUAL_BASE + vmm.L1.offset(a0); - - asm volatile ("":::"memory"); + const relOffset = vmm.KERNEL_VIRTUAL_BASE + vmm.L1.offset(realAddress); + arch.barrier(.acq_rel); rv64RelocateKernel(relOffset, relaStart, relaEnd); - - asm volatile ("":::"memory"); - - // Can unmap lower half now - for (0..4) |i| { - vmm.fixed.entry(i).* = .INVALID; - } - - asm volatile ("":::"memory"); + vmm.unmapEarly(); debug.log.setWriteFn(&sbi.debugPrintByte); + kernel.mem.PhysicalAddress.gVirtualizeBase = 0; + kernel.mem.PhysicalAddress.gVirtualizeSize = vmm.virtualizeRange(); - log.info("Still alive", .{}); + // Setup physical memory management + const fdt = dtb.Fdt.fromPhysicalAddress(.{ .raw = gDtbAddress }) catch |err| { + log.panic("Cannot initialize raw DTB: {}", .{ err }); + }; + fdt.dump(); - arch.halt(); + kernel.kernel_main(); } -fn longJump(pc: usize, sp: usize, a0: usize, a1: usize) noreturn { +inline fn longJump(pc: usize, sp: usize, a0: usize, a1: usize) noreturn { asm volatile ( \\ mv sp, %[sp] \\ jr %[pc] : - : [a0]"{a0}"(a0), - [a1]"{a1}"(a1), - [pc]"r"(pc), - [sp]"r"(sp) - :"memory" + : [a0] "{a0}" (a0), + [a1] "{a1}" (a1), + [pc] "r" (pc), + [sp] "r" (sp), + : "memory" ); unreachable; } -fn setupMmu(realAddress: usize) void { - var table = &vmm.fixed; - const realL1 = vmm.L1.index(realAddress); - - // Lower half - for (0..4) |i| { - table.entry(i).* = vmm.TableEntry(vmm.L1).page( - .{ .raw = i * vmm.L1.SIZE }, - .{ .r = true, .w = true, .x = true } - ); - } - - // Map 1GiB at KERNEL_VIRTUAL_BASE -> physical 1GiB where the kernel is loaded - table.entry(vmm.KERNEL_VIRTUAL_L1I).* = vmm.TableEntry(vmm.L1).page( - .{ .raw = vmm.L1.address(realL1) }, - .{ .r = true, .w = true, .x = true } - ); - - const address = @as(usize, @intFromPtr(table)); - regs.SATP.write(.{ - .PPN = @intCast(address >> 12), - .MODE = .sv39 - }); -} - -pub export fn rv64BspLowerEntry(realAddress: usize) callconv(.C) noreturn { +pub export fn rv64BspLowerEntry(realAddress: usize, bspHartId: usize, dtbAddress: usize) callconv(.C) noreturn { debug.log.setWriteFn(&sbi.debugPrintByte); - setupMmu(realAddress); + gDtbAddress = dtbAddress; + gBspHartId = bspHartId; + + vmm.mapEarly(realAddress); // &bspUpperEntry will yield a pointer like: X + P, where // * X is symbol's raw address, diff --git a/src/arch/riscv64/entry.S b/src/arch/riscv64/entry.S index d855b4c..e2e817c 100644 --- a/src/arch/riscv64/entry.S +++ b/src/arch/riscv64/entry.S @@ -8,12 +8,35 @@ .extern RELOC_SYMBOL .pushsection .text.entry -.option push -.option norvc .type __rv64_entry, @function +// .option push +// .option rvc __rv64_entry: - auipc s0, 0 // a0 = real PC (also a real load address/offset) +// .ascii "MZ" // Magic 0 +// j __rv64_real_entry // Jump to real entry (if entered by non-Linux bootloader) +// .long 0 +// .quad 0x200000 // Offset from RAM start +// #.quad 0 // Image size (filled by build.zig) +// .quad 0x1122334455667788 // Image size (filled by build.zig) +// .quad 0 // Kernel flags +// .long 0x2 // Header version +// .long 0 +// .quad 0 +// .ascii "RISCV\x00\x00\x00" // Magic 1 +// .ascii "RSC\x05" // Magic 2 +// .long 0 +// .option pop + +.option push +.option norvc +__rv64_real_entry: + + // a0 - bootstrap HART ID + // a1 - device tree blob physical address + auipc s0, 0 // s0 = real PC (also a real load address/offset) + mv s1, a0 + mv s2, a1 csrw sie, zero csrw sip, zero @@ -53,6 +76,8 @@ __rv64_entry: addi t0, t0, %pcrel_lo(.P06) mv a0, s0 + mv a1, s1 + mv a2, s2 jr t0 .size __rv64_entry, . - __rv64_entry diff --git a/src/arch/riscv64/regs.zig b/src/arch/riscv64/regs.zig index 8721b9a..340e607 100644 --- a/src/arch/riscv64/regs.zig +++ b/src/arch/riscv64/regs.zig @@ -13,6 +13,11 @@ fn makeRegister(comptime name: []const u8, comptime bits: type) type { set(@bitCast(value)); } + pub fn modify(s: bits, c: bits) void { + const v = get(); + set((v & ~@as(repr, @bitCast(c))) | @as(repr, @bitCast(s))); + } + pub fn read() bits { return @bitCast(get()); } @@ -33,3 +38,20 @@ pub const SATP = makeRegister("satp", packed struct(u64) { _, } = .bare }); + +pub const SSTATUS = makeRegister("sstatus", packed struct(u64) { + // 0 + _0: u1 = 0, + // 1 + SIE: bool = false, + // 2..8 + _1: u6 = 0, + // 8 + SPP: bool = false, + // 9..18 + _2: u9 = 0, + // 18 + SUM: bool = false, + // 19..64 + _3: u45 = 0, +}); diff --git a/src/arch/riscv64/vmm.zig b/src/arch/riscv64/vmm.zig index 81ea0b9..f9ba648 100644 --- a/src/arch/riscv64/vmm.zig +++ b/src/arch/riscv64/vmm.zig @@ -1,30 +1,18 @@ -const PhysicalAddress = @import("../../mem.zig").PhysicalAddress; +const sync = @import("../../sync.zig"); +const regs = @import("regs.zig"); +const mem = @import("../../mem.zig"); +const arch = @import("../../kernel.zig").arch; + +const PhysicalAddress = mem.PhysicalAddress; pub const KERNEL_VIRTUAL_BASE: usize = 0xFFFFFFF000000000; pub const KERNEL_VIRTUAL_L1I: usize = (KERNEL_VIRTUAL_BASE >> L1.SHIFT) & 511; +// 16 GiB +const EARLY_MAPPING_SIZE: usize = 16; -fn translationLevel(comptime shift: usize) type { - return struct { - pub const SHIFT: usize = shift; - pub const SIZE: usize = 1 << shift; - - pub inline fn index(addr: usize) usize { - return (addr >> shift) & 511; - } - - pub inline fn offset(addr: usize) usize { - return addr & ((1 << shift) - 1); - } - - pub inline fn address(idx: usize) usize { - return idx << shift; - } - }; -} - -pub const L1 = translationLevel(30); -pub const L2 = translationLevel(21); -pub const L3 = translationLevel(12); +pub const L1 = mem.translationLevel(30); +pub const L2 = mem.translationLevel(21); +pub const L3 = mem.translationLevel(12); pub const RawEntry = packed struct(u64) { // 0: Valid @@ -96,12 +84,10 @@ pub fn TableEntry(comptime Level: type) type { pub fn table(addr: PhysicalAddress, flags: RawEntry) @This() { flags.clear(.{ .r = true, .w = true, .x = true }); - return .{ - .raw = flags.makeUnion(.{ - .address = @as(u39, @intCast(addr.raw >> 12)), - .v = true, - }) - }; + return .{ .raw = flags.makeUnion(.{ + .address = @as(u39, @intCast(addr.raw >> 12)), + .v = true, + }) }; } }; } @@ -113,9 +99,7 @@ pub fn Table(comptime Level: type) type { entries: [512]Entry align(4096), pub fn empty() @This() { - return .{ - .entries = [_]Entry{.INVALID} ** 512 - }; + return .{ .entries = [_]Entry{.INVALID} ** 512 }; } pub inline fn entry(self: *@This(), index: usize) *Entry { @@ -124,4 +108,67 @@ pub fn Table(comptime Level: type) type { }; } -pub var fixed = Table(L1).empty(); +var gFixed = Table(L1).empty(); +var gFixedLock: sync.IrqSafeSpinlock = .{}; + +pub fn virtualizeRange() usize { + return EARLY_MAPPING_SIZE * L1.SIZE; +} + +pub fn unmapEarly() void { + // Make lower half mappings non-executable + gFixedLock.lock(); + defer gFixedLock.release(); + for (0..EARLY_MAPPING_SIZE) |i| { + gFixed.entry(i).* = .page( + .{ .raw = L1.address(i) }, + .{ .r = true, .w = true }, + ); + } +} + +pub fn mapEarly(realAddress: usize) void { + const realL1 = L1.index(realAddress); + + // Identity map first 16GiB of memory + for (0..EARLY_MAPPING_SIZE) |i| { + gFixed.entry(i).* = .page(.{ .raw = L1.address(i) }, .{ .r = true, .w = true, .x = true }); + } + + // Map 1GiB at KERNEL_VIRTUAL_BASE -> physical 1GiB where the kernel is loaded + gFixed.entry(KERNEL_VIRTUAL_L1I).* = .page(.{ .raw = L1.address(realL1) }, .{ .r = true, .w = true, .x = true }); + + const address = @as(usize, @intFromPtr(&gFixed)); + regs.SATP.write(.{ .PPN = @intCast(address >> 12), .MODE = .sv39 }); +} + +pub inline fn flush_vma(page: usize) void { + asm volatile ("sfence.vma %[page], zero" + : + : [page] "r" (page), + : "memory" + ); +} + +pub inline fn flush_vma_asid(page: usize, asid: usize) void { + asm volatile ("sfence.vma %[page], %[asid]" + : + : [page] "r" (page), + [asid] "r" (asid), + ); +} + +pub fn flush_vma_range(start: usize, end: usize, asid: ?usize) void { + var i = L3.align_down(start); + const rend = L3.align_up(end); + + if (asid) |a| { + while (i < rend) : (i += L3.SIZE) { + flush_vma_asid(i, a); + } + } else { + while (i < rend) : (i += L3.SIZE) { + flush_vma(i); + } + } +} diff --git a/src/debug.zig b/src/debug.zig index 44d16e8..96b3a0a 100644 --- a/src/debug.zig +++ b/src/debug.zig @@ -27,16 +27,55 @@ pub const log = struct { writeFn = f; } - pub fn info(comptime format: []const u8, args: anytype) void { - write(.info, format ++ "\r\n", args); + pub inline fn info(comptime format: []const u8, args: anytype) void { + write(.info, format, args); + } + + pub inline fn debug(comptime format: []const u8, args: anytype) void { + write(.debug, format, args); + } + + pub inline fn warn(comptime format: []const u8, args: anytype) void { + write(.warn, format, args); + } + + pub inline fn err(comptime format: []const u8, args: anytype) void { + write(.err, format, args); } pub fn writeRaw(data: []const u8) void { - writeWrapperFn(void, data); + _ = writeWrapperFn(0, data) catch return; } - pub fn write(level: Level, comptime format: []const u8, args: anytype) void { - _ = level; - writer.print(format, args) catch return; + pub fn write(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; + } + + pub fn todo(comptime msg: []const u8, args: anytype) noreturn { + err("Not yet implemented: " ++ msg, args); + @panic("Not yet implemented"); + } + + pub fn panic(comptime msg: []const u8, args: anytype) noreturn { + err("PANIC: " ++ msg, args); + @panic("Explicit kernel panic"); + } + + fn logPrefix(comptime level: Level) []const u8 { + switch (level) { + .debug => return "", + .info => return "\x1B[1;36m", + .warn => return "\x1B[1;33m", + .err => return "\x1B[1;31m", + } + } + fn logSuffix(comptime level: Level) []const u8 { + if (level == .debug) { + return ""; + } else { + return "\x1B[0m"; + } } }; diff --git a/src/kernel.zig b/src/kernel.zig index 5264bac..900001b 100644 --- a/src/kernel.zig +++ b/src/kernel.zig @@ -1,7 +1,25 @@ // export const _ = @import("arch/riscv64/boot.zig").rv64BspLowerEntry; pub const arch = @import("arch.zig").arch(); pub const mem = @import("mem.zig"); +pub const debug = @import("debug.zig"); + +pub const log = debug.log; + +const std = @import("std"); + +pub export fn kernel_main() callconv(.C) noreturn { + log.info("Hello", .{}); + arch.halt(); +} + +pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, return_address: ?usize) noreturn { + _ = error_return_trace; + + const ra = return_address orelse @returnAddress(); + + log.err("!!! Kernel panic !!!", .{}); + log.err(" Reason: {s}", .{ msg }); + log.err(" At: 0x{x}", .{ ra }); -export fn kernel_main() callconv(.C) void { arch.halt(); } diff --git a/src/mem.zig b/src/mem.zig index ae68d29..9d5daef 100644 --- a/src/mem.zig +++ b/src/mem.zig @@ -1,9 +1,30 @@ +pub const translationLevel = @import("mem/vmm.zig").translationLevel; + pub const PhysicalAddress = packed struct(u64) { raw: u64, pub const NULL: @This() = .{ .raw = 0 }; + pub var gVirtualizeBase: usize = 0; + pub var gVirtualizeSize: usize = 0; + pub fn add(self: @This(), offset: usize) @This() { return .{ .raw = self.raw + @as(u64, @intCast(offset)) }; } + + pub fn virtualize(self: @This()) usize { + if (self.raw > gVirtualizeSize) { + @panic("Physical address out of virtualize bounds"); + } + + return self.raw + gVirtualizeBase; + } + + pub fn from_virtualized(virt: usize) @This() { + if ((virt < gVirtualizeBase) || (virt - gVirtualizeBase > gVirtualizeSize)) { + @panic("Invalid virtualized physical address"); + } + + return .{ .raw = virt - gVirtualizeBase }; + } }; diff --git a/src/mem/vmm.zig b/src/mem/vmm.zig new file mode 100644 index 0000000..7437cf6 --- /dev/null +++ b/src/mem/vmm.zig @@ -0,0 +1,26 @@ +pub fn translationLevel(comptime shift: usize) type { + return struct { + pub const SHIFT: usize = shift; + pub const SIZE: usize = 1 << shift; + + pub inline fn index(addr: usize) usize { + return (addr >> shift) & 511; + } + + pub inline fn offset(addr: usize) usize { + return addr & (SIZE - 1); + } + + pub inline fn address(idx: usize) usize { + return idx << shift; + } + + pub inline fn align_down(addr: usize) usize { + return addr & ~(SIZE - 1); + } + + pub inline fn align_up(addr: usize) usize { + return (addr + SIZE - 1) & ~(SIZE - 1); + } + }; +} diff --git a/src/sync.zig b/src/sync.zig index fddee00..af67634 100644 --- a/src/sync.zig +++ b/src/sync.zig @@ -1,6 +1,20 @@ -pub fn IrqSafeSpinlock(comptime T: type) type { - return struct { - inner: T, +const std = @import("std"); +const arch = @import("kernel.zig").arch; - }; -} +pub const IrqSafeSpinlock = struct { + state: std.atomic.Value(bool) = .{ .raw = false }, + + pub fn tryLock(self: *@This()) bool { + return self.state.cmpxchgStrong(false, true, .acquire, .monotonic) orelse false; + } + + pub fn lock(self: *@This()) void { + while (!self.tryLock()) { + arch.spinHint(); + } + } + + pub fn release(self: *@This()) void { + self.state.store(false, .release); + } +}; diff --git a/src/util/dtb.zig b/src/util/dtb.zig new file mode 100644 index 0000000..d9e8bd8 --- /dev/null +++ b/src/util/dtb.zig @@ -0,0 +1,244 @@ +const mem = @import("../mem.zig"); +const log = @import("../debug.zig").log; +const value = @import("value.zig"); +const std = @import("std"); + +const fdt_header = extern struct { + magic: u32, + totalsize: u32, + off_dt_struct: u32, + off_dt_strings: u32, + off_mem_rsvmap: u32, + version: u32, + last_comp_version: u32, + boot_cpuid_phys: u32, + size_dt_strings: u32, + 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_prop = extern struct { + len: u32, + nameoff: u32, +}; + +const FdtPropTag = struct { + len: u32, + nameoff: u32, + data: []const u8, +}; + +const FdtTag = union(enum) { + begin_node: []const u8, + end_node, + prop: FdtPropTag, + nop, + end, +}; + +pub const FdtNode = struct { + fdt: *const Fdt, + off: usize, + name: []const u8, + depth: usize, + + pub fn propIterator(self: *const @This()) FdtNodePropIterator { + return .{ .node = self, .tagIter = self.fdt.tagIteratorAt(self.off) }; + } +}; + +pub const FdtNodeProp = struct { + node: *const FdtNode, + name: []const u8, + value: []const u8, +}; + +pub const FdtNodePropIterator = struct { + node: *const FdtNode, + tagIter: FdtTagIterator, + depth: usize = 0, + + fn next(self: *FdtNodePropIterator) ?FdtNodeProp { + while (self.tagIter.next()) |tag| { + switch (tag) { + .begin_node => |_| { + self.depth += 1; + }, + .nop => {}, + .prop => |prop| { + if (self.depth == 0) { + const name = self.node.fdt.stringAt(prop.nameoff); + return .{ + .node = self.node, + .value = prop.data, + .name = name + }; + } + }, + .end_node => { + if (self.depth == 0) { + return null; + } + + self.depth -= 1; + }, + .end => { + return null; + } + } + } + + return null; + } +}; + +pub const FdtNodeIterator = struct { + tagIter: FdtTagIterator, + depth: usize = 0, + + fn next(self: *FdtNodeIterator) ?FdtNode { + while (self.tagIter.next()) |tag| { + switch (tag) { + .begin_node => |name| { + self.depth += 1; + return .{ + .fdt = self.tagIter.fdt, + .off = self.tagIter.off, + .name = name, + .depth = self.depth - 1, + }; + }, + .end_node => { + self.depth -= 1; + }, + else => {}, + } + } + return null; + } +}; + +pub const FdtTagIterator = struct { + fdt: *const Fdt, + raw: []const u8, + off: usize, + + fn next(self: *FdtTagIterator) ?FdtTag { + if (self.off >= self.raw.len) { + return null; + } + + const tag: fdt_op = @enumFromInt(value.u32FromBigEndian(@as(*const u32, @ptrCast(@alignCast(&self.raw[self.off]))).*)); + + self.off += @sizeOf(u32); + + switch (tag) { + .FDT_BEGIN_NODE => { + const nameCStr: [*c]const u8 = @ptrCast(self.raw[self.off..]); + const nameLength = std.mem.len(nameCStr); + const name = self.raw[self.off .. self.off + nameLength]; + self.off += (nameLength + 4) & ~@as(usize, 3); + return .{ .begin_node = name }; + }, + .FDT_PROP => { + const info: *const fdt_prop = @ptrCast(@alignCast(&self.raw[self.off])); + const nameoff = value.u32FromBigEndian(info.nameoff); + const len = value.u32FromBigEndian(info.len); + self.off += @sizeOf(fdt_prop); + const data = self.raw[self.off .. self.off + len]; + self.off += (len + 3) & ~@as(usize, 3); + + return .{ .prop = .{ .nameoff = nameoff, .len = len, .data = data } }; + }, + .FDT_NOP => { + return .nop; + }, + .FDT_END => { + self.off = self.raw.len; + return .end; + }, + .FDT_END_NODE => { + return .end_node; + }, + else => { + return null; + }, + } + } +}; + +pub const FdtError = error{invalid_magic}; + +pub const FDT_MAGIC: u32 = 0xD00DFEED; + +pub const Fdt = struct { + bytes: []const u8, + + pub fn fromPhysicalAddress(phys: mem.PhysicalAddress) FdtError!@This() { + const virt = phys.virtualize(); + const hdr = @as(*const fdt_header, @ptrFromInt(virt)); + if (value.u32FromBigEndian(hdr.magic) != FDT_MAGIC) { + return error.invalid_magic; + } + const totalsize = value.u32FromBigEndian(hdr.totalsize); + const x = @as([*]const u8, @ptrFromInt(virt)); + return .{ .bytes = x[0..totalsize] }; + } + + pub fn header(self: *const @This()) *const fdt_header { + return @ptrCast(@alignCast(&self.bytes[0])); + } + + fn data(self: *const @This()) []const u8 { + const off = value.u32FromBigEndian(self.header().off_dt_struct); + return self.bytes[off..]; + } + + pub fn tagIterator(self: *const @This()) FdtTagIterator { + return self.tagIteratorAt(0); + } + + pub fn tagIteratorAt(self: *const @This(), off: usize) FdtTagIterator { + return .{ .raw = self.data(), .fdt = self, .off = off }; + } + + pub fn nodeIterator(self: *const @This()) FdtNodeIterator { + return .{ .tagIter = self.tagIterator() }; + } + + fn stringData(self: *const @This()) [*c]const u8 { + const offStrings = value.u32FromBigEndian(self.header().off_dt_strings); + const sizeStrings = value.u32FromBigEndian(self.header().off_dt_strings); + const off = @min(offStrings, self.bytes.len); + const len = @min(sizeStrings, self.bytes.len - off); + return @ptrCast(self.bytes[off..off + len]); + } + + 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]); + } + + pub fn dump(self: *const @This()) void { + var nodeIter = self.nodeIterator(); + while (nodeIter.next()) |node| { + for (0..node.depth) |_| { + log.writeRaw(" "); + } + if (node.name.len == 0) { + log.info("Root node", .{}); + } else { + log.info("Node {s}", .{ node.name }); + } + var nodePropIter = node.propIterator(); + while (nodePropIter.next()) |prop| { + for (0..node.depth + 1) |_| { + log.writeRaw(" "); + } + log.info("Prop {s}", .{ prop.name }); + } + } + } +}; diff --git a/src/util/value.zig b/src/util/value.zig new file mode 100644 index 0000000..cf33e0e --- /dev/null +++ b/src/util/value.zig @@ -0,0 +1,9 @@ +const builtin = @import("builtin"); + +pub fn u32FromBigEndian(input: u32) u32 { + if (comptime builtin.cpu.arch.endian() == .little) { + return @byteSwap(input); + } else { + return input; + } +}