//! USB device-framework wire ABI: the set-up packets, standard requests, and standard //! descriptors every USB device speaks over its default control pipe, as defined by chapter 9 //! of the USB 2.0 specification (see https://wiki.osdev.org/Universal_Serial_Bus). Pure data //! definitions — no hardware access — shared by the host-controller bus drivers (which build //! the requests) and anything that parses what devices return (device naming, driver //! matching, configuration). The structs mirror the wire byte-for-byte: multi-byte fields are //! little-endian and align(1), so a descriptor can be bit-cast straight out of a transfer //! 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) { // 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. attached, // A device is in this state after it has both been attached to the bus, and the VBUS line is // applied to the device (the host controller drives the VBUS at +5V, however this is only // particularly important for hardware developers). In this state, the device must not respond // to any bus transactions. The USB specification recognizes three potential scenarios with // respect to how a device draws power: // - Self-Powered Devices draw power from an external power source (e.g, a USB printer plugs // into the wall as well as a USB port). Although the device may be considered // technically "powered" even before attachment to the USB, it is still only considered // powered after the VBUS line is applied to the device. // - Bus-Powered Devices draw power solely from the USB up to 100mA. // - Self- or Bus-Powered Devices may draw power from either the bus or an external power // source, depending on the configuration. These devices may change power source at any // time. If a device is currently self-powered and requires more than 100mA of power, but // switches to being bus-powered, then the device must return to the Address state. powered, // A device in the powered state enters the default state after receiving a bus reset. In this // state, the device is addressable at the default, reserved address of 0. At this point, the // device is operating at the correct speed. The host is expected to allow 10 milliseconds // before expecting the device to respond to data transfers after reset. default, // A device enters this state after the host assigns it an address via the default control pipe, // which is always accessible whether the device's address has been set or not. address, // A device is in this state after the host examines its possible configurations and selects // one. All endpoint's data toggle bits are initialized to zero when a device enters this state. configured, // When no traffic is observed on the bus for a period of 1 millisecond, a USB device enters // this state, characterized by its low power consumption. The device's address and // configuration settings are maintained while suspended. A device exits the suspended state as // soon as it begins seeing bus activity again. The host is expected to allow 10 milliseconds // before expecting the device to respond to data transfers after resume. suspended, }; const RequestCode = enum(u8) { get_status = 0, clear_feature = 1, set_feature = 3, set_address = 5, get_descriptor = 6, set_descriptor = 7, get_configuration = 8, set_configuration = 9, get_interface = 10, set_interface = 11, sync_frame = 12, }; // Direction of an endpoint, from the host's point of view const EndpointDirection = enum(u1) { out = 0, in = 1, }; // Identifier newtypes: distinct wire-sized types for values that identify something on the // device rather than count something. Each is a non-exhaustive enum whose values originate // in the descriptors below and flow, still typed, into the standard request constructors — // so an interface number can never be passed where a configuration value is expected. // The bus address of a device, assigned by the host with SET_ADDRESS. Addresses are 7 bits // wide. const DeviceAddress = enum(u7) { // The default address every device answers at after a reset, until SET_ADDRESS // completes default = 0, _, }; // Identifies a configuration; from ConfigurationDescriptor.configuration_value. 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 none = 0, _, }; // Identifies an interface within a configuration; from // InterfaceDescriptor.interface_number. const InterfaceNumber = enum(u8) { _ }; // Selects between the alternate settings of one interface; from // InterfaceDescriptor.alternate_setting. const AlternateSetting = enum(u8) { // The default setting of an interface default = 0, _, }; // 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) { // Endpoint zero: the default control pipe every device provides default_control = 0, _, }; // Index of a STRING descriptor, stored in descriptors that reference a string and passed to // GET_DESCRIPTOR to read it. const StringIndex = enum(u8) { // The device has no string descriptor for this field none = 0, _, }; // 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) { // The recipient of the request (values 4...31 are reserved) recipient: Recipient, // The type of the request kind: Kind, // Data transfer direction. The value of this bit is ignored when length is zero. direction: Direction, const Recipient = enum(u5) { device = 0, interface = 1, endpoint = 2, other = 3, }; const Kind = enum(u2) { standard = 0, class = 1, vendor = 2, reserved = 3, }; const Direction = enum(u1) { host_to_device = 0, device_to_host = 1, }; }; const Request = extern struct { // Characteristics of the request request_type: RequestType, // Specific request request_code: RequestCode, // Word-sized field that may (or may not) serve as a parameter to the request, depending // on the specific request. For GET_DESCRIPTOR and SET_DESCRIPTOR, bit-cast a // DescriptorValue into this field. value: u16 align(1), // Word-sized field that may (or may not) serve as a parameter to the request, depending // on the specific request. Typically this field holds an index or an offset value. When // request_type specifies an endpoint or an interface as the recipient, bit-cast an // EndpointIndex or an InterfaceIndex into this field. index: u16 align(1), // Number of bytes to transfer if there is a DATA stage. // - If this field is non-zero, and request_type indicates a transfer from // device-to-host, then the device must never return more than length bytes of data. // However, a device may return less. // - If this field is non-zero, and request_type indicates a transfer from // host-to-device, then the host must send exactly length bytes of data. If the host // sends more than length bytes, the behavior of the device is undefined. length: u16 align(1), // 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) { // Endpoint number number: EndpointNumber, // Reserved (reset to zero) reserved: u3 = 0, // Selects the OUT or the IN endpoint with the specified endpoint number direction: EndpointDirection, // Reserved (reset to zero) reserved_high: u8 = 0, }; // The format of the index field when request_type specifies an interface as the // recipient. const InterfaceIndex = packed struct(u16) { // Interface number number: u8, // Reserved (reset to zero) reserved: u8 = 0, }; // The format of the value field of GET_DESCRIPTOR and SET_DESCRIPTOR requests: the // 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) { // Descriptor index index: u8 = 0, // Descriptor type kind: DescriptorType, }; }; // 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) { // Halts an endpoint (recipient: endpoint) endpoint_halt = 0, // Enables or disables the device's remote wakeup capability (recipient: device) device_remote_wakeup = 1, // Puts a hi-speed device into a test mode, selected by a TestMode value in the high // byte of the index field (recipient: device) test_mode = 2, }; // Test mode selectors, passed in the high byte of the index field of a SET_FEATURE request // 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) { test_j = 0x01, test_k = 0x02, test_se0_nak = 0x03, test_packet = 0x04, test_force_enable = 0x05, _, }; // The two bytes returned by a GET_STATUS request directed at a device. Fields are declared // least-significant first. 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, // Whether the device is currently enabled to request remote wakeup. Changed with the // SET_FEATURE and CLEAR_FEATURE requests using the device_remote_wakeup feature // selector. remote_wakeup: bool, // Reserved (reset to zero) reserved: u14, }; // 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) { // 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, // Reserved (reset to zero) reserved: u15, }; // A target for the standard requests that may be directed at the device, an interface, or // an endpoint. const Target = union(enum) { device, interface: InterfaceNumber, endpoint: Request.EndpointIndex, fn recipient(target: Target) RequestType.Recipient { return switch (target) { .device => .device, .interface => .interface, .endpoint => .endpoint, }; } fn index(target: Target) u16 { return switch (target) { .device => 0, .interface => |number| @intFromEnum(number), .endpoint => |endpoint| @bitCast(endpoint), }; } }; // Constructors for the standard device requests, one per RequestCode. Each returns a // ready-to-send set-up packet with the request_type, value, index, and length fields the // specification prescribes for that request. // 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 { return .{ .request_type = .{ .recipient = target.recipient(), .kind = .standard, .direction = .device_to_host, }, .request_code = .get_status, .value = 0, .index = target.index(), .length = 2, }; } // 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 { return .{ .request_type = .{ .recipient = target.recipient(), .kind = .standard, .direction = .host_to_device, }, .request_code = .clear_feature, .value = @intFromEnum(feature), .index = target.index(), .length = 0, }; } // 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 { return .{ .request_type = .{ .recipient = target.recipient(), .kind = .standard, .direction = .host_to_device, }, .request_code = .set_feature, .value = @intFromEnum(feature), .index = target.index(), .length = 0, }; } // 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 { return .{ .request_type = .{ .recipient = .device, .kind = .standard, .direction = .host_to_device, }, .request_code = .set_feature, .value = @intFromEnum(FeatureSelector.test_mode), .index = @as(u16, @intFromEnum(mode)) << 8, .length = 0, }; } // 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 { return .{ .request_type = .{ .recipient = .device, .kind = .standard, .direction = .host_to_device, }, .request_code = .set_address, .value = @intFromEnum(address), .index = 0, .length = 0, }; } // Reads a descriptor from the device. // - descriptor_index selects among descriptors of the same type, and is only used for // configuration and string descriptors. // - 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 { return .{ .request_type = .{ .recipient = .device, .kind = .standard, .direction = .device_to_host, }, .request_code = .get_descriptor, .value = @bitCast(Request.DescriptorValue{ .index = descriptor_index, .kind = kind }), .index = language_id, .length = length, }; } // 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 { return .{ .request_type = .{ .recipient = .device, .kind = .standard, .direction = .host_to_device, }, .request_code = .set_descriptor, .value = @bitCast(Request.DescriptorValue{ .index = descriptor_index, .kind = kind }), .index = language_id, .length = length, }; } // 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 { return .{ .request_type = .{ .recipient = .device, .kind = .standard, .direction = .device_to_host, }, .request_code = .get_configuration, .value = 0, .index = 0, .length = 1, }; } // 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 { return .{ .request_type = .{ .recipient = .device, .kind = .standard, .direction = .host_to_device, }, .request_code = .set_configuration, .value = @intFromEnum(configuration_value), .index = 0, .length = 0, }; } // Reads the alternate setting currently selected for the given interface: @enumFromInt the // byte the device returns into an AlternateSetting. fn getInterface(interface: InterfaceNumber) Request { return .{ .request_type = .{ .recipient = .interface, .kind = .standard, .direction = .device_to_host, }, .request_code = .get_interface, .value = 0, .index = @intFromEnum(interface), .length = 1, }; } // Selects an alternate setting (from InterfaceDescriptor.alternate_setting) for the given // interface. fn setInterface(interface: InterfaceNumber, alternate_setting: AlternateSetting) Request { return .{ .request_type = .{ .recipient = .interface, .kind = .standard, .direction = .host_to_device, }, .request_code = .set_interface, .value = @intFromEnum(alternate_setting), .index = @intFromEnum(interface), .length = 0, }; } // 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 { return .{ .request_type = .{ .recipient = .endpoint, .kind = .standard, .direction = .device_to_host, }, .request_code = .sync_frame, .value = 0, .index = @bitCast(endpoint), .length = 2, }; } const DescriptorType = enum(u8) { device = 1, configuration = 2, string = 3, interface = 4, endpoint = 5, device_qualifier = 6, other_speed_configuration = 7, interface_power = 8, _, }; const DeviceDescriptor = extern struct { // Size of this descriptor in bytes length: u8, // DEVICE Descriptor Type descriptor_type: DescriptorType, // USB Specification Release Number in Binary-Coded Decimal (i.e, 2.10 is expressed as 210h). // Identifies the release of the USB Specification with with the device and its // descriptors are compliant. bcd_usb: u16 align(1), // Class code (assigned by the USB-IF) // - This field is reset to zero if each interface within a configuration specifies its own // class information and the various interfaces operate independently. // - A value of FFh in this field indicates the device class is vendor-specific. device_class: u8, // Subclass Code (assigned by the USB-IF) // - The subclass code of a device is qualified by the class code of that device. // - If device_class is reset to zero, then this field must also be reset to zero. // - When device_class is not set to FFh, then all values for this field are reserved for // assignment by the USB-IF. device_subclass: u8, // Protocol code (assigned by the USB-IF) // - The protocol code of a device is qualified by both the class and subclass codes of // that device. // - A value of 00h in this field means that the device may specify class-specific // protocols on an interface basis, though this is not a requirement. // - If this field is set to FFh, then the device uses a vendor-specific protocol. device_protocol: u8, // Maximum packet size for endpoint zero (8, 16, 32, or 64 are the only valid options) max_packet_size_0: u8, // Vendor ID (assigned by the USB-IF) vendor_id: u16 align(1), // Product ID (assigned by the USB-IF) product_id: u16 align(1), // Device release number in binary-coded decimal bcd_device: u16 align(1), // Index of STRING descriptor describing manufacturer manufacturer_index: StringIndex, // Index of STRING descriptor describing product product_index: StringIndex, // Index of STRING descriptor describing the device's serial number serial_number_index: StringIndex, // Number of possible configurations configuration_count: u8, }; const DeviceQualifierDescriptor = extern struct { // Size of this descriptor in bytes length: u8, // DEVICE_QUALIFIER Descriptor Type descriptor_type: DescriptorType, // USB Specification Release Number in Binary-Coded Decimal (i.e, 2.00 is expressed as 200h). // Identifies the release of the USB Specification with with the device and its // descriptors are compliant. This field must be at least 0200h. bcd_usb: u16 align(1), // Class code (assigned by the USB-IF) device_class: u8, // Subclass Code (assigned by the USB-IF) device_subclass: u8, // Protocol code (assigned by the USB-IF) device_protocol: u8, // Maximum packet size for endpoint zero (8, 16, 32, or 64 are the only valid options) max_packet_size_0: u8, // Number of possible configurations configuration_count: u8, // Reserved for future uses, must be zero. reserved: u8, }; const ConfigurationDescriptor = extern struct { // Size of this descriptor in bytes length: u8, // CONFIGURATION Descriptor Type descriptor_type: DescriptorType, // The total combined length in bytes of all the descriptors returned with the request for // this CONFIGURATION descriptor (including CONFIGURATION, INTERFACE, ENDPOINT, class- and // vendor-specific descriptors). total_length: u16 align(1), // Number of interfaces supported by this configuration interface_count: u8, // Value which when used as an argument in the SET_CONFIGURATION request, causes the device // to assume the configuration described by this descriptor. configuration_value: ConfigurationValue, // Index of STRING descriptor describing this configuration. configuration_index: StringIndex, // Configuration Characteristics attributes: Attributes, // Maximum power consumption of this device from the bus when fully operational and using // this configuration. Expressed in units of 2mA (i.e., a value of 50 in this field // indicates 100mA). // - A device reports with the attributes field whether the configuration is bus- or // self-powered, but the device status (retrieved with a GET_STATUS request) reports // whether the device is currently self-powered. // - If a device is disconnected from an external power source, it may not draw more // power from the bus than specified in this field. max_power: u8, // Configuration characteristics. Fields are declared least-significant first. const Attributes = packed struct(u8) { // Reserved, reset to zero (D4...0) reserved: u5, // Whether Remote Wakeup is supported by this configuration (D5) remote_wakeup: bool, // Self-Powered (D6) // - false: Device runs on power supplied by the bus // - true: Device provides a local power source; if max_power is non-zero, the // device also may use bus power. self_powered: bool, // Reserved, must be set to one for historical reasons (D7) reserved_one: u1, }; }; // This descriptor describes the configuration of a high-speed device if it were operating at // 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; const InterfaceDescriptor = extern struct { // Size of this descriptor in bytes length: u8, // INTERFACE Descriptor Type descriptor_type: DescriptorType, // Number of this interface. Zero-based value which identifies the index of this interface // in the array of interfaces supported within a configuration. interface_number: InterfaceNumber, // Value used to select the alternate settings described by this INTERFACE descriptor for // the interface with the interface_number in the previous field. This value is zero if // this descriptor describes the default settings for a particular interface. alternate_setting: AlternateSetting, // Number of endpoints used by this interface, not including endpoint zero. endpoint_count: u8, // Class code (assigned by the USB-IF) // - A value of zero here is reserved for future standardization. // - If this value is FFh, the interface class is vendor-specific. // - All other values are reserved for assignment by the USB-IF. interface_class: u8, // Subclass code (assigned by the USB-IF) // - The subclass code in this field is qualified by the value of the interface_class // field. // - If interface_class is reset to zero, then this field must also be reset to zero. // - If interface_class is not set to the value of FFh, then all values of this field are // reserved for assignment by the USB-IF. interface_subclass: u8, // Protocol code (assigned by the USB-IF) // - The protocol code in this field is qualified by the values of the interface_class // and interface_subclass fields. // - If an interface supports class-specific requests, then this field identifies the // protocols that the device uses as defined by the specifications of the device class. // - If this field is reset to zero, then the device does not use a class-specific // protocol on this interface. // - If this field is set to FFh, then the device uses a vendor-specific protocol on // this interface. interface_protocol: u8, // Index of STRING descriptor describing this interface interface_index: StringIndex, }; const EndpointDescriptor = extern struct { // Size of this descriptor in bytes length: u8, // ENDPOINT Descriptor Type descriptor_type: DescriptorType, // The address of the endpoint on the USB device described by this descriptor endpoint_address: Address, // The endpoint's attributes attributes: Attributes, // Maximum packet size that this endpoint is capable of sending or receiving. For // isochronous endpoints, this value is used to reserve bus time; the pipe, however, may // not always use all of the reserved bus time. max_packet_size: MaxPacketSize align(1), // Interval for polling a device during a data transfer, expressed in units of microframes // for high-speed devices, and frames for low- and full-speed devices. The exact meaning of // the value in this field depends on the endpoint type and the operating speed of the // device: // - Full- and High-speed isochronous endpoints, and high-speed interrupt endpoints: // This field must be in the range from 1 to 16, and is used to calculate the period // as 2^(interval - 1). That is, a value of 4 calculates to 2^(4 - 1) = 2^3 = 8. // - Full- and Low-speed interrupt endpoints: This field must be in the range from // 1 to 255. // - High-speed bulk and control OUT endpoints: This field must be in the range from // 0 to 255, and specifies the maximum NAK rate of the endpoint. A value of zero // indicates that the endpoint never NAKs; other values indicate at most 1 NAK each // interval number of microframes. interval: u8, // The address of an endpoint. Fields are declared least-significant first. const Address = packed struct(u8) { // Endpoint Number (D3...0) number: EndpointNumber, // Reserved, reset to zero (D6...4) reserved: u3, // Direction, ignored for control endpoints (D7) direction: EndpointDirection, }; // An endpoint's attributes. Fields are declared least-significant first. const Attributes = packed struct(u8) { // Transfer Type (D1...0) transfer_type: TransferType, // Synchronization Type; isochronous endpoints only, reserved and reset to zero for // other endpoint types (D3...2) synchronization: Synchronization, // Usage Type; isochronous endpoints only, reserved and reset to zero for other // endpoints (D5...4) usage: Usage, // Reserved, reset to zero (D7...6) reserved: u2, }; const TransferType = enum(u2) { control = 0, isochronous = 1, bulk = 2, interrupt = 3, }; const Synchronization = enum(u2) { none = 0, asynchronous = 1, adaptive = 2, synchronous = 3, }; const Usage = enum(u2) { data = 0, feedback = 1, implicit_feedback_data = 2, _, }; // The maximum packet size of an endpoint. Fields are declared least-significant first. 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 // isochronous and interrupt endpoints; reserved and reset to zero for other // endpoints (bits 12...11) additional_transactions: AdditionalTransactions, // Reserved, must be reset to zero (bits 15...13) reserved: u3, }; const AdditionalTransactions = enum(u2) { // None (1 transaction per microframe) none = 0, // 1 additional (2 transactions per microframe) one = 1, // 2 additional (3 transactions per microframe) two = 2, _, }; }; // A STRING descriptor at index zero returns the list of LANGID codes supported by the // device; all other indices return a Unicode string. Both forms start with this two-byte // 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 { // Size of this descriptor in bytes length: u8, // STRING Descriptor Type descriptor_type: DescriptorType, }; const std = @import("std"); test "wire sizes and offsets match the specification" { const expectEqual = std.testing.expectEqual; try expectEqual(8, @sizeOf(Request)); try expectEqual(18, @sizeOf(DeviceDescriptor)); try expectEqual(10, @sizeOf(DeviceQualifierDescriptor)); try expectEqual(9, @sizeOf(ConfigurationDescriptor)); try expectEqual(9, @sizeOf(InterfaceDescriptor)); try expectEqual(7, @sizeOf(EndpointDescriptor)); try expectEqual(2, @sizeOf(StringDescriptor)); try expectEqual(2, @offsetOf(DeviceDescriptor, "bcd_usb")); try expectEqual(8, @offsetOf(DeviceDescriptor, "vendor_id")); try expectEqual(17, @offsetOf(DeviceDescriptor, "configuration_count")); try expectEqual(2, @offsetOf(ConfigurationDescriptor, "total_length")); try expectEqual(4, @offsetOf(EndpointDescriptor, "max_packet_size")); } test "bitmap packings match the specification" { const expectEqual = std.testing.expectEqual; const expect = std.testing.expect; // bmRequestType for GET_DESCRIPTOR: device-to-host | standard | device = 80h const request_type = RequestType{ .recipient = .device, .kind = .standard, .direction = .device_to_host, }; try expectEqual(0x80, @as(u8, @bitCast(request_type))); // wValue for GET_DESCRIPTOR(CONFIGURATION, index 0) = 0200h const descriptor_value = Request.DescriptorValue{ .kind = .configuration }; try expectEqual(0x0200, @as(u16, @bitCast(descriptor_value))); // wIndex for the IN endpoint 1 = 0081h const endpoint_index = Request.EndpointIndex{ .number = @enumFromInt(1), .direction = .in }; try expectEqual(0x0081, @as(u16, @bitCast(endpoint_index))); // Endpoint address 81h = IN endpoint 1 const address: EndpointDescriptor.Address = @bitCast(@as(u8, 0x81)); try expectEqual(1, @intFromEnum(address.number)); try expectEqual(.in, address.direction); // Endpoint attributes 03h = interrupt transfer const attributes: EndpointDescriptor.Attributes = @bitCast(@as(u8, 0x03)); try expectEqual(.interrupt, attributes.transfer_type); // wMaxPacketSize 0008h = 8 bytes, no additional transactions const max_packet_size: EndpointDescriptor.MaxPacketSize = @bitCast(@as(u16, 0x0008)); try expectEqual(8, max_packet_size.size); try expectEqual(.none, max_packet_size.additional_transactions); // Configuration attributes C0h = self-powered, with the historical D7 bit set const configuration_attributes: ConfigurationDescriptor.Attributes = @bitCast(@as(u8, 0xC0)); try expect(configuration_attributes.self_powered); try expect(!configuration_attributes.remote_wakeup); try expectEqual(1, configuration_attributes.reserved_one); // GET_STATUS words: device 0001h = self-powered; endpoint 0001h = halted const device_status: DeviceStatus = @bitCast(@as(u16, 0x0001)); try expect(device_status.self_powered and !device_status.remote_wakeup); const endpoint_status: EndpointStatus = @bitCast(@as(u16, 0x0001)); try expect(endpoint_status.halted); // DescriptorType is non-exhaustive: class-specific values (HID = 21h) pass through const hid_type: DescriptorType = @enumFromInt(0x21); try expectEqual(0x21, @intFromEnum(hid_type)); try expect(hid_type != .device); } fn expectRequestBytes(request: Request, expected: [8]u8) !void { try std.testing.expectEqualSlices(u8, &expected, std.mem.asBytes(&request)); } test "standard request constructors encode the specification's set-up packets" { try expectRequestBytes(getStatus(.device), .{ 0x80, 0, 0, 0, 0, 0, 2, 0 }); try expectRequestBytes(getStatus(.{ .interface = @enumFromInt(3) }), .{ 0x81, 0, 0, 0, 3, 0, 2, 0 }); try expectRequestBytes(getStatus(.{ .endpoint = .{ .number = @enumFromInt(2), .direction = .in } }), .{ 0x82, 0, 0, 0, 0x82, 0, 2, 0 }); try expectRequestBytes(clearFeature(.endpoint_halt, .{ .endpoint = .{ .number = @enumFromInt(1), .direction = .out } }), .{ 0x02, 1, 0, 0, 0x01, 0, 0, 0 }); try expectRequestBytes(setFeature(.device_remote_wakeup, .device), .{ 0x00, 3, 1, 0, 0, 0, 0, 0 }); try expectRequestBytes(setTestMode(.test_packet), .{ 0x00, 3, 2, 0, 0, 0x04, 0, 0 }); try expectRequestBytes(setAddress(@enumFromInt(5)), .{ 0x00, 5, 5, 0, 0, 0, 0, 0 }); try expectRequestBytes(getDescriptor(.device, 0, 0, 18), .{ 0x80, 6, 0, 1, 0, 0, 18, 0 }); try expectRequestBytes(getDescriptor(.string, 2, 0x0409, 255), .{ 0x80, 6, 2, 3, 0x09, 0x04, 255, 0 }); try expectRequestBytes(setDescriptor(.string, 2, 0x0409, 16), .{ 0x00, 7, 2, 3, 0x09, 0x04, 16, 0 }); try expectRequestBytes(getConfiguration(), .{ 0x80, 8, 0, 0, 0, 0, 1, 0 }); try expectRequestBytes(setConfiguration(@enumFromInt(1)), .{ 0x00, 9, 1, 0, 0, 0, 0, 0 }); try expectRequestBytes(getInterface(@enumFromInt(2)), .{ 0x81, 10, 0, 0, 2, 0, 1, 0 }); 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 }); }