From c5958697a5fa29d3ba9332205a88725afe9ed912 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Sat, 8 Jun 2024 11:01:22 +0200 Subject: Documentation: HID: amend HID-BPF for struct_ops Now that we are using struct_ops, the docs need to be changed. Link: https://lore.kernel.org/r/20240608-hid_bpf_struct_ops-v3-10-6ac6ade58329@kernel.org Signed-off-by: Benjamin Tissoires --- Documentation/hid/hid-bpf.rst | 156 +++++++++++++++++++----------------------- 1 file changed, 72 insertions(+), 84 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hid/hid-bpf.rst b/Documentation/hid/hid-bpf.rst index 0765b3298ecf..456e15097d87 100644 --- a/Documentation/hid/hid-bpf.rst +++ b/Documentation/hid/hid-bpf.rst @@ -132,16 +132,17 @@ input events. Available types of programs =========================== -HID-BPF is built "on top" of BPF, meaning that we use tracing method to +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("fmod_ret/hid_bpf_device_event")`` in libbpf +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("fmod_ret/hid_bpf_rdesc_fixup")`` 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_bpf_device_event`` is calling a BPF program when an event is received from +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. @@ -149,37 +150,42 @@ 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 +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_bpf_rdesc_fixup`` +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: ============== -User API data structures available in programs: ------------------------------------------------ +Available ``struct_ops`` for HID-BPF: +------------------------------------- .. kernel-doc:: include/linux/hid_bpf.h + :identifiers: hid_bpf_ops -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 +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 programs: -------------------------------------------------------- +Available API that can be used in all HID-BPF struct_ops programs: +------------------------------------------------------------------ .. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c - :functions: hid_bpf_get_data + :identifiers: hid_bpf_get_data -Available API that can be used in syscall HID-BPF programs: ------------------------------------------------------------ +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 - :functions: hid_bpf_attach_prog hid_bpf_hw_request hid_bpf_hw_output_report hid_bpf_input_report hid_bpf_allocate_context hid_bpf_release_context + :identifiers: hid_bpf_hw_request hid_bpf_hw_output_report hid_bpf_input_report hid_bpf_allocate_context hid_bpf_release_context General overview of a HID-BPF program ===================================== @@ -222,20 +228,21 @@ This allows the following: 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 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 ``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. +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 ``HID_BPF_FLAG_INSERT_HEAD`` flag, +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("fmod_ret/hid_bpf_device_event")`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``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. @@ -258,17 +265,17 @@ with, userspace needs to refer to the device by its unique system id (the last 4 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` +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 -:c:func:`hid_bpf_get_data`. This memory is big enough to support all input/output/feature +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")`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``SEC("struct_ops/hid_rdesc_fixup")`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``hid_bpf_rdesc_fixup`` program works in a similar manner to -``.report_fixup`` of ``struct hid_driver``. +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 @@ -277,33 +284,31 @@ content of the report descriptor. The memory associated with that buffer is 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. +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 the ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` program is -detached, the kernel issues a disconnect on the device. +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 program are closed. +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 program in the bpffs, and we can then +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 =================================== -``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)``. +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``) -``progam_fd`` is the opened file descriptor of the program to attach. - -``flags`` is of type ``enum hid_bpf_attach_flags``. +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 @@ -358,32 +363,15 @@ For that, we can create a basic skeleton for our BPF program:: 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") + 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 */); @@ -407,37 +395,37 @@ 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:: + 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, 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), - ); + int err, link_fd; - args.prog_fd = bpf_program__fd(hid_skel->progs.filter_switch); + hid_skel->struct_ops.haptic_tablet->hid_id = hid_id; + err = hid__load(skel); + if (err) + return err; - prog_fd = bpf_program__fd(hid_skel->progs.attach_prog); - - err = bpf_prog_test_run_opts(prog_fd, &tattrs); - 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 args.retval; /* the fd of the created bpf_link */ + 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 fd from :c:func:`attach_filter`, which will tell the kernel to +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 -- cgit v1.2.3 From 5f42e19de53faf9e6d4455638f75b7c3a3f8d58f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Sat, 8 Jun 2024 11:01:23 +0200 Subject: Documentation: HID: add a small blurb on udev-hid-bpf This is the current decision we took: we don't provide automatic loading of HID-BPF by the kernel directly, but rely on an external tool for it. This tool is currently udev-hid-bpf, so let's make people aware of it. Link: https://lore.kernel.org/r/20240608-hid_bpf_struct_ops-v3-11-6ac6ade58329@kernel.org Signed-off-by: Benjamin Tissoires --- Documentation/hid/hid-bpf.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'Documentation') diff --git a/Documentation/hid/hid-bpf.rst b/Documentation/hid/hid-bpf.rst index 456e15097d87..8ae8f49801cb 100644 --- a/Documentation/hid/hid-bpf.rst +++ b/Documentation/hid/hid-bpf.rst @@ -129,6 +129,23 @@ 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 `_. + +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 =========================== -- cgit v1.2.3 From 9acbb7ba4589d4715141d4e14230a828ddc95f3d Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 26 Jun 2024 15:46:32 +0200 Subject: HID: bpf: allow hid_device_event hooks to inject input reports on self This is the same logic than hid_hw_raw_request or hid_hw_output_report: we can allow hid_bpf_try_input_report to be called from a hook on hid_input_report if we ensure that the call can not be made twice in a row. There is one extra subtlety in which there is a lock in hid_input_report. But given that we can detect if we are already in the hook, we can notify hid_input_report to not take the lock. This is done by checking if ctx_kern data is valid or null, and if it is equal to the dedicated incoming data buffer. In order to have more control on whether the lock needs to be taken or not we introduce a new kfunc for it: hid_bpf_try_input_report() Link: https://patch.msgid.link/20240626-hid_hw_req_bpf-v2-11-cfd60fb6c79f@kernel.org Acked-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- Documentation/hid/hid-bpf.rst | 2 +- drivers/hid/bpf/hid_bpf_dispatch.c | 56 ++++++++++++++++++++++++++++++++------ drivers/hid/hid-core.c | 5 ++-- include/linux/hid_bpf.h | 6 ++-- 4 files changed, 55 insertions(+), 14 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hid/hid-bpf.rst b/Documentation/hid/hid-bpf.rst index 8ae8f49801cb..5939eeafb361 100644 --- a/Documentation/hid/hid-bpf.rst +++ b/Documentation/hid/hid-bpf.rst @@ -202,7 +202,7 @@ Available API that can be used in syscall HID-BPF programs or in sleepable HID-B ------------------------------------------------------------------------------------------------------- .. 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_allocate_context hid_bpf_release_context + :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 ===================================== diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index 5174dc6d9d18..23daf94510bb 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -24,7 +24,7 @@ EXPORT_SYMBOL(hid_ops); u8 * dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type, u8 *data, - u32 *size, int interrupt, u64 source) + u32 *size, int interrupt, u64 source, bool from_bpf) { struct hid_bpf_ctx_kern ctx_kern = { .ctx = { @@ -33,6 +33,7 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type .size = *size, }, .data = hdev->bpf.device_data, + .from_bpf = from_bpf, }; struct hid_bpf_ops *e; int ret; @@ -488,6 +489,50 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz) return ret; } +static int +__hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf, + size_t size, bool lock_already_taken) +{ + struct hid_bpf_ctx_kern *ctx_kern; + int ret; + + ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx); + if (ctx_kern->from_bpf) + return -EDEADLOCK; + + /* check arguments */ + ret = __hid_bpf_hw_check_params(ctx, buf, &size, type); + if (ret) + return ret; + + return hid_ops->hid_input_report(ctx->hid, type, buf, size, 0, (__u64)ctx, true, + lock_already_taken); +} + +/** + * hid_bpf_try_input_report - Inject a HID report in the kernel from a HID device + * + * @ctx: the HID-BPF context previously allocated in hid_bpf_allocate_context() + * @type: the type of the report (%HID_INPUT_REPORT, %HID_FEATURE_REPORT, %HID_OUTPUT_REPORT) + * @buf: a %PTR_TO_MEM buffer + * @buf__sz: the size of the data to transfer + * + * Returns %0 on success, a negative error code otherwise. This function will immediately + * fail if the device is not available, thus can be safely used in IRQ context. + */ +__bpf_kfunc int +hid_bpf_try_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf, + const size_t buf__sz) +{ + struct hid_bpf_ctx_kern *ctx_kern; + bool from_hid_event_hook; + + ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx); + from_hid_event_hook = ctx_kern->data && ctx_kern->data == ctx->hid->bpf.device_data; + + return __hid_bpf_input_report(ctx, type, buf, buf__sz, from_hid_event_hook); +} + /** * hid_bpf_input_report - Inject a HID report in the kernel from a HID device * @@ -504,7 +549,6 @@ __bpf_kfunc int hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf, const size_t buf__sz) { - size_t size = buf__sz; int ret; ret = down_interruptible(&ctx->hid->driver_input_lock); @@ -512,12 +556,7 @@ hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf return ret; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, type); - if (ret) - return ret; - - ret = hid_ops->hid_input_report(ctx->hid, type, buf, size, 0, (__u64)ctx, - true /* lock_already_taken */); + ret = __hid_bpf_input_report(ctx, type, buf, buf__sz, true /* lock_already_taken */); up(&ctx->hid->driver_input_lock); @@ -536,6 +575,7 @@ BTF_ID_FLAGS(func, hid_bpf_release_context, KF_RELEASE | KF_SLEEPABLE) BTF_ID_FLAGS(func, hid_bpf_hw_request, KF_SLEEPABLE) BTF_ID_FLAGS(func, hid_bpf_hw_output_report, KF_SLEEPABLE) BTF_ID_FLAGS(func, hid_bpf_input_report, KF_SLEEPABLE) +BTF_ID_FLAGS(func, hid_bpf_try_input_report) BTF_KFUNCS_END(hid_bpf_kfunc_ids) static const struct btf_kfunc_id_set hid_bpf_kfunc_set = { diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index e9b5f44683fd..52a75afe3e7d 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2027,7 +2027,7 @@ EXPORT_SYMBOL_GPL(hid_report_raw_event); static int __hid_input_report(struct hid_device *hid, enum hid_report_type type, - u8 *data, u32 size, int interrupt, u64 source, + u8 *data, u32 size, int interrupt, u64 source, bool from_bpf, bool lock_already_taken) { struct hid_report_enum *report_enum; @@ -2053,7 +2053,7 @@ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type, report_enum = hid->report_enum + type; hdrv = hid->driver; - data = dispatch_hid_bpf_device_event(hid, type, data, &size, interrupt, source); + data = dispatch_hid_bpf_device_event(hid, type, data, &size, interrupt, source, from_bpf); if (IS_ERR(data)) { ret = PTR_ERR(data); goto unlock; @@ -2105,6 +2105,7 @@ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data int interrupt) { return __hid_input_report(hid, type, data, size, interrupt, 0, + false, /* from_bpf */ false /* lock_already_taken */); } EXPORT_SYMBOL_GPL(hid_input_report); diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index 7f04353d09e9..93546ee7677a 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -72,7 +72,7 @@ struct hid_ops { int (*hid_hw_output_report)(struct hid_device *hdev, __u8 *buf, size_t len, __u64 source, bool from_bpf); int (*hid_input_report)(struct hid_device *hid, enum hid_report_type type, - u8 *data, u32 size, int interrupt, u64 source, + u8 *data, u32 size, int interrupt, u64 source, bool from_bpf, bool lock_already_taken); struct module *owner; const struct bus_type *bus_type; @@ -195,7 +195,7 @@ struct hid_bpf { #ifdef CONFIG_HID_BPF u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type, u8 *data, - u32 *size, int interrupt, u64 source); + u32 *size, int interrupt, u64 source, bool from_bpf); int dispatch_hid_bpf_raw_requests(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, u32 size, enum hid_report_type rtype, @@ -211,7 +211,7 @@ u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *s #else /* CONFIG_HID_BPF */ static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 *size, int interrupt, - u64 source) { return data; } + u64 source, bool from_bpf) { return data; } static inline int dispatch_hid_bpf_raw_requests(struct hid_device *hdev, unsigned char reportnum, u8 *buf, u32 size, enum hid_report_type rtype, -- cgit v1.2.3