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