//! The backend-agnostic device model. //! //! Discovery backends (ACPI today, device-tree later) translate their native //! hardware description into this one shape, so the rest of the kernel walks a //! plain `Device` tree without knowing which firmware described the machine — //! the same discipline `ps2-library.zig`'s `MemoryKind` applies to memory and `architecture` //! applies to the CPU. //! //! This is deliberately minimal: enough to *describe* what was discovered (a //! named node, its class, and its hardware resources) and where it sits in the //! bus hierarchy. Driver matching, families, and probing are a later layer built //! on top of this — nothing here presumes them. const std = @import("std"); const device_abi = @import("device-abi"); /// The hardware primitives a discovery backend needs but can't express portably. /// The kernel injects an implementation (the architecture VMM + port I/O), so the device /// layer touches hardware without importing `architecture` — the same discipline that lets /// it stay firmware-agnostic. `pioRead`/`pioWrite` take a width in bytes (1/2/4). pub const Hal = struct { /// Map a physical MMIO range and return the virtual address to reach it at. /// The device layer dereferences the returned address and never learns how /// the kernel places it (identity, physmap, a window — the kernel's choice). mapMmio: *const fn (physical: u64, len: u64, writable: bool) u64, pioRead: *const fn (width: u8, port: u16) u32, pioWrite: *const fn (width: u8, port: u16, value: u32) void, }; /// The kind of hardware resource a device occupies. Canonically defined by the /// device ABI (system/devices/device-abi.zig) and re-exported here, so the kernel's /// internal tree and the descriptors it hands user space share one enum. pub const ResourceKind = device_abi.ResourceKind; /// One hardware resource claimed by a device. pub const Resource = struct { kind: ResourceKind, start: u64, len: u64, }; /// A coarse classification of a device, independent of the describing firmware. /// Canonically defined by the device ABI (system/devices/device-abi.zig) and /// re-exported here — the enum a driver matches on and the one the kernel classifies /// with are the same type. Kept small on purpose; refine as real drivers arrive. pub const DeviceClass = device_abi.DeviceClass; /// Firmware-independent identity. Each backend fills only the fields it knows; /// the rest stay null. The generic layer never branches on *how* an id was /// obtained, only on its value. pub const Ids = struct { /// The device's ACPI hardware ID (`_HID`), EISA-encoded into 4 bytes, when applicable. acpi_hid: ?u32 = null, /// PCI configuration-space identity, when this node is a PCI function. pci_vendor: ?u16 = null, pci_device: ?u16 = null, /// PCI class/subclass/prog-if packed as 0xCCSSPP. pci_class: ?u24 = null, /// PCI bus/device/function packed as (bus << 8) | (device << 3) | function — the key /// the ACPI address (`_ADR`) merge uses to match a namespace device to this node. pci_bdf: ?u16 = null, }; /// Upper bound on resources tracked per device (6 PCI BARs + a couple of IRQs is /// the busy case). Stored inline so a device is a single allocation. pub const maximum_resources = 8; /// One node in the device tree. Nodes are individually heap-allocated and linked /// intrusively (first-child / next-sibling), the classic device-tree layout — /// no per-node dynamic arrays to manage. pub const Device = struct { name_buffer: [24]u8 = undefined, name_len: u8 = 0, class: DeviceClass = .unknown, ids: Ids = .{}, /// Human-readable hardware id (e.g. "PNP0A03"), when known. Backed inline like /// `name`; empty when unset. The generic layer stores/prints it without knowing /// how a backend encoded it. hid_buffer: [8]u8 = undefined, hid_len: u8 = 0, resources: [maximum_resources]Resource = undefined, resource_count: u8 = 0, parent: ?*Device = null, first_child: ?*Device = null, next_sibling: ?*Device = null, /// The device's short name (e.g. "cpu0", "pci0:00:1f.0"). Backed by an inline /// buffer, so it stays valid for the life of the node with no extra allocation. pub fn name(self: *const Device) []const u8 { return self.name_buffer[0..self.name_len]; } fn setName(self: *Device, s: []const u8) void { const n: u8 = @intCast(@min(s.len, self.name_buffer.len)); @memcpy(self.name_buffer[0..n], s[0..n]); self.name_len = n; } /// The device's hardware id string, or empty if none is set. pub fn hid(self: *const Device) []const u8 { return self.hid_buffer[0..self.hid_len]; } pub fn setHid(self: *Device, s: []const u8) void { const n: u8 = @intCast(@min(s.len, self.hid_buffer.len)); @memcpy(self.hid_buffer[0..n], s[0..n]); self.hid_len = n; } /// Record a resource. Silently drops beyond `maximum_resources` — discovery logs /// the truncation rather than failing the whole tree. pub fn addResource(self: *Device, kind: ResourceKind, start: u64, len: u64) bool { if (self.resource_count >= maximum_resources) return false; self.resources[self.resource_count] = .{ .kind = kind, .start = start, .len = len }; self.resource_count += 1; return true; } /// The device's first resource of `kind`, or null — e.g. a timer's MMIO base. pub fn firstResource(self: *const Device, kind: ResourceKind) ?Resource { for (self.resources[0..self.resource_count]) |r| { if (r.kind == kind) return r; } return null; } }; /// Owns the discovered device tree and the allocator its nodes came from. pub const DeviceTree = struct { allocator: std.mem.Allocator, root: *Device, /// Create a tree with just the synthetic root node. pub fn init(allocator: std.mem.Allocator) !DeviceTree { const root = try allocator.create(Device); root.* = .{ .class = .root }; root.setName("root"); return .{ .allocator = allocator, .root = root }; } /// The first device of `class` anywhere in the tree (depth-first), or null — /// how the kernel pulls e.g. the HPET or IOAPIC MMIO base out of discovery. pub fn firstOfClass(self: *const DeviceTree, class: DeviceClass) ?*Device { return firstOfClassIn(self.root, class); } /// Allocate a device and append it under `parent`, returning it so the caller /// can attach resources/ids. Appended at the tail so a dump reads in the order /// devices were discovered. pub fn addChild( self: *DeviceTree, parent: *Device, class: DeviceClass, device_name: []const u8, ) !*Device { const d = try self.allocator.create(Device); d.* = .{ .class = class, .parent = parent }; d.setName(device_name); if (parent.first_child == null) { parent.first_child = d; } else { var current = parent.first_child.?; while (current.next_sibling) |sib| current = sib; current.next_sibling = d; } return d; } /// Walk the tree depth-first, emitting an indented, human-readable listing. /// `emit` is a raw byte sink (e.g. the serial `debugWrite`), so this stays /// independent of the kernel console. pub fn dump(self: *const DeviceTree, emit: *const fn ([]const u8) void) void { dumpNode(self.root, 0, emit); } }; fn firstOfClassIn(node: *Device, class: DeviceClass) ?*Device { var child = node.first_child; while (child) |c| : (child = c.next_sibling) { if (c.class == class) return c; if (firstOfClassIn(c, class)) |found| return found; } return null; } fn dumpNode(device: *const Device, depth: usize, emit: *const fn ([]const u8) void) void { const indent = @min(depth * 2, 40); // Raw values only. Decoding a _HID or a PCI class code to a human name is a // user-space concern (the device manager owns those taxonomies); the // microkernel's boot dump reports the tree structure and the raw ids, and // does no device decoding of its own. var buffer: [200]u8 = undefined; @memset(buffer[0..indent], ' '); const body = if (device.hid_len != 0) std.fmt.bufPrint(buffer[indent..], "{s} [{s}] hid={s}\n", .{ device.name(), @tagName(device.class), device.hid() }) catch return else std.fmt.bufPrint(buffer[indent..], "{s} [{s}]\n", .{ device.name(), @tagName(device.class) }) catch return; emit(buffer[0 .. indent + body.len]); // For a PCI function, print the raw packed class code (base/subclass/prog-IF). if (device.ids.pci_class) |packed_code| { var cbuf: [200]u8 = undefined; const pad = @min(indent + 2, 42); @memset(cbuf[0..pad], ' '); const cline = std.fmt.bufPrint(cbuf[pad..], "pci-class 0x{x:0>6}\n", .{packed_code}) catch return; emit(cbuf[0 .. pad + cline.len]); } for (device.resources[0..device.resource_count]) |r| { var rbuf: [200]u8 = undefined; const pad = @min(indent + 2, 42); @memset(rbuf[0..pad], ' '); const rline = std.fmt.bufPrint( rbuf[pad..], "- {s} 0x{x} len 0x{x}\n", .{ @tagName(r.kind), r.start, r.len }, ) catch continue; emit(rbuf[0 .. pad + rline.len]); } var child = device.first_child; while (child) |c| : (child = c.next_sibling) dumpNode(c, depth + 1, emit); }