Path: blob/master/drivers/hid/bpf/progs/Huion__Inspiroy-2-M.bpf.c
38242 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_M 0x00671213HID_BPF_CONFIG(14HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HUION, PID_INSPIROY_2_M),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_T21k_";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 133154#define PEN_REPORT_DESCRIPTOR_LENGTH 93155#define VENDOR_REPORT_DESCRIPTOR_LENGTH 36156#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__u16 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(0x8)202LogicalMinimum_i8(0x1)203LogicalMaximum_i8(0x8)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// Bytes 4 and 5 are the button state345UsagePage_Button346UsageMinimum_i8(0x1)347UsageMaximum_i8(0xa)348LogicalMinimum_i8(0x0)349LogicalMaximum_i8(0x1)350ReportCount(10)351ReportSize(1)352Input(Var|Abs)353Usage_i8(0x31) // maps to BTN_SOUTH354ReportCount(1)355Input(Var|Abs)356ReportCount(5)357Input(Const)358// Byte 6 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);421}422return 0;423}424425SEC(HID_BPF_DEVICE_EVENT)426int BPF_PROG(inspiroy_2_fix_events, struct hid_bpf_ctx *hctx)427{428__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);429430if (!data)431return 0; /* EPERM check */432433/* Only sent if tablet is in default mode */434if (data[0] == PAD_REPORT_ID) {435/* Nicely enough, this device only supports one button down at a time so436* the reports are easy to match. Buttons numbered from the top437* Button released: 03 00 00 00 00 00 00 00438* Button 1: 03 00 05 00 00 00 00 00 -> b439* Button 2: 03 07 11 00 00 00 00 00 -> Ctrl Shift N440* Button 3: 03 00 08 00 00 00 00 00 -> e441* Button 4: 03 00 0c 00 00 00 00 00 -> i442* Button 5: 03 00 2c 00 00 00 00 00 -> space443* Button 6: 03 01 08 00 00 00 00 00 -> Ctrl E444* Button 7: 03 01 16 00 00 00 00 00 -> Ctrl S445* Button 8: 03 05 1d 00 00 00 00 00 -> Ctrl Alt Z446*447* Wheel down: 03 01 2d 00 00 00 00 00 -> Ctrl -448* Wheel up: 03 01 2e 00 00 00 00 00 -> Ctrl =449*/450__u8 button = 0;451__u8 wheel = 0;452453switch (data[1] << 8 | data[2]) {454case 0x0000:455break;456case 0x0005:457button = 1;458break;459case 0x0711:460button = 2;461break;462case 0x0008:463button = 3;464break;465case 0x000c:466button = 4;467break;468case 0x002c:469button = 5;470break;471case 0x0108:472button = 6;473break;474case 0x0116:475button = 7;476break;477case 0x051d:478button = 8;479break;480case 0x012d:481wheel = -1;482break;483case 0x012e:484wheel = 1;485break;486}487488__u8 report[6] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, wheel, button};489490__builtin_memcpy(data, report, sizeof(report));491return sizeof(report);492}493494/* Nothing to do for the PEN_REPORT_ID, it's already mapped */495496/* Only sent if tablet is in raw mode */497if (data[0] == VENDOR_REPORT_ID) {498/* Pad reports */499if (data[1] & 0x20) {500/* See fixed_rdesc_pad */501struct pad_report {502__u8 report_id;503__u8 btn_stylus;504__u8 x;505__u8 y;506__u16 buttons;507__u8 wheel;508} __attribute__((packed)) *pad_report;509__u8 wheel = 0;510511/* Wheel report */512if (data[1] == 0xf1) {513if (data[5] == 2)514wheel = 0xff;515else516wheel = data[5];517} else {518/* data[4] and data[5] are the buttons, mapped correctly */519last_button_state = data[4] | (data[5] << 8);520wheel = 0; // wheel521}522523pad_report = (struct pad_report *)data;524525pad_report->report_id = PAD_REPORT_ID;526pad_report->btn_stylus = 0;527pad_report->x = 0;528pad_report->y = 0;529pad_report->buttons = last_button_state;530pad_report->wheel = wheel;531532return sizeof(struct pad_report);533}534535/* Pen reports need nothing done */536}537538return 0;539}540541HID_BPF_OPS(inspiroy_2) = {542.hid_device_event = (void *)inspiroy_2_fix_events,543.hid_rdesc_fixup = (void *)hid_fix_rdesc,544};545546SEC("syscall")547int probe(struct hid_bpf_probe_args *ctx)548{549switch (ctx->rdesc_size) {550case PAD_REPORT_DESCRIPTOR_LENGTH:551case PEN_REPORT_DESCRIPTOR_LENGTH:552case VENDOR_REPORT_DESCRIPTOR_LENGTH:553ctx->retval = 0;554break;555default:556ctx->retval = -EINVAL;557}558559return 0;560}561562char _license[] SEC("license") = "GPL";563564565