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
14typedef int (*hid_bpf_async_callback_t)(void *map, int *key, void *value);
15
16enum 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
24struct 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
32struct {
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) \
46cb(void *map, int *key, void *value); \
47static __always_inline int \
48____##cb(struct hid_bpf_ctx *ctx); \
49typeof(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} \
67static __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) \
76fun(struct hid_bpf_ctx *ctx); \
77int ____key__##fun; \
78static 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} \
85static int HID_BPF_ASYNC_CB(____##fun##_cb)(struct hid_bpf_ctx *hctx) \
86{ \
87 return fun(hctx); \
88} \
89typeof(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 */
98static 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(wq: &e->wq, flags: 0);
103
104 return 0;
105}
106
107static 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
133static 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(wq: &elem->wq, p__map: &hid_bpf_async_ctx_map, flags: 0);
155 if (err)
156 return err;
157
158 elem->state = HID_BPF_ASYNC_STATE_INITIALIZED;
159
160 return key;
161}
162
163static inline u64 ms_to_ns(u64 milliseconds)
164{
165 return (u64)milliseconds * 1000UL * 1000UL;
166}
167
168static 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(wq: &elem->wq, flags: 0);
211}
212
213static 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__ */
220

source code of linux/drivers/hid/bpf/progs/hid_bpf_async.h