| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527 |
- .. SPDX-License-Identifier: GPL-2.0
- =======
- HID-BPF
- =======
- HID is a standard protocol for input devices but some devices may require
- custom tweaks, traditionally done with a kernel driver fix. Using the eBPF
- capabilities instead speeds up development and adds new capabilities to the
- existing HID interfaces.
- .. contents::
- :local:
- :depth: 2
- When (and why) to use HID-BPF
- =============================
- There are several use cases when using HID-BPF is better
- than standard kernel driver fix:
- Dead zone of a joystick
- -----------------------
- Assuming you have a joystick that is getting older, it is common to see it
- wobbling around its neutral point. This is usually filtered at the application
- level by adding a *dead zone* for this specific axis.
- With HID-BPF, we can apply this filtering in the kernel directly so userspace
- does not get woken up when nothing else is happening on the input controller.
- Of course, given that this dead zone is specific to an individual device, we
- can not create a generic fix for all of the same joysticks. Adding a custom
- kernel API for this (e.g. by adding a sysfs entry) does not guarantee this new
- kernel API will be broadly adopted and maintained.
- HID-BPF allows the userspace program to load the program itself, ensuring we
- only load the custom API when we have a user.
- Simple fixup of report descriptor
- ---------------------------------
- In the HID tree, half of the drivers only fix one key or one byte
- in the report descriptor. These fixes all require a kernel patch and the
- subsequent shepherding into a release, a long and painful process for users.
- We can reduce this burden by providing an eBPF program instead. Once such a
- program has been verified by the user, we can embed the source code into the
- kernel tree and ship the eBPF program and load it directly instead of loading
- a specific kernel module for it.
- Note: distribution of eBPF programs and their inclusion in the kernel is not
- yet fully implemented
- Add a new feature that requires a new kernel API
- ------------------------------------------------
- An example for such a feature are the Universal Stylus Interface (USI) pens.
- Basically, USI pens require a new kernel API because there are new
- channels of communication that our HID and input stack do not support.
- Instead of using hidraw or creating new sysfs entries or ioctls, we can rely
- on eBPF to have the kernel API controlled by the consumer and to not
- impact the performances by waking up userspace every time there is an
- event.
- Morph a device into something else and control that from userspace
- ------------------------------------------------------------------
- The kernel has a relatively static mapping of HID items to evdev bits.
- It cannot decide to dynamically transform a given device into something else
- as it does not have the required context and any such transformation cannot be
- undone (or even discovered) by userspace.
- However, some devices are useless with that static way of defining devices. For
- example, the Microsoft Surface Dial is a pushbutton with haptic feedback that
- is barely usable as of today.
- With eBPF, userspace can morph that device into a mouse, and convert the dial
- events into wheel events. Also, the userspace program can set/unset the haptic
- feedback depending on the context. For example, if a menu is visible on the
- screen we likely need to have a haptic click every 15 degrees. But when
- scrolling in a web page the user experience is better when the device emits
- events at the highest resolution.
- Firewall
- --------
- What if we want to prevent other users to access a specific feature of a
- device? (think a possibly broken firmware update entry point)
- With eBPF, we can intercept any HID command emitted to the device and
- validate it or not.
- This also allows to sync the state between the userspace and the
- kernel/bpf program because we can intercept any incoming command.
- Tracing
- -------
- The last usage is tracing events and all the fun we can do we BPF to summarize
- and analyze events.
- Right now, tracing relies on hidraw. It works well except for a couple
- of issues:
- 1. if the driver doesn't export a hidraw node, we can't trace anything
- (eBPF will be a "god-mode" there, so this may raise some eyebrows)
- 2. hidraw doesn't catch other processes' requests to the device, which
- means that we have cases where we need to add printks to the kernel
- to understand what is happening.
- High-level view of HID-BPF
- ==========================
- The main idea behind HID-BPF is that it works at an array of bytes level.
- Thus, all of the parsing of the HID report and the HID report descriptor
- must be implemented in the userspace component that loads the eBPF
- program.
- For example, in the dead zone joystick from above, knowing which fields
- in the data stream needs to be set to ``0`` needs to be computed by userspace.
- A corollary of this is that HID-BPF doesn't know about the other subsystems
- available in the kernel. *You can not directly emit input event through the
- input API from eBPF*.
- When a BPF program needs to emit input events, it needs to talk with the HID
- protocol, and rely on the HID kernel processing to translate the HID data into
- input events.
- In-tree HID-BPF programs and ``udev-hid-bpf``
- =============================================
- Official device fixes are shipped in the kernel tree as source in the
- ``drivers/hid/bpf/progs`` directory. This allows to add selftests to them in
- ``tools/testing/selftests/hid``.
- However, the compilation of these objects is not part of a regular kernel compilation
- given that they need an external tool to be loaded. This tool is currently
- `udev-hid-bpf <https://libevdev.pages.freedesktop.org/udev-hid-bpf/index.html>`_.
- For convenience, that external repository duplicates the files from here in
- ``drivers/hid/bpf/progs`` into its own ``src/bpf/stable`` directory. This allows
- distributions to not have to pull the entire kernel source tree to ship and package
- those HID-BPF fixes. ``udev-hid-bpf`` also has capabilities of handling multiple
- objects files depending on the kernel the user is running.
- Available types of programs
- ===========================
- HID-BPF is built "on top" of BPF, meaning that we use bpf struct_ops method to
- declare our programs.
- HID-BPF has the following attachment types available:
- 1. event processing/filtering with ``SEC("struct_ops/hid_device_event")`` in libbpf
- 2. actions coming from userspace with ``SEC("syscall")`` in libbpf
- 3. change of the report descriptor with ``SEC("struct_ops/hid_rdesc_fixup")`` or
- ``SEC("struct_ops.s/hid_rdesc_fixup")`` in libbpf
- A ``hid_device_event`` is calling a BPF program when an event is received from
- the device. Thus we are in IRQ context and can act on the data or notify userspace.
- And given that we are in IRQ context, we can not talk back to the device.
- A ``syscall`` means that userspace called the syscall ``BPF_PROG_RUN`` facility.
- This time, we can do any operations allowed by HID-BPF, and talking to the device is
- allowed.
- Last, ``hid_rdesc_fixup`` is different from the others as there can be only one
- BPF program of this type. This is called on ``probe`` from the driver and allows to
- change the report descriptor from the BPF program. Once a ``hid_rdesc_fixup``
- program has been loaded, it is not possible to overwrite it unless the program which
- inserted it allows us by pinning the program and closing all of its fds pointing to it.
- Note that ``hid_rdesc_fixup`` can be declared as sleepable (``SEC("struct_ops.s/hid_rdesc_fixup")``).
- Developer API:
- ==============
- Available ``struct_ops`` for HID-BPF:
- -------------------------------------
- .. kernel-doc:: include/linux/hid_bpf.h
- :identifiers: hid_bpf_ops
- User API data structures available in programs:
- -----------------------------------------------
- .. kernel-doc:: include/linux/hid_bpf.h
- :identifiers: hid_bpf_ctx
- Available API that can be used in all HID-BPF struct_ops programs:
- ------------------------------------------------------------------
- .. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
- :identifiers: hid_bpf_get_data
- Available API that can be used in syscall HID-BPF programs or in sleepable HID-BPF struct_ops programs:
- -------------------------------------------------------------------------------------------------------
- .. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
- :identifiers: hid_bpf_hw_request hid_bpf_hw_output_report hid_bpf_input_report hid_bpf_try_input_report hid_bpf_allocate_context hid_bpf_release_context
- General overview of a HID-BPF program
- =====================================
- Accessing the data attached to the context
- ------------------------------------------
- The ``struct hid_bpf_ctx`` doesn't export the ``data`` fields directly and to access
- it, a bpf program needs to first call :c:func:`hid_bpf_get_data`.
- ``offset`` can be any integer, but ``size`` needs to be constant, known at compile
- time.
- This allows the following:
- 1. for a given device, if we know that the report length will always be of a certain value,
- we can request the ``data`` pointer to point at the full report length.
- The kernel will ensure we are using a correct size and offset and eBPF will ensure
- the code will not attempt to read or write outside of the boundaries::
- __u8 *data = hid_bpf_get_data(ctx, 0 /* offset */, 256 /* size */);
- if (!data)
- return 0; /* ensure data is correct, now the verifier knows we
- * have 256 bytes available */
- bpf_printk("hello world: %02x %02x %02x", data[0], data[128], data[255]);
- 2. if the report length is variable, but we know the value of ``X`` is always a 16-bit
- integer, we can then have a pointer to that value only::
- __u16 *x = hid_bpf_get_data(ctx, offset, sizeof(*x));
- if (!x)
- return 0; /* something went wrong */
- *x += 1; /* increment X by one */
- Effect of a HID-BPF program
- ---------------------------
- For all HID-BPF attachment types except for :c:func:`hid_rdesc_fixup`, several eBPF
- programs can be attached to the same device. If a HID-BPF struct_ops has a
- :c:func:`hid_rdesc_fixup` while another is already attached to the device, the
- kernel will return `-EINVAL` when attaching the struct_ops.
- Unless ``BPF_F_BEFORE`` is added to the flags while attaching the program, the new
- program is appended at the end of the list.
- ``BPF_F_BEFORE`` will insert the new program at the beginning of the list which is
- useful for e.g. tracing where we need to get the unprocessed events from the device.
- Note that if there are multiple programs using the ``BPF_F_BEFORE`` flag,
- only the most recently loaded one is actually the first in the list.
- ``SEC("struct_ops/hid_device_event")``
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Whenever a matching event is raised, the eBPF programs are called one after the other
- and are working on the same data buffer.
- If a program changes the data associated with the context, the next one will see
- the modified data but it will have *no* idea of what the original data was.
- Once all the programs are run and return ``0`` or a positive value, the rest of the
- HID stack will work on the modified data, with the ``size`` field of the last hid_bpf_ctx
- being the new size of the input stream of data.
- A BPF program returning a negative error discards the event, i.e. this event will not be
- processed by the HID stack. Clients (hidraw, input, LEDs) will **not** see this event.
- ``SEC("syscall")``
- ~~~~~~~~~~~~~~~~~~
- ``syscall`` are not attached to a given device. To tell which device we are working
- with, userspace needs to refer to the device by its unique system id (the last 4 numbers
- in the sysfs path: ``/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000``).
- To retrieve a context associated with the device, the program must call
- hid_bpf_allocate_context() and must release it with hid_bpf_release_context()
- before returning.
- Once the context is retrieved, one can also request a pointer to kernel memory with
- hid_bpf_get_data(). This memory is big enough to support all input/output/feature
- reports of the given device.
- ``SEC("struct_ops/hid_rdesc_fixup")``
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- The ``hid_rdesc_fixup`` program works in a similar manner to ``.report_fixup``
- of ``struct hid_driver``.
- When the device is probed, the kernel sets the data buffer of the context with the
- content of the report descriptor. The memory associated with that buffer is
- ``HID_MAX_DESCRIPTOR_SIZE`` (currently 4kB).
- The eBPF program can modify the data buffer at-will and the kernel uses the
- modified content and size as the report descriptor.
- Whenever a struct_ops containing a ``SEC("struct_ops/hid_rdesc_fixup")`` program
- is attached (if no program was attached before), the kernel immediately disconnects
- the HID device and does a reprobe.
- In the same way, when this struct_ops is detached, the kernel issues a disconnect
- on the device.
- There is no ``detach`` facility in HID-BPF. Detaching a program happens when
- all the user space file descriptors pointing at a HID-BPF struct_ops link are closed.
- Thus, if we need to replace a report descriptor fixup, some cooperation is
- required from the owner of the original report descriptor fixup.
- The previous owner will likely pin the struct_ops link in the bpffs, and we can then
- replace it through normal bpf operations.
- Attaching a bpf program to a device
- ===================================
- We now use standard struct_ops attachment through ``bpf_map__attach_struct_ops()``.
- But given that we need to attach a struct_ops to a dedicated HID device, the caller
- must set ``hid_id`` in the struct_ops map before loading the program in the kernel.
- ``hid_id`` is the unique system ID of the HID device (the last 4 numbers in the
- sysfs path: ``/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000``)
- One can also set ``flags``, which is of type ``enum hid_bpf_attach_flags``.
- We can not rely on hidraw to bind a BPF program to a HID device. hidraw is an
- artefact of the processing of the HID device, and is not stable. Some drivers
- even disable it, so that removes the tracing capabilities on those devices
- (where it is interesting to get the non-hidraw traces).
- On the other hand, the ``hid_id`` is stable for the entire life of the HID device,
- even if we change its report descriptor.
- Given that hidraw is not stable when the device disconnects/reconnects, we recommend
- accessing the current report descriptor of the device through the sysfs.
- This is available at ``/sys/bus/hid/devices/BUS:VID:PID.000N/report_descriptor`` as a
- binary stream.
- Parsing the report descriptor is the responsibility of the BPF programmer or the userspace
- component that loads the eBPF program.
- An (almost) complete example of a BPF enhanced HID device
- =========================================================
- *Foreword: for most parts, this could be implemented as a kernel driver*
- Let's imagine we have a new tablet device that has some haptic capabilities
- to simulate the surface the user is scratching on. This device would also have
- a specific 3 positions switch to toggle between *pencil on paper*, *cray on a wall*
- and *brush on a painting canvas*. To make things even better, we can control the
- physical position of the switch through a feature report.
- And of course, the switch is relying on some userspace component to control the
- haptic feature of the device itself.
- Filtering events
- ----------------
- The first step consists in filtering events from the device. Given that the switch
- position is actually reported in the flow of the pen events, using hidraw to implement
- that filtering would mean that we wake up userspace for every single event.
- This is OK for libinput, but having an external library that is just interested in
- one byte in the report is less than ideal.
- For that, we can create a basic skeleton for our BPF program::
- #include "vmlinux.h"
- #include <bpf/bpf_helpers.h>
- #include <bpf/bpf_tracing.h>
- /* HID programs need to be GPL */
- char _license[] SEC("license") = "GPL";
- /* HID-BPF kfunc API definitions */
- extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
- unsigned int offset,
- const size_t __sz) __ksym;
- struct {
- __uint(type, BPF_MAP_TYPE_RINGBUF);
- __uint(max_entries, 4096 * 64);
- } ringbuf SEC(".maps");
- __u8 current_value = 0;
- SEC("struct_ops/hid_device_event")
- int BPF_PROG(filter_switch, struct hid_bpf_ctx *hid_ctx)
- {
- __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 192 /* size */);
- __u8 *buf;
- if (!data)
- return 0; /* EPERM check */
- if (current_value != data[152]) {
- buf = bpf_ringbuf_reserve(&ringbuf, 1, 0);
- if (!buf)
- return 0;
- *buf = data[152];
- bpf_ringbuf_commit(buf, 0);
- current_value = data[152];
- }
- return 0;
- }
- SEC(".struct_ops.link")
- struct hid_bpf_ops haptic_tablet = {
- .hid_device_event = (void *)filter_switch,
- };
- To attach ``haptic_tablet``, userspace needs to set ``hid_id`` first::
- static int attach_filter(struct hid *hid_skel, int hid_id)
- {
- int err, link_fd;
- hid_skel->struct_ops.haptic_tablet->hid_id = hid_id;
- err = hid__load(skel);
- if (err)
- return err;
- link_fd = bpf_map__attach_struct_ops(hid_skel->maps.haptic_tablet);
- if (!link_fd) {
- fprintf(stderr, "can not attach HID-BPF program: %m\n");
- return -1;
- }
- return link_fd; /* the fd of the created bpf_link */
- }
- Our userspace program can now listen to notifications on the ring buffer, and
- is awaken only when the value changes.
- When the userspace program doesn't need to listen to events anymore, it can just
- close the returned bpf link from :c:func:`attach_filter`, which will tell the kernel to
- detach the program from the HID device.
- Of course, in other use cases, the userspace program can also pin the fd to the
- BPF filesystem through a call to :c:func:`bpf_obj_pin`, as with any bpf_link.
- Controlling the device
- ----------------------
- To be able to change the haptic feedback from the tablet, the userspace program
- needs to emit a feature report on the device itself.
- Instead of using hidraw for that, we can create a ``SEC("syscall")`` program
- that talks to the device::
- /* some more HID-BPF kfunc API definitions */
- extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __ksym;
- extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __ksym;
- extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx,
- __u8* data,
- size_t len,
- enum hid_report_type type,
- enum hid_class_request reqtype) __ksym;
- struct hid_send_haptics_args {
- /* data needs to come at offset 0 so we can do a memcpy into it */
- __u8 data[10];
- unsigned int hid;
- };
- SEC("syscall")
- int send_haptic(struct hid_send_haptics_args *args)
- {
- struct hid_bpf_ctx *ctx;
- int ret = 0;
- ctx = hid_bpf_allocate_context(args->hid);
- if (!ctx)
- return 0; /* EPERM check */
- ret = hid_bpf_hw_request(ctx,
- args->data,
- 10,
- HID_FEATURE_REPORT,
- HID_REQ_SET_REPORT);
- hid_bpf_release_context(ctx);
- return ret;
- }
- And then userspace needs to call that program directly::
- static int set_haptic(struct hid *hid_skel, int hid_id, __u8 haptic_value)
- {
- int err, prog_fd;
- int ret = -1;
- struct hid_send_haptics_args args = {
- .hid = hid_id,
- };
- DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
- .ctx_in = &args,
- .ctx_size_in = sizeof(args),
- );
- args.data[0] = 0x02; /* report ID of the feature on our device */
- args.data[1] = haptic_value;
- prog_fd = bpf_program__fd(hid_skel->progs.set_haptic);
- err = bpf_prog_test_run_opts(prog_fd, &tattrs);
- return err;
- }
- Now our userspace program is aware of the haptic state and can control it. The
- program could make this state further available to other userspace programs
- (e.g. via a DBus API).
- The interesting bit here is that we did not created a new kernel API for this.
- Which means that if there is a bug in our implementation, we can change the
- interface with the kernel at-will, because the userspace application is
- responsible for its own usage.
|