diff mbox series

[hid,v11,14/14] Documentation: add HID-BPF docs

Message ID 20221025093458.457089-15-benjamin.tissoires@redhat.com
State Superseded
Headers show
Series Introduce eBPF support for HID devices | expand

Commit Message

Benjamin Tissoires Oct. 25, 2022, 9:34 a.m. UTC
Gives a primer on HID-BPF.

Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>

---

changes in v11:
- add MAINTAINERS entry for the HID doc

changes in v10:
- some fixes in the documentation from the review

no changes in v9

no changes in v8

no changes in v7

changes in v6:
- amended the example now that we can directly use the data from the
  syscall context

changes in v5:
- amended for new API
- reworded most of the sentences (thanks to Peter Hutterer for the review)

changes in v4:
- fixed typos

new in v3
---
 Documentation/hid/hid-bpf.rst | 513 ++++++++++++++++++++++++++++++++++
 Documentation/hid/index.rst   |   1 +
 MAINTAINERS                   |   1 +
 3 files changed, 515 insertions(+)
 create mode 100644 Documentation/hid/hid-bpf.rst

Comments

Bagas Sanjaya Oct. 26, 2022, 1:21 p.m. UTC | #1
On Tue, Oct 25, 2022 at 11:34:58AM +0200, Benjamin Tissoires wrote:
> diff --git a/Documentation/hid/hid-bpf.rst b/Documentation/hid/hid-bpf.rst
> new file mode 100644
> index 000000000000..ba35aa2e2ba8
> --- /dev/null
> +++ b/Documentation/hid/hid-bpf.rst
> @@ -0,0 +1,513 @@
> +.. 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.
> +
> +Available types of programs
> +===========================
> +
> +HID-BPF is built "on top" of BPF, meaning that we use tracing method to
> +declare our programs.
> +
> +HID-BPF has the following attachment types available:
> +
> +1. event processing/filtering with ``SEC("fmod_ret/hid_bpf_device_event")`` in libbpf
> +2. actions coming from userspace with ``SEC("syscall")`` in libbpf
> +3. change of the report descriptor with ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` in libbpf
> +
> +A ``hid_bpf_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_bpf_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_bpf_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.
> +
> +Developer API:
> +==============
> +
> +User API data structures available in programs:
> +-----------------------------------------------
> +
> +.. kernel-doc:: include/uapi/linux/hid_bpf.h
> +.. kernel-doc:: include/linux/hid_bpf.h
> +
> +Available tracing functions to attach a HID-BPF program:
> +--------------------------------------------------------
> +
> +.. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
> +   :functions: hid_bpf_device_event hid_bpf_rdesc_fixup
> +
> +Available API that can be used in all HID-BPF programs:
> +-------------------------------------------------------
> +
> +.. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
> +   :functions: hid_bpf_get_data
> +
> +Available API that can be used in syscall HID-BPF programs:
> +-----------------------------------------------------------
> +
> +.. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
> +   :functions: hid_bpf_attach_prog hid_bpf_hw_request 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_bpf_rdesc_fixup`, several eBPF
> +programs can be attached to the same device.
> +
> +Unless ``HID_BPF_FLAG_INSERT_HEAD`` is added to the flags while attaching the
> +program, the new program is appended at the end of the list.
> +``HID_BPF_FLAG_INSERT_HEAD`` 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 ``HID_BPF_FLAG_INSERT_HEAD`` flag,
> +only the most recently loaded one is actually the first in the list.
> +
> +``SEC("fmod_ret/hid_bpf_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
> +:c:func:`hid_bpf_allocate_context` and must release it with :c:func:`hid_bpf_release_context`
> +before returning.
> +Once the context is retrieved, one can also request a pointer to kernel memory with
> +:c:func:`hid_bpf_get_data`. This memory is big enough to support all input/output/feature
> +reports of the given device.
> +
> +``SEC("fmod_ret/hid_bpf_rdesc_fixup")``
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +The ``hid_bpf_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 ``SEC("fmod_ret/hid_bpf_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 the ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` program 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 program 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 program in the bpffs, and we can then
> +replace it through normal bpf operations.
> +
> +Attaching a bpf program to a device
> +===================================
> +
> +``libbpf`` does not export any helper to attach a HID-BPF program.
> +Users need to use a dedicated ``syscall`` program which will call
> +``hid_bpf_attach_prog(hid_id, program_fd, flags)``.
> +
> +``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``)
> +
> +``progam_fd`` is the opened file descriptor of the program to attach.
> +
> +``flags`` 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 capabilies 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;
> +  extern int hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, u32 flags) __ksym;
> +
> +  struct {
> +	__uint(type, BPF_MAP_TYPE_RINGBUF);
> +	__uint(max_entries, 4096 * 64);
> +  } ringbuf SEC(".maps");
> +
> +  struct attach_prog_args {
> +	int prog_fd;
> +	unsigned int hid;
> +	unsigned int flags;
> +	int retval;
> +  };
> +
> +  SEC("syscall")
> +  int attach_prog(struct attach_prog_args *ctx)
> +  {
> +	ctx->retval = hid_bpf_attach_prog(ctx->hid,
> +					  ctx->prog_fd,
> +					  ctx->flags);
> +	return 0;
> +  }
> +
> +  __u8 current_value = 0;
> +
> +  SEC("?fmod_ret/hid_bpf_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;
> +  }
> +
> +To attach ``filter_switch``, userspace needs to call the ``attach_prog`` syscall
> +program first::
> +
> +  static int attach_filter(struct hid *hid_skel, int hid_id)
> +  {
> +	int err, prog_fd;
> +	int ret = -1;
> +	struct attach_prog_args args = {
> +		.hid = hid_id,
> +	};
> +	DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
> +		.ctx_in = &args,
> +		.ctx_size_in = sizeof(args),
> +	);
> +
> +	args.prog_fd = bpf_program__fd(hid_skel->progs.filter_switch);
> +
> +	prog_fd = bpf_program__fd(hid_skel->progs.attach_prog);
> +
> +	err = bpf_prog_test_run_opts(prog_fd, &tattrs);
> +	return err;
> +  }
> +
> +Our userspace program can now listen to notifications on the ring buffer, and
> +is awaken only when the value changes.
> +
> +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.
> diff --git a/Documentation/hid/index.rst b/Documentation/hid/index.rst
> index e50f513c579c..b2028f382f11 100644
> --- a/Documentation/hid/index.rst
> +++ b/Documentation/hid/index.rst
> @@ -11,6 +11,7 @@ Human Interface Devices (HID)
>     hidraw
>     hid-sensor
>     hid-transport
> +   hid-bpf
>  
>     uhid
>  

The wordings are somewhat confusing, so here's the alternative:

---- >8 ----

diff --git a/Documentation/hid/hid-bpf.rst b/Documentation/hid/hid-bpf.rst
index ba35aa2e2ba836..c59bce4b9cf214 100644
--- a/Documentation/hid/hid-bpf.rst
+++ b/Documentation/hid/hid-bpf.rst
@@ -27,88 +27,89 @@ 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.
+With HID-BPF, the filter can be applied in the kernel directly so that
+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.
+Of course, given that dead zone filter is device-specific, it is not possible
+to 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 that
+the 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.
+HID-BPF allows the userspace program to load the program itself, ensuring that
+custom API is only needed for edge cases (like esoteric joysticks)
 
 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.
+subsequent shepherding into a release, which can be 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.
+This burden can be reduced by providing an eBPF program instead. Once the
+program has been verified by the user, the unmodified driver source code
+can be shipped in the kernel tree with corresponding eBPF program. The latter
+can be loaded directly instead of loading a specific kernel module for the
+device.
 
-Note: distribution of eBPF programs and their inclusion in the kernel is not
-yet fully implemented
+.. 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.
+channels of communication that aren't yet supported by current HID/input
+kernel stack. Instead of using hidraw or creating new sysfs entries or ioctls,
+eBPF program can be used to have existing kernel API consumed and adapted
+by users of such pens and to not impact performance 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.
+It cannot decide how 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.
+However, some devices requires such feature. For example, 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.
+With eBPF, userspace programs can morph the aforementioned device into a mouse
+and convert the dial events into wheel events. Also, userspace programs can
+set/unset the haptic feedback depending on the context. For example, if there
+is a visible menu on the screen, it is likely to need having a haptic click
+every 15 degrees. On the other hand, when scrolling 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)
+What if there is a desire to prevent other users using a specific feature of
+the 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.
+With eBPF, any HID command emitted to the device can be intercepted and
+validated. This also allows to sync the state between userspace and kernel/bpf
+program.
 
 Tracing
 -------
 
-The last usage is tracing events and all the fun we can do we BPF to summarize
-and analyze events.
+The last usage is tracing events, where BPF programs can be written 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
+1. if the driver doesn't export a hidraw node, tracing is impossible
    (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.
+   means that in some cases, adding debugging printks to the kernel is
+   needed to understand what is happening.
 
 High-level view of HID-BPF
 ==========================
@@ -118,12 +119,13 @@ 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.
+For example, in the dead zone joystick example from above, knowing which fields
+in the data stream that needs to be set to ``0`` needs to be determined 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*.
+available in the kernel. This means that 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
@@ -132,28 +134,30 @@ input events.
 Available types of programs
 ===========================
 
-HID-BPF is built "on top" of BPF, meaning that we use tracing method to
-declare our programs.
+HID-BPF is built "on top" of BPF, meaning that you can use tracing method to
+write the program.
 
-HID-BPF has the following attachment types available:
+HID-BPF has the following program types available, all available in libbpf:
 
-1. event processing/filtering with ``SEC("fmod_ret/hid_bpf_device_event")`` in libbpf
-2. actions coming from userspace with ``SEC("syscall")`` in libbpf
-3. change of the report descriptor with ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` in libbpf
+1. event processing/filtering with ``SEC("fmod_ret/hid_bpf_device_event")``
+2. actions coming from userspace with ``SEC("syscall")``
+3. changing report descriptor with ``SEC("fmod_ret/hid_bpf_rdesc_fixup")``
 
-A ``hid_bpf_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.
+``hid_bpf_device_event`` calls a BPF program when an event is received from
+the device, which puts the program in IRQ context and can act on the data or
+notify userspace. However, you can not talk back to the device while in IRQ
+context.
 
-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.
+``syscall`` means that userspace called the ``BPF_PROG_RUN`` command through
+``bpf()`` syscall. You can do any operations allowed by HID-BPF, including
+talking to the device.
 
-Last, ``hid_bpf_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_bpf_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.
+Last, ``hid_bpf_rdesc_fixup`` is different from the others as there can be
+only one BPF program of this type running. This is called on ``probe`` from
+the driver and allows to change the report descriptor from the program. Once
+a ``hid_bpf_rdesc_fixup`` program has been loaded, it is not possible to
+overwrite it unless the program which inserted it allows pinning the program
+and closing all of its fds pointing to it.
 
 Developer API:
 ==============
@@ -188,82 +192,87 @@ 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`.
+The ``struct hid_bpf_ctx`` doesn't export the ``data`` fields directly. 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.
+``offset`` can be any integer, but ``size`` needs to be compile-time constant.
 
-This allows the following:
+Depending on report length, there are two possibilities:
 
-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.
+1. If you know that the report length will always be certain fixed value, you
+   can set ``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::
+   The kernel will ensure that data size and offset are correct and eBPF
+   will ensure the code will not attempt to read or write outside data
+   boundaries::
 
      __u8 *data = hid_bpf_get_data(ctx, 0 /* offset */, 256 /* size */);
 
+     /* check that the data is correct (256 byte length) */
      if (!data)
-         return 0; /* ensure data is correct, now the verifier knows we
-                    * have 256 bytes available */
+         return 0;
 
      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::
+2. if the report length is variable, but you know the value of ``x`` data is
+   always a 16-bit integer, you can simply have a pointer to the value::
 
       __u16 *x = hid_bpf_get_data(ctx, offset, sizeof(*x));
 
+      /* return 0 if the data is empty, otherwise increment the data
+       * pointer by one */
       if (!x)
-          return 0; /* something went wrong */
+          return 0;
 
-      *x += 1; /* increment X by one */
+      *x += 1;
 
 Effect of a HID-BPF program
 ---------------------------
 
-For all HID-BPF attachment types except for :c:func:`hid_bpf_rdesc_fixup`, several eBPF
-programs can be attached to the same device.
+For all HID-BPF program types except for :c:func:`hid_bpf_rdesc_fixup`,
+multiple eBPF programs can be attached to the same device.
 
-Unless ``HID_BPF_FLAG_INSERT_HEAD`` is added to the flags while attaching the
-program, the new program is appended at the end of the list.
-``HID_BPF_FLAG_INSERT_HEAD`` 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.
+By default, new programs are appended at the end of the list when attached.
+With ``HID_BPF_FLAG_INSERT_HEAD`` flag, programs will instead be prepended
+at the beginning of programs list, which is useful for e.g. tracing where
+you need to get raw events from the device.
 
-Note that if there are multiple programs using the ``HID_BPF_FLAG_INSERT_HEAD`` flag,
-only the most recently loaded one is actually the first in the list.
+Note that if there are multiple programs loaded with
+``HID_BPF_FLAG_INSERT_HEAD``, only the most recently loaded one is actually the first in the list.
 
 ``SEC("fmod_ret/hid_bpf_device_event")``
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Whenever a matching event is raised, the eBPF programs are called one after the other
+Whenever a matching event is raised, the eBPF programs are called sequencially
 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.
+If a program changes the data associated with the context, the next program
+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.
+Once all the programs are run and return ``0`` or positive value, the rest of
+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.
+A BPF program returning a negative error discards the event, which will not be
+processed by the HID stack. The stack users (hidraw, input, LEDs) will not see
+the discarded 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``).
+``syscall`` programs are not attached to a given device. To tell which device
+is associated with such programs, userspace program needs to refer to the
+device by its unique system id (the base name in the sysfs path, e.g.
+``xxxx:yyyy:zzzz:0000`` for the path named
+``/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000``).
 
 To retrieve a context associated with the device, the program must call
-:c:func:`hid_bpf_allocate_context` and must release it with :c:func:`hid_bpf_release_context`
-before returning.
-Once the context is retrieved, one can also request a pointer to kernel memory with
-:c:func:`hid_bpf_get_data`. This memory is big enough to support all input/output/feature
-reports of the given device.
+:c:func:`hid_bpf_allocate_context` and must release it with
+:c:func:`hid_bpf_release_context` before returning.
+Once the context is retrieved, the program can also allocate a pointer to
+kernel memory with :c:func:`hid_bpf_get_data`. This memory is big enough to
+support all input/output/feature reports of the given device.
 
 ``SEC("fmod_ret/hid_bpf_rdesc_fixup")``
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -271,26 +280,24 @@ reports of the given device.
 The ``hid_bpf_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
+When the device is probed, the kernel sets data buffer of the context with
+the content of report descriptor. The buffer memory size 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 ``SEC("fmod_ret/hid_bpf_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 the ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` program is
-detached, the kernel issues a disconnect on the device.
+Whenever a program is attached (and no programs were attached before), the
+kernel immediately disconnects the HID device and does a reprobe. Likewise,
+when the ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` program is detached, the
+kernel disconnects the device.
 
 There is no ``detach`` facility in HID-BPF. Detaching a program happens when
 all the user space file descriptors pointing at a program 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 program in the bpffs, and we can then
-replace it through normal bpf operations.
+Consequently, if you need to replace a report descriptor fixup, you need to
+coordinate with the owner of original fixup. The owner will likely pin the
+program in bpffs, which you can then replace the fixup through normal bpf
+operations.
 
 Attaching a bpf program to a device
 ===================================
@@ -299,54 +306,52 @@ Attaching a bpf program to a device
 Users need to use a dedicated ``syscall`` program which will call
 ``hid_bpf_attach_prog(hid_id, program_fd, flags)``.
 
-``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``)
+``hid_id`` is the unique system ID of the HID device.
 
-``progam_fd`` is the opened file descriptor of the program to attach.
+``program_fd`` is the opened file descriptor of attached program.
 
 ``flags`` 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 capabilies on those devices
-(where it is interesting to get the non-hidraw traces).
+You can not rely on hidraw to bind a BPF program to a HID device, because
+it is unstable. Some drivers even disable it, which means tracing capabilies
+on those devices are also removed (it is interesting to get traces on such
+devices via other means).
 
-On the other hand, the ``hid_id`` is stable for the entire life of the HID device,
-even if we change its report descriptor.
+On the other hand, ``hid_id`` is stable for the entire life of the HID device,
+even if its report descriptor is changed.
 
-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.
+For the reason above, it is recommended to access the current report descriptor
+of the device through the sysfs, which 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.
+Parsing the report descriptor is the responsibility of BPF program authors or
+userspace components that load 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*
+*Foreword: for most parts, this example could be implemented as a kernel
+driver instead.*
 
-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.
+Let's imagine that we have a new tablet device that has haptic capabilities
+to simulate the surface the user is scratching on. For this to happen,
+the device have 3 positions switch to toggle between *pencil on paper*, *cray
+on a wall*, and *brush on a painting canvas*. To make things even better, the
+physical position of the switch can be controlled via feature report. Of
+course, the switch is relying on userspace to control the haptic feature of
+the device.
 
 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.
+The first step is filtering events from the device. Given that the switch
+position is actually reported in the form of the pen events, using hidraw to
+implement filtering would mean that we wake up userspace for every single
+event. This is OK for libinput, but having an external library that is only
+interested in one byte in the report is less than ideal.
 
-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::
+Based on the requirements above, we will write userspace BPF program, starting
+with the skeleton::
 
   #include "vmlinux.h"
   #include <bpf/bpf_helpers.h>
@@ -408,8 +413,8 @@ For that, we can create a basic skeleton for our BPF program::
 	return 0;
   }
 
-To attach ``filter_switch``, userspace needs to call the ``attach_prog`` syscall
-program first::
+To attach ``filter_switch``, the program needs to call ``attach_prog`` syscall
+first::
 
   static int attach_filter(struct hid *hid_skel, int hid_id)
   {
@@ -431,17 +436,17 @@ program first::
 	return err;
   }
 
-Our userspace program can now listen to notifications on the ring buffer, and
-is awaken only when the value changes.
+The program can now listen to notifications on the ring buffer. It is awaken
+only when the value changes.
 
 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.
+To be able to change haptic feedback from the tablet, the program needs to
+emit feature report on the device.
 
-Instead of using hidraw for that, we can create a ``SEC("syscall")`` program
-that talks to the device::
+Instead of using hidraw for the purpose, we can write a ``SEC("syscall")``
+routine 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;
@@ -480,7 +485,7 @@ that talks to the device::
 	return ret;
   }
 
-And then userspace needs to call that program directly::
+The program needs to call the routine directly::
 
   static int set_haptic(struct hid *hid_skel, int hid_id, __u8 haptic_value)
   {
@@ -503,11 +508,10 @@ And then userspace needs to call that program directly::
 	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).
+Now the 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
+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.
+The interesting bit here is that no new kernel API is created. This means
+if there is a bug in the BPF program, the interface with the kernel can be
+changed if desired, because the program is responsible for its own usage.

Thanks.
Jonathan Corbet Oct. 26, 2022, 1:44 p.m. UTC | #2
Bagas Sanjaya <bagasdotme@gmail.com> writes:

> The wordings are somewhat confusing, so here's the alternative:

I've been kind of ignoring these editorial comments of yours, but this
bit of bikeshedding kind of exceeded my threshold.

> diff --git a/Documentation/hid/hid-bpf.rst b/Documentation/hid/hid-bpf.rst
> index ba35aa2e2ba836..c59bce4b9cf214 100644
> --- a/Documentation/hid/hid-bpf.rst
> +++ b/Documentation/hid/hid-bpf.rst
> @@ -27,88 +27,89 @@ 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.
> +With HID-BPF, the filter can be applied in the kernel directly so that
> +userspace does not get woken up when nothing else is happening on the input
> +controller.

How does a shift to the passive voice help here?  What is the problem
you are trying to solve?

> -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.
> +Of course, given that dead zone filter is device-specific, it is not possible

                       ^
Missing article -------+

> +to 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 that
> +the 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.
> +HID-BPF allows the userspace program to load the program itself, ensuring that
> +custom API is only needed for edge cases (like esoteric joysticks)

That (beyond the missing article) changes the meaning of the sentence;
it no longer really makes sense.

I'll stop here.

Bagas, I've asked this several times: *please* stop trying to tell other
contributors what to do and, instead, focus on contributing something
useful yourself.  You seem to have the energy and interest to do
worthwhile stuff - please do that!

Thanks,

jon
diff mbox series

Patch

diff --git a/Documentation/hid/hid-bpf.rst b/Documentation/hid/hid-bpf.rst
new file mode 100644
index 000000000000..ba35aa2e2ba8
--- /dev/null
+++ b/Documentation/hid/hid-bpf.rst
@@ -0,0 +1,513 @@ 
+.. 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.
+
+Available types of programs
+===========================
+
+HID-BPF is built "on top" of BPF, meaning that we use tracing method to
+declare our programs.
+
+HID-BPF has the following attachment types available:
+
+1. event processing/filtering with ``SEC("fmod_ret/hid_bpf_device_event")`` in libbpf
+2. actions coming from userspace with ``SEC("syscall")`` in libbpf
+3. change of the report descriptor with ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` in libbpf
+
+A ``hid_bpf_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_bpf_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_bpf_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.
+
+Developer API:
+==============
+
+User API data structures available in programs:
+-----------------------------------------------
+
+.. kernel-doc:: include/uapi/linux/hid_bpf.h
+.. kernel-doc:: include/linux/hid_bpf.h
+
+Available tracing functions to attach a HID-BPF program:
+--------------------------------------------------------
+
+.. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
+   :functions: hid_bpf_device_event hid_bpf_rdesc_fixup
+
+Available API that can be used in all HID-BPF programs:
+-------------------------------------------------------
+
+.. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
+   :functions: hid_bpf_get_data
+
+Available API that can be used in syscall HID-BPF programs:
+-----------------------------------------------------------
+
+.. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
+   :functions: hid_bpf_attach_prog hid_bpf_hw_request 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_bpf_rdesc_fixup`, several eBPF
+programs can be attached to the same device.
+
+Unless ``HID_BPF_FLAG_INSERT_HEAD`` is added to the flags while attaching the
+program, the new program is appended at the end of the list.
+``HID_BPF_FLAG_INSERT_HEAD`` 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 ``HID_BPF_FLAG_INSERT_HEAD`` flag,
+only the most recently loaded one is actually the first in the list.
+
+``SEC("fmod_ret/hid_bpf_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
+:c:func:`hid_bpf_allocate_context` and must release it with :c:func:`hid_bpf_release_context`
+before returning.
+Once the context is retrieved, one can also request a pointer to kernel memory with
+:c:func:`hid_bpf_get_data`. This memory is big enough to support all input/output/feature
+reports of the given device.
+
+``SEC("fmod_ret/hid_bpf_rdesc_fixup")``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``hid_bpf_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 ``SEC("fmod_ret/hid_bpf_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 the ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` program 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 program 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 program in the bpffs, and we can then
+replace it through normal bpf operations.
+
+Attaching a bpf program to a device
+===================================
+
+``libbpf`` does not export any helper to attach a HID-BPF program.
+Users need to use a dedicated ``syscall`` program which will call
+``hid_bpf_attach_prog(hid_id, program_fd, flags)``.
+
+``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``)
+
+``progam_fd`` is the opened file descriptor of the program to attach.
+
+``flags`` 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 capabilies 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;
+  extern int hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, u32 flags) __ksym;
+
+  struct {
+	__uint(type, BPF_MAP_TYPE_RINGBUF);
+	__uint(max_entries, 4096 * 64);
+  } ringbuf SEC(".maps");
+
+  struct attach_prog_args {
+	int prog_fd;
+	unsigned int hid;
+	unsigned int flags;
+	int retval;
+  };
+
+  SEC("syscall")
+  int attach_prog(struct attach_prog_args *ctx)
+  {
+	ctx->retval = hid_bpf_attach_prog(ctx->hid,
+					  ctx->prog_fd,
+					  ctx->flags);
+	return 0;
+  }
+
+  __u8 current_value = 0;
+
+  SEC("?fmod_ret/hid_bpf_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;
+  }
+
+To attach ``filter_switch``, userspace needs to call the ``attach_prog`` syscall
+program first::
+
+  static int attach_filter(struct hid *hid_skel, int hid_id)
+  {
+	int err, prog_fd;
+	int ret = -1;
+	struct attach_prog_args args = {
+		.hid = hid_id,
+	};
+	DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
+		.ctx_in = &args,
+		.ctx_size_in = sizeof(args),
+	);
+
+	args.prog_fd = bpf_program__fd(hid_skel->progs.filter_switch);
+
+	prog_fd = bpf_program__fd(hid_skel->progs.attach_prog);
+
+	err = bpf_prog_test_run_opts(prog_fd, &tattrs);
+	return err;
+  }
+
+Our userspace program can now listen to notifications on the ring buffer, and
+is awaken only when the value changes.
+
+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.
diff --git a/Documentation/hid/index.rst b/Documentation/hid/index.rst
index e50f513c579c..b2028f382f11 100644
--- a/Documentation/hid/index.rst
+++ b/Documentation/hid/index.rst
@@ -11,6 +11,7 @@  Human Interface Devices (HID)
    hidraw
    hid-sensor
    hid-transport
+   hid-bpf
 
    uhid
 
diff --git a/MAINTAINERS b/MAINTAINERS
index b3e16057834a..2934d74e8f88 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9094,6 +9094,7 @@  M:	Benjamin Tissoires <benjamin.tissoires@redhat.com>
 L:	linux-input@vger.kernel.org
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git
+F:	Documentation/hid/
 F:	drivers/hid/
 F:	include/linux/hid*
 F:	include/uapi/linux/hid*