rmed

blog

Using RPi Zero as a Keyboard Part 2: Report descriptor

2017-07-05 12:40

In this second part of the series we'll see the structure of HID report descriptors and how to apply them to the virtual keyboard. When done, the gadget will be completely configured and ready to use.

Edited August 19, 2018: Added binary report descriptor for those that do not have the hidrd tool.

Edited July 8, 2018: Tested on Raspbian Stretch (lite) 2018-06-27. Copy report descriptor from existing keyboard.

Edited August 13, 2019: Updated link to USB HID document.

Edited August 19, 2020: Fixed incorrect write destination for the report length and added not regarding xxd (thanks Bob)


Index

  1. Setup and device definition
  2. Report descriptor (this post)
  3. Sending and receiving reports

What is it?

As the name suggests, a report descriptor specifies the structure of the reports sent from/to a USB device. In practice, any generic descriptor would be enough for our gadget considering that input reports are always going to be 8 bytes long and output reports are going to be 1 byte long.

Input reports (sent from keyboard to computer) have the following structure for a total of 8 bytes:

  • 1 byte: modifier keys (Control, Shift, Alt, etc.), where each bit corresponds to a key
  • 1 byte: unused/reserved for OEM
  • 6 bytes: pressed key codes

While output reports (sent from computer to keyboard) have the following structure:

  • 1 byte: LED states (Num lock, Caps lock, etc.), where each bit corresponds to a LED

However, this may not be the case for other human interface devices. For instance, a mouse may have more than three buttons and treat the (X, Y) coordinates very differently from another one.

Understanding the descriptor

The following is a descriptor given as an example in the HID specification, [1] page 59:

Usage Page (Generic Desktop),
Usage (Keyboard),
Collection (Application),
   Report Size (1),
   Report Count (8),
   Usage Page (Key Codes),
   Usage Minimum (224), 
   Usage Maximum (231),
   Logical Minimum (0),
   Logical Maximum (1),
   Input (Data, Variable, Absolute),            ;Modifier byte
   Report Count (1),
   Report Size (8),
   Input (Constant),                            ;Reserved byte
   Report Count (5),
   Report Size (1),
   Usage Page (LEDs),
   Usage Minimum (1),
   Usage Maximum (5),
   Output (Data, Variable, Absolute),           ;LED report
   Report Count (1),
   Report Size (3),
   Output (Constant),                           ;LED report padding
   Report Count (6),
   Report Size (8),
   Logical Minimum (0),
   Logical Maximum(255),
   Usage Page (Key Codes),
   Usage Minimum (0),
   Usage Maximum (255),
   Input (Data, Array),
End Collection

Let's go over it bit by bit.

Usage Page (Generic Desktop),
Usage (Keyboard),

Here we specify that the report is intended for a generic USB device, in this case a keyboard.


Collection (Application),

From the specification [1] : "A group of Main items that might be familiar to applications. It could also be used to identify item groups serving different purposes in a single device". Generally speaking, this is the type of collection to use for keyboards or mice.


   Report Size (1),
   Report Count (8),
   Usage Page (Key Codes),
   Usage Minimum (224), 
   Usage Maximum (231),
   Logical Minimum (0),
   Logical Maximum (1),
   Input (Data, Variable, Absolute),            ;Modifier byte

The previous is used to specify the modifier keys in the input report. This defines 8 fields which are 1 bit long each, resulting in the modifier byte mentioned previously. Also note that the values stored in these fields must be in the range [224, 231], as these are the key codes defined for the modifier keys:

  • Left Control: 224 (0x00e0)
  • Left Shift: 225 (0x00e1)
  • Left Alt: 226 (0x00e2)
  • Left Meta (Windows key): 227 (0x00e3)
  • Right Control: 228 (0x00e4)
  • Right Shift: 229 (0x00e5)
  • Right Alt: 230 (0x00e6)
  • Right Meta (Windows key): 231 (0x00e7)

Finally, as these are represented as bits, the final values in the fields will be either 1 or 0 (key is pressed or not).


   Report Count (1),
   Report Size (8),
   Input (Constant),                            ;Reserved byte

The previous defines the constant/reserved byte in the input report, which is again formed by 8 fields which are 1 bit long each.


   Report Count (5),
   Report Size (1),
   Usage Page (LEDs),
   Usage Minimum (1),
   Usage Maximum (5),
   Output (Data, Variable, Absolute),           ;LED report

This defines LEDs that can be set in the output report, formed by 5 fields which are 1 bit long each. LEDs are specified in the following order:

  • Num Lock
  • Caps Lock
  • Scroll Lock
  • Compose
  • Kana

In this case, the allowed values are in the range [1, 5] given that the value specified is converted to binary to set each specific bit. For example, a value of 3 (011) will set LEDs Num Lock and Caps Lock.


   Report Count (1),
   Report Size (3),
   Output (Constant),                           ;LED report padding

As I mentioned previously, output reports are 1 byte long. The block above set 5 bits, so it is necessary to add 3 constant fields which are 1 bit long each in order to complete the byte.


   Report Count (6),
   Report Size (8),
   Logical Minimum (0),
   Logical Maximum(255),
   Usage Page (Key Codes),
   Usage Minimum (0),
   Usage Maximum (255),
   Input (Data, Array),
End Collection

Finally, this specifies the key codes pressed in the keyboard in the input report. As shown, the block specifies 6 fields which are 8 bits (1 byte) long each. The value of each field corresponds to the HID code of the key pressed, in the range [0, 255].

Given that it is the last part of the definition of the report, the collection is closed.

Setting the descriptor

Once the report descriptor has been defined, we need to give it to the gadget. This is done through the /sys/kernel/config/usb_gadget/mykeyboard/functions/hid.usb0/report_desc file we skipped in the last post. However, we cannot simply write the previous text into the file. It is necessary to convert it into a binary format.

While hidrd is a great tool for reading and writing such reports, it might be easier to simply copy a report from an already existing keyboard. Such reports can be found in the /sys/class/input/eventXX/device/device/report_descriptor file, where XX is the corresponding event number. Keyboards are usually assigned event0.

If you cannot simply copy the file, especially when using a startup script to set up the keyboard, you can copy its hexadecimal representation to your script and then write the report descriptor manually.

For the following commands you will need the xxd program. In Raspbian systems it is available as a standalone package and can be installed with the following:

apt install xxd

Other systems may include the tool as part of the vim package, so you may need to install vim:

apt install vim

Now, on to the report. First, get a one-liner hex representation of the report bytes:

xxd -p /sys/class/input/event0/device/device/report_descriptor | tr -d '\n' > /tmp/mydesc

Then you can copy this file to the Raspberry Pi and use it to set the report descriptor. The function section of startup script shown in the previous post would look like this:

echo 1 > functions/hid.usb0/protocol
echo 8 > functions/hid.usb0/report_length
echo 1 > functions/hid.usb0/subclass
xxd -r -ps /tmp/mydesc functions/hid.usb0/report_desc

If you don't want to use the additional file, you may simply include the contents in the script directly. For this example, I'm writing the report descriptor used to test this:

echo 1 > functions/hid.usb0/protocol
echo 8 > functions/hid.usb0/report_length
echo 1 > functions/hid.usb0/subclass
echo "05010906a101050719e029e71500250175019508810275089501810175019503050819012903910275019505910175089506150026ff00050719002aff008100c0" | xxd -r -ps > functions/hid.usb0/report_desc

With this, the configuration of the gadget should be complete.

References

Post series index

  1. Setup and device definition
  2. Report descriptor (this post)
  3. Sending and receiving reports