reorg: move device data modules into library/device/<domain>/
First step of the device-code reorganization (plan: group device code by domain, split each domain into a shareable data module + a logic module). This moves the pure-data modules — the enums, wire types, and taxonomies that any layer including the kernel can import cheaply — out of system/devices/ and into their domain home: device-abi -> library/device/model/device-abi.zig pci-class -> library/device/pci/pci-class.zig usb-abi -> library/device/usb/usb-abi.zig usb-ids -> library/device/usb/usb-ids.zig acpi-ids -> library/device/acpi/acpi-ids.zig aml/ -> library/device/acpi/aml/ Module names are unchanged, so this is a pure file move: only the b.addModule paths and the host-test file list in build.zig change; no importer is touched. system/devices/ now holds only the kernel-internal device model (device-model, platform, acpi, device-tree, power). The device *logic* (the pci Function helper, the usb transfer client) and the kernel's cosmetic taxonomy dependency are handled in following commits. zig build + zig build test green.
This commit is contained in:
@@ -0,0 +1,542 @@
|
||||
//! 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);
|
||||
}
|
||||
Reference in New Issue
Block a user