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