diff --git a/system/devices/usb-ids.zig b/system/devices/usb-ids.zig index c086aaf..1ebedda 100644 --- a/system/devices/usb-ids.zig +++ b/system/devices/usb-ids.zig @@ -135,18 +135,37 @@ pub fn linkStateName(pls: u32) []const u8 { }; } -/// 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. +/// 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 { - if (@as(Class, @enumFromInt(class)) == .hid and subclass == @intFromEnum(hid.SubClass.boot)) { - return switch (@as(hid.Protocol, @enumFromInt(protocol))) { + 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", - }; - } - return className(class); + 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 @@ -163,6 +182,16 @@ pub const hub = struct { 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. @@ -183,6 +212,23 @@ pub const hid = struct { 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 @@ -226,6 +272,33 @@ pub const mass_storage = struct { 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 @@ -261,6 +334,25 @@ pub const communications = struct { 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. @@ -283,6 +375,23 @@ pub const wireless_controller = struct { 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. @@ -300,6 +409,20 @@ pub const miscellaneous = struct { 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. @@ -313,6 +436,15 @@ pub const application_specific = struct { 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 @@ -359,6 +491,30 @@ test "class codes match the USB-IF assignments" { _ = 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;