//! 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 }