Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/hid/bpf/progs/Huion__Inspiroy-2-S.bpf.c
26285 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_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
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(0x6)
203
LogicalMinimum_i8(0x1)
204
LogicalMaximum_i8(0x6)
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
// Byte 4 is the button state
346
UsagePage_Button
347
UsageMinimum_i8(0x1)
348
UsageMaximum_i8(0x6)
349
LogicalMinimum_i8(0x0)
350
LogicalMaximum_i8(0x1)
351
ReportCount(6)
352
ReportSize(1)
353
Input(Var|Abs)
354
ReportCount(2)
355
Input(Const)
356
// Byte 5 is the wheel
357
UsagePage_GenericDesktop
358
Usage_GD_Wheel
359
LogicalMinimum_i8(-1)
360
LogicalMaximum_i8(1)
361
ReportCount(1)
362
ReportSize(8)
363
Input(Var|Rel)
364
)
365
// Make sure we match our original report length
366
FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
367
)
368
};
369
370
static const __u8 disabled_rdesc_pen[] = {
371
FixedSizeVendorReport(PEN_REPORT_LENGTH)
372
};
373
374
static const __u8 disabled_rdesc_pad[] = {
375
FixedSizeVendorReport(PAD_REPORT_LENGTH)
376
};
377
378
SEC(HID_BPF_RDESC_FIXUP)
379
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
380
{
381
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
382
__s32 rdesc_size = hctx->size;
383
__u8 have_fw_id;
384
385
if (!data)
386
return 0; /* EPERM check */
387
388
/* If we have a firmware ID and it matches our expected prefix, we
389
* disable the default pad/pen nodes. They won't send events
390
* but cause duplicate devices.
391
*/
392
have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,
393
EXPECTED_FIRMWARE_ID,
394
sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;
395
if (rdesc_size == PAD_REPORT_DESCRIPTOR_LENGTH) {
396
if (have_fw_id) {
397
__builtin_memcpy(data, disabled_rdesc_pad, sizeof(disabled_rdesc_pad));
398
return sizeof(disabled_rdesc_pad);
399
}
400
401
__builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
402
return sizeof(fixed_rdesc_pad);
403
}
404
if (rdesc_size == PEN_REPORT_DESCRIPTOR_LENGTH) {
405
if (have_fw_id) {
406
__builtin_memcpy(data, disabled_rdesc_pen, sizeof(disabled_rdesc_pen));
407
return sizeof(disabled_rdesc_pen);
408
}
409
410
__builtin_memcpy(data, fixed_rdesc_pen, sizeof(fixed_rdesc_pen));
411
return sizeof(fixed_rdesc_pen);
412
}
413
/* Always fix the vendor mode so the tablet will work even if nothing sets
414
* the udev property (e.g. huion-switcher run manually)
415
*/
416
if (rdesc_size == VENDOR_REPORT_DESCRIPTOR_LENGTH) {
417
__builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
418
return sizeof(fixed_rdesc_vendor);
419
420
}
421
return 0;
422
}
423
424
SEC(HID_BPF_DEVICE_EVENT)
425
int BPF_PROG(inspiroy_2_fix_events, struct hid_bpf_ctx *hctx)
426
{
427
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
428
429
if (!data)
430
return 0; /* EPERM check */
431
432
/* Only sent if tablet is in default mode */
433
if (data[0] == PAD_REPORT_ID) {
434
/* Nicely enough, this device only supports one button down at a time so
435
* the reports are easy to match. Buttons numbered from the top
436
* Button released: 03 00 00 00 00 00 00 00
437
* Button 1: 03 00 05 00 00 00 00 00 -> b
438
* Button 2: 03 00 0c 00 00 00 00 00 -> i
439
* Button 3: 03 00 08 00 00 00 00 00 -> e
440
* Button 4: 03 01 16 00 00 00 00 00 -> Ctrl S
441
* Button 5: 03 00 2c 00 00 00 00 00 -> space
442
* Button 6: 03 05 1d 00 00 00 00 00 -> Ctrl Alt Z
443
*
444
* Wheel down: 03 01 2d 00 00 00 00 00 -> Ctrl -
445
* Wheel up: 03 01 2e 00 00 00 00 00 -> Ctrl =
446
*/
447
__u8 button = 0;
448
__u8 wheel = 0;
449
450
switch (data[1] << 8 | data[2]) {
451
case 0x0000:
452
break;
453
case 0x0005:
454
button = 1;
455
break;
456
case 0x000c:
457
button = 2;
458
break;
459
case 0x0008:
460
button = 3;
461
break;
462
case 0x0116:
463
button = 4;
464
break;
465
case 0x002c:
466
button = 5;
467
break;
468
case 0x051d:
469
button = 6;
470
break;
471
case 0x012d:
472
wheel = -1;
473
break;
474
case 0x012e:
475
wheel = 1;
476
break;
477
478
}
479
480
__u8 report[6] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, wheel, button};
481
482
__builtin_memcpy(data, report, sizeof(report));
483
return sizeof(report);
484
}
485
486
/* Nothing to do for the PEN_REPORT_ID, it's already mapped */
487
488
/* Only sent if tablet is in raw mode */
489
if (data[0] == VENDOR_REPORT_ID) {
490
/* Pad reports */
491
if (data[1] & 0x20) {
492
/* See fixed_rdesc_pad */
493
struct pad_report {
494
__u8 report_id;
495
__u8 btn_stylus;
496
__u8 x;
497
__u8 y;
498
__u8 buttons;
499
__u8 wheel;
500
} __attribute__((packed)) *pad_report;
501
__u8 wheel = 0;
502
503
/* Wheel report */
504
if (data[1] == 0xf1) {
505
if (data[5] == 2)
506
wheel = 0xff;
507
else
508
wheel = data[5];
509
} else {
510
/* data[4] are the buttons, mapped correctly */
511
last_button_state = data[4];
512
wheel = 0; // wheel
513
}
514
515
pad_report = (struct pad_report *)data;
516
517
pad_report->report_id = PAD_REPORT_ID;
518
pad_report->btn_stylus = 0;
519
pad_report->x = 0;
520
pad_report->y = 0;
521
pad_report->buttons = last_button_state;
522
pad_report->wheel = wheel;
523
524
return sizeof(struct pad_report);
525
}
526
527
/* Pen reports need nothing done */
528
}
529
530
return 0;
531
}
532
533
HID_BPF_OPS(inspiroy_2) = {
534
.hid_device_event = (void *)inspiroy_2_fix_events,
535
.hid_rdesc_fixup = (void *)hid_fix_rdesc,
536
};
537
538
SEC("syscall")
539
int probe(struct hid_bpf_probe_args *ctx)
540
{
541
switch (ctx->rdesc_size) {
542
case PAD_REPORT_DESCRIPTOR_LENGTH:
543
case PEN_REPORT_DESCRIPTOR_LENGTH:
544
case VENDOR_REPORT_DESCRIPTOR_LENGTH:
545
ctx->retval = 0;
546
break;
547
default:
548
ctx->retval = -EINVAL;
549
}
550
551
return 0;
552
}
553
554
char _license[] SEC("license") = "GPL";
555
556