750 lines
29 KiB
Zig
750 lines
29 KiB
Zig
//! 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));
|
|
}
|