add device platform module with ACPI support
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
//! 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 `root.zig`'s `MemoryKind` applies to memory and `arch`
|
||||
//! 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");
|
||||
|
||||
/// The hardware primitives a discovery backend needs but can't express portably.
|
||||
/// The kernel injects an implementation (the arch VMM + port I/O), so the device
|
||||
/// layer touches hardware without importing `arch` — the same discipline that lets
|
||||
/// it stay firmware-agnostic. `pioRead`/`pioWrite` take a width in bytes (1/2/4).
|
||||
pub const Hal = struct {
|
||||
mapMmio: *const fn (virt: u64, phys: u64, writable: bool) void,
|
||||
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.
|
||||
pub const ResourceKind = enum {
|
||||
/// A memory-mapped I/O window: `start` is the physical base, `len` its size.
|
||||
memory,
|
||||
/// A legacy I/O-port range: `start` is the first port, `len` the count.
|
||||
io_port,
|
||||
/// An interrupt: `start` is the global system interrupt (GSI), `len` is 1.
|
||||
irq,
|
||||
/// A range of bus numbers owned by a bridge: `start`..`start+len`.
|
||||
bus_range,
|
||||
};
|
||||
|
||||
/// 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.
|
||||
/// Kept small on purpose; refine as real drivers arrive.
|
||||
pub const DeviceClass = enum {
|
||||
/// The synthetic root every discovered device hangs beneath.
|
||||
root,
|
||||
processor,
|
||||
interrupt_controller,
|
||||
timer,
|
||||
/// A PCI(e) host bridge — the root of a PCI segment (owns an ECAM window).
|
||||
pci_host_bridge,
|
||||
/// A single PCI function.
|
||||
pci_device,
|
||||
/// A device named in the ACPI namespace (from the DSDT/SSDT), carrying a
|
||||
/// hardware ID (`_HID`) and, where static, current resource settings (`_CRS`).
|
||||
acpi_device,
|
||||
unknown,
|
||||
};
|
||||
|
||||
/// 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) | (dev << 3) | func — 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 max_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_buf: [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_buf: [8]u8 = undefined,
|
||||
hid_len: u8 = 0,
|
||||
resources: [max_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_buf[0..self.name_len];
|
||||
}
|
||||
|
||||
fn setName(self: *Device, s: []const u8) void {
|
||||
const n: u8 = @intCast(@min(s.len, self.name_buf.len));
|
||||
@memcpy(self.name_buf[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_buf[0..self.hid_len];
|
||||
}
|
||||
|
||||
pub fn setHid(self: *Device, s: []const u8) void {
|
||||
const n: u8 = @intCast(@min(s.len, self.hid_buf.len));
|
||||
@memcpy(self.hid_buf[0..n], s[0..n]);
|
||||
self.hid_len = n;
|
||||
}
|
||||
|
||||
/// Record a resource. Silently drops beyond `max_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 >= max_resources) return false;
|
||||
self.resources[self.resource_count] = .{ .kind = kind, .start = start, .len = len };
|
||||
self.resource_count += 1;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/// 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 };
|
||||
}
|
||||
|
||||
/// 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,
|
||||
dev_name: []const u8,
|
||||
) !*Device {
|
||||
const d = try self.allocator.create(Device);
|
||||
d.* = .{ .class = class, .parent = parent };
|
||||
d.setName(dev_name);
|
||||
if (parent.first_child == null) {
|
||||
parent.first_child = d;
|
||||
} else {
|
||||
var cur = parent.first_child.?;
|
||||
while (cur.next_sibling) |sib| cur = sib;
|
||||
cur.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 dumpNode(dev: *const Device, depth: usize, emit: *const fn ([]const u8) void) void {
|
||||
const indent = @min(depth * 2, 40);
|
||||
|
||||
var buf: [200]u8 = undefined;
|
||||
@memset(buf[0..indent], ' ');
|
||||
const body = if (dev.hid_len != 0)
|
||||
std.fmt.bufPrint(buf[indent..], "{s} [{s}] hid={s}\n", .{ dev.name(), @tagName(dev.class), dev.hid() }) catch return
|
||||
else
|
||||
std.fmt.bufPrint(buf[indent..], "{s} [{s}]\n", .{ dev.name(), @tagName(dev.class) }) catch return;
|
||||
emit(buf[0 .. indent + body.len]);
|
||||
|
||||
for (dev.resources[0..dev.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 = dev.first_child;
|
||||
while (child) |c| : (child = c.next_sibling) dumpNode(c, depth + 1, emit);
|
||||
}
|
||||
Reference in New Issue
Block a user