Using RPi Zero as a Keyboard Part 1: Setup and device definition
The Raspberry Pi Zero is a cool little piece of hardware with many possibilities. One of them is that it can work as a USB host OR as a USB gadget, meaning that it is possible to implement different types of devices such as ethernet, HID (keyboard, mouse, gamepad, etc.), audio, mass storage, etc. In this 3-part series of post we'll see how to configure and use a simple and generic keyboard gadget to send keys to the connected host.
In this part I'll go over the process of defining the gadget, breaking down what each different configuration files is used for and giving example values.
Edited October 12, 2017: Corrected the bmAttributes
value used and added explanation for the values.
Edited July 8, 2018: Tested on Raspbian Stretch (lite) 2018-06-27.
Edited August 1, 2018: Added link to gist with gadget creation and tester scripts at the end of the post.
Edited August 13, 2019: Updated link to USB HID document.
Post series index
ConfigFS
First, let's talk about ConfigFS. For this part, I'm going to reference a brilliant presentation by Matt Porter [1], which greatly summarizes the key points of ConfigFS.
In essence, ConfigFS is a virtual filesystem which exposes a userspace API for the creation of USB devices. This was introduced in Linux 3.11, and recent versions of the Raspbian operating system already include the module.
Furthermore, this allows creating several devices at the same time, meaning that the Pi can act as a composite USB device with different functionalities.
Usage in Raspberry Pi Zero
First, let's assume that we have a microSD card with the following two partitions created when writing the official Raspbian image to the card:
BOOT
: contains the configuration applied during boot timeDATA
: contains the filesystem of the OS
Before using ConfigFS, it is necessary to load the module. Although it should be possible to use modprobe
for this, it's just easier to add the following line at the end of BOOT/config.txt
:
dtoverlay=dwc2
And the following two lines at the end of DATA/etc/modules
:
dwc2
libcomposite
Once done, ConfigFS will be loaded in /sys/kernel/config/usb_gadget
when the Pi is started.
Defining a USB device
The way to define a new device in ConfigFS is to create a directory inside the virtual filesystem. Note that these commands are only for illustration purposes, as they are expected to be executed during runtime. For this example, the device will be called mykeyboard
:
mkdir /sys/kernel/config/usb_gadget/mykeyboard
cd /sys/kernel/config/usb_gadget/mykeyboard
Listing the contents of this new directory shows that several files and directories have been created automatically. The meaning of each individual file can be obtained from the official USB specification [2]. Regarding the files:
bcdDevice
: device release number, assigned by manufacturer (format:0x0000
)bcdUSB
: USB specification number that the device implements (format:0x0000
)bDeviceClass
: USB class code, assigned by USB organization (format:0x00
)bDeviceProtocol
: USB protocol code, assigned by USB organization (format:0x00
)bDeviceSubClass
: USB subclass code, assigned by USB organization (format:0x00
)bMaxPacketSize0
: maximum packet size for the device, only possible values are 8, 16, 32, and 64 (format:0x00
)idProduct
: product ID, assigned by manufacturer (format:0x0000
)idVendor
: vendor ID, assigned by USB organization (format:0x0000
)UDC
: USB Device Controller, used to attach the gadget to the UDC driver in the machine
Note the format
comment at the end of each file. This means that the file should contain the given number of hex characters. For example, if 0x0000
is shown, then the file must contain a 4-digit hexadecimal number, such as 0x12AB
.
Now, let's start writing content to these files. Vendor and product IDs can be obtained from the Linux USB project [3]:
echo 0x0100 > bcdDevice # Version 1.0.0
echo 0x0200 > bcdUSB # USB 2.0
echo 0x00 > bDeviceClass
echo 0x00 > bDeviceProtocol
echo 0x00 > bDeviceSubClass
echo 0x08 > bMaxPacketSize0
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x1d6b > idVendor # Linux Foundation
In addition, note that the following directories are also present:
configs/
: contains specific configurations for the devicefunctions/
: defines the capabilities of the virtual deviceos_desc/
: OS Descriptors (ignore it for now)strings/
: localized description of the device (e.g. in English)
Let's begin with the localization. For English, the hexadecimal code would be 0x409
, which must be created as a directory inside strings/
. Once present, it is populated with three files:
manufacturer
: name of the manufacturer of the deviceproduct
: product name/descriptionserialnumber
: complete serial number of the product
Therefore:
mkdir strings/0x409
echo "My manufacturer" > strings/0x409/manufacturer
echo "My virtual keyboard" > strings/0x409/product
echo "0123456789" > strings/0x409/serialnumber
Now let's define the functions of the device. ConfigFS supports many different types of devices, but in this case we are interested in a USB keyboard. In order to have the device behave like a HID, a directory named hid.usb0
has to be created inside functions/
. It contains the following files:
protocol
: the protocol for the devicereport_desc
: binary descriptor for the reports sent by the keyboardreport_length
: length of the reports sent by the keyboardsubclass
: type of HID
For now we are going to ignore the report_desc
, as I will cover that in the next post, but we can write the values for the other files, as these are common for all keyboards:
mkdir functions/hid.usb0
echo 1 > functions/hid.usb0/protocol
echo 8 > functions/hid.usb0/report_length # 8-byte reports
echo 1 > functions/hid.usb0/subclass
# echo "..." > functions/hid.usb0/report_desc # Check second post
Lastly, for the configuration, creating a new directory inside configs/
will include the following:
MaxPower
: maximum power for the device (in mA)bmAttributes
: configuration characteristics bitmask (in hex format:0x00
), the following bits can be set depending on the power characteristics:- bit 7: bus powered (e.g.
10000000
) - bit 6: self powered (e.g.
01000000
) - bit 5: remote wakeup (e.g.
00100000
) - bit 4 to bit 0: reserved
- bit 7: bus powered (e.g.
strings/
: directory for localization of the configuration description (we will use0x409
for English, as before)
Note that it is possible to set several bits in the bmAttributes
bitmask at the same time. For instance, to indicate that the device is bus powered and has remote wakeup capabilities, the bitmask should be 10100000
, which in hex translates to 0xa0
.
Therefore:
mkdir configs/c.1
mkdir configs/c.1/strings/0x409
echo 0x80 > configs/c.1/bmAttributes # Only bus powered
echo 100 > configs/c.1/MaxPower # 100 mA
echo "Example configuration" > configs/c.1/strings/0x409/configuration
Once that is set up, the HID function defined previously must be linked to the configuration like so:
ln -s functions/hid.usb0 configs/c.1
Activating the device
Even though we are still missing the report descriptor, the device that was just configured can be activated by attaching the gadget to a UDC driver:
ls /sys/class/udc > UDC
After this (and after adding the report descriptor), the host to which the Pi is connected should recognize it as a USB keyboard.
TL;DR
The following script defines a USB keyboard as a gadget in ConfigFS. It should be executed by the Pi during runtime (e.g. on startup):
#!/bin/bash
# Create gadget
mkdir /sys/kernel/config/usb_gadget/mykeyboard
cd /sys/kernel/config/usb_gadget/mykeyboard
# Add basic information
echo 0x0100 > bcdDevice # Version 1.0.0
echo 0x0200 > bcdUSB # USB 2.0
echo 0x00 > bDeviceClass
echo 0x00 > bDeviceProtocol
echo 0x00 > bDeviceSubClass
echo 0x08 > bMaxPacketSize0
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x1d6b > idVendor # Linux Foundation
# Create English locale
mkdir strings/0x409
echo "My manufacturer" > strings/0x409/manufacturer
echo "My virtual keyboard" > strings/0x409/product
echo "0123456789" > strings/0x409/serialnumber
# Create HID function
mkdir functions/hid.usb0
echo 1 > functions/hid.usb0/protocol
echo 8 > functions/hid.usb0/report_length # 8-byte reports
echo 1 > functions/hid.usb0/subclass
# echo "..." > functions/hid.usb0/report_desc # Check second post
# Create configuration
mkdir configs/c.1
mkdir configs/c.1/strings/0x409
echo 0x80 > configs/c.1/bmAttributes
echo 200 > configs/c.1/MaxPower # 200 mA
echo "Example configuration" > configs/c.1/strings/0x409/configuration
# Link HID function to configuration
ln -s functions/hid.usb0 configs/c.1
# Enable gadget
ls /sys/class/udc > UDC
Bonus: The previous snippet and the test scripts used in the next posts are available at https://gist.github.com/rmed/0d11b7225b3b772bb0dd89108ee93df0
References
- [1] https://www.elinux.org/images/e/ef/USB_Gadget_Configfs_API_0.pdf
- [2] https://www.usb.org/sites/default/files/documents/hid1_11.pdf
- [3] http://www.linux-usb.org/usb.ids