Skip to main content

HID Device Stack

This section describes XRUSB’s USB HID (Human Interface Device) device-class implementation and how to extend it. It covers:

  • The template HID base class LibXR::USB::HID<REPORT_DESC_LEN, TX_REPORT_LEN, RX_REPORT_LEN>
  • Automatic generation of the configuration-descriptor block (Interface + HID Descriptor + Endpoint Descriptors)
  • Optional Interrupt OUT (Output Report over Interrupt OUT)
  • Standard request GET_DESCRIPTOR (HID / Report Descriptor)
  • A handling framework for HID class requests (GET_REPORT/SET_REPORT/GET_IDLE/SET_IDLE/GET_PROTOCOL/SET_PROTOCOL)
  • Input Report transmission and IN/OUT completion callbacks
  • Typical derived classes: mouse, keyboard, gamepad

1. Base Class Overview

1.1 LibXR::USB::HID<REPORT_DESC_LEN, TX_REPORT_LEN, RX_REPORT_LEN>

HID is a template HID base class (derived from DeviceClass). Template parameters fix report and descriptor sizes at compile time, making it convenient to implement devices such as keyboards, mice, and gamepads.

Template parameters:

  • REPORT_DESC_LEN: Report Descriptor length (bytes)
  • TX_REPORT_LEN: Maximum Input Report length (Interrupt IN endpoint max packet size)
  • RX_REPORT_LEN: Maximum Output Report length (Interrupt OUT endpoint max packet size)
    • Set to 0 to disable the Interrupt OUT endpoint

The base class provides:

  • Endpoint allocation and configuration: Interrupt IN (required) + Interrupt OUT (optional)
  • Automatic configuration-descriptor block generation
  • GET_DESCRIPTOR (HID / Report) responses
  • A HID class-request handling framework (override/extend in derived classes)
  • Input Report send helper: SendInputReport(...)
  • IN/OUT transfer-completion hooks: OnDataInComplete(...) / OnDataOutComplete(...)

2. Descriptor and Endpoint Layout

2.1 Interface

The HID base class contributes one HID interface and does not use an IAD:

  • bInterfaceClass = 0x03 (HID)
  • bNumEndpoints = 1 (IN only) or 2 (IN + OUT)

2.2 HID Descriptor (0x21)

The configuration descriptor includes a HID class descriptor (9 bytes). Key fields:

  • bcdHID = 0x0111 (HID v1.11)
  • bNumDescriptors = 1
  • bReportDescriptorType = 0x22
  • wReportDescriptorLength = REPORT_DESC_LEN

2.3 Endpoints

EndpointDirTypeMax Packet SizePurpose
IN endpointININTERRUPTTX_REPORT_LENDevice → Host Input Report
OUT endpoint (opt.)OUTINTERRUPTRX_REPORT_LENHost → Device Output Report

Polling interval:

  • in_ep_interval_ / out_ep_interval_ are written into endpoint descriptors as bInterval

Note: bInterval semantics differ by speed (FS is typically 1 ms frames; HS uses microframe encoding). This implementation organizes parameters in “millisecond” terms; the final interpretation depends on the underlying USB controller/stack.


3. Lifecycle: Bind / Unbind

3.1 Bind (Initialization)

During initialization, the class typically:

  1. Records the interface number and clears runtime flags
  2. Allocates Interrupt IN from EndpointPool and configures it with TX_REPORT_LEN
  3. If RX_REPORT_LEN > 0: allocates Interrupt OUT and configures it with RX_REPORT_LEN
  4. Generates and submits the configuration-descriptor block (Interface + HID Descriptor + Endpoint Descriptors)
  5. Registers endpoint completion callbacks (IN required; OUT optional)
  6. If OUT is enabled: starts the first OUT receive (subsequent receives are re-armed automatically)
  7. Sets inited_ = true

3.2 Unbind (Release)

During unbind, the class typically:

  • Clears inited_, closes and returns IN/OUT endpoints to EndpointPool
  • Sets endpoint pointers to null

4. Standard Request: GET_DESCRIPTOR (HID / Report)

The base class handles the standard request GET_DESCRIPTOR:

  • DescriptorType = 0x21: returns the HID Descriptor
  • DescriptorType = 0x22: returns the Report Descriptor
  • Other types (e.g., Physical 0x23): not supported

Returned data is truncated to wLength to avoid exceeding the host-requested length.

Derived classes must provide the Report Descriptor (called by the base class):

virtual ConstRawData GetReportDesc() = 0;

5. HID Class Requests and Data Stage

The base class supports common HID Class-Specific Requests:

  • GET_REPORT
    • Uses the high byte of wValue to distinguish INPUT/OUTPUT/FEATURE, then calls derived hooks to produce response data
  • SET_REPORT
    • Performs basic validation in the Setup stage and prepares to receive data
    • When the Data stage arrives, invokes a derived hook to process the payload
  • GET_IDLE / SET_IDLE
    • Maintains idle_rate_ (units of 4 ms; many implementations only support report_id = 0)
  • GET_PROTOCOL / SET_PROTOCOL
    • Maintains protocol_ (BOOT / REPORT)

Recommended hooks to override as needed:

  • Report retrieval: OnGetInputReport(...) / OnGetLastOutputReport(...) / OnGetFeatureReport(...)
  • Report setting: OnSetReport(...) (Setup stage) and OnSetReportData(...) (Data stage)
  • Custom extensions: OnCustomClassRequest(...) / OnCustomClassData(...)

6. Data Path: Interrupt IN/OUT

6.1 Sending an Input Report: SendInputReport(...)

Typical behavior:

  1. Validates initialization and endpoint availability
  2. Validates 0 < report_len <= TX_REPORT_LEN
  3. Validates the IN endpoint is idle; otherwise returns BUSY
  4. Copies the report into the IN endpoint buffer and starts the transfer

Common return-code semantics (subject to stack definitions):

  • OK: transfer started successfully
  • BUSY: endpoint is still transferring
  • ARG_ERR: invalid report length
  • FAILED/NO_BUFF: endpoint/buffer unavailable

6.2 Receiving an Output Report (Interrupt OUT, optional)

If the OUT endpoint is enabled, the base class continuously receives:

  • Starts one OUT receive after Bind
  • After each OUT completion:
    • Calls OnDataOutComplete(in_isr, data) so the derived class can consume the data
    • Automatically re-arms to receive the next packet

This path is suitable for “asynchronous Output Reports” such as keyboard LED states or gamepad vibration commands.

6.3 IN Completion Callback

After an IN transfer completes, OnDataInComplete(in_isr, data) is called. Typical uses:

  • Advancing a send queue (send the next report)
  • Statistics/monitoring (throughput, link state, etc.)

7. Typical Derived Classes (Overview)

7.1 HIDMouse

  • Standard Boot mouse
  • Input Report: commonly 4 bytes (Buttons + X + Y + Wheel)
  • IN endpoint only

7.2 HIDKeyboard

  • Standard Boot keyboard
  • Input Report: commonly 8 bytes (Modifier + Reserved + 6 KeyCodes)
  • Optional OUT endpoint (e.g., 1-byte LED)
  • Can also support host LED updates via control endpoint (SET_REPORT)

7.3 HIDGamepadT

  • Template gamepad (e.g., 4 axes + 8 buttons)
  • Input Report and Report Descriptor are fixed at compile time
  • Typically provides convenience send APIs to update axes and button bitmap

8. Usage Examples (Conceptual)

Note: How the USB device framework registers the class list depends on the upper-layer usb_dev implementation. The examples below illustrate typical usage only.

8.1 Mouse

#include "hid_mouse.hpp"

LibXR::USB::HIDMouse hid_mouse;

// usb_dev class list: {{&hid_mouse}}
// usb_dev.Init();
// usb_dev.Start();

hid_mouse.Move(LibXR::USB::HIDMouse::LEFT, 10, 0);
hid_mouse.Release();

8.2 Keyboard (with LED callback)

#include "hid_keyboard.hpp"

// enable_out_endpoint=true enables the optional Interrupt OUT endpoint for LED reports
LibXR::USB::HIDKeyboard hid_kbd(true);

// Send: Shift + A
hid_kbd.PressKey({LibXR::USB::HIDKeyboard::KeyCode::A},
LibXR::USB::HIDKeyboard::Modifier::LEFT_SHIFT);
hid_kbd.ReleaseAll();

8.3 Gamepad

#include "hid_gamepad.hpp"

LibXR::USB::HIDGamepad gamepad;
gamepad.Send(1024, 1024, 1024, 1024, LibXR::USB::HIDGamepad::BTN1);

9. Extension Guidance (Derived-Class Implementation)

Implementing a new HID derived class typically requires:

  1. Provide the Report Descriptor (implement GetReportDesc())
  2. Define and manage the Input Report data structure (length must not exceed TX_REPORT_LEN)
  3. If Output/Feature is needed:
    • Control transfer: override OnSetReport(...) / OnSetReportData(...)
    • Interrupt OUT endpoint: enable RX_REPORT_LEN > 0 and override OnDataOutComplete(...)

If continuous sending or queueing is needed, implement “send next report” scheduling in OnDataInComplete(...).