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