Commit c5958697 authored by Benjamin Tissoires's avatar Benjamin Tissoires

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.orgSigned-off-by: default avatarBenjamin Tissoires <bentiss@kernel.org>
parent 05b3b8f1
...@@ -132,16 +132,17 @@ input events. ...@@ -132,16 +132,17 @@ input events.
Available types of programs 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. declare our programs.
HID-BPF has the following attachment types available: 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 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. 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. 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. ...@@ -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 This time, we can do any operations allowed by HID-BPF, and talking to the device is
allowed. 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 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 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. 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: Developer API:
============== ==============
User API data structures available in programs: Available ``struct_ops`` for HID-BPF:
----------------------------------------------- -------------------------------------
.. kernel-doc:: include/linux/hid_bpf.h .. 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 User API data structures available in programs:
:functions: hid_bpf_device_event hid_bpf_rdesc_fixup -----------------------------------------------
.. 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 .. 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 .. 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 General overview of a HID-BPF program
===================================== =====================================
...@@ -222,20 +228,21 @@ This allows the following: ...@@ -222,20 +228,21 @@ This allows the following:
Effect of a HID-BPF program Effect of a HID-BPF program
--------------------------- ---------------------------
For all HID-BPF attachment types except for :c:func:`hid_bpf_rdesc_fixup`, several eBPF For all HID-BPF attachment types except for :c:func:`hid_rdesc_fixup`, several eBPF
programs can be attached to the same device. 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 Unless ``BPF_F_BEFORE`` is added to the flags while attaching the program, the new
program, the new program is appended at the end of the list. program is appended at the end of the list.
``HID_BPF_FLAG_INSERT_HEAD`` will insert the new program at the beginning of the ``BPF_F_BEFORE`` will insert the new program at the beginning of the list which is
list which is useful for e.g. tracing where we need to get the unprocessed events useful for e.g. tracing where we need to get the unprocessed events from the device.
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. 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 Whenever a matching event is raised, the eBPF programs are called one after the other
and are working on the same data buffer. 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 ...@@ -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``). in the sysfs path: ``/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000``).
To retrieve a context associated with the device, the program must call 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. before returning.
Once the context is retrieved, one can also request a pointer to kernel memory with 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. 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 The ``hid_rdesc_fixup`` program works in a similar manner to ``.report_fixup``
``.report_fixup`` of ``struct hid_driver``. of ``struct hid_driver``.
When the device is probed, the kernel sets the data buffer of the context with the 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 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 ...@@ -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 The eBPF program can modify the data buffer at-will and the kernel uses the
modified content and size as the report descriptor. modified content and size as the report descriptor.
Whenever a ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` program is attached (if no Whenever a struct_ops containing a ``SEC("struct_ops/hid_rdesc_fixup")`` program
program was attached before), the kernel immediately disconnects the HID device is attached (if no program was attached before), the kernel immediately disconnects
and does a reprobe. the HID device and does a reprobe.
In the same way, when the ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` program is In the same way, when this struct_ops is detached, the kernel issues a disconnect
detached, the kernel issues a disconnect on the device. on the device.
There is no ``detach`` facility in HID-BPF. Detaching a program happens when 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 Thus, if we need to replace a report descriptor fixup, some cooperation is
required from the owner of the original report descriptor fixup. 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. replace it through normal bpf operations.
Attaching a bpf program to a device Attaching a bpf program to a device
=================================== ===================================
``libbpf`` does not export any helper to attach a HID-BPF program. We now use standard struct_ops attachment through ``bpf_map__attach_struct_ops()``.
Users need to use a dedicated ``syscall`` program which will call But given that we need to attach a struct_ops to a dedicated HID device, the caller
``hid_bpf_attach_prog(hid_id, program_fd, flags)``. 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 ``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``) sysfs path: ``/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000``)
``progam_fd`` is the opened file descriptor of the program to attach. One can also set ``flags``, which is of type ``enum hid_bpf_attach_flags``.
``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 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 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:: ...@@ -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, extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
unsigned int offset, unsigned int offset,
const size_t __sz) __ksym; const size_t __sz) __ksym;
extern int hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, u32 flags) __ksym;
struct { struct {
__uint(type, BPF_MAP_TYPE_RINGBUF); __uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 4096 * 64); __uint(max_entries, 4096 * 64);
} ringbuf SEC(".maps"); } 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; __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) int BPF_PROG(filter_switch, struct hid_bpf_ctx *hid_ctx)
{ {
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 192 /* size */); __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:: ...@@ -407,37 +395,37 @@ For that, we can create a basic skeleton for our BPF program::
return 0; return 0;
} }
To attach ``filter_switch``, userspace needs to call the ``attach_prog`` syscall SEC(".struct_ops.link")
program first:: struct hid_bpf_ops haptic_tablet = {
.hid_device_event = (void *)filter_switch,
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); To attach ``haptic_tablet``, userspace needs to set ``hid_id`` first::
err = bpf_prog_test_run_opts(prog_fd, &tattrs); static int attach_filter(struct hid *hid_skel, int hid_id)
{
int err, link_fd;
hid_skel->struct_ops.haptic_tablet->hid_id = hid_id;
err = hid__load(skel);
if (err) if (err)
return err; return err;
return args.retval; /* the fd of the created bpf_link */ link_fd = bpf_map__attach_struct_ops(hid_skel->maps.haptic_tablet);
if (!link_fd) {
fprintf(stderr, "can not attach HID-BPF program: %m\n");
return -1;
}
return link_fd; /* the fd of the created bpf_link */
} }
Our userspace program can now listen to notifications on the ring buffer, and Our userspace program can now listen to notifications on the ring buffer, and
is awaken only when the value changes. is awaken only when the value changes.
When the userspace program doesn't need to listen to events anymore, it can just 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. detach the program from the HID device.
Of course, in other use cases, the userspace program can also pin the fd to the Of course, in other use cases, the userspace program can also pin the fd to the
......
...@@ -20,11 +20,9 @@ struct hid_device; ...@@ -20,11 +20,9 @@ struct hid_device;
* struct hid_bpf_ctx - User accessible data for all HID programs * struct hid_bpf_ctx - User accessible data for all HID programs
* *
* ``data`` is not directly accessible from the context. We need to issue * ``data`` is not directly accessible from the context. We need to issue
* a call to ``hid_bpf_get_data()`` in order to get a pointer to that field. * a call to hid_bpf_get_data() in order to get a pointer to that field.
* *
* All of these fields are currently read-only. * @hid: the &struct hid_device representing the device itself
*
* @hid: the ``struct hid_device`` representing the device itself
* @allocated_size: Allocated size of data. * @allocated_size: Allocated size of data.
* *
* This is how much memory is available and can be requested * This is how much memory is available and can be requested
...@@ -41,6 +39,8 @@ struct hid_device; ...@@ -41,6 +39,8 @@ struct hid_device;
* ``size`` must always be less or equal than ``allocated_size`` (it is enforced * ``size`` must always be less or equal than ``allocated_size`` (it is enforced
* once all BPF programs have been run). * once all BPF programs have been run).
* @retval: Return value of the previous program. * @retval: Return value of the previous program.
*
* ``hid`` and ``allocated_size`` are read-only, ``size`` and ``retval`` are read-write.
*/ */
struct hid_bpf_ctx { struct hid_bpf_ctx {
const struct hid_device *hid; const struct hid_device *hid;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment