//! 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", }; } /// A fuller name for a HID interface's (subclass, protocol): "HID boot keyboard" /// / "HID boot mouse", else just the class name. Used to make input devices /// recognizable in logs. pub fn interfaceName(class: u8, subclass: u8, protocol: u8) []const u8 { if (@as(Class, @enumFromInt(class)) == .hid and subclass == @intFromEnum(hid.SubClass.boot)) { return switch (@as(hid.Protocol, @enumFromInt(protocol))) { .keyboard => "HID boot keyboard", .mouse => "HID boot mouse", else => "HID", }; } return 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, _, }; }; // 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, _, }; }; // 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, _, }; }; // 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, _, }; }; // 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, _, }; }; // 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, _, }; }; // 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, _, }; }; /// 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 "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); }