1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Copyright (c) 2024 Red Hat, Inc
3 */
4
5 #include "vmlinux.h"
6 #include "hid_bpf.h"
7 #include "hid_bpf_helpers.h"
8 #include "hid_report_helpers.h"
9 #include <bpf/bpf_tracing.h>
10
11 #define VID_HUION 0x256C
12 #define PID_INSPIROY_2_S 0x0066
13
14 HID_BPF_CONFIG(
15 HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HUION, PID_INSPIROY_2_S),
16 );
17
18 /* Filled in by udev-hid-bpf */
19 char UDEV_PROP_HUION_FIRMWARE_ID[64];
20
21 /* The prefix of the firmware ID we expect for this device. The full firmware
22 * string has a date suffix, e.g. HUION_T21j_221221
23 */
24 char EXPECTED_FIRMWARE_ID[] = "HUION_T21j_";
25
26 /* How this BPF program works: the tablet has two modes, firmware mode and
27 * tablet mode. In firmware mode (out of the box) the tablet sends button events
28 * and the dial as keyboard combinations. In tablet mode it uses a vendor specific
29 * hid report to report everything instead.
30 * Depending on the mode some hid reports are never sent and the corresponding
31 * devices are mute.
32 *
33 * To switch the tablet use e.g. https://github.com/whot/huion-switcher
34 * or one of the tools from the digimend project
35 *
36 * This BPF works for both modes. The huion-switcher tool sets the
37 * HUION_FIRMWARE_ID udev property - if that is set then we disable the firmware
38 * pad and pen reports (by making them vendor collections that are ignored).
39 * If that property is not set we fix all hidraw nodes so the tablet works in
40 * either mode though the drawback is that the device will show up twice if
41 * you bind it to all event nodes
42 *
43 * Default report descriptor for the first exposed hidraw node:
44 *
45 * # HUION Huion Tablet_H641P
46 * # Report descriptor length: 18 bytes
47 * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 0xFF00) 0
48 * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 3
49 * # 0xa1, 0x01, // Collection (Application) 5
50 * # 0x85, 0x08, // Report ID (8) 7
51 * # 0x75, 0x58, // Report Size (88) 9
52 * # 0x95, 0x01, // Report Count (1) 11
53 * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 13
54 * # 0x81, 0x02, // Input (Data,Var,Abs) 15
55 * # 0xc0, // End Collection 17
56 * R: 18 06 00 ff 09 01 a1 01 85 08 75 58 95 01 09 01 81 02 c0
57 *
58 * This rdesc does nothing until the tablet is switched to raw mode, see
59 * https://github.com/whot/huion-switcher
60 *
61 *
62 * Second hidraw node is the Pen. This one sends events until the tablet is
63 * switched to raw mode, then it's mute.
64 *
65 * # Report descriptor length: 93 bytes
66 * # 0x05, 0x0d, // Usage Page (Digitizers) 0
67 * # 0x09, 0x02, // Usage (Pen) 2
68 * # 0xa1, 0x01, // Collection (Application) 4
69 * # 0x85, 0x0a, // Report ID (10) 6
70 * # 0x09, 0x20, // Usage (Stylus) 8
71 * # 0xa1, 0x01, // Collection (Application) 10
72 * # 0x09, 0x42, // Usage (Tip Switch) 12
73 * # 0x09, 0x44, // Usage (Barrel Switch) 14
74 * # 0x09, 0x45, // Usage (Eraser) 16
75 * # 0x09, 0x3c, // Usage (Invert) 18 <-- has no Invert eraser
76 * # 0x15, 0x00, // Logical Minimum (0) 20
77 * # 0x25, 0x01, // Logical Maximum (1) 22
78 * # 0x75, 0x01, // Report Size (1) 24
79 * # 0x95, 0x06, // Report Count (6) 26
80 * # 0x81, 0x02, // Input (Data,Var,Abs) 28
81 * # 0x09, 0x32, // Usage (In Range) 30
82 * # 0x75, 0x01, // Report Size (1) 32
83 * # 0x95, 0x01, // Report Count (1) 34
84 * # 0x81, 0x02, // Input (Data,Var,Abs) 36
85 * # 0x81, 0x03, // Input (Cnst,Var,Abs) 38
86 * # 0x05, 0x01, // Usage Page (Generic Desktop) 40
87 * # 0x09, 0x30, // Usage (X) 42
88 * # 0x09, 0x31, // Usage (Y) 44
89 * # 0x55, 0x0d, // Unit Exponent (-3) 46 <-- change to -2
90 * # 0x65, 0x33, // Unit (EnglishLinear: in³) 48 <-- change in³ to in
91 * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 50
92 * # 0x35, 0x00, // Physical Minimum (0) 53
93 * # 0x46, 0x00, 0x08, // Physical Maximum (2048) 55 <-- invalid size
94 * # 0x75, 0x10, // Report Size (16) 58
95 * # 0x95, 0x02, // Report Count (2) 60
96 * # 0x81, 0x02, // Input (Data,Var,Abs) 62
97 * # 0x05, 0x0d, // Usage Page (Digitizers) 64
98 * # 0x09, 0x30, // Usage (Tip Pressure) 66
99 * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 68
100 * # 0x75, 0x10, // Report Size (16) 71
101 * # 0x95, 0x01, // Report Count (1) 73
102 * # 0x81, 0x02, // Input (Data,Var,Abs) 75
103 * # 0x09, 0x3d, // Usage (X Tilt) 77 <-- No tilt reported
104 * # 0x09, 0x3e, // Usage (Y Tilt) 79
105 * # 0x15, 0x81, // Logical Minimum (-127) 81
106 * # 0x25, 0x7f, // Logical Maximum (127) 83
107 * # 0x75, 0x08, // Report Size (8) 85
108 * # 0x95, 0x02, // Report Count (2) 87
109 * # 0x81, 0x02, // Input (Data,Var,Abs) 89
110 * # 0xc0, // End Collection 91
111 * # 0xc0, // End Collection 92
112 * R: 93 05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 45 09 3c 15 00 25 01 7501 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 09 3d09 3e 15 81 25 7f 75 08 95 02 81 02 c0 c0
113 *
114 * Third hidraw node is the pad which sends a combination of keyboard shortcuts until
115 * the tablet is switched to raw mode, then it's mute:
116 *
117 * # Report descriptor length: 65 bytes
118 * # 0x05, 0x01, // Usage Page (Generic Desktop) 0
119 * # 0x09, 0x06, // Usage (Keyboard) 2
120 * # 0xa1, 0x01, // Collection (Application) 4
121 * # 0x85, 0x03, // Report ID (3) 6
122 * # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 8
123 * # 0x19, 0xe0, // UsageMinimum (224) 10
124 * # 0x29, 0xe7, // UsageMaximum (231) 12
125 * # 0x15, 0x00, // Logical Minimum (0) 14
126 * # 0x25, 0x01, // Logical Maximum (1) 16
127 * # 0x75, 0x01, // Report Size (1) 18
128 * # 0x95, 0x08, // Report Count (8) 20
129 * # 0x81, 0x02, // Input (Data,Var,Abs) 22
130 * # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 24
131 * # 0x19, 0x00, // UsageMinimum (0) 26
132 * # 0x29, 0xff, // UsageMaximum (255) 28
133 * # 0x26, 0xff, 0x00, // Logical Maximum (255) 30
134 * # 0x75, 0x08, // Report Size (8) 33
135 * # 0x95, 0x06, // Report Count (6) 35
136 * # 0x81, 0x00, // Input (Data,Arr,Abs) 37
137 * # 0xc0, // End Collection 39
138 * # 0x05, 0x0c, // Usage Page (Consumer) 40
139 * # 0x09, 0x01, // Usage (Consumer Control) 42
140 * # 0xa1, 0x01, // Collection (Application) 44
141 * # 0x85, 0x04, // Report ID (4) 46
142 * # 0x19, 0x00, // UsageMinimum (0) 48
143 * # 0x2a, 0x3c, 0x02, // UsageMaximum (572) 50
144 * # 0x15, 0x00, // Logical Minimum (0) 53
145 * # 0x26, 0x3c, 0x02, // Logical Maximum (572) 55
146 * # 0x95, 0x01, // Report Count (1) 58
147 * # 0x75, 0x10, // Report Size (16) 60
148 * # 0x81, 0x00, // Input (Data,Arr,Abs) 62
149 * # 0xc0, // End Collection 64
150 * R: 65 05 01 09 06 a1 01 85 03 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 0507 19 00 29 ff 26 ff 00 75 08 95 06 81 00 c0 05 0c 09 01 a1 01 85 04 19 00 2a 3c02 15 00 26 3c 02 95 01 75 10 81 00 c0
151 * N: HUION Huion Tablet_H641P
152 */
153
154 #define PAD_REPORT_DESCRIPTOR_LENGTH 65
155 #define PEN_REPORT_DESCRIPTOR_LENGTH 93
156 #define VENDOR_REPORT_DESCRIPTOR_LENGTH 18
157 #define PAD_REPORT_ID 3
158 #define PEN_REPORT_ID 10
159 #define VENDOR_REPORT_ID 8
160 #define PAD_REPORT_LENGTH 8
161 #define PEN_REPORT_LENGTH 10
162 #define VENDOR_REPORT_LENGTH 12
163
164
165 __u8 last_button_state;
166
167 static const __u8 fixed_rdesc_pad[] = {
168 UsagePage_GenericDesktop
169 Usage_GD_Keypad
170 CollectionApplication(
171 // -- Byte 0 in report
172 ReportId(PAD_REPORT_ID)
173 LogicalRange_i8(0, 1)
174 UsagePage_Digitizers
175 Usage_Dig_TabletFunctionKeys
176 CollectionPhysical(
177 // Byte 1 in report - just exists so we get to be a tablet pad
178 Usage_Dig_BarrelSwitch // BTN_STYLUS
179 ReportCount(1)
180 ReportSize(1)
181 Input(Var|Abs)
182 ReportCount(7) // padding
183 Input(Const)
184 // Bytes 2/3 in report - just exists so we get to be a tablet pad
185 UsagePage_GenericDesktop
186 Usage_GD_X
187 Usage_GD_Y
188 ReportCount(2)
189 ReportSize(8)
190 Input(Var|Abs)
191 // Byte 4 in report is the wheel
192 Usage_GD_Wheel
193 LogicalRange_i8(-1, 1)
194 ReportCount(1)
195 ReportSize(8)
196 Input(Var|Rel)
197 // Byte 5 is the button state
198 UsagePage_Button
199 UsageRange_i8(0x01, 0x6)
200 LogicalRange_i8(0x01, 0x6)
201 ReportCount(1)
202 ReportSize(8)
203 Input(Arr|Abs)
204 )
205 // Make sure we match our original report length
206 FixedSizeVendorReport(PAD_REPORT_LENGTH)
207 )
208 };
209
210 static const __u8 fixed_rdesc_pen[] = {
211 UsagePage_Digitizers
212 Usage_Dig_Pen
213 CollectionApplication(
214 // -- Byte 0 in report
215 ReportId(PEN_REPORT_ID)
216 Usage_Dig_Pen
217 CollectionPhysical(
218 // -- Byte 1 in report
219 Usage_Dig_TipSwitch
220 Usage_Dig_BarrelSwitch
221 Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
222 LogicalRange_i8(0, 1)
223 ReportSize(1)
224 ReportCount(3)
225 Input(Var|Abs)
226 ReportCount(4) // Padding
227 Input(Const)
228 Usage_Dig_InRange
229 ReportCount(1)
230 Input(Var|Abs)
231 ReportSize(16)
232 ReportCount(1)
233 PushPop(
234 UsagePage_GenericDesktop
235 Unit(cm)
236 UnitExponent(-1)
237 PhysicalRange_i16(0, 160)
238 LogicalRange_i16(0, 32767)
239 Usage_GD_X
240 Input(Var|Abs) // Bytes 2+3
241 PhysicalRange_i16(0, 100)
242 LogicalRange_i16(0, 32767)
243 Usage_GD_Y
244 Input(Var|Abs) // Bytes 4+5
245 )
246 UsagePage_Digitizers
247 Usage_Dig_TipPressure
248 LogicalRange_i16(0, 8191)
249 Input(Var|Abs) // Byte 6+7
250 // Two bytes padding so we don't need to change the report at all
251 ReportSize(8)
252 ReportCount(2)
253 Input(Const) // Byte 6+7
254 )
255 )
256 };
257
258 static const __u8 fixed_rdesc_vendor[] = {
259 UsagePage_Digitizers
260 Usage_Dig_Pen
261 CollectionApplication(
262 // Byte 0
263 // We leave the pen on the vendor report ID
264 ReportId(VENDOR_REPORT_ID)
265 Usage_Dig_Pen
266 CollectionPhysical(
267 // Byte 1 are the buttons
268 LogicalRange_i8(0, 1)
269 ReportSize(1)
270 Usage_Dig_TipSwitch
271 Usage_Dig_BarrelSwitch
272 Usage_Dig_SecondaryBarrelSwitch
273 ReportCount(3)
274 Input(Var|Abs)
275 ReportCount(4) // Padding
276 Input(Const)
277 Usage_Dig_InRange
278 ReportCount(1)
279 Input(Var|Abs)
280 ReportSize(16)
281 ReportCount(1)
282 PushPop(
283 UsagePage_GenericDesktop
284 Unit(cm)
285 UnitExponent(-1)
286 // Note: reported logical range differs
287 // from the pen report ID for x and y
288 LogicalRange_i16(0, 32000)
289 PhysicalRange_i16(0, 160)
290 // Bytes 2/3 in report
291 Usage_GD_X
292 Input(Var|Abs)
293 LogicalRange_i16(0, 20000)
294 PhysicalRange_i16(0, 100)
295 // Bytes 4/5 in report
296 Usage_GD_Y
297 Input(Var|Abs)
298 )
299 // Bytes 6/7 in report
300 LogicalRange_i16(0, 8192)
301 Usage_Dig_TipPressure
302 Input(Var|Abs)
303 )
304 )
305 UsagePage_GenericDesktop
306 Usage_GD_Keypad
307 CollectionApplication(
308 // Byte 0
309 ReportId(PAD_REPORT_ID)
310 LogicalRange_i8(0, 1)
311 UsagePage_Digitizers
312 Usage_Dig_TabletFunctionKeys
313 CollectionPhysical(
314 // Byte 1 are the buttons
315 Usage_Dig_BarrelSwitch // BTN_STYLUS, needed so we get to be a tablet pad
316 ReportCount(1)
317 ReportSize(1)
318 Input(Var|Abs)
319 ReportCount(7) // Padding
320 Input(Const)
321 // Bytes 2/3 - x/y just exist so we get to be a tablet pad
322 UsagePage_GenericDesktop
323 Usage_GD_X
324 Usage_GD_Y
325 ReportCount(2)
326 ReportSize(8)
327 Input(Var|Abs)
328 // Byte 4 is the button state
329 UsagePage_Button
330 UsageRange_i8(0x01, 0x6)
331 LogicalRange_i8(0x0, 0x1)
332 ReportCount(6)
333 ReportSize(1)
334 Input(Var|Abs)
335 ReportCount(2)
336 Input(Const)
337 // Byte 5 is the wheel
338 UsagePage_GenericDesktop
339 Usage_GD_Wheel
340 LogicalRange_i8(-1, 1)
341 ReportCount(1)
342 ReportSize(8)
343 Input(Var|Rel)
344 )
345 // Make sure we match our original report length
346 FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
347 )
348 };
349
350 static const __u8 disabled_rdesc_pen[] = {
351 FixedSizeVendorReport(PEN_REPORT_LENGTH)
352 };
353
354 static const __u8 disabled_rdesc_pad[] = {
355 FixedSizeVendorReport(PAD_REPORT_LENGTH)
356 };
357
SEC(HID_BPF_RDESC_FIXUP)358 SEC(HID_BPF_RDESC_FIXUP)
359 int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
360 {
361 __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
362 __s32 rdesc_size = hctx->size;
363 __u8 have_fw_id;
364
365 if (!data)
366 return 0; /* EPERM check */
367
368 /* If we have a firmware ID and it matches our expected prefix, we
369 * disable the default pad/pen nodes. They won't send events
370 * but cause duplicate devices.
371 */
372 have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,
373 EXPECTED_FIRMWARE_ID,
374 sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;
375 if (rdesc_size == PAD_REPORT_DESCRIPTOR_LENGTH) {
376 if (have_fw_id) {
377 __builtin_memcpy(data, disabled_rdesc_pad, sizeof(disabled_rdesc_pad));
378 return sizeof(disabled_rdesc_pad);
379 }
380
381 __builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
382 return sizeof(fixed_rdesc_pad);
383 }
384 if (rdesc_size == PEN_REPORT_DESCRIPTOR_LENGTH) {
385 if (have_fw_id) {
386 __builtin_memcpy(data, disabled_rdesc_pen, sizeof(disabled_rdesc_pen));
387 return sizeof(disabled_rdesc_pen);
388 }
389
390 __builtin_memcpy(data, fixed_rdesc_pen, sizeof(fixed_rdesc_pen));
391 return sizeof(fixed_rdesc_pen);
392 }
393 /* Always fix the vendor mode so the tablet will work even if nothing sets
394 * the udev property (e.g. huion-switcher run manually)
395 */
396 if (rdesc_size == VENDOR_REPORT_DESCRIPTOR_LENGTH) {
397 __builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
398 return sizeof(fixed_rdesc_vendor);
399
400 }
401 return 0;
402 }
403
SEC(HID_BPF_DEVICE_EVENT)404 SEC(HID_BPF_DEVICE_EVENT)
405 int BPF_PROG(inspiroy_2_fix_events, struct hid_bpf_ctx *hctx)
406 {
407 __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
408
409 if (!data)
410 return 0; /* EPERM check */
411
412 /* Only sent if tablet is in default mode */
413 if (data[0] == PAD_REPORT_ID) {
414 /* Nicely enough, this device only supports one button down at a time so
415 * the reports are easy to match. Buttons numbered from the top
416 * Button released: 03 00 00 00 00 00 00 00
417 * Button 1: 03 00 05 00 00 00 00 00 -> b
418 * Button 2: 03 00 0c 00 00 00 00 00 -> i
419 * Button 3: 03 00 08 00 00 00 00 00 -> e
420 * Button 4: 03 01 16 00 00 00 00 00 -> Ctrl S
421 * Button 5: 03 00 2c 00 00 00 00 00 -> space
422 * Button 6: 03 05 1d 00 00 00 00 00 -> Ctrl Alt Z
423 *
424 * Wheel down: 03 01 2d 00 00 00 00 00 -> Ctrl -
425 * Wheel up: 03 01 2e 00 00 00 00 00 -> Ctrl =
426 */
427 __u8 button = 0;
428 __u8 wheel = 0;
429
430 switch (data[1] << 8 | data[2]) {
431 case 0x0000:
432 break;
433 case 0x0005:
434 button = 1;
435 break;
436 case 0x000c:
437 button = 2;
438 break;
439 case 0x0008:
440 button = 3;
441 break;
442 case 0x0116:
443 button = 4;
444 break;
445 case 0x002c:
446 button = 5;
447 break;
448 case 0x051d:
449 button = 6;
450 break;
451 case 0x012d:
452 wheel = -1;
453 break;
454 case 0x012e:
455 wheel = 1;
456 break;
457
458 }
459
460 __u8 report[6] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, wheel, button};
461
462 __builtin_memcpy(data, report, sizeof(report));
463 return sizeof(report);
464 }
465
466 /* Nothing to do for the PEN_REPORT_ID, it's already mapped */
467
468 /* Only sent if tablet is in raw mode */
469 if (data[0] == VENDOR_REPORT_ID) {
470 /* Pad reports */
471 if (data[1] & 0x20) {
472 /* See fixed_rdesc_pad */
473 struct pad_report {
474 __u8 report_id;
475 __u8 btn_stylus;
476 __u8 x;
477 __u8 y;
478 __u8 buttons;
479 __u8 wheel;
480 } __attribute__((packed)) *pad_report;
481 __u8 wheel = 0;
482
483 /* Wheel report */
484 if (data[1] == 0xf1) {
485 if (data[5] == 2)
486 wheel = 0xff;
487 else
488 wheel = data[5];
489 } else {
490 /* data[4] are the buttons, mapped correctly */
491 last_button_state = data[4];
492 wheel = 0; // wheel
493 }
494
495 pad_report = (struct pad_report *)data;
496
497 pad_report->report_id = PAD_REPORT_ID;
498 pad_report->btn_stylus = 0;
499 pad_report->x = 0;
500 pad_report->y = 0;
501 pad_report->buttons = last_button_state;
502 pad_report->wheel = wheel;
503
504 return sizeof(struct pad_report);
505 }
506
507 /* Pen reports need nothing done */
508 }
509
510 return 0;
511 }
512
513 HID_BPF_OPS(inspiroy_2) = {
514 .hid_device_event = (void *)inspiroy_2_fix_events,
515 .hid_rdesc_fixup = (void *)hid_fix_rdesc,
516 };
517
518 SEC("syscall")
probe(struct hid_bpf_probe_args * ctx)519 int probe(struct hid_bpf_probe_args *ctx)
520 {
521 switch (ctx->rdesc_size) {
522 case PAD_REPORT_DESCRIPTOR_LENGTH:
523 case PEN_REPORT_DESCRIPTOR_LENGTH:
524 case VENDOR_REPORT_DESCRIPTOR_LENGTH:
525 ctx->retval = 0;
526 break;
527 default:
528 ctx->retval = -EINVAL;
529 }
530
531 return 0;
532 }
533
534 char _license[] SEC("license") = "GPL";
535