diff --git a/build.zig b/build.zig index fee0f82..5f34b34 100644 --- a/build.zig +++ b/build.zig @@ -116,6 +116,11 @@ pub fn build(b: *std.Build) void { const device_abi_module = b.addModule("device-abi", .{ .root_source_file = b.path("system/devices/device-abi.zig"), }); + // PCI class-code decoding (class/subclass/prog-IF -> names). Pure reference data, + // shared by kernel discovery (the device-tree dump) and any user-space PCI tool. + const pci_class_module = b.addModule("pci-class", .{ + .root_source_file = b.path("system/devices/pci-class.zig"), + }); // Kernel tunables (maximum_cpus, stack sizes, tick rate). A dependency-free module of // compile-time constants, imported wherever a knob is read; keeps the trade-offs @@ -153,6 +158,7 @@ pub fn build(b: *std.Build) void { .{ .name = "boot-handoff", .module = boot_handoff_module }, // BootInformation (carries the ACPI RSDP), physicalToVirtual .{ .name = "abi", .module = abi_module }, // acpi.zig works in page_size units .{ .name = "device-abi", .module = device_abi_module }, // device-model's DeviceClass/ResourceKind live here + .{ .name = "pci-class", .module = pci_class_module }, // decode PCI class codes in the device dump .{ .name = "parameters", .module = parameters_module }, // maximum_cpus (the discovery pool) }, }); @@ -439,6 +445,7 @@ pub fn build(b: *std.Build) void { "system/boot-handoff.zig", "system/abi.zig", "system/devices/device-abi.zig", + "system/devices/pci-class.zig", // class/subclass/prog-IF name decoding "library/mmio/mmio.zig", // barriers assemble + registers round-trip }) |root| { const mod_tests = b.addTest(.{ diff --git a/system/devices/device-model.zig b/system/devices/device-model.zig index 5946546..a47697e 100644 --- a/system/devices/device-model.zig +++ b/system/devices/device-model.zig @@ -13,6 +13,7 @@ const std = @import("std"); const device_abi = @import("device-abi"); +const pci_class = @import("pci-class"); /// 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 @@ -195,6 +196,21 @@ fn dumpNode(device: *const Device, depth: usize, emit: *const fn ([]const u8) vo 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, decode its class code — the (class / subclass / prog-IF) + // triple that says what it actually is, which the coarse `DeviceClass` can't. + if (device.ids.pci_class) |packed_code| { + const cc = pci_class.ClassCode.unpack(packed_code); + var cbuf: [200]u8 = undefined; + const pad = @min(indent + 2, 42); + @memset(cbuf[0..pad], ' '); + const pif = pci_class.progIfName(cc.base, cc.subclass, cc.prog_if); + const cline = if (pif.len != 0) + std.fmt.bufPrint(cbuf[pad..], "class 0x{x:0>2} ({s}) subclass 0x{x:0>2} ({s}) progif 0x{x:0>2} ({s})\n", .{ 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(cbuf[pad..], "class 0x{x:0>2} ({s}) subclass 0x{x:0>2} ({s}) progif 0x{x:0>2}\n", .{ cc.base, pci_class.className(cc.base), cc.subclass, pci_class.subclassName(cc.base, cc.subclass), cc.prog_if }) 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); diff --git a/system/devices/pci-class.zig b/system/devices/pci-class.zig new file mode 100644 index 0000000..078f607 --- /dev/null +++ b/system/devices/pci-class.zig @@ -0,0 +1,261 @@ +//! PCI class-code decoding: turn the (class, subclass, prog-IF) triple a PCI function +//! reports in its configuration header into human-readable names. Every PCI function +//! carries a 24-bit class code — base class (config byte 0x0B), subclass (0x0A), and +//! programming interface (0x09) — that says *what it is* far more precisely than +//! danos's coarse `DeviceClass`: an ISA bridge, a SATA/AHCI controller, and an xHCI USB +//! controller are all just `pci_device` by class, and only this triple tells them +//! 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 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). +pub const ClassCode = struct { + base: u8, // class code (config offset 0x0B) + subclass: u8, // subclass (0x0A) + prog_if: u8, // programming interface (0x09) + + pub fn unpack(packed_code: u24) ClassCode { + return .{ + .base = @intCast((packed_code >> 16) & 0xFF), + .subclass = @intCast((packed_code >> 8) & 0xFF), + .prog_if = @intCast(packed_code & 0xFF), + }; + } +}; + +/// 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", + }; +} + +/// 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), + }; +} + +fn defaultSubclass(subclass: u8) []const u8 { + return if (subclass == 0x80) "Other" else "Unknown"; +} + +/// Name of the programming interface, for the subclasses that define standard ones +/// (IDE modes, SATA/AHCI, NVMe, PCI-bridge decode, UART generation, USB host type). +/// 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 => "", + }, + 0x08 => switch (prog_if) { // Non-Volatile Memory + 0x01 => "NVMHCI", + 0x02 => "NVM Express", + else => "", + }, + else => "", + }, + 0x03 => switch (subclass) { + 0x00 => switch (prog_if) { // VGA Compatible + 0x00 => "VGA Controller", + 0x01 => "8514-Compatible Controller", + else => "", + }, + else => "", + }, + 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 => "", + }; +} + +test "decodes the common class codes" { + const std = @import("std"); + const eq = std.testing.expectEqualStrings; + + const isa = ClassCode.unpack(0x06_01_00); + try std.testing.expectEqual(@as(u8, 0x06), isa.base); + try std.testing.expectEqual(@as(u8, 0x01), isa.subclass); + try eq("Bridge", className(isa.base)); + try eq("ISA Bridge", subclassName(isa.base, isa.subclass)); + + const ahci = ClassCode.unpack(0x01_06_01); + try eq("Mass Storage Controller", className(ahci.base)); + try eq("Serial ATA Controller", subclassName(ahci.base, ahci.subclass)); + try eq("AHCI 1.0", progIfName(ahci.base, ahci.subclass, ahci.prog_if)); + + const xhci = ClassCode.unpack(0x0C_03_30); + 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 +}