Linux HID Driver
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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