xref: /linux/drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c (revision d53b8e36925256097a08d7cb749198d85cbf9b2b)
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_PRO14_GEN2 0x095A
12 #define PID_ARTIST_PRO16_GEN2 0x095B
13 
14 HID_BPF_CONFIG(
15 	HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_PRO14_GEN2),
16 	HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_PRO16_GEN2)
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  * - when the eraser button is pressed and the stylus is touching the tablet,
23  *   the device sends Tip Switch instead of sending Eraser
24  *
25  * This descriptor uses physical dimensions of the 16" device.
26  */
27 static const __u8 fixed_rdesc[] = {
28 	0x05, 0x0d,                    // Usage Page (Digitizers)             0
29 	0x09, 0x02,                    // Usage (Pen)                         2
30 	0xa1, 0x01,                    // Collection (Application)            4
31 	0x85, 0x07,                    //  Report ID (7)                      6
32 	0x09, 0x20,                    //  Usage (Stylus)                     8
33 	0xa1, 0x00,                    //  Collection (Physical)              10
34 	0x09, 0x42,                    //   Usage (Tip Switch)                12
35 	0x09, 0x44,                    //   Usage (Barrel Switch)             14
36 	0x09, 0x5a,                    //   Usage (Secondary Barrel Switch)   16  /* changed from 0x45 (Eraser) to 0x5a (Secondary Barrel Switch) */
37 	0x09, 0x3c,                    //   Usage (Invert)                    18
38 	0x09, 0x45,                    //   Usage (Eraser)                    16  /* created over a padding bit at offset 29-33 */
39 	0x15, 0x00,                    //   Logical Minimum (0)               20
40 	0x25, 0x01,                    //   Logical Maximum (1)               22
41 	0x75, 0x01,                    //   Report Size (1)                   24
42 	0x95, 0x05,                    //   Report Count (5)                  26  /* changed from 4 to 5 */
43 	0x81, 0x02,                    //   Input (Data,Var,Abs)              28
44 	0x09, 0x32,                    //   Usage (In Range)                  34
45 	0x15, 0x00,                    //   Logical Minimum (0)               36
46 	0x25, 0x01,                    //   Logical Maximum (1)               38
47 	0x95, 0x01,                    //   Report Count (1)                  40
48 	0x81, 0x02,                    //   Input (Data,Var,Abs)              42
49 	0x95, 0x02,                    //   Report Count (2)                  44
50 	0x81, 0x03,                    //   Input (Cnst,Var,Abs)              46
51 	0x75, 0x10,                    //   Report Size (16)                  48
52 	0x95, 0x01,                    //   Report Count (1)                  50
53 	0x35, 0x00,                    //   Physical Minimum (0)              52
54 	0xa4,                          //   Push                              54
55 	0x05, 0x01,                    //   Usage Page (Generic Desktop)      55
56 	0x09, 0x30,                    //   Usage (X)                         57
57 	0x65, 0x13,                    //   Unit (EnglishLinear: in)          59
58 	0x55, 0x0d,                    //   Unit Exponent (-3)                61
59 	0x46, 0xff, 0x34,              //   Physical Maximum (13567)          63
60 	0x26, 0xff, 0x7f,              //   Logical Maximum (32767)           66
61 	0x81, 0x02,                    //   Input (Data,Var,Abs)              69
62 	0x09, 0x31,                    //   Usage (Y)                         71
63 	0x46, 0x20, 0x21,              //   Physical Maximum (8480)           73
64 	0x26, 0xff, 0x7f,              //   Logical Maximum (32767)           76
65 	0x81, 0x02,                    //   Input (Data,Var,Abs)              79
66 	0xb4,                          //   Pop                               81
67 	0x09, 0x30,                    //   Usage (Tip Pressure)              82
68 	0x45, 0x00,                    //   Physical Maximum (0)              84
69 	0x26, 0xff, 0x3f,              //   Logical Maximum (16383)           86
70 	0x81, 0x42,                    //   Input (Data,Var,Abs,Null)         89
71 	0x09, 0x3d,                    //   Usage (X Tilt)                    91
72 	0x15, 0x81,                    //   Logical Minimum (-127)            93
73 	0x25, 0x7f,                    //   Logical Maximum (127)             95
74 	0x75, 0x08,                    //   Report Size (8)                   97
75 	0x95, 0x01,                    //   Report Count (1)                  99
76 	0x81, 0x02,                    //   Input (Data,Var,Abs)              101
77 	0x09, 0x3e,                    //   Usage (Y Tilt)                    103
78 	0x15, 0x81,                    //   Logical Minimum (-127)            105
79 	0x25, 0x7f,                    //   Logical Maximum (127)             107
80 	0x81, 0x02,                    //   Input (Data,Var,Abs)              109
81 	0xc0,                          //  End Collection                     111
82 	0xc0,                          // End Collection                      112
83 };
84 
85 SEC(HID_BPF_RDESC_FIXUP)
86 int BPF_PROG(hid_fix_rdesc_xppen_artistpro16gen2, struct hid_bpf_ctx *hctx)
87 {
88 	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
89 
90 	if (!data)
91 		return 0; /* EPERM check */
92 
93 	__builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
94 
95 	/* Fix the Physical maximum values for different sizes of the device
96 	 * The 14" screen device descriptor size is 11.874" x 7.421"
97 	 */
98 	if (hctx->hid->product == PID_ARTIST_PRO14_GEN2) {
99 		data[63] = 0x2e;
100 		data[62] = 0x62;
101 		data[73] = 0x1c;
102 		data[72] = 0xfd;
103 	}
104 
105 	return sizeof(fixed_rdesc);
106 }
107 
108 static int xppen_16_fix_eraser(struct hid_bpf_ctx *hctx)
109 {
110 	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
111 
112 	if (!data)
113 		return 0; /* EPERM check */
114 
115 	if ((data[1] & 0x29) != 0x29) /* tip switch=1 invert=1 inrange=1 */
116 		return 0;
117 
118 	/* xor bits 0,3 and 4: convert Tip Switch + Invert into Eraser only */
119 	data[1] ^= 0x19;
120 
121 	return 0;
122 }
123 
124 /*
125  * Static coordinate offset table based on positive only angles
126  * Two tables are needed, because the logical coordinates are scaled
127  *
128  * The table can be generated by Python like this:
129  * >>> full_scale = 11.874 # the display width/height in inches
130  * >>> tip_height = 0.055677699 # the center of the pen coil distance from screen in inch (empirical)
131  * >>> h = tip_height * (32767 / full_scale) # height of the coil in logical coordinates
132  * >>> [round(h*math.sin(math.radians(d))) for d in range(0, 128)]
133  * [0, 13, 26, ....]
134  */
135 
136 /* 14" inch screen 11.874" x 7.421" */
137 static const __u16 angle_offsets_horizontal_14[128] = {
138 	0, 3, 5, 8, 11, 13, 16, 19, 21, 24, 27, 29, 32, 35, 37, 40, 42, 45, 47, 50, 53,
139 	55, 58, 60, 62, 65, 67, 70, 72, 74, 77, 79, 81, 84, 86, 88, 90, 92, 95, 97, 99,
140 	101, 103, 105, 107, 109, 111, 112, 114, 116, 118, 119, 121, 123, 124, 126, 127,
141 	129, 130, 132, 133, 134, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146,
142 	147, 148, 148, 149, 150, 150, 151, 151, 152, 152, 153, 153, 153, 153, 153, 154,
143 	154, 154, 154, 154, 153, 153, 153, 153, 153, 152, 152, 151, 151, 150, 150, 149,
144 	148, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 134, 133,
145 	132, 130, 129, 127, 126, 124, 123
146 };
147 static const __u16 angle_offsets_vertical_14[128] = {
148 	0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 59, 64, 68, 72, 76, 80, 84,
149 	88, 92, 96, 100, 104, 108, 112, 115, 119, 123, 127, 130, 134, 137, 141, 145, 148,
150 	151, 155, 158, 161, 165, 168, 171, 174, 177, 180, 183, 186, 188, 191, 194, 196,
151 	199, 201, 204, 206, 208, 211, 213, 215, 217, 219, 221, 223, 225, 226, 228, 230,
152 	231, 232, 234, 235, 236, 237, 239, 240, 240, 241, 242, 243, 243, 244, 244, 245,
153 	245, 246, 246, 246, 246, 246, 246, 246, 245, 245, 244, 244, 243, 243, 242, 241,
154 	240, 240, 239, 237, 236, 235, 234, 232, 231, 230, 228, 226, 225, 223, 221, 219,
155 	217, 215, 213, 211, 208, 206, 204, 201, 199, 196
156 };
157 
158 /* 16" inch screen 13.567" x 8.480" */
159 static const __u16 angle_offsets_horizontal_16[128] = {
160 	0, 2, 5, 7, 9, 12, 14, 16, 19, 21, 23, 26, 28, 30, 33, 35, 37, 39, 42, 44, 46, 48,
161 	50, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90,
162 	92, 93, 95, 97, 98, 100, 101, 103, 105, 106, 107, 109, 110, 111, 113, 114, 115,
163 	116, 118, 119, 120, 121, 122, 123, 124, 125, 126, 126, 127, 128, 129, 129, 130,
164 	130, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 134,
165 	134, 134, 134, 134, 134, 133, 133, 133, 132, 132, 132, 131, 130, 130, 129, 129,
166 	128, 127, 126, 126, 125, 124, 123, 122, 121, 120, 119, 118, 116, 115, 114, 113,
167 	111, 110, 109, 107
168 };
169 static const __u16 angle_offsets_vertical_16[128] = {
170 	0, 4, 8, 11, 15, 19, 22, 26, 30, 34, 37, 41, 45, 48, 52, 56, 59, 63, 66, 70, 74,
171 	77, 81, 84, 88, 91, 94, 98, 101, 104, 108, 111, 114, 117, 120, 123, 126, 129, 132,
172 	135, 138, 141, 144, 147, 149, 152, 155, 157, 160, 162, 165, 167, 170, 172, 174,
173 	176, 178, 180, 182, 184, 186, 188, 190, 192, 193, 195, 197, 198, 199, 201, 202,
174 	203, 205, 206, 207, 208, 209, 210, 210, 211, 212, 212, 213, 214, 214, 214, 215,
175 	215, 215, 215, 215, 215, 215, 215, 215, 214, 214, 214, 213, 212, 212, 211, 210,
176 	210, 209, 208, 207, 206, 205, 203, 202, 201, 199, 198, 197, 195, 193, 192, 190,
177 	188, 186, 184, 182, 180, 178, 176, 174, 172
178 };
179 
180 static void compensate_coordinates_by_tilt(__u8 *data, const __u8 idx,
181 		const __s8 tilt, const __u16 (*compensation_table)[128])
182 {
183 	__u16 coords = data[idx+1];
184 
185 	coords <<= 8;
186 	coords += data[idx];
187 
188 	__u8 direction = tilt > 0 ? 0 : 1; /* Positive tilt means we need to subtract the compensation (vs. negative angle where we need to add) */
189 	__u8 angle = tilt > 0 ? tilt : -tilt;
190 
191 	if (angle > 127)
192 		return;
193 
194 	__u16 compensation = (*compensation_table)[angle];
195 
196 	if (direction == 0) {
197 		coords = (coords > compensation) ? coords - compensation : 0;
198 	} else {
199 		const __u16 logical_maximum = 32767;
200 		__u16 max = logical_maximum - compensation;
201 
202 		coords = (coords < max) ? coords + compensation : logical_maximum;
203 	}
204 
205 	data[idx] = coords & 0xff;
206 	data[idx+1] = coords >> 8;
207 }
208 
209 static int xppen_16_fix_angle_offset(struct hid_bpf_ctx *hctx)
210 {
211 	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
212 
213 	if (!data)
214 		return 0; /* EPERM check */
215 
216 	/*
217 	 * Compensate X and Y offset caused by tilt.
218 	 *
219 	 * The magnetic center moves when the pen is tilted, because the coil
220 	 * is not touching the screen.
221 	 *
222 	 * a (tilt angle)
223 	 * |  /... h (coil distance from tip)
224 	 * | /
225 	 * |/______
226 	 *         |x (position offset)
227 	 *
228 	 * x = sin a * h
229 	 *
230 	 * Subtract the offset from the coordinates. Use the precomputed table!
231 	 *
232 	 * bytes 0   - report id
233 	 *       1   - buttons
234 	 *       2-3 - X coords (logical)
235 	 *       4-5 - Y coords
236 	 *       6-7 - pressure (ignore)
237 	 *       8   - tilt X
238 	 *       9   - tilt Y
239 	 */
240 
241 	__s8 tilt_x = (__s8) data[8];
242 	__s8 tilt_y = (__s8) data[9];
243 
244 	if (hctx->hid->product == PID_ARTIST_PRO14_GEN2) {
245 		compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_14);
246 		compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_14);
247 	} else if (hctx->hid->product == PID_ARTIST_PRO16_GEN2) {
248 		compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_16);
249 		compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_16);
250 	}
251 
252 	return 0;
253 }
254 
255 SEC(HID_BPF_DEVICE_EVENT)
256 int BPF_PROG(xppen_artist_pro_16_device_event, struct hid_bpf_ctx *hctx)
257 {
258 	int ret = xppen_16_fix_angle_offset(hctx);
259 
260 	if (ret)
261 		return ret;
262 
263 	return xppen_16_fix_eraser(hctx);
264 }
265 
266 HID_BPF_OPS(xppen_artist_pro_16) = {
267 	.hid_rdesc_fixup = (void *)hid_fix_rdesc_xppen_artistpro16gen2,
268 	.hid_device_event = (void *)xppen_artist_pro_16_device_event,
269 };
270 
271 SEC("syscall")
272 int probe(struct hid_bpf_probe_args *ctx)
273 {
274 	/*
275 	 * The device exports 3 interfaces.
276 	 */
277 	ctx->retval = ctx->rdesc_size != 113;
278 	if (ctx->retval)
279 		ctx->retval = -EINVAL;
280 
281 	/* ensure the kernel isn't fixed already */
282 	if (ctx->rdesc[17] != 0x45) /* Eraser */
283 		ctx->retval = -EINVAL;
284 
285 	return 0;
286 }
287 
288 char _license[] SEC("license") = "GPL";
289