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