USB driver stack: xHCI transfers, HID keyboard/mouse, mass storage

Flesh out the xHCI host-controller driver into a full transfer engine and build
the three USB class drivers on top, all verified end to end under QEMU.

- xHCI engine (usb-xhci-library.zig): controller reset, command/event rings with
  cycle-bit bookkeeping (gated on a No-Op-command proof), device slots, Address
  Device, control transfers, full chapter-9 enumeration, Configure Endpoint, and
  interrupt/bulk transfers. Each interface is device_registered with its
  (class,subclass,protocol) identity, unique per (port,interface).
- Bus<->class transfer protocol (usb-transfer-protocol.zig + runtime.usb): open /
  control / interrupt-subscribe (async report pump on a poll timer) / bulk-by-
  physical-address, so sector data never crosses the 256-byte IPC limit.
- USB HID keyboard + mouse (usb-hid/): decode boot-protocol reports and publish
  to the input service. A USB usage is already the input protocol's keycode.
- USB mass storage (usb-storage/): Bulk-Only Transport + transparent SCSI,
  serving a block device under the new .block service id (block-protocol).
- device-manager matches USB interfaces to class drivers (usbDriverForIdentity).
- usb-abi / usb-ids made importable modules; add HID and mass-storage class
  requests, packTriple, and a usb_device DeviceClass.
- Fix test/qemu_test.py on macOS: the QMP unix-socket path was built from the
  deep worktree path and exceeded the 104-byte sun_path limit, so QEMU exited
  before booting. It now lives under a short temp path.

Tests: usb-report, usb-hid, usb-storage pass under python3 test/qemu_test.py;
host units (usb-abi, usb-ids, hid-report, bulk-only-transport, scsi) green.
This commit is contained in:
Daniel Samson
2026-07-13 14:11:00 +01:00
parent 452080e997
commit 3fb9d5936a
21 changed files with 2856 additions and 110 deletions
+5
View File
@@ -33,6 +33,11 @@ pub const DeviceClass = enum(u32) {
/// a broad io_port grant for OperationRegion access, and the SCI interrupt.
/// The one node whose claimant is trusted to run firmware bytecode.
acpi_tables,
/// One interface of a USB device, registered by the xHCI bus driver. It owns
/// no MMIO — it is reached through its controller — so it carries no
/// resources; the (class, subclass, protocol) triple that says what it is
/// travels in the bus report's identity, not here.
usb_device,
unknown,
};
+139 -51
View File
@@ -8,7 +8,7 @@
//! buffer at any offset, and bitmap bytes are packed structs so no caller ever needs a magic
//! mask. Class, subclass, and protocol code tables live in usb-ids.zig.
const DeviceState = enum(u8) {
pub const DeviceState = enum(u8) {
// Immediately after the USB device is attached to the USB system, it is in this state.
// The USB specifications do not define the state of a USB device that is detached from
// a USB system.
@@ -47,7 +47,7 @@ const DeviceState = enum(u8) {
suspended,
};
const RequestCode = enum(u8) {
pub const RequestCode = enum(u8) {
get_status = 0,
clear_feature = 1,
set_feature = 3,
@@ -59,10 +59,15 @@ const RequestCode = enum(u8) {
get_interface = 10,
set_interface = 11,
sync_frame = 12,
// Non-exhaustive: class-specific requests (HID, mass storage) reuse this byte
// field with codes from their own class's namespace — see the class-request
// constructors below. Some class codes numerically coincide with a standard
// one; the wire byte is what matters, and the constructors set it explicitly.
_,
};
// Direction of an endpoint, from the host's point of view
const EndpointDirection = enum(u1) {
pub const EndpointDirection = enum(u1) {
out = 0,
in = 1,
};
@@ -74,7 +79,7 @@ const EndpointDirection = enum(u1) {
// The bus address of a device, assigned by the host with SET_ADDRESS. Addresses are 7 bits
// wide.
const DeviceAddress = enum(u7) {
pub const DeviceAddress = enum(u7) {
// The default address every device answers at after a reset, until SET_ADDRESS
// completes
default = 0,
@@ -82,7 +87,7 @@ const DeviceAddress = enum(u7) {
};
// Identifies a configuration; from ConfigurationDescriptor.configuration_value.
const ConfigurationValue = enum(u8) {
pub const ConfigurationValue = enum(u8) {
// Not configured: returned by GET_CONFIGURATION while the device is in the address
// state, and passed to SET_CONFIGURATION to return a configured device to the address
// state
@@ -92,11 +97,11 @@ const ConfigurationValue = enum(u8) {
// Identifies an interface within a configuration; from
// InterfaceDescriptor.interface_number.
const InterfaceNumber = enum(u8) { _ };
pub const InterfaceNumber = enum(u8) { _ };
// Selects between the alternate settings of one interface; from
// InterfaceDescriptor.alternate_setting.
const AlternateSetting = enum(u8) {
pub const AlternateSetting = enum(u8) {
// The default setting of an interface
default = 0,
_,
@@ -104,7 +109,7 @@ const AlternateSetting = enum(u8) {
// The number of an endpoint within a device, 4 bits wide. The direction bit carried
// alongside it tells the two endpoints sharing a number apart.
const EndpointNumber = enum(u4) {
pub const EndpointNumber = enum(u4) {
// Endpoint zero: the default control pipe every device provides
default_control = 0,
_,
@@ -112,7 +117,7 @@ const EndpointNumber = enum(u4) {
// Index of a STRING descriptor, stored in descriptors that reference a string and passed to
// GET_DESCRIPTOR to read it.
const StringIndex = enum(u8) {
pub const StringIndex = enum(u8) {
// The device has no string descriptor for this field
none = 0,
_,
@@ -121,7 +126,7 @@ const StringIndex = enum(u8) {
// Characteristics of a device request (the bmRequestType field of a set-up packet). Fields are
// declared least-significant first: recipient occupies bits 4...0, kind bits 6...5, and
// direction bit 7.
const RequestType = packed struct(u8) {
pub const RequestType = packed struct(u8) {
// The recipient of the request (values 4...31 are reserved)
recipient: Recipient,
// The type of the request
@@ -129,27 +134,27 @@ const RequestType = packed struct(u8) {
// Data transfer direction. The value of this bit is ignored when length is zero.
direction: Direction,
const Recipient = enum(u5) {
pub const Recipient = enum(u5) {
device = 0,
interface = 1,
endpoint = 2,
other = 3,
};
const Kind = enum(u2) {
pub const Kind = enum(u2) {
standard = 0,
class = 1,
vendor = 2,
reserved = 3,
};
const Direction = enum(u1) {
pub const Direction = enum(u1) {
host_to_device = 0,
device_to_host = 1,
};
};
const Request = extern struct {
pub const Request = extern struct {
// Characteristics of the request
request_type: RequestType,
// Specific request
@@ -175,7 +180,7 @@ const Request = extern struct {
// The format of the index field when request_type specifies an endpoint as the
// recipient. The host should always set the direction bit to zero (but the device
// should accept either value) when the endpoint is part of a control pipe.
const EndpointIndex = packed struct(u16) {
pub const EndpointIndex = packed struct(u16) {
// Endpoint number
number: EndpointNumber,
// Reserved (reset to zero)
@@ -188,7 +193,7 @@ const Request = extern struct {
// The format of the index field when request_type specifies an interface as the
// recipient.
const InterfaceIndex = packed struct(u16) {
pub const InterfaceIndex = packed struct(u16) {
// Interface number
number: u8,
// Reserved (reset to zero)
@@ -199,7 +204,7 @@ const Request = extern struct {
// descriptor type in the high byte, and the descriptor index in the low byte. The index
// is used to select a specific descriptor (only for CONFIGURATION and STRING
// descriptors) when several descriptors of that type are implemented by a device.
const DescriptorValue = packed struct(u16) {
pub const DescriptorValue = packed struct(u16) {
// Descriptor index
index: u8 = 0,
// Descriptor type
@@ -209,7 +214,7 @@ const Request = extern struct {
// Feature selectors, used as the value field of CLEAR_FEATURE and SET_FEATURE requests. The
// comment on each value notes the recipient the selector applies to.
const FeatureSelector = enum(u16) {
pub const FeatureSelector = enum(u16) {
// Halts an endpoint (recipient: endpoint)
endpoint_halt = 0,
// Enables or disables the device's remote wakeup capability (recipient: device)
@@ -223,7 +228,7 @@ const FeatureSelector = enum(u16) {
// with the test_mode feature selector. Values 06h...3Fh are reserved for standard test
// selectors and C0h...FFh for vendor-specific test modes; all other unlisted values are
// reserved.
const TestMode = enum(u8) {
pub const TestMode = enum(u8) {
test_j = 0x01,
test_k = 0x02,
test_se0_nak = 0x03,
@@ -234,7 +239,7 @@ const TestMode = enum(u8) {
// The two bytes returned by a GET_STATUS request directed at a device. Fields are declared
// least-significant first.
const DeviceStatus = packed struct(u16) {
pub const DeviceStatus = packed struct(u16) {
// Whether the device is currently self-powered (as opposed to bus-powered). This bit
// cannot be changed with the SET_FEATURE or CLEAR_FEATURE requests.
self_powered: bool,
@@ -248,7 +253,7 @@ const DeviceStatus = packed struct(u16) {
// The two bytes returned by a GET_STATUS request directed at an endpoint. (A GET_STATUS
// request directed at an interface returns two bytes that are entirely reserved.)
const EndpointStatus = packed struct(u16) {
pub const EndpointStatus = packed struct(u16) {
// Whether the endpoint is currently halted. Set with the SET_FEATURE request using the
// endpoint_halt feature selector, and cleared with CLEAR_FEATURE.
halted: bool,
@@ -258,7 +263,7 @@ const EndpointStatus = packed struct(u16) {
// A target for the standard requests that may be directed at the device, an interface, or
// an endpoint.
const Target = union(enum) {
pub const Target = union(enum) {
device,
interface: InterfaceNumber,
endpoint: Request.EndpointIndex,
@@ -287,7 +292,7 @@ const Target = union(enum) {
// Reads the status of the given target: bit-cast the two bytes the device returns into a
// DeviceStatus or an EndpointStatus. (The two bytes returned for an interface are entirely
// reserved.)
fn getStatus(target: Target) Request {
pub fn getStatus(target: Target) Request {
return .{
.request_type = .{
.recipient = target.recipient(),
@@ -303,7 +308,7 @@ fn getStatus(target: Target) Request {
// Clears or disables the given feature. A device cannot be taken out of a test mode with
// this request; test_mode is only cleared by cycling power.
fn clearFeature(feature: FeatureSelector, target: Target) Request {
pub fn clearFeature(feature: FeatureSelector, target: Target) Request {
return .{
.request_type = .{
.recipient = target.recipient(),
@@ -319,7 +324,7 @@ fn clearFeature(feature: FeatureSelector, target: Target) Request {
// Sets or enables the given feature. For the test_mode feature selector, use setTestMode
// instead: the test selector rides in the high byte of the index field.
fn setFeature(feature: FeatureSelector, target: Target) Request {
pub fn setFeature(feature: FeatureSelector, target: Target) Request {
return .{
.request_type = .{
.recipient = target.recipient(),
@@ -335,7 +340,7 @@ fn setFeature(feature: FeatureSelector, target: Target) Request {
// Puts a hi-speed device into the given test mode: a SET_FEATURE request with the test_mode
// feature selector and the test selector in the high byte of the index field.
fn setTestMode(mode: TestMode) Request {
pub fn setTestMode(mode: TestMode) Request {
return .{
.request_type = .{
.recipient = .device,
@@ -352,7 +357,7 @@ fn setTestMode(mode: TestMode) Request {
// Assigns the device its bus address, moving it from the default state to the address
// state. The device does not answer at the new address until the status stage of this
// request completes.
fn setAddress(address: DeviceAddress) Request {
pub fn setAddress(address: DeviceAddress) Request {
return .{
.request_type = .{
.recipient = .device,
@@ -372,7 +377,7 @@ fn setAddress(address: DeviceAddress) Request {
// - language_id selects the language of a string descriptor, and is zero otherwise.
// - length is the number of bytes to read; a device never returns more than length bytes,
// but may return less if the descriptor is shorter.
fn getDescriptor(kind: DescriptorType, descriptor_index: u8, language_id: u16, length: u16) Request {
pub fn getDescriptor(kind: DescriptorType, descriptor_index: u8, language_id: u16, length: u16) Request {
return .{
.request_type = .{
.recipient = .device,
@@ -389,7 +394,7 @@ fn getDescriptor(kind: DescriptorType, descriptor_index: u8, language_id: u16, l
// Updates an existing descriptor or adds a new one (optional; many devices do not support
// this request). The parameters mirror getDescriptor; the descriptor itself is sent in the
// DATA stage.
fn setDescriptor(kind: DescriptorType, descriptor_index: u8, language_id: u16, length: u16) Request {
pub fn setDescriptor(kind: DescriptorType, descriptor_index: u8, language_id: u16, length: u16) Request {
return .{
.request_type = .{
.recipient = .device,
@@ -405,7 +410,7 @@ fn setDescriptor(kind: DescriptorType, descriptor_index: u8, language_id: u16, l
// Reads the currently active configuration: @enumFromInt the byte the device returns into a
// ConfigurationValue, which is none while the device is not configured.
fn getConfiguration() Request {
pub fn getConfiguration() Request {
return .{
.request_type = .{
.recipient = .device,
@@ -422,7 +427,7 @@ fn getConfiguration() Request {
// Selects the configuration with the given configuration_value (from
// ConfigurationDescriptor.configuration_value), moving the device from the address state to
// the configured state. Selecting none returns the device to the address state.
fn setConfiguration(configuration_value: ConfigurationValue) Request {
pub fn setConfiguration(configuration_value: ConfigurationValue) Request {
return .{
.request_type = .{
.recipient = .device,
@@ -438,7 +443,7 @@ fn setConfiguration(configuration_value: ConfigurationValue) Request {
// Reads the alternate setting currently selected for the given interface: @enumFromInt the
// byte the device returns into an AlternateSetting.
fn getInterface(interface: InterfaceNumber) Request {
pub fn getInterface(interface: InterfaceNumber) Request {
return .{
.request_type = .{
.recipient = .interface,
@@ -454,7 +459,7 @@ fn getInterface(interface: InterfaceNumber) Request {
// Selects an alternate setting (from InterfaceDescriptor.alternate_setting) for the given
// interface.
fn setInterface(interface: InterfaceNumber, alternate_setting: AlternateSetting) Request {
pub fn setInterface(interface: InterfaceNumber, alternate_setting: AlternateSetting) Request {
return .{
.request_type = .{
.recipient = .interface,
@@ -470,7 +475,7 @@ fn setInterface(interface: InterfaceNumber, alternate_setting: AlternateSetting)
// Reads the two-byte number of the frame in which the given isochronous endpoint's
// repeating pattern of transfers begins.
fn syncFrame(endpoint: Request.EndpointIndex) Request {
pub fn syncFrame(endpoint: Request.EndpointIndex) Request {
return .{
.request_type = .{
.recipient = .endpoint,
@@ -484,7 +489,79 @@ fn syncFrame(endpoint: Request.EndpointIndex) Request {
};
}
const DescriptorType = enum(u8) {
// Class-specific requests. These carry a `kind = .class` request_type and a
// request_code from the interface's class namespace (not the standard
// RequestCode set above); the code is written into the same byte field, which
// is why RequestCode is non-exhaustive. Each is directed at an interface, whose
// number rides in the index field.
// The HID class request codes (USB HID 1.11 §7.2). Only the ones danos issues
// are named; the field on the wire is the raw byte.
pub const HidRequestCode = enum(u8) {
get_report = 0x01,
get_idle = 0x02,
get_protocol = 0x03,
set_report = 0x09,
set_idle = 0x0A,
set_protocol = 0x0B,
};
// The two protocols a boot-capable HID device can run (USB HID 1.11 §7.2.5).
// A driver selects `boot` for the simplified fixed-format boot report, usable
// before a full report-descriptor parser exists.
pub const HidProtocol = enum(u8) {
boot = 0,
report = 1,
};
// SET_PROTOCOL: choose the boot or report protocol on a HID interface.
pub fn setProtocol(interface: InterfaceNumber, protocol: HidProtocol) Request {
return .{
.request_type = .{ .recipient = .interface, .kind = .class, .direction = .host_to_device },
.request_code = @enumFromInt(@intFromEnum(HidRequestCode.set_protocol)),
.value = @intFromEnum(protocol),
.index = @intFromEnum(interface),
.length = 0,
};
}
// SET_IDLE: bound a HID interface's report rate. `duration` is in 4 ms units
// (0 means report only on change); `report_id` selects a report (0 = all).
pub fn setIdle(interface: InterfaceNumber, duration: u8, report_id: u8) Request {
return .{
.request_type = .{ .recipient = .interface, .kind = .class, .direction = .host_to_device },
.request_code = @enumFromInt(@intFromEnum(HidRequestCode.set_idle)),
.value = (@as(u16, duration) << 8) | report_id,
.index = @intFromEnum(interface),
.length = 0,
};
}
// Bulk-Only Mass Storage Reset (USB MSC BOT §3.1): ready a mass-storage
// interface for the next Command Block Wrapper after a protocol error.
pub fn bulkOnlyMassStorageReset(interface: InterfaceNumber) Request {
return .{
.request_type = .{ .recipient = .interface, .kind = .class, .direction = .host_to_device },
.request_code = @enumFromInt(0xFF),
.value = 0,
.index = @intFromEnum(interface),
.length = 0,
};
}
// Get Max LUN (USB MSC BOT §3.2): read the highest logical unit number the
// device supports (0 for a single-LUN flash drive). One byte is returned.
pub fn getMaxLun(interface: InterfaceNumber) Request {
return .{
.request_type = .{ .recipient = .interface, .kind = .class, .direction = .device_to_host },
.request_code = @enumFromInt(0xFE),
.value = 0,
.index = @intFromEnum(interface),
.length = 1,
};
}
pub const DescriptorType = enum(u8) {
device = 1,
configuration = 2,
string = 3,
@@ -496,7 +573,7 @@ const DescriptorType = enum(u8) {
_,
};
const DeviceDescriptor = extern struct {
pub const DeviceDescriptor = extern struct {
// Size of this descriptor in bytes
length: u8,
// DEVICE Descriptor Type
@@ -541,7 +618,7 @@ const DeviceDescriptor = extern struct {
configuration_count: u8,
};
const DeviceQualifierDescriptor = extern struct {
pub const DeviceQualifierDescriptor = extern struct {
// Size of this descriptor in bytes
length: u8,
// DEVICE_QUALIFIER Descriptor Type
@@ -564,7 +641,7 @@ const DeviceQualifierDescriptor = extern struct {
reserved: u8,
};
const ConfigurationDescriptor = extern struct {
pub const ConfigurationDescriptor = extern struct {
// Size of this descriptor in bytes
length: u8,
// CONFIGURATION Descriptor Type
@@ -593,7 +670,7 @@ const ConfigurationDescriptor = extern struct {
max_power: u8,
// Configuration characteristics. Fields are declared least-significant first.
const Attributes = packed struct(u8) {
pub const Attributes = packed struct(u8) {
// Reserved, reset to zero (D4...0)
reserved: u5,
// Whether Remote Wakeup is supported by this configuration (D5)
@@ -612,9 +689,9 @@ const ConfigurationDescriptor = extern struct {
// its alternative speed. The structure of the OTHER_SPEED_CONFIGURATION is identical to that
// of the CONFIGURATION descriptor; the only difference is that the descriptor_type field
// reflects that the descriptor is an OTHER_SPEED_CONFIGURATION descriptor.
const OtherSpeedConfigurationDescriptor = ConfigurationDescriptor;
pub const OtherSpeedConfigurationDescriptor = ConfigurationDescriptor;
const InterfaceDescriptor = extern struct {
pub const InterfaceDescriptor = extern struct {
// Size of this descriptor in bytes
length: u8,
// INTERFACE Descriptor Type
@@ -654,7 +731,7 @@ const InterfaceDescriptor = extern struct {
interface_index: StringIndex,
};
const EndpointDescriptor = extern struct {
pub const EndpointDescriptor = extern struct {
// Size of this descriptor in bytes
length: u8,
// ENDPOINT Descriptor Type
@@ -683,7 +760,7 @@ const EndpointDescriptor = extern struct {
interval: u8,
// The address of an endpoint. Fields are declared least-significant first.
const Address = packed struct(u8) {
pub const Address = packed struct(u8) {
// Endpoint Number (D3...0)
number: EndpointNumber,
// Reserved, reset to zero (D6...4)
@@ -693,7 +770,7 @@ const EndpointDescriptor = extern struct {
};
// An endpoint's attributes. Fields are declared least-significant first.
const Attributes = packed struct(u8) {
pub const Attributes = packed struct(u8) {
// Transfer Type (D1...0)
transfer_type: TransferType,
// Synchronization Type; isochronous endpoints only, reserved and reset to zero for
@@ -706,21 +783,21 @@ const EndpointDescriptor = extern struct {
reserved: u2,
};
const TransferType = enum(u2) {
pub const TransferType = enum(u2) {
control = 0,
isochronous = 1,
bulk = 2,
interrupt = 3,
};
const Synchronization = enum(u2) {
pub const Synchronization = enum(u2) {
none = 0,
asynchronous = 1,
adaptive = 2,
synchronous = 3,
};
const Usage = enum(u2) {
pub const Usage = enum(u2) {
data = 0,
feedback = 1,
implicit_feedback_data = 2,
@@ -728,7 +805,7 @@ const EndpointDescriptor = extern struct {
};
// The maximum packet size of an endpoint. Fields are declared least-significant first.
const MaxPacketSize = packed struct(u16) {
pub const MaxPacketSize = packed struct(u16) {
// Maximum packet size in bytes (bits 10...0)
size: u11,
// Number of additional transaction opportunities per microframe, for high-speed
@@ -739,7 +816,7 @@ const EndpointDescriptor = extern struct {
reserved: u3,
};
const AdditionalTransactions = enum(u2) {
pub const AdditionalTransactions = enum(u2) {
// None (1 transaction per microframe)
none = 0,
// 1 additional (2 transactions per microframe)
@@ -755,7 +832,7 @@ const EndpointDescriptor = extern struct {
// header, followed by the variable-length payload:
// - index 0: an array of two-byte LANGID codes (wLangID[0] through wLangID[x])
// - other indices: a Unicode string of N bytes
const StringDescriptor = extern struct {
pub const StringDescriptor = extern struct {
// Size of this descriptor in bytes
length: u8,
// STRING Descriptor Type
@@ -834,7 +911,7 @@ test "bitmap packings match the specification" {
try expect(hid_type != .device);
}
fn expectRequestBytes(request: Request, expected: [8]u8) !void {
pub fn expectRequestBytes(request: Request, expected: [8]u8) !void {
try std.testing.expectEqualSlices(u8, &expected, std.mem.asBytes(&request));
}
@@ -855,3 +932,14 @@ test "standard request constructors encode the specification's set-up packets" {
try expectRequestBytes(setInterface(@enumFromInt(2), @enumFromInt(1)), .{ 0x01, 11, 1, 0, 2, 0, 0, 0 });
try expectRequestBytes(syncFrame(.{ .number = @enumFromInt(3), .direction = .in }), .{ 0x82, 12, 0, 0, 0x83, 0, 2, 0 });
}
test "class request constructors encode the specification's set-up packets" {
// bmRequestType for a host-to-device class request to an interface = 0x21;
// device-to-host = 0xA1. The request_code byte is the class code, not a
// standard one — SET_PROTOCOL 0x0B, SET_IDLE 0x0A, BOT reset 0xFF, Max LUN 0xFE.
try expectRequestBytes(setProtocol(@enumFromInt(0), .boot), .{ 0x21, 0x0B, 0, 0, 0, 0, 0, 0 });
try expectRequestBytes(setProtocol(@enumFromInt(1), .report), .{ 0x21, 0x0B, 1, 0, 1, 0, 0, 0 });
try expectRequestBytes(setIdle(@enumFromInt(1), 0, 0), .{ 0x21, 0x0A, 0, 0, 1, 0, 0, 0 });
try expectRequestBytes(bulkOnlyMassStorageReset(@enumFromInt(0)), .{ 0x21, 0xFF, 0, 0, 0, 0, 0, 0 });
try expectRequestBytes(getMaxLun(@enumFromInt(0)), .{ 0xA1, 0xFE, 0, 0, 0, 0, 1, 0 });
}
+62 -19
View File
@@ -11,7 +11,7 @@
// 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.
const Class = enum(u8) {
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.
@@ -72,8 +72,8 @@ const Class = enum(u8) {
// Subclass and protocol codes qualified by Class.hub. Hubs have no subclass codes; the
// protocol distinguishes the hub's transaction-translator arrangement.
const hub = struct {
const Protocol = enum(u8) {
pub const hub = struct {
pub const Protocol = enum(u8) {
// Full-speed hub
full_speed = 0x00,
// Hi-speed hub with a single transaction translator
@@ -87,8 +87,8 @@ const hub = struct {
};
// Subclass and protocol codes qualified by Class.hid.
const hid = struct {
const SubClass = enum(u8) {
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
@@ -98,7 +98,7 @@ const hid = struct {
};
// Only meaningful when the subclass is boot
const Protocol = enum(u8) {
pub const Protocol = enum(u8) {
none = 0x00,
keyboard = 0x01,
mouse = 0x02,
@@ -109,8 +109,8 @@ const hid = struct {
// 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.
const mass_storage = struct {
const SubClass = enum(u8) {
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
@@ -134,7 +134,7 @@ const mass_storage = struct {
_,
};
const Protocol = enum(u8) {
pub const Protocol = enum(u8) {
// Control/Bulk/Interrupt with command completion interrupt
cbi_completion_interrupt = 0x00,
// Control/Bulk/Interrupt without command completion interrupt
@@ -152,8 +152,8 @@ const mass_storage = struct {
// 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.
const communications = struct {
const SubClass = enum(u8) {
pub const communications = struct {
pub const SubClass = enum(u8) {
// Direct line control model
direct_line = 0x01,
// Abstract control model: USB modems and serial adapters
@@ -185,15 +185,15 @@ const communications = struct {
};
// Subclass and protocol codes qualified by Class.wireless_controller.
const wireless_controller = struct {
const SubClass = enum(u8) {
pub const wireless_controller = struct {
pub const SubClass = enum(u8) {
// Radio frequency controllers
radio_frequency = 0x01,
_,
};
// Only meaningful when the subclass is radio_frequency
const Protocol = enum(u8) {
pub const Protocol = enum(u8) {
// Bluetooth programming interface
bluetooth = 0x01,
// Ultra-wideband radio control
@@ -207,15 +207,15 @@ const wireless_controller = struct {
};
// Subclass and protocol codes qualified by Class.miscellaneous.
const miscellaneous = struct {
const SubClass = enum(u8) {
pub const miscellaneous = struct {
pub const SubClass = enum(u8) {
// Common class
common = 0x02,
_,
};
// Only meaningful when the subclass is common
const Protocol = enum(u8) {
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,
@@ -224,8 +224,8 @@ const miscellaneous = struct {
};
// Subclass and protocol codes qualified by Class.application_specific.
const application_specific = struct {
const SubClass = enum(u8) {
pub const application_specific = struct {
pub const SubClass = enum(u8) {
// Device firmware upgrade
firmware_upgrade = 0x01,
// IrDA bridge
@@ -236,6 +236,23 @@ const application_specific = struct {
};
};
/// 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;
@@ -262,3 +279,29 @@ test "class codes match the USB-IF assignments" {
_ = 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);
}