commit 6f76f9dd9dd1aa5fc14b054ce6ba266715a653af Author: Lorenzo Torres Date: Fri Jan 30 16:21:31 2026 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..639a11a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +**/*.swp +**/*~ +zig-out/ +.zig-cache/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..5f680cd --- /dev/null +++ b/build.zig @@ -0,0 +1,41 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const optimize = b.standardOptimizeOption(.{}); + + const target = b.resolveTargetQuery(.{ + .cpu_arch = .riscv64, + .os_tag = .freestanding, + .abi = .none, + }); + + const kernel = b.addExecutable(.{ + .name = "kernel", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .code_model = .medium, + }), + }); + + kernel.setLinkerScript(b.path("linker.ld")); + + b.installArtifact(kernel); + + const run_cmd = b.addSystemCommand(&.{ + "qemu-system-riscv64", + "-machine", + "virt", + "-bios", + "default", + "-kernel", + }); + run_cmd.addArtifactArg(kernel); + run_cmd.addArgs(&.{ + "-nographic", + }); + + const run_step = b.step("run", "Run the kernel in QEMU"); + run_step.dependOn(&run_cmd.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..eb33509 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,13 @@ +.{ + .name = .hydra, + .version = "0.0.0", + .fingerprint = 0x11350dc1acc0b33e, // Changing this has security and trust implications. + .minimum_zig_version = "0.16.0-dev.2261+d6b3dd25a", + .dependencies = .{ + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/linker.ld b/linker.ld new file mode 100644 index 0000000..f7559dd --- /dev/null +++ b/linker.ld @@ -0,0 +1,33 @@ +OUTPUT_ARCH(riscv) +ENTRY(_start) + +MEMORY +{ + RAM (rwx) : ORIGIN = 0x80200000, LENGTH = 126M +} + +SECTIONS +{ + .text : { + *(.text.init) + *(.text .text.*) + } > RAM + + .rodata : { + *(.rodata .rodata.*) + } > RAM + + .data : { + *(.data .data.*) + } > RAM + + .bss : { + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + __bss_end = .; + } > RAM + + . = ALIGN(16); + __stack_top = . + 0x10000; +} diff --git a/qemu.dts b/qemu.dts new file mode 100644 index 0000000..aa41650 --- /dev/null +++ b/qemu.dts @@ -0,0 +1,215 @@ +/dts-v1/; + +/ { + #address-cells = <0x02>; + #size-cells = <0x02>; + compatible = "riscv-virtio"; + model = "riscv-virtio,qemu"; + + poweroff { + value = <0x5555>; + offset = <0x00>; + regmap = <0x04>; + compatible = "syscon-poweroff"; + }; + + reboot { + value = <0x7777>; + offset = <0x00>; + regmap = <0x04>; + compatible = "syscon-reboot"; + }; + + platform-bus@4000000 { + interrupt-parent = <0x03>; + ranges = <0x00 0x00 0x4000000 0x2000000>; + #address-cells = <0x01>; + #size-cells = <0x01>; + compatible = "qemu,platform", "simple-bus"; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x00 0x80000000 0x00 0x8000000>; + }; + + cpus { + #address-cells = <0x01>; + #size-cells = <0x00>; + timebase-frequency = <0x989680>; + + cpu@0 { + phandle = <0x01>; + device_type = "cpu"; + reg = <0x00>; + status = "okay"; + compatible = "riscv"; + riscv,cbop-block-size = <0x40>; + riscv,cboz-block-size = <0x40>; + riscv,cbom-block-size = <0x40>; + riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "h", "zic64b", "zicbom", "zicbop", "zicboz", "ziccamoa", "ziccif", "zicclsm", "ziccrse", "zicntr", "zicsr", "zifencei", "zihintntl", "zihintpause", "zihpm", "zmmul", "za64rs", "zaamo", "zalrsc", "zawrs", "zfa", "zca", "zcd", "zba", "zbb", "zbc", "zbs", "sdtrig", "shcounterenw", "shgatpa", "shtvala", "shvsatpa", "shvstvala", "shvstvecd", "ssccptr", "sscounterenw", "ssstrict", "sstc", "sstvala", "sstvecd", "ssu64xl", "svadu", "svvptc"; + riscv,isa-base = "rv64i"; + riscv,isa = "rv64imafdch_zic64b_zicbom_zicbop_zicboz_ziccamoa_ziccif_zicclsm_ziccrse_zicntr_zicsr_zifencei_zihintntl_zihintpause_zihpm_zmmul_za64rs_zaamo_zalrsc_zawrs_zfa_zca_zcd_zba_zbb_zbc_zbs_sdtrig_shcounterenw_shgatpa_shtvala_shvsatpa_shvstvala_shvstvecd_ssccptr_sscounterenw_ssstrict_sstc_sstvala_sstvecd_ssu64xl_svadu_svvptc"; + mmu-type = "riscv,sv57"; + + interrupt-controller { + #interrupt-cells = <0x01>; + interrupt-controller; + compatible = "riscv,cpu-intc"; + phandle = <0x02>; + }; + }; + + cpu-map { + + cluster0 { + + core0 { + cpu = <0x01>; + }; + }; + }; + }; + + pmu { + riscv,event-to-mhpmcounters = <0x01 0x01 0x7fff9 0x02 0x02 0x7fffc 0x10019 0x10019 0x7fff8 0x1001b 0x1001b 0x7fff8 0x10021 0x10021 0x7fff8>; + compatible = "riscv,pmu"; + }; + + fw-cfg@10100000 { + dma-coherent; + reg = <0x00 0x10100000 0x00 0x18>; + compatible = "qemu,fw-cfg-mmio"; + }; + + flash@20000000 { + bank-width = <0x04>; + reg = <0x00 0x20000000 0x00 0x2000000 0x00 0x22000000 0x00 0x2000000>; + compatible = "cfi-flash"; + }; + + aliases { + serial0 = "/soc/serial@10000000"; + }; + + chosen { + stdout-path = "/soc/serial@10000000"; + rng-seed = <0xacd2fc9e 0xf4c10c62 0xddd24e52 0x36e0b525 0x96b76823 0x29825fdc 0xbf75e5be 0xde8eb233>; + }; + + soc { + #address-cells = <0x02>; + #size-cells = <0x02>; + compatible = "simple-bus"; + ranges; + + rtc@101000 { + interrupts = <0x0b>; + interrupt-parent = <0x03>; + reg = <0x00 0x101000 0x00 0x1000>; + compatible = "google,goldfish-rtc"; + }; + + serial@10000000 { + interrupts = <0x0a>; + interrupt-parent = <0x03>; + clock-frequency = "", "8@"; + reg = <0x00 0x10000000 0x00 0x100>; + compatible = "ns16550a"; + }; + + test@100000 { + phandle = <0x04>; + reg = <0x00 0x100000 0x00 0x1000>; + compatible = "sifive,test1", "sifive,test0", "syscon"; + }; + + virtio_mmio@10008000 { + interrupts = <0x08>; + interrupt-parent = <0x03>; + reg = <0x00 0x10008000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10007000 { + interrupts = <0x07>; + interrupt-parent = <0x03>; + reg = <0x00 0x10007000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10006000 { + interrupts = <0x06>; + interrupt-parent = <0x03>; + reg = <0x00 0x10006000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10005000 { + interrupts = <0x05>; + interrupt-parent = <0x03>; + reg = <0x00 0x10005000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10004000 { + interrupts = <0x04>; + interrupt-parent = <0x03>; + reg = <0x00 0x10004000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10003000 { + interrupts = <0x03>; + interrupt-parent = <0x03>; + reg = <0x00 0x10003000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10002000 { + interrupts = <0x02>; + interrupt-parent = <0x03>; + reg = <0x00 0x10002000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + virtio_mmio@10001000 { + interrupts = <0x01>; + interrupt-parent = <0x03>; + reg = <0x00 0x10001000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + + plic@c000000 { + phandle = <0x03>; + riscv,ndev = <0x5f>; + reg = <0x00 0xc000000 0x00 0x600000>; + interrupts-extended = <0x02 0x0b 0x02 0x09>; + interrupt-controller; + compatible = "sifive,plic-1.0.0", "riscv,plic0"; + #address-cells = <0x00>; + #interrupt-cells = <0x01>; + }; + + clint@2000000 { + interrupts-extended = <0x02 0x03 0x02 0x07>; + reg = <0x00 0x2000000 0x00 0x10000>; + compatible = "sifive,clint0", "riscv,clint0"; + }; + + pci@30000000 { + interrupt-map-mask = <0x1800 0x00 0x00 0x07>; + interrupt-map = <0x00 0x00 0x00 0x01 0x03 0x20 0x00 0x00 0x00 0x02 0x03 0x21 0x00 0x00 0x00 0x03 0x03 0x22 0x00 0x00 0x00 0x04 0x03 0x23 0x800 0x00 0x00 0x01 0x03 0x21 0x800 0x00 0x00 0x02 0x03 0x22 0x800 0x00 0x00 0x03 0x03 0x23 0x800 0x00 0x00 0x04 0x03 0x20 0x1000 0x00 0x00 0x01 0x03 0x22 0x1000 0x00 0x00 0x02 0x03 0x23 0x1000 0x00 0x00 0x03 0x03 0x20 0x1000 0x00 0x00 0x04 0x03 0x21 0x1800 0x00 0x00 0x01 0x03 0x23 0x1800 0x00 0x00 0x02 0x03 0x20 0x1800 0x00 0x00 0x03 0x03 0x21 0x1800 0x00 0x00 0x04 0x03 0x22>; + ranges = <0x1000000 0x00 0x00 0x00 0x3000000 0x00 0x10000 0x2000000 0x00 0x40000000 0x00 0x40000000 0x00 0x40000000 0x3000000 0x04 0x00 0x04 0x00 0x04 0x00>; + reg = <0x00 0x30000000 0x00 0x10000000>; + dma-coherent; + bus-range = <0x00 0xff>; + linux,pci-domain = <0x00>; + device_type = "pci"; + compatible = "pci-host-ecam-generic"; + #size-cells = <0x02>; + #interrupt-cells = <0x01>; + #address-cells = <0x03>; + }; + }; +}; diff --git a/src/Fdt.zig b/src/Fdt.zig new file mode 100644 index 0000000..1522122 --- /dev/null +++ b/src/Fdt.zig @@ -0,0 +1,692 @@ +const std = @import("std"); +const Fdt = @This(); + +pub const MAGIC = 0xd00dfeed; + +pub const Token = enum(u32) { + begin_node = 0x01, + end_node = 0x02, + prop = 0x03, + nop = 0x04, + end = 0x09, +}; + +/// Raw FDT header as it appears in memory (big-endian) +pub const RawHeader = 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, +}; + +/// Memory reservation entry +pub const ReserveEntry = extern struct { + address: u64, + size: u64, + + pub fn toNative(self: *const ReserveEntry) struct { address: u64, size: u64 } { + return .{ + .address = std.mem.bigToNative(u64, self.address), + .size = std.mem.bigToNative(u64, self.size), + }; + } +}; + +/// Parsed property data +pub const Property = struct { + name: []const u8, + data: []const u8, + + pub fn asU32(self: Property) ?u32 { + if (self.data.len != 4) return null; + return std.mem.bigToNative(u32, @as(*const u32, @alignCast(@ptrCast(self.data.ptr))).*); + } + + pub fn asU64(self: Property) ?u64 { + if (self.data.len != 8) return null; + return std.mem.bigToNative(u64, @as(*const u64, @alignCast(@ptrCast(self.data.ptr))).*); + } + + /// Read property as a null-terminated string + pub fn asString(self: Property) ?[]const u8 { + if (self.data.len == 0) return null; + for (self.data, 0..) |c, i| { + if (c == 0) return self.data[0..i]; + } + return self.data; + } + + pub fn asStringList(self: Property) StringListIterator { + return .{ .data = self.data }; + } + + pub fn asU32Array(self: Property) U32ArrayIterator { + return .{ .data = self.data }; + } + + pub fn asU64Array(self: Property) U64ArrayIterator { + return .{ .data = self.data }; + } + + pub fn isEmpty(self: Property) bool { + return self.data.len == 0; + } +}; + +pub const StringListIterator = struct { + data: []const u8, + pos: usize = 0, + + pub fn next(self: *StringListIterator) ?[]const u8 { + if (self.pos >= self.data.len) return null; + + const start = self.pos; + while (self.pos < self.data.len and self.data[self.pos] != 0) { + self.pos += 1; + } + const end = self.pos; + self.pos += 1; // skip null terminator + + if (start == end) return null; + return self.data[start..end]; + } +}; + +pub const U32ArrayIterator = struct { + data: []const u8, + pos: usize = 0, + + pub fn next(self: *U32ArrayIterator) ?u32 { + if (self.pos + 4 > self.data.len) return null; + const value = std.mem.bigToNative(u32, @as(*const u32, @alignCast(@ptrCast(self.data.ptr + self.pos))).*); + self.pos += 4; + return value; + } + + pub fn count(self: U32ArrayIterator) usize { + return self.data.len / 4; + } +}; + +pub const U64ArrayIterator = struct { + data: []const u8, + pos: usize = 0, + + pub fn next(self: *U64ArrayIterator) ?u64 { + if (self.pos + 8 > self.data.len) return null; + const value = std.mem.bigToNative(u64, @as(*const u64, @alignCast(@ptrCast(self.data.ptr + self.pos))).*); + self.pos += 8; + return value; + } + + pub fn count(self: U64ArrayIterator) usize { + return self.data.len / 8; + } +}; + +/// FDT node with its properties and children +pub const Node = struct { + /// Full path to this node (e.g., "/soc/uart@10000000") + name: []const u8, + fdt: *const Fdt, + /// Offset in structure block where this node's content starts (after name) + struct_offset: usize, + + /// Get the unit address part of the node name (after '@') + pub fn unitAddress(self: Node) ?[]const u8 { + for (self.name, 0..) |c, i| { + if (c == '@') return self.name[i + 1 ..]; + } + return null; + } + + /// Get the base name (before '@') + pub fn baseName(self: Node) []const u8 { + for (self.name, 0..) |c, i| { + if (c == '@') return self.name[0..i]; + } + return self.name; + } + + /// Iterate over all properties of this node + pub fn properties(self: Node) PropertyIterator { + return .{ + .fdt = self.fdt, + .pos = self.struct_offset, + }; + } + + /// Get a specific property by name + pub fn getProperty(self: Node, name: []const u8) ?Property { + var iter = self.properties(); + while (iter.next()) |prop| { + if (std.mem.eql(u8, prop.name, name)) { + return prop; + } + } + return null; + } + + /// Iterate over direct children of this node + pub fn children(self: Node) ChildIterator { + // Skip to the end of properties to find children + var pos = self.struct_offset; + while (pos < self.fdt.struct_block.len) { + const token = self.fdt.readToken(pos) orelse break; + pos += 4; + + switch (token) { + .prop => { + const len = self.fdt.readU32(pos) orelse break; + pos += 8; // skip len and nameoff + pos += alignUp(len, 4); + }, + .nop => {}, + .begin_node, .end_node, .end => { + pos -= 4; // back up to re-read this token + break; + }, + } + } + + return .{ + .fdt = self.fdt, + .pos = pos, + .depth = 0, + }; + } + + /// Find a child node by name + pub fn getChild(self: Node, name: []const u8) ?Node { + var iter = self.children(); + while (iter.next()) |child| { + if (std.mem.eql(u8, child.name, name)) { + return child; + } + } + return null; + } + + /// Get the 'compatible' property as a string list + pub fn compatible(self: Node) ?StringListIterator { + const prop = self.getProperty("compatible") orelse return null; + return prop.asStringList(); + } + + /// Check if node is compatible with given string + pub fn isCompatible(self: Node, compat: []const u8) bool { + var iter = self.compatible() orelse return false; + while (iter.next()) |c| { + if (std.mem.eql(u8, c, compat)) return true; + } + return false; + } + + /// Get the 'reg' property - returns iterator over address/size pairs + /// Note: caller must know address-cells and size-cells from parent + pub fn reg(self: Node) ?[]const u8 { + const prop = self.getProperty("reg") orelse return null; + return prop.data; + } + + /// Get the 'status' property + pub fn status(self: Node) ?[]const u8 { + const prop = self.getProperty("status") orelse return null; + return prop.asString(); + } + + /// Check if node is enabled (status = "okay" or "ok" or no status property) + pub fn isEnabled(self: Node) bool { + const s = self.status() orelse return true; + return std.mem.eql(u8, s, "okay") or std.mem.eql(u8, s, "ok"); + } + + /// Get #address-cells (defaults to 2 if not present) + pub fn addressCells(self: Node) u32 { + const prop = self.getProperty("#address-cells") orelse return 2; + return prop.asU32() orelse 2; + } + + /// Get #size-cells (defaults to 1 if not present) + pub fn sizeCells(self: Node) u32 { + const prop = self.getProperty("#size-cells") orelse return 1; + return prop.asU32() orelse 1; + } + + /// Get phandle of this node + pub fn phandle(self: Node) ?u32 { + // Try both 'phandle' and legacy 'linux,phandle' + if (self.getProperty("phandle")) |prop| { + return prop.asU32(); + } + if (self.getProperty("linux,phandle")) |prop| { + return prop.asU32(); + } + return null; + } +}; + +pub const PropertyIterator = struct { + fdt: *const Fdt, + pos: usize, + + pub fn next(self: *PropertyIterator) ?Property { + while (self.pos < self.fdt.struct_block.len) { + const token = self.fdt.readToken(self.pos) orelse return null; + self.pos += 4; + + switch (token) { + .prop => { + const len = self.fdt.readU32(self.pos) orelse return null; + const nameoff = self.fdt.readU32(self.pos + 4) orelse return null; + self.pos += 8; + + const name = self.fdt.getString(nameoff) orelse return null; + const data = if (len > 0 and self.pos + len <= self.fdt.struct_block.len) + self.fdt.struct_block[self.pos..][0..len] + else + &[_]u8{}; + + self.pos += alignUp(len, 4); + + return Property{ + .name = name, + .data = data, + }; + }, + .nop => continue, + .begin_node, .end_node, .end => { + self.pos -= 4; + return null; + }, + } + } + return null; + } +}; + +pub const ChildIterator = struct { + fdt: *const Fdt, + pos: usize, + depth: i32, + + pub fn next(self: *ChildIterator) ?Node { + while (self.pos < self.fdt.struct_block.len) { + const token = self.fdt.readToken(self.pos) orelse return null; + self.pos += 4; + + switch (token) { + .begin_node => { + const name_start = self.pos; + // Find end of name + while (self.pos < self.fdt.struct_block.len and self.fdt.struct_block[self.pos] != 0) { + self.pos += 1; + } + const name = self.fdt.struct_block[name_start..self.pos]; + self.pos += 1; // skip null + self.pos = alignUp(self.pos, 4); + + if (self.depth == 0) { + self.depth = 1; + return Node{ + .name = name, + .fdt = self.fdt, + .struct_offset = self.pos, + }; + } else { + self.depth += 1; + } + }, + .end_node => { + self.depth -= 1; + if (self.depth < 0) return null; + }, + .prop => { + const len = self.fdt.readU32(self.pos) orelse return null; + self.pos += 8; + self.pos += alignUp(len, 4); + }, + .nop => {}, + .end => return null, + } + } + return null; + } +}; + +/// Iterator for all nodes in the tree (depth-first) +pub const NodeIterator = struct { + fdt: *const Fdt, + pos: usize, + depth: usize = 0, + include_root: bool = true, + root_seen: bool = false, + + pub fn next(self: *NodeIterator) ?Node { + while (self.pos < self.fdt.struct_block.len) { + const token = self.fdt.readToken(self.pos) orelse return null; + self.pos += 4; + + switch (token) { + .begin_node => { + const name_start = self.pos; + while (self.pos < self.fdt.struct_block.len and self.fdt.struct_block[self.pos] != 0) { + self.pos += 1; + } + const name = self.fdt.struct_block[name_start..self.pos]; + self.pos += 1; + self.pos = alignUp(self.pos, 4); + self.depth += 1; + + // Skip root node if requested + if (!self.root_seen) { + self.root_seen = true; + if (!self.include_root) continue; + } + + return Node{ + .name = name, + .fdt = self.fdt, + .struct_offset = self.pos, + }; + }, + .end_node => { + if (self.depth > 0) self.depth -= 1; + }, + .prop => { + const len = self.fdt.readU32(self.pos) orelse return null; + self.pos += 8; + self.pos += alignUp(len, 4); + }, + .nop => {}, + .end => return null, + } + } + return null; + } +}; + +pub const RegIterator = struct { + data: []const u8, + address_cells: u32, + size_cells: u32, + pos: usize, + + pub const Entry = struct { + address: u64, + size: u64, + }; + + pub fn next(self: *RegIterator) ?Entry { + const entry_size = (self.address_cells + self.size_cells) * 4; + if (self.pos + entry_size > self.data.len) return null; + + var address: u64 = 0; + var i: usize = 0; + while (i < self.address_cells) : (i += 1) { + const val = std.mem.bigToNative(u32, @as(*const u32, @alignCast(@ptrCast(self.data.ptr + self.pos + i * 4))).*); + address = (address << 32) | val; + } + + var size: u64 = 0; + i = 0; + while (i < self.size_cells) : (i += 1) { + const val = std.mem.bigToNative(u32, @as(*const u32, @alignCast(@ptrCast(self.data.ptr + self.pos + (self.address_cells + i) * 4))).*); + size = (size << 32) | val; + } + + self.pos += entry_size; + + return Entry{ + .address = address, + .size = size, + }; + } +}; + +base: [*]const u8, +totalsize: u32, +version: u32, +last_comp_version: u32, +boot_cpuid_phys: u32, +struct_block: []const u8, +strings_block: []const u8, +mem_rsv_block: []const u8, + +pub const Error = error{ + InvalidMagic, + UnsupportedVersion, + InvalidStructure, +}; + +/// Parse an FDT from a raw pointer (as provided by OpenSBI) +pub fn parse(ptr: *const anyopaque) Error!Fdt { + const base: [*]const u8 = @ptrCast(ptr); + const header: *const RawHeader = @alignCast(@ptrCast(ptr)); + + // Validate magic + const magic = std.mem.bigToNative(u32, header.magic); + if (magic != MAGIC) { + return Error.InvalidMagic; + } + + const version = std.mem.bigToNative(u32, header.version); + const last_comp_version = std.mem.bigToNative(u32, header.last_comp_version); + + // We support FDT version 17 (current standard) + if (last_comp_version > 17) { + return Error.UnsupportedVersion; + } + + const totalsize = std.mem.bigToNative(u32, header.totalsize); + const off_dt_struct = std.mem.bigToNative(u32, header.off_dt_struct); + const size_dt_struct = std.mem.bigToNative(u32, header.size_dt_struct); + const off_dt_strings = std.mem.bigToNative(u32, header.off_dt_strings); + const size_dt_strings = std.mem.bigToNative(u32, header.size_dt_strings); + const off_mem_rsvmap = std.mem.bigToNative(u32, header.off_mem_rsvmap); + + return Fdt{ + .base = base, + .totalsize = totalsize, + .version = version, + .last_comp_version = last_comp_version, + .boot_cpuid_phys = std.mem.bigToNative(u32, header.boot_cpuid_phys), + .struct_block = base[off_dt_struct..][0..size_dt_struct], + .strings_block = base[off_dt_strings..][0..size_dt_strings], + .mem_rsv_block = base[off_mem_rsvmap..totalsize], + }; +} + +/// Get the root node +pub fn root(self: *const Fdt) ?Node { + if (self.struct_block.len < 4) return null; + + const token = self.readToken(0) orelse return null; + if (token != .begin_node) return null; + + var pos: usize = 4; + // Skip root node name (usually empty) + while (pos < self.struct_block.len and self.struct_block[pos] != 0) { + pos += 1; + } + pos += 1; + pos = alignUp(pos, 4); + + return Node{ + .name = "", + .fdt = self, + .struct_offset = pos, + }; +} + +/// Iterate over all nodes in the tree +pub fn nodes(self: *const Fdt) NodeIterator { + return .{ + .fdt = self, + .pos = 0, + .include_root = true, + }; +} + +/// Iterate over all nodes except the root +pub fn nodesWithoutRoot(self: *const Fdt) NodeIterator { + return .{ + .fdt = self, + .pos = 0, + .include_root = false, + }; +} + +/// Find a node by path (e.g., "/soc/uart@10000000") +pub fn findNode(self: *const Fdt, path: []const u8) ?Node { + if (path.len == 0 or path[0] != '/') return null; + + var current = self.root() orelse return null; + + // Handle root path + if (path.len == 1) return current; + + // Parse path components + var remaining = path[1..]; // skip leading '/' + while (remaining.len > 0) { + // Find next component + var end: usize = 0; + while (end < remaining.len and remaining[end] != '/') { + end += 1; + } + + const component = remaining[0..end]; + if (component.len == 0) break; + + // Find child with this name + current = current.getChild(component) orelse return null; + + // Move past this component and any '/' + remaining = if (end < remaining.len) remaining[end + 1 ..] else &[_]u8{}; + } + + return current; +} + +/// Find a node by its phandle +pub fn findByPhandle(self: *const Fdt, handle: u32) ?Node { + var iter = self.nodes(); + while (iter.next()) |node| { + if (node.phandle()) |ph| { + if (ph == handle) return node; + } + } + return null; +} + +/// Find all nodes compatible with a given string +pub fn findCompatible(self: *const Fdt, compat: []const u8) CompatibleIterator { + return .{ + .inner = self.nodesWithoutRoot(), + .compat = compat, + }; +} + +/// Find the first node compatible with a given string +pub fn findFirstCompatible(self: *const Fdt, compat: []const u8) ?Node { + var iter = self.findCompatible(compat); + return iter.next(); +} + +/// Iterate over memory reservation entries +pub fn memoryReservations(self: *const Fdt) MemoryReservationIterator { + return .{ + .data = self.mem_rsv_block, + .pos = 0, + }; +} + +/// Get the chosen node (boot parameters) +pub fn chosen(self: *const Fdt) ?Node { + return self.findNode("/chosen"); +} + +/// Get memory node(s) +pub fn memory(self: *const Fdt) ?Node { + return self.findNode("/memory") orelse self.findFirstCompatible("memory"); +} + +/// Get the CPUs node +pub fn cpus(self: *const Fdt) ?Node { + return self.findNode("/cpus"); +} + +// Internal helpers +fn readToken(self: *const Fdt, offset: usize) ?Token { + if (offset + 4 > self.struct_block.len) return null; + const val = std.mem.bigToNative(u32, @as(*const u32, @alignCast(@ptrCast(self.struct_block.ptr + offset))).*); + return @as(Token, @enumFromInt(val)); +} + +fn readU32(self: *const Fdt, offset: usize) ?u32 { + if (offset + 4 > self.struct_block.len) return null; + return std.mem.bigToNative(u32, @as(*const u32, @alignCast(@ptrCast(self.struct_block.ptr + offset))).*); +} + +fn getString(self: *const Fdt, offset: u32) ?[]const u8 { + if (offset >= self.strings_block.len) return null; + const start = self.strings_block[offset..]; + for (start, 0..) |c, i| { + if (c == 0) return start[0..i]; + } + return null; +} + +pub const CompatibleIterator = struct { + inner: NodeIterator, + compat: []const u8, + + pub fn next(self: *CompatibleIterator) ?Node { + while (self.inner.next()) |node| { + if (node.isCompatible(self.compat)) { + return node; + } + } + return null; + } +}; + +pub const MemoryReservationIterator = struct { + data: []const u8, + pos: usize, + + pub fn next(self: *MemoryReservationIterator) ?struct { address: u64, size: u64 } { + if (self.pos + 16 > self.data.len) return null; + + const entry: *const ReserveEntry = @alignCast(@ptrCast(self.data.ptr + self.pos)); + const result = entry.toNative(); + self.pos += 16; + + // End marker is all zeros + if (result.address == 0 and result.size == 0) return null; + + return result; + } +}; + +fn alignUp(value: anytype, alignment: @TypeOf(value)) @TypeOf(value) { + return (value + alignment - 1) & ~(alignment - 1); +} + +/// Helper to parse reg property with known cell sizes +pub fn parseReg(data: []const u8, address_cells: u32, size_cells: u32) RegIterator { + return .{ + .data = data, + .address_cells = address_cells, + .size_cells = size_cells, + .pos = 0, + }; +} + + diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..c3f18c3 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,47 @@ +const std = @import("std"); +const isa = @import("riscv/isa.zig"); +const Fdt = @import("Fdt.zig"); + +const UART_BASE: usize = 0x10000000; + +fn uart_put(c: u8) void { + const uart: *volatile u8 = @ptrFromInt(UART_BASE); + uart.* = c; +} + +fn print(s: []const u8) void { + for (s) |c| { + uart_put(c); + } +} + +export fn _start() linksection(".text.init") callconv(.naked) noreturn { + asm volatile ( + \\li sp, 0x88000000 + \\tail kmain + ); +} + +export fn kmain(hartid: u64, fdt_ptr: *const anyopaque) callconv(.c) noreturn { + _ = hartid; + print("Booting hydra...\n"); + + const fdt = Fdt.parse(fdt_ptr) catch { + print("Failed to parse FDT\n"); + while (true) asm volatile ("wfi"); + }; + + if (fdt.findFirstCompatible("ns16550a")) |console| { + var iter = console.getProperty("reg").?.asU32Array(); + const start = iter.next(); + const end = iter.next(); + _ = end; + if (start == 0x100) { + print("Found console\n"); + } + } + + while (true) { + asm volatile ("wfi"); + } +} diff --git a/src/riscv/isa.zig b/src/riscv/isa.zig new file mode 100644 index 0000000..e94f4ca --- /dev/null +++ b/src/riscv/isa.zig @@ -0,0 +1,26 @@ +pub const Satp = packed struct { + pub const Mode = enum(u4) { + bare = 0, + sv38 = 8, + sv48 = 9, + sv57 = 10, + sv64 = 11, + }; + + ppn: u44 = 0, + asid: u16 = 0, + mode: Mode = .bare, +}; + +pub inline fn write_satp(satp: Satp) void { + asm volatile ("csrw satp, %[val]" + : + : [val] "r" (satp), + ); +} + +pub inline fn read_satp() Satp { + return asm ("csrr %[ret], satp" + : [ret] "=r" (-> Satp), + ); +} diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..e69de29