Decode PCI class codes in device discovery
`pci_device` alone says nothing — an ISA bridge, an AHCI controller, and an xHCI USB
controller are all just `pci_device` by DeviceClass. The identity lives in the 24-bit
class code (base class / subclass / prog-IF) that discovery already recorded in
ids.pci_class but the dump threw away.
New system/devices/pci-class.zig (module `pci-class`): pure reference data from the PCI
spec (per the OSDev PCI table) decoding the triple into names — className, subclassName,
progIfName, plus ClassCode.unpack. No hardware access, so it's shared by kernel
discovery and any future user-space PCI tool. device_tree.dump now prints, under each
PCI function, its class/subclass/prog-IF as both hex and name:
pci0:00:1f.0 [pci_device]
class 0x06 (Bridge) subclass 0x01 (ISA Bridge) progif 0x00
pci0:00:1f.2 [pci_device]
class 0x01 (Mass Storage Controller) subclass 0x06 (Serial ATA Controller) progif 0x01 (AHCI 1.0)
Host test covers the decoder (bridge/AHCI/xHCI, the "Other" 0x80 convention, unknowns).
Suite 41/41 plus host tests.
This commit is contained in:
@@ -116,6 +116,11 @@ pub fn build(b: *std.Build) void {
|
|||||||
const device_abi_module = b.addModule("device-abi", .{
|
const device_abi_module = b.addModule("device-abi", .{
|
||||||
.root_source_file = b.path("system/devices/device-abi.zig"),
|
.root_source_file = b.path("system/devices/device-abi.zig"),
|
||||||
});
|
});
|
||||||
|
// PCI class-code decoding (class/subclass/prog-IF -> names). Pure reference data,
|
||||||
|
// shared by kernel discovery (the device-tree dump) and any user-space PCI tool.
|
||||||
|
const pci_class_module = b.addModule("pci-class", .{
|
||||||
|
.root_source_file = b.path("system/devices/pci-class.zig"),
|
||||||
|
});
|
||||||
|
|
||||||
// Kernel tunables (maximum_cpus, stack sizes, tick rate). A dependency-free module of
|
// Kernel tunables (maximum_cpus, stack sizes, tick rate). A dependency-free module of
|
||||||
// compile-time constants, imported wherever a knob is read; keeps the trade-offs
|
// compile-time constants, imported wherever a knob is read; keeps the trade-offs
|
||||||
@@ -153,6 +158,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
.{ .name = "boot-handoff", .module = boot_handoff_module }, // BootInformation (carries the ACPI RSDP), physicalToVirtual
|
.{ .name = "boot-handoff", .module = boot_handoff_module }, // BootInformation (carries the ACPI RSDP), physicalToVirtual
|
||||||
.{ .name = "abi", .module = abi_module }, // acpi.zig works in page_size units
|
.{ .name = "abi", .module = abi_module }, // acpi.zig works in page_size units
|
||||||
.{ .name = "device-abi", .module = device_abi_module }, // device-model's DeviceClass/ResourceKind live here
|
.{ .name = "device-abi", .module = device_abi_module }, // device-model's DeviceClass/ResourceKind live here
|
||||||
|
.{ .name = "pci-class", .module = pci_class_module }, // decode PCI class codes in the device dump
|
||||||
.{ .name = "parameters", .module = parameters_module }, // maximum_cpus (the discovery pool)
|
.{ .name = "parameters", .module = parameters_module }, // maximum_cpus (the discovery pool)
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -439,6 +445,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
"system/boot-handoff.zig",
|
"system/boot-handoff.zig",
|
||||||
"system/abi.zig",
|
"system/abi.zig",
|
||||||
"system/devices/device-abi.zig",
|
"system/devices/device-abi.zig",
|
||||||
|
"system/devices/pci-class.zig", // class/subclass/prog-IF name decoding
|
||||||
"library/mmio/mmio.zig", // barriers assemble + registers round-trip
|
"library/mmio/mmio.zig", // barriers assemble + registers round-trip
|
||||||
}) |root| {
|
}) |root| {
|
||||||
const mod_tests = b.addTest(.{
|
const mod_tests = b.addTest(.{
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const device_abi = @import("device-abi");
|
const device_abi = @import("device-abi");
|
||||||
|
const pci_class = @import("pci-class");
|
||||||
|
|
||||||
/// The hardware primitives a discovery backend needs but can't express portably.
|
/// The hardware primitives a discovery backend needs but can't express portably.
|
||||||
/// The kernel injects an implementation (the architecture VMM + port I/O), so the device
|
/// The kernel injects an implementation (the architecture VMM + port I/O), so the device
|
||||||
@@ -195,6 +196,21 @@ fn dumpNode(device: *const Device, depth: usize, emit: *const fn ([]const u8) vo
|
|||||||
std.fmt.bufPrint(buffer[indent..], "{s} [{s}]\n", .{ device.name(), @tagName(device.class) }) catch return;
|
std.fmt.bufPrint(buffer[indent..], "{s} [{s}]\n", .{ device.name(), @tagName(device.class) }) catch return;
|
||||||
emit(buffer[0 .. indent + body.len]);
|
emit(buffer[0 .. indent + body.len]);
|
||||||
|
|
||||||
|
// For a PCI function, decode its class code — the (class / subclass / prog-IF)
|
||||||
|
// triple that says what it actually is, which the coarse `DeviceClass` can't.
|
||||||
|
if (device.ids.pci_class) |packed_code| {
|
||||||
|
const cc = pci_class.ClassCode.unpack(packed_code);
|
||||||
|
var cbuf: [200]u8 = undefined;
|
||||||
|
const pad = @min(indent + 2, 42);
|
||||||
|
@memset(cbuf[0..pad], ' ');
|
||||||
|
const pif = pci_class.progIfName(cc.base, cc.subclass, cc.prog_if);
|
||||||
|
const cline = if (pif.len != 0)
|
||||||
|
std.fmt.bufPrint(cbuf[pad..], "class 0x{x:0>2} ({s}) subclass 0x{x:0>2} ({s}) progif 0x{x:0>2} ({s})\n", .{ cc.base, pci_class.className(cc.base), cc.subclass, pci_class.subclassName(cc.base, cc.subclass), cc.prog_if, pif }) catch return
|
||||||
|
else
|
||||||
|
std.fmt.bufPrint(cbuf[pad..], "class 0x{x:0>2} ({s}) subclass 0x{x:0>2} ({s}) progif 0x{x:0>2}\n", .{ cc.base, pci_class.className(cc.base), cc.subclass, pci_class.subclassName(cc.base, cc.subclass), cc.prog_if }) catch return;
|
||||||
|
emit(cbuf[0 .. pad + cline.len]);
|
||||||
|
}
|
||||||
|
|
||||||
for (device.resources[0..device.resource_count]) |r| {
|
for (device.resources[0..device.resource_count]) |r| {
|
||||||
var rbuf: [200]u8 = undefined;
|
var rbuf: [200]u8 = undefined;
|
||||||
const pad = @min(indent + 2, 42);
|
const pad = @min(indent + 2, 42);
|
||||||
|
|||||||
@@ -0,0 +1,261 @@
|
|||||||
|
//! 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user