Authored by Arvind Aiyar [MSFT]

One of the descriptors that a USB HID device needs to support is the HID Report Descriptor. HID devices transmit data using HID reports, and the Report Descriptor is the blueprint for interpreting the data that is being sent across the wire. Typically, a USB Host will request the HID Report Descriptor when enumerating the device.

In the Windows USB HID stack, there is an upper limit of 4K on the size of the report descriptor - this is related to the maximum transfer size limits for Control transfers on USB low speed. For most HID devices, this size limit is never attained. However, with newer HID devices, especially sensors, there is a possibility that the descriptor may be larger in size than 4k.

In this blog post, we will go over some common methods to reduce the size of your HID report descriptor. It might be useful to have a working knowledge of the HID Protocol Specification to better follow through the examples.

Avoiding Re-definition of Global Items

Per the HID spec, there are certain items that are considered Global. In other words, their value once declared, is carried over to the lines following it until it needs to be changed, at which point it can be declared again. Therefore, you don't need to repeatedly declare same value for global items. Here is an example:

0x05, 0x20, // USAGE_PAGE (Sensors)

0x09, 0x01, // USAGE (Sensor: All)

0xa1, 0x01, // COLLECTION (Application)

0x85, 0x01, // REPORT_ID (1)

0x05, 0x20, // USAGE_PAGE (Sensors) <-- Re-defined Global

0x09, 0x73, // USAGE (Motion: Accelerometer 3D)

0xa1, 0x00, // COLLECTION (Physical)

0x0a, 0x09, 0x03, // USAGE (Property: Sensor Connection Type)

0x15, 0x00, // LOGICAL_MINIMUM (0)

0x25, 0x02, // LOGICAL_MAXIMUM (2)

0x75, 0x08, // REPORT_SIZE (8)

0x95, 0x01, // REPORT_COUNT (1)

0x0a, 0x30, 0x08, // USAGE (SEL: PC_INTEGRATED)

0x0a, 0x31, 0x08, // USAGE (SEL: PC_ATTACHED)

0x0a, 0x32, 0x08, // USAGE (SEL: PC_EXTERNAL)

0xb1, 0x00, // FEATURE (Data,Ary,Abs)

0x0a, 0x16, 0x03, // USAGE (Property: Reporting State)

0x15, 0x00, // LOGICAL_MINIMUM (0) <-- Re-defined Global

0x25, 0x02, // LOGICAL_MAXIMUM (5)

0x75, 0x08, // REPORT_SIZE (8) <-- Re-defined Global

0x95, 0x01, // REPORT_COUNT (1) <-- Re-defined Global

0x0a, 0x40, 0x08, // USAGE (SEL: NO EVENTS)

0x0a, 0x41, 0x08, // USAGE (SEL: ALL EVENTS)

0x0a, 0x42, 0x08, // USAGE (SEL: THRESHOLD EVENTS)

0x0a, 0x43, 0x08, // USAGE (SEL: NO EVENTS WAKE)

0x0a, 0x44, 0x08, // USAGE (SEL: ALL EVENTS WAKE)

0x0a, 0x45, 0x08, // USAGE (SEL: THRESHOLD EVENTS WAKE)

0xb1, 0x00, // FEATURE (Data,Ary,Abs)

0xc0, // END_COLLECTION

0xc0, // END_COLLECTION

In the above example, there are several items which are Global and thus they need not be re-defined repeatedly. Also, the two Feature items of the same format can be gathered into a single Feature item.

0x05, 0x20, // USAGE_PAGE (Sensors) <-- Definition carries forward

0x09, 0x01, // USAGE (Sensor: All)

0xa1, 0x01, // COLLECTION (Application)

0x85, 0x01, // REPORT_ID (1)

0x09, 0x73, // USAGE (Motion: Accelerometer 3D)

0xa1, 0x00, // COLLECTION (Physical)

0x0a, 0x09, 0x03, // USAGE (Property: Sensor Connection Type)

0x15, 0x00, // LOGICAL_MINIMUM (0) <-- Definition carries forward

0x25, 0x02, // LOGICAL_MAXIMUM (2)

0x0a, 0x30, 0x08, // USAGE (SEL: PC_INTEGRATED)

0x0a, 0x31, 0x08, // USAGE (SEL: PC_ATTACHED)

0x0a, 0x32, 0x08, // USAGE (SEL: PC_EXTERNAL)

0x0a, 0x16, 0x03, // USAGE (Property: Reporting State)

0x25, 0x02, // LOGICAL_MAXIMUM (5)

0x0a, 0x40, 0x08, // USAGE (SEL: NO EVENTS)

0x0a, 0x41, 0x08, // USAGE (SEL: ALL EVENTS)

0x0a, 0x42, 0x08, // USAGE (SEL: THRESHOLD EVENTS)

0x0a, 0x43, 0x08, // USAGE (SEL: NO EVENTS WAKE)

0x0a, 0x44, 0x08, // USAGE (SEL: ALL EVENTS WAKE)

0x0a, 0x45, 0x08, // USAGE (SEL: THRESHOLD EVENTS WAKE)

0x75, 0x08, // REPORT_SIZE (8)

0x95, 0x02, // REPORT_COUNT (2) <-- Increased to 2

0xb1, 0x00, // FEATURE (Data,Ary,Abs)

0xc0, // END_COLLECTION

0xc0, // END_COLLECTION

Using Usage Ranges

Another technique that can reduce descriptor size is using usage ranges. Whenever there are contiguous usage values, they can all be combined into a usage range. Using a snippet from the report descriptor above:

0x0a, 0x30, 0x08, // USAGE (SEL: PC_INTEGRATED)

0x0a, 0x31, 0x08, // USAGE (SEL: PC_ATTACHED)

0x0a, 0x32, 0x08, // USAGE (SEL: PC_EXTERNAL)

Can be transformed into:

0x19, 0x30, 0x08, // USAGE MIN (SEL: PC_INTEGRATED)

0x29, 0x32, 0x08, // USAGE MAX (SEL: PC_EXTERNAL)

And …

0x0a, 0x40, 0x08, // USAGE (SEL: NO EVENTS)

0x0a, 0x41, 0x08, // USAGE (SEL: ALL EVENTS)

0x0a, 0x42, 0x08, // USAGE (SEL: THRESHOLD EVENTS)

0x0a, 0x43, 0x08, // USAGE (SEL: NO EVENTS WAKE)

0x0a, 0x44, 0x08, // USAGE (SEL: ALL EVENTS WAKE)

Into:

0x19, 0x40, 0x08, // USAGE MIN (SEL: NO EVENTS)

0x29, 0x44, 0x08, // USAGE (SEL: ALL EVENTS WAKE)

Report IDs and Default Values

When there is only one report of a given type – Input, Output or Feature – it is not necessary to specify a Report ID. A Report ID is required only when there are multiple Input reports for example (same for Feature and Output).

Also, certain items have inbuilt default values and do not need to be defined explicitly. For example a definition like:

0x55, 0x00, // UNIT_EXPONENT (0)

0x65, 0x00, // UNIT (None)

0x35, 0x00, // PHYSICAL_MINIMUM (0)

0x46, 0x00, 0x00, // PHYSICAL_MAXIMUM (0)

is not necessary as these are already the default values.

Feedback

These are just some techniques that can be employed to get your HID report descriptor size reduced. If you have more tips/tricks to share, please post it here as comments.