danos/system/devices/pci-class.zig

569 lines
20 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;
}
};
/// 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());
}