diff options
Diffstat (limited to 'drivers/hid/bpf/progs/XPPen__Artist24.bpf.c')
-rw-r--r-- | drivers/hid/bpf/progs/XPPen__Artist24.bpf.c | 229 |
1 files changed, 229 insertions, 0 deletions
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 000000000000..e1be6a12bb75 --- /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"; |