4
    The HID Interface

All HID interfaces are required to have one IN endpoint, besides the control endpoint zero. If there is no IN endpoint, a HID device will not be created on the host. This is so even if the HID interface does not send any input reports. At least this is how the Linux kernel behaves.

In addition all HID endpoints must use “interrupt transfers”. If no interrupt IN endpoints exists, dmesg will display “couldn’t find an input interrupt endpoint”. However, the Linux kernel is nice enough to change a “bulk transfer” IN endpoint to an interrupt transfer one to maintain compliance.

In order to set up the IN 1 endpoint buffer for the next IN transaction, the firmware calls the V-USB routine usbSetInterrupt(). The figure below provides an illustration of how this routine is used. It also shows how to handle an IN transaction for control endpoint zero.

For a hidraw device all input reports must come over the interrupt IN endpoint. Of course a control request can be made for the report. The GET_REPORT control transfer request is used to do this. But unless libusb or some other driver is used by the host application, we are out of luck since hidraw provides no support for making this request.

A HID interface may also have an interrupt OUT endpoint. This endpoint is optional. Just like how an interrupt IN endpoint is used to send input reports, an interrupt OUT endpoint is used to transfer output reports from the host to the device. If an interrupt OUT endpoint is not provided by the interface then any output reports will be transferred using the SET_REPORT control transfer request.

    Example Two

Again create a new example directory and set it up as done before in example one. Next edit usbconfig.h and change the values to the following.

  • Set USB_CFG_HAVE_INTRIN_ENDPOINT to 1,
  • set USB_CFG_DEVICE_CLASS to 0,
  • set USB_CFG_INTERFACE_CLASS to 3, and
  • set USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH to 21.

The source code for example two is given below. Save it as main.c.

#include <avr/io.h>
#include <avr/wdt.h>         // for wdt routines
#include <avr/interrupt.h>   // for sei()
#include <util/delay.h>      // for _delay_ms()

#include <avr/pgmspace.h>    // required by usbdrv.h
#include "usbdrv.h"
#include "oddebug.h"         // debug macros

/* USB report descriptor */
PROGMEM char usbHidReportDescriptor[] = {
  0x06, 0xa0, 0xff, // USAGE_PAGE (Vendor Defined Page 1)
  0x09, 0x01,       // USAGE (Vendor Usage 1)
  0xa1, 0x01,       // COLLECTION (Application)

  // Input Report
  0x09, 0x02,       // Usage ID - vendor defined
  0x15, 0x00,       // Logical Minimum (0)
  0x26, 0xFF, 0x00, // Logical Maximum (255)
  0x75, 0x08,       // Report Size (8 bits)
  0x95, 0x08,       // Report Count (8 fields)
  0x81, 0x02,       // Input (Data, Variable, Absolute)

  0xc0              // END_COLLECTION
};

void main_loop (void)
{
  static uchar n, inbuf[8];
  uchar i;
  if (usbInterruptIsReady()) {
    n += 1;
    if (n > 8 ) n = 1;
    for (i=0; i<n-1; i++) inbuf[i] = '0' + i;
    inbuf[i++] = '\n';
    usbSetInterrupt(inbuf, i);
  }
}

usbMsgLen_t usbFunctionSetup (uchar *data)
{
  return 0;
}

int main (void)
{
  wdt_disable();
  odDebugInit();
  usbInit();

  usbDeviceDisconnect();  // force re-enumeration
  _delay_ms(250);
  usbDeviceConnect();

  sei();                  // now enable interrupts
  for(;;){
    usbPoll();
    main_loop();
  }
}

Compile the firmware. But before burning it, copy the following file as 99-hidraw.rules to /etc/udev/rules.d, otherwise superuser access is required to read and write the hidraw device. This is a huge advantage over using libusb which always requires superuser access.

KERNEL=="hidraw*", MODE="666"

Once the firmware is burned and example two enumerates, a new HID device should of been created on the host computer. The output of dmesg, if everything worked, will say something like the following.

generic-usb 0003:16C0:05DC.0001: hiddev0,hidraw0: USB HID v1.01 Device [obdev.at Template] on usb-0000:00:1d.7-4.1/input0

To display the input reports of our USB device to the screen execute, “cat /dev/hidraw0”.

    Polling of the Interrupt IN Endpoint

It is very interesting to note that no polling of the interrupt IN endpoint takes place until hidraw0 is read. When the shell command is killed or the device node closed, the polling immediately stops. This can be seen both in the debug output and by using an oscilloscope. Interrupt IN polling can also be triggered by the select() command and, interesting enough, by the write() command too.

Whether or not the host polls the interrupt endpoint continuously when there are no pending I/O operations, apparently depends on the design of the driver. For example, the driver hiddev immediately starts polling once opened and will not stop even when closed. This can be witnessed by executing and thenkilling “sudo hexdump /dev/usb/hiddev0”.

    Reading Input Reports

One word of caution needs to be mentioned about hidraw. When using the read() command make sure the number of bytes requested is equal to or greater than 8, the maximum packet size. Otherwise the data in the rest of the packet may be lost. The library libusb has a similar issue, any call to usb_interrupt_read() must set the number of bytes requested to 8 at most, but not less than the report size.

The figure below shows the additional requests that get tacked on compared to the previous example. Three new SETUP requests are made, of which two are class requests, SET_IDLE and GET_REPORT, and the other is a standard GET_DESCRIPTOR request. The GET_DESCRIPTOR request for the report descriptor is handled automatically by the V-USB library. Since the other two requests are class-specific they are handled in usbFunctionSetup().

Unlike in the previous example where the recipient of the requests was always the device, for these three requests the recipient is an interface. If there were multiple interfaces the firmware must determine which interface the request is intended for. The interface number is passed in the wIndex field. Since we only use one interface in this example interface zero will always be the recipient.

The shell command lsusb prints out the following for example two.

It be should be noted that usbFunctionSetup() does not test for either request SET_IDLE or GET_REPORT, it simply returns the value zero in response. This tells V-USB to reply with an “zero length packet” or ZLP. In the GET_REPORT case, this means a ZLP will be sent back as the input report. Since all future input reports in this example will be read through the HID interface’s interrupt IN 1 endpoint, responding back here with no data is appropriate. It did not seem to upset the host device driver.

    Control Transfer Direction

As a rule of thumb, any request that starts with the word GET has a “data transfer direction” from the device-to-host, as well as a data phase. So if usbFunctionSetup() returns zero for any of these requests, a ZLP will be sent back as its data phase. Some requests with the same data transfer direction do not have a data phase. In this case, if a zero is returned causing a ZLP to be queued, the ZLP will never get sent to the host because of the lack of a data phase.

For a request, where the “data transfer direction” is going the other way, from the host-to-device, a ZLP reply means the request has been successfully completed. This ZLP is sent out during the last phase of the control transfer called the “status phase”. An example of such a request is SET_IDLE. This request is mainly used to support an autorepeat capability on devices such as keyboards. If this capability is not needed, the requests can be safely ignored. Nevertheless, a ZLP still must be returned during the status phase to say everything is fine.

    The Report Descriptor

Report descriptors are confusing. A good way of understanding them is to think of them in terms of a database table definition. The Usage ID can be considered to be a field name. The actual content of the field can be considered to be an array of values. The report size is the bit size of this value and the report count is the array size. Lastly, the input, output, and feature tags create the actual field in the report. For example, the report descriptor given in example two creates an input report with a single field. The name of this field is 2 and it consists of an array of 8 values, each of which are 8-bits long.

There are complexities however, if the report provides a Minimum Usage ID and a Maximum Usage ID, it might create all those fields in the range. The content of each field consists of just one value then. There is also support for radio buttons with the Usages being the value, rather than the name, of a field.

As for what a Usage Page is, Usage IDs are organized into Usage Pages. So a Usage Page might be Generic Desktop which has a Usage Page ID of 1 and contains Usages such as Mouse X and Y movements.

It should be mentioned that an input report must be defined in the report descriptor, otherwise hidraw will not return any data it might receive from its interrupt IN endpoint. In other worlds, without an input report definition any read of the hidraw device by the host application will block forever.

    Interrupt Transfers

The USB specification defines interrupt transfers as “small-data, low-frequency, bounded-latency communication” that are always unidirectional. It is small-data because the most number of packets that can get sent is limited. The figure below gives an indication of how much space each interrupt transfer takes up per frame.

My experiments place the maximum data rate of an interrupt endpoint at a 1,000 bytes per second, each way, when full 8-byte data packets are transfered. The service interval used was 10ms. This is the smallest interval allowed for low speed devices. Service intervals are defined by the endpoint descriptor field, bInterval. For a service interval of 100ms, the maximum data rate turned out to be 125 bytes per second.

    USB Device Drivers

When using a HID device, the host application does not need to know which specific endpoint to read or write. The device driver can figure this out by looking at the endpoint descriptors from the device. As a result, input reports can be retrieved simply by using the driver’s read() command, no reference to the IN endpoint is required. Similarly, output reports can be sent to the device using the driver’s write() command.

If we look at the full output of dmesg from example two, we see that the kernel not only created a HID device but also loaded the HID core driver as well.

generic-usb 0003:16C0:05DC.0001: hiddev0,hidraw0: USB HID v1.01 Device [obdev.at Template] on usb-0000:00:1d.7-4.1/input0
usbcore: registered new interface driver usbhid
usbhid: USB HID core driver

The figure below gives a model, hopefully correct and based on the above output from dmesg, of the HID interface and the kernel device drivers that handle it.

It should be noted that endpoint zero is shared across other device drivers including libusb. Unfortunately if, for example, endpoint 1 has been reserved in an endpoint descriptor to a HID interface, it cannot be shared with libusb, according to my experiments. If libusb tries to read or write such an endpoint it will error out with a resource busy message. This is may be different on Windows. For vendor interfaces, the situation is different. The libusb library can access the endpoint so long as there is an endpoint descriptor for it. Accessing an endpoint without an endpoint descriptor will error out with the message “error submitting URB: No such file or directory”.

There might be ways to get around this however. The libusb host application can use control transfers to access any of the input or output reports. But there is some overhead associated with this solution. Another option is for the USB device to provide two interfaces, one for HID and the other for vendor. To communicate with the device, the HID device can access the HID interface while the libusb based host application can access the vendor interface.

For a more detailed diagram of the internal operation of endpoints and interfaces, see the figure below which was taken from the USB specification.

    Downloads

Example Two (62)