xref: /linux/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c (revision 55d0969c451159cff86949b38c39171cab962069)
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 
14 HID_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  */
25 static 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 TIP_SWITCH		BIT(0)
82 #define BARREL_SWITCH		BIT(1)
83 #define ERASER			BIT(2)
84 /* padding			BIT(3) */
85 /* padding			BIT(4) */
86 #define IN_RANGE		BIT(5)
87 /* padding			BIT(6) */
88 /* padding			BIT(7) */
89 
90 #define U16(index) (data[index] | (data[index + 1] << 8))
91 
92 SEC(HID_BPF_RDESC_FIXUP)
93 int BPF_PROG(hid_fix_rdesc_xppen_artist24, struct hid_bpf_ctx *hctx)
94 {
95 	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
96 
97 	if (!data)
98 		return 0; /* EPERM check */
99 
100 	__builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
101 
102 	return sizeof(fixed_rdesc);
103 }
104 
105 static __u8 prev_state = 0;
106 
107 /*
108  * There are a few cases where the device is sending wrong event
109  * sequences, all related to the second button (the pen doesn't
110  * have an eraser switch on the tail end):
111  *
112  *   whenever the second button gets pressed or released, an
113  *   out-of-proximity event is generated and then the firmware
114  *   compensate for the missing state (and the firmware uses
115  *   eraser for that button):
116  *
117  *   - if the pen is in range, an extra out-of-range is sent
118  *     when the second button is pressed/released:
119  *     // Pen is in range
120  *     E:                               InRange
121  *
122  *     // Second button is pressed
123  *     E:
124  *     E:                        Eraser InRange
125  *
126  *     // Second button is released
127  *     E:
128  *     E:                               InRange
129  *
130  *     This case is ignored by this filter, it's "valid"
131  *     and userspace knows how to deal with it, there are just
132  *     a few out-of-prox events generated, but the user doesn´t
133  *     see them.
134  *
135  *   - if the pen is in contact, 2 extra events are added when
136  *     the second button is pressed/released: an out of range
137  *     and an in range:
138  *
139  *     // Pen is in contact
140  *     E: TipSwitch                     InRange
141  *
142  *     // Second button is pressed
143  *     E:                                         <- false release, needs to be filtered out
144  *     E:                        Eraser InRange   <- false release, needs to be filtered out
145  *     E: TipSwitch              Eraser InRange
146  *
147  *     // Second button is released
148  *     E:                                         <- false release, needs to be filtered out
149  *     E:                               InRange   <- false release, needs to be filtered out
150  *     E: TipSwitch                     InRange
151  *
152  */
153 SEC(HID_BPF_DEVICE_EVENT)
154 int BPF_PROG(xppen_24_fix_eraser, struct hid_bpf_ctx *hctx)
155 {
156 	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
157 	__u8 current_state, changed_state;
158 	bool prev_tip;
159 
160 	if (!data)
161 		return 0; /* EPERM check */
162 
163 	current_state = data[1];
164 
165 	/* if the state is identical to previously, early return */
166 	if (current_state == prev_state)
167 		return 0;
168 
169 	prev_tip = !!(prev_state & TIP_SWITCH);
170 
171 	/*
172 	 * Illegal transition: pen is in range with the tip pressed, and
173 	 * it goes into out of proximity.
174 	 *
175 	 * Ideally we should hold the event, start a timer and deliver it
176 	 * only if the timer ends, but we are not capable of that now.
177 	 *
178 	 * And it doesn't matter because when we are in such cases, this
179 	 * means we are detecting a false release.
180 	 */
181 	if ((current_state & IN_RANGE) == 0) {
182 		if (prev_tip)
183 			return HID_IGNORE_EVENT;
184 		return 0;
185 	}
186 
187 	/*
188 	 * XOR to only set the bits that have changed between
189 	 * previous and current state
190 	 */
191 	changed_state = prev_state ^ current_state;
192 
193 	/* Store the new state for future processing */
194 	prev_state = current_state;
195 
196 	/*
197 	 * We get both a tipswitch and eraser change in the same HID report:
198 	 * this is not an authorized transition and is unlikely to happen
199 	 * in real life.
200 	 * This is likely to be added by the firmware to emulate the
201 	 * eraser mode so we can skip the event.
202 	 */
203 	if ((changed_state & (TIP_SWITCH | ERASER)) == (TIP_SWITCH | ERASER)) /* we get both a tipswitch and eraser change at the same time */
204 		return HID_IGNORE_EVENT;
205 
206 	return 0;
207 }
208 
209 HID_BPF_OPS(xppen_artist_24) = {
210 	.hid_rdesc_fixup = (void *)hid_fix_rdesc_xppen_artist24,
211 	.hid_device_event = (void *)xppen_24_fix_eraser,
212 };
213 
214 SEC("syscall")
215 int probe(struct hid_bpf_probe_args *ctx)
216 {
217 	/*
218 	 * The device exports 3 interfaces.
219 	 */
220 	ctx->retval = ctx->rdesc_size != 107;
221 	if (ctx->retval)
222 		ctx->retval = -EINVAL;
223 
224 	/* ensure the kernel isn't fixed already */
225 	if (ctx->rdesc[17] != 0x45) /* Eraser */
226 		ctx->retval = -EINVAL;
227 
228 	return 0;
229 }
230 
231 char _license[] SEC("license") = "GPL";
232