//! USB class-code decoding: turn the (class, subclass, protocol) triple a USB device or //! interface reports in its descriptors into typed values. The device descriptor carries one //! triple for the whole device, and each interface descriptor carries its own; a class code //! of zero at the device level defers entirely to the interfaces. Subclass and protocol //! codes are qualified by the class code — the same value means different things under //! different classes — so there is no single SubClass or Protocol enum: each class with //! spec-defined codes gets its own namespace below. Pure reference data (from the USB-IF //! defined class codes; see https://www.usb.org/defined-class-codes) — no hardware access — //! so it is shared by kernel discovery and any user-space tool (device naming, driver //! matching). // Base class codes (assigned by the USB-IF). The comment on each value notes where the code // may legally appear: in the device descriptor, in interface descriptors, or both. pub const Class = enum(u8) { // Use class information in the interface descriptors (device descriptor only). Each // interface within a configuration specifies its own class information and the various // interfaces operate independently. per_interface = 0x00, // Audio: speakers, microphones, sound cards (interface) audio = 0x01, // Communications and CDC control: modems, network adapters (both) communications = 0x02, // Human Interface Device: keyboards, mice, game controllers (interface) hid = 0x03, // Physical: force-feedback devices (interface) physical = 0x05, // Image: still-imaging cameras, scanners (interface) image = 0x06, // Printer (interface) printer = 0x07, // Mass storage: flash drives, external disks, card readers (interface) mass_storage = 0x08, // Hub (device descriptor only) hub = 0x09, // CDC-Data: the data interfaces paired with a communications control interface // (interface) cdc_data = 0x0A, // Smart card readers (interface) smart_card = 0x0B, // Content security (interface) content_security = 0x0D, // Video: webcams (interface) video = 0x0E, // Personal healthcare devices (interface) personal_healthcare = 0x0F, // Audio/Video devices (interface) audio_video = 0x10, // Billboard: describes alternate modes a USB Type-C device supports (device descriptor // only) billboard = 0x11, // USB Type-C bridge (interface) type_c_bridge = 0x12, // USB Bulk Display Protocol devices (interface) bulk_display = 0x13, // MCTP over USB protocol endpoint devices (interface) mctp = 0x14, // I3C devices (interface) i3c = 0x3C, // Diagnostic devices (both) diagnostic = 0xDC, // Wireless controllers: Bluetooth adapters (interface) wireless_controller = 0xE0, // Miscellaneous (both) miscellaneous = 0xEF, // Application-specific: firmware upgrade, IrDA bridges, test and measurement // (interface) application_specific = 0xFE, // Vendor-specific (both) vendor_specific = 0xFF, _, }; /// A human-readable name for a device/interface class code, for logs. Unknown /// codes fall through to "class 0xNN". pub fn className(class: u8) []const u8 { return switch (@as(Class, @enumFromInt(class))) { .per_interface => "per-interface", .audio => "Audio", .communications => "Communications", .hid => "HID", .physical => "Physical", .image => "Image", .printer => "Printer", .mass_storage => "Mass Storage", .hub => "Hub", .cdc_data => "CDC Data", .smart_card => "Smart Card", .content_security => "Content Security", .video => "Video", .personal_healthcare => "Personal Healthcare", .audio_video => "Audio/Video", .billboard => "Billboard", .type_c_bridge => "Type-C Bridge", .bulk_display => "Bulk Display", .mctp => "MCTP", .i3c => "I3C", .diagnostic => "Diagnostic", .wireless_controller => "Wireless Controller", .miscellaneous => "Miscellaneous", .application_specific => "Application-specific", .vendor_specific => "Vendor-specific", _ => "Unknown", }; } /// The USB speed class (as xHCI reports it in PORTSC/slot contexts) named. pub fn speedName(speed: u32) []const u8 { return switch (speed) { 1 => "Full-speed", 2 => "Low-speed", 3 => "High-speed", 4 => "SuperSpeed", 5 => "SuperSpeedPlus", else => "unknown-speed", }; } /// A USB3 Port Link State (xHCI PORTSC PLS field) named. pub fn linkStateName(pls: u32) []const u8 { return switch (pls) { 0 => "U0", 1 => "U1", 2 => "U2", 3 => "U3-suspended", 4 => "Disabled", 5 => "RxDetect", 6 => "Inactive", 7 => "Polling", 8 => "Recovery", 9 => "HotReset", 10 => "Compliance", 11 => "Test", 15 => "Resume", else => "reserved", }; } /// The most useful readable name for an interface's (class, subclass, protocol) /// triple, decoding the well-known combinations recognizable in a log — e.g. /// "HID boot keyboard", "Mass Storage SCSI Bulk-Only", "Bluetooth". Falls back /// to the class name (and then "Unknown") for codes without a spelled-out combo. pub fn interfaceName(class: u8, subclass: u8, protocol: u8) []const u8 { return switch (@as(Class, @enumFromInt(class))) { .hid => if (subclass == @intFromEnum(hid.SubClass.boot)) switch (@as(hid.Protocol, @enumFromInt(protocol))) { .keyboard => "HID boot keyboard", .mouse => "HID boot mouse", else => "HID boot device", } else "HID", .mass_storage => switch (@as(mass_storage.Protocol, @enumFromInt(protocol))) { .bulk_only => "Mass Storage (Bulk-Only)", .uas => "Mass Storage (UAS)", else => "Mass Storage", }, .hub => switch (@as(hub.Protocol, @enumFromInt(protocol))) { .super_speed => "Hub (SuperSpeed)", .hi_speed_multi_tt => "Hub (Hi-Speed multi-TT)", .hi_speed_single_tt => "Hub (Hi-Speed single-TT)", else => "Hub", }, .wireless_controller => if (subclass == @intFromEnum(wireless_controller.SubClass.radio_frequency)) wireless_controller.protocolName(protocol) else "Wireless Controller", .communications => communications.subclassName(subclass), .application_specific => application_specific.subclassName(subclass), .miscellaneous => "Miscellaneous", else => className(class), }; } // Subclass and protocol codes qualified by Class.hub. Hubs have no subclass codes; the // protocol distinguishes the hub's transaction-translator arrangement. pub const hub = struct { pub const Protocol = enum(u8) { // Full-speed hub full_speed = 0x00, // Hi-speed hub with a single transaction translator hi_speed_single_tt = 0x01, // Hi-speed hub with multiple transaction translators hi_speed_multi_tt = 0x02, // SuperSpeed hub (USB 3) super_speed = 0x03, _, }; pub fn protocolName(protocol: u8) []const u8 { return switch (@as(Protocol, @enumFromInt(protocol))) { .full_speed => "full-speed", .hi_speed_single_tt => "Hi-Speed single-TT", .hi_speed_multi_tt => "Hi-Speed multi-TT", .super_speed => "SuperSpeed", _ => "unknown", }; } }; // Subclass and protocol codes qualified by Class.hid. pub const hid = struct { pub const SubClass = enum(u8) { // No subclass none = 0x00, // Boot interface: the device also supports the simplified boot protocol, usable by // firmware before a full HID report-descriptor parser is available boot = 0x01, _, }; // Only meaningful when the subclass is boot pub const Protocol = enum(u8) { none = 0x00, keyboard = 0x01, mouse = 0x02, _, }; pub fn subclassName(subclass: u8) []const u8 { return switch (@as(SubClass, @enumFromInt(subclass))) { .none => "none", .boot => "boot", _ => "unknown", }; } pub fn protocolName(protocol: u8) []const u8 { return switch (@as(Protocol, @enumFromInt(protocol))) { .none => "none", .keyboard => "keyboard", .mouse => "mouse", _ => "unknown", }; } }; // Subclass and protocol codes qualified by Class.mass_storage. The subclass identifies the // command set the device understands; the protocol identifies the transport used to carry // commands, data, and status over the bus. pub const mass_storage = struct { pub const SubClass = enum(u8) { // SCSI command set not reported; de facto, treat as scsi not_reported = 0x00, // Reduced Block Commands: typically flash devices rbc = 0x01, // MMC-5 (ATAPI): CD and DVD drives atapi = 0x02, // QIC-157 tape drives (obsolete) qic_157 = 0x03, // UFI: floppy disk drives ufi = 0x04, // SFF-8070i (obsolete) sff_8070i = 0x05, // Transparent SCSI command set: the common case for flash drives and disks scsi = 0x06, // LSD FS: negotiated access to large storage devices lsd_fs = 0x07, // IEEE 1667 ieee_1667 = 0x08, // Vendor-specific vendor_specific = 0xFF, _, }; pub const Protocol = enum(u8) { // Control/Bulk/Interrupt with command completion interrupt cbi_completion_interrupt = 0x00, // Control/Bulk/Interrupt without command completion interrupt cbi = 0x01, // Bulk-only transport: the common case for flash drives and disks bulk_only = 0x50, // USB attached SCSI uas = 0x62, // Vendor-specific vendor_specific = 0xFF, _, }; pub fn subclassName(subclass: u8) []const u8 { return switch (@as(SubClass, @enumFromInt(subclass))) { .not_reported => "SCSI (not reported)", .rbc => "RBC", .atapi => "ATAPI", .qic_157 => "QIC-157", .ufi => "UFI", .sff_8070i => "SFF-8070i", .scsi => "SCSI", .lsd_fs => "LSD FS", .ieee_1667 => "IEEE 1667", .vendor_specific => "vendor-specific", _ => "unknown", }; } pub fn protocolName(protocol: u8) []const u8 { return switch (@as(Protocol, @enumFromInt(protocol))) { .cbi_completion_interrupt => "CBI", .cbi => "CBI (no completion IRQ)", .bulk_only => "Bulk-Only", .uas => "UAS", .vendor_specific => "vendor-specific", _ => "unknown", }; } }; // Subclass and protocol codes qualified by Class.communications (CDC). The protocol codes // are model-specific; the useful invariant is the subclass, which selects the control model // the interface implements. pub const communications = struct { pub const SubClass = enum(u8) { // Direct line control model direct_line = 0x01, // Abstract control model: USB modems and serial adapters abstract_control = 0x02, // Telephone control model telephone = 0x03, // Multi-channel control model multi_channel = 0x04, // CAPI control model capi = 0x05, // Ethernet networking control model ethernet = 0x06, // ATM networking control model atm = 0x07, // Wireless handset control model wireless_handset = 0x08, // Device management device_management = 0x09, // Mobile direct line model mobile_direct_line = 0x0A, // OBEX obex = 0x0B, // Ethernet emulation model ethernet_emulation = 0x0C, // Network control model network_control = 0x0D, _, }; pub fn subclassName(subclass: u8) []const u8 { return switch (@as(SubClass, @enumFromInt(subclass))) { .direct_line => "Direct Line", .abstract_control => "Abstract Control (modem/serial)", .telephone => "Telephone", .multi_channel => "Multi-Channel", .capi => "CAPI", .ethernet => "Ethernet", .atm => "ATM", .wireless_handset => "Wireless Handset", .device_management => "Device Management", .mobile_direct_line => "Mobile Direct Line", .obex => "OBEX", .ethernet_emulation => "Ethernet Emulation", .network_control => "Network Control", _ => "unknown", }; } }; // Subclass and protocol codes qualified by Class.wireless_controller. pub const wireless_controller = struct { pub const SubClass = enum(u8) { // Radio frequency controllers radio_frequency = 0x01, _, }; // Only meaningful when the subclass is radio_frequency pub const Protocol = enum(u8) { // Bluetooth programming interface bluetooth = 0x01, // Ultra-wideband radio control ultra_wideband = 0x02, // Remote NDIS remote_ndis = 0x03, // Bluetooth AMP controller bluetooth_amp = 0x04, _, }; pub fn subclassName(subclass: u8) []const u8 { return switch (@as(SubClass, @enumFromInt(subclass))) { .radio_frequency => "RF", _ => "unknown", }; } pub fn protocolName(protocol: u8) []const u8 { return switch (@as(Protocol, @enumFromInt(protocol))) { .bluetooth => "Bluetooth", .ultra_wideband => "Ultra-Wideband", .remote_ndis => "Remote NDIS", .bluetooth_amp => "Bluetooth AMP", _ => "unknown", }; } }; // Subclass and protocol codes qualified by Class.miscellaneous. pub const miscellaneous = struct { pub const SubClass = enum(u8) { // Common class common = 0x02, _, }; // Only meaningful when the subclass is common pub const Protocol = enum(u8) { // Interface association descriptor: at the device level, announces that the // configuration groups interfaces into functions with IADs interface_association = 0x01, _, }; pub fn subclassName(subclass: u8) []const u8 { return switch (@as(SubClass, @enumFromInt(subclass))) { .common => "common", _ => "unknown", }; } pub fn protocolName(protocol: u8) []const u8 { return switch (@as(Protocol, @enumFromInt(protocol))) { .interface_association => "Interface Association", _ => "unknown", }; } }; // Subclass and protocol codes qualified by Class.application_specific. pub const application_specific = struct { pub const SubClass = enum(u8) { // Device firmware upgrade firmware_upgrade = 0x01, // IrDA bridge irda_bridge = 0x02, // Test and measurement test_and_measurement = 0x03, _, }; pub fn subclassName(subclass: u8) []const u8 { return switch (@as(SubClass, @enumFromInt(subclass))) { .firmware_upgrade => "Device Firmware Upgrade", .irda_bridge => "IrDA Bridge", .test_and_measurement => "Test & Measurement", _ => "unknown", }; } }; /// Pack a (class, subclass, protocol) triple into one 0xCCSSPP value — the /// bus-native identity a USB bus driver reports in `ChildAdded.identity` and the /// device manager matches on (the USB analog of a packed PCI class code). Mirrors /// `pci_class.ClassCode.pack`, so both sides build/decode the identical u64. pub fn packTriple(class: u8, subclass: u8, protocol: u8) u64 { return (@as(u64, class) << 16) | (@as(u64, subclass) << 8) | protocol; } /// The inverse of `packTriple`. pub fn unpackTriple(triple: u64) struct { class: u8, subclass: u8, protocol: u8 } { return .{ .class = @truncate(triple >> 16), .subclass = @truncate(triple >> 8), .protocol = @truncate(triple), }; } test "class codes match the USB-IF assignments" { const std = @import("std"); const expectEqual = std.testing.expectEqual; try expectEqual(0x03, @intFromEnum(Class.hid)); try expectEqual(0x09, @intFromEnum(Class.hub)); try expectEqual(0xFF, @intFromEnum(Class.vendor_specific)); // A typical flash drive: mass storage, transparent SCSI, bulk-only transport. try expectEqual(0x06, @intFromEnum(mass_storage.SubClass.scsi)); try expectEqual(0x50, @intFromEnum(mass_storage.Protocol.bulk_only)); // A boot keyboard: HID, boot subclass, keyboard protocol. try expectEqual(0x01, @intFromEnum(hid.SubClass.boot)); try expectEqual(0x01, @intFromEnum(hid.Protocol.keyboard)); // Class codes are non-exhaustive: unlisted values pass through undamaged. const unknown: Class = @enumFromInt(0x42); try expectEqual(0x42, @intFromEnum(unknown)); _ = hub.Protocol.hi_speed_multi_tt; _ = communications.SubClass.abstract_control; _ = wireless_controller.Protocol.bluetooth; _ = miscellaneous.Protocol.interface_association; _ = application_specific.SubClass.firmware_upgrade; } test "readable names decode the well-known triples" { const std = @import("std"); const eql = std.testing.expectEqualStrings; try eql("Hub", className(0x09)); try eql("Unknown", className(0x42)); // interfaceName decodes the combos we log. try eql("HID boot keyboard", interfaceName(0x03, 0x01, 0x01)); try eql("HID boot mouse", interfaceName(0x03, 0x01, 0x02)); try eql("Mass Storage (Bulk-Only)", interfaceName(0x08, 0x06, 0x50)); try eql("Hub (SuperSpeed)", interfaceName(0x09, 0x00, 0x03)); try eql("Bluetooth", interfaceName(0xE0, 0x01, 0x01)); // The per-enum name functions. try eql("Bulk-Only", mass_storage.protocolName(0x50)); try eql("SCSI", mass_storage.subclassName(0x06)); try eql("Bluetooth", wireless_controller.protocolName(0x01)); try eql("SuperSpeed", hub.protocolName(0x03)); try eql("keyboard", hid.protocolName(0x01)); try eql("SuperSpeed", speedName(4)); try eql("Polling", linkStateName(7)); } test "packTriple / unpackTriple round-trip the identity a bus driver reports" { const std = @import("std"); const expectEqual = std.testing.expectEqual; // A boot keyboard interface: HID / boot / keyboard. const keyboard = packTriple( @intFromEnum(Class.hid), @intFromEnum(hid.SubClass.boot), @intFromEnum(hid.Protocol.keyboard), ); try expectEqual(@as(u64, 0x03_01_01), keyboard); // A flash drive interface: mass storage / SCSI / bulk-only. const storage = packTriple( @intFromEnum(Class.mass_storage), @intFromEnum(mass_storage.SubClass.scsi), @intFromEnum(mass_storage.Protocol.bulk_only), ); try expectEqual(@as(u64, 0x08_06_50), storage); const parts = unpackTriple(storage); try expectEqual(@as(u8, 0x08), parts.class); try expectEqual(@as(u8, 0x06), parts.subclass); try expectEqual(@as(u8, 0x50), parts.protocol); }