← Documents Documentation/hid/hid-bpf.rst GitHub 원문 ↗

Linux 6.18.37 · HID

HID-BPF

eBPF로 HID event·report descriptor·device request를 확장하는 구조와 API를 설명합니다.

Source pathDocumentation/hid/hid-bpf.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약·해설

hid-bpf.rst:1-527

HID-BPF는 byte-array 기반 eBPF hook으로 device event filter, userspace syscall action과 report descriptor fixup을 구현합니다.

문서 위치
항목
SourceDocumentation/hid/hid-bpf.rst
분량527 source lines
Contextstruct hid_bpf_ctx
핵심 APIhid_bpf_get_data · hid_bpf_hw_request

Source와 핵심 API입니다.

HID-BPF 구성
Userspace가 report descriptor parsingBPF object와 target hid_id 준비Struct_ops 또는 syscall program loadKernel HID data를 filter·fixup·request기존 HID stack이 input event 처리

Userspace parser·loader와 kernel HID processing이 역할을 나눕니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

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

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

HID-BPF 개요

1-16

HID는 input device를 위한 표준 protocol이지만 일부 device는 custom 조정이 필요합니다. 전통적으로는 kernel driver 수정으로 처리했습니다.

대신 eBPF capability를 사용하면 개발 속도를 높이고 기존 HID interface에 새 기능을 추가할 수 있습니다.

이 문서는 local contents를 depth 2까지 제공하며 HID-BPF를 사용할 이유, program type, developer API, attach 방식과 완전한 예제를 순서대로 설명합니다.

HID 수정 방식
방식특징
Kernel driver fixKernel patch·release 반영 필요
HID-BPFUserspace loader·빠른 배포·기존 HID 경로 확장

전통적인 kernel patch와 HID-BPF의 차이입니다.

HID-BPF 적용
HID device와 report format 분석eBPF program 작성Kernel verifier 검증Device에 struct_ops attachHID event·descriptor·request 처리

Device별 조정을 eBPF program으로 HID path에 연결합니다.

.. 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

HID-BPF를 사용하는 이유와 사례

17-112

표준 kernel driver 수정보다 HID-BPF가 나은 몇 가지 use case가 있습니다.

오래된 joystick은 neutral point 주변에서 흔들릴 수 있으며 application은 보통 특정 axis에 dead zone을 둡니다. HID-BPF로 kernel에서 직접 filter하면 controller의 다른 동작이 없을 때 userspace를 깨우지 않아도 됩니다.

Dead zone은 개별 device 특성이므로 같은 model 전체에 generic fix를 적용할 수 없습니다. Sysfs entry 같은 custom kernel API를 추가해도 널리 채택·유지된다는 보장이 없습니다. HID-BPF는 실제 consumer가 있을 때만 userspace program이 custom API를 load하게 합니다.

HID tree driver의 절반가량은 report descriptor의 key 하나나 byte 하나만 수정합니다. 이런 작은 수정도 kernel patch와 release 반영을 기다려야 해 user에게 길고 힘든 과정입니다. 검증된 eBPF program을 제공하면 부담을 줄일 수 있습니다.

검증 뒤 source를 kernel tree에 넣고 특정 kernel module 대신 eBPF program을 배포·직접 load할 수 있습니다. 다만 eBPF program 배포와 kernel 포함 절차는 아직 완전히 구현되지 않았습니다.

새 kernel API가 필요한 기능의 예는 Universal Stylus Interface(USI) pen입니다. USI는 기존 HID·input stack이 지원하지 않는 새 communication channel이 필요합니다. Hidraw, 새 sysfs entry 또는 ioctl 대신 eBPF를 사용하면 consumer가 kernel API를 통제하고 매 event마다 userspace를 깨우는 성능 비용도 피할 수 있습니다.

Kernel은 HID item을 evdev bit에 비교적 고정적으로 mapping합니다. 필요한 context가 없어 device를 다른 형태로 동적으로 바꾸기 어렵고, 한번 바꾼 변환을 userspace가 되돌리거나 발견할 수도 없습니다.

Microsoft Surface Dial처럼 haptic feedback이 있는 pushbutton은 static mapping만으로 거의 쓸 수 없습니다. Userspace는 eBPF로 이를 mouse로 바꾸고 dial event를 wheel event로 변환할 수 있습니다. Context에 따라 haptic feedback도 설정·해제할 수 있습니다.

화면에 menu가 보이면 15도마다 haptic click이 필요할 수 있지만 web page를 scroll할 때는 최고 resolution으로 event를 내는 편이 낫습니다. HID-BPF는 이 context-dependent 동작을 userspace가 제어하게 합니다.

Firewall 용도로는 broken firmware update entry point 같은 device 기능에 다른 user가 접근하지 못하게 할 수 있습니다. Device로 향하는 모든 HID command를 가로채 허용 여부를 검증하고, incoming command도 intercept하여 userspace와 kernel/BPF program의 state를 동기화할 수 있습니다.

Tracing에서는 event를 요약·분석하는 BPF 기능을 활용합니다. 현재 hidraw 기반 tracing은 driver가 hidraw node를 내보내지 않으면 아무것도 볼 수 없고, 다른 process가 device에 보낸 request를 잡지 못해 kernel에 `printk`를 추가해야 하는 경우가 있습니다. eBPF는 hidraw가 없는 device까지 볼 수 있는 강한 권한을 제공하므로 보안 검토도 필요합니다.

HID-BPF use case
사례HID-BPF 효과
Joystick dead zoneKernel filter로 불필요한 userspace wakeup 방지
Descriptor fixup작은 수정의 patch·release 지연 축소
USI penConsumer-controlled 새 communication API
Device morphingSurface Dial을 mouse wheel 등으로 변환
FirewallHID command 검증·state 동기화
TracingHidraw 밖의 event·request 관찰

Device별 수정과 관찰을 kernel patch 없이 수행합니다.

Context-aware device morphing
Raw HID item 수신BPF가 dial 값을 wheel event 형식으로 변환Kernel HID stack이 input event 생성Userspace가 menu·scroll context 판단Haptic feature report를 device에 전송

Userspace context를 HID event와 feedback에 반영합니다.

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.

Byte-array 모델과 in-tree program 배포

113-148

HID-BPF의 핵심은 byte array 수준에서 동작한다는 점입니다. HID report와 report descriptor parsing은 eBPF program을 load하는 userspace component가 구현해야 합니다.

Joystick dead zone 예에서는 data stream의 어느 field를 `0`으로 만들지 userspace가 계산합니다.

이 모델의 결과로 HID-BPF는 kernel의 다른 subsystem을 알지 못합니다. eBPF에서 input API를 통해 input event를 직접 emit할 수 없습니다.

BPF program이 input event를 내야 하면 HID protocol로 통신하고, HID kernel processing이 HID data를 input event로 변환하도록 해야 합니다.

공식 device fix는 kernel tree의 `drivers/hid/bpf/progs`에 source로 들어갑니다. 그러면 `tools/testing/selftests/hid`에 selftest를 추가할 수 있습니다.

이 object들은 외부 loader가 필요하므로 regular kernel build에는 compile되지 않습니다. 현재 loader는 `udev-hid-bpf`이며 문서는 `https://libevdev.pages.freedesktop.org/udev-hid-bpf/index.html`에 있습니다.

외부 repository는 배포 편의를 위해 kernel의 `drivers/hid/bpf/progs` file을 자체 `src/bpf/stable` directory에 복제합니다. Distribution은 전체 kernel source를 가져오지 않고 HID-BPF fix를 package할 수 있고, `udev-hid-bpf`는 실행 중인 kernel에 따라 여러 object file을 처리할 수 있습니다.

HID-BPF 배포 구조
구성위치·책임
Byte parsingUserspace loader component
Official programsdrivers/hid/bpf/progs
Selfteststools/testing/selftests/hid
Loaderudev-hid-bpf
Stable package copysrc/bpf/stable

Source·selftest·loader의 위치입니다.

In-tree fix 배포
Fix source를 drivers/hid/bpf/progs에 추가HID selftest 작성udev-hid-bpf stable tree에 복제Distribution package 설치실행 kernel에 맞는 object 선택·load

Kernel source의 program을 external loader가 device에 적용합니다.

High-level view of HID-BPF
==========================

The main idea behind HID-BPF is that it works at an array of bytes level.
Thus, all of the parsing of the HID report and the HID report descriptor
must be implemented in the userspace component that loads the eBPF
program.

For example, in the dead zone joystick from above, knowing which fields
in the data stream needs to be set to ``0`` needs to be computed by userspace.

A corollary of this is that HID-BPF doesn't know about the other subsystems
available in the kernel. *You can not directly emit input event through the
input API from eBPF*.

When a BPF program needs to emit input events, it needs to talk with the HID
protocol, and rely on the HID kernel processing to translate the HID data into
input events.

In-tree HID-BPF programs and ``udev-hid-bpf``
=============================================

Official device fixes are shipped in the kernel tree as source in the
``drivers/hid/bpf/progs`` directory. This allows to add selftests to them in
``tools/testing/selftests/hid``.

However, the compilation of these objects is not part of a regular kernel compilation
given that they need an external tool to be loaded. This tool is currently
`udev-hid-bpf <https://libevdev.pages.freedesktop.org/udev-hid-bpf/index.html>`_.

For convenience, that external repository duplicates the files from here in
``drivers/hid/bpf/progs`` into its own ``src/bpf/stable`` directory. This allows
distributions to not have to pull the entire kernel source tree to ship and package
those HID-BPF fixes. ``udev-hid-bpf`` also has capabilities of handling multiple
objects files depending on the kernel the user is running.

Program type과 developer API

149-209

HID-BPF는 BPF 위에서 `bpf struct_ops` method로 program을 선언합니다.

Attachment type은 세 가지입니다. Event processing/filtering은 libbpf의 `SEC("struct_ops/hid_device_event")`, userspace action은 `SEC("syscall")`, report descriptor 변경은 `SEC("struct_ops/hid_rdesc_fixup")` 또는 sleepable `SEC("struct_ops.s/hid_rdesc_fixup")`을 사용합니다.

`hid_device_event`는 device event 수신 때 BPF program을 호출합니다. IRQ context이므로 data를 바꾸거나 userspace에 알릴 수 있지만 device에 다시 통신할 수는 없습니다.

`syscall`은 userspace가 `BPF_PROG_RUN` facility를 호출한 경우입니다. HID-BPF가 허용하는 모든 operation을 수행할 수 있고 device와 통신할 수 있습니다.

`hid_rdesc_fixup`은 다른 type과 달리 하나만 attach할 수 있습니다. Driver probe 때 호출되어 report descriptor를 BPF program에서 바꿉니다. 이미 load된 program은 이를 넣은 program이 pinning하고 자신을 가리키는 모든 fd를 닫아 교체를 허용하지 않는 한 덮어쓸 수 없습니다. 이 hook은 sleepable로 선언할 수 있습니다.

HID-BPF용 struct_ops는 `include/linux/hid_bpf.h`의 `hid_bpf_ops`, userspace API context는 같은 header의 `hid_bpf_ctx`로 문서화됩니다.

모든 HID-BPF struct_ops program에서 쓸 수 있는 API는 `drivers/hid/bpf/hid_bpf_dispatch.c`의 `hid_bpf_get_data`입니다.

Syscall program 또는 sleepable struct_ops program에서는 `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`를 사용할 수 있습니다.

HID-BPF attachment type
Type호출 시점Device 통신
struct_ops/hid_device_eventDevice event·IRQ context불가
syscallUserspace BPF_PROG_RUN가능
struct_ops/hid_rdesc_fixupDriver probeDescriptor 변경
struct_ops.s/hid_rdesc_fixupSleepable probe hookSleepable API 가능

Context와 허용 operation을 구분합니다.

API 선택
Program attachment type 결정hid_bpf_ctx 수신 또는 allocatehid_bpf_get_data로 buffer 접근Sleepable/syscall이면 hardware request 가능할당 context는 release

Hook context에 따라 사용할 kfunc가 달라집니다.

Available types of programs
===========================

HID-BPF is built "on top" of BPF, meaning that we use bpf struct_ops method to
declare our programs.

HID-BPF has the following attachment types available:

1. event processing/filtering with ``SEC("struct_ops/hid_device_event")`` in libbpf
2. actions coming from userspace with ``SEC("syscall")`` in libbpf
3. change of the report descriptor with ``SEC("struct_ops/hid_rdesc_fixup")`` or
   ``SEC("struct_ops.s/hid_rdesc_fixup")`` in libbpf

A ``hid_device_event`` is calling a BPF program when an event is received from
the device. Thus we are in IRQ context and can act on the data or notify userspace.
And given that we are in IRQ context, we can not talk back to the device.

A ``syscall`` means that userspace called the syscall ``BPF_PROG_RUN`` facility.
This time, we can do any operations allowed by HID-BPF, and talking to the device is
allowed.

Last, ``hid_rdesc_fixup`` is different from the others as there can be only one
BPF program of this type. This is called on ``probe`` from the driver and allows to
change the report descriptor from the BPF program. Once a ``hid_rdesc_fixup``
program has been loaded, it is not possible to overwrite it unless the program which
inserted it allows us by pinning the program and closing all of its fds pointing to it.

Note that ``hid_rdesc_fixup`` can be declared as sleepable (``SEC("struct_ops.s/hid_rdesc_fixup")``).


Developer API:
==============

Available ``struct_ops`` for HID-BPF:
-------------------------------------

.. kernel-doc:: include/linux/hid_bpf.h
   :identifiers: hid_bpf_ops


User API data structures available in programs:
-----------------------------------------------

.. kernel-doc:: include/linux/hid_bpf.h
   :identifiers: hid_bpf_ctx

Available API that can be used in all HID-BPF struct_ops programs:
------------------------------------------------------------------

.. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
   :identifiers: hid_bpf_get_data

Available API that can be used in syscall HID-BPF programs or in sleepable HID-BPF struct_ops programs:
-------------------------------------------------------------------------------------------------------

.. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
   :identifiers: hid_bpf_hw_request hid_bpf_hw_output_report hid_bpf_input_report hid_bpf_try_input_report hid_bpf_allocate_context hid_bpf_release_context

General overview of a HID-BPF program
=====================================

Context data 접근과 program chain 효과

210-260

`struct hid_bpf_ctx`는 `data` field를 직접 export하지 않습니다. BPF program은 먼저 `hid_bpf_get_data()`를 호출해야 합니다. `offset`은 임의 integer일 수 있지만 `size`는 compile time에 알려진 constant여야 합니다.

특정 device의 report 길이가 항상 일정하다고 알면 전체 길이를 요청할 수 있습니다. 예시는 `hid_bpf_get_data(ctx, 0, 256)`으로 256byte pointer를 얻고 null을 확인합니다. Kernel은 size·offset이 올바른지 확인하고 eBPF verifier는 code가 boundary 밖을 읽거나 쓰지 못하게 합니다. 그 뒤 `data[0]`, `data[128]`, `data[255]`를 안전하게 참조할 수 있습니다.

Report 길이가 가변이어도 X 값이 항상 16bit integer임을 알면 `hid_bpf_get_data(ctx, offset, sizeof(*x))`로 그 값만 가리킬 수 있습니다. Null을 확인한 뒤 `*x += 1`처럼 수정합니다.

`hid_rdesc_fixup`을 제외한 attachment type은 한 device에 여러 program을 붙일 수 있습니다. 이미 report fixup이 있는데 다른 struct_ops에 또 `hid_rdesc_fixup`이 있으면 attach할 때 kernel이 `-EINVAL`을 반환합니다.

Attach flag에 `BPF_F_BEFORE`가 없으면 새 program은 list 끝에 append됩니다. `BPF_F_BEFORE`는 list 처음에 넣으므로 가공 전 device event를 봐야 하는 tracing에 유용합니다. 이 flag를 여러 program이 쓰면 가장 최근에 load된 program만 실제 첫 번째가 됩니다.

hid_bpf_get_data 계약
항목규칙
Contextstruct hid_bpf_ctx
Offset임의 integer
SizeCompile-time constant
FailureNULL 반환 후 즉시 처리
SafetyKernel size check + eBPF bounds verification

Verifier가 알 수 있는 고정 범위만 요청합니다.

Event program chain
BPF_F_BEFORE program을 앞에 배치기존 program list 순서로 실행각 program이 같은 context data를 읽고 수정후속 program은 수정된 data 관찰마지막 context size를 HID stack에 전달

Attach 순서에 따라 같은 buffer를 연속 처리합니다.

Accessing the data attached to the context
------------------------------------------

The ``struct hid_bpf_ctx`` doesn't export the ``data`` fields directly and to access
it, a bpf program needs to first call :c:func:`hid_bpf_get_data`.

``offset`` can be any integer, but ``size`` needs to be constant, known at compile
time.

This allows the following:

1. for a given device, if we know that the report length will always be of a certain value,
   we can request the ``data`` pointer to point at the full report length.

   The kernel will ensure we are using a correct size and offset and eBPF will ensure
   the code will not attempt to read or write outside of the boundaries::

     __u8 *data = hid_bpf_get_data(ctx, 0 /* offset */, 256 /* size */);

     if (!data)
         return 0; /* ensure data is correct, now the verifier knows we
                    * have 256 bytes available */

     bpf_printk("hello world: %02x %02x %02x", data[0], data[128], data[255]);

2. if the report length is variable, but we know the value of ``X`` is always a 16-bit
   integer, we can then have a pointer to that value only::

      __u16 *x = hid_bpf_get_data(ctx, offset, sizeof(*x));

      if (!x)
          return 0; /* something went wrong */

      *x += 1; /* increment X by one */

Effect of a HID-BPF program
---------------------------

For all HID-BPF attachment types except for :c:func:`hid_rdesc_fixup`, several eBPF
programs can be attached to the same device. If a HID-BPF struct_ops has a
:c:func:`hid_rdesc_fixup` while another is already attached to the device, the
kernel will return `-EINVAL` when attaching the struct_ops.

Unless ``BPF_F_BEFORE`` is added to the flags while attaching the program, the new
program is appended at the end of the list.
``BPF_F_BEFORE`` will insert the new program at the beginning of the list which is
useful for e.g. tracing where we need to get the unprocessed events from the device.

Note that if there are multiple programs using the ``BPF_F_BEFORE`` flag,
only the most recently loaded one is actually the first in the list.

Event·syscall·report descriptor hook

261-317

`SEC("struct_ops/hid_device_event")`에서는 matching event마다 eBPF program이 차례로 호출되고 모두 같은 data buffer를 사용합니다. 한 program이 context data를 바꾸면 다음 program은 수정된 값을 보지만 원래 값이 무엇이었는지는 알 수 없습니다.

모든 program이 0 또는 양수를 반환하면 나머지 HID stack은 수정된 data를 처리합니다. 마지막 `hid_bpf_ctx`의 `size` field가 input stream의 새 길이입니다. Negative error를 반환하면 event가 폐기되어 HID stack과 hidraw, input, LED client 모두 보지 못합니다.

`SEC("syscall")` program은 특정 device에 attach되지 않습니다. Userspace는 sysfs path `/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000`의 마지막 네 숫자인 unique system ID로 device를 지정해야 합니다.

Program은 `hid_bpf_allocate_context()`로 device context를 얻고 return 전에 `hid_bpf_release_context()`로 반드시 해제합니다. Context를 얻은 뒤 `hid_bpf_get_data()`로 kernel memory pointer를 요청할 수 있으며, 이 memory는 해당 device의 모든 input/output/feature report를 담을 만큼 큽니다.

`SEC("struct_ops/hid_rdesc_fixup")`은 `struct hid_driver`의 `.report_fixup`과 비슷합니다. Probe 때 kernel이 context data buffer에 report descriptor를 넣고 buffer memory는 현재 4KiB인 `HID_MAX_DESCRIPTOR_SIZE`입니다.

eBPF program은 data buffer를 자유롭게 수정하고 kernel은 변경된 content와 size를 report descriptor로 사용합니다. Fixup program을 가진 struct_ops가 처음 attach되면 kernel이 HID device를 즉시 disconnect하고 reprobe합니다. Detach 때도 device disconnect가 발생합니다.

HID-BPF에는 별도 `detach` facility가 없습니다. HID-BPF struct_ops link를 가리키는 userspace file descriptor가 모두 닫히면 program이 detach됩니다. Report descriptor fixup 교체에는 원래 owner의 협력이 필요하며, owner가 bpffs에 struct_ops link를 pin했다면 일반 BPF operation으로 교체할 수 있습니다.

Hook 반환 효과
Hook성공·변경실패·detach
hid_device_event수정 data·마지막 size를 HID stack에 전달Negative면 event 폐기
syscall할당 context로 report I/O 수행Context release 필수
hid_rdesc_fixup4KiB buffer의 descriptor 수정·reprobeDetach 시 disconnect

Hook별 data와 device 생명주기 변화입니다.

Report descriptor fixup
Driver probe에서 original descriptor 준비HID_MAX_DESCRIPTOR_SIZE context buffer 생성BPF fixup이 content·size 수정Attach 시 disconnect·reprobeFd 전체 close 또는 link 교체Detach 시 다시 disconnect

Attach와 detach가 HID reprobe를 일으킵니다.

``SEC("struct_ops/hid_device_event")``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Whenever a matching event is raised, the eBPF programs are called one after the other
and are working on the same data buffer.

If a program changes the data associated with the context, the next one will see
the modified data but it will have *no* idea of what the original data was.

Once all the programs are run and return ``0`` or a positive value, the rest of the
HID stack will work on the modified data, with the ``size`` field of the last hid_bpf_ctx
being the new size of the input stream of data.

A BPF program returning a negative error discards the event, i.e. this event will not be
processed by the HID stack. Clients (hidraw, input, LEDs) will **not** see this event.

``SEC("syscall")``
~~~~~~~~~~~~~~~~~~

``syscall`` are not attached to a given device. To tell which device we are working
with, userspace needs to refer to the device by its unique system id (the last 4 numbers
in the sysfs path: ``/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000``).

To retrieve a context associated with the device, the program must call
hid_bpf_allocate_context() and must release it with hid_bpf_release_context()
before returning.
Once the context is retrieved, one can also request a pointer to kernel memory with
hid_bpf_get_data(). This memory is big enough to support all input/output/feature
reports of the given device.

``SEC("struct_ops/hid_rdesc_fixup")``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``hid_rdesc_fixup`` program works in a similar manner to ``.report_fixup``
of ``struct hid_driver``.

When the device is probed, the kernel sets the data buffer of the context with the
content of the report descriptor. The memory associated with that buffer is
``HID_MAX_DESCRIPTOR_SIZE`` (currently 4kB).

The eBPF program can modify the data buffer at-will and the kernel uses the
modified content and size as the report descriptor.

Whenever a struct_ops containing a ``SEC("struct_ops/hid_rdesc_fixup")`` program
is attached (if no program was attached before), the kernel immediately disconnects
the HID device and does a reprobe.

In the same way, when this struct_ops is detached, the kernel issues a disconnect
on the device.

There is no ``detach`` facility in HID-BPF. Detaching a program happens when
all the user space file descriptors pointing at a HID-BPF struct_ops link are closed.
Thus, if we need to replace a report descriptor fixup, some cooperation is
required from the owner of the original report descriptor fixup.
The previous owner will likely pin the struct_ops link in the bpffs, and we can then
replace it through normal bpf operations.

BPF program을 HID device에 attach

318-345

현재는 표준 `bpf_map__attach_struct_ops()`로 struct_ops를 attach합니다. 전용 HID device를 지정해야 하므로 caller는 kernel에 program을 load하기 전에 struct_ops map의 `hid_id`를 설정해야 합니다.

`hid_id`는 sysfs path `/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000`의 마지막 네 숫자로 된 HID device unique system ID입니다. `enum hid_bpf_attach_flags` type의 `flags`도 설정할 수 있습니다.

Hidraw는 HID device processing의 결과물이며 안정적이지 않고 일부 driver는 아예 disable하므로 BPF program을 device에 bind하는 기준으로 사용할 수 없습니다. 그 경우 특히 필요한 tracing capability도 사라집니다.

반면 `hid_id`는 report descriptor를 바꿔도 HID device의 전체 lifetime 동안 안정적입니다.

Device disconnect·reconnect 때 hidraw가 불안정하므로 current report descriptor는 sysfs의 `/sys/bus/hid/devices/BUS:VID:PID.000N/report_descriptor` binary stream으로 읽는 것을 권장합니다.

Report descriptor parsing은 BPF programmer 또는 eBPF program을 load하는 userspace component의 책임입니다.

Device 식별 방식
대상권장
Attach IDhid_id: xxxx:yyyy:zzzz:0000
Attach APIbpf_map__attach_struct_ops()
Flagsenum hid_bpf_attach_flags
Descriptorsysfs report_descriptor binary stream
HidrawBind 기준으로 사용하지 않음

Attach와 descriptor 접근에 안정적인 identifier를 사용합니다.

Struct_ops attach
Sysfs에서 stable hid_id 확인Current report descriptor 읽고 parsingStruct_ops map의 hid_id·flags 설정BPF program kernel loadbpf_map__attach_struct_ops 호출bpf_link fd 보관·pin 또는 close

Program load 전에 target HID ID를 map에 넣습니다.

Attaching a bpf program to a device
===================================

We now use standard struct_ops attachment through ``bpf_map__attach_struct_ops()``.
But given that we need to attach a struct_ops to a dedicated HID device, the caller
must set ``hid_id`` in the struct_ops map before loading the program in the kernel.

``hid_id`` is the unique system ID of the HID device (the last 4 numbers in the
sysfs path: ``/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000``)

One can also set ``flags``, which is of type ``enum hid_bpf_attach_flags``.

We can not rely on hidraw to bind a BPF program to a HID device. hidraw is an
artefact of the processing of the HID device, and is not stable. Some drivers
even disable it, so that removes the tracing capabilities on those devices
(where it is interesting to get the non-hidraw traces).

On the other hand, the ``hid_id`` is stable for the entire life of the HID device,
even if we change its report descriptor.

Given that hidraw is not stable when the device disconnects/reconnects, we recommend
accessing the current report descriptor of the device through the sysfs.
This is available at ``/sys/bus/hid/devices/BUS:VID:PID.000N/report_descriptor`` as a
binary stream.

Parsing the report descriptor is the responsibility of the BPF programmer or the userspace
component that loads the eBPF program.

Haptic tablet 예제와 event filtering

346-450

예제 device는 사용자가 긁는 표면을 흉내 내는 haptic capability가 있는 새 tablet입니다. 세 위치 switch로 `pencil on paper`, `crayon on a wall`, `brush on a painting canvas`를 선택하며 feature report로 switch의 physical position도 제어할 수 있다고 가정합니다.

Switch는 device의 haptic feature를 제어하는 userspace component에 의존합니다. 대부분은 kernel driver로도 구현할 수 있지만 여기서는 HID-BPF 예제로 구성합니다.

첫 단계는 device event filtering입니다. Switch position은 pen event stream 안에 포함되므로 hidraw로 filter하면 모든 event마다 userspace를 깨웁니다. Libinput에는 괜찮을 수 있지만 report의 byte 하나만 필요한 외부 library에는 비효율적입니다.

BPF skeleton은 `vmlinux.h`, BPF helper·tracing header를 include하고 HID program에 필요한 GPL license를 선언합니다. `hid_bpf_get_data` kfunc를 정의하고 `BPF_MAP_TYPE_RINGBUF`, 최대 `4096 * 64`인 ring buffer와 `current_value`를 둡니다.

`filter_switch`는 `SEC("struct_ops/hid_device_event")` program입니다. `hid_bpf_get_data(hid_ctx, 0, 192)`로 data를 얻고 null이면 0을 반환합니다. `data[152]`가 `current_value`와 다를 때만 ring buffer에서 1byte를 reserve합니다.

Reserve에 실패하면 event를 그대로 허용합니다. 성공하면 새 byte를 buffer에 쓰고 `bpf_ringbuf_commit()`한 뒤 `current_value`를 갱신합니다. Program은 항상 0을 반환하므로 HID event 자체는 폐기하지 않습니다.

`.struct_ops.link` section의 `struct hid_bpf_ops haptic_tablet`은 `hid_device_event` callback에 `filter_switch`를 연결합니다.

Userspace attach 함수는 먼저 `haptic_tablet->hid_id`를 설정하고 skeleton을 load합니다. `bpf_map__attach_struct_ops()`로 link를 만들고 실패하면 error를 출력합니다. 성공하면 생성된 `bpf_link`의 fd를 반환합니다.

Userspace는 ring buffer notification을 들어 switch 값이 바뀔 때만 깨어납니다. 더 이상 event가 필요 없으면 반환된 link를 닫아 kernel이 program을 detach하게 합니다. 다른 BPF link처럼 `bpf_obj_pin()`으로 BPF filesystem에 fd를 pin할 수도 있습니다.

Haptic filter state
항목
Programfilter_switch
Sectionstruct_ops/hid_device_event
Data rangeoffset 0 · size 192
Switch bytedata[152]
MapBPF_MAP_TYPE_RINGBUF
Map size4096 * 64
Struct opshaptic_tablet

예제 code의 map·offset·attach 정보를 보존합니다.

Switch-change notification
Pen HID event 수신192byte data boundary 검증data[152]와 current_value 비교같으면 추가 작업 없이 반환다르면 ring buffer에 1byte commitUserspace가 변화 notification 수신

모든 pen event 중 switch 변화만 userspace에 알립니다.

An (almost) complete example of a BPF enhanced HID device
=========================================================

*Foreword: for most parts, this could be implemented as a kernel driver*

Let's imagine we have a new tablet device that has some haptic capabilities
to simulate the surface the user is scratching on. This device would also have
a specific 3 positions switch to toggle between *pencil on paper*, *cray on a wall*
and *brush on a painting canvas*. To make things even better, we can control the
physical position of the switch through a feature report.

And of course, the switch is relying on some userspace component to control the
haptic feature of the device itself.

Filtering events
----------------

The first step consists in filtering events from the device. Given that the switch
position is actually reported in the flow of the pen events, using hidraw to implement
that filtering would mean that we wake up userspace for every single event.

This is OK for libinput, but having an external library that is just interested in
one byte in the report is less than ideal.

For that, we can create a basic skeleton for our BPF program::

  #include "vmlinux.h"
  #include <bpf/bpf_helpers.h>
  #include <bpf/bpf_tracing.h>

  /* HID programs need to be GPL */
  char _license[] SEC("license") = "GPL";

  /* HID-BPF kfunc API definitions */
  extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
                              unsigned int offset,
                              const size_t __sz) __ksym;

  struct {
        __uint(type, BPF_MAP_TYPE_RINGBUF);
        __uint(max_entries, 4096 * 64);
  } ringbuf SEC(".maps");

  __u8 current_value = 0;

  SEC("struct_ops/hid_device_event")
  int BPF_PROG(filter_switch, struct hid_bpf_ctx *hid_ctx)
  {
        __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 192 /* size */);
        __u8 *buf;

        if (!data)
                return 0; /* EPERM check */

        if (current_value != data[152]) {
                buf = bpf_ringbuf_reserve(&ringbuf, 1, 0);
                if (!buf)
                        return 0;

                *buf = data[152];

                bpf_ringbuf_commit(buf, 0);

                current_value = data[152];
        }

        return 0;
  }

  SEC(".struct_ops.link")
  struct hid_bpf_ops haptic_tablet = {
          .hid_device_event = (void *)filter_switch,
  };


To attach ``haptic_tablet``, userspace needs to set ``hid_id`` first::

  static int attach_filter(struct hid *hid_skel, int hid_id)
  {
          int err, link_fd;

          hid_skel->struct_ops.haptic_tablet->hid_id = hid_id;
          err = hid__load(skel);
          if (err)
                  return err;

          link_fd = bpf_map__attach_struct_ops(hid_skel->maps.haptic_tablet);
          if (!link_fd) {
                  fprintf(stderr, "can not attach HID-BPF program: %m\n");
                  return -1;
          }

          return link_fd; /* the fd of the created bpf_link */
  }

Our userspace program can now listen to notifications on the ring buffer, and
is awaken only when the value changes.

When the userspace program doesn't need to listen to events anymore, it can just
close the returned bpf link from :c:func:`attach_filter`, which will tell the kernel to
detach the program from the HID device.

Of course, in other use cases, the userspace program can also pin the fd to the
BPF filesystem through a call to :c:func:`bpf_obj_pin`, as with any bpf_link.

Syscall program으로 haptic feature 제어

451-527

Tablet의 haptic feedback을 바꾸려면 userspace program이 device에 feature report를 보내야 합니다. Hidraw 대신 device와 통신하는 `SEC("syscall")` program을 만듭니다.

예제는 `hid_bpf_allocate_context`, `hid_bpf_release_context`, `hid_bpf_hw_request` kfunc를 선언합니다. Hardware request는 context, data, length, `enum hid_report_type`, `enum hid_class_request`를 받습니다.

`struct hid_send_haptics_args`는 kernel 쪽에서 `memcpy`할 수 있도록 offset 0에 `__u8 data[10]`을 두고 그 뒤에 `unsigned int hid`를 둡니다.

`send_haptic` syscall program은 `args->hid`로 context를 할당합니다. 실패하면 0을 반환합니다. 성공하면 `hid_bpf_hw_request()`에 10byte data, `HID_FEATURE_REPORT`, `HID_REQ_SET_REPORT`를 전달합니다.

Hardware request 뒤에는 항상 `hid_bpf_release_context()`로 context를 해제하고 request return value를 반환합니다.

Userspace `set_haptic` 함수는 `hid_send_haptics_args`에 `hid_id`를 넣고 `DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, ...)`로 input context와 size를 설정합니다.

`args.data[0]`은 device feature의 report ID `0x02`, `args.data[1]`은 haptic value입니다. `bpf_program__fd(hid_skel->progs.set_haptic)`로 program fd를 얻고 `bpf_prog_test_run_opts()`로 직접 실행합니다.

이제 userspace program은 haptic state를 알고 제어할 수 있으며 D-Bus API 같은 방식으로 다른 userspace program에 공개할 수도 있습니다.

이 설계의 핵심은 새 kernel API를 만들지 않았다는 점입니다. 구현에 bug가 있으면 userspace application이 자체 사용 contract를 책임지므로 kernel과의 interface를 자유롭게 바꿀 수 있습니다.

Haptic syscall contract
항목
Program sectionSEC("syscall")
Contexthid_bpf_allocate_context / release_context
Dataargs.data[10] at offset 0
Report ID0x02
TypeHID_FEATURE_REPORT
RequestHID_REQ_SET_REPORT
Runnerbpf_prog_test_run_opts

Feature report 전송에 쓰이는 정확한 field와 API입니다.

Haptic feature report
Userspace가 hid_id·haptic_value 구성Feature report ID 0x02 설정bpf_prog_test_run_opts 호출BPF가 device context 할당hid_bpf_hw_request로 10byte SET_REPORTContext 해제·결과 반환필요하면 D-Bus로 state 공유

Userspace 값이 syscall BPF를 거쳐 device에 전달됩니다.

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.