1// SPDX-License-Identifier: GPL-2.0-only
2/* Copyright (c) 2023 Benjamin Tissoires
3 */
4
5#include "vmlinux.h"
6#include "hid_bpf.h"
7#include "hid_bpf_helpers.h"
8#include <bpf/bpf_tracing.h>
9
10#define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */
11#define PID_ARTIST_24 0x093A
12#define PID_ARTIST_24_PRO 0x092D
13
14HID_BPF_CONFIG(
15	HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24),
16	HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24_PRO)
17);
18
19/*
20 * We need to amend the report descriptor for the following:
21 * - the device reports Eraser instead of using Secondary Barrel Switch
22 * - the pen doesn't have a rubber tail, so basically we are removing any
23 *   eraser/invert bits
24 */
25static const __u8 fixed_rdesc[] = {
26	0x05, 0x0d,                    // Usage Page (Digitizers)             0
27	0x09, 0x02,                    // Usage (Pen)                         2
28	0xa1, 0x01,                    // Collection (Application)            4
29	0x85, 0x07,                    //  Report ID (7)                      6
30	0x09, 0x20,                    //  Usage (Stylus)                     8
31	0xa1, 0x00,                    //  Collection (Physical)              10
32	0x09, 0x42,                    //   Usage (Tip Switch)                12
33	0x09, 0x44,                    //   Usage (Barrel Switch)             14
34	0x09, 0x5a,                    //   Usage (Secondary Barrel Switch)   16  /* changed from 0x45 (Eraser) to 0x5a (Secondary Barrel Switch) */
35	0x15, 0x00,                    //   Logical Minimum (0)               18
36	0x25, 0x01,                    //   Logical Maximum (1)               20
37	0x75, 0x01,                    //   Report Size (1)                   22
38	0x95, 0x03,                    //   Report Count (3)                  24
39	0x81, 0x02,                    //   Input (Data,Var,Abs)              26
40	0x95, 0x02,                    //   Report Count (2)                  28
41	0x81, 0x03,                    //   Input (Cnst,Var,Abs)              30
42	0x09, 0x32,                    //   Usage (In Range)                  32
43	0x95, 0x01,                    //   Report Count (1)                  34
44	0x81, 0x02,                    //   Input (Data,Var,Abs)              36
45	0x95, 0x02,                    //   Report Count (2)                  38
46	0x81, 0x03,                    //   Input (Cnst,Var,Abs)              40
47	0x75, 0x10,                    //   Report Size (16)                  42
48	0x95, 0x01,                    //   Report Count (1)                  44
49	0x35, 0x00,                    //   Physical Minimum (0)              46
50	0xa4,                          //   Push                              48
51	0x05, 0x01,                    //   Usage Page (Generic Desktop)      49
52	0x09, 0x30,                    //   Usage (X)                         51
53	0x65, 0x13,                    //   Unit (EnglishLinear: in)          53
54	0x55, 0x0d,                    //   Unit Exponent (-3)                55
55	0x46, 0xf0, 0x50,              //   Physical Maximum (20720)          57
56	0x26, 0xff, 0x7f,              //   Logical Maximum (32767)           60
57	0x81, 0x02,                    //   Input (Data,Var,Abs)              63
58	0x09, 0x31,                    //   Usage (Y)                         65
59	0x46, 0x91, 0x2d,              //   Physical Maximum (11665)          67
60	0x26, 0xff, 0x7f,              //   Logical Maximum (32767)           70
61	0x81, 0x02,                    //   Input (Data,Var,Abs)              73
62	0xb4,                          //   Pop                               75
63	0x09, 0x30,                    //   Usage (Tip Pressure)              76
64	0x45, 0x00,                    //   Physical Maximum (0)              78
65	0x26, 0xff, 0x1f,              //   Logical Maximum (8191)            80
66	0x81, 0x42,                    //   Input (Data,Var,Abs,Null)         83
67	0x09, 0x3d,                    //   Usage (X Tilt)                    85
68	0x15, 0x81,                    //   Logical Minimum (-127)            87
69	0x25, 0x7f,                    //   Logical Maximum (127)             89
70	0x75, 0x08,                    //   Report Size (8)                   91
71	0x95, 0x01,                    //   Report Count (1)                  93
72	0x81, 0x02,                    //   Input (Data,Var,Abs)              95
73	0x09, 0x3e,                    //   Usage (Y Tilt)                    97
74	0x15, 0x81,                    //   Logical Minimum (-127)            99
75	0x25, 0x7f,                    //   Logical Maximum (127)             101
76	0x81, 0x02,                    //   Input (Data,Var,Abs)              103
77	0xc0,                          //  End Collection                     105
78	0xc0,                          // End Collection                      106
79};
80
81#define BIT(n) (1UL << n)
82
83#define TIP_SWITCH		BIT(0)
84#define BARREL_SWITCH		BIT(1)
85#define ERASER			BIT(2)
86/* padding			BIT(3) */
87/* padding			BIT(4) */
88#define IN_RANGE		BIT(5)
89/* padding			BIT(6) */
90/* padding			BIT(7) */
91
92#define U16(index) (data[index] | (data[index + 1] << 8))
93
94SEC("fmod_ret/hid_bpf_rdesc_fixup")
95int BPF_PROG(hid_fix_rdesc_xppen_artist24, struct hid_bpf_ctx *hctx)
96{
97	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
98
99	if (!data)
100		return 0; /* EPERM check */
101
102	__builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
103
104	return sizeof(fixed_rdesc);
105}
106
107static __u8 prev_state = 0;
108
109/*
110 * There are a few cases where the device is sending wrong event
111 * sequences, all related to the second button (the pen doesn't
112 * have an eraser switch on the tail end):
113 *
114 *   whenever the second button gets pressed or released, an
115 *   out-of-proximity event is generated and then the firmware
116 *   compensate for the missing state (and the firmware uses
117 *   eraser for that button):
118 *
119 *   - if the pen is in range, an extra out-of-range is sent
120 *     when the second button is pressed/released:
121 *     // Pen is in range
122 *     E:                               InRange
123 *
124 *     // Second button is pressed
125 *     E:
126 *     E:                        Eraser InRange
127 *
128 *     // Second button is released
129 *     E:
130 *     E:                               InRange
131 *
132 *     This case is ignored by this filter, it's "valid"
133 *     and userspace knows how to deal with it, there are just
134 *     a few out-of-prox events generated, but the user doesn��t
135 *     see them.
136 *
137 *   - if the pen is in contact, 2 extra events are added when
138 *     the second button is pressed/released: an out of range
139 *     and an in range:
140 *
141 *     // Pen is in contact
142 *     E: TipSwitch                     InRange
143 *
144 *     // Second button is pressed
145 *     E:                                         <- false release, needs to be filtered out
146 *     E:                        Eraser InRange   <- false release, needs to be filtered out
147 *     E: TipSwitch              Eraser InRange
148 *
149 *     // Second button is released
150 *     E:                                         <- false release, needs to be filtered out
151 *     E:                               InRange   <- false release, needs to be filtered out
152 *     E: TipSwitch                     InRange
153 *
154 */
155SEC("fmod_ret/hid_bpf_device_event")
156int BPF_PROG(xppen_24_fix_eraser, struct hid_bpf_ctx *hctx)
157{
158	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
159	__u8 current_state, changed_state;
160	bool prev_tip;
161	__u16 tilt;
162
163	if (!data)
164		return 0; /* EPERM check */
165
166	current_state = data[1];
167
168	/* if the state is identical to previously, early return */
169	if (current_state == prev_state)
170		return 0;
171
172	prev_tip = !!(prev_state & TIP_SWITCH);
173
174	/*
175	 * Illegal transition: pen is in range with the tip pressed, and
176	 * it goes into out of proximity.
177	 *
178	 * Ideally we should hold the event, start a timer and deliver it
179	 * only if the timer ends, but we are not capable of that now.
180	 *
181	 * And it doesn't matter because when we are in such cases, this
182	 * means we are detecting a false release.
183	 */
184	if ((current_state & IN_RANGE) == 0) {
185		if (prev_tip)
186			return HID_IGNORE_EVENT;
187		return 0;
188	}
189
190	/*
191	 * XOR to only set the bits that have changed between
192	 * previous and current state
193	 */
194	changed_state = prev_state ^ current_state;
195
196	/* Store the new state for future processing */
197	prev_state = current_state;
198
199	/*
200	 * We get both a tipswitch and eraser change in the same HID report:
201	 * this is not an authorized transition and is unlikely to happen
202	 * in real life.
203	 * This is likely to be added by the firmware to emulate the
204	 * eraser mode so we can skip the event.
205	 */
206	if ((changed_state & (TIP_SWITCH | ERASER)) == (TIP_SWITCH | ERASER)) /* we get both a tipswitch and eraser change at the same time */
207		return HID_IGNORE_EVENT;
208
209	return 0;
210}
211
212SEC("syscall")
213int probe(struct hid_bpf_probe_args *ctx)
214{
215	/*
216	 * The device exports 3 interfaces.
217	 */
218	ctx->retval = ctx->rdesc_size != 107;
219	if (ctx->retval)
220		ctx->retval = -EINVAL;
221
222	/* ensure the kernel isn't fixed already */
223	if (ctx->rdesc[17] != 0x45) /* Eraser */
224		ctx->retval = -EINVAL;
225
226	return 0;
227}
228
229char _license[] SEC("license") = "GPL";
230