rmed

blog

Using RPi Zero as a Keyboard Part 1: Setup and device definition

2017-06-26 14:40

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

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

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 time
  • DATA: 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 device
  • functions/: defines the capabilities of the virtual device
  • os_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 device
  • product: product name/description
  • serialnumber: 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 device
  • report_desc: binary descriptor for the reports sent by the keyboard
  • report_length: length of the reports sent by the keyboard
  • subclass: 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
  • strings/: directory for localization of the configuration description (we will use 0x409 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

Post series index

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