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 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 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") 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