summaryrefslogtreecommitdiffstats
path: root/drivers/hid/bpf
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid/bpf')
-rw-r--r--drivers/hid/bpf/hid_bpf_dispatch.c226
-rw-r--r--drivers/hid/bpf/progs/FR-TEC__Raptor-Mach-2.bpf.c185
-rw-r--r--drivers/hid/bpf/progs/HP__Elite-Presenter.bpf.c58
-rw-r--r--drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c290
-rw-r--r--drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c59
-rw-r--r--drivers/hid/bpf/progs/Makefile91
-rw-r--r--drivers/hid/bpf/progs/Microsoft__XBox-Elite-2.bpf.c133
-rw-r--r--drivers/hid/bpf/progs/README102
-rw-r--r--drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c173
-rw-r--r--drivers/hid/bpf/progs/XPPen__Artist24.bpf.c229
-rw-r--r--drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c274
-rw-r--r--drivers/hid/bpf/progs/hid_bpf.h15
-rw-r--r--drivers/hid/bpf/progs/hid_bpf_helpers.h168
13 files changed, 1932 insertions, 71 deletions
diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
index e630caf644..10289f44d0 100644
--- a/drivers/hid/bpf/hid_bpf_dispatch.c
+++ b/drivers/hid/bpf/hid_bpf_dispatch.c
@@ -143,48 +143,6 @@ u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *s
}
EXPORT_SYMBOL_GPL(call_hid_bpf_rdesc_fixup);
-/* Disables missing prototype warnings */
-__bpf_kfunc_start_defs();
-
-/**
- * hid_bpf_get_data - Get the kernel memory pointer associated with the context @ctx
- *
- * @ctx: The HID-BPF context
- * @offset: The offset within the memory
- * @rdwr_buf_size: the const size of the buffer
- *
- * @returns %NULL on error, an %__u8 memory pointer on success
- */
-__bpf_kfunc __u8 *
-hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t rdwr_buf_size)
-{
- struct hid_bpf_ctx_kern *ctx_kern;
-
- if (!ctx)
- return NULL;
-
- ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
-
- if (rdwr_buf_size + offset > ctx->allocated_size)
- return NULL;
-
- return ctx_kern->data + offset;
-}
-__bpf_kfunc_end_defs();
-
-/*
- * The following set contains all functions we agree BPF programs
- * can use.
- */
-BTF_KFUNCS_START(hid_bpf_kfunc_ids)
-BTF_ID_FLAGS(func, hid_bpf_get_data, KF_RET_NULL)
-BTF_KFUNCS_END(hid_bpf_kfunc_ids)
-
-static const struct btf_kfunc_id_set hid_bpf_kfunc_set = {
- .owner = THIS_MODULE,
- .set = &hid_bpf_kfunc_ids,
-};
-
static int device_match_id(struct device *dev, const void *id)
{
struct hid_device *hdev = to_hid_device(dev);
@@ -282,6 +240,31 @@ static int do_hid_bpf_attach_prog(struct hid_device *hdev, int prog_fd, struct b
__bpf_kfunc_start_defs();
/**
+ * hid_bpf_get_data - Get the kernel memory pointer associated with the context @ctx
+ *
+ * @ctx: The HID-BPF context
+ * @offset: The offset within the memory
+ * @rdwr_buf_size: the const size of the buffer
+ *
+ * @returns %NULL on error, an %__u8 memory pointer on success
+ */
+__bpf_kfunc __u8 *
+hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t rdwr_buf_size)
+{
+ struct hid_bpf_ctx_kern *ctx_kern;
+
+ if (!ctx)
+ return NULL;
+
+ ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
+
+ if (rdwr_buf_size + offset > ctx->allocated_size)
+ return NULL;
+
+ return ctx_kern->data + offset;
+}
+
+/**
* hid_bpf_attach_prog - Attach the given @prog_fd to the given HID device
*
* @hid_id: the system unique identifier of the HID device
@@ -393,6 +376,46 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx)
put_device(&hid->dev);
}
+static int
+__hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz,
+ enum hid_report_type rtype)
+{
+ struct hid_report_enum *report_enum;
+ struct hid_report *report;
+ struct hid_device *hdev;
+ u32 report_len;
+
+ /* check arguments */
+ if (!ctx || !hid_bpf_ops || !buf)
+ return -EINVAL;
+
+ switch (rtype) {
+ case HID_INPUT_REPORT:
+ case HID_OUTPUT_REPORT:
+ case HID_FEATURE_REPORT:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (*buf__sz < 1)
+ return -EINVAL;
+
+ hdev = (struct hid_device *)ctx->hid; /* discard const */
+
+ report_enum = hdev->report_enum + rtype;
+ report = hid_bpf_ops->hid_get_report(report_enum, buf);
+ if (!report)
+ return -EINVAL;
+
+ report_len = hid_report_len(report);
+
+ if (*buf__sz > report_len)
+ *buf__sz = report_len;
+
+ return 0;
+}
+
/**
* hid_bpf_hw_request - Communicate with a HID device
*
@@ -409,24 +432,14 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
enum hid_report_type rtype, enum hid_class_request reqtype)
{
struct hid_device *hdev;
- struct hid_report *report;
- struct hid_report_enum *report_enum;
+ size_t size = buf__sz;
u8 *dma_data;
- u32 report_len;
int ret;
/* check arguments */
- if (!ctx || !hid_bpf_ops || !buf)
- return -EINVAL;
-
- switch (rtype) {
- case HID_INPUT_REPORT:
- case HID_OUTPUT_REPORT:
- case HID_FEATURE_REPORT:
- break;
- default:
- return -EINVAL;
- }
+ ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype);
+ if (ret)
+ return ret;
switch (reqtype) {
case HID_REQ_GET_REPORT:
@@ -440,29 +453,16 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
return -EINVAL;
}
- if (buf__sz < 1)
- return -EINVAL;
-
hdev = (struct hid_device *)ctx->hid; /* discard const */
- report_enum = hdev->report_enum + rtype;
- report = hid_bpf_ops->hid_get_report(report_enum, buf);
- if (!report)
- return -EINVAL;
-
- report_len = hid_report_len(report);
-
- if (buf__sz > report_len)
- buf__sz = report_len;
-
- dma_data = kmemdup(buf, buf__sz, GFP_KERNEL);
+ dma_data = kmemdup(buf, size, GFP_KERNEL);
if (!dma_data)
return -ENOMEM;
ret = hid_bpf_ops->hid_hw_raw_request(hdev,
dma_data[0],
dma_data,
- buf__sz,
+ size,
rtype,
reqtype);
@@ -472,8 +472,90 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
kfree(dma_data);
return ret;
}
+
+/**
+ * hid_bpf_hw_output_report - Send an output report to a HID device
+ *
+ * @ctx: the HID-BPF context previously allocated in hid_bpf_allocate_context()
+ * @buf: a %PTR_TO_MEM buffer
+ * @buf__sz: the size of the data to transfer
+ *
+ * Returns the number of bytes transferred on success, a negative error code otherwise.
+ */
+__bpf_kfunc int
+hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz)
+{
+ struct hid_device *hdev;
+ size_t size = buf__sz;
+ u8 *dma_data;
+ int ret;
+
+ /* check arguments */
+ ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT);
+ if (ret)
+ return ret;
+
+ hdev = (struct hid_device *)ctx->hid; /* discard const */
+
+ dma_data = kmemdup(buf, size, GFP_KERNEL);
+ if (!dma_data)
+ return -ENOMEM;
+
+ ret = hid_bpf_ops->hid_hw_output_report(hdev,
+ dma_data,
+ size);
+
+ kfree(dma_data);
+ return ret;
+}
+
+/**
+ * hid_bpf_input_report - Inject a HID report in the kernel from a HID device
+ *
+ * @ctx: the HID-BPF context previously allocated in hid_bpf_allocate_context()
+ * @type: the type of the report (%HID_INPUT_REPORT, %HID_FEATURE_REPORT, %HID_OUTPUT_REPORT)
+ * @buf: a %PTR_TO_MEM buffer
+ * @buf__sz: the size of the data to transfer
+ *
+ * Returns %0 on success, a negative error code otherwise.
+ */
+__bpf_kfunc int
+hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf,
+ const size_t buf__sz)
+{
+ struct hid_device *hdev;
+ size_t size = buf__sz;
+ int ret;
+
+ /* check arguments */
+ ret = __hid_bpf_hw_check_params(ctx, buf, &size, type);
+ if (ret)
+ return ret;
+
+ hdev = (struct hid_device *)ctx->hid; /* discard const */
+
+ return hid_bpf_ops->hid_input_report(hdev, type, buf, size, 0);
+}
__bpf_kfunc_end_defs();
+/*
+ * The following set contains all functions we agree BPF programs
+ * can use.
+ */
+BTF_KFUNCS_START(hid_bpf_kfunc_ids)
+BTF_ID_FLAGS(func, hid_bpf_get_data, KF_RET_NULL)
+BTF_ID_FLAGS(func, hid_bpf_allocate_context, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, hid_bpf_release_context, KF_RELEASE | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, hid_bpf_hw_request, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, hid_bpf_hw_output_report, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, hid_bpf_input_report, KF_SLEEPABLE)
+BTF_KFUNCS_END(hid_bpf_kfunc_ids)
+
+static const struct btf_kfunc_id_set hid_bpf_kfunc_set = {
+ .owner = THIS_MODULE,
+ .set = &hid_bpf_kfunc_ids,
+};
+
/* our HID-BPF entrypoints */
BTF_SET8_START(hid_bpf_fmodret_ids)
BTF_ID_FLAGS(func, hid_bpf_device_event)
@@ -492,6 +574,8 @@ BTF_ID_FLAGS(func, hid_bpf_attach_prog)
BTF_ID_FLAGS(func, hid_bpf_allocate_context, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, hid_bpf_release_context, KF_RELEASE)
BTF_ID_FLAGS(func, hid_bpf_hw_request)
+BTF_ID_FLAGS(func, hid_bpf_hw_output_report)
+BTF_ID_FLAGS(func, hid_bpf_input_report)
BTF_KFUNCS_END(hid_bpf_syscall_kfunc_ids)
static const struct btf_kfunc_id_set hid_bpf_syscall_kfunc_set = {
diff --git a/drivers/hid/bpf/progs/FR-TEC__Raptor-Mach-2.bpf.c b/drivers/hid/bpf/progs/FR-TEC__Raptor-Mach-2.bpf.c
new file mode 100644
index 0000000000..dc26a7677d
--- /dev/null
+++ b/drivers/hid/bpf/progs/FR-TEC__Raptor-Mach-2.bpf.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_BETOP_2185PC 0x11C0
+#define PID_RAPTOR_MACH_2 0x5606
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_BETOP_2185PC, PID_RAPTOR_MACH_2),
+);
+
+/*
+ * For reference, this is the fixed report descriptor
+ *
+ * static const __u8 fixed_rdesc[] = {
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 0
+ * 0x09, 0x04, // Usage (Joystick) 2
+ * 0xa1, 0x01, // Collection (Application) 4
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 6
+ * 0x85, 0x01, // Report ID (1) 8
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 10
+ * 0x09, 0x30, // Usage (X) 12
+ * 0x75, 0x10, // Report Size (16) 14
+ * 0x95, 0x01, // Report Count (1) 16
+ * 0x15, 0x00, // Logical Minimum (0) 18
+ * 0x26, 0xff, 0x07, // Logical Maximum (2047) 20
+ * 0x46, 0xff, 0x07, // Physical Maximum (2047) 23
+ * 0x81, 0x02, // Input (Data,Var,Abs) 26
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 28
+ * 0x09, 0x31, // Usage (Y) 30
+ * 0x75, 0x10, // Report Size (16) 32
+ * 0x95, 0x01, // Report Count (1) 34
+ * 0x15, 0x00, // Logical Minimum (0) 36
+ * 0x26, 0xff, 0x07, // Logical Maximum (2047) 38
+ * 0x46, 0xff, 0x07, // Physical Maximum (2047) 41
+ * 0x81, 0x02, // Input (Data,Var,Abs) 44
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 46
+ * 0x09, 0x33, // Usage (Rx) 48
+ * 0x75, 0x10, // Report Size (16) 50
+ * 0x95, 0x01, // Report Count (1) 52
+ * 0x15, 0x00, // Logical Minimum (0) 54
+ * 0x26, 0xff, 0x03, // Logical Maximum (1023) 56
+ * 0x46, 0xff, 0x03, // Physical Maximum (1023) 59
+ * 0x81, 0x02, // Input (Data,Var,Abs) 62
+ * 0x05, 0x00, // Usage Page (Undefined) 64
+ * 0x09, 0x00, // Usage (Undefined) 66
+ * 0x75, 0x10, // Report Size (16) 68
+ * 0x95, 0x01, // Report Count (1) 70
+ * 0x15, 0x00, // Logical Minimum (0) 72
+ * 0x26, 0xff, 0x03, // Logical Maximum (1023) 74
+ * 0x46, 0xff, 0x03, // Physical Maximum (1023) 77
+ * 0x81, 0x02, // Input (Data,Var,Abs) 80
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 82
+ * 0x09, 0x32, // Usage (Z) 84
+ * 0x75, 0x10, // Report Size (16) 86
+ * 0x95, 0x01, // Report Count (1) 88
+ * 0x15, 0x00, // Logical Minimum (0) 90
+ * 0x26, 0xff, 0x03, // Logical Maximum (1023) 92
+ * 0x46, 0xff, 0x03, // Physical Maximum (1023) 95
+ * 0x81, 0x02, // Input (Data,Var,Abs) 98
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 100
+ * 0x09, 0x35, // Usage (Rz) 102
+ * 0x75, 0x10, // Report Size (16) 104
+ * 0x95, 0x01, // Report Count (1) 106
+ * 0x15, 0x00, // Logical Minimum (0) 108
+ * 0x26, 0xff, 0x03, // Logical Maximum (1023) 110
+ * 0x46, 0xff, 0x03, // Physical Maximum (1023) 113
+ * 0x81, 0x02, // Input (Data,Var,Abs) 116
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 118
+ * 0x09, 0x34, // Usage (Ry) 120
+ * 0x75, 0x10, // Report Size (16) 122
+ * 0x95, 0x01, // Report Count (1) 124
+ * 0x15, 0x00, // Logical Minimum (0) 126
+ * 0x26, 0xff, 0x07, // Logical Maximum (2047) 128
+ * 0x46, 0xff, 0x07, // Physical Maximum (2047) 131
+ * 0x81, 0x02, // Input (Data,Var,Abs) 134
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 136
+ * 0x09, 0x36, // Usage (Slider) 138
+ * 0x75, 0x10, // Report Size (16) 140
+ * 0x95, 0x01, // Report Count (1) 142
+ * 0x15, 0x00, // Logical Minimum (0) 144
+ * 0x26, 0xff, 0x03, // Logical Maximum (1023) 146
+ * 0x46, 0xff, 0x03, // Physical Maximum (1023) 149
+ * 0x81, 0x02, // Input (Data,Var,Abs) 152
+ * 0x05, 0x09, // Usage Page (Button) 154
+ * 0x19, 0x01, // Usage Minimum (1) 156
+ * 0x2a, 0x1d, 0x00, // Usage Maximum (29) 158
+ * 0x15, 0x00, // Logical Minimum (0) 161
+ * 0x25, 0x01, // Logical Maximum (1) 163
+ * 0x75, 0x01, // Report Size (1) 165
+ * 0x96, 0x80, 0x00, // Report Count (128) 167
+ * 0x81, 0x02, // Input (Data,Var,Abs) 170
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 172
+ * 0x09, 0x39, // Usage (Hat switch) 174
+ * 0x26, 0x07, 0x00, // Logical Maximum (7) 176 // changed (was 239)
+ * 0x46, 0x68, 0x01, // Physical Maximum (360) 179
+ * 0x65, 0x14, // Unit (EnglishRotation: deg) 182
+ * 0x75, 0x10, // Report Size (16) 184
+ * 0x95, 0x01, // Report Count (1) 186
+ * 0x81, 0x42, // Input (Data,Var,Abs,Null) 188
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 190
+ * 0x09, 0x00, // Usage (Undefined) 192
+ * 0x75, 0x08, // Report Size (8) 194
+ * 0x95, 0x1d, // Report Count (29) 196
+ * 0x81, 0x01, // Input (Cnst,Arr,Abs) 198
+ * 0x15, 0x00, // Logical Minimum (0) 200
+ * 0x26, 0xef, 0x00, // Logical Maximum (239) 202
+ * 0x85, 0x58, // Report ID (88) 205
+ * 0x26, 0xff, 0x00, // Logical Maximum (255) 207
+ * 0x46, 0xff, 0x00, // Physical Maximum (255) 210
+ * 0x75, 0x08, // Report Size (8) 213
+ * 0x95, 0x3f, // Report Count (63) 215
+ * 0x09, 0x00, // Usage (Undefined) 217
+ * 0x91, 0x02, // Output (Data,Var,Abs) 219
+ * 0x85, 0x59, // Report ID (89) 221
+ * 0x75, 0x08, // Report Size (8) 223
+ * 0x95, 0x80, // Report Count (128) 225
+ * 0x09, 0x00, // Usage (Undefined) 227
+ * 0xb1, 0x02, // Feature (Data,Var,Abs) 229
+ * 0xc0, // End Collection 231
+ * };
+ */
+
+/*
+ * We need to amend the report descriptor for the following:
+ * - the joystick sends its hat_switch data between 0 and 239 but
+ * the kernel expects the logical max to stick into a signed 8 bits
+ * integer. We thus divide it by 30 to match what other joysticks are
+ * doing
+ */
+SEC("fmod_ret/hid_bpf_rdesc_fixup")
+int BPF_PROG(hid_fix_rdesc_raptor_mach_2, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ data[177] = 0x07;
+
+ return 0;
+}
+
+/*
+ * The hat_switch value at offsets 33 and 34 (16 bits) needs
+ * to be reduced to a single 8 bit signed integer. So we
+ * divide it by 30.
+ * Byte 34 is always null, so it is ignored.
+ */
+SEC("fmod_ret/hid_bpf_device_event")
+int BPF_PROG(raptor_mach_2_fix_hat_switch, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 64 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if (data[0] != 0x01) /* not the joystick report ID */
+ return 0;
+
+ data[33] /= 30;
+
+ return 0;
+}
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ ctx->retval = ctx->rdesc_size != 232;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ /* ensure the kernel isn't fixed already */
+ if (ctx->rdesc[177] != 0xef) /* Logical Max of 239 */
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/HP__Elite-Presenter.bpf.c b/drivers/hid/bpf/progs/HP__Elite-Presenter.bpf.c
new file mode 100644
index 0000000000..3d14bbb6f2
--- /dev/null
+++ b/drivers/hid/bpf/progs/HP__Elite-Presenter.bpf.c
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2023 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_HP 0x03F0
+#define PID_ELITE_PRESENTER 0x464A
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_HP, PID_ELITE_PRESENTER)
+);
+
+/*
+ * Already fixed as of commit 0db117359e47 ("HID: add quirk for 03f0:464a
+ * HP Elite Presenter Mouse") in the kernel, but this is a slightly better
+ * fix.
+ *
+ * The HP Elite Presenter Mouse HID Record Descriptor shows
+ * two mice (Report ID 0x1 and 0x2), one keypad (Report ID 0x5),
+ * two Consumer Controls (Report IDs 0x6 and 0x3).
+ * Prior to these fixes it registers one mouse, one keypad
+ * and one Consumer Control, and it was usable only as a
+ * digital laser pointer (one of the two mouses).
+ * We replace the second mouse collection with a pointer collection,
+ * allowing to use the device both as a mouse and a digital laser
+ * pointer.
+ */
+
+SEC("fmod_ret/hid_bpf_rdesc_fixup")
+int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* replace application mouse by application pointer on the second collection */
+ if (data[79] == 0x02)
+ data[79] = 0x01;
+
+ return 0;
+}
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ ctx->retval = ctx->rdesc_size != 264;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c b/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c
new file mode 100644
index 0000000000..ff759f2276
--- /dev/null
+++ b/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_HUION 0x256C
+#define PID_KAMVAS_PRO_19 0x006B
+#define NAME_KAMVAS_PRO_19 "HUION Huion Tablet_GT1902"
+
+#define TEST_PREFIX "uhid test "
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, VID_HUION, PID_KAMVAS_PRO_19),
+);
+
+bool prev_was_out_of_range;
+bool in_eraser_mode;
+
+/*
+ * We need to amend the report descriptor for the following:
+ * - the second button is reported through Secondary Tip Switch instead of Secondary Barrel Switch
+ * - the third button is reported through Invert, and we need some room to report it.
+ *
+ */
+static const __u8 fixed_rdesc[] = {
+ 0x05, 0x0d, // Usage Page (Digitizers) 0
+ 0x09, 0x02, // Usage (Pen) 2
+ 0xa1, 0x01, // Collection (Application) 4
+ 0x85, 0x0a, // Report ID (10) 6
+ 0x09, 0x20, // Usage (Stylus) 8
+ 0xa1, 0x01, // Collection (Application) 10
+ 0x09, 0x42, // Usage (Tip Switch) 12
+ 0x09, 0x44, // Usage (Barrel Switch) 14
+ 0x09, 0x5a, // Usage (Secondary Barrel Switch) 16 /* changed from Secondary Tip Switch */
+ 0x09, 0x3c, // Usage (Invert) 18
+ 0x09, 0x45, // Usage (Eraser) 20
+ 0x15, 0x00, // Logical Minimum (0) 22
+ 0x25, 0x01, // Logical Maximum (1) 24
+ 0x75, 0x01, // Report Size (1) 26
+ 0x95, 0x05, // Report Count (5) 28 /* changed (was 5) */
+ 0x81, 0x02, // Input (Data,Var,Abs) 30
+ 0x05, 0x09, // Usage Page (Button) /* inserted */
+ 0x09, 0x4a, // Usage (0x4a) /* inserted to be translated as input usage 0x149: BTN_STYLUS3 */
+ 0x95, 0x01, // Report Count (1) /* inserted */
+ 0x81, 0x02, // Input (Data,Var,Abs) /* inserted */
+ 0x05, 0x0d, // Usage Page (Digitizers) /* inserted */
+ 0x09, 0x32, // Usage (In Range) 32
+ 0x75, 0x01, // Report Size (1) 34
+ 0x95, 0x01, // Report Count (1) 36
+ 0x81, 0x02, // Input (Data,Var,Abs) 38
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 40
+ 0x05, 0x01, // Usage Page (Generic Desktop) 42
+ 0x09, 0x30, // Usage (X) 44
+ 0x09, 0x31, // Usage (Y) 46
+ 0x55, 0x0d, // Unit Exponent (-3) 48
+ 0x65, 0x33, // Unit (EnglishLinear: in³) 50
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 52
+ 0x35, 0x00, // Physical Minimum (0) 55
+ 0x46, 0x00, 0x08, // Physical Maximum (2048) 57
+ 0x75, 0x10, // Report Size (16) 60
+ 0x95, 0x02, // Report Count (2) 62
+ 0x81, 0x02, // Input (Data,Var,Abs) 64
+ 0x05, 0x0d, // Usage Page (Digitizers) 66
+ 0x09, 0x30, // Usage (Tip Pressure) 68
+ 0x26, 0xff, 0x3f, // Logical Maximum (16383) 70
+ 0x75, 0x10, // Report Size (16) 73
+ 0x95, 0x01, // Report Count (1) 75
+ 0x81, 0x02, // Input (Data,Var,Abs) 77
+ 0x09, 0x3d, // Usage (X Tilt) 79
+ 0x09, 0x3e, // Usage (Y Tilt) 81
+ 0x15, 0xa6, // Logical Minimum (-90) 83
+ 0x25, 0x5a, // Logical Maximum (90) 85
+ 0x75, 0x08, // Report Size (8) 87
+ 0x95, 0x02, // Report Count (2) 89
+ 0x81, 0x02, // Input (Data,Var,Abs) 91
+ 0xc0, // End Collection 93
+ 0xc0, // End Collection 94
+ 0x05, 0x0d, // Usage Page (Digitizers) 95
+ 0x09, 0x04, // Usage (Touch Screen) 97
+ 0xa1, 0x01, // Collection (Application) 99
+ 0x85, 0x04, // Report ID (4) 101
+ 0x09, 0x22, // Usage (Finger) 103
+ 0xa1, 0x02, // Collection (Logical) 105
+ 0x05, 0x0d, // Usage Page (Digitizers) 107
+ 0x95, 0x01, // Report Count (1) 109
+ 0x75, 0x06, // Report Size (6) 111
+ 0x09, 0x51, // Usage (Contact Id) 113
+ 0x15, 0x00, // Logical Minimum (0) 115
+ 0x25, 0x3f, // Logical Maximum (63) 117
+ 0x81, 0x02, // Input (Data,Var,Abs) 119
+ 0x09, 0x42, // Usage (Tip Switch) 121
+ 0x25, 0x01, // Logical Maximum (1) 123
+ 0x75, 0x01, // Report Size (1) 125
+ 0x95, 0x01, // Report Count (1) 127
+ 0x81, 0x02, // Input (Data,Var,Abs) 129
+ 0x75, 0x01, // Report Size (1) 131
+ 0x95, 0x01, // Report Count (1) 133
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 135
+ 0x05, 0x01, // Usage Page (Generic Desktop) 137
+ 0x75, 0x10, // Report Size (16) 139
+ 0x55, 0x0e, // Unit Exponent (-2) 141
+ 0x65, 0x11, // Unit (SILinear: cm) 143
+ 0x09, 0x30, // Usage (X) 145
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 147
+ 0x35, 0x00, // Physical Minimum (0) 150
+ 0x46, 0x15, 0x0c, // Physical Maximum (3093) 152
+ 0x81, 0x42, // Input (Data,Var,Abs,Null) 155
+ 0x09, 0x31, // Usage (Y) 157
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 159
+ 0x46, 0xcb, 0x06, // Physical Maximum (1739) 162
+ 0x81, 0x42, // Input (Data,Var,Abs,Null) 165
+ 0x05, 0x0d, // Usage Page (Digitizers) 167
+ 0x09, 0x30, // Usage (Tip Pressure) 169
+ 0x26, 0xff, 0x1f, // Logical Maximum (8191) 171
+ 0x75, 0x10, // Report Size (16) 174
+ 0x95, 0x01, // Report Count (1) 176
+ 0x81, 0x02, // Input (Data,Var,Abs) 178
+ 0xc0, // End Collection 180
+ 0x05, 0x0d, // Usage Page (Digitizers) 181
+ 0x09, 0x22, // Usage (Finger) 183
+ 0xa1, 0x02, // Collection (Logical) 185
+ 0x05, 0x0d, // Usage Page (Digitizers) 187
+ 0x95, 0x01, // Report Count (1) 189
+ 0x75, 0x06, // Report Size (6) 191
+ 0x09, 0x51, // Usage (Contact Id) 193
+ 0x15, 0x00, // Logical Minimum (0) 195
+ 0x25, 0x3f, // Logical Maximum (63) 197
+ 0x81, 0x02, // Input (Data,Var,Abs) 199
+ 0x09, 0x42, // Usage (Tip Switch) 201
+ 0x25, 0x01, // Logical Maximum (1) 203
+ 0x75, 0x01, // Report Size (1) 205
+ 0x95, 0x01, // Report Count (1) 207
+ 0x81, 0x02, // Input (Data,Var,Abs) 209
+ 0x75, 0x01, // Report Size (1) 211
+ 0x95, 0x01, // Report Count (1) 213
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 215
+ 0x05, 0x01, // Usage Page (Generic Desktop) 217
+ 0x75, 0x10, // Report Size (16) 219
+ 0x55, 0x0e, // Unit Exponent (-2) 221
+ 0x65, 0x11, // Unit (SILinear: cm) 223
+ 0x09, 0x30, // Usage (X) 225
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 227
+ 0x35, 0x00, // Physical Minimum (0) 230
+ 0x46, 0x15, 0x0c, // Physical Maximum (3093) 232
+ 0x81, 0x42, // Input (Data,Var,Abs,Null) 235
+ 0x09, 0x31, // Usage (Y) 237
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 239
+ 0x46, 0xcb, 0x06, // Physical Maximum (1739) 242
+ 0x81, 0x42, // Input (Data,Var,Abs,Null) 245
+ 0x05, 0x0d, // Usage Page (Digitizers) 247
+ 0x09, 0x30, // Usage (Tip Pressure) 249
+ 0x26, 0xff, 0x1f, // Logical Maximum (8191) 251
+ 0x75, 0x10, // Report Size (16) 254
+ 0x95, 0x01, // Report Count (1) 256
+ 0x81, 0x02, // Input (Data,Var,Abs) 258
+ 0xc0, // End Collection 260
+ 0x05, 0x0d, // Usage Page (Digitizers) 261
+ 0x09, 0x56, // Usage (Scan Time) 263
+ 0x55, 0x00, // Unit Exponent (0) 265
+ 0x65, 0x00, // Unit (None) 267
+ 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) 269
+ 0x95, 0x01, // Report Count (1) 274
+ 0x75, 0x20, // Report Size (32) 276
+ 0x81, 0x02, // Input (Data,Var,Abs) 278
+ 0x09, 0x54, // Usage (Contact Count) 280
+ 0x25, 0x7f, // Logical Maximum (127) 282
+ 0x95, 0x01, // Report Count (1) 284
+ 0x75, 0x08, // Report Size (8) 286
+ 0x81, 0x02, // Input (Data,Var,Abs) 288
+ 0x75, 0x08, // Report Size (8) 290
+ 0x95, 0x08, // Report Count (8) 292
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 294
+ 0x85, 0x05, // Report ID (5) 296
+ 0x09, 0x55, // Usage (Contact Max) 298
+ 0x25, 0x0a, // Logical Maximum (10) 300
+ 0x75, 0x08, // Report Size (8) 302
+ 0x95, 0x01, // Report Count (1) 304
+ 0xb1, 0x02, // Feature (Data,Var,Abs) 306
+ 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 308
+ 0x09, 0xc5, // Usage (Vendor Usage 0xc5) 311
+ 0x85, 0x06, // Report ID (6) 313
+ 0x15, 0x00, // Logical Minimum (0) 315
+ 0x26, 0xff, 0x00, // Logical Maximum (255) 317
+ 0x75, 0x08, // Report Size (8) 320
+ 0x96, 0x00, 0x01, // Report Count (256) 322
+ 0xb1, 0x02, // Feature (Data,Var,Abs) 325
+ 0xc0, // End Collection 327
+};
+
+SEC("fmod_ret/hid_bpf_rdesc_fixup")
+int BPF_PROG(hid_fix_rdesc_huion_kamvas_pro_19, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ __builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
+
+ return sizeof(fixed_rdesc);
+}
+
+/*
+ * This tablet reports the 3rd button through invert, but this conflict
+ * with the normal eraser mode.
+ * Fortunately, before entering eraser mode, (so Invert = 1),
+ * the tablet always sends an out-of-proximity event.
+ * So we can detect that single event and:
+ * - if there was none but the invert bit was toggled: this is the
+ * third button
+ * - if there was this out-of-proximity event, we are entering
+ * eraser mode, and we will until the next out-of-proximity.
+ */
+SEC("fmod_ret/hid_bpf_device_event")
+int BPF_PROG(kamvas_pro_19_fix_3rd_button, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if (data[0] != 0x0a) /* not the pen report ID */
+ return 0;
+
+ /* stylus is out of range */
+ if (!(data[1] & 0x40)) {
+ prev_was_out_of_range = true;
+ in_eraser_mode = false;
+ return 0;
+ }
+
+ /* going into eraser mode (Invert = 1) only happens after an
+ * out of range event
+ */
+ if (prev_was_out_of_range && (data[1] & 0x18))
+ in_eraser_mode = true;
+
+ /* eraser mode works fine */
+ if (in_eraser_mode)
+ return 0;
+
+ /* copy the Invert bit reported for the 3rd button in bit 7 */
+ if (data[1] & 0x08)
+ data[1] |= 0x20;
+
+ /* clear Invert bit now that it was copied */
+ data[1] &= 0xf7;
+
+ prev_was_out_of_range = false;
+
+ return 0;
+}
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ ctx->retval = ctx->rdesc_size != 328;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ /* ensure the kernel isn't fixed already */
+ if (ctx->rdesc[17] != 0x43) /* Secondary Tip Switch */
+ ctx->retval = -EINVAL;
+
+ struct hid_bpf_ctx *hctx = hid_bpf_allocate_context(ctx->hid);
+
+ if (!hctx) {
+ return ctx->retval = -EINVAL;
+ return 0;
+ }
+
+ const char *name = hctx->hid->name;
+
+ /* strip out TEST_PREFIX */
+ if (!__builtin_memcmp(name, TEST_PREFIX, sizeof(TEST_PREFIX) - 1))
+ name += sizeof(TEST_PREFIX) - 1;
+
+ if (__builtin_memcmp(name, NAME_KAMVAS_PRO_19, sizeof(NAME_KAMVAS_PRO_19)))
+ ctx->retval = -EINVAL;
+
+ hid_bpf_release_context(hctx);
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c b/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c
new file mode 100644
index 0000000000..225cbefdbf
--- /dev/null
+++ b/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2023 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_IOGEAR 0x258A /* VID is shared with SinoWealth and Glorious and prob others */
+#define PID_MOMENTUM 0x0027
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_IOGEAR, PID_MOMENTUM)
+);
+
+/*
+ * The IOGear Kaliber Gaming MMOmentum Pro mouse has multiple buttons (12)
+ * but only 5 are accessible out of the box because the report descriptor
+ * marks the other buttons as constants.
+ * We just fix the report descriptor to enable those missing 7 buttons.
+ */
+
+SEC("fmod_ret/hid_bpf_rdesc_fixup")
+int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
+{
+ const u8 offsets[] = {84, 112, 140};
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* if not Keyboard */
+ if (data[3] != 0x06)
+ return 0;
+
+ for (int idx = 0; idx < ARRAY_SIZE(offsets); idx++) {
+ u8 offset = offsets[idx];
+
+ /* if Input (Cnst,Var,Abs) , make it Input (Data,Var,Abs) */
+ if (data[offset] == 0x81 && data[offset + 1] == 0x03)
+ data[offset + 1] = 0x02;
+ }
+
+ return 0;
+}
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ /* only bind to the keyboard interface */
+ ctx->retval = ctx->rdesc_size != 213;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/Makefile b/drivers/hid/bpf/progs/Makefile
new file mode 100644
index 0000000000..63ed7e02ad
--- /dev/null
+++ b/drivers/hid/bpf/progs/Makefile
@@ -0,0 +1,91 @@
+# SPDX-License-Identifier: GPL-2.0
+OUTPUT := .output
+abs_out := $(abspath $(OUTPUT))
+
+CLANG ?= clang
+LLC ?= llc
+LLVM_STRIP ?= llvm-strip
+
+TOOLS_PATH := $(abspath ../../../../tools)
+BPFTOOL_SRC := $(TOOLS_PATH)/bpf/bpftool
+BPFTOOL_OUTPUT := $(abs_out)/bpftool
+DEFAULT_BPFTOOL := $(BPFTOOL_OUTPUT)/bootstrap/bpftool
+BPFTOOL ?= $(DEFAULT_BPFTOOL)
+
+LIBBPF_SRC := $(TOOLS_PATH)/lib/bpf
+LIBBPF_OUTPUT := $(abs_out)/libbpf
+LIBBPF_DESTDIR := $(LIBBPF_OUTPUT)
+LIBBPF_INCLUDE := $(LIBBPF_DESTDIR)/include
+BPFOBJ := $(LIBBPF_OUTPUT)/libbpf.a
+
+INCLUDES := -I$(OUTPUT) -I$(LIBBPF_INCLUDE) -I$(TOOLS_PATH)/include/uapi
+CFLAGS := -g -Wall
+
+VMLINUX_BTF_PATHS ?= $(if $(O),$(O)/vmlinux) \
+ $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux) \
+ ../../../../vmlinux \
+ /sys/kernel/btf/vmlinux \
+ /boot/vmlinux-$(shell uname -r)
+VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS))))
+ifeq ($(VMLINUX_BTF),)
+$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)")
+endif
+
+ifeq ($(V),1)
+Q =
+msg =
+else
+Q = @
+msg = @printf ' %-8s %s%s\n' "$(1)" "$(notdir $(2))" "$(if $(3), $(3))";
+MAKEFLAGS += --no-print-directory
+submake_extras := feature_display=0
+endif
+
+.DELETE_ON_ERROR:
+
+.PHONY: all clean
+
+SOURCES = $(wildcard *.bpf.c)
+TARGETS = $(SOURCES:.bpf.c=.bpf.o)
+
+all: $(TARGETS)
+
+clean:
+ $(call msg,CLEAN)
+ $(Q)rm -rf $(OUTPUT) $(TARGETS)
+
+%.bpf.o: %.bpf.c vmlinux.h $(BPFOBJ) | $(OUTPUT)
+ $(call msg,BPF,$@)
+ $(Q)$(CLANG) -g -O2 --target=bpf $(INCLUDES) \
+ -c $(filter %.c,$^) -o $@ && \
+ $(LLVM_STRIP) -g $@
+
+vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR)
+ifeq ($(VMLINUX_H),)
+ $(call msg,GEN,,$@)
+ $(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
+else
+ $(call msg,CP,,$@)
+ $(Q)cp "$(VMLINUX_H)" $@
+endif
+
+$(OUTPUT) $(LIBBPF_OUTPUT) $(BPFTOOL_OUTPUT):
+ $(call msg,MKDIR,$@)
+ $(Q)mkdir -p $@
+
+$(BPFOBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(LIBBPF_OUTPUT)
+ $(Q)$(MAKE) $(submake_extras) -C $(LIBBPF_SRC) \
+ OUTPUT=$(abspath $(dir $@))/ prefix= \
+ DESTDIR=$(LIBBPF_DESTDIR) $(abspath $@) install_headers
+
+ifeq ($(CROSS_COMPILE),)
+$(DEFAULT_BPFTOOL): $(BPFOBJ) | $(BPFTOOL_OUTPUT)
+ $(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC) \
+ OUTPUT=$(BPFTOOL_OUTPUT)/ \
+ LIBBPF_BOOTSTRAP_OUTPUT=$(LIBBPF_OUTPUT)/ \
+ LIBBPF_BOOTSTRAP_DESTDIR=$(LIBBPF_DESTDIR)/ bootstrap
+else
+$(DEFAULT_BPFTOOL): | $(BPFTOOL_OUTPUT)
+ $(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC) \
+ OUTPUT=$(BPFTOOL_OUTPUT)/ bootstrap
+endif
diff --git a/drivers/hid/bpf/progs/Microsoft__XBox-Elite-2.bpf.c b/drivers/hid/bpf/progs/Microsoft__XBox-Elite-2.bpf.c
new file mode 100644
index 0000000000..c04abecab8
--- /dev/null
+++ b/drivers/hid/bpf/progs/Microsoft__XBox-Elite-2.bpf.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_MICROSOFT 0x045e
+#define PID_XBOX_ELITE_2 0x0b22
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_MICROSOFT, PID_XBOX_ELITE_2)
+);
+
+/*
+ * When using the XBox Wireless Controller Elite 2 over Bluetooth,
+ * the device exports the paddle on the back of the device as a single
+ * bitfield value of usage "Assign Selection".
+ *
+ * The kernel doesn't process those usages properly and report KEY_UNKNOWN
+ * for it.
+ *
+ * SDL doesn't know how to interprete that KEY_UNKNOWN and thus ignores the paddles.
+ *
+ * Given that over USB the kernel uses BTN_TRIGGER_HAPPY[5-8], we
+ * can tweak the report descriptor to make the kernel interprete it properly:
+ * - we need an application collection of gamepad (so we have to close the current
+ * Consumer Control one)
+ * - we need to change the usage to be buttons from 0x15 to 0x18
+ */
+
+#define OFFSET_ASSIGN_SELECTION 211
+#define ORIGINAL_RDESC_SIZE 464
+
+const __u8 rdesc_assign_selection[] = {
+ 0x0a, 0x99, 0x00, // Usage (Media Select Security) 211
+ 0x15, 0x00, // Logical Minimum (0) 214
+ 0x26, 0xff, 0x00, // Logical Maximum (255) 216
+ 0x95, 0x01, // Report Count (1) 219
+ 0x75, 0x04, // Report Size (4) 221
+ 0x81, 0x02, // Input (Data,Var,Abs) 223
+ 0x15, 0x00, // Logical Minimum (0) 225
+ 0x25, 0x00, // Logical Maximum (0) 227
+ 0x95, 0x01, // Report Count (1) 229
+ 0x75, 0x04, // Report Size (4) 231
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 233
+ 0x0a, 0x81, 0x00, // Usage (Assign Selection) 235
+ 0x15, 0x00, // Logical Minimum (0) 238
+ 0x26, 0xff, 0x00, // Logical Maximum (255) 240
+ 0x95, 0x01, // Report Count (1) 243
+ 0x75, 0x04, // Report Size (4) 245
+ 0x81, 0x02, // Input (Data,Var,Abs) 247
+};
+
+/*
+ * we replace the above report descriptor extract
+ * with the one below.
+ * To make things equal in size, we take out a larger
+ * portion than just the "Assign Selection" range, because
+ * we need to insert a new application collection to force
+ * the kernel to use BTN_TRIGGER_HAPPY[4-7].
+ */
+const __u8 fixed_rdesc_assign_selection[] = {
+ 0x0a, 0x99, 0x00, // Usage (Media Select Security) 211
+ 0x15, 0x00, // Logical Minimum (0) 214
+ 0x26, 0xff, 0x00, // Logical Maximum (255) 216
+ 0x95, 0x01, // Report Count (1) 219
+ 0x75, 0x04, // Report Size (4) 221
+ 0x81, 0x02, // Input (Data,Var,Abs) 223
+ /* 0x15, 0x00, */ // Logical Minimum (0) ignored
+ 0x25, 0x01, // Logical Maximum (1) 225
+ 0x95, 0x04, // Report Count (4) 227
+ 0x75, 0x01, // Report Size (1) 229
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 231
+ 0xc0, // End Collection 233
+ 0x05, 0x01, // Usage Page (Generic Desktop) 234
+ 0x0a, 0x05, 0x00, // Usage (Game Pad) 236
+ 0xa1, 0x01, // Collection (Application) 239
+ 0x05, 0x09, // Usage Page (Button) 241
+ 0x19, 0x15, // Usage Minimum (21) 243
+ 0x29, 0x18, // Usage Maximum (24) 245
+ /* 0x15, 0x00, */ // Logical Minimum (0) ignored
+ /* 0x25, 0x01, */ // Logical Maximum (1) ignored
+ /* 0x95, 0x01, */ // Report Size (1) ignored
+ /* 0x75, 0x04, */ // Report Count (4) ignored
+ 0x81, 0x02, // Input (Data,Var,Abs) 247
+};
+
+_Static_assert(sizeof(rdesc_assign_selection) == sizeof(fixed_rdesc_assign_selection),
+ "Rdesc and fixed rdesc of different size");
+_Static_assert(sizeof(rdesc_assign_selection) + OFFSET_ASSIGN_SELECTION < ORIGINAL_RDESC_SIZE,
+ "Rdesc at given offset is too big");
+
+SEC("fmod_ret/hid_bpf_rdesc_fixup")
+int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* Check that the device is compatible */
+ if (__builtin_memcmp(data + OFFSET_ASSIGN_SELECTION,
+ rdesc_assign_selection,
+ sizeof(rdesc_assign_selection)))
+ return 0;
+
+ __builtin_memcpy(data + OFFSET_ASSIGN_SELECTION,
+ fixed_rdesc_assign_selection,
+ sizeof(fixed_rdesc_assign_selection));
+
+ return 0;
+}
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ /* only bind to the keyboard interface */
+ ctx->retval = ctx->rdesc_size != ORIGINAL_RDESC_SIZE;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ if (__builtin_memcmp(ctx->rdesc + OFFSET_ASSIGN_SELECTION,
+ rdesc_assign_selection,
+ sizeof(rdesc_assign_selection)))
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/README b/drivers/hid/bpf/progs/README
new file mode 100644
index 0000000000..20b0928f38
--- /dev/null
+++ b/drivers/hid/bpf/progs/README
@@ -0,0 +1,102 @@
+# HID-BPF programs
+
+This directory contains various fixes for devices. They add new features or
+fix some behaviors without being entirely mandatory. It is better to load them
+when you have such a device, but they should not be a requirement for a device
+to be working during the boot stage.
+
+The .bpf.c files provided here are not automatically compiled in the kernel.
+They should be loaded in the kernel by `udev-hid-bpf`:
+
+https://gitlab.freedesktop.org/libevdev/udev-hid-bpf
+
+The main reasons for these fixes to be here is to have a central place to
+"upstream" them, but also this way we can test them thanks to the HID
+selftests.
+
+Once a .bpf.c file is accepted here, it is duplicated in `udev-hid-bpf`
+in the `src/bpf/stable` directory, and distributions are encouraged to
+only ship those bpf objects. So adding a file here should eventually
+land in distributions when they update `udev-hid-bpf`
+
+## Compilation
+
+Just run `make`
+
+## Installation
+
+### Automated way
+
+Just run `sudo udev-hid-bpf install ./my-awesome-fix.bpf.o`
+
+### Manual way
+
+- copy the `.bpf.o` you want in `/etc/udev-hid-bpf/`
+- create a new udev rule to automatically load it
+
+The following should do the trick (assuming udev-hid-bpf is available in
+/usr/bin):
+
+```
+$> cp xppen-ArtistPro16Gen2.bpf.o /etc/udev-hid-bpf/
+$> udev-hid-bpf inspect xppen-ArtistPro16Gen2.bpf.o
+[
+ {
+ "name": "xppen-ArtistPro16Gen2.bpf.o",
+ "devices": [
+ {
+ "bus": "0x0003",
+ "group": "0x0001",
+ "vid": "0x28BD",
+ "pid": "0x095A"
+ },
+ {
+ "bus": "0x0003",
+ "group": "0x0001",
+ "vid": "0x28BD",
+ "pid": "0x095B"
+ }
+ ],
+...
+$> cat <EOF > /etc/udev/rules.d/99-load-hid-bpf-xppen-ArtistPro16Gen2.rules
+ACTION!="add|remove", GOTO="hid_bpf_end"
+SUBSYSTEM!="hid", GOTO="hid_bpf_end"
+
+# xppen-ArtistPro16Gen2.bpf.o
+ACTION=="add",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095A", RUN{program}+="/usr/local/bin/udev-hid-bpf add $sys$devpath /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o"
+ACTION=="remove",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095A", RUN{program}+="/usr/local/bin/udev-hid-bpf remove $sys$devpath "
+# xppen-ArtistPro16Gen2.bpf.o
+ACTION=="add",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095B", RUN{program}+="/usr/local/bin/udev-hid-bpf add $sys$devpath /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o"
+ACTION=="remove",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095B", RUN{program}+="/usr/local/bin/udev-hid-bpf remove $sys$devpath "
+
+LABEL="hid_bpf_end"
+EOF
+$> udevadm control --reload
+```
+
+Then unplug and replug the device.
+
+## Checks
+
+### udev rule
+
+You can check that the udev rule is correctly working by issuing
+
+```
+$> udevadm test /sys/bus/hid/devices/0003:28BD:095B*
+...
+run: '/usr/local/bin/udev-hid-bpf add /sys/devices/virtual/misc/uhid/0003:28BD:095B.0E57 /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o'
+```
+
+### program loaded
+
+You can check that the program has been properly loaded with `bpftool`
+
+```
+$> bpftool prog
+...
+247: tracing name xppen_16_fix_eraser tag 18d389353ed2ef07 gpl
+ loaded_at 2024-03-28T16:02:28+0100 uid 0
+ xlated 120B jited 77B memlock 4096B
+ btf_id 487
+```
diff --git a/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c b/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c
new file mode 100644
index 0000000000..dc05aa48fa
--- /dev/null
+++ b/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_WACOM 0x056a
+#define ART_PEN_ID 0x0804
+#define PID_INTUOS_PRO_2_M 0x0357
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_WACOM, PID_INTUOS_PRO_2_M)
+);
+
+/*
+ * This filter is here for the Art Pen stylus only:
+ * - when used on some Wacom devices (see the list of attached PIDs), this pen
+ * reports pressure every other events.
+ * - to solve that, given that we know that the next event will be the same as
+ * the current one, we can emulate a smoother pressure reporting by reporting
+ * the mean of the previous value and the current one.
+ *
+ * We are effectively delaying the pressure by one event every other event, but
+ * that's less of an annoyance compared to the chunkiness of the reported data.
+ *
+ * For example, let's assume the following set of events:
+ * <Tip switch 0> <X 0> <Y 0> <Pressure 0 > <Tooltype 0x0804>
+ * <Tip switch 1> <X 1> <Y 1> <Pressure 100 > <Tooltype 0x0804>
+ * <Tip switch 1> <X 2> <Y 2> <Pressure 100 > <Tooltype 0x0804>
+ * <Tip switch 1> <X 3> <Y 3> <Pressure 200 > <Tooltype 0x0804>
+ * <Tip switch 1> <X 4> <Y 4> <Pressure 200 > <Tooltype 0x0804>
+ * <Tip switch 0> <X 5> <Y 5> <Pressure 0 > <Tooltype 0x0804>
+ *
+ * The filter will report:
+ * <Tip switch 0> <X 0> <Y 0> <Pressure 0 > <Tooltype 0x0804>
+ * <Tip switch 1> <X 1> <Y 1> <Pressure * 50*> <Tooltype 0x0804>
+ * <Tip switch 1> <X 2> <Y 2> <Pressure 100 > <Tooltype 0x0804>
+ * <Tip switch 1> <X 3> <Y 3> <Pressure *150*> <Tooltype 0x0804>
+ * <Tip switch 1> <X 4> <Y 4> <Pressure 200 > <Tooltype 0x0804>
+ * <Tip switch 0> <X 5> <Y 5> <Pressure 0 > <Tooltype 0x0804>
+ *
+ */
+
+struct wacom_params {
+ __u16 pid;
+ __u16 rdesc_len;
+ __u8 report_id;
+ __u8 report_len;
+ struct {
+ __u8 tip_switch;
+ __u8 pressure;
+ __u8 tool_type;
+ } offsets;
+};
+
+/*
+ * Multiple device can support the same stylus, so
+ * we need to know which device has which offsets
+ */
+static const struct wacom_params devices[] = {
+ {
+ .pid = PID_INTUOS_PRO_2_M,
+ .rdesc_len = 949,
+ .report_id = 16,
+ .report_len = 27,
+ .offsets = {
+ .tip_switch = 1,
+ .pressure = 8,
+ .tool_type = 25,
+ },
+ },
+};
+
+static struct wacom_params params = { 0 };
+
+/* HID-BPF reports a 64 bytes chunk anyway, so this ensures
+ * the verifier to know we are addressing the memory correctly
+ */
+#define PEN_REPORT_LEN 64
+
+/* only odd frames are modified */
+static bool odd;
+
+static __u16 prev_pressure;
+
+static inline void *get_bits(__u8 *data, unsigned int byte_offset)
+{
+ return data + byte_offset;
+}
+
+static inline __u16 *get_u16(__u8 *data, unsigned int offset)
+{
+ return (__u16 *)get_bits(data, offset);
+}
+
+static inline __u8 *get_u8(__u8 *data, unsigned int offset)
+{
+ return (__u8 *)get_bits(data, offset);
+}
+
+SEC("fmod_ret/hid_bpf_device_event")
+int BPF_PROG(artpen_pressure_interpolate, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PEN_REPORT_LEN /* size */);
+ __u16 *pressure, *tool_type;
+ __u8 *tip_switch;
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if (data[0] != params.report_id ||
+ params.offsets.tip_switch >= PEN_REPORT_LEN ||
+ params.offsets.pressure >= PEN_REPORT_LEN - 1 ||
+ params.offsets.tool_type >= PEN_REPORT_LEN - 1)
+ return 0; /* invalid report or parameters */
+
+ tool_type = get_u16(data, params.offsets.tool_type);
+ if (*tool_type != ART_PEN_ID)
+ return 0;
+
+ tip_switch = get_u8(data, params.offsets.tip_switch);
+ if ((*tip_switch & 0x01) == 0) {
+ prev_pressure = 0;
+ odd = true;
+ return 0;
+ }
+
+ pressure = get_u16(data, params.offsets.pressure);
+
+ if (odd)
+ *pressure = (*pressure + prev_pressure) / 2;
+
+ prev_pressure = *pressure;
+ odd = !odd;
+
+ return 0;
+}
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ struct hid_bpf_ctx *hid_ctx;
+ __u16 pid;
+ int i;
+
+ /* get a struct hid_device to access the actual pid of the device */
+ hid_ctx = hid_bpf_allocate_context(ctx->hid);
+ if (!hid_ctx) {
+ ctx->retval = -ENODEV;
+ return -1; /* EPERM check */
+ }
+ pid = hid_ctx->hid->product;
+
+ ctx->retval = -EINVAL;
+
+ /* Match the given device with the list of known devices */
+ for (i = 0; i < ARRAY_SIZE(devices); i++) {
+ const struct wacom_params *device = &devices[i];
+
+ if (device->pid == pid && device->rdesc_len == ctx->rdesc_size) {
+ params = *device;
+ ctx->retval = 0;
+ }
+ }
+
+ hid_bpf_release_context(hid_ctx);
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c b/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c
new file mode 100644
index 0000000000..e1be6a12bb
--- /dev/null
+++ b/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2023 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */
+#define PID_ARTIST_24 0x093A
+#define PID_ARTIST_24_PRO 0x092D
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24),
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24_PRO)
+);
+
+/*
+ * We need to amend the report descriptor for the following:
+ * - the device reports Eraser instead of using Secondary Barrel Switch
+ * - the pen doesn't have a rubber tail, so basically we are removing any
+ * eraser/invert bits
+ */
+static const __u8 fixed_rdesc[] = {
+ 0x05, 0x0d, // Usage Page (Digitizers) 0
+ 0x09, 0x02, // Usage (Pen) 2
+ 0xa1, 0x01, // Collection (Application) 4
+ 0x85, 0x07, // Report ID (7) 6
+ 0x09, 0x20, // Usage (Stylus) 8
+ 0xa1, 0x00, // Collection (Physical) 10
+ 0x09, 0x42, // Usage (Tip Switch) 12
+ 0x09, 0x44, // Usage (Barrel Switch) 14
+ 0x09, 0x5a, // Usage (Secondary Barrel Switch) 16 /* changed from 0x45 (Eraser) to 0x5a (Secondary Barrel Switch) */
+ 0x15, 0x00, // Logical Minimum (0) 18
+ 0x25, 0x01, // Logical Maximum (1) 20
+ 0x75, 0x01, // Report Size (1) 22
+ 0x95, 0x03, // Report Count (3) 24
+ 0x81, 0x02, // Input (Data,Var,Abs) 26
+ 0x95, 0x02, // Report Count (2) 28
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 30
+ 0x09, 0x32, // Usage (In Range) 32
+ 0x95, 0x01, // Report Count (1) 34
+ 0x81, 0x02, // Input (Data,Var,Abs) 36
+ 0x95, 0x02, // Report Count (2) 38
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 40
+ 0x75, 0x10, // Report Size (16) 42
+ 0x95, 0x01, // Report Count (1) 44
+ 0x35, 0x00, // Physical Minimum (0) 46
+ 0xa4, // Push 48
+ 0x05, 0x01, // Usage Page (Generic Desktop) 49
+ 0x09, 0x30, // Usage (X) 51
+ 0x65, 0x13, // Unit (EnglishLinear: in) 53
+ 0x55, 0x0d, // Unit Exponent (-3) 55
+ 0x46, 0xf0, 0x50, // Physical Maximum (20720) 57
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 60
+ 0x81, 0x02, // Input (Data,Var,Abs) 63
+ 0x09, 0x31, // Usage (Y) 65
+ 0x46, 0x91, 0x2d, // Physical Maximum (11665) 67
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 70
+ 0x81, 0x02, // Input (Data,Var,Abs) 73
+ 0xb4, // Pop 75
+ 0x09, 0x30, // Usage (Tip Pressure) 76
+ 0x45, 0x00, // Physical Maximum (0) 78
+ 0x26, 0xff, 0x1f, // Logical Maximum (8191) 80
+ 0x81, 0x42, // Input (Data,Var,Abs,Null) 83
+ 0x09, 0x3d, // Usage (X Tilt) 85
+ 0x15, 0x81, // Logical Minimum (-127) 87
+ 0x25, 0x7f, // Logical Maximum (127) 89
+ 0x75, 0x08, // Report Size (8) 91
+ 0x95, 0x01, // Report Count (1) 93
+ 0x81, 0x02, // Input (Data,Var,Abs) 95
+ 0x09, 0x3e, // Usage (Y Tilt) 97
+ 0x15, 0x81, // Logical Minimum (-127) 99
+ 0x25, 0x7f, // Logical Maximum (127) 101
+ 0x81, 0x02, // Input (Data,Var,Abs) 103
+ 0xc0, // End Collection 105
+ 0xc0, // End Collection 106
+};
+
+#define BIT(n) (1UL << n)
+
+#define TIP_SWITCH BIT(0)
+#define BARREL_SWITCH BIT(1)
+#define ERASER BIT(2)
+/* padding BIT(3) */
+/* padding BIT(4) */
+#define IN_RANGE BIT(5)
+/* padding BIT(6) */
+/* padding BIT(7) */
+
+#define U16(index) (data[index] | (data[index + 1] << 8))
+
+SEC("fmod_ret/hid_bpf_rdesc_fixup")
+int BPF_PROG(hid_fix_rdesc_xppen_artist24, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ __builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
+
+ return sizeof(fixed_rdesc);
+}
+
+static __u8 prev_state = 0;
+
+/*
+ * There are a few cases where the device is sending wrong event
+ * sequences, all related to the second button (the pen doesn't
+ * have an eraser switch on the tail end):
+ *
+ * whenever the second button gets pressed or released, an
+ * out-of-proximity event is generated and then the firmware
+ * compensate for the missing state (and the firmware uses
+ * eraser for that button):
+ *
+ * - if the pen is in range, an extra out-of-range is sent
+ * when the second button is pressed/released:
+ * // Pen is in range
+ * E: InRange
+ *
+ * // Second button is pressed
+ * E:
+ * E: Eraser InRange
+ *
+ * // Second button is released
+ * E:
+ * E: InRange
+ *
+ * This case is ignored by this filter, it's "valid"
+ * and userspace knows how to deal with it, there are just
+ * a few out-of-prox events generated, but the user doesn´t
+ * see them.
+ *
+ * - if the pen is in contact, 2 extra events are added when
+ * the second button is pressed/released: an out of range
+ * and an in range:
+ *
+ * // Pen is in contact
+ * E: TipSwitch InRange
+ *
+ * // Second button is pressed
+ * E: <- false release, needs to be filtered out
+ * E: Eraser InRange <- false release, needs to be filtered out
+ * E: TipSwitch Eraser InRange
+ *
+ * // Second button is released
+ * E: <- false release, needs to be filtered out
+ * E: InRange <- false release, needs to be filtered out
+ * E: TipSwitch InRange
+ *
+ */
+SEC("fmod_ret/hid_bpf_device_event")
+int BPF_PROG(xppen_24_fix_eraser, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
+ __u8 current_state, changed_state;
+ bool prev_tip;
+ __u16 tilt;
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ current_state = data[1];
+
+ /* if the state is identical to previously, early return */
+ if (current_state == prev_state)
+ return 0;
+
+ prev_tip = !!(prev_state & TIP_SWITCH);
+
+ /*
+ * Illegal transition: pen is in range with the tip pressed, and
+ * it goes into out of proximity.
+ *
+ * Ideally we should hold the event, start a timer and deliver it
+ * only if the timer ends, but we are not capable of that now.
+ *
+ * And it doesn't matter because when we are in such cases, this
+ * means we are detecting a false release.
+ */
+ if ((current_state & IN_RANGE) == 0) {
+ if (prev_tip)
+ return HID_IGNORE_EVENT;
+ return 0;
+ }
+
+ /*
+ * XOR to only set the bits that have changed between
+ * previous and current state
+ */
+ changed_state = prev_state ^ current_state;
+
+ /* Store the new state for future processing */
+ prev_state = current_state;
+
+ /*
+ * We get both a tipswitch and eraser change in the same HID report:
+ * this is not an authorized transition and is unlikely to happen
+ * in real life.
+ * This is likely to be added by the firmware to emulate the
+ * eraser mode so we can skip the event.
+ */
+ if ((changed_state & (TIP_SWITCH | ERASER)) == (TIP_SWITCH | ERASER)) /* we get both a tipswitch and eraser change at the same time */
+ return HID_IGNORE_EVENT;
+
+ return 0;
+}
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ /*
+ * The device exports 3 interfaces.
+ */
+ ctx->retval = ctx->rdesc_size != 107;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ /* ensure the kernel isn't fixed already */
+ if (ctx->rdesc[17] != 0x45) /* Eraser */
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c b/drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c
new file mode 100644
index 0000000000..65ef100361
--- /dev/null
+++ b/drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2023 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */
+#define PID_ARTIST_PRO14_GEN2 0x095A
+#define PID_ARTIST_PRO16_GEN2 0x095B
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_PRO14_GEN2),
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_PRO16_GEN2)
+);
+
+/*
+ * We need to amend the report descriptor for the following:
+ * - the device reports Eraser instead of using Secondary Barrel Switch
+ * - when the eraser button is pressed and the stylus is touching the tablet,
+ * the device sends Tip Switch instead of sending Eraser
+ *
+ * This descriptor uses physical dimensions of the 16" device.
+ */
+static const __u8 fixed_rdesc[] = {
+ 0x05, 0x0d, // Usage Page (Digitizers) 0
+ 0x09, 0x02, // Usage (Pen) 2
+ 0xa1, 0x01, // Collection (Application) 4
+ 0x85, 0x07, // Report ID (7) 6
+ 0x09, 0x20, // Usage (Stylus) 8
+ 0xa1, 0x00, // Collection (Physical) 10
+ 0x09, 0x42, // Usage (Tip Switch) 12
+ 0x09, 0x44, // Usage (Barrel Switch) 14
+ 0x09, 0x5a, // Usage (Secondary Barrel Switch) 16 /* changed from 0x45 (Eraser) to 0x5a (Secondary Barrel Switch) */
+ 0x09, 0x3c, // Usage (Invert) 18
+ 0x09, 0x45, // Usage (Eraser) 16 /* created over a padding bit at offset 29-33 */
+ 0x15, 0x00, // Logical Minimum (0) 20
+ 0x25, 0x01, // Logical Maximum (1) 22
+ 0x75, 0x01, // Report Size (1) 24
+ 0x95, 0x05, // Report Count (5) 26 /* changed from 4 to 5 */
+ 0x81, 0x02, // Input (Data,Var,Abs) 28
+ 0x09, 0x32, // Usage (In Range) 34
+ 0x15, 0x00, // Logical Minimum (0) 36
+ 0x25, 0x01, // Logical Maximum (1) 38
+ 0x95, 0x01, // Report Count (1) 40
+ 0x81, 0x02, // Input (Data,Var,Abs) 42
+ 0x95, 0x02, // Report Count (2) 44
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 46
+ 0x75, 0x10, // Report Size (16) 48
+ 0x95, 0x01, // Report Count (1) 50
+ 0x35, 0x00, // Physical Minimum (0) 52
+ 0xa4, // Push 54
+ 0x05, 0x01, // Usage Page (Generic Desktop) 55
+ 0x09, 0x30, // Usage (X) 57
+ 0x65, 0x13, // Unit (EnglishLinear: in) 59
+ 0x55, 0x0d, // Unit Exponent (-3) 61
+ 0x46, 0xff, 0x34, // Physical Maximum (13567) 63
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 66
+ 0x81, 0x02, // Input (Data,Var,Abs) 69
+ 0x09, 0x31, // Usage (Y) 71
+ 0x46, 0x20, 0x21, // Physical Maximum (8480) 73
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 76
+ 0x81, 0x02, // Input (Data,Var,Abs) 79
+ 0xb4, // Pop 81
+ 0x09, 0x30, // Usage (Tip Pressure) 82
+ 0x45, 0x00, // Physical Maximum (0) 84
+ 0x26, 0xff, 0x3f, // Logical Maximum (16383) 86
+ 0x81, 0x42, // Input (Data,Var,Abs,Null) 89
+ 0x09, 0x3d, // Usage (X Tilt) 91
+ 0x15, 0x81, // Logical Minimum (-127) 93
+ 0x25, 0x7f, // Logical Maximum (127) 95
+ 0x75, 0x08, // Report Size (8) 97
+ 0x95, 0x01, // Report Count (1) 99
+ 0x81, 0x02, // Input (Data,Var,Abs) 101
+ 0x09, 0x3e, // Usage (Y Tilt) 103
+ 0x15, 0x81, // Logical Minimum (-127) 105
+ 0x25, 0x7f, // Logical Maximum (127) 107
+ 0x81, 0x02, // Input (Data,Var,Abs) 109
+ 0xc0, // End Collection 111
+ 0xc0, // End Collection 112
+};
+
+SEC("fmod_ret/hid_bpf_rdesc_fixup")
+int BPF_PROG(hid_fix_rdesc_xppen_artistpro16gen2, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ __builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
+
+ /* Fix the Physical maximum values for different sizes of the device
+ * The 14" screen device descriptor size is 11.874" x 7.421"
+ */
+ if (hctx->hid->product == PID_ARTIST_PRO14_GEN2) {
+ data[63] = 0x2e;
+ data[62] = 0x62;
+ data[73] = 0x1c;
+ data[72] = 0xfd;
+ }
+
+ return sizeof(fixed_rdesc);
+}
+
+SEC("fmod_ret/hid_bpf_device_event")
+int BPF_PROG(xppen_16_fix_eraser, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if ((data[1] & 0x29) != 0x29) /* tip switch=1 invert=1 inrange=1 */
+ return 0;
+
+ /* xor bits 0,3 and 4: convert Tip Switch + Invert into Eraser only */
+ data[1] ^= 0x19;
+
+ return 0;
+}
+
+/*
+ * Static coordinate offset table based on positive only angles
+ * Two tables are needed, because the logical coordinates are scaled
+ *
+ * The table can be generated by Python like this:
+ * >>> full_scale = 11.874 # the display width/height in inches
+ * >>> tip_height = 0.055677699 # the center of the pen coil distance from screen in inch (empirical)
+ * >>> h = tip_height * (32767 / full_scale) # height of the coil in logical coordinates
+ * >>> [round(h*math.sin(math.radians(d))) for d in range(0, 128)]
+ * [0, 13, 26, ....]
+ */
+
+/* 14" inch screen 11.874" x 7.421" */
+static const __u16 angle_offsets_horizontal_14[128] = {
+ 0, 3, 5, 8, 11, 13, 16, 19, 21, 24, 27, 29, 32, 35, 37, 40, 42, 45, 47, 50, 53,
+ 55, 58, 60, 62, 65, 67, 70, 72, 74, 77, 79, 81, 84, 86, 88, 90, 92, 95, 97, 99,
+ 101, 103, 105, 107, 109, 111, 112, 114, 116, 118, 119, 121, 123, 124, 126, 127,
+ 129, 130, 132, 133, 134, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146,
+ 147, 148, 148, 149, 150, 150, 151, 151, 152, 152, 153, 153, 153, 153, 153, 154,
+ 154, 154, 154, 154, 153, 153, 153, 153, 153, 152, 152, 151, 151, 150, 150, 149,
+ 148, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 134, 133,
+ 132, 130, 129, 127, 126, 124, 123
+};
+static const __u16 angle_offsets_vertical_14[128] = {
+ 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 59, 64, 68, 72, 76, 80, 84,
+ 88, 92, 96, 100, 104, 108, 112, 115, 119, 123, 127, 130, 134, 137, 141, 145, 148,
+ 151, 155, 158, 161, 165, 168, 171, 174, 177, 180, 183, 186, 188, 191, 194, 196,
+ 199, 201, 204, 206, 208, 211, 213, 215, 217, 219, 221, 223, 225, 226, 228, 230,
+ 231, 232, 234, 235, 236, 237, 239, 240, 240, 241, 242, 243, 243, 244, 244, 245,
+ 245, 246, 246, 246, 246, 246, 246, 246, 245, 245, 244, 244, 243, 243, 242, 241,
+ 240, 240, 239, 237, 236, 235, 234, 232, 231, 230, 228, 226, 225, 223, 221, 219,
+ 217, 215, 213, 211, 208, 206, 204, 201, 199, 196
+};
+
+/* 16" inch screen 13.567" x 8.480" */
+static const __u16 angle_offsets_horizontal_16[128] = {
+ 0, 2, 5, 7, 9, 12, 14, 16, 19, 21, 23, 26, 28, 30, 33, 35, 37, 39, 42, 44, 46, 48,
+ 50, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90,
+ 92, 93, 95, 97, 98, 100, 101, 103, 105, 106, 107, 109, 110, 111, 113, 114, 115,
+ 116, 118, 119, 120, 121, 122, 123, 124, 125, 126, 126, 127, 128, 129, 129, 130,
+ 130, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 134,
+ 134, 134, 134, 134, 134, 133, 133, 133, 132, 132, 132, 131, 130, 130, 129, 129,
+ 128, 127, 126, 126, 125, 124, 123, 122, 121, 120, 119, 118, 116, 115, 114, 113,
+ 111, 110, 109, 107
+};
+static const __u16 angle_offsets_vertical_16[128] = {
+ 0, 4, 8, 11, 15, 19, 22, 26, 30, 34, 37, 41, 45, 48, 52, 56, 59, 63, 66, 70, 74,
+ 77, 81, 84, 88, 91, 94, 98, 101, 104, 108, 111, 114, 117, 120, 123, 126, 129, 132,
+ 135, 138, 141, 144, 147, 149, 152, 155, 157, 160, 162, 165, 167, 170, 172, 174,
+ 176, 178, 180, 182, 184, 186, 188, 190, 192, 193, 195, 197, 198, 199, 201, 202,
+ 203, 205, 206, 207, 208, 209, 210, 210, 211, 212, 212, 213, 214, 214, 214, 215,
+ 215, 215, 215, 215, 215, 215, 215, 215, 214, 214, 214, 213, 212, 212, 211, 210,
+ 210, 209, 208, 207, 206, 205, 203, 202, 201, 199, 198, 197, 195, 193, 192, 190,
+ 188, 186, 184, 182, 180, 178, 176, 174, 172
+};
+
+static void compensate_coordinates_by_tilt(__u8 *data, const __u8 idx,
+ const __s8 tilt, const __u16 (*compensation_table)[128])
+{
+ __u16 coords = data[idx+1];
+
+ coords <<= 8;
+ coords += data[idx];
+
+ __u8 direction = tilt > 0 ? 0 : 1; /* Positive tilt means we need to subtract the compensation (vs. negative angle where we need to add) */
+ __u8 angle = tilt > 0 ? tilt : -tilt;
+
+ if (angle > 127)
+ return;
+
+ __u16 compensation = (*compensation_table)[angle];
+
+ if (direction == 0) {
+ coords = (coords > compensation) ? coords - compensation : 0;
+ } else {
+ const __u16 logical_maximum = 32767;
+ __u16 max = logical_maximum - compensation;
+
+ coords = (coords < max) ? coords + compensation : logical_maximum;
+ }
+
+ data[idx] = coords & 0xff;
+ data[idx+1] = coords >> 8;
+}
+
+SEC("fmod_ret/hid_bpf_device_event")
+int BPF_PROG(xppen_16_fix_angle_offset, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /*
+ * Compensate X and Y offset caused by tilt.
+ *
+ * The magnetic center moves when the pen is tilted, because the coil
+ * is not touching the screen.
+ *
+ * a (tilt angle)
+ * | /... h (coil distance from tip)
+ * | /
+ * |/______
+ * |x (position offset)
+ *
+ * x = sin a * h
+ *
+ * Subtract the offset from the coordinates. Use the precomputed table!
+ *
+ * bytes 0 - report id
+ * 1 - buttons
+ * 2-3 - X coords (logical)
+ * 4-5 - Y coords
+ * 6-7 - pressure (ignore)
+ * 8 - tilt X
+ * 9 - tilt Y
+ */
+
+ __s8 tilt_x = (__s8) data[8];
+ __s8 tilt_y = (__s8) data[9];
+
+ if (hctx->hid->product == PID_ARTIST_PRO14_GEN2) {
+ compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_14);
+ compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_14);
+ } else if (hctx->hid->product == PID_ARTIST_PRO16_GEN2) {
+ compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_16);
+ compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_16);
+ }
+
+ return 0;
+}
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ /*
+ * The device exports 3 interfaces.
+ */
+ ctx->retval = ctx->rdesc_size != 113;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ /* ensure the kernel isn't fixed already */
+ if (ctx->rdesc[17] != 0x45) /* Eraser */
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/hid_bpf.h b/drivers/hid/bpf/progs/hid_bpf.h
new file mode 100644
index 0000000000..7ee371cac2
--- /dev/null
+++ b/drivers/hid/bpf/progs/hid_bpf.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2022 Benjamin Tissoires
+ */
+
+#ifndef ____HID_BPF__H
+#define ____HID_BPF__H
+
+struct hid_bpf_probe_args {
+ unsigned int hid;
+ unsigned int rdesc_size;
+ unsigned char rdesc[4096];
+ int retval;
+};
+
+#endif /* ____HID_BPF__H */
diff --git a/drivers/hid/bpf/progs/hid_bpf_helpers.h b/drivers/hid/bpf/progs/hid_bpf_helpers.h
new file mode 100644
index 0000000000..8f226f6e88
--- /dev/null
+++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h
@@ -0,0 +1,168 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2022 Benjamin Tissoires
+ */
+
+#ifndef __HID_BPF_HELPERS_H
+#define __HID_BPF_HELPERS_H
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <linux/errno.h>
+
+extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
+ unsigned int offset,
+ const size_t __sz) __ksym;
+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 buf__sz,
+ enum hid_report_type type,
+ enum hid_class_request reqtype) __ksym;
+
+#define HID_MAX_DESCRIPTOR_SIZE 4096
+#define HID_IGNORE_EVENT -1
+
+/* extracted from <linux/input.h> */
+#define BUS_ANY 0x00
+#define BUS_PCI 0x01
+#define BUS_ISAPNP 0x02
+#define BUS_USB 0x03
+#define BUS_HIL 0x04
+#define BUS_BLUETOOTH 0x05
+#define BUS_VIRTUAL 0x06
+#define BUS_ISA 0x10
+#define BUS_I8042 0x11
+#define BUS_XTKBD 0x12
+#define BUS_RS232 0x13
+#define BUS_GAMEPORT 0x14
+#define BUS_PARPORT 0x15
+#define BUS_AMIGA 0x16
+#define BUS_ADB 0x17
+#define BUS_I2C 0x18
+#define BUS_HOST 0x19
+#define BUS_GSC 0x1A
+#define BUS_ATARI 0x1B
+#define BUS_SPI 0x1C
+#define BUS_RMI 0x1D
+#define BUS_CEC 0x1E
+#define BUS_INTEL_ISHTP 0x1F
+#define BUS_AMD_SFH 0x20
+
+/* extracted from <linux/hid.h> */
+#define HID_GROUP_ANY 0x0000
+#define HID_GROUP_GENERIC 0x0001
+#define HID_GROUP_MULTITOUCH 0x0002
+#define HID_GROUP_SENSOR_HUB 0x0003
+#define HID_GROUP_MULTITOUCH_WIN_8 0x0004
+#define HID_GROUP_RMI 0x0100
+#define HID_GROUP_WACOM 0x0101
+#define HID_GROUP_LOGITECH_DJ_DEVICE 0x0102
+#define HID_GROUP_STEAM 0x0103
+#define HID_GROUP_LOGITECH_27MHZ_DEVICE 0x0104
+#define HID_GROUP_VIVALDI 0x0105
+
+/* include/linux/mod_devicetable.h defines as (~0), but that gives us negative size arrays */
+#define HID_VID_ANY 0x0000
+#define HID_PID_ANY 0x0000
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+/* Helper macro to convert (foo, __LINE__) into foo134 so we can use __LINE__ for
+ * field/variable names
+ */
+#define COMBINE1(X, Y) X ## Y
+#define COMBINE(X, Y) COMBINE1(X, Y)
+
+/* Macro magic:
+ * __uint(foo, 123) creates a int (*foo)[1234]
+ *
+ * We use that macro to declare an anonymous struct with several
+ * fields, each is the declaration of an pointer to an array of size
+ * bus/group/vid/pid. (Because it's a pointer to such an array, actual storage
+ * would be sizeof(pointer) rather than sizeof(array). Not that we ever
+ * instantiate it anyway).
+ *
+ * This is only used for BTF introspection, we can later check "what size
+ * is the bus array" in the introspection data and thus extract the bus ID
+ * again.
+ *
+ * And we use the __LINE__ to give each of our structs a unique name so the
+ * BPF program writer doesn't have to.
+ *
+ * $ bpftool btf dump file target/bpf/HP_Elite_Presenter.bpf.o
+ * shows the inspection data, start by searching for .hid_bpf_config
+ * and working backwards from that (each entry references the type_id of the
+ * content).
+ */
+
+#define HID_DEVICE(b, g, ven, prod) \
+ struct { \
+ __uint(name, 0); \
+ __uint(bus, (b)); \
+ __uint(group, (g)); \
+ __uint(vid, (ven)); \
+ __uint(pid, (prod)); \
+ } COMBINE(_entry, __LINE__)
+
+/* Macro magic below is to make HID_BPF_CONFIG() look like a function call that
+ * we can pass multiple HID_DEVICE() invocations in.
+ *
+ * For up to 16 arguments, HID_BPF_CONFIG(one, two) resolves to
+ *
+ * union {
+ * HID_DEVICE(...);
+ * HID_DEVICE(...);
+ * } _device_ids SEC(".hid_bpf_config")
+ *
+ */
+
+/* Returns the number of macro arguments, this expands
+ * NARGS(a, b, c) to NTH_ARG(a, b, c, 15, 14, 13, .... 4, 3, 2, 1).
+ * NTH_ARG always returns the 16th argument which in our case is 3.
+ *
+ * If we want more than 16 values _COUNTDOWN and _NTH_ARG both need to be
+ * updated.
+ */
+#define _NARGS(...) _NARGS1(__VA_ARGS__, _COUNTDOWN)
+#define _NARGS1(...) _NTH_ARG(__VA_ARGS__)
+
+/* Add to this if we need more than 16 args */
+#define _COUNTDOWN \
+ 15, 14, 13, 12, 11, 10, 9, 8, \
+ 7, 6, 5, 4, 3, 2, 1, 0
+
+/* Return the 16 argument passed in. See _NARGS above for usage. Note this is
+ * 1-indexed.
+ */
+#define _NTH_ARG( \
+ _1, _2, _3, _4, _5, _6, _7, _8, \
+ _9, _10, _11, _12, _13, _14, _15,\
+ N, ...) N
+
+/* Turns EXPAND(_ARG, a, b, c) into _ARG3(a, b, c) */
+#define _EXPAND(func, ...) COMBINE(func, _NARGS(__VA_ARGS__)) (__VA_ARGS__)
+
+/* And now define all the ARG macros for each number of args we want to accept */
+#define _ARG1(_1) _1;
+#define _ARG2(_1, _2) _1; _2;
+#define _ARG3(_1, _2, _3) _1; _2; _3;
+#define _ARG4(_1, _2, _3, _4) _1; _2; _3; _4;
+#define _ARG5(_1, _2, _3, _4, _5) _1; _2; _3; _4; _5;
+#define _ARG6(_1, _2, _3, _4, _5, _6) _1; _2; _3; _4; _5; _6;
+#define _ARG7(_1, _2, _3, _4, _5, _6, _7) _1; _2; _3; _4; _5; _6; _7;
+#define _ARG8(_1, _2, _3, _4, _5, _6, _7, _8) _1; _2; _3; _4; _5; _6; _7; _8;
+#define _ARG9(_1, _2, _3, _4, _5, _6, _7, _8, _9) _1; _2; _3; _4; _5; _6; _7; _8; _9;
+#define _ARG10(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a;
+#define _ARG11(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b;
+#define _ARG12(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c;
+#define _ARG13(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d;
+#define _ARG14(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e;
+#define _ARG15(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e, _f) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e; _f;
+
+
+#define HID_BPF_CONFIG(...) union { \
+ _EXPAND(_ARG, __VA_ARGS__) \
+} _device_ids SEC(".hid_bpf_config")
+
+#endif /* __HID_BPF_HELPERS_H */