UHID: User-Space HID I/O drivers

Linux-next currently contains a new HID transport-level driver called UHID. If nothing goes wrong it will be released with linux-3.6 in about 2 months. The curious reader can currently find it in the HID maintainer’s (Jiri Kosina) tree. I get often asked what this driver is good for and why uinput wasn’t used to achieve the same? Lets look closer at this:

HID Subsystem Overview

The kernel HID subsystem (./drivers/hid/) implements the HID specifications and is responsible for handling HID requests from devices and forwarding them to the related kernel interfaces. The most known devices are USB keyboards. Therefore, the HID subsystem contains an USBHID called driver (it can be found in ./drivers/hid/usbhid/) which takes care of handling the transport-level I/O details of the USB HID devices. It registers each device it finds with the HID core and therefore is only responsible for handling pure I/O. The protocol parsing is done by the HID core. There is also the HIDP driver (it can be found in ./net/bluetooth/hidp) which does the same for Bluetooth devices. USBHID and HIDP are called “hid_ll_drivers: HID low level drivers” and are responsible for the transport-level (or I/O), thus also called “transport-level driver” or “I/O driver”.

In a perfect world, the HID core would handle the HID reports from the low-level drivers, parse them and feed them into the input subsystem which provides them as input data to user-space. However, many devices need some quirks to work correctly as they are not standard-conforming. Therefore, the “hid_driver: HID device driver” infrastructure was built which allows to write HID drivers handling the device-specific quirks or protocol. They are mostly independent of the transport-level and can work with any low-level HID driver. Some drivers (specifically hid-picolcd and hid-wiimote) even implement complex input-unrelated protocols on top of HID to allow full device-control.

UHID Driver

The UHID driver is a “low-level/transport-level driver (hid_ll_driver)” which was written to allow user-space to act as I/O drivers for the HID subsystem. The UHID driver does not allow writing HID-device drivers (hid_driver), though. There is already the “hidraw” driver which can serve for this purpose.

User-space can simply open /dev/uhid and create/destroy hid-devices in the hid-core. Each open file-descriptor on /dev/uhid can control exactly one device. Let’s look at this from the perspective of HoG (HID over GATT/Bluetooth-Low-Energy): GATT is a Bluetooth protocol implemented in user-space. When user-space opens an LE (low-energy) connection to a Bluetooth device, the device can advertise HID capabilities via GATT. User-space then opens /dev/uhid and creates a new device via the UHID_CREATE message. The UHID driver registers the new device with the HID core and user-space can now transmit I/O data to the kernel. The important design pattern is, that the transport-driver is actually implemented in user-space. If it was realized in kernel-space, then you wouldn’t need UHID and could register your own low-level HID driver. The reasons why HoG is implemented in user-space are out of the scope of this document.

So why doesn’t HoG use uinput? uinput is a kernel module that allows creating kernel input-devices from user-space. It does not do any sophisticated protocol parsing or similar but simply forwards the events to all interested listeners. If HoG was using uinput, it would have to implement the whole HID stack in user-space, although the kernel already has the whole infrastructure for that. Hence, we would duplicate a whole bunch of code without a real gain. uinput isn’t even faster than UHID, neither is it smaller. uinput is simply not suited for this use-case. Therefore, UHID was created.

UHID Design

Each open file-descriptor on /dev/uhid can register a single HID device with the HID-core. Communication is solely done via write()/read() with a special buffer-format. The in-tree documentation describes the protocol in full detail. Each write()/read() call transmit zero or one messages. If multiple messages are to be transmitted, user-space must use readv()/writev(). The UHID_CREATE and UHID_DESTROY messages allow user-space to register and destroy the HID device so it can control the device lifetime. UHID_INPUT is used to feed raw I/O data into the kernel. Similarly, the kernel sends several events to user-space (which can be poll()’ed for) to notify the application about new output-data or device-state changes.

The initial patchset already includes an example program that demonstrates how to use the UHID API. It emulates an HID mouse with 3 buttons and a mouse-wheel. Obviously, this program can be easily written with uinput and uinput would be better suited for this use-case. But again, this is only an example to demonstrate how this is achieved. Systems like HoG cannot use uinput so they can now be seamlessly integrated into the linux eco-system with UHID, without needing any special application support to use these new devices.

User-space HoG implementation has already been merged into BlueZ so you can test this feature when running linux-next. Lets see how all this works out. If the performance is Ok, there is also the idea of moving HIDP into user-space, too. That is, both Bluetooth HID transport-drivers would run inside of the user-space Bluetooth daemon. But lets first make sure HoG is working great!

6 thoughts on “UHID: User-Space HID I/O drivers

  1. nr

    Very nice write up! Thanks for the information!

    As i understood

    HID over GATT passes data to ==> UHID (passes it to) ==> HID subsystem

    but now I am not able to find how HID subsystem is passing that data to input subsystem.
    I did some research on hot plugging and found out that

    node for uhid is getting created in
    /sys/class/misc/uhid and I am creating corresponding node(mynode) in user space in /dev/input
    using mknod after getting major and minor version numbers from /sys/class/misc/uhid/dev .

    but still don’t see any event coming to this character interface /dev/input/mynode

    Could you please explain How does HID susbsyetm passes data to input subsystem and how will that data reach to my character interface /dev/input/mynode

    Do I have to write special hid driver for my ble hid device?

    Any suggestion would be highly appreciated!

    I also tried to create a udev rule in /lib/udev/rules.d as follows

    KERNEL==”uhid”, SUBSYSTEM=”misc”, DRIVER=””

    The only bottleneck i am facing is how do i get data out of HID susbsyetm so that my application can read that data from /dev/mynode character interface?

    Could you please point out if understanding is correct ?
    Do i have to write a specific HID IO driver for my device?
    Do i have to program something related to generic event interface to do that?

    I have done lot of research on these bits and pieces but not able to put them together.

    Reply
    1. David Herrmann Post author

      You might want to have a look at “http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/hid/uhid.txt”. The first paragraph explains the data-flow.

      HID I/O drivers like HoG, BT-HID, USB-HID, I2C-HID, … send raw I/O to the kernel (either directly or via UHID from userspace). The kernel HID core parses the reports (drivers/hid/hid-input.c) and creates linux input devices (/dev/input/eventX). These input devices report the data to user-space as input events.

      So if the kernel doesn’t create input devices for your HID device, then your HID device is probably not following the standard and needs a special HID device driver. There are lots of quirks available. Just look at the many drivers in drivers/hid/hid-*.c.

      You might also want to enable hidraw to receive the raw HID reports via /dev/hidrawX. This makes it pretty easy to reverse-engineer misbehaving devices.

      But note that UHID only provides a path between device and kernel for raw I/O. Nothing input-device related is handled with UHID! These are two totally different things. It doesn’t matter to the HID core whether a device is a Bluetooth, UHID, USB, I2C, or whatever device. They are handled the same way.

      Reply
  2. nr

    Thanks creating a special HID driver helps! I have another question in your write up you mentioned “Each open file-descriptor on /dev/uhid can register a single HID device with the HID-core” ,So if i have a multitouch device say HID device having keyboard and touchpad, in that case do i have to open 2 file descriptors on /dev/uhid or 2 interfaces can be multiplexed on single decsriptors?
    Thanks

    Reply
    1. David Herrmann Post author

      You need to open the interface twice. There is no way to multiplex multiple connections as I didn’t see a reason to allow that. Synchronization should be the same, anyway.

      Reply
  3. MA

    Hi David, I am trying to compile the uhid-example.c but facing compilation issues. I am on 3.8.0-31-generic kernel version.

    ./uhid-example.c:163:29: error: dereferencing pointer to incomplete type
    ./uhid-example.c:167:27: error: dereferencing pointer to incomplete type
    ./uhid-example.c: In function âcreateâ:
    ./uhid-example.c:178:20: error: storage size of âevâ isnât known
    ./uhid-example.c:181:12: error: âUHID_CREATEâ undeclared (first use in this function)
    ./uhid-example.c:181:12: note: each undeclared identifier is reported only once for each function it appears in
    ./uhid-example.c:185:20: error: âBUS_USBâ undeclared (first use in this function)
    ./uhid-example.c: In function âdestroyâ:
    ./uhid-example.c:196:20: error: storage size of âevâ isnât known
    ./uhid-example.c:199:12: error: âUHID_DESTROYâ undeclared (first use in this function

    Am I missing something? Are there multiple versions of the header files? If you can throw some light, that will be helpful.

    Thx,
    MA

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s