Commit | Line | Data |
---|---|---|
dfae6bec BT |
1 | .. SPDX-License-Identifier: GPL-2.0 |
2 | ||
3 | ======= | |
4 | HID-BPF | |
5 | ======= | |
6 | ||
7 | HID is a standard protocol for input devices but some devices may require | |
8 | custom tweaks, traditionally done with a kernel driver fix. Using the eBPF | |
9 | capabilities instead speeds up development and adds new capabilities to the | |
10 | existing HID interfaces. | |
11 | ||
12 | .. contents:: | |
13 | :local: | |
14 | :depth: 2 | |
15 | ||
16 | ||
17 | When (and why) to use HID-BPF | |
18 | ============================= | |
19 | ||
20 | There are several use cases when using HID-BPF is better | |
21 | than standard kernel driver fix: | |
22 | ||
23 | Dead zone of a joystick | |
24 | ----------------------- | |
25 | ||
26 | Assuming you have a joystick that is getting older, it is common to see it | |
27 | wobbling around its neutral point. This is usually filtered at the application | |
28 | level by adding a *dead zone* for this specific axis. | |
29 | ||
30 | With HID-BPF, we can apply this filtering in the kernel directly so userspace | |
31 | does not get woken up when nothing else is happening on the input controller. | |
32 | ||
33 | Of course, given that this dead zone is specific to an individual device, we | |
34 | can not create a generic fix for all of the same joysticks. Adding a custom | |
35 | kernel API for this (e.g. by adding a sysfs entry) does not guarantee this new | |
36 | kernel API will be broadly adopted and maintained. | |
37 | ||
38 | HID-BPF allows the userspace program to load the program itself, ensuring we | |
39 | only load the custom API when we have a user. | |
40 | ||
41 | Simple fixup of report descriptor | |
42 | --------------------------------- | |
43 | ||
44 | In the HID tree, half of the drivers only fix one key or one byte | |
45 | in the report descriptor. These fixes all require a kernel patch and the | |
46 | subsequent shepherding into a release, a long and painful process for users. | |
47 | ||
48 | We can reduce this burden by providing an eBPF program instead. Once such a | |
49 | program has been verified by the user, we can embed the source code into the | |
50 | kernel tree and ship the eBPF program and load it directly instead of loading | |
51 | a specific kernel module for it. | |
52 | ||
53 | Note: distribution of eBPF programs and their inclusion in the kernel is not | |
54 | yet fully implemented | |
55 | ||
56 | Add a new feature that requires a new kernel API | |
57 | ------------------------------------------------ | |
58 | ||
59 | An example for such a feature are the Universal Stylus Interface (USI) pens. | |
60 | Basically, USI pens require a new kernel API because there are new | |
61 | channels of communication that our HID and input stack do not support. | |
62 | Instead of using hidraw or creating new sysfs entries or ioctls, we can rely | |
63 | on eBPF to have the kernel API controlled by the consumer and to not | |
64 | impact the performances by waking up userspace every time there is an | |
65 | event. | |
66 | ||
67 | Morph a device into something else and control that from userspace | |
68 | ------------------------------------------------------------------ | |
69 | ||
70 | The kernel has a relatively static mapping of HID items to evdev bits. | |
71 | It cannot decide to dynamically transform a given device into something else | |
72 | as it does not have the required context and any such transformation cannot be | |
73 | undone (or even discovered) by userspace. | |
74 | ||
75 | However, some devices are useless with that static way of defining devices. For | |
76 | example, the Microsoft Surface Dial is a pushbutton with haptic feedback that | |
77 | is barely usable as of today. | |
78 | ||
79 | With eBPF, userspace can morph that device into a mouse, and convert the dial | |
80 | events into wheel events. Also, the userspace program can set/unset the haptic | |
81 | feedback depending on the context. For example, if a menu is visible on the | |
82 | screen we likely need to have a haptic click every 15 degrees. But when | |
83 | scrolling in a web page the user experience is better when the device emits | |
84 | events at the highest resolution. | |
85 | ||
86 | Firewall | |
87 | -------- | |
88 | ||
89 | What if we want to prevent other users to access a specific feature of a | |
90 | device? (think a possibly broken firmware update entry point) | |
91 | ||
92 | With eBPF, we can intercept any HID command emitted to the device and | |
93 | validate it or not. | |
94 | ||
95 | This also allows to sync the state between the userspace and the | |
96 | kernel/bpf program because we can intercept any incoming command. | |
97 | ||
98 | Tracing | |
99 | ------- | |
100 | ||
101 | The last usage is tracing events and all the fun we can do we BPF to summarize | |
102 | and analyze events. | |
103 | ||
104 | Right now, tracing relies on hidraw. It works well except for a couple | |
105 | of issues: | |
106 | ||
107 | 1. if the driver doesn't export a hidraw node, we can't trace anything | |
108 | (eBPF will be a "god-mode" there, so this may raise some eyebrows) | |
109 | 2. hidraw doesn't catch other processes' requests to the device, which | |
110 | means that we have cases where we need to add printks to the kernel | |
111 | to understand what is happening. | |
112 | ||
113 | High-level view of HID-BPF | |
114 | ========================== | |
115 | ||
116 | The main idea behind HID-BPF is that it works at an array of bytes level. | |
117 | Thus, all of the parsing of the HID report and the HID report descriptor | |
118 | must be implemented in the userspace component that loads the eBPF | |
119 | program. | |
120 | ||
121 | For example, in the dead zone joystick from above, knowing which fields | |
122 | in the data stream needs to be set to ``0`` needs to be computed by userspace. | |
123 | ||
124 | A corollary of this is that HID-BPF doesn't know about the other subsystems | |
125 | available in the kernel. *You can not directly emit input event through the | |
126 | input API from eBPF*. | |
127 | ||
128 | When a BPF program needs to emit input events, it needs to talk with the HID | |
129 | protocol, and rely on the HID kernel processing to translate the HID data into | |
130 | input events. | |
131 | ||
132 | Available types of programs | |
133 | =========================== | |
134 | ||
135 | HID-BPF is built "on top" of BPF, meaning that we use tracing method to | |
136 | declare our programs. | |
137 | ||
138 | HID-BPF has the following attachment types available: | |
139 | ||
140 | 1. event processing/filtering with ``SEC("fmod_ret/hid_bpf_device_event")`` in libbpf | |
141 | 2. actions coming from userspace with ``SEC("syscall")`` in libbpf | |
142 | 3. change of the report descriptor with ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` in libbpf | |
143 | ||
144 | A ``hid_bpf_device_event`` is calling a BPF program when an event is received from | |
145 | the device. Thus we are in IRQ context and can act on the data or notify userspace. | |
146 | And given that we are in IRQ context, we can not talk back to the device. | |
147 | ||
148 | A ``syscall`` means that userspace called the syscall ``BPF_PROG_RUN`` facility. | |
149 | This time, we can do any operations allowed by HID-BPF, and talking to the device is | |
150 | allowed. | |
151 | ||
152 | Last, ``hid_bpf_rdesc_fixup`` is different from the others as there can be only one | |
153 | BPF program of this type. This is called on ``probe`` from the driver and allows to | |
154 | change the report descriptor from the BPF program. Once a ``hid_bpf_rdesc_fixup`` | |
155 | program has been loaded, it is not possible to overwrite it unless the program which | |
156 | inserted it allows us by pinning the program and closing all of its fds pointing to it. | |
157 | ||
158 | Developer API: | |
159 | ============== | |
160 | ||
161 | User API data structures available in programs: | |
162 | ----------------------------------------------- | |
163 | ||
164 | .. kernel-doc:: include/linux/hid_bpf.h | |
165 | ||
166 | Available tracing functions to attach a HID-BPF program: | |
167 | -------------------------------------------------------- | |
168 | ||
169 | .. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c | |
170 | :functions: hid_bpf_device_event hid_bpf_rdesc_fixup | |
171 | ||
172 | Available API that can be used in all HID-BPF programs: | |
173 | ------------------------------------------------------- | |
174 | ||
175 | .. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c | |
176 | :functions: hid_bpf_get_data | |
177 | ||
178 | Available API that can be used in syscall HID-BPF programs: | |
179 | ----------------------------------------------------------- | |
180 | ||
181 | .. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c | |
182 | :functions: hid_bpf_attach_prog hid_bpf_hw_request hid_bpf_allocate_context hid_bpf_release_context | |
183 | ||
184 | General overview of a HID-BPF program | |
185 | ===================================== | |
186 | ||
187 | Accessing the data attached to the context | |
188 | ------------------------------------------ | |
189 | ||
190 | The ``struct hid_bpf_ctx`` doesn't export the ``data`` fields directly and to access | |
191 | it, a bpf program needs to first call :c:func:`hid_bpf_get_data`. | |
192 | ||
193 | ``offset`` can be any integer, but ``size`` needs to be constant, known at compile | |
194 | time. | |
195 | ||
196 | This allows the following: | |
197 | ||
198 | 1. for a given device, if we know that the report length will always be of a certain value, | |
199 | we can request the ``data`` pointer to point at the full report length. | |
200 | ||
201 | The kernel will ensure we are using a correct size and offset and eBPF will ensure | |
202 | the code will not attempt to read or write outside of the boundaries:: | |
203 | ||
204 | __u8 *data = hid_bpf_get_data(ctx, 0 /* offset */, 256 /* size */); | |
205 | ||
206 | if (!data) | |
207 | return 0; /* ensure data is correct, now the verifier knows we | |
208 | * have 256 bytes available */ | |
209 | ||
210 | bpf_printk("hello world: %02x %02x %02x", data[0], data[128], data[255]); | |
211 | ||
212 | 2. if the report length is variable, but we know the value of ``X`` is always a 16-bit | |
213 | integer, we can then have a pointer to that value only:: | |
214 | ||
215 | __u16 *x = hid_bpf_get_data(ctx, offset, sizeof(*x)); | |
216 | ||
217 | if (!x) | |
218 | return 0; /* something went wrong */ | |
219 | ||
220 | *x += 1; /* increment X by one */ | |
221 | ||
222 | Effect of a HID-BPF program | |
223 | --------------------------- | |
224 | ||
225 | For all HID-BPF attachment types except for :c:func:`hid_bpf_rdesc_fixup`, several eBPF | |
226 | programs can be attached to the same device. | |
227 | ||
228 | Unless ``HID_BPF_FLAG_INSERT_HEAD`` is added to the flags while attaching the | |
229 | program, the new program is appended at the end of the list. | |
230 | ``HID_BPF_FLAG_INSERT_HEAD`` will insert the new program at the beginning of the | |
231 | list which is useful for e.g. tracing where we need to get the unprocessed events | |
232 | from the device. | |
233 | ||
234 | Note that if there are multiple programs using the ``HID_BPF_FLAG_INSERT_HEAD`` flag, | |
235 | only the most recently loaded one is actually the first in the list. | |
236 | ||
237 | ``SEC("fmod_ret/hid_bpf_device_event")`` | |
238 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
239 | ||
240 | Whenever a matching event is raised, the eBPF programs are called one after the other | |
241 | and are working on the same data buffer. | |
242 | ||
243 | If a program changes the data associated with the context, the next one will see | |
244 | the modified data but it will have *no* idea of what the original data was. | |
245 | ||
246 | Once all the programs are run and return ``0`` or a positive value, the rest of the | |
247 | HID stack will work on the modified data, with the ``size`` field of the last hid_bpf_ctx | |
248 | being the new size of the input stream of data. | |
249 | ||
250 | A BPF program returning a negative error discards the event, i.e. this event will not be | |
251 | processed by the HID stack. Clients (hidraw, input, LEDs) will **not** see this event. | |
252 | ||
253 | ``SEC("syscall")`` | |
254 | ~~~~~~~~~~~~~~~~~~ | |
255 | ||
256 | ``syscall`` are not attached to a given device. To tell which device we are working | |
257 | with, userspace needs to refer to the device by its unique system id (the last 4 numbers | |
258 | in the sysfs path: ``/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000``). | |
259 | ||
260 | To retrieve a context associated with the device, the program must call | |
261 | :c:func:`hid_bpf_allocate_context` and must release it with :c:func:`hid_bpf_release_context` | |
262 | before returning. | |
263 | Once the context is retrieved, one can also request a pointer to kernel memory with | |
264 | :c:func:`hid_bpf_get_data`. This memory is big enough to support all input/output/feature | |
265 | reports of the given device. | |
266 | ||
267 | ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` | |
268 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
269 | ||
270 | The ``hid_bpf_rdesc_fixup`` program works in a similar manner to | |
271 | ``.report_fixup`` of ``struct hid_driver``. | |
272 | ||
273 | When the device is probed, the kernel sets the data buffer of the context with the | |
274 | content of the report descriptor. The memory associated with that buffer is | |
275 | ``HID_MAX_DESCRIPTOR_SIZE`` (currently 4kB). | |
276 | ||
277 | The eBPF program can modify the data buffer at-will and the kernel uses the | |
278 | modified content and size as the report descriptor. | |
279 | ||
280 | Whenever a ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` program is attached (if no | |
281 | program was attached before), the kernel immediately disconnects the HID device | |
282 | and does a reprobe. | |
283 | ||
284 | In the same way, when the ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` program is | |
285 | detached, the kernel issues a disconnect on the device. | |
286 | ||
287 | There is no ``detach`` facility in HID-BPF. Detaching a program happens when | |
288 | all the user space file descriptors pointing at a program are closed. | |
289 | Thus, if we need to replace a report descriptor fixup, some cooperation is | |
290 | required from the owner of the original report descriptor fixup. | |
291 | The previous owner will likely pin the program in the bpffs, and we can then | |
292 | replace it through normal bpf operations. | |
293 | ||
294 | Attaching a bpf program to a device | |
295 | =================================== | |
296 | ||
297 | ``libbpf`` does not export any helper to attach a HID-BPF program. | |
298 | Users need to use a dedicated ``syscall`` program which will call | |
299 | ``hid_bpf_attach_prog(hid_id, program_fd, flags)``. | |
300 | ||
301 | ``hid_id`` is the unique system ID of the HID device (the last 4 numbers in the | |
302 | sysfs path: ``/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000``) | |
303 | ||
304 | ``progam_fd`` is the opened file descriptor of the program to attach. | |
305 | ||
306 | ``flags`` is of type ``enum hid_bpf_attach_flags``. | |
307 | ||
308 | We can not rely on hidraw to bind a BPF program to a HID device. hidraw is an | |
309 | artefact of the processing of the HID device, and is not stable. Some drivers | |
2f7f4efb | 310 | even disable it, so that removes the tracing capabilities on those devices |
dfae6bec BT |
311 | (where it is interesting to get the non-hidraw traces). |
312 | ||
313 | On the other hand, the ``hid_id`` is stable for the entire life of the HID device, | |
314 | even if we change its report descriptor. | |
315 | ||
316 | Given that hidraw is not stable when the device disconnects/reconnects, we recommend | |
317 | accessing the current report descriptor of the device through the sysfs. | |
318 | This is available at ``/sys/bus/hid/devices/BUS:VID:PID.000N/report_descriptor`` as a | |
319 | binary stream. | |
320 | ||
321 | Parsing the report descriptor is the responsibility of the BPF programmer or the userspace | |
322 | component that loads the eBPF program. | |
323 | ||
324 | An (almost) complete example of a BPF enhanced HID device | |
325 | ========================================================= | |
326 | ||
327 | *Foreword: for most parts, this could be implemented as a kernel driver* | |
328 | ||
329 | Let's imagine we have a new tablet device that has some haptic capabilities | |
330 | to simulate the surface the user is scratching on. This device would also have | |
331 | a specific 3 positions switch to toggle between *pencil on paper*, *cray on a wall* | |
332 | and *brush on a painting canvas*. To make things even better, we can control the | |
333 | physical position of the switch through a feature report. | |
334 | ||
335 | And of course, the switch is relying on some userspace component to control the | |
336 | haptic feature of the device itself. | |
337 | ||
338 | Filtering events | |
339 | ---------------- | |
340 | ||
341 | The first step consists in filtering events from the device. Given that the switch | |
342 | position is actually reported in the flow of the pen events, using hidraw to implement | |
343 | that filtering would mean that we wake up userspace for every single event. | |
344 | ||
345 | This is OK for libinput, but having an external library that is just interested in | |
346 | one byte in the report is less than ideal. | |
347 | ||
348 | For that, we can create a basic skeleton for our BPF program:: | |
349 | ||
350 | #include "vmlinux.h" | |
351 | #include <bpf/bpf_helpers.h> | |
352 | #include <bpf/bpf_tracing.h> | |
353 | ||
354 | /* HID programs need to be GPL */ | |
355 | char _license[] SEC("license") = "GPL"; | |
356 | ||
357 | /* HID-BPF kfunc API definitions */ | |
358 | extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx, | |
359 | unsigned int offset, | |
360 | const size_t __sz) __ksym; | |
361 | extern int hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, u32 flags) __ksym; | |
362 | ||
363 | struct { | |
364 | __uint(type, BPF_MAP_TYPE_RINGBUF); | |
365 | __uint(max_entries, 4096 * 64); | |
366 | } ringbuf SEC(".maps"); | |
367 | ||
368 | struct attach_prog_args { | |
369 | int prog_fd; | |
370 | unsigned int hid; | |
371 | unsigned int flags; | |
372 | int retval; | |
373 | }; | |
374 | ||
375 | SEC("syscall") | |
376 | int attach_prog(struct attach_prog_args *ctx) | |
377 | { | |
378 | ctx->retval = hid_bpf_attach_prog(ctx->hid, | |
379 | ctx->prog_fd, | |
380 | ctx->flags); | |
381 | return 0; | |
382 | } | |
383 | ||
384 | __u8 current_value = 0; | |
385 | ||
386 | SEC("?fmod_ret/hid_bpf_device_event") | |
387 | int BPF_PROG(filter_switch, struct hid_bpf_ctx *hid_ctx) | |
388 | { | |
389 | __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 192 /* size */); | |
390 | __u8 *buf; | |
391 | ||
392 | if (!data) | |
393 | return 0; /* EPERM check */ | |
394 | ||
395 | if (current_value != data[152]) { | |
396 | buf = bpf_ringbuf_reserve(&ringbuf, 1, 0); | |
397 | if (!buf) | |
398 | return 0; | |
399 | ||
400 | *buf = data[152]; | |
401 | ||
402 | bpf_ringbuf_commit(buf, 0); | |
403 | ||
404 | current_value = data[152]; | |
405 | } | |
406 | ||
407 | return 0; | |
408 | } | |
409 | ||
410 | To attach ``filter_switch``, userspace needs to call the ``attach_prog`` syscall | |
411 | program first:: | |
412 | ||
413 | static int attach_filter(struct hid *hid_skel, int hid_id) | |
414 | { | |
415 | int err, prog_fd; | |
416 | int ret = -1; | |
417 | struct attach_prog_args args = { | |
418 | .hid = hid_id, | |
419 | }; | |
420 | DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, | |
421 | .ctx_in = &args, | |
422 | .ctx_size_in = sizeof(args), | |
423 | ); | |
424 | ||
425 | args.prog_fd = bpf_program__fd(hid_skel->progs.filter_switch); | |
426 | ||
427 | prog_fd = bpf_program__fd(hid_skel->progs.attach_prog); | |
428 | ||
429 | err = bpf_prog_test_run_opts(prog_fd, &tattrs); | |
4b9a3f49 BT |
430 | if (err) |
431 | return err; | |
432 | ||
433 | return args.retval; /* the fd of the created bpf_link */ | |
dfae6bec BT |
434 | } |
435 | ||
436 | Our userspace program can now listen to notifications on the ring buffer, and | |
437 | is awaken only when the value changes. | |
438 | ||
4b9a3f49 BT |
439 | When the userspace program doesn't need to listen to events anymore, it can just |
440 | close the returned fd from :c:func:`attach_filter`, which will tell the kernel to | |
441 | detach the program from the HID device. | |
442 | ||
443 | Of course, in other use cases, the userspace program can also pin the fd to the | |
444 | BPF filesystem through a call to :c:func:`bpf_obj_pin`, as with any bpf_link. | |
445 | ||
dfae6bec BT |
446 | Controlling the device |
447 | ---------------------- | |
448 | ||
449 | To be able to change the haptic feedback from the tablet, the userspace program | |
450 | needs to emit a feature report on the device itself. | |
451 | ||
452 | Instead of using hidraw for that, we can create a ``SEC("syscall")`` program | |
453 | that talks to the device:: | |
454 | ||
455 | /* some more HID-BPF kfunc API definitions */ | |
456 | extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __ksym; | |
457 | extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __ksym; | |
458 | extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx, | |
459 | __u8* data, | |
460 | size_t len, | |
461 | enum hid_report_type type, | |
462 | enum hid_class_request reqtype) __ksym; | |
463 | ||
464 | ||
465 | struct hid_send_haptics_args { | |
466 | /* data needs to come at offset 0 so we can do a memcpy into it */ | |
467 | __u8 data[10]; | |
468 | unsigned int hid; | |
469 | }; | |
470 | ||
471 | SEC("syscall") | |
472 | int send_haptic(struct hid_send_haptics_args *args) | |
473 | { | |
474 | struct hid_bpf_ctx *ctx; | |
475 | int ret = 0; | |
476 | ||
477 | ctx = hid_bpf_allocate_context(args->hid); | |
478 | if (!ctx) | |
479 | return 0; /* EPERM check */ | |
480 | ||
481 | ret = hid_bpf_hw_request(ctx, | |
482 | args->data, | |
483 | 10, | |
484 | HID_FEATURE_REPORT, | |
485 | HID_REQ_SET_REPORT); | |
486 | ||
487 | hid_bpf_release_context(ctx); | |
488 | ||
489 | return ret; | |
490 | } | |
491 | ||
492 | And then userspace needs to call that program directly:: | |
493 | ||
494 | static int set_haptic(struct hid *hid_skel, int hid_id, __u8 haptic_value) | |
495 | { | |
496 | int err, prog_fd; | |
497 | int ret = -1; | |
498 | struct hid_send_haptics_args args = { | |
499 | .hid = hid_id, | |
500 | }; | |
501 | DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, | |
502 | .ctx_in = &args, | |
503 | .ctx_size_in = sizeof(args), | |
504 | ); | |
505 | ||
506 | args.data[0] = 0x02; /* report ID of the feature on our device */ | |
507 | args.data[1] = haptic_value; | |
508 | ||
509 | prog_fd = bpf_program__fd(hid_skel->progs.set_haptic); | |
510 | ||
511 | err = bpf_prog_test_run_opts(prog_fd, &tattrs); | |
512 | return err; | |
513 | } | |
514 | ||
515 | Now our userspace program is aware of the haptic state and can control it. The | |
516 | program could make this state further available to other userspace programs | |
517 | (e.g. via a DBus API). | |
518 | ||
519 | The interesting bit here is that we did not created a new kernel API for this. | |
520 | Which means that if there is a bug in our implementation, we can change the | |
521 | interface with the kernel at-will, because the userspace application is | |
522 | responsible for its own usage. |