add UART console

This commit is contained in:
Lorenzo Torres 2026-01-31 15:57:54 +01:00
parent 25ec14c4a1
commit 48421fc0ef
3 changed files with 55 additions and 48 deletions

View file

@ -11,7 +11,6 @@ pub const Token = enum(u32) {
end = 0x09, end = 0x09,
}; };
/// Raw FDT header as it appears in memory (big-endian)
pub const RawHeader = extern struct { pub const RawHeader = extern struct {
magic: u32, magic: u32,
totalsize: u32, totalsize: u32,
@ -25,7 +24,6 @@ pub const RawHeader = extern struct {
size_dt_struct: u32, size_dt_struct: u32,
}; };
/// Memory reservation entry
pub const ReserveEntry = extern struct { pub const ReserveEntry = extern struct {
address: u64, address: u64,
size: u64, size: u64,
@ -38,7 +36,6 @@ pub const ReserveEntry = extern struct {
} }
}; };
/// Parsed property data
pub const Property = struct { pub const Property = struct {
name: []const u8, name: []const u8,
data: []const u8, data: []const u8,
@ -53,7 +50,6 @@ pub const Property = struct {
return std.mem.bigToNative(u64, @as(*const u64, @alignCast(@ptrCast(self.data.ptr))).*); 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 { pub fn asString(self: Property) ?[]const u8 {
if (self.data.len == 0) return null; if (self.data.len == 0) return null;
for (self.data, 0..) |c, i| { for (self.data, 0..) |c, i| {
@ -91,7 +87,7 @@ pub const StringListIterator = struct {
self.pos += 1; self.pos += 1;
} }
const end = self.pos; const end = self.pos;
self.pos += 1; // skip null terminator self.pos += 1;
if (start == end) return null; if (start == end) return null;
return self.data[start..end]; return self.data[start..end];
@ -130,15 +126,11 @@ pub const U64ArrayIterator = struct {
} }
}; };
/// FDT node with its properties and children
pub const Node = struct { pub const Node = struct {
/// Full path to this node (e.g., "/soc/uart@10000000")
name: []const u8, name: []const u8,
fdt: *const Fdt, fdt: *const Fdt,
/// Offset in structure block where this node's content starts (after name)
struct_offset: usize, struct_offset: usize,
/// Get the unit address part of the node name (after '@')
pub fn unitAddress(self: Node) ?[]const u8 { pub fn unitAddress(self: Node) ?[]const u8 {
for (self.name, 0..) |c, i| { for (self.name, 0..) |c, i| {
if (c == '@') return self.name[i + 1 ..]; if (c == '@') return self.name[i + 1 ..];
@ -146,7 +138,6 @@ pub const Node = struct {
return null; return null;
} }
/// Get the base name (before '@')
pub fn baseName(self: Node) []const u8 { pub fn baseName(self: Node) []const u8 {
for (self.name, 0..) |c, i| { for (self.name, 0..) |c, i| {
if (c == '@') return self.name[0..i]; if (c == '@') return self.name[0..i];
@ -154,7 +145,6 @@ pub const Node = struct {
return self.name; return self.name;
} }
/// Iterate over all properties of this node
pub fn properties(self: Node) PropertyIterator { pub fn properties(self: Node) PropertyIterator {
return .{ return .{
.fdt = self.fdt, .fdt = self.fdt,
@ -162,7 +152,6 @@ pub const Node = struct {
}; };
} }
/// Get a specific property by name
pub fn getProperty(self: Node, name: []const u8) ?Property { pub fn getProperty(self: Node, name: []const u8) ?Property {
var iter = self.properties(); var iter = self.properties();
while (iter.next()) |prop| { while (iter.next()) |prop| {
@ -173,7 +162,6 @@ pub const Node = struct {
return null; return null;
} }
/// Iterate over direct children of this node
pub fn children(self: Node) ChildIterator { pub fn children(self: Node) ChildIterator {
// Skip to the end of properties to find children // Skip to the end of properties to find children
var pos = self.struct_offset; var pos = self.struct_offset;
@ -202,7 +190,6 @@ pub const Node = struct {
}; };
} }
/// Find a child node by name
pub fn getChild(self: Node, name: []const u8) ?Node { pub fn getChild(self: Node, name: []const u8) ?Node {
var iter = self.children(); var iter = self.children();
while (iter.next()) |child| { while (iter.next()) |child| {
@ -213,13 +200,11 @@ pub const Node = struct {
return null; return null;
} }
/// Get the 'compatible' property as a string list
pub fn compatible(self: Node) ?StringListIterator { pub fn compatible(self: Node) ?StringListIterator {
const prop = self.getProperty("compatible") orelse return null; const prop = self.getProperty("compatible") orelse return null;
return prop.asStringList(); return prop.asStringList();
} }
/// Check if node is compatible with given string
pub fn isCompatible(self: Node, compat: []const u8) bool { pub fn isCompatible(self: Node, compat: []const u8) bool {
var iter = self.compatible() orelse return false; var iter = self.compatible() orelse return false;
while (iter.next()) |c| { while (iter.next()) |c| {
@ -228,38 +213,31 @@ pub const Node = struct {
return false; 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 { pub fn reg(self: Node) ?[]const u8 {
const prop = self.getProperty("reg") orelse return null; const prop = self.getProperty("reg") orelse return null;
return prop.data; return prop.data;
} }
/// Get the 'status' property
pub fn status(self: Node) ?[]const u8 { pub fn status(self: Node) ?[]const u8 {
const prop = self.getProperty("status") orelse return null; const prop = self.getProperty("status") orelse return null;
return prop.asString(); return prop.asString();
} }
/// Check if node is enabled (status = "okay" or "ok" or no status property)
pub fn isEnabled(self: Node) bool { pub fn isEnabled(self: Node) bool {
const s = self.status() orelse return true; const s = self.status() orelse return true;
return std.mem.eql(u8, s, "okay") or std.mem.eql(u8, s, "ok"); 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 { pub fn addressCells(self: Node) u32 {
const prop = self.getProperty("#address-cells") orelse return 2; const prop = self.getProperty("#address-cells") orelse return 2;
return prop.asU32() orelse 2; return prop.asU32() orelse 2;
} }
/// Get #size-cells (defaults to 1 if not present)
pub fn sizeCells(self: Node) u32 { pub fn sizeCells(self: Node) u32 {
const prop = self.getProperty("#size-cells") orelse return 1; const prop = self.getProperty("#size-cells") orelse return 1;
return prop.asU32() orelse 1; return prop.asU32() orelse 1;
} }
/// Get phandle of this node
pub fn phandle(self: Node) ?u32 { pub fn phandle(self: Node) ?u32 {
// Try both 'phandle' and legacy 'linux,phandle' // Try both 'phandle' and legacy 'linux,phandle'
if (self.getProperty("phandle")) |prop| { if (self.getProperty("phandle")) |prop| {
@ -360,7 +338,6 @@ pub const ChildIterator = struct {
} }
}; };
/// Iterator for all nodes in the tree (depth-first)
pub const NodeIterator = struct { pub const NodeIterator = struct {
fdt: *const Fdt, fdt: *const Fdt,
pos: usize, pos: usize,
@ -465,7 +442,6 @@ pub const Error = error{
InvalidStructure, InvalidStructure,
}; };
/// Parse an FDT from a raw pointer (as provided by OpenSBI)
pub fn parse(ptr: *const anyopaque) Error!Fdt { pub fn parse(ptr: *const anyopaque) Error!Fdt {
const base: [*]const u8 = @ptrCast(ptr); const base: [*]const u8 = @ptrCast(ptr);
const header: *const RawHeader = @alignCast(@ptrCast(ptr)); const header: *const RawHeader = @alignCast(@ptrCast(ptr));
@ -503,7 +479,6 @@ pub fn parse(ptr: *const anyopaque) Error!Fdt {
}; };
} }
/// Get the root node
pub fn root(self: *const Fdt) ?Node { pub fn root(self: *const Fdt) ?Node {
if (self.struct_block.len < 4) return null; if (self.struct_block.len < 4) return null;
@ -525,7 +500,6 @@ pub fn root(self: *const Fdt) ?Node {
}; };
} }
/// Iterate over all nodes in the tree
pub fn nodes(self: *const Fdt) NodeIterator { pub fn nodes(self: *const Fdt) NodeIterator {
return .{ return .{
.fdt = self, .fdt = self,
@ -534,7 +508,6 @@ pub fn nodes(self: *const Fdt) NodeIterator {
}; };
} }
/// Iterate over all nodes except the root
pub fn nodesWithoutRoot(self: *const Fdt) NodeIterator { pub fn nodesWithoutRoot(self: *const Fdt) NodeIterator {
return .{ return .{
.fdt = self, .fdt = self,
@ -543,7 +516,6 @@ pub fn nodesWithoutRoot(self: *const Fdt) NodeIterator {
}; };
} }
/// Find a node by path (e.g., "/soc/uart@10000000")
pub fn findNode(self: *const Fdt, path: []const u8) ?Node { pub fn findNode(self: *const Fdt, path: []const u8) ?Node {
if (path.len == 0 or path[0] != '/') return null; if (path.len == 0 or path[0] != '/') return null;
@ -574,7 +546,6 @@ pub fn findNode(self: *const Fdt, path: []const u8) ?Node {
return current; return current;
} }
/// Find a node by its phandle
pub fn findByPhandle(self: *const Fdt, handle: u32) ?Node { pub fn findByPhandle(self: *const Fdt, handle: u32) ?Node {
var iter = self.nodes(); var iter = self.nodes();
while (iter.next()) |node| { while (iter.next()) |node| {
@ -585,7 +556,6 @@ pub fn findByPhandle(self: *const Fdt, handle: u32) ?Node {
return null; return null;
} }
/// Find all nodes compatible with a given string
pub fn findCompatible(self: *const Fdt, compat: []const u8) CompatibleIterator { pub fn findCompatible(self: *const Fdt, compat: []const u8) CompatibleIterator {
return .{ return .{
.inner = self.nodesWithoutRoot(), .inner = self.nodesWithoutRoot(),
@ -593,13 +563,11 @@ pub fn findCompatible(self: *const Fdt, compat: []const u8) CompatibleIterator {
}; };
} }
/// Find the first node compatible with a given string
pub fn findFirstCompatible(self: *const Fdt, compat: []const u8) ?Node { pub fn findFirstCompatible(self: *const Fdt, compat: []const u8) ?Node {
var iter = self.findCompatible(compat); var iter = self.findCompatible(compat);
return iter.next(); return iter.next();
} }
/// Iterate over memory reservation entries
pub fn memoryReservations(self: *const Fdt) MemoryReservationIterator { pub fn memoryReservations(self: *const Fdt) MemoryReservationIterator {
return .{ return .{
.data = self.mem_rsv_block, .data = self.mem_rsv_block,
@ -607,17 +575,14 @@ pub fn memoryReservations(self: *const Fdt) MemoryReservationIterator {
}; };
} }
/// Get the chosen node (boot parameters)
pub fn chosen(self: *const Fdt) ?Node { pub fn chosen(self: *const Fdt) ?Node {
return self.findNode("/chosen"); return self.findNode("/chosen");
} }
/// Get memory node(s)
pub fn memory(self: *const Fdt) ?Node { pub fn memory(self: *const Fdt) ?Node {
return self.findNode("/memory") orelse self.findFirstCompatible("memory"); return self.findNode("/memory") orelse self.findFirstCompatible("memory");
} }
/// Get the CPUs node
pub fn cpus(self: *const Fdt) ?Node { pub fn cpus(self: *const Fdt) ?Node {
return self.findNode("/cpus"); return self.findNode("/cpus");
} }
@ -679,7 +644,6 @@ fn alignUp(value: anytype, alignment: @TypeOf(value)) @TypeOf(value) {
return (value + alignment - 1) & ~(alignment - 1); 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 { pub fn parseReg(data: []const u8, address_cells: u32, size_cells: u32) RegIterator {
return .{ return .{
.data = data, .data = data,

48
src/drivers/Console.zig Normal file
View file

@ -0,0 +1,48 @@
const std = @import("std");
const Fdt = @import("../Fdt.zig");
const Console = @This();
pub const Writer = struct {
base: *volatile u8,
interface: std.Io.Writer,
pub fn drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize {
_ = splat;
const self: *Writer = @fieldParentPtr("interface", io_w);
self.base.* = data[0][0];
return data.len;
}
};
mmio: *volatile u8,
pub fn init(fdt: Fdt) ?Console {
if (fdt.findFirstCompatible("ns16550a")) |console| {
var iter = console.getProperty("reg").?.asU32Array();
_ = iter.next();
const start = iter.next().?;
return .{
.mmio = @ptrFromInt(@as(usize, @intCast(start))),
};
}
return null;
}
pub fn writer(self: *const Console) Writer {
return .{
.base = self.mmio,
.interface = std.Io.Writer {
.buffer = &[_]u8{},
.vtable = &.{.drain = Writer.drain},
},
};
}
pub fn print(self: *const Console, comptime s: []const u8, args: anytype) void {
var w = self.writer();
w.interface.print(s, args) catch {
@panic("Failed to print debug message.\n");
};
}

View file

@ -1,9 +1,12 @@
const std = @import("std"); const std = @import("std");
const isa = @import("riscv/isa.zig"); const isa = @import("riscv/isa.zig");
const Fdt = @import("Fdt.zig"); const Fdt = @import("Fdt.zig");
const Console = @import("drivers/Console.zig");
const UART_BASE: usize = 0x10000000; const UART_BASE: usize = 0x10000000;
var console: Console = undefined;
fn uart_put(c: u8) void { fn uart_put(c: u8) void {
const uart: *volatile u8 = @ptrFromInt(UART_BASE); const uart: *volatile u8 = @ptrFromInt(UART_BASE);
uart.* = c; uart.* = c;
@ -24,22 +27,14 @@ export fn _start() linksection(".text.init") callconv(.naked) noreturn {
export fn kmain(hartid: u64, fdt_ptr: *const anyopaque) callconv(.c) noreturn { export fn kmain(hartid: u64, fdt_ptr: *const anyopaque) callconv(.c) noreturn {
_ = hartid; _ = hartid;
print("Booting hydra...\n");
const fdt = Fdt.parse(fdt_ptr) catch { const fdt = Fdt.parse(fdt_ptr) catch {
print("Failed to parse FDT\n");
while (true) asm volatile ("wfi"); while (true) asm volatile ("wfi");
}; };
if (fdt.findFirstCompatible("ns16550a")) |console| { console = Console.init(fdt).?;
var iter = console.getProperty("reg").?.asU32Array();
const start = iter.next(); console.print("booting hydra...\n", .{});
const end = iter.next();
_ = end;
if (start == 0x100) {
print("Found console\n");
}
}
while (true) { while (true) {
asm volatile ("wfi"); asm volatile ("wfi");