Skip to content

Commit 4a94deb

Browse files
author
Benjamin Tissoires
committed
HID: bpf: new hid_bpf_async.h common header
The purpose is to simplify the use of bpf_wq to defer blocking operations in a sleepable context. Compared to a more "classic" async approach, there is no sync mechanism to wait for the async to finish. The "simple" API is the following: ``` static int HID_BPF_ASYNC(async_fun)(struct hid_bpf_ctx *hctx) { bpf_printk("%s", __fun__); return 0; } SEC("syscall") int probe(struct hid_bpf_probe_args *ctx) { ctx->retval = HID_BPF_ASYNC_INIT(async_fun); return 0; } SEC(HID_BPF_DEVICE_EVENT) int BPF_PROG(event_handler, struct hid_bpf_ctx *hctx) { /* async_fun() can be called now, it's not a sleepable * function in this example */ async_fun(hctx); /* but we can also delay the call by 10 ms */ HID_BPF_ASYNC_DELAYED_CALL(async_fun, hctx, 10); return 0; } HID_BPF_OPS(xppen_ack05_remote) = { .hid_device_event = (void *)event_handler, }; ``` Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/133 Acked-by: Jiri Kosina <[email protected]> Link: https://patch.msgid.link/[email protected] Signed-off-by: Benjamin Tissoires <[email protected]>
1 parent 91bb311 commit 4a94deb

File tree

1 file changed

+219
-0
lines changed

1 file changed

+219
-0
lines changed

drivers/hid/bpf/progs/hid_bpf_async.h

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/* SPDX-License-Identifier: GPL-2.0-only
2+
* Copyright (c) 2024 Benjamin Tissoires
3+
*/
4+
5+
#ifndef __HID_BPF_ASYNC_H__
6+
#define __HID_BPF_ASYNC_H__
7+
8+
#ifndef HID_BPF_ASYNC_MAX_CTX
9+
#error "HID_BPF_ASYNC_MAX_CTX should be set to the maximum number of concurrent async functions"
10+
#endif /* HID_BPF_ASYNC_MAX_CTX */
11+
12+
#define CLOCK_MONOTONIC 1
13+
14+
typedef int (*hid_bpf_async_callback_t)(void *map, int *key, void *value);
15+
16+
enum hid_bpf_async_state {
17+
HID_BPF_ASYNC_STATE_UNSET = 0,
18+
HID_BPF_ASYNC_STATE_INITIALIZING,
19+
HID_BPF_ASYNC_STATE_INITIALIZED,
20+
HID_BPF_ASYNC_STATE_STARTING,
21+
HID_BPF_ASYNC_STATE_RUNNING,
22+
};
23+
24+
struct hid_bpf_async_map_elem {
25+
struct bpf_spin_lock lock;
26+
enum hid_bpf_async_state state;
27+
struct bpf_timer t;
28+
struct bpf_wq wq;
29+
u32 hid;
30+
};
31+
32+
struct {
33+
__uint(type, BPF_MAP_TYPE_ARRAY);
34+
__uint(max_entries, HID_BPF_ASYNC_MAX_CTX);
35+
__type(key, u32);
36+
__type(value, struct hid_bpf_async_map_elem);
37+
} hid_bpf_async_ctx_map SEC(".maps");
38+
39+
/**
40+
* HID_BPF_ASYNC_CB: macro to define an async callback used in a bpf_wq
41+
*
42+
* The caller is responsible for allocating a key in the async map
43+
* with hid_bpf_async_get_ctx().
44+
*/
45+
#define HID_BPF_ASYNC_CB(cb) \
46+
cb(void *map, int *key, void *value); \
47+
static __always_inline int \
48+
____##cb(struct hid_bpf_ctx *ctx); \
49+
typeof(cb(0, 0, 0)) cb(void *map, int *key, void *value) \
50+
{ \
51+
struct hid_bpf_async_map_elem *e; \
52+
struct hid_bpf_ctx *ctx; \
53+
\
54+
e = (struct hid_bpf_async_map_elem *)value; \
55+
ctx = hid_bpf_allocate_context(e->hid); \
56+
if (!ctx) \
57+
return 0; /* EPERM check */ \
58+
\
59+
e->state = HID_BPF_ASYNC_STATE_RUNNING; \
60+
\
61+
____##cb(ctx); \
62+
\
63+
e->state = HID_BPF_ASYNC_STATE_INITIALIZED; \
64+
hid_bpf_release_context(ctx); \
65+
return 0; \
66+
} \
67+
static __always_inline int \
68+
____##cb
69+
70+
/**
71+
* ASYNC: macro to automatically handle async callbacks contexts
72+
*
73+
* Needs to be used in conjunction with HID_BPF_ASYNC_INIT and HID_BPF_ASYNC_DELAYED_CALL
74+
*/
75+
#define HID_BPF_ASYNC_FUN(fun) \
76+
fun(struct hid_bpf_ctx *ctx); \
77+
int ____key__##fun; \
78+
static int ____async_init_##fun(void) \
79+
{ \
80+
____key__##fun = hid_bpf_async_get_ctx(); \
81+
if (____key__##fun < 0) \
82+
return ____key__##fun; \
83+
return 0; \
84+
} \
85+
static int HID_BPF_ASYNC_CB(____##fun##_cb)(struct hid_bpf_ctx *hctx) \
86+
{ \
87+
return fun(hctx); \
88+
} \
89+
typeof(fun(0)) fun
90+
91+
#define HID_BPF_ASYNC_INIT(fun) ____async_init_##fun()
92+
#define HID_BPF_ASYNC_DELAYED_CALL(fun, ctx, delay) \
93+
hid_bpf_async_delayed_call(ctx, delay, ____key__##fun, ____##fun##_cb)
94+
95+
/*
96+
* internal cb for starting the delayed work callback in a workqueue.
97+
*/
98+
static int __start_wq_timer_cb(void *map, int *key, void *value)
99+
{
100+
struct hid_bpf_async_map_elem *e = (struct hid_bpf_async_map_elem *)value;
101+
102+
bpf_wq_start(&e->wq, 0);
103+
104+
return 0;
105+
}
106+
107+
static int hid_bpf_async_find_empty_key(void)
108+
{
109+
int i;
110+
111+
bpf_for(i, 0, HID_BPF_ASYNC_MAX_CTX) {
112+
struct hid_bpf_async_map_elem *elem;
113+
int key = i;
114+
115+
elem = bpf_map_lookup_elem(&hid_bpf_async_ctx_map, &key);
116+
if (!elem)
117+
return -ENOMEM; /* should never happen */
118+
119+
bpf_spin_lock(&elem->lock);
120+
121+
if (elem->state == HID_BPF_ASYNC_STATE_UNSET) {
122+
elem->state = HID_BPF_ASYNC_STATE_INITIALIZING;
123+
bpf_spin_unlock(&elem->lock);
124+
return i;
125+
}
126+
127+
bpf_spin_unlock(&elem->lock);
128+
}
129+
130+
return -EINVAL;
131+
}
132+
133+
static int hid_bpf_async_get_ctx(void)
134+
{
135+
int key = hid_bpf_async_find_empty_key();
136+
struct hid_bpf_async_map_elem *elem;
137+
int err;
138+
139+
if (key < 0)
140+
return key;
141+
142+
elem = bpf_map_lookup_elem(&hid_bpf_async_ctx_map, &key);
143+
if (!elem)
144+
return -EINVAL;
145+
146+
err = bpf_timer_init(&elem->t, &hid_bpf_async_ctx_map, CLOCK_MONOTONIC);
147+
if (err)
148+
return err;
149+
150+
err = bpf_timer_set_callback(&elem->t, __start_wq_timer_cb);
151+
if (err)
152+
return err;
153+
154+
err = bpf_wq_init(&elem->wq, &hid_bpf_async_ctx_map, 0);
155+
if (err)
156+
return err;
157+
158+
elem->state = HID_BPF_ASYNC_STATE_INITIALIZED;
159+
160+
return key;
161+
}
162+
163+
static inline u64 ms_to_ns(u64 milliseconds)
164+
{
165+
return (u64)milliseconds * 1000UL * 1000UL;
166+
}
167+
168+
static int hid_bpf_async_delayed_call(struct hid_bpf_ctx *hctx, u64 milliseconds, int key,
169+
hid_bpf_async_callback_t wq_cb)
170+
{
171+
struct hid_bpf_async_map_elem *elem;
172+
int err;
173+
174+
elem = bpf_map_lookup_elem(&hid_bpf_async_ctx_map, &key);
175+
if (!elem)
176+
return -EINVAL;
177+
178+
bpf_spin_lock(&elem->lock);
179+
/* The wq must be:
180+
* - HID_BPF_ASYNC_STATE_INITIALIZED -> it's been initialized and ready to be called
181+
* - HID_BPF_ASYNC_STATE_RUNNING -> possible re-entry from the wq itself
182+
*/
183+
if (elem->state != HID_BPF_ASYNC_STATE_INITIALIZED &&
184+
elem->state != HID_BPF_ASYNC_STATE_RUNNING) {
185+
bpf_spin_unlock(&elem->lock);
186+
return -EINVAL;
187+
}
188+
elem->state = HID_BPF_ASYNC_STATE_STARTING;
189+
bpf_spin_unlock(&elem->lock);
190+
191+
elem->hid = hctx->hid->id;
192+
193+
err = bpf_wq_set_callback(&elem->wq, wq_cb, 0);
194+
if (err)
195+
return err;
196+
197+
if (milliseconds) {
198+
/* needed for every call because a cancel might unset this */
199+
err = bpf_timer_set_callback(&elem->t, __start_wq_timer_cb);
200+
if (err)
201+
return err;
202+
203+
err = bpf_timer_start(&elem->t, ms_to_ns(milliseconds), 0);
204+
if (err)
205+
return err;
206+
207+
return 0;
208+
}
209+
210+
return bpf_wq_start(&elem->wq, 0);
211+
}
212+
213+
static inline int hid_bpf_async_call(struct hid_bpf_ctx *ctx, int key,
214+
hid_bpf_async_callback_t wq_cb)
215+
{
216+
return hid_bpf_async_delayed_call(ctx, 0, key, wq_cb);
217+
}
218+
219+
#endif /* __HID_BPF_ASYNC_H__ */

0 commit comments

Comments
 (0)