xref: /linux/drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c (revision 001821b0e79716c4e17c71d8e053a23599a7a508)
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("fmod_ret/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 SEC("fmod_ret/hid_bpf_device_event")
109 int BPF_PROG(xppen_16_fix_eraser, struct hid_bpf_ctx *hctx)
110 {
111 	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
112 
113 	if (!data)
114 		return 0; /* EPERM check */
115 
116 	if ((data[1] & 0x29) != 0x29) /* tip switch=1 invert=1 inrange=1 */
117 		return 0;
118 
119 	/* xor bits 0,3 and 4: convert Tip Switch + Invert into Eraser only */
120 	data[1] ^= 0x19;
121 
122 	return 0;
123 }
124 
125 /*
126  * Static coordinate offset table based on positive only angles
127  * Two tables are needed, because the logical coordinates are scaled
128  *
129  * The table can be generated by Python like this:
130  * >>> full_scale = 11.874 # the display width/height in inches
131  * >>> tip_height = 0.055677699 # the center of the pen coil distance from screen in inch (empirical)
132  * >>> h = tip_height * (32767 / full_scale) # height of the coil in logical coordinates
133  * >>> [round(h*math.sin(math.radians(d))) for d in range(0, 128)]
134  * [0, 13, 26, ....]
135  */
136 
137 /* 14" inch screen 11.874" x 7.421" */
138 static const __u16 angle_offsets_horizontal_14[128] = {
139 	0, 3, 5, 8, 11, 13, 16, 19, 21, 24, 27, 29, 32, 35, 37, 40, 42, 45, 47, 50, 53,
140 	55, 58, 60, 62, 65, 67, 70, 72, 74, 77, 79, 81, 84, 86, 88, 90, 92, 95, 97, 99,
141 	101, 103, 105, 107, 109, 111, 112, 114, 116, 118, 119, 121, 123, 124, 126, 127,
142 	129, 130, 132, 133, 134, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146,
143 	147, 148, 148, 149, 150, 150, 151, 151, 152, 152, 153, 153, 153, 153, 153, 154,
144 	154, 154, 154, 154, 153, 153, 153, 153, 153, 152, 152, 151, 151, 150, 150, 149,
145 	148, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 134, 133,
146 	132, 130, 129, 127, 126, 124, 123
147 };
148 static const __u16 angle_offsets_vertical_14[128] = {
149 	0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 59, 64, 68, 72, 76, 80, 84,
150 	88, 92, 96, 100, 104, 108, 112, 115, 119, 123, 127, 130, 134, 137, 141, 145, 148,
151 	151, 155, 158, 161, 165, 168, 171, 174, 177, 180, 183, 186, 188, 191, 194, 196,
152 	199, 201, 204, 206, 208, 211, 213, 215, 217, 219, 221, 223, 225, 226, 228, 230,
153 	231, 232, 234, 235, 236, 237, 239, 240, 240, 241, 242, 243, 243, 244, 244, 245,
154 	245, 246, 246, 246, 246, 246, 246, 246, 245, 245, 244, 244, 243, 243, 242, 241,
155 	240, 240, 239, 237, 236, 235, 234, 232, 231, 230, 228, 226, 225, 223, 221, 219,
156 	217, 215, 213, 211, 208, 206, 204, 201, 199, 196
157 };
158 
159 /* 16" inch screen 13.567" x 8.480" */
160 static const __u16 angle_offsets_horizontal_16[128] = {
161 	0, 2, 5, 7, 9, 12, 14, 16, 19, 21, 23, 26, 28, 30, 33, 35, 37, 39, 42, 44, 46, 48,
162 	50, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90,
163 	92, 93, 95, 97, 98, 100, 101, 103, 105, 106, 107, 109, 110, 111, 113, 114, 115,
164 	116, 118, 119, 120, 121, 122, 123, 124, 125, 126, 126, 127, 128, 129, 129, 130,
165 	130, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 134,
166 	134, 134, 134, 134, 134, 133, 133, 133, 132, 132, 132, 131, 130, 130, 129, 129,
167 	128, 127, 126, 126, 125, 124, 123, 122, 121, 120, 119, 118, 116, 115, 114, 113,
168 	111, 110, 109, 107
169 };
170 static const __u16 angle_offsets_vertical_16[128] = {
171 	0, 4, 8, 11, 15, 19, 22, 26, 30, 34, 37, 41, 45, 48, 52, 56, 59, 63, 66, 70, 74,
172 	77, 81, 84, 88, 91, 94, 98, 101, 104, 108, 111, 114, 117, 120, 123, 126, 129, 132,
173 	135, 138, 141, 144, 147, 149, 152, 155, 157, 160, 162, 165, 167, 170, 172, 174,
174 	176, 178, 180, 182, 184, 186, 188, 190, 192, 193, 195, 197, 198, 199, 201, 202,
175 	203, 205, 206, 207, 208, 209, 210, 210, 211, 212, 212, 213, 214, 214, 214, 215,
176 	215, 215, 215, 215, 215, 215, 215, 215, 214, 214, 214, 213, 212, 212, 211, 210,
177 	210, 209, 208, 207, 206, 205, 203, 202, 201, 199, 198, 197, 195, 193, 192, 190,
178 	188, 186, 184, 182, 180, 178, 176, 174, 172
179 };
180 
181 static void compensate_coordinates_by_tilt(__u8 *data, const __u8 idx,
182 		const __s8 tilt, const __u16 (*compensation_table)[128])
183 {
184 	__u16 coords = data[idx+1];
185 
186 	coords <<= 8;
187 	coords += data[idx];
188 
189 	__u8 direction = tilt > 0 ? 0 : 1; /* Positive tilt means we need to subtract the compensation (vs. negative angle where we need to add) */
190 	__u8 angle = tilt > 0 ? tilt : -tilt;
191 
192 	if (angle > 127)
193 		return;
194 
195 	__u16 compensation = (*compensation_table)[angle];
196 
197 	if (direction == 0) {
198 		coords = (coords > compensation) ? coords - compensation : 0;
199 	} else {
200 		const __u16 logical_maximum = 32767;
201 		__u16 max = logical_maximum - compensation;
202 
203 		coords = (coords < max) ? coords + compensation : logical_maximum;
204 	}
205 
206 	data[idx] = coords & 0xff;
207 	data[idx+1] = coords >> 8;
208 }
209 
210 SEC("fmod_ret/hid_bpf_device_event")
211 int BPF_PROG(xppen_16_fix_angle_offset, struct hid_bpf_ctx *hctx)
212 {
213 	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
214 
215 	if (!data)
216 		return 0; /* EPERM check */
217 
218 	/*
219 	 * Compensate X and Y offset caused by tilt.
220 	 *
221 	 * The magnetic center moves when the pen is tilted, because the coil
222 	 * is not touching the screen.
223 	 *
224 	 * a (tilt angle)
225 	 * |  /... h (coil distance from tip)
226 	 * | /
227 	 * |/______
228 	 *         |x (position offset)
229 	 *
230 	 * x = sin a * h
231 	 *
232 	 * Subtract the offset from the coordinates. Use the precomputed table!
233 	 *
234 	 * bytes 0   - report id
235 	 *       1   - buttons
236 	 *       2-3 - X coords (logical)
237 	 *       4-5 - Y coords
238 	 *       6-7 - pressure (ignore)
239 	 *       8   - tilt X
240 	 *       9   - tilt Y
241 	 */
242 
243 	__s8 tilt_x = (__s8) data[8];
244 	__s8 tilt_y = (__s8) data[9];
245 
246 	if (hctx->hid->product == PID_ARTIST_PRO14_GEN2) {
247 		compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_14);
248 		compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_14);
249 	} else if (hctx->hid->product == PID_ARTIST_PRO16_GEN2) {
250 		compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_16);
251 		compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_16);
252 	}
253 
254 	return 0;
255 }
256 
257 SEC("syscall")
258 int probe(struct hid_bpf_probe_args *ctx)
259 {
260 	/*
261 	 * The device exports 3 interfaces.
262 	 */
263 	ctx->retval = ctx->rdesc_size != 113;
264 	if (ctx->retval)
265 		ctx->retval = -EINVAL;
266 
267 	/* ensure the kernel isn't fixed already */
268 	if (ctx->rdesc[17] != 0x45) /* Eraser */
269 		ctx->retval = -EINVAL;
270 
271 	return 0;
272 }
273 
274 char _license[] SEC("license") = "GPL";
275