danos/system/devices/pci-class.zig

262 lines
10 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 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
}