drivers: log devices.csv columns + friendly labels for all buses

Each bus's discovery line now prints the device's would-be /etc/devices.csv row
(bus, base, class, prog_if, vendor, device, subsystem / hid) in uppercase hex,
followed by the human-readable names — so a row for a new driver reads straight
off the boot log, for pci, usb, and acpi alike.

- pci-bus: logFunction moved to registerAndReport (where vendor/device/subsystem
  are read from config space) and reformatted to columns + names; subsystem
  prints '*' when the function has none. Read unclaimed via the bridge ECAM, so
  no per-function claim is needed.
- usb-xhci-bus, acpi: the same column framing on their existing discovery lines.
- qemu_test.py: the acpi-ps2 and acpi-report regexes updated to the new ACPI
  format (both verified passing in QEMU).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KJqSiLLchDUUCoXn5jsiwd
This commit is contained in:
2026-07-26 17:32:27 +01:00
co-authored by Claude Opus 4.8
parent 081ba1d74e
commit 48b9ed4001
4 changed files with 28 additions and 19 deletions
+16 -12
View File
@@ -21,19 +21,20 @@ const logging = @import("logging");
const device_manager_protocol = @import("device-manager-protocol"); const device_manager_protocol = @import("device-manager-protocol");
const pci_class = @import("pci-class"); const pci_class = @import("pci-class");
/// Log a discovered function with its (class / subclass / prog-IF) triple decoded /// Log a discovered function as its would-be /etc/devices.csv columns (bus, base,
/// to human names — the boot-log breadcrumb that says *what* the hardware is, so /// class, prog_if, vendor, device, subsystem) followed by the human-readable
/// "class 0x01 (Mass Storage Controller) subclass 0x06 (Serial ATA Controller) /// class/subclass/prog-IF names — so a row for a new driver reads straight off the
/// progif 0x01 (AHCI 1.0)" reads straight off the log when writing a new driver. /// boot log. `subsystem` prints as `*` when the function has none, matching the CSV
/// A dedicated wider buffer than `writeLine`'s, since the decoded names are long. /// wildcard. All read unclaimed, through the bridge's ECAM: the enumerator never
fn logFunction(bus: u64, dev: u64, function: u64, class_triple: u32) void { /// claims the functions it probes (pci.zig's header — the device-owned pci.Function
/// view is what needs a claim, not this one). A wide buffer: the names are long.
fn logFunction(bus: u64, dev: u64, function: u64, class_triple: u32, vendor_id: u16, product_id: u16, subsystem: u32) void {
const cc = pci_class.ClassCode.unpack(@truncate(class_triple)); const cc = pci_class.ClassCode.unpack(@truncate(class_triple));
const pif = pci_class.progIfName(cc.base, cc.subclass, cc.prog_if); const pif = pci_class.progIfName(cc.base, cc.subclass, cc.prog_if);
var line: [200]u8 = undefined; var sub_buffer: [8]u8 = undefined;
const text = if (pif.len != 0) const sub = if (subsystem == 0) "*" else std.fmt.bufPrint(&sub_buffer, "{X:0>8}", .{subsystem}) catch "*";
std.fmt.bufPrint(&line, "/system/drivers/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 var line: [320]u8 = undefined;
else const text = std.fmt.bufPrint(&line, "/system/drivers/pci-bus: {d}:{d}.{d} bus=pci base={X:0>2} class={X:0>2} prog_if={X:0>2} vendor={X:0>4} device={X:0>4} subsystem={s} — {s} / {s}{s}{s}\n", .{ bus, dev, function, cc.base, cc.subclass, cc.prog_if, vendor_id, product_id, sub, pci_class.className(cc.base), pci_class.subclassName(cc.base, cc.subclass), if (pif.len != 0) " / " else "", pif }) catch return;
std.fmt.bufPrint(&line, "/system/drivers/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;
_ = logging.write(text); _ = logging.write(text);
} }
@@ -136,7 +137,6 @@ fn scan() void {
if (vendor_device & 0xFFFF == 0xFFFF) continue; if (vendor_device & 0xFFFF == 0xFFFF) continue;
const class_revision = configRead(bus, dev, function, 0x08); const class_revision = configRead(bus, dev, function, 0x08);
found += 1; found += 1;
logFunction(bus, dev, function, class_revision >> 8);
registerAndReport(bus, dev, function, class_revision >> 8); registerAndReport(bus, dev, function, class_revision >> 8);
} }
} }
@@ -221,6 +221,10 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void
configWrite16(bus, dev, function, 0x04, command); configWrite16(bus, dev, function, 0x04, command);
} }
// The devices.csv-column + friendly-name breadcrumb, now that vendor/device/
// subsystem are read. Every discovered function is logged, matched or not.
logFunction(bus, dev, function, class_triple, descriptor.vendor, descriptor.device, descriptor.subsystem);
const registered = device.register(bridge_id, &descriptor) orelse { const registered = device.register(bridge_id, &descriptor) orelse {
std.log.info("register refused for {d}:{d}.{d}", .{ bus, dev, function }); std.log.info("register refused for {d}:{d}.{d}", .{ bus, dev, function });
return; return;
+5 -2
View File
@@ -390,13 +390,16 @@ fn reportInterface(manager: ipc.Handle, port: u32, interface: library.InterfaceI
std.log.info("child report for port {d} interface {d} failed", .{ port, interface.number }); std.log.info("child report for port {d} interface {d} failed", .{ port, interface.number });
return null; return null;
}; };
std.log.info("port {d} interface {d}: {s} ({d}/{d}/{d}) registered as device {d}", .{ // The devices.csv columns (bus=usb, and the class triple as base/class/prog_if)
// then the human-readable interface name — a would-be /etc/devices.csv row read
// straight off the boot log.
std.log.info("port {d} interface {d} bus=usb base={X:0>2} class={X:0>2} prog_if={X:0>2} — {s} registered as device {d}", .{
port, port,
interface.number, interface.number,
usb_ids.interfaceName(interface.class, interface.subclass, interface.protocol),
interface.class, interface.class,
interface.subclass, interface.subclass,
interface.protocol, interface.protocol,
usb_ids.interfaceName(interface.class, interface.subclass, interface.protocol),
registered, registered,
}); });
return registered; return registered;
+4 -2
View File
@@ -214,11 +214,13 @@ fn onInit(endpoint: ipc.Handle) bool {
while (i < registered_count) : (i += 1) { while (i < registered_count) : (i += 1) {
const entry = registered[i]; const entry = registered[i];
const hid = entry.hid[0..entry.hid_len]; const hid = entry.hid[0..entry.hid_len];
// The devices.csv columns (bus=acpi, hid) then the human-readable name — a
// would-be /etc/devices.csv row read straight off the boot log.
const desc = acpi_ids.description(hid); const desc = acpi_ids.description(hid);
if (desc.len != 0) if (desc.len != 0)
std.log.info("reported {s} (device {d}, {d} resources) — {s}", .{ hid, entry.device_id, entry.resource_count, desc }) std.log.info("device {d} bus=acpi hid={s} — {s} ({d} resources)", .{ entry.device_id, hid, desc, entry.resource_count })
else else
std.log.info("reported {s} (device {d}, {d} resources)", .{ hid, entry.device_id, entry.resource_count }); std.log.info("device {d} bus=acpi hid={s} ({d} resources)", .{ entry.device_id, hid, entry.resource_count });
if (manager) |h| { if (manager) |h| {
var report = device_manager_protocol.ChildAdded{ .bus = @intFromEnum(device_manager_protocol.BusKind.acpi), .parent = node_id, .bus_address = entry.device_id, .identity = 0, .device_id = entry.device_id }; var report = device_manager_protocol.ChildAdded{ .bus = @intFromEnum(device_manager_protocol.BusKind.acpi), .parent = node_id, .bus_address = entry.device_id, .identity = 0, .device_id = entry.device_id };
@memcpy(report.hid[0..entry.hid_len], entry.hid[0..entry.hid_len]); @memcpy(report.hid[0..entry.hid_len], entry.hid[0..entry.hid_len]);
+3 -3
View File
@@ -628,7 +628,7 @@ CASES = [
{"name": "acpi-ps2", {"name": "acpi-ps2",
"smp": 4, "smp": 4,
"timeout": 150, "timeout": 150,
"expect": r"discovery: reported PNP0303[\s\S]*" "expect": r"discovery: device \d+\s+bus=acpi hid=PNP0303[\s\S]*"
r"device-manager: spawned \S*ps2-bus[\s\S]*" r"device-manager: spawned \S*ps2-bus[\s\S]*"
r"ps2-bus: keyboard driver attached", r"ps2-bus: keyboard driver attached",
"fail": r"DANOS-TEST-RESULT: FAIL"}, "fail": r"DANOS-TEST-RESULT: FAIL"},
@@ -675,8 +675,8 @@ CASES = [
{"name": "acpi-report", {"name": "acpi-report",
"smp": 4, "smp": 4,
"timeout": 150, "timeout": 150,
"expect": r"discovery: reported PNP0303 \(device \d+, 3 resources\)[\s\S]*" "expect": r"discovery: device \d+\s+bus=acpi hid=PNP0303[^\n]*\(3 resources\)[\s\S]*"
r"discovery: reported PNP0F13 \(device \d+, 1 resources\)", r"discovery: device \d+\s+bus=acpi hid=PNP0F13[^\n]*\(1 resources\)",
"fail": r"DANOS-TEST-RESULT: FAIL"}, "fail": r"DANOS-TEST-RESULT: FAIL"},
# M19.1/M19.3: the ring-3 PCI scan. pci-bus walks the ECAM through its mmio_map # M19.1/M19.3: the ring-3 PCI scan. pci-bus walks the ECAM through its mmio_map
# grant and registers every function it finds; the kernel's own walk retired, so # grant and registers every function it finds; the kernel's own walk retired, so