//! 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 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). 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), }; } /// 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; } }; // --- Configuration-space layout --------------------------------------------------------- // The offsets and bit layouts of the PCI configuration header (PCI spec; see // https://wiki.osdev.org/PCI). Pure data — named here so both a device driver's view of // its own claimed function (library/device/pci/pci.zig) and the bus enumerator name the // same bytes instead of scattering bare 0x04/0x34/0xFFFF_FFF0 magic across the tree. /// Header field offsets (byte offsets into the 256-byte configuration space). pub const config_vendor_id: usize = 0x00; pub const config_device_id: usize = 0x02; pub const config_command: usize = 0x04; pub const config_status: usize = 0x06; pub const config_revision_id: usize = 0x08; pub const config_class_code: usize = 0x09; // 3 bytes: prog-IF 0x09, subclass 0x0A, base class 0x0B pub const config_bar0: usize = 0x10; // BAR0; BAR n is at config_bar0 + n*4 pub const config_subsystem_vendor_id: usize = 0x2C; pub const config_subsystem_id: usize = 0x2E; pub const config_expansion_rom: usize = 0x30; pub const config_capabilities_pointer: usize = 0x34; pub const config_interrupt_line: usize = 0x3C; pub const config_interrupt_pin: usize = 0x3D; // 0 = none, 1..4 = INTA..INTD /// Command register bits. pub const command_io_space: u16 = 0x0001; // bit 0: I/O-space decode enable pub const command_memory_space: u16 = 0x0002; // bit 1: memory-space decode enable pub const command_bus_master: u16 = 0x0004; // bit 2: bus-master (DMA) enable pub const command_interrupt_disable: u16 = 0x0400; // bit 10: suppress legacy INTx (MSI/MSI-X unaffected) /// The pair a bus-mastering driver enables together: decode my BARs, let me DMA. pub const command_memory_and_bus_master: u16 = command_memory_space | command_bus_master; /// Status register bit 3: legacy INTx is asserted (upstream of the command bit-10 gate). pub const status_interrupt: u16 = 0x0008; /// Status register bit 4: a capability list is present at config_capabilities_pointer. pub const status_capabilities_list: u16 = 0x10; /// Capability pointers are dword-aligned; the low two bits are reserved. pub const capability_pointer_mask: u8 = 0xFC; /// Capability IDs — the first byte of each entry in the legacy capability list. /// Non-exhaustive: hardware may report IDs not named here. pub const CapabilityId = enum(u8) { power_management = 0x01, msi = 0x05, vendor_specific = 0x09, pci_express = 0x10, msix = 0x11, _, }; /// MSI capability (id 0x05) register layout. Offsets are relative to the capability /// header; whether the address is one or two dwords (and therefore where the data word /// sits) depends on `control_64bit_capable`. pub const msi = struct { pub const control: usize = 0x02; // u16 Message Control pub const control_enable: u16 = 0x0001; pub const control_multiple_message_capable_mask: u16 = 0x000E; // bits 3:1, log2(vectors requested) pub const control_multiple_message_enable_mask: u16 = 0x0070; // bits 6:4, log2(vectors granted) pub const control_64bit_capable: u16 = 0x0080; // bit 7: address is 64-bit (layout shifts) pub const control_per_vector_masking: u16 = 0x0100; // bit 8 pub const address: usize = 0x04; // u32 low address dword (both layouts) pub const address_high: usize = 0x08; // u32, present only when 64-bit capable pub const data_32: usize = 0x08; // u16 message data, 32-bit layout pub const data_64: usize = 0x0C; // u16 message data, 64-bit layout pub const mask_bits_32: usize = 0x0C; // u32, only with per-vector masking pub const mask_bits_64: usize = 0x10; }; /// MSI-X capability (id 0x11) register layout, plus the 16-byte vector table entry that /// lives in BAR space (not configuration space) at the decoded (BIR, offset). pub const msix = struct { pub const control: usize = 0x02; // u16 Message Control pub const control_table_size_mask: u16 = 0x07FF; // bits 10:0, encoded as N-1 pub const control_function_mask: u16 = 0x4000; // bit 14: mask every vector pub const control_enable: u16 = 0x8000; // bit 15 pub const table_offset_word: usize = 0x04; // u32: BIR in bits 2:0, table offset in bits 31:3 pub const pba_offset_word: usize = 0x08; // u32: same encoding, pending-bit array pub const bir_mask: u32 = 0x0000_0007; pub const offset_mask: u32 = 0xFFFF_FFF8; pub const entry_size: usize = 16; // table entry stride; offsets within an entry: pub const entry_address: usize = 0x0; // u32 low pub const entry_address_high: usize = 0x4; // u32 high pub const entry_data: usize = 0x8; // u32 pub const entry_vector_control: usize = 0xC; // u32 pub const entry_vector_control_masked: u32 = 0x1; // bit 0; entries reset to masked /// Where the table (or pending-bit array) lives, decoded from its offset/BIR dword. pub const TableLocation = struct { bar: u8, offset: u32 }; pub fn tableLocation(word: u32) TableLocation { return .{ .bar = @intCast(word & bir_mask), .offset = word & offset_mask }; } /// Number of table entries (the control field encodes N-1). pub fn tableSize(control_value: u16) u16 { return (control_value & control_table_size_mask) + 1; } }; /// Power-management capability (id 0x01) register layout. pub const power_management = struct { pub const capabilities: usize = 0x02; // u16 PMC (read-only: version, D-state support) pub const control_status: usize = 0x04; // u16 PMCSR pub const control_status_power_state_mask: u16 = 0x0003; // bits 1:0 pub const power_state_d0: u16 = 0x0; pub const power_state_d3_hot: u16 = 0x3; pub const control_status_pme_enable: u16 = 0x0100; // bit 8: plain RW — preserve on writes pub const control_status_pme_status: u16 = 0x8000; // bit 15: RW1C — write 0 or you clear it }; /// PCI Express capability (id 0x10) register layout — the slice function-level reset /// needs; the full capability is much larger. pub const pci_express = struct { pub const capabilities: usize = 0x02; // u16 PCIe Capabilities register pub const device_capabilities: usize = 0x04; // u32 pub const device_capabilities_flr: u32 = 1 << 28; // Function Level Reset supported pub const device_control: usize = 0x08; // u16 pub const device_control_initiate_flr: u16 = 1 << 15; pub const device_status: usize = 0x0A; // u16 pub const device_status_transactions_pending: u16 = 1 << 5; }; /// Extended (PCI Express) capabilities start here in the 4 KiB configuration space; a /// conventional-PCI function has nothing there (the space reads as all-ones). pub const extended_capability_start: usize = 0x100; /// Extended-capability next pointers are dword-aligned within the 4 KiB space. pub const extended_capability_pointer_mask: u16 = 0xFFC; /// The 32-bit header at the start of each extended capability: ID in bits 15:0, /// version in 19:16, next offset in 31:20 (0 = end of list). pub const ExtendedCapabilityHeader = struct { id: u16, version: u4, next: u16, pub fn decode(word: u32) ExtendedCapabilityHeader { return .{ .id = @truncate(word), .version = @truncate(word >> 16), .next = @intCast((word >> 20) & extended_capability_pointer_mask), }; } }; /// BAR bit layout: bit 0 selects I/O (1) vs memory (0) space; for a memory BAR, bits 2:1 /// give the type (00 = 32-bit, 10 = 64-bit spanning the next BAR), and the base address is /// the dword with the low 4 flag bits masked off. pub const bar_io_space: u32 = 0x1; pub const bar_type_mask: u32 = 0x6; pub const bar_type_64bit: u32 = 0x4; pub const bar_memory_base_mask: u32 = 0xFFFF_FFF0; /// 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 @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 { 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 { 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 { 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, }, .display => switch (std.enums.fromInt(display.SubClass, subclass) orelse return "") { .vga_compatible => enumName(display.vga_compatible.ProgIf, prog_if), else => null, }, .bridge => switch (std.enums.fromInt(bridge.SubClass, subclass) orelse return "") { .pci_to_pci => enumName(bridge.pci_to_pci.ProgIf, prog_if), else => null, }, .simple_communication => switch (std.enums.fromInt(simple_communication.SubClass, subclass) orelse return "") { .serial => enumName(simple_communication.serial.ProgIf, prog_if), else => null, }, .serial_bus => switch (std.enums.fromInt(serial_bus.SubClass, subclass) orelse return "") { .usb => enumName(serial_bus.usb.ProgIf, prog_if), else => null, }, else => null, }; return named orelse ""; } test "decodes the common class codes" { 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("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)); } 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()); } test "MSI-X table word decodes to BIR and offset" { const eq = std.testing.expectEqual; // BIR 3, table at 0x2000 within that BAR. try eq(msix.TableLocation{ .bar = 3, .offset = 0x2000 }, msix.tableLocation(0x0000_2003)); // BIR 0, offset 0 — the degenerate-but-common "table at BAR start" case. try eq(msix.TableLocation{ .bar = 0, .offset = 0 }, msix.tableLocation(0)); // Table size encodes N-1 in bits 10:0; enable/function-mask bits must not leak in. try eq(@as(u16, 11), msix.tableSize(msix.control_enable | 0x000A)); try eq(@as(u16, 1), msix.tableSize(0)); try eq(@as(u16, 2048), msix.tableSize(msix.control_table_size_mask)); } test "extended capability header unpacks id, version, next" { const eq = std.testing.expectEqual; // AER (id 0x0001), version 1, next capability at 0x140. const aer = ExtendedCapabilityHeader.decode(0x1401_0001); try eq(@as(u16, 0x0001), aer.id); try eq(@as(u4, 1), aer.version); try eq(@as(u16, 0x140), aer.next); // A zero header is the "nothing here" terminator. const none = ExtendedCapabilityHeader.decode(0); try eq(@as(u16, 0), none.id); try eq(@as(u16, 0), none.next); } test "command bits and capability ids compose" { const eq = std.testing.expectEqual; try eq(command_memory_space | command_bus_master, command_memory_and_bus_master); try eq(@as(u8, 0x05), @intFromEnum(CapabilityId.msi)); try eq(@as(u8, 0x11), @intFromEnum(CapabilityId.msix)); try eq(@as(u8, 0x01), @intFromEnum(CapabilityId.power_management)); try eq(@as(u8, 0x10), @intFromEnum(CapabilityId.pci_express)); }