535 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/* Copyright (c) 2024 Red Hat, Inc3 */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 0x256C12#define PID_INSPIROY_2_S 0x006613 14HID_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 */19char UDEV_PROP_HUION_FIRMWARE_ID[64];20 21/* The prefix of the firmware ID we expect for this device. The full firmware22 * string has a date suffix, e.g. HUION_T21j_22122123 */24char EXPECTED_FIRMWARE_ID[] = "HUION_T21j_";25 26/* How this BPF program works: the tablet has two modes, firmware mode and27 * tablet mode. In firmware mode (out of the box) the tablet sends button events28 * and the dial as keyboard combinations. In tablet mode it uses a vendor specific29 * hid report to report everything instead.30 * Depending on the mode some hid reports are never sent and the corresponding31 * devices are mute.32 *33 * To switch the tablet use e.g. https://github.com/whot/huion-switcher34 * or one of the tools from the digimend project35 *36 * This BPF works for both modes. The huion-switcher tool sets the37 * HUION_FIRMWARE_ID udev property - if that is set then we disable the firmware38 * 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 in40 * either mode though the drawback is that the device will show up twice if41 * you bind it to all event nodes42 *43 * Default report descriptor for the first exposed hidraw node:44 *45 * # HUION Huion Tablet_H641P46 * # Report descriptor length: 18 bytes47 * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 0xFF00) 048 * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 349 * # 0xa1, 0x01, // Collection (Application) 550 * # 0x85, 0x08, // Report ID (8) 751 * # 0x75, 0x58, // Report Size (88) 952 * # 0x95, 0x01, // Report Count (1) 1153 * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 1354 * # 0x81, 0x02, // Input (Data,Var,Abs) 1555 * # 0xc0, // End Collection 1756 * R: 18 06 00 ff 09 01 a1 01 85 08 75 58 95 01 09 01 81 02 c057 *58 * This rdesc does nothing until the tablet is switched to raw mode, see59 * https://github.com/whot/huion-switcher60 *61 *62 * Second hidraw node is the Pen. This one sends events until the tablet is63 * switched to raw mode, then it's mute.64 *65 * # Report descriptor length: 93 bytes66 * # 0x05, 0x0d, // Usage Page (Digitizers) 067 * # 0x09, 0x02, // Usage (Pen) 268 * # 0xa1, 0x01, // Collection (Application) 469 * # 0x85, 0x0a, // Report ID (10) 670 * # 0x09, 0x20, // Usage (Stylus) 871 * # 0xa1, 0x01, // Collection (Application) 1072 * # 0x09, 0x42, // Usage (Tip Switch) 1273 * # 0x09, 0x44, // Usage (Barrel Switch) 1474 * # 0x09, 0x45, // Usage (Eraser) 1675 * # 0x09, 0x3c, // Usage (Invert) 18 <-- has no Invert eraser76 * # 0x15, 0x00, // Logical Minimum (0) 2077 * # 0x25, 0x01, // Logical Maximum (1) 2278 * # 0x75, 0x01, // Report Size (1) 2479 * # 0x95, 0x06, // Report Count (6) 2680 * # 0x81, 0x02, // Input (Data,Var,Abs) 2881 * # 0x09, 0x32, // Usage (In Range) 3082 * # 0x75, 0x01, // Report Size (1) 3283 * # 0x95, 0x01, // Report Count (1) 3484 * # 0x81, 0x02, // Input (Data,Var,Abs) 3685 * # 0x81, 0x03, // Input (Cnst,Var,Abs) 3886 * # 0x05, 0x01, // Usage Page (Generic Desktop) 4087 * # 0x09, 0x30, // Usage (X) 4288 * # 0x09, 0x31, // Usage (Y) 4489 * # 0x55, 0x0d, // Unit Exponent (-3) 46 <-- change to -290 * # 0x65, 0x33, // Unit (EnglishLinear: in³) 48 <-- change in³ to in91 * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 5092 * # 0x35, 0x00, // Physical Minimum (0) 5393 * # 0x46, 0x00, 0x08, // Physical Maximum (2048) 55 <-- invalid size94 * # 0x75, 0x10, // Report Size (16) 5895 * # 0x95, 0x02, // Report Count (2) 6096 * # 0x81, 0x02, // Input (Data,Var,Abs) 6297 * # 0x05, 0x0d, // Usage Page (Digitizers) 6498 * # 0x09, 0x30, // Usage (Tip Pressure) 6699 * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 68100 * # 0x75, 0x10, // Report Size (16) 71101 * # 0x95, 0x01, // Report Count (1) 73102 * # 0x81, 0x02, // Input (Data,Var,Abs) 75103 * # 0x09, 0x3d, // Usage (X Tilt) 77 <-- No tilt reported104 * # 0x09, 0x3e, // Usage (Y Tilt) 79105 * # 0x15, 0x81, // Logical Minimum (-127) 81106 * # 0x25, 0x7f, // Logical Maximum (127) 83107 * # 0x75, 0x08, // Report Size (8) 85108 * # 0x95, 0x02, // Report Count (2) 87109 * # 0x81, 0x02, // Input (Data,Var,Abs) 89110 * # 0xc0, // End Collection 91111 * # 0xc0, // End Collection 92112 * 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 c0113 *114 * Third hidraw node is the pad which sends a combination of keyboard shortcuts until115 * the tablet is switched to raw mode, then it's mute:116 *117 * # Report descriptor length: 65 bytes118 * # 0x05, 0x01, // Usage Page (Generic Desktop) 0119 * # 0x09, 0x06, // Usage (Keyboard) 2120 * # 0xa1, 0x01, // Collection (Application) 4121 * # 0x85, 0x03, // Report ID (3) 6122 * # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 8123 * # 0x19, 0xe0, // UsageMinimum (224) 10124 * # 0x29, 0xe7, // UsageMaximum (231) 12125 * # 0x15, 0x00, // Logical Minimum (0) 14126 * # 0x25, 0x01, // Logical Maximum (1) 16127 * # 0x75, 0x01, // Report Size (1) 18128 * # 0x95, 0x08, // Report Count (8) 20129 * # 0x81, 0x02, // Input (Data,Var,Abs) 22130 * # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 24131 * # 0x19, 0x00, // UsageMinimum (0) 26132 * # 0x29, 0xff, // UsageMaximum (255) 28133 * # 0x26, 0xff, 0x00, // Logical Maximum (255) 30134 * # 0x75, 0x08, // Report Size (8) 33135 * # 0x95, 0x06, // Report Count (6) 35136 * # 0x81, 0x00, // Input (Data,Arr,Abs) 37137 * # 0xc0, // End Collection 39138 * # 0x05, 0x0c, // Usage Page (Consumer) 40139 * # 0x09, 0x01, // Usage (Consumer Control) 42140 * # 0xa1, 0x01, // Collection (Application) 44141 * # 0x85, 0x04, // Report ID (4) 46142 * # 0x19, 0x00, // UsageMinimum (0) 48143 * # 0x2a, 0x3c, 0x02, // UsageMaximum (572) 50144 * # 0x15, 0x00, // Logical Minimum (0) 53145 * # 0x26, 0x3c, 0x02, // Logical Maximum (572) 55146 * # 0x95, 0x01, // Report Count (1) 58147 * # 0x75, 0x10, // Report Size (16) 60148 * # 0x81, 0x00, // Input (Data,Arr,Abs) 62149 * # 0xc0, // End Collection 64150 * 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 c0151 * N: HUION Huion Tablet_H641P152 */153 154#define PAD_REPORT_DESCRIPTOR_LENGTH 65155#define PEN_REPORT_DESCRIPTOR_LENGTH 93156#define VENDOR_REPORT_DESCRIPTOR_LENGTH 18157#define PAD_REPORT_ID 3158#define PEN_REPORT_ID 10159#define VENDOR_REPORT_ID 8160#define PAD_REPORT_LENGTH 8161#define PEN_REPORT_LENGTH 10162#define VENDOR_REPORT_LENGTH 12163 164 165__u8 last_button_state;166 167static const __u8 fixed_rdesc_pad[] = {168 UsagePage_GenericDesktop169 Usage_GD_Keypad170 CollectionApplication(171 // -- Byte 0 in report172 ReportId(PAD_REPORT_ID)173 LogicalRange_i8(0, 1)174 UsagePage_Digitizers175 Usage_Dig_TabletFunctionKeys176 CollectionPhysical(177 // Byte 1 in report - just exists so we get to be a tablet pad178 Usage_Dig_BarrelSwitch // BTN_STYLUS179 ReportCount(1)180 ReportSize(1)181 Input(Var|Abs)182 ReportCount(7) // padding183 Input(Const)184 // Bytes 2/3 in report - just exists so we get to be a tablet pad185 UsagePage_GenericDesktop186 Usage_GD_X187 Usage_GD_Y188 ReportCount(2)189 ReportSize(8)190 Input(Var|Abs)191 // Byte 4 in report is the wheel192 Usage_GD_Wheel193 LogicalRange_i8(-1, 1)194 ReportCount(1)195 ReportSize(8)196 Input(Var|Rel)197 // Byte 5 is the button state198 UsagePage_Button199 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 length206 FixedSizeVendorReport(PAD_REPORT_LENGTH)207 )208};209 210static const __u8 fixed_rdesc_pen[] = {211 UsagePage_Digitizers212 Usage_Dig_Pen213 CollectionApplication(214 // -- Byte 0 in report215 ReportId(PEN_REPORT_ID)216 Usage_Dig_Pen217 CollectionPhysical(218 // -- Byte 1 in report219 Usage_Dig_TipSwitch220 Usage_Dig_BarrelSwitch221 Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2222 LogicalRange_i8(0, 1)223 ReportSize(1)224 ReportCount(3)225 Input(Var|Abs)226 ReportCount(4) // Padding227 Input(Const)228 Usage_Dig_InRange229 ReportCount(1)230 Input(Var|Abs)231 ReportSize(16)232 ReportCount(1)233 PushPop(234 UsagePage_GenericDesktop235 Unit(cm)236 UnitExponent(-1)237 PhysicalRange_i16(0, 160)238 LogicalRange_i16(0, 32767)239 Usage_GD_X240 Input(Var|Abs) // Bytes 2+3241 PhysicalRange_i16(0, 100)242 LogicalRange_i16(0, 32767)243 Usage_GD_Y244 Input(Var|Abs) // Bytes 4+5245 )246 UsagePage_Digitizers247 Usage_Dig_TipPressure248 LogicalRange_i16(0, 8191)249 Input(Var|Abs) // Byte 6+7250 // Two bytes padding so we don't need to change the report at all251 ReportSize(8)252 ReportCount(2)253 Input(Const) // Byte 6+7254 )255 )256};257 258static const __u8 fixed_rdesc_vendor[] = {259 UsagePage_Digitizers260 Usage_Dig_Pen261 CollectionApplication(262 // Byte 0263 // We leave the pen on the vendor report ID264 ReportId(VENDOR_REPORT_ID)265 Usage_Dig_Pen266 CollectionPhysical(267 // Byte 1 are the buttons268 LogicalRange_i8(0, 1)269 ReportSize(1)270 Usage_Dig_TipSwitch271 Usage_Dig_BarrelSwitch272 Usage_Dig_SecondaryBarrelSwitch273 ReportCount(3)274 Input(Var|Abs)275 ReportCount(4) // Padding276 Input(Const)277 Usage_Dig_InRange278 ReportCount(1)279 Input(Var|Abs)280 ReportSize(16)281 ReportCount(1)282 PushPop(283 UsagePage_GenericDesktop284 Unit(cm)285 UnitExponent(-1)286 // Note: reported logical range differs287 // from the pen report ID for x and y288 LogicalRange_i16(0, 32000)289 PhysicalRange_i16(0, 160)290 // Bytes 2/3 in report291 Usage_GD_X292 Input(Var|Abs)293 LogicalRange_i16(0, 20000)294 PhysicalRange_i16(0, 100)295 // Bytes 4/5 in report296 Usage_GD_Y297 Input(Var|Abs)298 )299 // Bytes 6/7 in report300 LogicalRange_i16(0, 8192)301 Usage_Dig_TipPressure302 Input(Var|Abs)303 )304 )305 UsagePage_GenericDesktop306 Usage_GD_Keypad307 CollectionApplication(308 // Byte 0309 ReportId(PAD_REPORT_ID)310 LogicalRange_i8(0, 1)311 UsagePage_Digitizers312 Usage_Dig_TabletFunctionKeys313 CollectionPhysical(314 // Byte 1 are the buttons315 Usage_Dig_BarrelSwitch // BTN_STYLUS, needed so we get to be a tablet pad316 ReportCount(1)317 ReportSize(1)318 Input(Var|Abs)319 ReportCount(7) // Padding320 Input(Const)321 // Bytes 2/3 - x/y just exist so we get to be a tablet pad322 UsagePage_GenericDesktop323 Usage_GD_X324 Usage_GD_Y325 ReportCount(2)326 ReportSize(8)327 Input(Var|Abs)328 // Byte 4 is the button state329 UsagePage_Button330 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 wheel338 UsagePage_GenericDesktop339 Usage_GD_Wheel340 LogicalRange_i8(-1, 1)341 ReportCount(1)342 ReportSize(8)343 Input(Var|Rel)344 )345 // Make sure we match our original report length346 FixedSizeVendorReport(VENDOR_REPORT_LENGTH)347 )348};349 350static const __u8 disabled_rdesc_pen[] = {351 FixedSizeVendorReport(PEN_REPORT_LENGTH)352};353 354static const __u8 disabled_rdesc_pad[] = {355 FixedSizeVendorReport(PAD_REPORT_LENGTH)356};357 358SEC(HID_BPF_RDESC_FIXUP)359int 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, we369 * disable the default pad/pen nodes. They won't send events370 * 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 sets394 * 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 404SEC(HID_BPF_DEVICE_EVENT)405int 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 so415 * the reports are easy to match. Buttons numbered from the top416 * Button released: 03 00 00 00 00 00 00 00417 * Button 1: 03 00 05 00 00 00 00 00 -> b418 * Button 2: 03 00 0c 00 00 00 00 00 -> i419 * Button 3: 03 00 08 00 00 00 00 00 -> e420 * Button 4: 03 01 16 00 00 00 00 00 -> Ctrl S421 * Button 5: 03 00 2c 00 00 00 00 00 -> space422 * Button 6: 03 05 1d 00 00 00 00 00 -> Ctrl Alt Z423 *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 else488 wheel = data[5];489 } else {490 /* data[4] are the buttons, mapped correctly */491 last_button_state = data[4];492 wheel = 0; // wheel493 }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 513HID_BPF_OPS(inspiroy_2) = {514 .hid_device_event = (void *)inspiroy_2_fix_events,515 .hid_rdesc_fixup = (void *)hid_fix_rdesc,516};517 518SEC("syscall")519int 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 534char _license[] SEC("license") = "GPL";535