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