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 | ||
5f42e19d BT |
132 | In-tree HID-BPF programs and ``udev-hid-bpf`` |
133 | ============================================= | |
134 | ||
135 | Official device fixes are shipped in the kernel tree as source in the | |
136 | ``drivers/hid/bpf/progs`` directory. This allows to add selftests to them in | |
137 | ``tools/testing/selftests/hid``. | |
138 | ||
139 | However, the compilation of these objects is not part of a regular kernel compilation | |
140 | given that they need an external tool to be loaded. This tool is currently | |
141 | `udev-hid-bpf <https://libevdev.pages.freedesktop.org/udev-hid-bpf/index.html>`_. | |
142 | ||
143 | For convenience, that external repository duplicates the files from here in | |
144 | ``drivers/hid/bpf/progs`` into its own ``src/bpf/stable`` directory. This allows | |
145 | distributions to not have to pull the entire kernel source tree to ship and package | |
146 | those HID-BPF fixes. ``udev-hid-bpf`` also has capabilities of handling multiple | |
147 | objects files depending on the kernel the user is running. | |
148 | ||
dfae6bec BT |
149 | Available types of programs |
150 | =========================== | |
151 | ||
c5958697 | 152 | HID-BPF is built "on top" of BPF, meaning that we use bpf struct_ops method to |
dfae6bec BT |
153 | declare our programs. |
154 | ||
155 | HID-BPF has the following attachment types available: | |
156 | ||
c5958697 | 157 | 1. event processing/filtering with ``SEC("struct_ops/hid_device_event")`` in libbpf |
dfae6bec | 158 | 2. actions coming from userspace with ``SEC("syscall")`` in libbpf |
c5958697 BT |
159 | 3. change of the report descriptor with ``SEC("struct_ops/hid_rdesc_fixup")`` or |
160 | ``SEC("struct_ops.s/hid_rdesc_fixup")`` in libbpf | |
dfae6bec | 161 | |
c5958697 | 162 | A ``hid_device_event`` is calling a BPF program when an event is received from |
dfae6bec BT |
163 | the device. Thus we are in IRQ context and can act on the data or notify userspace. |
164 | And given that we are in IRQ context, we can not talk back to the device. | |
165 | ||
166 | A ``syscall`` means that userspace called the syscall ``BPF_PROG_RUN`` facility. | |
167 | This time, we can do any operations allowed by HID-BPF, and talking to the device is | |
168 | allowed. | |
169 | ||
c5958697 | 170 | Last, ``hid_rdesc_fixup`` is different from the others as there can be only one |
dfae6bec | 171 | BPF program of this type. This is called on ``probe`` from the driver and allows to |
c5958697 | 172 | change the report descriptor from the BPF program. Once a ``hid_rdesc_fixup`` |
dfae6bec BT |
173 | program has been loaded, it is not possible to overwrite it unless the program which |
174 | inserted it allows us by pinning the program and closing all of its fds pointing to it. | |
175 | ||
c5958697 BT |
176 | Note that ``hid_rdesc_fixup`` can be declared as sleepable (``SEC("struct_ops.s/hid_rdesc_fixup")``). |
177 | ||
178 | ||
dfae6bec BT |
179 | Developer API: |
180 | ============== | |
181 | ||
c5958697 BT |
182 | Available ``struct_ops`` for HID-BPF: |
183 | ------------------------------------- | |
dfae6bec BT |
184 | |
185 | .. kernel-doc:: include/linux/hid_bpf.h | |
c5958697 | 186 | :identifiers: hid_bpf_ops |
dfae6bec | 187 | |
dfae6bec | 188 | |
c5958697 BT |
189 | User API data structures available in programs: |
190 | ----------------------------------------------- | |
dfae6bec | 191 | |
c5958697 BT |
192 | .. kernel-doc:: include/linux/hid_bpf.h |
193 | :identifiers: hid_bpf_ctx | |
194 | ||
195 | Available API that can be used in all HID-BPF struct_ops programs: | |
196 | ------------------------------------------------------------------ | |
dfae6bec BT |
197 | |
198 | .. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c | |
c5958697 | 199 | :identifiers: hid_bpf_get_data |
dfae6bec | 200 | |
c5958697 BT |
201 | Available API that can be used in syscall HID-BPF programs or in sleepable HID-BPF struct_ops programs: |
202 | ------------------------------------------------------------------------------------------------------- | |
dfae6bec BT |
203 | |
204 | .. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c | |
9acbb7ba | 205 | :identifiers: hid_bpf_hw_request hid_bpf_hw_output_report hid_bpf_input_report hid_bpf_try_input_report hid_bpf_allocate_context hid_bpf_release_context |
dfae6bec BT |
206 | |
207 | General overview of a HID-BPF program | |
208 | ===================================== | |
209 | ||
210 | Accessing the data attached to the context | |
211 | ------------------------------------------ | |
212 | ||
213 | The ``struct hid_bpf_ctx`` doesn't export the ``data`` fields directly and to access | |
214 | it, a bpf program needs to first call :c:func:`hid_bpf_get_data`. | |
215 | ||
216 | ``offset`` can be any integer, but ``size`` needs to be constant, known at compile | |
217 | time. | |
218 | ||
219 | This allows the following: | |
220 | ||
221 | 1. for a given device, if we know that the report length will always be of a certain value, | |
222 | we can request the ``data`` pointer to point at the full report length. | |
223 | ||
224 | The kernel will ensure we are using a correct size and offset and eBPF will ensure | |
225 | the code will not attempt to read or write outside of the boundaries:: | |
226 | ||
227 | __u8 *data = hid_bpf_get_data(ctx, 0 /* offset */, 256 /* size */); | |
228 | ||
229 | if (!data) | |
230 | return 0; /* ensure data is correct, now the verifier knows we | |
231 | * have 256 bytes available */ | |
232 | ||
233 | bpf_printk("hello world: %02x %02x %02x", data[0], data[128], data[255]); | |
234 | ||
235 | 2. if the report length is variable, but we know the value of ``X`` is always a 16-bit | |
236 | integer, we can then have a pointer to that value only:: | |
237 | ||
238 | __u16 *x = hid_bpf_get_data(ctx, offset, sizeof(*x)); | |
239 | ||
240 | if (!x) | |
241 | return 0; /* something went wrong */ | |
242 | ||
243 | *x += 1; /* increment X by one */ | |
244 | ||
245 | Effect of a HID-BPF program | |
246 | --------------------------- | |
247 | ||
c5958697 BT |
248 | For all HID-BPF attachment types except for :c:func:`hid_rdesc_fixup`, several eBPF |
249 | programs can be attached to the same device. If a HID-BPF struct_ops has a | |
250 | :c:func:`hid_rdesc_fixup` while another is already attached to the device, the | |
251 | kernel will return `-EINVAL` when attaching the struct_ops. | |
dfae6bec | 252 | |
c5958697 BT |
253 | Unless ``BPF_F_BEFORE`` is added to the flags while attaching the program, the new |
254 | program is appended at the end of the list. | |
255 | ``BPF_F_BEFORE`` will insert the new program at the beginning of the list which is | |
256 | useful for e.g. tracing where we need to get the unprocessed events from the device. | |
dfae6bec | 257 | |
c5958697 | 258 | Note that if there are multiple programs using the ``BPF_F_BEFORE`` flag, |
dfae6bec BT |
259 | only the most recently loaded one is actually the first in the list. |
260 | ||
c5958697 BT |
261 | ``SEC("struct_ops/hid_device_event")`` |
262 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
dfae6bec BT |
263 | |
264 | Whenever a matching event is raised, the eBPF programs are called one after the other | |
265 | and are working on the same data buffer. | |
266 | ||
267 | If a program changes the data associated with the context, the next one will see | |
268 | the modified data but it will have *no* idea of what the original data was. | |
269 | ||
270 | Once all the programs are run and return ``0`` or a positive value, the rest of the | |
271 | HID stack will work on the modified data, with the ``size`` field of the last hid_bpf_ctx | |
272 | being the new size of the input stream of data. | |
273 | ||
274 | A BPF program returning a negative error discards the event, i.e. this event will not be | |
275 | processed by the HID stack. Clients (hidraw, input, LEDs) will **not** see this event. | |
276 | ||
277 | ``SEC("syscall")`` | |
278 | ~~~~~~~~~~~~~~~~~~ | |
279 | ||
280 | ``syscall`` are not attached to a given device. To tell which device we are working | |
281 | with, userspace needs to refer to the device by its unique system id (the last 4 numbers | |
282 | in the sysfs path: ``/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000``). | |
283 | ||
284 | To retrieve a context associated with the device, the program must call | |
c5958697 | 285 | hid_bpf_allocate_context() and must release it with hid_bpf_release_context() |
dfae6bec BT |
286 | before returning. |
287 | Once the context is retrieved, one can also request a pointer to kernel memory with | |
c5958697 | 288 | hid_bpf_get_data(). This memory is big enough to support all input/output/feature |
dfae6bec BT |
289 | reports of the given device. |
290 | ||
c5958697 BT |
291 | ``SEC("struct_ops/hid_rdesc_fixup")`` |
292 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
dfae6bec | 293 | |
c5958697 BT |
294 | The ``hid_rdesc_fixup`` program works in a similar manner to ``.report_fixup`` |
295 | of ``struct hid_driver``. | |
dfae6bec BT |
296 | |
297 | When the device is probed, the kernel sets the data buffer of the context with the | |
298 | content of the report descriptor. The memory associated with that buffer is | |
299 | ``HID_MAX_DESCRIPTOR_SIZE`` (currently 4kB). | |
300 | ||
301 | The eBPF program can modify the data buffer at-will and the kernel uses the | |
302 | modified content and size as the report descriptor. | |
303 | ||
c5958697 BT |
304 | Whenever a struct_ops containing a ``SEC("struct_ops/hid_rdesc_fixup")`` program |
305 | is attached (if no program was attached before), the kernel immediately disconnects | |
306 | the HID device and does a reprobe. | |
dfae6bec | 307 | |
c5958697 BT |
308 | In the same way, when this struct_ops is detached, the kernel issues a disconnect |
309 | on the device. | |
dfae6bec BT |
310 | |
311 | There is no ``detach`` facility in HID-BPF. Detaching a program happens when | |
c5958697 | 312 | all the user space file descriptors pointing at a HID-BPF struct_ops link are closed. |
dfae6bec BT |
313 | Thus, if we need to replace a report descriptor fixup, some cooperation is |
314 | required from the owner of the original report descriptor fixup. | |
c5958697 | 315 | The previous owner will likely pin the struct_ops link in the bpffs, and we can then |
dfae6bec BT |
316 | replace it through normal bpf operations. |
317 | ||
318 | Attaching a bpf program to a device | |
319 | =================================== | |
320 | ||
c5958697 BT |
321 | We now use standard struct_ops attachment through ``bpf_map__attach_struct_ops()``. |
322 | But given that we need to attach a struct_ops to a dedicated HID device, the caller | |
323 | must set ``hid_id`` in the struct_ops map before loading the program in the kernel. | |
dfae6bec BT |
324 | |
325 | ``hid_id`` is the unique system ID of the HID device (the last 4 numbers in the | |
326 | sysfs path: ``/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000``) | |
327 | ||
c5958697 | 328 | One can also set ``flags``, which is of type ``enum hid_bpf_attach_flags``. |
dfae6bec BT |
329 | |
330 | We can not rely on hidraw to bind a BPF program to a HID device. hidraw is an | |
331 | artefact of the processing of the HID device, and is not stable. Some drivers | |
2f7f4efb | 332 | even disable it, so that removes the tracing capabilities on those devices |
dfae6bec BT |
333 | (where it is interesting to get the non-hidraw traces). |
334 | ||
335 | On the other hand, the ``hid_id`` is stable for the entire life of the HID device, | |
336 | even if we change its report descriptor. | |
337 | ||
338 | Given that hidraw is not stable when the device disconnects/reconnects, we recommend | |
339 | accessing the current report descriptor of the device through the sysfs. | |
340 | This is available at ``/sys/bus/hid/devices/BUS:VID:PID.000N/report_descriptor`` as a | |
341 | binary stream. | |
342 | ||
343 | Parsing the report descriptor is the responsibility of the BPF programmer or the userspace | |
344 | component that loads the eBPF program. | |
345 | ||
346 | An (almost) complete example of a BPF enhanced HID device | |
347 | ========================================================= | |
348 | ||
349 | *Foreword: for most parts, this could be implemented as a kernel driver* | |
350 | ||
351 | Let's imagine we have a new tablet device that has some haptic capabilities | |
352 | to simulate the surface the user is scratching on. This device would also have | |
353 | a specific 3 positions switch to toggle between *pencil on paper*, *cray on a wall* | |
354 | and *brush on a painting canvas*. To make things even better, we can control the | |
355 | physical position of the switch through a feature report. | |
356 | ||
357 | And of course, the switch is relying on some userspace component to control the | |
358 | haptic feature of the device itself. | |
359 | ||
360 | Filtering events | |
361 | ---------------- | |
362 | ||
363 | The first step consists in filtering events from the device. Given that the switch | |
364 | position is actually reported in the flow of the pen events, using hidraw to implement | |
365 | that filtering would mean that we wake up userspace for every single event. | |
366 | ||
367 | This is OK for libinput, but having an external library that is just interested in | |
368 | one byte in the report is less than ideal. | |
369 | ||
370 | For that, we can create a basic skeleton for our BPF program:: | |
371 | ||
372 | #include "vmlinux.h" | |
373 | #include <bpf/bpf_helpers.h> | |
374 | #include <bpf/bpf_tracing.h> | |
375 | ||
376 | /* HID programs need to be GPL */ | |
377 | char _license[] SEC("license") = "GPL"; | |
378 | ||
379 | /* HID-BPF kfunc API definitions */ | |
380 | extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx, | |
381 | unsigned int offset, | |
382 | const size_t __sz) __ksym; | |
dfae6bec BT |
383 | |
384 | struct { | |
385 | __uint(type, BPF_MAP_TYPE_RINGBUF); | |
386 | __uint(max_entries, 4096 * 64); | |
387 | } ringbuf SEC(".maps"); | |
388 | ||
dfae6bec BT |
389 | __u8 current_value = 0; |
390 | ||
c5958697 | 391 | SEC("struct_ops/hid_device_event") |
dfae6bec BT |
392 | int BPF_PROG(filter_switch, struct hid_bpf_ctx *hid_ctx) |
393 | { | |
394 | __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 192 /* size */); | |
395 | __u8 *buf; | |
396 | ||
397 | if (!data) | |
398 | return 0; /* EPERM check */ | |
399 | ||
400 | if (current_value != data[152]) { | |
401 | buf = bpf_ringbuf_reserve(&ringbuf, 1, 0); | |
402 | if (!buf) | |
403 | return 0; | |
404 | ||
405 | *buf = data[152]; | |
406 | ||
407 | bpf_ringbuf_commit(buf, 0); | |
408 | ||
409 | current_value = data[152]; | |
410 | } | |
411 | ||
412 | return 0; | |
413 | } | |
414 | ||
c5958697 BT |
415 | SEC(".struct_ops.link") |
416 | struct hid_bpf_ops haptic_tablet = { | |
417 | .hid_device_event = (void *)filter_switch, | |
418 | }; | |
419 | ||
420 | ||
421 | To attach ``haptic_tablet``, userspace needs to set ``hid_id`` first:: | |
dfae6bec BT |
422 | |
423 | static int attach_filter(struct hid *hid_skel, int hid_id) | |
424 | { | |
c5958697 | 425 | int err, link_fd; |
dfae6bec | 426 | |
c5958697 BT |
427 | hid_skel->struct_ops.haptic_tablet->hid_id = hid_id; |
428 | err = hid__load(skel); | |
429 | if (err) | |
430 | return err; | |
dfae6bec | 431 | |
c5958697 BT |
432 | link_fd = bpf_map__attach_struct_ops(hid_skel->maps.haptic_tablet); |
433 | if (!link_fd) { | |
434 | fprintf(stderr, "can not attach HID-BPF program: %m\n"); | |
435 | return -1; | |
436 | } | |
4b9a3f49 | 437 | |
c5958697 | 438 | return link_fd; /* the fd of the created bpf_link */ |
dfae6bec BT |
439 | } |
440 | ||
441 | Our userspace program can now listen to notifications on the ring buffer, and | |
442 | is awaken only when the value changes. | |
443 | ||
4b9a3f49 | 444 | When the userspace program doesn't need to listen to events anymore, it can just |
c5958697 | 445 | close the returned bpf link from :c:func:`attach_filter`, which will tell the kernel to |
4b9a3f49 BT |
446 | detach the program from the HID device. |
447 | ||
448 | Of course, in other use cases, the userspace program can also pin the fd to the | |
449 | BPF filesystem through a call to :c:func:`bpf_obj_pin`, as with any bpf_link. | |
450 | ||
dfae6bec BT |
451 | Controlling the device |
452 | ---------------------- | |
453 | ||
454 | To be able to change the haptic feedback from the tablet, the userspace program | |
455 | needs to emit a feature report on the device itself. | |
456 | ||
457 | Instead of using hidraw for that, we can create a ``SEC("syscall")`` program | |
458 | that talks to the device:: | |
459 | ||
460 | /* some more HID-BPF kfunc API definitions */ | |
461 | extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __ksym; | |
462 | extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __ksym; | |
463 | extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx, | |
464 | __u8* data, | |
465 | size_t len, | |
466 | enum hid_report_type type, | |
467 | enum hid_class_request reqtype) __ksym; | |
468 | ||
469 | ||
470 | struct hid_send_haptics_args { | |
471 | /* data needs to come at offset 0 so we can do a memcpy into it */ | |
472 | __u8 data[10]; | |
473 | unsigned int hid; | |
474 | }; | |
475 | ||
476 | SEC("syscall") | |
477 | int send_haptic(struct hid_send_haptics_args *args) | |
478 | { | |
479 | struct hid_bpf_ctx *ctx; | |
480 | int ret = 0; | |
481 | ||
482 | ctx = hid_bpf_allocate_context(args->hid); | |
483 | if (!ctx) | |
484 | return 0; /* EPERM check */ | |
485 | ||
486 | ret = hid_bpf_hw_request(ctx, | |
487 | args->data, | |
488 | 10, | |
489 | HID_FEATURE_REPORT, | |
490 | HID_REQ_SET_REPORT); | |
491 | ||
492 | hid_bpf_release_context(ctx); | |
493 | ||
494 | return ret; | |
495 | } | |
496 | ||
497 | And then userspace needs to call that program directly:: | |
498 | ||
499 | static int set_haptic(struct hid *hid_skel, int hid_id, __u8 haptic_value) | |
500 | { | |
501 | int err, prog_fd; | |
502 | int ret = -1; | |
503 | struct hid_send_haptics_args args = { | |
504 | .hid = hid_id, | |
505 | }; | |
506 | DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, | |
507 | .ctx_in = &args, | |
508 | .ctx_size_in = sizeof(args), | |
509 | ); | |
510 | ||
511 | args.data[0] = 0x02; /* report ID of the feature on our device */ | |
512 | args.data[1] = haptic_value; | |
513 | ||
514 | prog_fd = bpf_program__fd(hid_skel->progs.set_haptic); | |
515 | ||
516 | err = bpf_prog_test_run_opts(prog_fd, &tattrs); | |
517 | return err; | |
518 | } | |
519 | ||
520 | Now our userspace program is aware of the haptic state and can control it. The | |
521 | program could make this state further available to other userspace programs | |
522 | (e.g. via a DBus API). | |
523 | ||
524 | The interesting bit here is that we did not created a new kernel API for this. | |
525 | Which means that if there is a bug in our implementation, we can change the | |
526 | interface with the kernel at-will, because the userspace application is | |
527 | responsible for its own usage. |