Linux HID Driver

in   Drivers   , ,

Most HIDs (Human Interface Devices) are supported in Linux without the need for a custom driver. However, some devices don’t necessarily implement the USB HID specification correctly, and the support for gamepad controls is rather lacking.

Of particular interest to me, and a few others in the Linux gaming community, is support for the Saitek X52/X52 Pro HOTAS joystick. For the most part, this joystick works very well in Linux, but with one limitation. The little thumb stick on the throttle module that controls the mouse motion. Linux only reports the X-axis for that thumbstick, and I traced it to the fact that Linux maps only one axis in the game controls usage page. For this to work as intended, we really need to build a customized driver.

I had a version of the driver module in the past for the X52 Pro version alone, that used the USB subsystem to communicate with, and receive reports to parse and report input events. However, there were some issues with the kernel tending to panic if I removed the module, and then reattached the joystick.

Given that I now have a userspace driver for the LEDs and MFD, I no longer need to maintain a kernel mode driver for those parts, and I only need to parse and report input events. This now means that I don’t have to deal with the USB transport layer, and I can let the HID subsystem deal with that.

The goal is to replace the hid-generic driver with our custom saitek-x52 driver. The simplest “Hello world”-esque driver is shown below - all it does is tell the kernel to load this driver when it detects a compatible joystick. All the actual communication with the joystick, converting the reports to input events, etc. is handled in hid-core.

Hello HID
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <linux/hid.h>
#include <linux/module.h>

#define VENDOR_SAITEK 0x06a3
#define DEV_X52_1 0x0255
#define DEV_X52_2 0x075c
#define DEV_X52_PRO 0x0762

static const struct hid_device_id x52_devices[] = {
    { HID_USB_DEVICE(VENDOR_SAITEK, DEV_X52_1) },
    { HID_USB_DEVICE(VENDOR_SAITEK, DEV_X52_2) },
    { HID_USB_DEVICE(VENDOR_SAITEK, DEV_X52_PRO) },
    {}
};

MODULE_DEVICE_TABLE(hid, x52_devices);

static struct hid_driver x52_driver = {
    .name = "saitek-x52",
    .id_table = x52_devices,
};

module_hid_driver(x52_driver);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("...");
MODULE_DESCRIPTION("HID driver for Saitek X52 HOTAS devices");

We need to disable the input mapping, i.e., tell HID core that our module will deal with parsing the report and raising the corresponding input events.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
static int x52_input_mapping(struct hid_device *dev,
                             struct hid_input *input,
                             struct hid_field *field,
                             struct hid_usage *usage,
                             unsigned long **bit,
                             int *max)
{
    /*
     * We are reporting the events in x52_raw_event.
     * Skip the hid-input processing.
     */
    return -1;
}
  static struct hid_driver x52_driver = {
      .name = "saitek-x52",
      .id_table = x52_devices,
+     .input_mapping = x52_input_mapping,
  };

 

Now, we have to describe the input fields. Because we are writing a driver for a joystick, we will need to enable both EV_ABS for the joystick axes and EV_KEY for the buttons. Also, for each ABS_* axis, we will need to describe the parameters by calling input_set_abs_params. Finally, we will save the pointer to the input structure in the driver data field, since we will need it during the event processing.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static int x52_input_configured(struct hid_device *dev,
                                struct hid_input *input)
{
    struct input_dev * input_dev = input->input;
    int i;

    hid_set_drvdata(dev, input_dev);

    set_bit(EV_KEY, input_dev->evbit);
    set_bit(EV_ABS, input_dev->evbit);

    // ...

    for (i = 0; i < max_btn; i++) {
        set_bit(BTN_TRIGGER_HAPPY1 + i, input_dev->keybit);
    }

    set_bit(ABS_X, input_dev->absbit);
    set_bit(ABS_Y, input_dev->absbit);
    set_bit(ABS_Z, input_dev->absbit);
    /* ... */

    input_set_abs_params(input_dev, ABS_X, 0, max_stick, max_stick >> 8, max_stick >> 4);
    input_set_abs_params(input_dev, ABS_Y, 0, max_stick, max_stick >> 8, max_stick >> 4);
    input_set_abs_params(input_dev, ABS_Z, 0, 255, 0, 15);
    /* ... */

    return 0;
}
  static struct hid_driver x52_driver = {
      .name = "saitek-x52",
      .id_table = x52_devices,
      .input_mapping = x52_input_mapping,
+     .input_configured = x52_input_configured,
  };

 

Finally, we have to parse the raw event and raise the corresponding input events. Note that we will get the input device structure that we stashed away in the driver data field, and use that pointer to write input events. We should be able to interpret the raw bytes sent by the device in data, and map them to the events that we described in the previous step.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
static int x52_raw_event(struct hid_device *dev,
                         struct hid_report *report, u8 *data, int len)
{
    struct input_dev *input_dev = hid_get_drvdata(dev);
    int ret;

    /* ... */
    input_report_abs(input_dev, ABS_Z, data[4]);
    input_report_abs(input_dev, ABS_RX, data[5]);
    input_report_abs(input_dev, ABS_RY, data[6]);
    input_report_abs(input_dev, ABS_MISC, data[7]);

    /* ... */
    for (i = 0; i < num_buttons; i++) {
        idx = 8 + (i / BITS_PER_BYTE);
        btn = !!(data[idx] & (1 << (i % BITS_PER_BYTE)));
        input_report_key(input_dev, BTN_TRIGGER_HAPPY + i, btn);
    }

    input_sync(input_dev);
    return ret;
}
  static struct hid_driver x52_driver = {
      .name = "saitek-x52",
      .id_table = x52_devices,
      .input_mapping = x52_input_mapping,
      .input_configured = x52_input_configured,
+     .raw_event = x52_raw_event,
  };

At this point, you can customize your event handler, e.g., handling different button order between similar devices, handling different axis parameters, etc.

You can see the full module source code here