Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5d15cefcb | ||
|
|
fd96a35eb9 | ||
|
|
e3fe3f3f45 | ||
|
|
60da667b42 | ||
|
|
36145e623b | ||
|
|
565415327d | ||
|
|
bf6bdb389d |
@@ -347,6 +347,9 @@ pub fn build(b: *std.Build) void {
|
||||
const ps2_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-mouse", "system/drivers/ps2-bus/mouse.zig");
|
||||
const usb_xhci_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "usb-xhci-bus", "system/drivers/usb-xhci-bus/usb-xhci-bus.zig");
|
||||
const pci_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "pci-bus", "system/drivers/pci-bus/pci-bus.zig");
|
||||
// The PCI bus driver decodes each function's class triple to human names in its
|
||||
// boot log (class/subclass/prog-IF), so pull in the shared pci-class reference.
|
||||
pci_bus_exe.root_module.addImport("pci-class", pci_class_module);
|
||||
// A test fixture, not a real driver: hellos to the device manager, then faults —
|
||||
// what the driver-restart scenario drives the crash-loop cap with.
|
||||
const crash_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "crash-test", "system/services/crash-test/crash-test.zig");
|
||||
@@ -367,6 +370,8 @@ pub fn build(b: *std.Build) void {
|
||||
const discovery_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "discovery", discovery_source);
|
||||
if (discovery == .acpi) discovery_exe.root_module.addImport("aml", aml_module);
|
||||
const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "device-manager", "system/services/device-manager/device-manager.zig");
|
||||
// Names the xHCI PCI class triple from the shared taxonomy instead of a bare 0x0C0330.
|
||||
device_manager_exe.root_module.addImport("pci-class", pci_class_module);
|
||||
// The input service and its exercisers: the fan-out server, a hardware-free synthetic
|
||||
// source, and a subscriber that doubles as the `input` test's oracle. See docs/input.md.
|
||||
const input_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "input", "system/services/input/input.zig");
|
||||
|
||||
@@ -142,6 +142,32 @@ conventions above — `snake_case` — because it's an identifier, not a filenam
|
||||
*directory* (`system/services/init`, `library/runtime`), with the repeated leaf
|
||||
resolving away. See the repository-layout section of [README.md](README.md).
|
||||
|
||||
## Named values, not magic numbers
|
||||
|
||||
The naming rule has a twin: **a value with meaning gets a name, too.** The same
|
||||
principle drives both — a reader should never have to leave the code to understand it.
|
||||
An abbreviated *name* forces a reader to guess; a bare *number* forces them worse, out
|
||||
to a spec or a header or a comment three files away, to learn what the value even *is*.
|
||||
If `0x0C` is the PCI serial-bus class, the code says `BaseClass.serial_bus`, not `0x0C`;
|
||||
if `0x04` is the ACPI IRQ resource descriptor, it says `SmallResourceType.irq`, not
|
||||
`0x04`. The number is an implementation detail of the name — recorded once, where the
|
||||
name is defined, and never spelled again at a use site.
|
||||
|
||||
**Prefer an `enum`** when the values form a set (device classes, AML opcodes, resource
|
||||
descriptor types, states): the type then also says *which* set a value belongs to, and
|
||||
the compiler rejects a value from the wrong one. A lone `pub const` with a descriptive
|
||||
name suffices for a one-off (`const large_descriptor_bit = 0x80`). Reach for the enum
|
||||
the moment code elsewhere compares against, packs, or produces the value — a packed PCI
|
||||
class triple is written from named parts (`.serial_bus`, `.usb`, `.xhci`), never as
|
||||
`0x0C_03_30` under a comment that decodes the bytes.
|
||||
|
||||
The exceptions are the numbers that carry no hidden meaning: `0` and `1` as plain zero
|
||||
and one, an index step, a field width, a bit shift. `x + 1`, `buffer[0]`, and `<< 8`
|
||||
need no christening — there is nothing to look up. The test is exactly the naming test:
|
||||
*would a reader have to look this up to know what it means?* If yes, name it. This is
|
||||
what `opcodes.zig`'s `*_opcode` constants, `acpi-ids`'s `HardwareId`, and `pci-class`'s
|
||||
class enums already are — reference data defined once and named everywhere it is used.
|
||||
|
||||
## Why acronyms are the line
|
||||
|
||||
Because an acronym has no letters to restore. `MMIO` doesn't become "memory mapped
|
||||
|
||||
@@ -131,7 +131,7 @@ branch is green; keep branches; push everything.
|
||||
race). The `acpi-ps2` scenario proves report → spawn → ps2-bus attaches
|
||||
its keyboard; `ioport` retargeted to the acpi-tables I/O window (the
|
||||
kernel-built PS/2 node is gone). Suite 58/58.
|
||||
- [ ] **merge** `feat/acpi-service` → main, push — **loop ends here**.
|
||||
- [x] **merge** `feat/acpi-service` → main, push (merged 2026-07-13) — **discovery migration complete**.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+1
-373
@@ -17,7 +17,6 @@
|
||||
const std = @import("std");
|
||||
const boot_handoff = @import("boot-handoff");
|
||||
const abi = @import("abi");
|
||||
const acpi_ids = @import("acpi-ids");
|
||||
const parameters = @import("parameters");
|
||||
const device_model = @import("device-model.zig");
|
||||
const aml = @import("aml/aml.zig");
|
||||
@@ -409,9 +408,7 @@ pub fn discover(rsdp_physical: u64, memory_regions: []const boot_handoff.MemoryR
|
||||
// tree (M20.3): the ring-3 acpi service claims the acpi-tables node
|
||||
// (published below), re-parses the same blobs, and registers + reports
|
||||
// the _HID devices itself. The kernel keeps the namespace only for the
|
||||
// \_S5 sleep type above. The device-building helpers below
|
||||
// (wireAcpiDevices and friends) are retained but unreferenced — a
|
||||
// focused dead-code sweep follows the migration.
|
||||
// \_S5 sleep type above.
|
||||
} else |_| {
|
||||
// AML parse failed (e.g. out of memory); power stays best-effort with
|
||||
// whatever the FADT alone provided.
|
||||
@@ -816,342 +813,6 @@ fn parseDmar(hal: Hal, header: *const SystemDescriptorTableHeader) void {
|
||||
}
|
||||
}
|
||||
|
||||
// --- AML namespace -> generic device tree -----------------------------------
|
||||
|
||||
/// The PCI bus context while descending the ACPI namespace: the generic host
|
||||
/// bridge whose children ACPI address (`_ADR`) devices resolve against, and the bus number.
|
||||
const PciContext = struct { bridge: *device_model.Device, bus: u8 };
|
||||
|
||||
/// Mirror the ACPI namespace's Device objects into the generic tree, *merging*
|
||||
/// them with the PCI-enumerated nodes: a PCI root bridge (`PNP0A03`/`PNP0A08`)
|
||||
/// folds onto the existing `pci_host_bridge`, and each addressed (`_ADR`) device folds onto
|
||||
/// the matching PCI function (annotating it with the ACPI hardware ID (`_HID`) and nesting the
|
||||
/// ACPI-only children — keyboard, RTC, … — beneath it). Namespace devices with no
|
||||
/// PCI match land under a synthetic `acpi` node.
|
||||
fn wireAcpiDevices(device_tree: *DeviceTree, aml_namespace: *aml.Namespace, hal: Hal) !void {
|
||||
var arena = std.heap.ArenaAllocator.init(device_tree.allocator);
|
||||
defer arena.deinit();
|
||||
var interpreter = aml.Interpreter.init(aml_namespace, .{
|
||||
.mapMmio = hal.mapMmio,
|
||||
.pioRead = hal.pioRead,
|
||||
.pioWrite = hal.pioWrite,
|
||||
}, arena.allocator());
|
||||
|
||||
const acpi_root = try device_tree.addChild(device_tree.root, .unknown, "acpi");
|
||||
try mirrorDevices(device_tree, aml_namespace.root, acpi_root, null, &interpreter);
|
||||
}
|
||||
|
||||
fn mirrorDevices(device_tree: *DeviceTree, node: *aml.Node, parent_device: *device_model.Device, context: ?PciContext, interpreter: *aml.Interpreter) (error{OutOfMemory})!void {
|
||||
var child = node.first_child;
|
||||
while (child) |c| : (child = c.next_sibling) {
|
||||
if (c.kind != .device) {
|
||||
// A scope — the System Bus (\_SB), General Purpose Events (\_GPE), … —
|
||||
// descend without adding a node.
|
||||
try mirrorDevices(device_tree, c, parent_device, context, interpreter);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip devices the firmware reports as not present (via a device-status (`_STA`) method),
|
||||
// along with their whole subtree — per the ACPI rules.
|
||||
if (!devicePresent(interpreter, c)) continue;
|
||||
|
||||
var mirrored_device: *device_model.Device = undefined;
|
||||
var child_context = context;
|
||||
|
||||
if (isPciRootNode(c)) {
|
||||
// The PCI root bridge folds onto the generic host bridge.
|
||||
mirrored_device = matchHostBridge(device_tree) orelse
|
||||
try device_tree.addChild(parent_device, .acpi_device, &c.segment);
|
||||
child_context = .{ .bridge = mirrored_device, .bus = 0 };
|
||||
} else {
|
||||
// An addressed device folds onto its matching PCI function; anything
|
||||
// else becomes a fresh node under the current parent.
|
||||
mirrored_device = pick: {
|
||||
if (context) |pc| {
|
||||
if (readAdr(c)) |adr| {
|
||||
if (findPciNode(pc.bridge, pc.bus, adr)) |pnode| break :pick pnode;
|
||||
}
|
||||
}
|
||||
break :pick try device_tree.addChild(parent_device, .acpi_device, &c.segment);
|
||||
};
|
||||
}
|
||||
|
||||
applyHid(mirrored_device, c, interpreter);
|
||||
applyCrs(mirrored_device, c, interpreter);
|
||||
try mirrorDevices(device_tree, c, mirrored_device, child_context, interpreter);
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a device's status (`_STA`) to decide if it is present. An absent status
|
||||
/// (`_STA`) means present by default; an evaluation failure is treated as present too (we'd
|
||||
/// rather over-report than hide a device we couldn't introspect).
|
||||
fn devicePresent(interpreter: *aml.Interpreter, node: *aml.Node) bool {
|
||||
const sta = aml.Namespace.childOf(node, seg4("_STA")) orelse return true;
|
||||
const obj = interpreter.evaluate(sta, &.{}) catch return true;
|
||||
const status = obj.asInteger() catch return true;
|
||||
return (status & 0x01) != 0; // bit 0 = present
|
||||
}
|
||||
|
||||
/// The first PCI host bridge in the generic tree (segment 0).
|
||||
fn matchHostBridge(device_tree: *DeviceTree) ?*device_model.Device {
|
||||
var c = device_tree.root.first_child;
|
||||
while (c) |ch| : (c = ch.next_sibling) {
|
||||
if (ch.class == .pci_host_bridge) return ch;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// The PCI function node under `bridge` at the address the device's address object
|
||||
/// (`_ADR`) names (device/function on
|
||||
/// `bus`), or null.
|
||||
fn findPciNode(bridge: *device_model.Device, bus: u8, adr: u32) ?*device_model.Device {
|
||||
const device: u16 = @truncate((adr >> 16) & 0x1F);
|
||||
const function: u16 = @truncate(adr & 0x7);
|
||||
const target: u16 = (@as(u16, bus) << 8) | (device << 3) | function;
|
||||
var c = bridge.first_child;
|
||||
while (c) |ch| : (c = ch.next_sibling) {
|
||||
if (ch.ids.pci_bdf) |bdf| {
|
||||
if (bdf == target) return ch;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// A device's address (`_ADR`) — a static integer Name — or null.
|
||||
fn readAdr(node: *aml.Node) ?u32 {
|
||||
const n = aml.Namespace.childOf(node, seg4("_ADR")) orelse return null;
|
||||
if (n.kind != .name) return null;
|
||||
var p: usize = 0;
|
||||
return @truncate(readIntObj(n.value, &p) orelse return null);
|
||||
}
|
||||
|
||||
/// Whether a `_HID` string names a PCI(e) host bridge.
|
||||
fn isPciRootHid(hid: []const u8) bool {
|
||||
const id = acpi_ids.HardwareId.fromHid(hid) orelse return false;
|
||||
return id == .pci_bus or id == .pci_express_root_bridge;
|
||||
}
|
||||
|
||||
/// Whether a namespace device is a PCI(e) host bridge. A packed EISA id is decoded
|
||||
/// to its string form first, so both encodings answer through the one registry.
|
||||
fn isPciRootNode(node: *aml.Node) bool {
|
||||
const hid = aml.Namespace.childOf(node, seg4("_HID")) orelse return false;
|
||||
if (hid.kind != .name or hid.value.len == 0) return false;
|
||||
switch (hid.value[0]) {
|
||||
0x00, 0x01, 0xFF, 0x0A, 0x0B, 0x0C, 0x0E => {
|
||||
var p: usize = 0;
|
||||
const n = readIntObj(hid.value, &p) orelse return false;
|
||||
var buffer: [8]u8 = undefined;
|
||||
return isPciRootHid(eisaIdToStr(@truncate(n), &buffer));
|
||||
},
|
||||
0x0D => return isPciRootHid(cstr(hid.value[1..])),
|
||||
else => return false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a device's hardware ID (`_HID`) into the generic device: an integer decodes as an EISA
|
||||
/// id ("PNP0A03"), a string is taken verbatim. Handles both the common static
|
||||
/// Name form and a Method form (evaluated).
|
||||
fn applyHid(device: *device_model.Device, node: *aml.Node, interpreter: *aml.Interpreter) void {
|
||||
const hid = aml.Namespace.childOf(node, seg4("_HID")) orelse return;
|
||||
if (hid.kind == .method) {
|
||||
const obj = interpreter.evaluate(hid, &.{}) catch return;
|
||||
switch (obj) {
|
||||
.integer => |n| setEisaHid(device, @truncate(n)),
|
||||
.string => |s| device.setHid(s),
|
||||
else => {},
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (hid.kind != .name or hid.value.len == 0) return;
|
||||
const v = hid.value;
|
||||
switch (v[0]) {
|
||||
0x00, 0x01, 0xFF, 0x0A, 0x0B, 0x0C, 0x0E => {
|
||||
var p: usize = 0;
|
||||
const n = readIntObj(v, &p) orelse return;
|
||||
setEisaHid(device, @truncate(n));
|
||||
},
|
||||
0x0D => device.setHid(cstr(v[1..])), // StringPrefix
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn setEisaHid(device: *device_model.Device, id: u32) void {
|
||||
device.ids.acpi_hid = id;
|
||||
var buffer: [8]u8 = undefined;
|
||||
device.setHid(eisaIdToStr(id, &buffer));
|
||||
}
|
||||
|
||||
/// Parse a device's current resource settings (`_CRS`). The evaluator handles both the static
|
||||
/// `Buffer` form (a `Name`) and the method form uniformly, yielding the
|
||||
/// ResourceTemplate bytes we then decode.
|
||||
fn applyCrs(device: *device_model.Device, node: *aml.Node, interpreter: *aml.Interpreter) void {
|
||||
const crs = aml.Namespace.childOf(node, seg4("_CRS")) orelse return;
|
||||
const obj = interpreter.evaluate(crs, &.{}) catch return;
|
||||
const buffer = switch (obj) {
|
||||
.buffer => |b| b,
|
||||
else => return,
|
||||
};
|
||||
parseResourceTemplate(device, buffer);
|
||||
}
|
||||
|
||||
/// Walk a ResourceTemplate byte list, adding recognised descriptors as resources.
|
||||
fn parseResourceTemplate(device: *device_model.Device, bytes: []const u8) void {
|
||||
var i: usize = 0;
|
||||
while (i < bytes.len) {
|
||||
const tag = bytes[i];
|
||||
if (tag & 0x80 == 0) {
|
||||
// Small descriptor: length in low 3 bits, type in bits [6:3].
|
||||
const len: usize = tag & 0x07;
|
||||
const body = i + 1;
|
||||
if (body + len > bytes.len) break;
|
||||
switch ((tag >> 3) & 0x0F) {
|
||||
0x04 => if (len >= 2) { // IRQ: a 16-bit mask, one resource per set bit
|
||||
const mask = @as(u16, bytes[body]) | (@as(u16, bytes[body + 1]) << 8);
|
||||
var b: usize = 0;
|
||||
while (b < 16) : (b += 1) {
|
||||
if (mask & (@as(u16, 1) << @intCast(b)) != 0) _ = device.addResource(.irq, b, 1);
|
||||
}
|
||||
},
|
||||
0x08 => if (len >= 7) { // IO port: minimum at +1, length at +6
|
||||
_ = device.addResource(.io_port, rd16(bytes, body + 1), bytes[body + 6]);
|
||||
},
|
||||
0x09 => if (len >= 3) { // Fixed IO: base at +0, length at +2
|
||||
_ = device.addResource(.io_port, rd16(bytes, body), bytes[body + 2]);
|
||||
},
|
||||
0x0F => break, // EndTag
|
||||
else => {},
|
||||
}
|
||||
i = body + len;
|
||||
} else {
|
||||
// Large descriptor: 16-bit length follows the tag.
|
||||
if (i + 3 > bytes.len) break;
|
||||
const len: usize = @intCast(rd16(bytes, i + 1));
|
||||
const body = i + 3;
|
||||
if (body + len > bytes.len) break;
|
||||
switch (tag) {
|
||||
0x85 => if (len >= 17) { // Memory32: minimum at +1, length at +13
|
||||
_ = device.addResource(.memory, rd32(bytes, body + 1), rd32(bytes, body + 13));
|
||||
},
|
||||
0x86 => if (len >= 9) { // Memory32Fixed: base at +1, length at +5
|
||||
_ = device.addResource(.memory, rd32(bytes, body + 1), rd32(bytes, body + 5));
|
||||
},
|
||||
0x89 => if (len >= 2) { // Extended IRQ: count at +1, then count u32s
|
||||
const count = bytes[body + 1];
|
||||
var k: usize = 0;
|
||||
while (k < count and body + 2 + k * 4 + 4 <= body + len) : (k += 1) {
|
||||
_ = device.addResource(.irq, rd32(bytes, body + 2 + k * 4), 1);
|
||||
}
|
||||
},
|
||||
0x87, 0x88, 0x8A => parseAddressSpace(device, tag, bytes[body .. body + len]),
|
||||
else => {},
|
||||
}
|
||||
i = body + len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Word/DWord/QWord address-space descriptors: resource type at [0], then
|
||||
/// granularity/minimum/maximum/translation/length, each of width `w`.
|
||||
fn parseAddressSpace(device: *device_model.Device, tag: u8, body: []const u8) void {
|
||||
const w: usize = switch (tag) {
|
||||
0x88 => 2, // Word
|
||||
0x87 => 4, // DWord
|
||||
else => 8, // QWord (0x8A)
|
||||
};
|
||||
if (body.len < 3 + 5 * w) return;
|
||||
const minimum = readN(body, 3 + w, w);
|
||||
const length = readN(body, 3 + 4 * w, w);
|
||||
const kind: device_model.ResourceKind = switch (body[0]) {
|
||||
0 => .memory,
|
||||
1 => .io_port,
|
||||
else => .bus_range,
|
||||
};
|
||||
_ = device.addResource(kind, minimum, length);
|
||||
}
|
||||
|
||||
/// Decode a packed EISA id into its 7-char string (e.g. 0x030AD041 -> "PNP0A03").
|
||||
fn eisaIdToStr(id: u32, buffer: *[8]u8) []const u8 {
|
||||
const b0: u16 = @intCast(id & 0xFF);
|
||||
const b1: u16 = @intCast((id >> 8) & 0xFF);
|
||||
const b2: u8 = @truncate(id >> 16);
|
||||
const b3: u8 = @truncate(id >> 24);
|
||||
const mfg = (b0 << 8) | b1;
|
||||
buffer[0] = '@' + @as(u8, @intCast((mfg >> 10) & 0x1F));
|
||||
buffer[1] = '@' + @as(u8, @intCast((mfg >> 5) & 0x1F));
|
||||
buffer[2] = '@' + @as(u8, @intCast(mfg & 0x1F));
|
||||
buffer[3] = hexDigit((b2 >> 4) & 0xF);
|
||||
buffer[4] = hexDigit(b2 & 0xF);
|
||||
buffer[5] = hexDigit((b3 >> 4) & 0xF);
|
||||
buffer[6] = hexDigit(b3 & 0xF);
|
||||
return buffer[0..7];
|
||||
}
|
||||
|
||||
fn hexDigit(n: u8) u8 {
|
||||
return if (n < 10) '0' + n else 'A' + (n - 10);
|
||||
}
|
||||
|
||||
fn seg4(comptime s: *const [4:0]u8) [4]u8 {
|
||||
return s[0..4].*;
|
||||
}
|
||||
|
||||
fn cstr(bytes: []const u8) []const u8 {
|
||||
const index = std.mem.indexOfScalar(u8, bytes, 0) orelse bytes.len;
|
||||
return bytes[0..index];
|
||||
}
|
||||
|
||||
const PkgLen = struct { value: usize, size: usize };
|
||||
|
||||
fn packageLength(bytes: []const u8, p: usize) ?PkgLen {
|
||||
if (p >= bytes.len) return null;
|
||||
const lead = bytes[p];
|
||||
const follow: usize = lead >> 6;
|
||||
if (p + 1 + follow > bytes.len) return null;
|
||||
if (follow == 0) return .{ .value = lead & 0x3F, .size = 1 };
|
||||
var value: usize = lead & 0x0F;
|
||||
var i: usize = 0;
|
||||
while (i < follow) : (i += 1) value |= @as(usize, bytes[p + 1 + i]) << @intCast(4 + i * 8);
|
||||
return .{ .value = value, .size = 1 + follow };
|
||||
}
|
||||
|
||||
/// Read an AML integer object at `p`, advancing `p` past it.
|
||||
fn readIntObj(bytes: []const u8, p: *usize) ?u64 {
|
||||
if (p.* >= bytes.len) return null;
|
||||
const opcode = bytes[p.*];
|
||||
p.* += 1;
|
||||
return switch (opcode) {
|
||||
0x00 => 0,
|
||||
0x01 => 1,
|
||||
0xFF => 0xFF,
|
||||
0x0A => readLE(bytes, p, 1),
|
||||
0x0B => readLE(bytes, p, 2),
|
||||
0x0C => readLE(bytes, p, 4),
|
||||
0x0E => readLE(bytes, p, 8),
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
fn readLE(bytes: []const u8, p: *usize, n: usize) ?u64 {
|
||||
if (p.* + n > bytes.len) return null;
|
||||
const v = readN(bytes, p.*, n);
|
||||
p.* += n;
|
||||
return v;
|
||||
}
|
||||
|
||||
fn readN(bytes: []const u8, off: usize, n: usize) u64 {
|
||||
var v: u64 = 0;
|
||||
var k: usize = 0;
|
||||
while (k < n and off + k < bytes.len) : (k += 1) v |= @as(u64, bytes[off + k]) << @intCast(k * 8);
|
||||
return v;
|
||||
}
|
||||
|
||||
fn rd16(bytes: []const u8, off: usize) u64 {
|
||||
return readN(bytes, off, 2);
|
||||
}
|
||||
|
||||
fn rd32(bytes: []const u8, off: usize) u64 {
|
||||
return readN(bytes, off, 4);
|
||||
}
|
||||
|
||||
// --- helpers ----------------------------------------------------------------
|
||||
|
||||
/// Sum `len` bytes; an ACPI table/pointer is valid when the low 8 bits are zero.
|
||||
@@ -1192,42 +853,9 @@ fn readCntRegister(base: [*]align(1) const u8, len: usize, xoff: usize, legacy_o
|
||||
return .{ .mmio = false, .address = port, .width = width };
|
||||
}
|
||||
|
||||
/// The mapped configuration space of one PCI function (its 4 KiB ECAM page). Mapped
|
||||
/// writable so BAR sizing can probe it; reads and writes both go through here.
|
||||
/// Read a little-endian integer at `off` from a (possibly unaligned) byte pointer.
|
||||
/// x86 is little-endian and native, so an unaligned load suffices.
|
||||
fn rd(comptime T: type, bytes: [*]align(1) const u8, off: usize) T {
|
||||
const p: *align(1) const T = @ptrCast(bytes + off);
|
||||
return p.*;
|
||||
}
|
||||
|
||||
// --- tests ------------------------------------------------------------------
|
||||
|
||||
test "eisaIdToStr decodes a packed EISA id" {
|
||||
var buffer: [8]u8 = undefined;
|
||||
// 0x030AD041 is the well-known encoding of "PNP0A03" (PCI root bridge).
|
||||
try std.testing.expectEqualStrings("PNP0A03", eisaIdToStr(0x030AD041, &buffer));
|
||||
}
|
||||
|
||||
test "parseResourceTemplate extracts IO, IRQ, and fixed memory" {
|
||||
// ResourceTemplate { IO(minimum 0x60, len 8), IRQ(4), Memory32Fixed(0xFED00000, 0x1000) }
|
||||
const runtime = [_]u8{
|
||||
0x47, 0x01, 0x60, 0x00, 0x60, 0x00, 0x01, 0x08, // small IO descriptor
|
||||
0x22, 0x10, 0x00, // small IRQ descriptor (mask bit 4 -> IRQ 4)
|
||||
0x86, 0x09, 0x00, 0x01, 0x00, 0x00, 0xD0, 0xFE, 0x00, 0x10, 0x00, 0x00, // Memory32Fixed
|
||||
0x79, 0x00, // EndTag
|
||||
};
|
||||
var device = device_model.Device{};
|
||||
parseResourceTemplate(&device, &runtime);
|
||||
|
||||
try std.testing.expectEqual(@as(u8, 3), device.resource_count);
|
||||
const rs = device.resources[0..device.resource_count];
|
||||
try std.testing.expectEqual(device_model.ResourceKind.io_port, rs[0].kind);
|
||||
try std.testing.expectEqual(@as(u64, 0x60), rs[0].start);
|
||||
try std.testing.expectEqual(@as(u64, 8), rs[0].len);
|
||||
try std.testing.expectEqual(device_model.ResourceKind.irq, rs[1].kind);
|
||||
try std.testing.expectEqual(@as(u64, 4), rs[1].start);
|
||||
try std.testing.expectEqual(device_model.ResourceKind.memory, rs[2].kind);
|
||||
try std.testing.expectEqual(@as(u64, 0xFED00000), rs[2].start);
|
||||
try std.testing.expectEqual(@as(u64, 0x1000), rs[2].len);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@ const std = @import("std");
|
||||
const opcode = @import("opcodes.zig");
|
||||
const parser = @import("parser.zig");
|
||||
|
||||
/// The named AML opcode/prefix bytes (`zero_opcode`, `byte_prefix`, …). Re-exported so
|
||||
/// callers that decode raw AML bytes — e.g. the acpi service reading a `_HID` integer —
|
||||
/// name the opcodes instead of writing bare 0x0A/0x0B/… literals (docs/coding-standards.md).
|
||||
pub const opcodes = @import("opcodes.zig");
|
||||
|
||||
pub const Namespace = @import("namespace.zig").Namespace;
|
||||
pub const Node = @import("namespace.zig").Node;
|
||||
pub const NodeKind = @import("namespace.zig").NodeKind;
|
||||
|
||||
+496
-189
@@ -7,6 +7,16 @@
|
||||
//! apart. Pure reference data (from the PCI spec; see https://wiki.osdev.org/PCI) — no
|
||||
//! hardware access — so it is shared by kernel discovery (the device-tree dump) and any
|
||||
//! user-space tool (a future lspci, driver matching).
|
||||
//!
|
||||
//! The taxonomy is named, not numbered (docs/coding-standards.md, "Named values"): the
|
||||
//! base class is a `BaseClass` enum, and each class with defined subclasses gets a
|
||||
//! namespace holding its `SubClass` enum (and, where the spec defines them, per-subclass
|
||||
//! `ProgIf` enums) — the same shape as `usb-ids.zig`. Code that *means* a specific class
|
||||
//! names it (`BaseClass.serial_bus`, `serial_bus.usb.ProgIf.xhci`) rather than writing a
|
||||
//! bare 0x0C/0x03/0x30. The `className`/`subclassName`/`progIfName` functions still take
|
||||
//! the raw bytes a function reports in its header, because that is what hardware hands us.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
/// The three bytes of a PCI class code, unpacked from the `0xCCSSPP` value discovery
|
||||
/// records in `Device.ids.pci_class` (CC = base class, SS = subclass, PP = prog-IF).
|
||||
@@ -22,148 +32,465 @@ pub const ClassCode = struct {
|
||||
.prog_if = @intCast(packed_code & 0xFF),
|
||||
};
|
||||
}
|
||||
|
||||
/// Re-pack the triple into the `0xCCSSPP` form. Lets code name a whole class code
|
||||
/// from its parts — `pack(.{ .base = @intFromEnum(BaseClass.serial_bus), … })` —
|
||||
/// instead of writing the literal 0x0C0330.
|
||||
pub fn pack(self: ClassCode) u24 {
|
||||
return (@as(u24, self.base) << 16) | (@as(u24, self.subclass) << 8) | self.prog_if;
|
||||
}
|
||||
};
|
||||
|
||||
/// Base class (config byte 0x0B). Non-exhaustive: an unlisted code is a real but
|
||||
/// unnamed class, decoded as "Unknown" rather than rejected.
|
||||
pub const BaseClass = enum(u8) {
|
||||
unclassified = 0x00,
|
||||
mass_storage = 0x01,
|
||||
network = 0x02,
|
||||
display = 0x03,
|
||||
multimedia = 0x04,
|
||||
memory = 0x05,
|
||||
bridge = 0x06,
|
||||
simple_communication = 0x07,
|
||||
base_system_peripheral = 0x08,
|
||||
input_device = 0x09,
|
||||
docking_station = 0x0A,
|
||||
processor = 0x0B,
|
||||
serial_bus = 0x0C,
|
||||
wireless = 0x0D,
|
||||
intelligent = 0x0E,
|
||||
satellite_communication = 0x0F,
|
||||
encryption = 0x10,
|
||||
signal_processing = 0x11,
|
||||
processing_accelerator = 0x12,
|
||||
non_essential_instrumentation = 0x13,
|
||||
co_processor = 0x40,
|
||||
unassigned = 0xFF,
|
||||
_,
|
||||
|
||||
pub fn name(self: BaseClass) []const u8 {
|
||||
return switch (self) {
|
||||
.unclassified => "Unclassified",
|
||||
.mass_storage => "Mass Storage Controller",
|
||||
.network => "Network Controller",
|
||||
.display => "Display Controller",
|
||||
.multimedia => "Multimedia Controller",
|
||||
.memory => "Memory Controller",
|
||||
.bridge => "Bridge",
|
||||
.simple_communication => "Simple Communication Controller",
|
||||
.base_system_peripheral => "Base System Peripheral",
|
||||
.input_device => "Input Device Controller",
|
||||
.docking_station => "Docking Station",
|
||||
.processor => "Processor",
|
||||
.serial_bus => "Serial Bus Controller",
|
||||
.wireless => "Wireless Controller",
|
||||
.intelligent => "Intelligent Controller",
|
||||
.satellite_communication => "Satellite Communication Controller",
|
||||
.encryption => "Encryption Controller",
|
||||
.signal_processing => "Signal Processing Controller",
|
||||
.processing_accelerator => "Processing Accelerator",
|
||||
.non_essential_instrumentation => "Non-Essential Instrumentation",
|
||||
.co_processor => "Co-Processor",
|
||||
.unassigned => "Unassigned Class (Vendor specific)",
|
||||
_ => "Unknown",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// --- Per-class subclass (and prog-IF) taxonomies --------------------------------------
|
||||
// One namespace per base class that has defined subclasses, named after the class. Each
|
||||
// holds an exhaustive `SubClass` enum (so an unlisted code decodes to the class default,
|
||||
// not a wrong name), and, where the spec assigns them, per-subclass `ProgIf` enums.
|
||||
|
||||
pub const mass_storage = struct {
|
||||
pub const SubClass = enum(u8) {
|
||||
scsi_bus = 0x00,
|
||||
ide = 0x01,
|
||||
floppy = 0x02,
|
||||
ipi_bus = 0x03,
|
||||
raid = 0x04,
|
||||
ata = 0x05,
|
||||
serial_ata = 0x06,
|
||||
serial_attached_scsi = 0x07,
|
||||
non_volatile_memory = 0x08,
|
||||
|
||||
pub fn name(self: SubClass) []const u8 {
|
||||
return switch (self) {
|
||||
.scsi_bus => "SCSI Bus Controller",
|
||||
.ide => "IDE Controller",
|
||||
.floppy => "Floppy Disk Controller",
|
||||
.ipi_bus => "IPI Bus Controller",
|
||||
.raid => "RAID Controller",
|
||||
.ata => "ATA Controller",
|
||||
.serial_ata => "Serial ATA Controller",
|
||||
.serial_attached_scsi => "Serial Attached SCSI Controller",
|
||||
.non_volatile_memory => "Non-Volatile Memory Controller",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const serial_ata = struct {
|
||||
pub const ProgIf = enum(u8) {
|
||||
vendor_specific = 0x00,
|
||||
ahci = 0x01,
|
||||
serial_storage_bus = 0x02,
|
||||
|
||||
pub fn name(self: ProgIf) []const u8 {
|
||||
return switch (self) {
|
||||
.vendor_specific => "Vendor Specific Interface",
|
||||
.ahci => "AHCI 1.0",
|
||||
.serial_storage_bus => "Serial Storage Bus",
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub const non_volatile_memory = struct {
|
||||
pub const ProgIf = enum(u8) {
|
||||
nvmhci = 0x01,
|
||||
nvm_express = 0x02,
|
||||
|
||||
pub fn name(self: ProgIf) []const u8 {
|
||||
return switch (self) {
|
||||
.nvmhci => "NVMHCI",
|
||||
.nvm_express => "NVM Express",
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pub const network = struct {
|
||||
pub const SubClass = enum(u8) {
|
||||
ethernet = 0x00,
|
||||
token_ring = 0x01,
|
||||
fddi = 0x02,
|
||||
atm = 0x03,
|
||||
isdn = 0x04,
|
||||
picmg_multi_computing = 0x06,
|
||||
infiniband = 0x07,
|
||||
fabric = 0x08,
|
||||
|
||||
pub fn name(self: SubClass) []const u8 {
|
||||
return switch (self) {
|
||||
.ethernet => "Ethernet Controller",
|
||||
.token_ring => "Token Ring Controller",
|
||||
.fddi => "FDDI Controller",
|
||||
.atm => "ATM Controller",
|
||||
.isdn => "ISDN Controller",
|
||||
.picmg_multi_computing => "PICMG 2.14 Multi Computing Controller",
|
||||
.infiniband => "Infiniband Controller",
|
||||
.fabric => "Fabric Controller",
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub const display = struct {
|
||||
pub const SubClass = enum(u8) {
|
||||
vga_compatible = 0x00,
|
||||
xga = 0x01,
|
||||
three_dimensional = 0x02,
|
||||
|
||||
pub fn name(self: SubClass) []const u8 {
|
||||
return switch (self) {
|
||||
.vga_compatible => "VGA Compatible Controller",
|
||||
.xga => "XGA Controller",
|
||||
.three_dimensional => "3D Controller (Not VGA-Compatible)",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const vga_compatible = struct {
|
||||
pub const ProgIf = enum(u8) {
|
||||
vga = 0x00,
|
||||
compatible_8514 = 0x01,
|
||||
|
||||
pub fn name(self: ProgIf) []const u8 {
|
||||
return switch (self) {
|
||||
.vga => "VGA Controller",
|
||||
.compatible_8514 => "8514-Compatible Controller",
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pub const multimedia = struct {
|
||||
pub const SubClass = enum(u8) {
|
||||
video = 0x00,
|
||||
audio = 0x01,
|
||||
telephony = 0x02,
|
||||
audio_device = 0x03,
|
||||
|
||||
pub fn name(self: SubClass) []const u8 {
|
||||
return switch (self) {
|
||||
.video => "Multimedia Video Controller",
|
||||
.audio => "Multimedia Audio Controller",
|
||||
.telephony => "Computer Telephony Device",
|
||||
.audio_device => "Audio Device",
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub const memory = struct {
|
||||
pub const SubClass = enum(u8) {
|
||||
ram = 0x00,
|
||||
flash = 0x01,
|
||||
|
||||
pub fn name(self: SubClass) []const u8 {
|
||||
return switch (self) {
|
||||
.ram => "RAM Controller",
|
||||
.flash => "Flash Controller",
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub const bridge = struct {
|
||||
pub const SubClass = enum(u8) {
|
||||
host = 0x00,
|
||||
isa = 0x01,
|
||||
eisa = 0x02,
|
||||
mca = 0x03,
|
||||
pci_to_pci = 0x04,
|
||||
pcmcia = 0x05,
|
||||
nubus = 0x06,
|
||||
cardbus = 0x07,
|
||||
raceway = 0x08,
|
||||
pci_to_pci_semi_transparent = 0x09,
|
||||
infiniband_to_pci = 0x0A,
|
||||
|
||||
pub fn name(self: SubClass) []const u8 {
|
||||
return switch (self) {
|
||||
.host => "Host Bridge",
|
||||
.isa => "ISA Bridge",
|
||||
.eisa => "EISA Bridge",
|
||||
.mca => "MCA Bridge",
|
||||
.pci_to_pci => "PCI-to-PCI Bridge",
|
||||
.pcmcia => "PCMCIA Bridge",
|
||||
.nubus => "NuBus Bridge",
|
||||
.cardbus => "CardBus Bridge",
|
||||
.raceway => "RACEway Bridge",
|
||||
.pci_to_pci_semi_transparent => "PCI-to-PCI Bridge (Semi-Transparent)",
|
||||
.infiniband_to_pci => "InfiniBand-to-PCI Host Bridge",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const pci_to_pci = struct {
|
||||
pub const ProgIf = enum(u8) {
|
||||
normal_decode = 0x00,
|
||||
subtractive_decode = 0x01,
|
||||
|
||||
pub fn name(self: ProgIf) []const u8 {
|
||||
return switch (self) {
|
||||
.normal_decode => "Normal Decode",
|
||||
.subtractive_decode => "Subtractive Decode",
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pub const simple_communication = struct {
|
||||
pub const SubClass = enum(u8) {
|
||||
serial = 0x00,
|
||||
parallel = 0x01,
|
||||
multiport_serial = 0x02,
|
||||
modem = 0x03,
|
||||
gpib = 0x04,
|
||||
smart_card = 0x05,
|
||||
|
||||
pub fn name(self: SubClass) []const u8 {
|
||||
return switch (self) {
|
||||
.serial => "Serial Controller",
|
||||
.parallel => "Parallel Controller",
|
||||
.multiport_serial => "Multiport Serial Controller",
|
||||
.modem => "Modem",
|
||||
.gpib => "IEEE 488.1/2 (GPIB) Controller",
|
||||
.smart_card => "Smart Card Controller",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const serial = struct {
|
||||
pub const ProgIf = enum(u8) {
|
||||
compatible_8250 = 0x00,
|
||||
compatible_16450 = 0x01,
|
||||
compatible_16550 = 0x02,
|
||||
compatible_16650 = 0x03,
|
||||
compatible_16750 = 0x04,
|
||||
compatible_16850 = 0x05,
|
||||
compatible_16950 = 0x06,
|
||||
|
||||
pub fn name(self: ProgIf) []const u8 {
|
||||
return switch (self) {
|
||||
.compatible_8250 => "8250-Compatible (Generic XT)",
|
||||
.compatible_16450 => "16450-Compatible",
|
||||
.compatible_16550 => "16550-Compatible",
|
||||
.compatible_16650 => "16650-Compatible",
|
||||
.compatible_16750 => "16750-Compatible",
|
||||
.compatible_16850 => "16850-Compatible",
|
||||
.compatible_16950 => "16950-Compatible",
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pub const base_system_peripheral = struct {
|
||||
pub const SubClass = enum(u8) {
|
||||
pic = 0x00,
|
||||
dma = 0x01,
|
||||
timer = 0x02,
|
||||
rtc = 0x03,
|
||||
pci_hot_plug = 0x04,
|
||||
sd_host = 0x05,
|
||||
iommu = 0x06,
|
||||
|
||||
pub fn name(self: SubClass) []const u8 {
|
||||
return switch (self) {
|
||||
.pic => "PIC",
|
||||
.dma => "DMA Controller",
|
||||
.timer => "Timer",
|
||||
.rtc => "RTC Controller",
|
||||
.pci_hot_plug => "PCI Hot-Plug Controller",
|
||||
.sd_host => "SD Host Controller",
|
||||
.iommu => "IOMMU",
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub const input_device = struct {
|
||||
pub const SubClass = enum(u8) {
|
||||
keyboard = 0x00,
|
||||
digitizer_pen = 0x01,
|
||||
mouse = 0x02,
|
||||
scanner = 0x03,
|
||||
gameport = 0x04,
|
||||
|
||||
pub fn name(self: SubClass) []const u8 {
|
||||
return switch (self) {
|
||||
.keyboard => "Keyboard Controller",
|
||||
.digitizer_pen => "Digitizer Pen",
|
||||
.mouse => "Mouse Controller",
|
||||
.scanner => "Scanner Controller",
|
||||
.gameport => "Gameport Controller",
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub const serial_bus = struct {
|
||||
pub const SubClass = enum(u8) {
|
||||
firewire = 0x00,
|
||||
access_bus = 0x01,
|
||||
ssa = 0x02,
|
||||
usb = 0x03,
|
||||
fibre_channel = 0x04,
|
||||
smbus = 0x05,
|
||||
infiniband = 0x06,
|
||||
ipmi = 0x07,
|
||||
sercos = 0x08,
|
||||
canbus = 0x09,
|
||||
|
||||
pub fn name(self: SubClass) []const u8 {
|
||||
return switch (self) {
|
||||
.firewire => "FireWire (IEEE 1394) Controller",
|
||||
.access_bus => "ACCESS Bus Controller",
|
||||
.ssa => "SSA",
|
||||
.usb => "USB Controller",
|
||||
.fibre_channel => "Fibre Channel",
|
||||
.smbus => "SMBus Controller",
|
||||
.infiniband => "InfiniBand Controller",
|
||||
.ipmi => "IPMI Interface",
|
||||
.sercos => "SERCOS Interface (IEC 61491)",
|
||||
.canbus => "CANbus Controller",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const usb = struct {
|
||||
pub const ProgIf = enum(u8) {
|
||||
uhci = 0x00,
|
||||
ohci = 0x10,
|
||||
ehci = 0x20,
|
||||
xhci = 0x30,
|
||||
unspecified = 0x80,
|
||||
device = 0xFE,
|
||||
|
||||
pub fn name(self: ProgIf) []const u8 {
|
||||
return switch (self) {
|
||||
.uhci => "UHCI Controller",
|
||||
.ohci => "OHCI Controller",
|
||||
.ehci => "EHCI (USB2) Controller",
|
||||
.xhci => "XHCI (USB3) Controller",
|
||||
.unspecified => "Unspecified",
|
||||
.device => "USB Device (not a host controller)",
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pub const wireless = struct {
|
||||
pub const SubClass = enum(u8) {
|
||||
irda = 0x00,
|
||||
consumer_ir = 0x01,
|
||||
rf = 0x10,
|
||||
bluetooth = 0x11,
|
||||
broadband = 0x12,
|
||||
ethernet_802_1a = 0x20,
|
||||
ethernet_802_1b = 0x21,
|
||||
|
||||
pub fn name(self: SubClass) []const u8 {
|
||||
return switch (self) {
|
||||
.irda => "iRDA Compatible Controller",
|
||||
.consumer_ir => "Consumer IR Controller",
|
||||
.rf => "RF Controller",
|
||||
.bluetooth => "Bluetooth Controller",
|
||||
.broadband => "Broadband Controller",
|
||||
.ethernet_802_1a => "Ethernet Controller (802.1a)",
|
||||
.ethernet_802_1b => "Ethernet Controller (802.1b)",
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// --- Raw-byte decoding (what a function reports in its header) -------------------------
|
||||
|
||||
/// The name of an exhaustive class-code enum member, or null if `value` is not one — the
|
||||
/// bridge from a raw config byte to a named taxonomy above.
|
||||
fn enumName(comptime Enum: type, value: u8) ?[]const u8 {
|
||||
return (std.enums.fromInt(Enum, value) orelse return null).name();
|
||||
}
|
||||
|
||||
/// Name of the base class (byte 0x0B), e.g. `0x06` -> "Bridge".
|
||||
pub fn className(base: u8) []const u8 {
|
||||
return switch (base) {
|
||||
0x00 => "Unclassified",
|
||||
0x01 => "Mass Storage Controller",
|
||||
0x02 => "Network Controller",
|
||||
0x03 => "Display Controller",
|
||||
0x04 => "Multimedia Controller",
|
||||
0x05 => "Memory Controller",
|
||||
0x06 => "Bridge",
|
||||
0x07 => "Simple Communication Controller",
|
||||
0x08 => "Base System Peripheral",
|
||||
0x09 => "Input Device Controller",
|
||||
0x0A => "Docking Station",
|
||||
0x0B => "Processor",
|
||||
0x0C => "Serial Bus Controller",
|
||||
0x0D => "Wireless Controller",
|
||||
0x0E => "Intelligent Controller",
|
||||
0x0F => "Satellite Communication Controller",
|
||||
0x10 => "Encryption Controller",
|
||||
0x11 => "Signal Processing Controller",
|
||||
0x12 => "Processing Accelerator",
|
||||
0x13 => "Non-Essential Instrumentation",
|
||||
0x40 => "Co-Processor",
|
||||
0xFF => "Unassigned Class (Vendor specific)",
|
||||
else => "Unknown",
|
||||
};
|
||||
return @as(BaseClass, @enumFromInt(base)).name();
|
||||
}
|
||||
|
||||
/// Name of the subclass within its base class, e.g. `(0x06, 0x01)` -> "ISA Bridge".
|
||||
/// Subclass `0x80` is "Other" by PCI convention; anything unlisted is "Unknown".
|
||||
pub fn subclassName(base: u8, subclass: u8) []const u8 {
|
||||
return switch (base) {
|
||||
0x01 => switch (subclass) {
|
||||
0x00 => "SCSI Bus Controller",
|
||||
0x01 => "IDE Controller",
|
||||
0x02 => "Floppy Disk Controller",
|
||||
0x03 => "IPI Bus Controller",
|
||||
0x04 => "RAID Controller",
|
||||
0x05 => "ATA Controller",
|
||||
0x06 => "Serial ATA Controller",
|
||||
0x07 => "Serial Attached SCSI Controller",
|
||||
0x08 => "Non-Volatile Memory Controller",
|
||||
else => defaultSubclass(subclass),
|
||||
},
|
||||
0x02 => switch (subclass) {
|
||||
0x00 => "Ethernet Controller",
|
||||
0x01 => "Token Ring Controller",
|
||||
0x02 => "FDDI Controller",
|
||||
0x03 => "ATM Controller",
|
||||
0x04 => "ISDN Controller",
|
||||
0x06 => "PICMG 2.14 Multi Computing Controller",
|
||||
0x07 => "Infiniband Controller",
|
||||
0x08 => "Fabric Controller",
|
||||
else => defaultSubclass(subclass),
|
||||
},
|
||||
0x03 => switch (subclass) {
|
||||
0x00 => "VGA Compatible Controller",
|
||||
0x01 => "XGA Controller",
|
||||
0x02 => "3D Controller (Not VGA-Compatible)",
|
||||
else => defaultSubclass(subclass),
|
||||
},
|
||||
0x04 => switch (subclass) {
|
||||
0x00 => "Multimedia Video Controller",
|
||||
0x01 => "Multimedia Audio Controller",
|
||||
0x02 => "Computer Telephony Device",
|
||||
0x03 => "Audio Device",
|
||||
else => defaultSubclass(subclass),
|
||||
},
|
||||
0x05 => switch (subclass) {
|
||||
0x00 => "RAM Controller",
|
||||
0x01 => "Flash Controller",
|
||||
else => defaultSubclass(subclass),
|
||||
},
|
||||
0x06 => switch (subclass) {
|
||||
0x00 => "Host Bridge",
|
||||
0x01 => "ISA Bridge",
|
||||
0x02 => "EISA Bridge",
|
||||
0x03 => "MCA Bridge",
|
||||
0x04 => "PCI-to-PCI Bridge",
|
||||
0x05 => "PCMCIA Bridge",
|
||||
0x06 => "NuBus Bridge",
|
||||
0x07 => "CardBus Bridge",
|
||||
0x08 => "RACEway Bridge",
|
||||
0x09 => "PCI-to-PCI Bridge (Semi-Transparent)",
|
||||
0x0A => "InfiniBand-to-PCI Host Bridge",
|
||||
else => defaultSubclass(subclass),
|
||||
},
|
||||
0x07 => switch (subclass) {
|
||||
0x00 => "Serial Controller",
|
||||
0x01 => "Parallel Controller",
|
||||
0x02 => "Multiport Serial Controller",
|
||||
0x03 => "Modem",
|
||||
0x04 => "IEEE 488.1/2 (GPIB) Controller",
|
||||
0x05 => "Smart Card Controller",
|
||||
else => defaultSubclass(subclass),
|
||||
},
|
||||
0x08 => switch (subclass) {
|
||||
0x00 => "PIC",
|
||||
0x01 => "DMA Controller",
|
||||
0x02 => "Timer",
|
||||
0x03 => "RTC Controller",
|
||||
0x04 => "PCI Hot-Plug Controller",
|
||||
0x05 => "SD Host Controller",
|
||||
0x06 => "IOMMU",
|
||||
else => defaultSubclass(subclass),
|
||||
},
|
||||
0x09 => switch (subclass) {
|
||||
0x00 => "Keyboard Controller",
|
||||
0x01 => "Digitizer Pen",
|
||||
0x02 => "Mouse Controller",
|
||||
0x03 => "Scanner Controller",
|
||||
0x04 => "Gameport Controller",
|
||||
else => defaultSubclass(subclass),
|
||||
},
|
||||
0x0C => switch (subclass) {
|
||||
0x00 => "FireWire (IEEE 1394) Controller",
|
||||
0x01 => "ACCESS Bus Controller",
|
||||
0x02 => "SSA",
|
||||
0x03 => "USB Controller",
|
||||
0x04 => "Fibre Channel",
|
||||
0x05 => "SMBus Controller",
|
||||
0x06 => "InfiniBand Controller",
|
||||
0x07 => "IPMI Interface",
|
||||
0x08 => "SERCOS Interface (IEC 61491)",
|
||||
0x09 => "CANbus Controller",
|
||||
else => defaultSubclass(subclass),
|
||||
},
|
||||
0x0D => switch (subclass) {
|
||||
0x00 => "iRDA Compatible Controller",
|
||||
0x01 => "Consumer IR Controller",
|
||||
0x10 => "RF Controller",
|
||||
0x11 => "Bluetooth Controller",
|
||||
0x12 => "Broadband Controller",
|
||||
0x20 => "Ethernet Controller (802.1a)",
|
||||
0x21 => "Ethernet Controller (802.1b)",
|
||||
else => defaultSubclass(subclass),
|
||||
},
|
||||
else => defaultSubclass(subclass),
|
||||
const named: ?[]const u8 = switch (@as(BaseClass, @enumFromInt(base))) {
|
||||
.mass_storage => enumName(mass_storage.SubClass, subclass),
|
||||
.network => enumName(network.SubClass, subclass),
|
||||
.display => enumName(display.SubClass, subclass),
|
||||
.multimedia => enumName(multimedia.SubClass, subclass),
|
||||
.memory => enumName(memory.SubClass, subclass),
|
||||
.bridge => enumName(bridge.SubClass, subclass),
|
||||
.simple_communication => enumName(simple_communication.SubClass, subclass),
|
||||
.base_system_peripheral => enumName(base_system_peripheral.SubClass, subclass),
|
||||
.input_device => enumName(input_device.SubClass, subclass),
|
||||
.serial_bus => enumName(serial_bus.SubClass, subclass),
|
||||
.wireless => enumName(wireless.SubClass, subclass),
|
||||
else => null,
|
||||
};
|
||||
return named orelse defaultSubclass(subclass);
|
||||
}
|
||||
|
||||
fn defaultSubclass(subclass: u8) []const u8 {
|
||||
@@ -175,68 +502,34 @@ fn defaultSubclass(subclass: u8) []const u8 {
|
||||
/// Returns "" when the prog-IF carries no standard meaning for this class/subclass —
|
||||
/// callers just print the hex byte in that case.
|
||||
pub fn progIfName(base: u8, subclass: u8, prog_if: u8) []const u8 {
|
||||
return switch (base) {
|
||||
0x01 => switch (subclass) {
|
||||
0x06 => switch (prog_if) { // Serial ATA
|
||||
0x00 => "Vendor Specific Interface",
|
||||
0x01 => "AHCI 1.0",
|
||||
0x02 => "Serial Storage Bus",
|
||||
else => "",
|
||||
const named: ?[]const u8 = switch (@as(BaseClass, @enumFromInt(base))) {
|
||||
.mass_storage => switch (std.enums.fromInt(mass_storage.SubClass, subclass) orelse return "") {
|
||||
.serial_ata => enumName(mass_storage.serial_ata.ProgIf, prog_if),
|
||||
.non_volatile_memory => enumName(mass_storage.non_volatile_memory.ProgIf, prog_if),
|
||||
else => null,
|
||||
},
|
||||
0x08 => switch (prog_if) { // Non-Volatile Memory
|
||||
0x01 => "NVMHCI",
|
||||
0x02 => "NVM Express",
|
||||
else => "",
|
||||
.display => switch (std.enums.fromInt(display.SubClass, subclass) orelse return "") {
|
||||
.vga_compatible => enumName(display.vga_compatible.ProgIf, prog_if),
|
||||
else => null,
|
||||
},
|
||||
else => "",
|
||||
.bridge => switch (std.enums.fromInt(bridge.SubClass, subclass) orelse return "") {
|
||||
.pci_to_pci => enumName(bridge.pci_to_pci.ProgIf, prog_if),
|
||||
else => null,
|
||||
},
|
||||
0x03 => switch (subclass) {
|
||||
0x00 => switch (prog_if) { // VGA Compatible
|
||||
0x00 => "VGA Controller",
|
||||
0x01 => "8514-Compatible Controller",
|
||||
else => "",
|
||||
.simple_communication => switch (std.enums.fromInt(simple_communication.SubClass, subclass) orelse return "") {
|
||||
.serial => enumName(simple_communication.serial.ProgIf, prog_if),
|
||||
else => null,
|
||||
},
|
||||
else => "",
|
||||
.serial_bus => switch (std.enums.fromInt(serial_bus.SubClass, subclass) orelse return "") {
|
||||
.usb => enumName(serial_bus.usb.ProgIf, prog_if),
|
||||
else => null,
|
||||
},
|
||||
0x06 => switch (subclass) {
|
||||
0x04 => switch (prog_if) { // PCI-to-PCI Bridge
|
||||
0x00 => "Normal Decode",
|
||||
0x01 => "Subtractive Decode",
|
||||
else => "",
|
||||
},
|
||||
else => "",
|
||||
},
|
||||
0x07 => switch (subclass) {
|
||||
0x00 => switch (prog_if) { // Serial Controller
|
||||
0x00 => "8250-Compatible (Generic XT)",
|
||||
0x01 => "16450-Compatible",
|
||||
0x02 => "16550-Compatible",
|
||||
0x03 => "16650-Compatible",
|
||||
0x04 => "16750-Compatible",
|
||||
0x05 => "16850-Compatible",
|
||||
0x06 => "16950-Compatible",
|
||||
else => "",
|
||||
},
|
||||
else => "",
|
||||
},
|
||||
0x0C => switch (subclass) {
|
||||
0x03 => switch (prog_if) { // USB Controller
|
||||
0x00 => "UHCI Controller",
|
||||
0x10 => "OHCI Controller",
|
||||
0x20 => "EHCI (USB2) Controller",
|
||||
0x30 => "XHCI (USB3) Controller",
|
||||
0x80 => "Unspecified",
|
||||
0xFE => "USB Device (not a host controller)",
|
||||
else => "",
|
||||
},
|
||||
else => "",
|
||||
},
|
||||
else => "",
|
||||
else => null,
|
||||
};
|
||||
return named orelse "";
|
||||
}
|
||||
|
||||
test "decodes the common class codes" {
|
||||
const std = @import("std");
|
||||
const eq = std.testing.expectEqualStrings;
|
||||
|
||||
const isa = ClassCode.unpack(0x06_01_00);
|
||||
@@ -251,11 +544,25 @@ test "decodes the common class codes" {
|
||||
try eq("AHCI 1.0", progIfName(ahci.base, ahci.subclass, ahci.prog_if));
|
||||
|
||||
const xhci = ClassCode.unpack(0x0C_03_30);
|
||||
try eq("Serial Bus Controller", className(xhci.base));
|
||||
try eq("USB Controller", subclassName(xhci.base, xhci.subclass));
|
||||
try eq("XHCI (USB3) Controller", progIfName(xhci.base, xhci.subclass, xhci.prog_if));
|
||||
}
|
||||
|
||||
// Unknowns and the "Other" convention.
|
||||
try eq("Other", subclassName(0x02, 0x80));
|
||||
try eq("Unknown", subclassName(0x06, 0x7E));
|
||||
try eq("", progIfName(0x06, 0x00, 0x00)); // host bridge: prog-IF has no standard name
|
||||
test "unlisted codes fall back without a wrong name" {
|
||||
const eq = std.testing.expectEqualStrings;
|
||||
try eq("Unknown", className(0x77)); // no such base class
|
||||
try eq("Other", subclassName(0x01, 0x80)); // 0x80 is the PCI "Other" convention
|
||||
try eq("Unknown", subclassName(0x01, 0x7A)); // unlisted mass-storage subclass
|
||||
try eq("", progIfName(0x01, 0x06, 0x7F)); // no standard SATA prog-IF for 0x7F
|
||||
try eq("", progIfName(0x02, 0x00, 0x00)); // class with no prog-IF taxonomy at all
|
||||
}
|
||||
|
||||
test "named parts pack to the raw triple" {
|
||||
const xhci = ClassCode{
|
||||
.base = @intFromEnum(BaseClass.serial_bus),
|
||||
.subclass = @intFromEnum(serial_bus.SubClass.usb),
|
||||
.prog_if = @intFromEnum(serial_bus.usb.ProgIf.xhci),
|
||||
};
|
||||
try std.testing.expectEqual(@as(u24, 0x0C_03_30), xhci.pack());
|
||||
}
|
||||
|
||||
@@ -14,12 +14,29 @@ const std = @import("std");
|
||||
const runtime = @import("runtime");
|
||||
const protocol = runtime.device_manager_protocol;
|
||||
const device = runtime.device;
|
||||
const pci_class = @import("pci-class");
|
||||
|
||||
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
||||
var line: [128]u8 = undefined;
|
||||
_ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return);
|
||||
}
|
||||
|
||||
/// Log a discovered function with its (class / subclass / prog-IF) triple decoded
|
||||
/// to human names — the boot-log breadcrumb that says *what* the hardware is, so
|
||||
/// "class 0x01 (Mass Storage Controller) subclass 0x06 (Serial ATA Controller)
|
||||
/// progif 0x01 (AHCI 1.0)" reads straight off the log when writing a new driver.
|
||||
/// A dedicated wider buffer than `writeLine`'s, since the decoded names are long.
|
||||
fn logFunction(bus: u64, dev: u64, function: u64, class_triple: u32) void {
|
||||
const cc = pci_class.ClassCode.unpack(@truncate(class_triple));
|
||||
const pif = pci_class.progIfName(cc.base, cc.subclass, cc.prog_if);
|
||||
var line: [200]u8 = undefined;
|
||||
const text = if (pif.len != 0)
|
||||
std.fmt.bufPrint(&line, "pci-bus: {d}:{d}.{d} class 0x{x:0>2} ({s}) subclass 0x{x:0>2} ({s}) progif 0x{x:0>2} ({s})\n", .{ bus, dev, function, cc.base, pci_class.className(cc.base), cc.subclass, pci_class.subclassName(cc.base, cc.subclass), cc.prog_if, pif }) catch return
|
||||
else
|
||||
std.fmt.bufPrint(&line, "pci-bus: {d}:{d}.{d} class 0x{x:0>2} ({s}) subclass 0x{x:0>2} ({s}) progif 0x{x:0>2}\n", .{ bus, dev, function, cc.base, pci_class.className(cc.base), cc.subclass, pci_class.subclassName(cc.base, cc.subclass), cc.prog_if }) catch return;
|
||||
_ = runtime.system.write(text);
|
||||
}
|
||||
|
||||
var bridge_id: u64 = protocol.no_device;
|
||||
var ecam_base: usize = 0;
|
||||
var ecam_physical: u64 = 0;
|
||||
@@ -137,7 +154,7 @@ fn scan() void {
|
||||
if (vendor_device & 0xFFFF == 0xFFFF) continue;
|
||||
const class_revision = configRead(bus, dev, function, 0x08);
|
||||
found += 1;
|
||||
writeLine("pci-bus: {d}:{d}.{d} class 0x{x:0>6}\n", .{ bus, dev, function, class_revision >> 8 });
|
||||
logFunction(bus, dev, function, class_revision >> 8);
|
||||
registerAndReport(bus, dev, function, class_revision >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,22 @@ fn readRegister(offset: usize) u32 {
|
||||
return register.*;
|
||||
}
|
||||
|
||||
/// The xHCI default Protocol Speed IDs (the PORTSC port-speed field, bits 13:10)
|
||||
/// decoded to human names — the boot-log breadcrumb for what actually enumerated on
|
||||
/// a port, the USB analog of the pci-bus class-code line. A controller may redefine
|
||||
/// these through its Supported Protocol capability, but the defaults cover every
|
||||
/// speed QEMU and real hardware report at this (pre-descriptor) stage.
|
||||
fn speedName(speed: u32) []const u8 {
|
||||
return switch (speed) {
|
||||
1 => "Full-speed (USB 2.0, 12 Mb/s)",
|
||||
2 => "Low-speed (USB 2.0, 1.5 Mb/s)",
|
||||
3 => "High-speed (USB 2.0, 480 Mb/s)",
|
||||
4 => "SuperSpeed (USB 3.0, 5 Gb/s)",
|
||||
5 => "SuperSpeedPlus (USB 3.1, 10 Gb/s)",
|
||||
else => "unknown speed",
|
||||
};
|
||||
}
|
||||
|
||||
/// The root-hub port scan: read the capability registers for the port count
|
||||
/// and the operational-register offset, then one PORTSC per port. The connect
|
||||
/// bit (CCS) and the speed field reflect hardware state directly — no
|
||||
@@ -129,7 +145,7 @@ fn scanPorts(manager: runtime.ipc.Handle) void {
|
||||
if (port_status & 1 == 0) continue; // CCS: nothing connected
|
||||
connected += 1;
|
||||
const speed = (port_status >> 10) & 0xF; // the PORTSC port-speed class
|
||||
writeLine("usb-xhci-bus: port {d} connected (speed class {d})\n", .{ port, speed });
|
||||
writeLine("usb-xhci-bus: port {d} connected — {s} (speed class {d})\n", .{ port, speedName(speed), speed });
|
||||
|
||||
const report = protocol.ChildAdded{
|
||||
.parent = controller_id,
|
||||
|
||||
@@ -15,8 +15,12 @@
|
||||
const std = @import("std");
|
||||
const runtime = @import("runtime");
|
||||
const aml = @import("aml");
|
||||
const acpi_ids = @import("acpi-ids");
|
||||
const device = runtime.device;
|
||||
const protocol = runtime.device_manager_protocol;
|
||||
/// AML opcode/prefix bytes by name (`zero_opcode`, `byte_prefix`, …) — so the `_HID`
|
||||
/// integer decode names the opcodes instead of bare 0x0A/0x0B/… (docs/coding-standards.md).
|
||||
const opcodes = aml.opcodes;
|
||||
|
||||
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
||||
var line: [128]u8 = undefined;
|
||||
@@ -139,7 +143,16 @@ pub fn main(init: runtime.process.Init) void {
|
||||
var i: usize = 0;
|
||||
while (i < registered_count) : (i += 1) {
|
||||
const entry = registered[i];
|
||||
writeLine("acpi: reported {s} (device {d}, {d} resources)\n", .{ entry.hid[0..entry.hid_len], entry.device_id, entry.resource_count });
|
||||
// Append the _HID's human-readable name when it is a known standard PnP/ACPI
|
||||
// id (e.g. PNP0303 -> "PS/2 Keyboard"), so the boot log says what each
|
||||
// reported device actually is. The description trails the existing fields so
|
||||
// the acpi-report/acpi-ps2 matchers still see "<hid> (device N, M resources)".
|
||||
const hid = entry.hid[0..entry.hid_len];
|
||||
const desc = acpi_ids.description(hid);
|
||||
if (desc.len != 0)
|
||||
writeLine("acpi: reported {s} (device {d}, {d} resources) — {s}\n", .{ hid, entry.device_id, entry.resource_count, desc })
|
||||
else
|
||||
writeLine("acpi: reported {s} (device {d}, {d} resources)\n", .{ hid, entry.device_id, entry.resource_count });
|
||||
if (manager) |h| {
|
||||
var report = protocol.ChildAdded{
|
||||
.parent = node_id,
|
||||
@@ -172,8 +185,10 @@ fn walkDevices(node: *aml.Node, interpreter: *aml.Interpreter) void {
|
||||
|
||||
if (readHid(c, interpreter)) |hid| {
|
||||
// Skip PCI roots — pci-bus already reports PCI functions; ACPI adds
|
||||
// only the non-PCI _HID devices (docs/m19-m20-plan.md M20.2).
|
||||
if (!std.mem.eql(u8, hid[0..7], "PNP0A03") and !std.mem.eql(u8, hid[0..7], "PNP0A08")) {
|
||||
// only the non-PCI _HID devices (docs/m19-m20-plan.md M20.2). The two
|
||||
// roots are named through the shared registry, not bare _HID strings.
|
||||
const id = acpi_ids.HardwareId.fromHid(hid[0..7]);
|
||||
if (id != .pci_bus and id != .pci_express_root_bridge) {
|
||||
registerDevice(c, hid, interpreter);
|
||||
}
|
||||
}
|
||||
@@ -225,7 +240,9 @@ fn readHid(node: *aml.Node, interpreter: *aml.Interpreter) ?[8]u8 {
|
||||
if (hid.kind != .name or hid.value.len == 0) return null;
|
||||
const v = hid.value;
|
||||
switch (v[0]) {
|
||||
0x00, 0x01, 0xFF, 0x0A, 0x0B, 0x0C, 0x0E => {
|
||||
// A static _HID names an integer EISA id: Zero/One/Ones or a Byte/Word/DWord/
|
||||
// QWord integer prefix. Anything else is not an integer we can EISA-decode.
|
||||
opcodes.zero_opcode, opcodes.one_opcode, opcodes.ones_opcode, opcodes.byte_prefix, opcodes.word_prefix, opcodes.dword_prefix, opcodes.qword_prefix => {
|
||||
var p: usize = 0;
|
||||
const n = readIntObj(v, &p) orelse return null;
|
||||
_ = eisaIdToStr(@truncate(n), &buffer);
|
||||
@@ -237,6 +254,33 @@ fn readHid(node: *aml.Node, interpreter: *aml.Interpreter) ?[8]u8 {
|
||||
|
||||
// --- _CRS resource-template decode (ported from the kernel's acpi.zig) --------
|
||||
|
||||
/// A resource template is a byte list of descriptors. Each starts with a tag byte whose
|
||||
/// high bit picks the encoding: a *small* descriptor carries its type in bits [6:3] and
|
||||
/// its length in bits [2:0]; a *large* descriptor is the whole tag byte, followed by a
|
||||
/// 16-bit length. These are the descriptor types danos decodes into resources — named so
|
||||
/// the walk below reads by descriptor, not by 0x04/0x85/… (docs/coding-standards.md).
|
||||
const large_descriptor_bit: u8 = 0x80; // set in a tag byte => large descriptor
|
||||
const small_length_mask: u8 = 0x07; // low 3 bits of a small tag = body length
|
||||
const small_type_shift: u3 = 3; // small type sits in bits [6:3]
|
||||
|
||||
/// Small resource descriptor types (tag bits [6:3]). Non-exhaustive: an unhandled type
|
||||
/// is skipped by its length, not misread.
|
||||
const SmallResourceType = enum(u8) {
|
||||
irq = 0x04,
|
||||
io_port = 0x08,
|
||||
fixed_io_port = 0x09,
|
||||
end_tag = 0x0F,
|
||||
_,
|
||||
};
|
||||
|
||||
/// Large resource descriptor types (the whole tag byte). Non-exhaustive for the same reason.
|
||||
const LargeResourceType = enum(u8) {
|
||||
memory32 = 0x85,
|
||||
memory32_fixed = 0x86,
|
||||
extended_irq = 0x89,
|
||||
_,
|
||||
};
|
||||
|
||||
fn applyCrs(descriptor: *device.DeviceDescriptor, node: *aml.Node, interpreter: *aml.Interpreter) void {
|
||||
const crs = aml.Namespace.childOf(node, seg4("_CRS")) orelse return;
|
||||
const obj = interpreter.evaluate(crs, &.{}) catch return;
|
||||
@@ -247,21 +291,21 @@ fn applyCrs(descriptor: *device.DeviceDescriptor, node: *aml.Node, interpreter:
|
||||
var i: usize = 0;
|
||||
while (i < bytes.len) {
|
||||
const tag = bytes[i];
|
||||
if (tag & 0x80 == 0) {
|
||||
const len: usize = tag & 0x07;
|
||||
if (tag & large_descriptor_bit == 0) {
|
||||
const len: usize = tag & small_length_mask;
|
||||
const body = i + 1;
|
||||
if (body + len > bytes.len) break;
|
||||
switch ((tag >> 3) & 0x0F) {
|
||||
0x04 => if (len >= 2) { // IRQ mask
|
||||
switch (@as(SmallResourceType, @enumFromInt((tag >> small_type_shift) & 0x0F))) {
|
||||
.irq => if (len >= 2) { // IRQ mask
|
||||
const mask = @as(u16, bytes[body]) | (@as(u16, bytes[body + 1]) << 8);
|
||||
var b: usize = 0;
|
||||
while (b < 16) : (b += 1) {
|
||||
if (mask & (@as(u16, 1) << @intCast(b)) != 0) addResource(descriptor, .irq, b, 1);
|
||||
}
|
||||
},
|
||||
0x08 => if (len >= 7) addResource(descriptor, .io_port, rd16(bytes, body + 1), bytes[body + 6]),
|
||||
0x09 => if (len >= 3) addResource(descriptor, .io_port, rd16(bytes, body), bytes[body + 2]),
|
||||
0x0F => break,
|
||||
.io_port => if (len >= 7) addResource(descriptor, .io_port, rd16(bytes, body + 1), bytes[body + 6]),
|
||||
.fixed_io_port => if (len >= 3) addResource(descriptor, .io_port, rd16(bytes, body), bytes[body + 2]),
|
||||
.end_tag => break,
|
||||
else => {},
|
||||
}
|
||||
i = body + len;
|
||||
@@ -270,10 +314,10 @@ fn applyCrs(descriptor: *device.DeviceDescriptor, node: *aml.Node, interpreter:
|
||||
const len: usize = @intCast(rd16(bytes, i + 1));
|
||||
const body = i + 3;
|
||||
if (body + len > bytes.len) break;
|
||||
switch (tag) {
|
||||
0x85 => if (len >= 17) addResource(descriptor, .memory, rd32(bytes, body + 1), rd32(bytes, body + 13)),
|
||||
0x86 => if (len >= 9) addResource(descriptor, .memory, rd32(bytes, body + 1), rd32(bytes, body + 5)),
|
||||
0x89 => if (len >= 2) {
|
||||
switch (@as(LargeResourceType, @enumFromInt(tag))) {
|
||||
.memory32 => if (len >= 17) addResource(descriptor, .memory, rd32(bytes, body + 1), rd32(bytes, body + 13)),
|
||||
.memory32_fixed => if (len >= 9) addResource(descriptor, .memory, rd32(bytes, body + 1), rd32(bytes, body + 5)),
|
||||
.extended_irq => if (len >= 2) {
|
||||
const count = bytes[body + 1];
|
||||
var k: usize = 0;
|
||||
while (k < count and body + 2 + k * 4 + 4 <= body + len) : (k += 1) {
|
||||
@@ -325,22 +369,22 @@ fn readIntObj(bytes: []const u8, p: *usize) ?u64 {
|
||||
const op = bytes[p.*];
|
||||
p.* += 1;
|
||||
switch (op) {
|
||||
0x00 => return 0,
|
||||
0x01 => return 1,
|
||||
0xFF => return 1,
|
||||
0x0A => {
|
||||
opcodes.zero_opcode => return 0,
|
||||
opcodes.one_opcode => return 1,
|
||||
opcodes.ones_opcode => return 1,
|
||||
opcodes.byte_prefix => {
|
||||
if (p.* >= bytes.len) return null;
|
||||
const v = bytes[p.*];
|
||||
p.* += 1;
|
||||
return v;
|
||||
},
|
||||
0x0B => {
|
||||
opcodes.word_prefix => {
|
||||
if (p.* + 2 > bytes.len) return null;
|
||||
const v = rd16(bytes, p.*);
|
||||
p.* += 2;
|
||||
return v;
|
||||
},
|
||||
0x0C => {
|
||||
opcodes.dword_prefix => {
|
||||
if (p.* + 4 > bytes.len) return null;
|
||||
const v = rd32(bytes, p.*);
|
||||
p.* += 4;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
const std = @import("std");
|
||||
const runtime = @import("runtime");
|
||||
const acpi_ids = @import("acpi-ids");
|
||||
const pci_class = @import("pci-class");
|
||||
const protocol = runtime.device_manager_protocol;
|
||||
const device = runtime.device;
|
||||
const system = runtime.system;
|
||||
@@ -41,10 +42,14 @@ fn driverFor(d: device.DeviceDescriptor) ?[]const u8 {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// The PCI class/subclass/prog-IF triple of an xHCI (USB 3) host controller:
|
||||
/// Serial Bus Controller (0x0C) / USB Controller (0x03) / XHCI (0x30) — the names
|
||||
/// pci-class.zig decodes.
|
||||
const xhci_pci_class: u64 = 0x0C_03_30;
|
||||
/// The PCI class/subclass/prog-IF triple of an xHCI (USB 3) host controller —
|
||||
/// Serial Bus Controller / USB Controller / XHCI — named from pci-class.zig rather
|
||||
/// than written as the bare 0x0C0330 (docs/coding-standards.md, "Named values").
|
||||
const xhci_pci_class: u64 = pci_class.ClassCode.pack(.{
|
||||
.base = @intFromEnum(pci_class.BaseClass.serial_bus),
|
||||
.subclass = @intFromEnum(pci_class.serial_bus.SubClass.usb),
|
||||
.prog_if = @intFromEnum(pci_class.serial_bus.usb.ProgIf.xhci),
|
||||
});
|
||||
|
||||
/// The driver that serves a *reported* PCI function (M19.3: matching moved
|
||||
/// from the boot snapshot to the bus reports), or null. A machine can carry
|
||||
|
||||
+9
-30
@@ -55,20 +55,16 @@ ARCHES = {
|
||||
"/opt/homebrew/share/qemu/edk2-i386-vars.fd", # macOS Homebrew (Apple Silicon)
|
||||
"/usr/local/share/qemu/edk2-i386-vars.fd", # macOS Homebrew (Intel)
|
||||
],
|
||||
# zig-out is a FHS-shaped image and the boot volume; the harness copies the
|
||||
# boot-critical files from their FHS paths into a fresh ESP with the same
|
||||
# layout. (dest in ESP, source path under zig-out) — identical here.
|
||||
"efi_app": ("EFI/BOOT/BOOTX64.efi", "EFI/BOOT/BOOTX64.efi"),
|
||||
"kernel": ("system/kernel", "system/kernel"),
|
||||
# The init user program and the initial-ramdisk (VFS server + drivers).
|
||||
"extra": [("system/services/init", "system/services/init"),
|
||||
("boot/initial-ramdisk.img", "boot/initial-ramdisk.img")],
|
||||
# zig-out is itself the FHS-shaped boot volume (docs/efi.md): the build
|
||||
# installs BOOTX64.efi, the kernel, init, and the initial-ramdisk at their
|
||||
# boot paths. The harness presents zig-out to the guest directly — exactly
|
||||
# as `zig build run-x86-64` does — so there is no separate ESP to assemble.
|
||||
# Built as a function so we can splice in per-run paths.
|
||||
"qemu_args": lambda a, esp, vars_fd, serial: [
|
||||
"qemu_args": lambda a, boot_volume, vars_fd, serial: [
|
||||
"-machine", "q35", "-m", "128M",
|
||||
"-drive", f"if=pflash,format=raw,readonly=on,file={a['ovmf_code']}",
|
||||
"-drive", f"if=pflash,format=raw,file={vars_fd}",
|
||||
"-drive", f"format=raw,file=fat:rw:{esp}",
|
||||
"-drive", f"format=raw,file=fat:rw:{boot_volume}",
|
||||
"-net", "none",
|
||||
"-vga", "none", "-device", "VGA,edid=on,xres=1280,yres=720",
|
||||
"-display", "none",
|
||||
@@ -404,24 +400,6 @@ def build(arch, case):
|
||||
return None
|
||||
|
||||
|
||||
def make_esp(arch):
|
||||
"""Assemble a fresh EFI System Partition from the freshly built binaries."""
|
||||
esp = os.path.join(WORK, "esp")
|
||||
if os.path.exists(esp):
|
||||
shutil.rmtree(esp)
|
||||
efi_dest, efi_src = arch["efi_app"]
|
||||
kern_dest, kern_src = arch["kernel"]
|
||||
fhs = os.path.join(REPO, "zig-out") # zig-out is the FHS image
|
||||
os.makedirs(os.path.join(esp, os.path.dirname(efi_dest)), exist_ok=True)
|
||||
os.makedirs(os.path.join(esp, os.path.dirname(kern_dest)), exist_ok=True)
|
||||
shutil.copy(os.path.join(fhs, efi_src), os.path.join(esp, efi_dest))
|
||||
shutil.copy(os.path.join(fhs, kern_src), os.path.join(esp, kern_dest))
|
||||
for dest, src in arch.get("extra", []):
|
||||
os.makedirs(os.path.join(esp, os.path.dirname(dest)), exist_ok=True)
|
||||
shutil.copy(os.path.join(fhs, src), os.path.join(esp, dest))
|
||||
return esp
|
||||
|
||||
|
||||
def resolve_firmware(arch):
|
||||
"""Collapse the ovmf_code/ovmf_vars candidate lists to the first path that
|
||||
exists on this machine. Mutates `arch` in place; idempotent (a resolved
|
||||
@@ -445,7 +423,8 @@ def run_case(arch, case):
|
||||
if err:
|
||||
return False, "build failed:\n" + err
|
||||
|
||||
esp = make_esp(arch)
|
||||
# zig-out is the FHS boot volume; hand it to the guest as-is (see qemu_args).
|
||||
boot_volume = os.path.join(REPO, "zig-out")
|
||||
vars_fd = os.path.join(WORK, "vars.fd")
|
||||
shutil.copy(arch["ovmf_vars"], vars_fd)
|
||||
serial = os.path.join(WORK, "serial.log")
|
||||
@@ -455,7 +434,7 @@ def run_case(arch, case):
|
||||
expect = re.compile(case["expect"])
|
||||
fail = re.compile(case["fail"]) if case.get("fail") else None
|
||||
|
||||
cmd = [arch["qemu"]] + arch["qemu_args"](arch, esp, vars_fd, serial)
|
||||
cmd = [arch["qemu"]] + arch["qemu_args"](arch, boot_volume, vars_fd, serial)
|
||||
if case.get("smp"): # some cases need more than one core (e.g. parallelism)
|
||||
cmd += ["-smp", str(case["smp"])]
|
||||
if case.get("qemu_extra"): # extra qemu args, e.g. -device intel-iommu for the IOMMU case
|
||||
|
||||
Reference in New Issue
Block a user