]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
HID: bpf: new hid_bpf_async.h common header
authorBenjamin Tissoires <bentiss@kernel.org>
Fri, 7 Feb 2025 13:56:02 +0000 (14:56 +0100)
committerBenjamin Tissoires <bentiss@kernel.org>
Mon, 24 Feb 2025 08:25:34 +0000 (09:25 +0100)
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 <jkosina@suse.com>
Link: https://patch.msgid.link/20250207-bpf-import-2025-02-07-v1-6-6048fdd5a206@kernel.org
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
drivers/hid/bpf/progs/hid_bpf_async.h [new file with mode: 0644]

diff --git a/drivers/hid/bpf/progs/hid_bpf_async.h b/drivers/hid/bpf/progs/hid_bpf_async.h
new file mode 100644 (file)
index 0000000..9ab5854
--- /dev/null
@@ -0,0 +1,219 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ * Copyright (c) 2024 Benjamin Tissoires
+ */
+
+#ifndef __HID_BPF_ASYNC_H__
+#define __HID_BPF_ASYNC_H__
+
+#ifndef HID_BPF_ASYNC_MAX_CTX
+#error "HID_BPF_ASYNC_MAX_CTX should be set to the maximum number of concurrent async functions"
+#endif /* HID_BPF_ASYNC_MAX_CTX */
+
+#define CLOCK_MONOTONIC                1
+
+typedef int (*hid_bpf_async_callback_t)(void *map, int *key, void *value);
+
+enum hid_bpf_async_state {
+       HID_BPF_ASYNC_STATE_UNSET = 0,
+       HID_BPF_ASYNC_STATE_INITIALIZING,
+       HID_BPF_ASYNC_STATE_INITIALIZED,
+       HID_BPF_ASYNC_STATE_STARTING,
+       HID_BPF_ASYNC_STATE_RUNNING,
+};
+
+struct hid_bpf_async_map_elem {
+       struct bpf_spin_lock lock;
+       enum hid_bpf_async_state state;
+       struct bpf_timer t;
+       struct bpf_wq wq;
+       u32 hid;
+};
+
+struct {
+       __uint(type, BPF_MAP_TYPE_ARRAY);
+       __uint(max_entries, HID_BPF_ASYNC_MAX_CTX);
+       __type(key, u32);
+       __type(value, struct hid_bpf_async_map_elem);
+} hid_bpf_async_ctx_map SEC(".maps");
+
+/**
+ * HID_BPF_ASYNC_CB: macro to define an async callback used in a bpf_wq
+ *
+ * The caller is responsible for allocating a key in the async map
+ * with hid_bpf_async_get_ctx().
+ */
+#define HID_BPF_ASYNC_CB(cb)                                   \
+cb(void *map, int *key, void *value);                          \
+static __always_inline int                                     \
+____##cb(struct hid_bpf_ctx *ctx);                             \
+typeof(cb(0, 0, 0)) cb(void *map, int *key, void *value)       \
+{                                                              \
+       struct hid_bpf_async_map_elem *e;                               \
+       struct hid_bpf_ctx *ctx;                                \
+                                                               \
+       e = (struct hid_bpf_async_map_elem *)value;                     \
+       ctx = hid_bpf_allocate_context(e->hid);                 \
+       if (!ctx)                                               \
+               return 0; /* EPERM check */                     \
+                                                               \
+       e->state = HID_BPF_ASYNC_STATE_RUNNING;                 \
+                                                               \
+       ____##cb(ctx);                                          \
+                                                               \
+       e->state = HID_BPF_ASYNC_STATE_INITIALIZED;             \
+       hid_bpf_release_context(ctx);                           \
+       return 0;                                               \
+}                                                              \
+static __always_inline int                                     \
+____##cb
+
+/**
+ * ASYNC: macro to automatically handle async callbacks contexts
+ *
+ * Needs to be used in conjunction with HID_BPF_ASYNC_INIT and HID_BPF_ASYNC_DELAYED_CALL
+ */
+#define HID_BPF_ASYNC_FUN(fun)                                         \
+fun(struct hid_bpf_ctx *ctx);                                  \
+int ____key__##fun;                                            \
+static int ____async_init_##fun(void)                          \
+{                                                              \
+       ____key__##fun = hid_bpf_async_get_ctx();                       \
+       if (____key__##fun < 0)                                 \
+               return ____key__##fun;                          \
+       return 0;                                               \
+}                                                              \
+static int HID_BPF_ASYNC_CB(____##fun##_cb)(struct hid_bpf_ctx *hctx)  \
+{                                                              \
+       return fun(hctx);                                       \
+}                                                              \
+typeof(fun(0)) fun
+
+#define HID_BPF_ASYNC_INIT(fun)        ____async_init_##fun()
+#define HID_BPF_ASYNC_DELAYED_CALL(fun, ctx, delay)            \
+       hid_bpf_async_delayed_call(ctx, delay, ____key__##fun, ____##fun##_cb)
+
+/*
+ * internal cb for starting the delayed work callback in a workqueue.
+ */
+static int __start_wq_timer_cb(void *map, int *key, void *value)
+{
+       struct hid_bpf_async_map_elem *e = (struct hid_bpf_async_map_elem *)value;
+
+       bpf_wq_start(&e->wq, 0);
+
+       return 0;
+}
+
+static int hid_bpf_async_find_empty_key(void)
+{
+       int i;
+
+       bpf_for(i, 0, HID_BPF_ASYNC_MAX_CTX) {
+               struct hid_bpf_async_map_elem *elem;
+               int key = i;
+
+               elem = bpf_map_lookup_elem(&hid_bpf_async_ctx_map, &key);
+               if (!elem)
+                       return -ENOMEM; /* should never happen */
+
+               bpf_spin_lock(&elem->lock);
+
+               if (elem->state == HID_BPF_ASYNC_STATE_UNSET) {
+                       elem->state = HID_BPF_ASYNC_STATE_INITIALIZING;
+                       bpf_spin_unlock(&elem->lock);
+                       return i;
+               }
+
+               bpf_spin_unlock(&elem->lock);
+       }
+
+       return -EINVAL;
+}
+
+static int hid_bpf_async_get_ctx(void)
+{
+       int key = hid_bpf_async_find_empty_key();
+       struct hid_bpf_async_map_elem *elem;
+       int err;
+
+       if (key < 0)
+               return key;
+
+       elem = bpf_map_lookup_elem(&hid_bpf_async_ctx_map, &key);
+       if (!elem)
+               return -EINVAL;
+
+       err = bpf_timer_init(&elem->t, &hid_bpf_async_ctx_map, CLOCK_MONOTONIC);
+       if (err)
+               return err;
+
+       err = bpf_timer_set_callback(&elem->t, __start_wq_timer_cb);
+       if (err)
+               return err;
+
+       err = bpf_wq_init(&elem->wq, &hid_bpf_async_ctx_map, 0);
+       if (err)
+               return err;
+
+       elem->state = HID_BPF_ASYNC_STATE_INITIALIZED;
+
+       return key;
+}
+
+static inline u64 ms_to_ns(u64 milliseconds)
+{
+       return (u64)milliseconds * 1000UL * 1000UL;
+}
+
+static int hid_bpf_async_delayed_call(struct hid_bpf_ctx *hctx, u64 milliseconds, int key,
+                             hid_bpf_async_callback_t wq_cb)
+{
+       struct hid_bpf_async_map_elem *elem;
+       int err;
+
+       elem = bpf_map_lookup_elem(&hid_bpf_async_ctx_map, &key);
+       if (!elem)
+               return -EINVAL;
+
+       bpf_spin_lock(&elem->lock);
+       /* The wq must be:
+        * - HID_BPF_ASYNC_STATE_INITIALIZED -> it's been initialized and ready to be called
+        * - HID_BPF_ASYNC_STATE_RUNNING -> possible re-entry from the wq itself
+        */
+       if (elem->state != HID_BPF_ASYNC_STATE_INITIALIZED &&
+           elem->state != HID_BPF_ASYNC_STATE_RUNNING) {
+               bpf_spin_unlock(&elem->lock);
+               return -EINVAL;
+       }
+       elem->state = HID_BPF_ASYNC_STATE_STARTING;
+       bpf_spin_unlock(&elem->lock);
+
+       elem->hid = hctx->hid->id;
+
+       err = bpf_wq_set_callback(&elem->wq, wq_cb, 0);
+       if (err)
+               return err;
+
+       if (milliseconds) {
+               /* needed for every call because a cancel might unset this */
+               err = bpf_timer_set_callback(&elem->t, __start_wq_timer_cb);
+               if (err)
+                       return err;
+
+               err = bpf_timer_start(&elem->t, ms_to_ns(milliseconds), 0);
+               if (err)
+                       return err;
+
+               return 0;
+       }
+
+       return bpf_wq_start(&elem->wq, 0);
+}
+
+static inline int hid_bpf_async_call(struct hid_bpf_ctx *ctx, int key,
+                                    hid_bpf_async_callback_t wq_cb)
+{
+       return hid_bpf_async_delayed_call(ctx, 0, key, wq_cb);
+}
+
+#endif /* __HID_BPF_ASYNC_H__ */