Skip to content
jean-edouard edited this page Nov 20, 2014 · 12 revisions

The HID specs are quite long and difficult to apprehend.
The main purpose of this page is to give some HID background to understand SuperHID.

The HID descriptor

Every HID device has a HID descriptor, that is read by the driver during its initialization.
The HID descriptor defines the format of the data the device will send. It is mandatory, as there's no pre-defined data format in HID.

Here is a HID descriptor (a stripped version of the one used in SuperHID) and its equivalent structure in C format. See next section for the explanations.

/* First a few constants */

#define REPORT_ID_MOUSE         0x02
#define REPORT_ID_MULTITOUCH    0x04
#define REPORT_ID_MT_MAX_COUNT  0x10

/* The HID descriptor */

char[] hid_descriptor = {
    0x05, 0x01,                   /* USAGE_PAGE (Generic Desktop)         */
    0x09, 0x02,                   /* USAGE (Mouse)                        */ 
    0xa1, 0x01,                   /* COLLECTION (Application)             */
    0x85, REPORT_ID_MOUSE,        /*   REPORT_ID (2)                      */    /* mouse_report.report_id */
    0x09, 0x01,                   /*   USAGE (Pointer)                    */
    0xa1, 0x00,                   /*   COLLECTION (Physical)              */
    0x05, 0x09,                   /*     USAGE_PAGE (Button)              */
    0x19, 0x01,                   /*     USAGE_MINIMUM (Button 1)         */
    0x29, 0x05,                   /*     USAGE_MAXIMUM (Button 5)         */
    0x15, 0x00,                   /*     LOGICAL_MINIMUM (0)              */
    0x25, 0x01,                   /*     LOGICAL_MAXIMUM (1)              */
    0x95, 0x05,                   /*     REPORT_COUNT (5)                 */
    0x75, 0x01,                   /*     REPORT_SIZE (1)                  */
    0x81, 0x02,                   /*     INPUT (Data,Var,Abs)             */    /* mouse_report.misc & 0x1F */
    0x95, 0x01,                   /*     REPORT_COUNT (1)                 */
    0x75, 0x03,                   /*     REPORT_SIZE (3)                  */
    0x81, 0x03,                   /*     INPUT (Cnst,Var,Abs)             */    /* mouse_report.misc & 0xE0 */
    0x05, 0x01,                   /*     USAGE_PAGE (Generic Desktop)     */
    0x09, 0x30,                   /*     USAGE (X)                        */
    0x09, 0x31,                   /*     USAGE (Y)                        */
    0x09, 0x38,                   /*     USAGE (Z)                        */
    0x15, 0x81,                   /*     LOGICAL_MINIMUM (-127)           */
    0x25, 0x7f,                   /*     LOGICAL_MAXIMUM (127)            */
    0x75, 0x08,                   /*     REPORT_SIZE (8)                  */
    0x95, 0x03,                   /*     REPORT_COUNT (3)                 */
    0x81, 0x06,                   /*     INPUT (Data,Var,Rel)             */    /* mouse_report.x mouse_report.y mouse_report.z */
    0xc0,                         /*   END_COLLECTION                     */
    0xc0,                         /* END_COLLECTION                       */
    0x05, 0x0D,                   /* USAGE PAGE (Digitizer),              */
    0x09, 0x04,                   /* USAGE (Touchscreen),                 */
    0xA1, 0x01,                   /* COLLECTION (Application),            */
    0x85, REPORT_ID_MULTITOUCH,   /*      Report ID (4),                  */    /* multitouch_report.report_id */
    0x09, 0x22,                   /*      Usage (Finger),                 */
    0xA1, 0x00,                   /*      Collection (Physical),          */
    0x09, 0x42,                   /*          Usage (Tip Switch),         */
    0x15, 0x00,                   /*          Logical Minimum (0),        */
    0x25, 0x01,                   /*          Logical Maximum (1),        */
    0x75, 0x01,                   /*          Report Size (1),            */
    0x95, 0x01,                   /*          Report Count (1),           */
    0x81, 0x02,                   /*          Input (Variable),           */    /* multitouch_report.misc & 0x01 */
    0x09, 0x32,                   /*          Usage (In Range),           */
    0x81, 0x02,                   /*          Input (Variable),           */    /* multitouch_report.misc & 0x02 */
    0x09, 0x37,                   /*          Usage (Data Valid),         */
    0x81, 0x02,                   /*          Input (Variable),           */    /* multitouch_report.misc & 0x04 */
    0x25, 0x1F,                   /*          Logical Maximum (31),       */
    0x75, 0x05,                   /*          Report Size (5),            */
    0x09, 0x51,                   /*          Usage (51h),                */
    0x81, 0x02,                   /*          Input (Variable),           */ 
    0x05, 0x01,                   /*

    0x65, 0x11,         

    0x75, 0x10,          
    0x46, 0x56, 0x0A,             /*          Physical Maximum (2646),    */
    0x26, 0xFF, 0x0F,             /*          Logical Maximum (4095),     */

    0x81, 0x02,                   /*          Input (Variable),           */
    0x46, 0xB2, 0x05,             /*          Physical Maximum (1458),    */
    0x26, 0xFF, 0x0F,          
    0x09, 0x31,                   /*          Usage (Y),                  */
    0x81, 0x02,                   /*          Input (Variable),           */
    0x05, 0x0D,                   /*          Usage Page (Digitizer),     */
    0x75, 0x08,                   /*          Report Size (8),            */
    0x85, REPORT_ID_MT_MAX_COUNT, /*          Report ID (10),             */
    0x09, 0x55,                   /*          Usage (55h),                */
    0x25, 0x10,                   /*          Logical Maximum (16),       */
    0xB1, 0x02,                   /*          Feature (Variable),         */
    0xC0,                         /*      End Collection,                 */
    0xC0                          /*  End Collection                      */
}

/* And the corresponding C str

struct mouse_report
{
    uint8_t  report_id;    /* Will always be REPORT_I
    uint8_t  misc;
    uint8_t  x;
    uint8_t  y;
    uint8_t  z;
} __attri

struct multitouch_report
{
    uint8_t  report_id;    /* Will always be REPORT_ID_MULTITOUCH */
    uint8_t  
    uint16_t x;
    uint16_t y;
} __attribute__ ((__packed__));

Explanations

The single HID descriptor above defines two applications (or sub-devices): a mouse (USAGE PAGE 1, USAGE 2) (explained in the following) and a touchscreen (USAGE PAGE D, USAGE 4). There's a lot of possible applications (potentially 255 * 255), they're all listed in the HID specs.

The device represented by this descriptor has to be considered as literally a single device that has 2 integrated sub-devices, a mouse and a touchscreen, which in real life probably doesn't exist.

A more common multi-sub-devices device would be a touchscreen + stylus, as you can find such a thing in most tablets.

But that's the beauty of HID, the device doesn't have to make sense. One HID device can potentially implement all the possible applications.

About the HID descriptor itself:

  • It describes the format of the data the device will send.
  • HID descriptors are very compressed. The amount of information per byte is quite impressive!
  • At first, it is easier to read the comments rather than the hex values in the the descriptors. But for more information about the values, read HID Generic Item Format
  • The parsing of HID descriptors has similarities the parsing of assembly code, in the sense that we define "functions" (opcodes in asm), which take a pre-defined number of "arguments" (operands in asm).

Here's the my meaning of a few "functions":

  • COLLECTION(Application): Starts the definition of a "sub-device".
  • USAGE PAGE: A "global variable" telling about which "set" of USAGEs we're in. In a bible, that would be the chapter, and the USAGE would be the verse.
  • USAGE: This "global variable" defines the current "object" we're describing, relative to the current USAGE PAGE. As for USAGE PAGE, it takes one 1-byte-long argument.
  • REPORT ID: Sets the "sub-device" ID for the current "COLLECTION(Application)" (or FEATURE). This field is not needed if the device implements only one "COLLECTION(Application)" (and no FEATURE).
  • REPORT SIZE: Sets the size in bits to be expected for the next INPUT (or FEATURE). As everything else, this will be the size for every INPUT (and FEATURE) until we change it.
  • REPORT COUNT: Sets how many values of "REPORT SIZE" bits are expected in the next INPUT (or FEATURE).
  • INPUT: Defines a value that will actually be sent by the device. The data will be in the format defined by REPORT SIZE, REPORT COUNT, LOGICAL MINIMUM/MAXIMUM,... The usual INPUT is "INPUT (Variable)" or "INPUT (Data,Var,Rel)". "Input (Constant)" or "INPUT (Cnst,Var,Abs)" just represents random bits that have no meaning, usually used for padding.
  • FEATURE: Has a meaning close to INPUT, except that a FEATURE has its own REPORT ID, and will be requested by the driver instead of 'randomly' sent by the device.

Note: Under the "touchscreen" USAGE PAGE (4), there's a few USAGEs (0x51 and 0x55 here) that are part of a HID extension.

0x51 is the finger ID, and 0x55 is the maximum number of fingers supported by the touchscreen (mandatory for the Windows driver...)

The C structures are just a representation of all the INPUT defined in the descriptor.

As they're defined here, the device packets can be directly casted to one or the other struct, depending on the REPORT ID.

The field "misc" in the multitouch report struct is just lazyness, it should be a bit-field, as it represents three 1-bit values, and one 5 bits value.

Example of packets

Here are a couple of (valid) example of raw HID data (in hexadecimal) that the device described in the HID descriptor could send.

02 05 12 00 00

This packet has a REPORT ID of 0x02, so it is a mouse move.

The next byte, 0x05, corresponds to the "misc" field. The 5 lower bits each represent one button, 1 for pressed, 0 for not. 0x05 = 000 0 0 1 0 1, so the user is currently clicking on both the left click and the wheel click.

Then it has an X value of 0x12 and Y and Z are 0x00.

So this packet is a 10 pixels mouse move on the X axis, with left click and wheel click pressed.

04 0F 12 34 56 78

This packet has a REPORT ID of 0x04, so it is a touchscreen event.

The next byte, 0x0F, corresponds to the "misc" field. So it needs to be cut in the 4 values it represents:

0x0F = 00001 1 1 1

From low bit to high:

1 is the "TIP SWITCH", meaning that the finger represented here is touching the screen 1 is "DATA VALID", meaning that the device determined that it was a "proper" event, not a user "accident". 1 is "IN RANGE", meaning that the coordinates are in-range... 00001 is the finger ID: 1 The next two bytes are the X coordinate of the finger: 0x1234. Now sometimes there's some endian crap to consider here, the value could actually be 0x3412.

The last two are the Y coordinate.

References

The HID Specs

The Usage Guide

The multitouch specs addition

Microsoft's rules about multitouch