Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/hid/bpf/progs/Huion__Inspiroy-2-M.bpf.c
38242 views
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_M 0x0067
13
14
HID_BPF_CONFIG(
15
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HUION, PID_INSPIROY_2_M),
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_T21k_";
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 133
155
#define PEN_REPORT_DESCRIPTOR_LENGTH 93
156
#define VENDOR_REPORT_DESCRIPTOR_LENGTH 36
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
__u16 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
LogicalMinimum_i8(0)
174
LogicalMaximum_i8(1)
175
UsagePage_Digitizers
176
Usage_Dig_TabletFunctionKeys
177
CollectionPhysical(
178
// Byte 1 in report - just exists so we get to be a tablet pad
179
Usage_Dig_BarrelSwitch // BTN_STYLUS
180
ReportCount(1)
181
ReportSize(1)
182
Input(Var|Abs)
183
ReportCount(7) // padding
184
Input(Const)
185
// Bytes 2/3 in report - just exists so we get to be a tablet pad
186
UsagePage_GenericDesktop
187
Usage_GD_X
188
Usage_GD_Y
189
ReportCount(2)
190
ReportSize(8)
191
Input(Var|Abs)
192
// Byte 4 in report is the wheel
193
Usage_GD_Wheel
194
LogicalMinimum_i8(-1)
195
LogicalMaximum_i8(1)
196
ReportCount(1)
197
ReportSize(8)
198
Input(Var|Rel)
199
// Byte 5 is the button state
200
UsagePage_Button
201
UsageMinimum_i8(0x1)
202
UsageMaximum_i8(0x8)
203
LogicalMinimum_i8(0x1)
204
LogicalMaximum_i8(0x8)
205
ReportCount(1)
206
ReportSize(8)
207
Input(Arr|Abs)
208
)
209
// Make sure we match our original report length
210
FixedSizeVendorReport(PAD_REPORT_LENGTH)
211
)
212
};
213
214
static const __u8 fixed_rdesc_pen[] = {
215
UsagePage_Digitizers
216
Usage_Dig_Pen
217
CollectionApplication(
218
// -- Byte 0 in report
219
ReportId(PEN_REPORT_ID)
220
Usage_Dig_Pen
221
CollectionPhysical(
222
// -- Byte 1 in report
223
Usage_Dig_TipSwitch
224
Usage_Dig_BarrelSwitch
225
Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
226
LogicalMinimum_i8(0)
227
LogicalMaximum_i8(1)
228
ReportSize(1)
229
ReportCount(3)
230
Input(Var|Abs)
231
ReportCount(4) // Padding
232
Input(Const)
233
Usage_Dig_InRange
234
ReportCount(1)
235
Input(Var|Abs)
236
ReportSize(16)
237
ReportCount(1)
238
PushPop(
239
UsagePage_GenericDesktop
240
Unit(cm)
241
UnitExponent(-1)
242
PhysicalMinimum_i16(0)
243
PhysicalMaximum_i16(160)
244
LogicalMinimum_i16(0)
245
LogicalMaximum_i16(32767)
246
Usage_GD_X
247
Input(Var|Abs) // Bytes 2+3
248
PhysicalMinimum_i16(0)
249
PhysicalMaximum_i16(100)
250
LogicalMinimum_i16(0)
251
LogicalMaximum_i16(32767)
252
Usage_GD_Y
253
Input(Var|Abs) // Bytes 4+5
254
)
255
UsagePage_Digitizers
256
Usage_Dig_TipPressure
257
LogicalMinimum_i16(0)
258
LogicalMaximum_i16(8191)
259
Input(Var|Abs) // Byte 6+7
260
// Two bytes padding so we don't need to change the report at all
261
ReportSize(8)
262
ReportCount(2)
263
Input(Const) // Byte 6+7
264
)
265
)
266
};
267
268
static const __u8 fixed_rdesc_vendor[] = {
269
UsagePage_Digitizers
270
Usage_Dig_Pen
271
CollectionApplication(
272
// Byte 0
273
// We leave the pen on the vendor report ID
274
ReportId(VENDOR_REPORT_ID)
275
Usage_Dig_Pen
276
CollectionPhysical(
277
// Byte 1 are the buttons
278
LogicalMinimum_i8(0)
279
LogicalMaximum_i8(1)
280
ReportSize(1)
281
Usage_Dig_TipSwitch
282
Usage_Dig_BarrelSwitch
283
Usage_Dig_SecondaryBarrelSwitch
284
ReportCount(3)
285
Input(Var|Abs)
286
ReportCount(4) // Padding
287
Input(Const)
288
Usage_Dig_InRange
289
ReportCount(1)
290
Input(Var|Abs)
291
ReportSize(16)
292
ReportCount(1)
293
PushPop(
294
UsagePage_GenericDesktop
295
Unit(cm)
296
UnitExponent(-1)
297
// Note: reported logical range differs
298
// from the pen report ID for x and y
299
LogicalMinimum_i16(0)
300
LogicalMaximum_i16(32000)
301
PhysicalMinimum_i16(0)
302
PhysicalMaximum_i16(160)
303
// Bytes 2/3 in report
304
Usage_GD_X
305
Input(Var|Abs)
306
LogicalMinimum_i16(0)
307
LogicalMaximum_i16(20000)
308
PhysicalMinimum_i16(0)
309
PhysicalMaximum_i16(100)
310
// Bytes 4/5 in report
311
Usage_GD_Y
312
Input(Var|Abs)
313
)
314
// Bytes 6/7 in report
315
LogicalMinimum_i16(0)
316
LogicalMaximum_i16(8192)
317
Usage_Dig_TipPressure
318
Input(Var|Abs)
319
)
320
)
321
UsagePage_GenericDesktop
322
Usage_GD_Keypad
323
CollectionApplication(
324
// Byte 0
325
ReportId(PAD_REPORT_ID)
326
LogicalMinimum_i8(0)
327
LogicalMaximum_i8(1)
328
UsagePage_Digitizers
329
Usage_Dig_TabletFunctionKeys
330
CollectionPhysical(
331
// Byte 1 are the buttons
332
Usage_Dig_BarrelSwitch // BTN_STYLUS, needed so we get to be a tablet pad
333
ReportCount(1)
334
ReportSize(1)
335
Input(Var|Abs)
336
ReportCount(7) // Padding
337
Input(Const)
338
// Bytes 2/3 - x/y just exist so we get to be a tablet pad
339
UsagePage_GenericDesktop
340
Usage_GD_X
341
Usage_GD_Y
342
ReportCount(2)
343
ReportSize(8)
344
Input(Var|Abs)
345
// Bytes 4 and 5 are the button state
346
UsagePage_Button
347
UsageMinimum_i8(0x1)
348
UsageMaximum_i8(0xa)
349
LogicalMinimum_i8(0x0)
350
LogicalMaximum_i8(0x1)
351
ReportCount(10)
352
ReportSize(1)
353
Input(Var|Abs)
354
Usage_i8(0x31) // maps to BTN_SOUTH
355
ReportCount(1)
356
Input(Var|Abs)
357
ReportCount(5)
358
Input(Const)
359
// Byte 6 is the wheel
360
UsagePage_GenericDesktop
361
Usage_GD_Wheel
362
LogicalMinimum_i8(-1)
363
LogicalMaximum_i8(1)
364
ReportCount(1)
365
ReportSize(8)
366
Input(Var|Rel)
367
)
368
// Make sure we match our original report length
369
FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
370
)
371
};
372
373
static const __u8 disabled_rdesc_pen[] = {
374
FixedSizeVendorReport(PEN_REPORT_LENGTH)
375
};
376
377
static const __u8 disabled_rdesc_pad[] = {
378
FixedSizeVendorReport(PAD_REPORT_LENGTH)
379
};
380
381
SEC(HID_BPF_RDESC_FIXUP)
382
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
383
{
384
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
385
__s32 rdesc_size = hctx->size;
386
__u8 have_fw_id;
387
388
if (!data)
389
return 0; /* EPERM check */
390
391
/* If we have a firmware ID and it matches our expected prefix, we
392
* disable the default pad/pen nodes. They won't send events
393
* but cause duplicate devices.
394
*/
395
have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,
396
EXPECTED_FIRMWARE_ID,
397
sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;
398
if (rdesc_size == PAD_REPORT_DESCRIPTOR_LENGTH) {
399
if (have_fw_id) {
400
__builtin_memcpy(data, disabled_rdesc_pad, sizeof(disabled_rdesc_pad));
401
return sizeof(disabled_rdesc_pad);
402
}
403
404
__builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
405
return sizeof(fixed_rdesc_pad);
406
}
407
if (rdesc_size == PEN_REPORT_DESCRIPTOR_LENGTH) {
408
if (have_fw_id) {
409
__builtin_memcpy(data, disabled_rdesc_pen, sizeof(disabled_rdesc_pen));
410
return sizeof(disabled_rdesc_pen);
411
}
412
413
__builtin_memcpy(data, fixed_rdesc_pen, sizeof(fixed_rdesc_pen));
414
return sizeof(fixed_rdesc_pen);
415
}
416
/* Always fix the vendor mode so the tablet will work even if nothing sets
417
* the udev property (e.g. huion-switcher run manually)
418
*/
419
if (rdesc_size == VENDOR_REPORT_DESCRIPTOR_LENGTH) {
420
__builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
421
return sizeof(fixed_rdesc_vendor);
422
}
423
return 0;
424
}
425
426
SEC(HID_BPF_DEVICE_EVENT)
427
int BPF_PROG(inspiroy_2_fix_events, struct hid_bpf_ctx *hctx)
428
{
429
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
430
431
if (!data)
432
return 0; /* EPERM check */
433
434
/* Only sent if tablet is in default mode */
435
if (data[0] == PAD_REPORT_ID) {
436
/* Nicely enough, this device only supports one button down at a time so
437
* the reports are easy to match. Buttons numbered from the top
438
* Button released: 03 00 00 00 00 00 00 00
439
* Button 1: 03 00 05 00 00 00 00 00 -> b
440
* Button 2: 03 07 11 00 00 00 00 00 -> Ctrl Shift N
441
* Button 3: 03 00 08 00 00 00 00 00 -> e
442
* Button 4: 03 00 0c 00 00 00 00 00 -> i
443
* Button 5: 03 00 2c 00 00 00 00 00 -> space
444
* Button 6: 03 01 08 00 00 00 00 00 -> Ctrl E
445
* Button 7: 03 01 16 00 00 00 00 00 -> Ctrl S
446
* Button 8: 03 05 1d 00 00 00 00 00 -> Ctrl Alt Z
447
*
448
* Wheel down: 03 01 2d 00 00 00 00 00 -> Ctrl -
449
* Wheel up: 03 01 2e 00 00 00 00 00 -> Ctrl =
450
*/
451
__u8 button = 0;
452
__u8 wheel = 0;
453
454
switch (data[1] << 8 | data[2]) {
455
case 0x0000:
456
break;
457
case 0x0005:
458
button = 1;
459
break;
460
case 0x0711:
461
button = 2;
462
break;
463
case 0x0008:
464
button = 3;
465
break;
466
case 0x000c:
467
button = 4;
468
break;
469
case 0x002c:
470
button = 5;
471
break;
472
case 0x0108:
473
button = 6;
474
break;
475
case 0x0116:
476
button = 7;
477
break;
478
case 0x051d:
479
button = 8;
480
break;
481
case 0x012d:
482
wheel = -1;
483
break;
484
case 0x012e:
485
wheel = 1;
486
break;
487
}
488
489
__u8 report[6] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, wheel, button};
490
491
__builtin_memcpy(data, report, sizeof(report));
492
return sizeof(report);
493
}
494
495
/* Nothing to do for the PEN_REPORT_ID, it's already mapped */
496
497
/* Only sent if tablet is in raw mode */
498
if (data[0] == VENDOR_REPORT_ID) {
499
/* Pad reports */
500
if (data[1] & 0x20) {
501
/* See fixed_rdesc_pad */
502
struct pad_report {
503
__u8 report_id;
504
__u8 btn_stylus;
505
__u8 x;
506
__u8 y;
507
__u16 buttons;
508
__u8 wheel;
509
} __attribute__((packed)) *pad_report;
510
__u8 wheel = 0;
511
512
/* Wheel report */
513
if (data[1] == 0xf1) {
514
if (data[5] == 2)
515
wheel = 0xff;
516
else
517
wheel = data[5];
518
} else {
519
/* data[4] and data[5] are the buttons, mapped correctly */
520
last_button_state = data[4] | (data[5] << 8);
521
wheel = 0; // wheel
522
}
523
524
pad_report = (struct pad_report *)data;
525
526
pad_report->report_id = PAD_REPORT_ID;
527
pad_report->btn_stylus = 0;
528
pad_report->x = 0;
529
pad_report->y = 0;
530
pad_report->buttons = last_button_state;
531
pad_report->wheel = wheel;
532
533
return sizeof(struct pad_report);
534
}
535
536
/* Pen reports need nothing done */
537
}
538
539
return 0;
540
}
541
542
HID_BPF_OPS(inspiroy_2) = {
543
.hid_device_event = (void *)inspiroy_2_fix_events,
544
.hid_rdesc_fixup = (void *)hid_fix_rdesc,
545
};
546
547
SEC("syscall")
548
int probe(struct hid_bpf_probe_args *ctx)
549
{
550
switch (ctx->rdesc_size) {
551
case PAD_REPORT_DESCRIPTOR_LENGTH:
552
case PEN_REPORT_DESCRIPTOR_LENGTH:
553
case VENDOR_REPORT_DESCRIPTOR_LENGTH:
554
ctx->retval = 0;
555
break;
556
default:
557
ctx->retval = -EINVAL;
558
}
559
560
return 0;
561
}
562
563
char _license[] SEC("license") = "GPL";
564
565