Path: blob/master/drivers/hid/bpf/progs/Huion__Inspiroy-2-S.bpf.c
26285 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;165166static const __u8 fixed_rdesc_pad[] = {167UsagePage_GenericDesktop168Usage_GD_Keypad169CollectionApplication(170// -- Byte 0 in report171ReportId(PAD_REPORT_ID)172LogicalMinimum_i8(0)173LogicalMaximum_i8(1)174UsagePage_Digitizers175Usage_Dig_TabletFunctionKeys176CollectionPhysical(177// Byte 1 in report - just exists so we get to be a tablet pad178Usage_Dig_BarrelSwitch // BTN_STYLUS179ReportCount(1)180ReportSize(1)181Input(Var|Abs)182ReportCount(7) // padding183Input(Const)184// Bytes 2/3 in report - just exists so we get to be a tablet pad185UsagePage_GenericDesktop186Usage_GD_X187Usage_GD_Y188ReportCount(2)189ReportSize(8)190Input(Var|Abs)191// Byte 4 in report is the wheel192Usage_GD_Wheel193LogicalMinimum_i8(-1)194LogicalMaximum_i8(1)195ReportCount(1)196ReportSize(8)197Input(Var|Rel)198// Byte 5 is the button state199UsagePage_Button200UsageMinimum_i8(0x1)201UsageMaximum_i8(0x6)202LogicalMinimum_i8(0x1)203LogicalMaximum_i8(0x6)204ReportCount(1)205ReportSize(8)206Input(Arr|Abs)207)208// Make sure we match our original report length209FixedSizeVendorReport(PAD_REPORT_LENGTH)210)211};212213static const __u8 fixed_rdesc_pen[] = {214UsagePage_Digitizers215Usage_Dig_Pen216CollectionApplication(217// -- Byte 0 in report218ReportId(PEN_REPORT_ID)219Usage_Dig_Pen220CollectionPhysical(221// -- Byte 1 in report222Usage_Dig_TipSwitch223Usage_Dig_BarrelSwitch224Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2225LogicalMinimum_i8(0)226LogicalMaximum_i8(1)227ReportSize(1)228ReportCount(3)229Input(Var|Abs)230ReportCount(4) // Padding231Input(Const)232Usage_Dig_InRange233ReportCount(1)234Input(Var|Abs)235ReportSize(16)236ReportCount(1)237PushPop(238UsagePage_GenericDesktop239Unit(cm)240UnitExponent(-1)241PhysicalMinimum_i16(0)242PhysicalMaximum_i16(160)243LogicalMinimum_i16(0)244LogicalMaximum_i16(32767)245Usage_GD_X246Input(Var|Abs) // Bytes 2+3247PhysicalMinimum_i16(0)248PhysicalMaximum_i16(100)249LogicalMinimum_i16(0)250LogicalMaximum_i16(32767)251Usage_GD_Y252Input(Var|Abs) // Bytes 4+5253)254UsagePage_Digitizers255Usage_Dig_TipPressure256LogicalMinimum_i16(0)257LogicalMaximum_i16(8191)258Input(Var|Abs) // Byte 6+7259// Two bytes padding so we don't need to change the report at all260ReportSize(8)261ReportCount(2)262Input(Const) // Byte 6+7263)264)265};266267static const __u8 fixed_rdesc_vendor[] = {268UsagePage_Digitizers269Usage_Dig_Pen270CollectionApplication(271// Byte 0272// We leave the pen on the vendor report ID273ReportId(VENDOR_REPORT_ID)274Usage_Dig_Pen275CollectionPhysical(276// Byte 1 are the buttons277LogicalMinimum_i8(0)278LogicalMaximum_i8(1)279ReportSize(1)280Usage_Dig_TipSwitch281Usage_Dig_BarrelSwitch282Usage_Dig_SecondaryBarrelSwitch283ReportCount(3)284Input(Var|Abs)285ReportCount(4) // Padding286Input(Const)287Usage_Dig_InRange288ReportCount(1)289Input(Var|Abs)290ReportSize(16)291ReportCount(1)292PushPop(293UsagePage_GenericDesktop294Unit(cm)295UnitExponent(-1)296// Note: reported logical range differs297// from the pen report ID for x and y298LogicalMinimum_i16(0)299LogicalMaximum_i16(32000)300PhysicalMinimum_i16(0)301PhysicalMaximum_i16(160)302// Bytes 2/3 in report303Usage_GD_X304Input(Var|Abs)305LogicalMinimum_i16(0)306LogicalMaximum_i16(20000)307PhysicalMinimum_i16(0)308PhysicalMaximum_i16(100)309// Bytes 4/5 in report310Usage_GD_Y311Input(Var|Abs)312)313// Bytes 6/7 in report314LogicalMinimum_i16(0)315LogicalMaximum_i16(8192)316Usage_Dig_TipPressure317Input(Var|Abs)318)319)320UsagePage_GenericDesktop321Usage_GD_Keypad322CollectionApplication(323// Byte 0324ReportId(PAD_REPORT_ID)325LogicalMinimum_i8(0)326LogicalMaximum_i8(1)327UsagePage_Digitizers328Usage_Dig_TabletFunctionKeys329CollectionPhysical(330// Byte 1 are the buttons331Usage_Dig_BarrelSwitch // BTN_STYLUS, needed so we get to be a tablet pad332ReportCount(1)333ReportSize(1)334Input(Var|Abs)335ReportCount(7) // Padding336Input(Const)337// Bytes 2/3 - x/y just exist so we get to be a tablet pad338UsagePage_GenericDesktop339Usage_GD_X340Usage_GD_Y341ReportCount(2)342ReportSize(8)343Input(Var|Abs)344// Byte 4 is the button state345UsagePage_Button346UsageMinimum_i8(0x1)347UsageMaximum_i8(0x6)348LogicalMinimum_i8(0x0)349LogicalMaximum_i8(0x1)350ReportCount(6)351ReportSize(1)352Input(Var|Abs)353ReportCount(2)354Input(Const)355// Byte 5 is the wheel356UsagePage_GenericDesktop357Usage_GD_Wheel358LogicalMinimum_i8(-1)359LogicalMaximum_i8(1)360ReportCount(1)361ReportSize(8)362Input(Var|Rel)363)364// Make sure we match our original report length365FixedSizeVendorReport(VENDOR_REPORT_LENGTH)366)367};368369static const __u8 disabled_rdesc_pen[] = {370FixedSizeVendorReport(PEN_REPORT_LENGTH)371};372373static const __u8 disabled_rdesc_pad[] = {374FixedSizeVendorReport(PAD_REPORT_LENGTH)375};376377SEC(HID_BPF_RDESC_FIXUP)378int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)379{380__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);381__s32 rdesc_size = hctx->size;382__u8 have_fw_id;383384if (!data)385return 0; /* EPERM check */386387/* If we have a firmware ID and it matches our expected prefix, we388* disable the default pad/pen nodes. They won't send events389* but cause duplicate devices.390*/391have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,392EXPECTED_FIRMWARE_ID,393sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;394if (rdesc_size == PAD_REPORT_DESCRIPTOR_LENGTH) {395if (have_fw_id) {396__builtin_memcpy(data, disabled_rdesc_pad, sizeof(disabled_rdesc_pad));397return sizeof(disabled_rdesc_pad);398}399400__builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));401return sizeof(fixed_rdesc_pad);402}403if (rdesc_size == PEN_REPORT_DESCRIPTOR_LENGTH) {404if (have_fw_id) {405__builtin_memcpy(data, disabled_rdesc_pen, sizeof(disabled_rdesc_pen));406return sizeof(disabled_rdesc_pen);407}408409__builtin_memcpy(data, fixed_rdesc_pen, sizeof(fixed_rdesc_pen));410return sizeof(fixed_rdesc_pen);411}412/* Always fix the vendor mode so the tablet will work even if nothing sets413* the udev property (e.g. huion-switcher run manually)414*/415if (rdesc_size == VENDOR_REPORT_DESCRIPTOR_LENGTH) {416__builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));417return sizeof(fixed_rdesc_vendor);418419}420return 0;421}422423SEC(HID_BPF_DEVICE_EVENT)424int BPF_PROG(inspiroy_2_fix_events, struct hid_bpf_ctx *hctx)425{426__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);427428if (!data)429return 0; /* EPERM check */430431/* Only sent if tablet is in default mode */432if (data[0] == PAD_REPORT_ID) {433/* Nicely enough, this device only supports one button down at a time so434* the reports are easy to match. Buttons numbered from the top435* Button released: 03 00 00 00 00 00 00 00436* Button 1: 03 00 05 00 00 00 00 00 -> b437* Button 2: 03 00 0c 00 00 00 00 00 -> i438* Button 3: 03 00 08 00 00 00 00 00 -> e439* Button 4: 03 01 16 00 00 00 00 00 -> Ctrl S440* Button 5: 03 00 2c 00 00 00 00 00 -> space441* Button 6: 03 05 1d 00 00 00 00 00 -> Ctrl Alt Z442*443* Wheel down: 03 01 2d 00 00 00 00 00 -> Ctrl -444* Wheel up: 03 01 2e 00 00 00 00 00 -> Ctrl =445*/446__u8 button = 0;447__u8 wheel = 0;448449switch (data[1] << 8 | data[2]) {450case 0x0000:451break;452case 0x0005:453button = 1;454break;455case 0x000c:456button = 2;457break;458case 0x0008:459button = 3;460break;461case 0x0116:462button = 4;463break;464case 0x002c:465button = 5;466break;467case 0x051d:468button = 6;469break;470case 0x012d:471wheel = -1;472break;473case 0x012e:474wheel = 1;475break;476477}478479__u8 report[6] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, wheel, button};480481__builtin_memcpy(data, report, sizeof(report));482return sizeof(report);483}484485/* Nothing to do for the PEN_REPORT_ID, it's already mapped */486487/* Only sent if tablet is in raw mode */488if (data[0] == VENDOR_REPORT_ID) {489/* Pad reports */490if (data[1] & 0x20) {491/* See fixed_rdesc_pad */492struct pad_report {493__u8 report_id;494__u8 btn_stylus;495__u8 x;496__u8 y;497__u8 buttons;498__u8 wheel;499} __attribute__((packed)) *pad_report;500__u8 wheel = 0;501502/* Wheel report */503if (data[1] == 0xf1) {504if (data[5] == 2)505wheel = 0xff;506else507wheel = data[5];508} else {509/* data[4] are the buttons, mapped correctly */510last_button_state = data[4];511wheel = 0; // wheel512}513514pad_report = (struct pad_report *)data;515516pad_report->report_id = PAD_REPORT_ID;517pad_report->btn_stylus = 0;518pad_report->x = 0;519pad_report->y = 0;520pad_report->buttons = last_button_state;521pad_report->wheel = wheel;522523return sizeof(struct pad_report);524}525526/* Pen reports need nothing done */527}528529return 0;530}531532HID_BPF_OPS(inspiroy_2) = {533.hid_device_event = (void *)inspiroy_2_fix_events,534.hid_rdesc_fixup = (void *)hid_fix_rdesc,535};536537SEC("syscall")538int probe(struct hid_bpf_probe_args *ctx)539{540switch (ctx->rdesc_size) {541case PAD_REPORT_DESCRIPTOR_LENGTH:542case PEN_REPORT_DESCRIPTOR_LENGTH:543case VENDOR_REPORT_DESCRIPTOR_LENGTH:544ctx->retval = 0;545break;546default:547ctx->retval = -EINVAL;548}549550return 0;551}552553char _license[] SEC("license") = "GPL";554555556