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