Path: blob/master/drivers/hid/bpf/progs/Huion__Inspiroy-2-S.bpf.c
53517 views
// SPDX-License-Identifier: GPL-2.0-only1/* Copyright (c) 2024 Red Hat, Inc2*/34#include "vmlinux.h"5#include "hid_bpf.h"6#include "hid_bpf_helpers.h"7#include "hid_report_helpers.h"8#include <bpf/bpf_tracing.h>910#define VID_HUION 0x256C11#define PID_INSPIROY_2_S 0x00661213HID_BPF_CONFIG(14HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HUION, PID_INSPIROY_2_S),15);1617/* Filled in by udev-hid-bpf */18char UDEV_PROP_HUION_FIRMWARE_ID[64];1920/* The prefix of the firmware ID we expect for this device. The full firmware21* string has a date suffix, e.g. HUION_T21j_22122122*/23char EXPECTED_FIRMWARE_ID[] = "HUION_T21j_";2425/* How this BPF program works: the tablet has two modes, firmware mode and26* tablet mode. In firmware mode (out of the box) the tablet sends button events27* and the dial as keyboard combinations. In tablet mode it uses a vendor specific28* hid report to report everything instead.29* Depending on the mode some hid reports are never sent and the corresponding30* devices are mute.31*32* To switch the tablet use e.g. https://github.com/whot/huion-switcher33* or one of the tools from the digimend project34*35* This BPF works for both modes. The huion-switcher tool sets the36* HUION_FIRMWARE_ID udev property - if that is set then we disable the firmware37* pad and pen reports (by making them vendor collections that are ignored).38* If that property is not set we fix all hidraw nodes so the tablet works in39* either mode though the drawback is that the device will show up twice if40* you bind it to all event nodes41*42* Default report descriptor for the first exposed hidraw node:43*44* # HUION Huion Tablet_H641P45* # Report descriptor length: 18 bytes46* # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 0xFF00) 047* # 0x09, 0x01, // Usage (Vendor Usage 0x01) 348* # 0xa1, 0x01, // Collection (Application) 549* # 0x85, 0x08, // Report ID (8) 750* # 0x75, 0x58, // Report Size (88) 951* # 0x95, 0x01, // Report Count (1) 1152* # 0x09, 0x01, // Usage (Vendor Usage 0x01) 1353* # 0x81, 0x02, // Input (Data,Var,Abs) 1554* # 0xc0, // End Collection 1755* R: 18 06 00 ff 09 01 a1 01 85 08 75 58 95 01 09 01 81 02 c056*57* This rdesc does nothing until the tablet is switched to raw mode, see58* https://github.com/whot/huion-switcher59*60*61* Second hidraw node is the Pen. This one sends events until the tablet is62* switched to raw mode, then it's mute.63*64* # Report descriptor length: 93 bytes65* # 0x05, 0x0d, // Usage Page (Digitizers) 066* # 0x09, 0x02, // Usage (Pen) 267* # 0xa1, 0x01, // Collection (Application) 468* # 0x85, 0x0a, // Report ID (10) 669* # 0x09, 0x20, // Usage (Stylus) 870* # 0xa1, 0x01, // Collection (Application) 1071* # 0x09, 0x42, // Usage (Tip Switch) 1272* # 0x09, 0x44, // Usage (Barrel Switch) 1473* # 0x09, 0x45, // Usage (Eraser) 1674* # 0x09, 0x3c, // Usage (Invert) 18 <-- has no Invert eraser75* # 0x15, 0x00, // Logical Minimum (0) 2076* # 0x25, 0x01, // Logical Maximum (1) 2277* # 0x75, 0x01, // Report Size (1) 2478* # 0x95, 0x06, // Report Count (6) 2679* # 0x81, 0x02, // Input (Data,Var,Abs) 2880* # 0x09, 0x32, // Usage (In Range) 3081* # 0x75, 0x01, // Report Size (1) 3282* # 0x95, 0x01, // Report Count (1) 3483* # 0x81, 0x02, // Input (Data,Var,Abs) 3684* # 0x81, 0x03, // Input (Cnst,Var,Abs) 3885* # 0x05, 0x01, // Usage Page (Generic Desktop) 4086* # 0x09, 0x30, // Usage (X) 4287* # 0x09, 0x31, // Usage (Y) 4488* # 0x55, 0x0d, // Unit Exponent (-3) 46 <-- change to -289* # 0x65, 0x33, // Unit (EnglishLinear: in³) 48 <-- change in³ to in90* # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 5091* # 0x35, 0x00, // Physical Minimum (0) 5392* # 0x46, 0x00, 0x08, // Physical Maximum (2048) 55 <-- invalid size93* # 0x75, 0x10, // Report Size (16) 5894* # 0x95, 0x02, // Report Count (2) 6095* # 0x81, 0x02, // Input (Data,Var,Abs) 6296* # 0x05, 0x0d, // Usage Page (Digitizers) 6497* # 0x09, 0x30, // Usage (Tip Pressure) 6698* # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 6899* # 0x75, 0x10, // Report Size (16) 71100* # 0x95, 0x01, // Report Count (1) 73101* # 0x81, 0x02, // Input (Data,Var,Abs) 75102* # 0x09, 0x3d, // Usage (X Tilt) 77 <-- No tilt reported103* # 0x09, 0x3e, // Usage (Y Tilt) 79104* # 0x15, 0x81, // Logical Minimum (-127) 81105* # 0x25, 0x7f, // Logical Maximum (127) 83106* # 0x75, 0x08, // Report Size (8) 85107* # 0x95, 0x02, // Report Count (2) 87108* # 0x81, 0x02, // Input (Data,Var,Abs) 89109* # 0xc0, // End Collection 91110* # 0xc0, // End Collection 92111* 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 c0112*113* Third hidraw node is the pad which sends a combination of keyboard shortcuts until114* the tablet is switched to raw mode, then it's mute:115*116* # Report descriptor length: 65 bytes117* # 0x05, 0x01, // Usage Page (Generic Desktop) 0118* # 0x09, 0x06, // Usage (Keyboard) 2119* # 0xa1, 0x01, // Collection (Application) 4120* # 0x85, 0x03, // Report ID (3) 6121* # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 8122* # 0x19, 0xe0, // UsageMinimum (224) 10123* # 0x29, 0xe7, // UsageMaximum (231) 12124* # 0x15, 0x00, // Logical Minimum (0) 14125* # 0x25, 0x01, // Logical Maximum (1) 16126* # 0x75, 0x01, // Report Size (1) 18127* # 0x95, 0x08, // Report Count (8) 20128* # 0x81, 0x02, // Input (Data,Var,Abs) 22129* # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 24130* # 0x19, 0x00, // UsageMinimum (0) 26131* # 0x29, 0xff, // UsageMaximum (255) 28132* # 0x26, 0xff, 0x00, // Logical Maximum (255) 30133* # 0x75, 0x08, // Report Size (8) 33134* # 0x95, 0x06, // Report Count (6) 35135* # 0x81, 0x00, // Input (Data,Arr,Abs) 37136* # 0xc0, // End Collection 39137* # 0x05, 0x0c, // Usage Page (Consumer) 40138* # 0x09, 0x01, // Usage (Consumer Control) 42139* # 0xa1, 0x01, // Collection (Application) 44140* # 0x85, 0x04, // Report ID (4) 46141* # 0x19, 0x00, // UsageMinimum (0) 48142* # 0x2a, 0x3c, 0x02, // UsageMaximum (572) 50143* # 0x15, 0x00, // Logical Minimum (0) 53144* # 0x26, 0x3c, 0x02, // Logical Maximum (572) 55145* # 0x95, 0x01, // Report Count (1) 58146* # 0x75, 0x10, // Report Size (16) 60147* # 0x81, 0x00, // Input (Data,Arr,Abs) 62148* # 0xc0, // End Collection 64149* 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 c0150* N: HUION Huion Tablet_H641P151*/152153#define PAD_REPORT_DESCRIPTOR_LENGTH 65154#define PEN_REPORT_DESCRIPTOR_LENGTH 93155#define VENDOR_REPORT_DESCRIPTOR_LENGTH 18156#define PAD_REPORT_ID 3157#define PEN_REPORT_ID 10158#define VENDOR_REPORT_ID 8159#define PAD_REPORT_LENGTH 8160#define PEN_REPORT_LENGTH 10161#define VENDOR_REPORT_LENGTH 12162163164__u8 last_button_state;165__u8 last_tip_state;166__u8 last_sec_barrel_state;167__u8 force_tip_down_count;168169static const __u8 fixed_rdesc_pad[] = {170UsagePage_GenericDesktop171Usage_GD_Keypad172CollectionApplication(173// -- Byte 0 in report174ReportId(PAD_REPORT_ID)175LogicalMinimum_i8(0)176LogicalMaximum_i8(1)177UsagePage_Digitizers178Usage_Dig_TabletFunctionKeys179CollectionPhysical(180// Byte 1 in report - just exists so we get to be a tablet pad181Usage_Dig_BarrelSwitch // BTN_STYLUS182ReportCount(1)183ReportSize(1)184Input(Var|Abs)185ReportCount(7) // padding186Input(Const)187// Bytes 2/3 in report - just exists so we get to be a tablet pad188UsagePage_GenericDesktop189Usage_GD_X190Usage_GD_Y191ReportCount(2)192ReportSize(8)193Input(Var|Abs)194// Byte 4 in report is the wheel195Usage_GD_Wheel196LogicalMinimum_i8(-1)197LogicalMaximum_i8(1)198ReportCount(1)199ReportSize(8)200Input(Var|Rel)201// Byte 5 is the button state202UsagePage_Button203UsageMinimum_i8(0x1)204UsageMaximum_i8(0x6)205LogicalMinimum_i8(0x1)206LogicalMaximum_i8(0x6)207ReportCount(1)208ReportSize(8)209Input(Arr|Abs)210)211// Make sure we match our original report length212FixedSizeVendorReport(PAD_REPORT_LENGTH)213)214};215216static const __u8 fixed_rdesc_pen[] = {217UsagePage_Digitizers218Usage_Dig_Pen219CollectionApplication(220// -- Byte 0 in report221ReportId(PEN_REPORT_ID)222Usage_Dig_Pen223CollectionPhysical(224// -- Byte 1 in report225Usage_Dig_TipSwitch226Usage_Dig_BarrelSwitch227Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2228LogicalMinimum_i8(0)229LogicalMaximum_i8(1)230ReportSize(1)231ReportCount(3)232Input(Var|Abs)233ReportCount(4) // Padding234Input(Const)235Usage_Dig_InRange236ReportCount(1)237Input(Var|Abs)238ReportSize(16)239ReportCount(1)240PushPop(241UsagePage_GenericDesktop242Unit(cm)243UnitExponent(-1)244PhysicalMinimum_i16(0)245PhysicalMaximum_i16(160)246LogicalMinimum_i16(0)247LogicalMaximum_i16(32767)248Usage_GD_X249Input(Var|Abs) // Bytes 2+3250PhysicalMinimum_i16(0)251PhysicalMaximum_i16(100)252LogicalMinimum_i16(0)253LogicalMaximum_i16(32767)254Usage_GD_Y255Input(Var|Abs) // Bytes 4+5256)257UsagePage_Digitizers258Usage_Dig_TipPressure259LogicalMinimum_i16(0)260LogicalMaximum_i16(8191)261Input(Var|Abs) // Byte 6+7262// Two bytes padding so we don't need to change the report at all263ReportSize(8)264ReportCount(2)265Input(Const) // Byte 6+7266)267)268};269270static const __u8 fixed_rdesc_vendor[] = {271UsagePage_Digitizers272Usage_Dig_Pen273CollectionApplication(274// Byte 0275// We leave the pen on the vendor report ID276ReportId(VENDOR_REPORT_ID)277Usage_Dig_Pen278CollectionPhysical(279// Byte 1 are the buttons280LogicalMinimum_i8(0)281LogicalMaximum_i8(1)282ReportSize(1)283Usage_Dig_TipSwitch284Usage_Dig_BarrelSwitch285Usage_Dig_SecondaryBarrelSwitch286ReportCount(3)287Input(Var|Abs)288ReportCount(4) // Padding289Input(Const)290Usage_Dig_InRange291ReportCount(1)292Input(Var|Abs)293ReportSize(16)294ReportCount(1)295PushPop(296UsagePage_GenericDesktop297Unit(cm)298UnitExponent(-1)299// Note: reported logical range differs300// from the pen report ID for x and y301LogicalMinimum_i16(0)302LogicalMaximum_i16(32000)303PhysicalMinimum_i16(0)304PhysicalMaximum_i16(160)305// Bytes 2/3 in report306Usage_GD_X307Input(Var|Abs)308LogicalMinimum_i16(0)309LogicalMaximum_i16(20000)310PhysicalMinimum_i16(0)311PhysicalMaximum_i16(100)312// Bytes 4/5 in report313Usage_GD_Y314Input(Var|Abs)315)316// Bytes 6/7 in report317LogicalMinimum_i16(0)318LogicalMaximum_i16(8192)319Usage_Dig_TipPressure320Input(Var|Abs)321)322)323UsagePage_GenericDesktop324Usage_GD_Keypad325CollectionApplication(326// Byte 0327ReportId(PAD_REPORT_ID)328LogicalMinimum_i8(0)329LogicalMaximum_i8(1)330UsagePage_Digitizers331Usage_Dig_TabletFunctionKeys332CollectionPhysical(333// Byte 1 are the buttons334Usage_Dig_BarrelSwitch // BTN_STYLUS, needed so we get to be a tablet pad335ReportCount(1)336ReportSize(1)337Input(Var|Abs)338ReportCount(7) // Padding339Input(Const)340// Bytes 2/3 - x/y just exist so we get to be a tablet pad341UsagePage_GenericDesktop342Usage_GD_X343Usage_GD_Y344ReportCount(2)345ReportSize(8)346Input(Var|Abs)347// Byte 4 is the button state348UsagePage_Button349UsageMinimum_i8(0x1)350UsageMaximum_i8(0x6)351LogicalMinimum_i8(0x0)352LogicalMaximum_i8(0x1)353ReportCount(6)354ReportSize(1)355Input(Var|Abs)356ReportCount(2)357Input(Const)358// Byte 5 is the wheel359UsagePage_GenericDesktop360Usage_GD_Wheel361LogicalMinimum_i8(-1)362LogicalMaximum_i8(1)363ReportCount(1)364ReportSize(8)365Input(Var|Rel)366)367// Make sure we match our original report length368FixedSizeVendorReport(VENDOR_REPORT_LENGTH)369)370};371372static const __u8 disabled_rdesc_pen[] = {373FixedSizeVendorReport(PEN_REPORT_LENGTH)374};375376static const __u8 disabled_rdesc_pad[] = {377FixedSizeVendorReport(PAD_REPORT_LENGTH)378};379380SEC(HID_BPF_RDESC_FIXUP)381int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)382{383__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);384__s32 rdesc_size = hctx->size;385__u8 have_fw_id;386387if (!data)388return 0; /* EPERM check */389390/* If we have a firmware ID and it matches our expected prefix, we391* disable the default pad/pen nodes. They won't send events392* but cause duplicate devices.393*/394have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,395EXPECTED_FIRMWARE_ID,396sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;397if (rdesc_size == PAD_REPORT_DESCRIPTOR_LENGTH) {398if (have_fw_id) {399__builtin_memcpy(data, disabled_rdesc_pad, sizeof(disabled_rdesc_pad));400return sizeof(disabled_rdesc_pad);401}402403__builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));404return sizeof(fixed_rdesc_pad);405}406if (rdesc_size == PEN_REPORT_DESCRIPTOR_LENGTH) {407if (have_fw_id) {408__builtin_memcpy(data, disabled_rdesc_pen, sizeof(disabled_rdesc_pen));409return sizeof(disabled_rdesc_pen);410}411412__builtin_memcpy(data, fixed_rdesc_pen, sizeof(fixed_rdesc_pen));413return sizeof(fixed_rdesc_pen);414}415/* Always fix the vendor mode so the tablet will work even if nothing sets416* the udev property (e.g. huion-switcher run manually)417*/418if (rdesc_size == VENDOR_REPORT_DESCRIPTOR_LENGTH) {419__builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));420return sizeof(fixed_rdesc_vendor);421422}423return 0;424}425426SEC(HID_BPF_DEVICE_EVENT)427int BPF_PROG(inspiroy_2_fix_events, struct hid_bpf_ctx *hctx)428{429__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);430431if (!data)432return 0; /* EPERM check */433434/* Only sent if tablet is in default mode */435if (data[0] == PAD_REPORT_ID) {436/* Nicely enough, this device only supports one button down at a time so437* the reports are easy to match. Buttons numbered from the top438* Button released: 03 00 00 00 00 00 00 00439* Button 1: 03 00 05 00 00 00 00 00 -> b440* Button 2: 03 00 0c 00 00 00 00 00 -> i441* Button 3: 03 00 08 00 00 00 00 00 -> e442* Button 4: 03 01 16 00 00 00 00 00 -> Ctrl S443* Button 5: 03 00 2c 00 00 00 00 00 -> space444* Button 6: 03 05 1d 00 00 00 00 00 -> Ctrl Alt Z445*446* Wheel down: 03 01 2d 00 00 00 00 00 -> Ctrl -447* Wheel up: 03 01 2e 00 00 00 00 00 -> Ctrl =448*/449__u8 button = 0;450__u8 wheel = 0;451452switch (data[1] << 8 | data[2]) {453case 0x0000:454break;455case 0x0005:456button = 1;457break;458case 0x000c:459button = 2;460break;461case 0x0008:462button = 3;463break;464case 0x0116:465button = 4;466break;467case 0x002c:468button = 5;469break;470case 0x051d:471button = 6;472break;473case 0x012d:474wheel = -1;475break;476case 0x012e:477wheel = 1;478break;479480}481482__u8 report[6] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, wheel, button};483484__builtin_memcpy(data, report, sizeof(report));485return sizeof(report);486}487488/* Nothing to do for the PEN_REPORT_ID, it's already mapped */489490/* Only sent if tablet is in raw mode */491if (data[0] == VENDOR_REPORT_ID) {492/* Pad reports */493if (data[1] & 0x20) {494/* See fixed_rdesc_pad */495struct pad_report {496__u8 report_id;497__u8 btn_stylus;498__u8 x;499__u8 y;500__u8 buttons;501__u8 wheel;502} __attribute__((packed)) *pad_report;503__u8 wheel = 0;504505/* Wheel report */506if (data[1] == 0xf1) {507if (data[5] == 2)508wheel = 0xff;509else510wheel = data[5];511} else {512/* data[4] are the buttons, mapped correctly */513last_button_state = data[4];514wheel = 0; // wheel515}516517pad_report = (struct pad_report *)data;518519pad_report->report_id = PAD_REPORT_ID;520pad_report->btn_stylus = 0;521pad_report->x = 0;522pad_report->y = 0;523pad_report->buttons = last_button_state;524pad_report->wheel = wheel;525526return sizeof(struct pad_report);527} else if (data[1] & 0x80) { /* Pen reports with InRange 1 */528__u8 tip_state = data[1] & 0x1;529__u8 sec_barrel_state = data[1] & 0x4;530531if (force_tip_down_count > 0) {532data[1] |= 0x1;533--force_tip_down_count;534if (tip_state)535force_tip_down_count = 0;536}537538/* Tip was down and we just pressed or released the539* secondary barrel switch (the physical eraser540* button). The device will send up to 4541* reports with Tip Switch 0 and sometimes542* this report has Tip Switch 0.543*/544if (last_tip_state &&545last_sec_barrel_state != sec_barrel_state) {546force_tip_down_count = 4;547data[1] |= 0x1;548}549last_tip_state = tip_state;550last_sec_barrel_state = sec_barrel_state;551}552}553554return 0;555}556557HID_BPF_OPS(inspiroy_2) = {558.hid_device_event = (void *)inspiroy_2_fix_events,559.hid_rdesc_fixup = (void *)hid_fix_rdesc,560};561562SEC("syscall")563int probe(struct hid_bpf_probe_args *ctx)564{565switch (ctx->rdesc_size) {566case PAD_REPORT_DESCRIPTOR_LENGTH:567case PEN_REPORT_DESCRIPTOR_LENGTH:568case VENDOR_REPORT_DESCRIPTOR_LENGTH:569ctx->retval = 0;570break;571default:572ctx->retval = -EINVAL;573}574575return 0;576}577578char _license[] SEC("license") = "GPL";579580581