How it works
Below is a high-level overview of how HID-BPF works and how udev-hid-bpf
enables us to take advantage of it immediately on plug-in.
Note that the udev-hid-bpf repository contains there distinct components:
udev-hid-bpf
the binary that loads HID-BPF programsA set of HID-BPF programs (see the
src/bpf
directory)Scaffolding to automate loading of HID-BPF programs via udev.
How HID-BPF works
HID-BPF is a feature available in kernels 6.3 and newer.
Most input devices today are HID devices (HID can be used over USB, Bluetooth, etc.). HID devices have two packets of information that get exchanged between the host and the device: the HID Report Descriptor and HID Reports. Both are simply arrays of bytes, the HID Report Descriptor describes how the bytes in HID Reports can be interpreted - it will effectively say e.g “bit 8 is button left” or “bits 9-16 is a relative x axis”. The HID Report Descriptor is static for a device and only exchanged once.
HID Reports are the actual events. Reports can be input reports (device to host, i.e. an event), output reports (host to device, e.g. setting an LED) and feature report (bidirectional, for on-device-configuration). The following only focuses on input reports but the same applies to output and feature reports. See An overview of HID for more details.
HID-BPF is a kernel feature that allows inserting an eBPF program that can change the data that is exchanged between the host and the device.
Typically, a report by the device is sent as-is to the kernel
(hid-generic
is the recipient driver in virtually all cases):
device: [ab|12|cd|34] -------------------------------> [kernel]
But where a HID BPF program is loaded for that device, that program can change the data before it arrives at the kernel driver:
device: [ab|12|cd|34] ----->[HID BPF program]
|
v
[ab|99|cd|34]----------> [kernel]
This applies to both HID Report Descriptors and to HID Reports. In other words, the BPF program can either change the data itself (e.g. “changing y to -y”) or it can change how the data is interpreted (e.g. change “bits 1/2/3 are buttons left/middle/right” to “bits 1/2/3 are buttons right/middle/left”).
How udev-hid-bpf works
udev-hid-bpf
- the binary - works similar to modprobe
or insmod
. It opens
a given .bpf.o
compiled BPF object file and loads it for the given device.
The binary can be invoked manually but in most cases we will trigger it via udev.
During meson compile
we generate udev rules and hwdb entries that tells us
which of our BPF object files matches which device. For example, such a hwdb
entry may look like this:
hid-bpf:hid:b0003g0001v000024AEp00002015
HID_BPF_T_002=0009-Rapoo__M50-Plus-Silent.bpf.o
Together with our udev rule this hwdb entry means that if a device with the
0x24AE
vendor ID and 0x2015
product ID is plugged in, the udev property
HID_BPF_T_002
(ignore the weird name) is set to the value
0009-Rapoo__M50-Plus-Silent.bpf.o
.
Our udev rule also calls udev-hid-bpf
for every device with such a property
set and udev-hid-bpf
will thus load the 0009-Rapoo__M50-Plus-Silent.bpf.o
BPF object file for the newly plugged-in HID device.
In summary, our flow is:
HID device is plugged in, udev runs its rules
our udev rule runs the hwdb builtin for the device’s modalias
the hwdb builtin sets the udev properties
HID_BPF_...
if there is a matchour udev rule runs the
udev-hid-bpf
binary if there is aHID_BPF...
property
udev-hid-bpf
loads the BPF program for the devicedevice sends HID reports, altered by BPF if applicable