Using RPi Zero as a Keyboard Part 2: Report descriptor
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
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
- [1] https://www.usb.org/sites/default/files/documents/hid1_11.pdf
- [2] https://github.com/DIGImend/hidrd