Development of a User-Space Application for an HID Device, Using libhid

When it's time to get a new device working on Linux, every piece of information helps, whether it's reading the hardware documentation, snooping data, reading sample code or even running utilities on a non-Linux OS.
Communicating with the Device

A block diagram depicting the flow of control of data is shown in Figure 2. It may help in picturing where your code fits in with respect to the libraries and the device. From my investigation, I know that control messages periodically are written by way of the control pipe, and interrupt reads are made through endpoint 0.

The control pipe is used for three tasks: receiving and responding to requests for USB control and class data; transmitting data when polled by the HID class driver, using the Get_Report request; and receiving data from the host. The Interrupt pipe is used for two tasks: receiving asynchronous, or unrequested, data from the device and transmitting low-latency data to the device.

Figure 2. The new driver uses libhid, which depends on libusb.

The kernel has a DEBUG feature that can be activated in order to log extra information about what is happening when communicating with the device. To do this, a file in the kernel source needs to be modified. In the /usr/src/linux/drivers/usr/input/hid-core.c file, these two lines need to be changed from:

#undef DEBUG


#define DEBUG
#define DEBUG_DATA

The module needs to be recompiled and installed. Once this is done, the modules should prove helpful in determining whether your code is working and doing what you expect.

Sample code containing some helpful comments comes with libhid. The file test_libhid.c in the libhid/test directory is a good place to start writing code for the device. Below is a snippet of that code, along with some more explanation of the functions; details are omitted for brevity:

HIDInterface* hid;
hid_return ret;

HIDInterfaceMatcher matcher =
	 { 0x0ce5, 0x0003, NULL, NULL, 0 };
ret = hid_force_open(hid, 0, &matcher, 3);

int const PATH_LEN = 2;
int const PATH_IN[2] = { 0xffa00001, 0xffa00003 };

int const WRITE_PACKET_LEN = 8;
char write_packet[8] =
	 { 0x04,0x7f,0x7f,0x00,0x02,0x00,0x00,0x00 };

int const READ_PACKET_LEN = 5;
char read_packet[5];

ret = hid_set_output_report(hid,

ret = hid_interrupt_read(hid,

The first thing to do is identify the particular device we want to talk to. This is done with the HIDInterfaceMatcher call simply by entering the vendor ID and the product ID. These two identifiers are all that is required to identify any USB device. If you have more than one identical device, it is possible to separate them by serial number, that is, two matrix note readers would have the same vendor ID and product ID but different serial numbers. The HIDInterfaceMatcher call can do this; see the comments in the test_libhid.c file.

After some variable setup, the next is to detach the kernel driver from the HID device. Upon insertion of the HID device, the kernel usually loads the usbhid module, which we don't want. We do have a few options, however, for unloading it or for not loading it in the first place. One such way is to enter this command:

root@localhost #> modprobe -r usbhid

When the hid_force_open function runs, it attempts, n times, to detach the device before it fails. The device is now free from any control, so our code now “opens” the device. As with any USB device, it is necessary to send control information to the device to activate it. This information must be sent periodically in order for the device to remain active. If the control poll stops, the device deactivates after a certain timeout.

Writing to a device requires the HID usage path and its length, plus a packet and its length. To find this out, we need to parse the usage tree—the output of lsusb -vvv—and obtain the path to the interface we want. As with everything else, there are various methods for determining the path. At this stage, a lot of time was spent determining what path to write to, and a number of tools are helpful here, such as:

  1. The test_libhid.c code: when the correct vendor and product ID are entered in the code, the function hid_dump_tree, which uses the MGE hidparser (see Resources), which parses the HID usage tree and places the available usages at its leaves, outputs the available paths.

  2. A Windows application available from Arnaud, one of the libhid authors, also parses the usage tree and produces a nice GUI output, as shown in Figure 3.

  3. By parsing the output of lsusb -vvv, run as root, it is possible to parse the tree manually to determine the path. This process is explained in the comments of test_libhid.c code.

Figure 3. Understanding a device: one way to browse the available nodes of the HID tree is to use the SystemSoft HID Browser.

From the above methods, we now have a path value we can use for the hid_set_output_report. Once we know where to write to, it's a matter of what to send. This information should be in the technical documentation that comes with the device, and it can be verified with the USB-sniffing tools. As with the particular device I was using, verifying the format of a packet with the sniffing tools turned out to be important, as the information in the documentation didn't match what the sniff log reported (see the Snooping section).

Once the control message or output report is sent, we can start to read from the read pipe, endpoint0. The function needed is an interrupt read function. It already exists in libusb, but a corresponding libhid function doesn't. The developers of libhid simply hadn't come across a device that required it yet, so I studied the format of the other functions and implemented it appropriately. I also added a new error code to the existing list. These additions are being considered for inclusion in the latest version of libhid.

At this stage, once the interrupt read value is stored, I then parse this value, as per the Matrix documentation, to display the results to the user. For this device, that equates to information such as, “A ten-euro note has been inserted” or “The cash box is disconnected” and other such device-specific information. The details are unnecessary for the purposes of this article, but if anyone requires this detail, feel free to contact me.

This process is repeated for as long as the driver is running. We must keep polling the device to keep it active. There is a status LED on the device that turns green when the device is active and remains orange when inactive. The goal for quite some time was to make the little light go green.



Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

HID Browser

Anonymous's picture

I'd like to try this HID browser you mentioned but cannot find it anywhere. Where can I get it?


Anonymous's picture