// SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2024 Red Hat, Inc */ #include "vmlinux.h" #include "hid_bpf.h" #include "hid_bpf_helpers.h" #include "hid_report_helpers.h" #include #define HID_BPF_ASYNC_MAX_CTX 1 #include "hid_bpf_async.h" #define VID_UGEE 0x28BD /* same PID whether connected directly or through the provided dongle: */ #define PID_ACK05_REMOTE 0x0202 HID_BPF_CONFIG( HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ACK05_REMOTE), ); /* * By default, the pad reports the buttons through a set of key sequences. * * The pad reports a classic keyboard report descriptor: * # HANVON UGEE Shortcut Remote * Report descriptor length: 102 bytes * 0x05, 0x01, // Usage Page (Generic Desktop) 0 * 0x09, 0x02, // Usage (Mouse) 2 * 0xa1, 0x01, // Collection (Application) 4 * 0x85, 0x09, // Report ID (9) 6 * 0x09, 0x01, // Usage (Pointer) 8 * 0xa1, 0x00, // Collection (Physical) 10 * 0x05, 0x09, // Usage Page (Button) 12 * 0x19, 0x01, // UsageMinimum (1) 14 * 0x29, 0x03, // UsageMaximum (3) 16 * 0x15, 0x00, // Logical Minimum (0) 18 * 0x25, 0x01, // Logical Maximum (1) 20 * 0x95, 0x03, // Report Count (3) 22 * 0x75, 0x01, // Report Size (1) 24 * 0x81, 0x02, // Input (Data,Var,Abs) 26 * 0x95, 0x05, // Report Count (5) 28 * 0x81, 0x01, // Input (Cnst,Arr,Abs) 30 * 0x05, 0x01, // Usage Page (Generic Desktop) 32 * 0x09, 0x30, // Usage (X) 34 * 0x09, 0x31, // Usage (Y) 36 * 0x26, 0xff, 0x7f, // Logical Maximum (32767) 38 * 0x95, 0x02, // Report Count (2) 41 * 0x75, 0x10, // Report Size (16) 43 * 0x81, 0x02, // Input (Data,Var,Abs) 45 * 0x05, 0x0d, // Usage Page (Digitizers) 47 * 0x09, 0x30, // Usage (Tip Pressure) 49 * 0x26, 0xff, 0x07, // Logical Maximum (2047) 51 * 0x95, 0x01, // Report Count (1) 54 * 0x75, 0x10, // Report Size (16) 56 * 0x81, 0x02, // Input (Data,Var,Abs) 58 * 0xc0, // End Collection 60 * 0xc0, // End Collection 61 * 0x05, 0x01, // Usage Page (Generic Desktop) 62 * 0x09, 0x06, // Usage (Keyboard) 64 * 0xa1, 0x01, // Collection (Application) 66 * 0x85, 0x06, // Report ID (6) 68 * 0x05, 0x07, // Usage Page (Keyboard/Keypad) 70 * 0x19, 0xe0, // UsageMinimum (224) 72 * 0x29, 0xe7, // UsageMaximum (231) 74 * 0x15, 0x00, // Logical Minimum (0) 76 * 0x25, 0x01, // Logical Maximum (1) 78 * 0x75, 0x01, // Report Size (1) 80 * 0x95, 0x08, // Report Count (8) 82 * 0x81, 0x02, // Input (Data,Var,Abs) 84 * 0x05, 0x07, // Usage Page (Keyboard/Keypad) 86 * 0x19, 0x00, // UsageMinimum (0) 88 * 0x29, 0xff, // UsageMaximum (255) 90 * 0x26, 0xff, 0x00, // Logical Maximum (255) 92 * 0x75, 0x08, // Report Size (8) 95 * 0x95, 0x06, // Report Count (6) 97 * 0x81, 0x00, // Input (Data,Arr,Abs) 99 * 0xc0, // End Collection 101 * * Each button gets assigned the following events: * * Buttons released: 06 00 00 00 00 00 00 00 * Button 1: 06 01 12 00 00 00 00 00 -> LControl + o * Button 2: 06 01 11 00 00 00 00 00 -> LControl + n * Button 3: 06 00 3e 00 00 00 00 00 -> F5 * Button 4: 06 02 00 00 00 00 00 00 -> LShift * Button 5: 06 01 00 00 00 00 00 00 -> LControl * Button 6: 06 04 00 00 00 00 00 00 -> LAlt * Button 7: 06 01 16 00 00 00 00 00 -> LControl + s * Button 8: 06 01 1d 00 00 00 00 00 -> LControl + z * Button 9: 06 00 2c 00 00 00 00 00 -> Space * Button 10: 06 03 1d 00 00 00 00 00 -> LControl + LShift + z * Wheel: 06 01 57 00 00 00 00 00 -> clockwise rotation (LControl + Keypad Plus) * Wheel: 06 01 56 00 00 00 00 00 -> counter-clockwise rotation * (LControl + Keypad Minus) * * However, multiple buttons can be pressed at the same time, and when this happens, * each button gets assigned a new slot in the Input (Data,Arr,Abs): * * Button 1 + 3: 06 01 12 3e 00 00 00 00 -> LControl + o + F5 * * When a modifier is pressed (Button 4, 5, or 6), the assigned key is set to 00: * * Button 5 + 7: 06 01 00 16 00 00 00 00 -> LControl + s * * This is mostly fine, but with Button 8 and Button 10 sharing the same * key value ("z"), there are cases where we can not know which is which. * */ #define PAD_WIRED_DESCRIPTOR_LENGTH 102 #define PAD_DONGLE_DESCRIPTOR_LENGTH 177 #define STYLUS_DESCRIPTOR_LENGTH 109 #define VENDOR_DESCRIPTOR_LENGTH 36 #define PAD_REPORT_ID 6 #define RAW_PAD_REPORT_ID 0xf0 #define RAW_BATTERY_REPORT_ID 0xf2 #define VENDOR_REPORT_ID 2 #define PAD_REPORT_LENGTH 8 #define VENDOR_REPORT_LENGTH 12 __u16 last_button_state; static const __u8 disabled_rdesc[] = { // Make sure we match our original report length FixedSizeVendorReport(VENDOR_REPORT_LENGTH) }; static const __u8 fixed_rdesc_vendor[] = { UsagePage_GenericDesktop Usage_GD_Keypad CollectionApplication( // -- Byte 0 in report ReportId(RAW_PAD_REPORT_ID) // Byte 1 in report - same than report ID ReportCount(1) ReportSize(8) Input(Const) // padding (internal report ID) LogicalMaximum_i8(0) LogicalMaximum_i8(1) UsagePage_Digitizers Usage_Dig_TabletFunctionKeys CollectionPhysical( // Byte 2-3 is the button state UsagePage_Button UsageMinimum_i8(0x01) UsageMaximum_i8(0x0a) LogicalMinimum_i8(0x0) LogicalMaximum_i8(0x1) ReportCount(10) ReportSize(1) Input(Var|Abs) Usage_i8(0x31) // will be mapped as BTN_A / BTN_SOUTH ReportCount(1) Input(Var|Abs) ReportCount(5) // padding Input(Const) // Byte 4 in report - just exists so we get to be a tablet pad Usage_Dig_BarrelSwitch // BTN_STYLUS ReportCount(1) ReportSize(1) Input(Var|Abs) ReportCount(7) // padding Input(Const) // Bytes 5/6 in report - just exists so we get to be a tablet pad UsagePage_GenericDesktop Usage_GD_X Usage_GD_Y ReportCount(2) ReportSize(8) Input(Var|Abs) // Byte 7 in report is the dial Usage_GD_Wheel LogicalMinimum_i8(-1) LogicalMaximum_i8(1) ReportCount(1) ReportSize(8) Input(Var|Rel) ) // -- Byte 0 in report ReportId(RAW_BATTERY_REPORT_ID) // Byte 1 in report - same than report ID ReportCount(1) ReportSize(8) Input(Const) // padding (internal report ID) // Byte 2 in report - always 0x01 Input(Const) // padding (internal report ID) UsagePage_Digitizers /* * We represent the device as a stylus to force the kernel to not * directly query its battery state. Instead the kernel will rely * only on the provided events. */ Usage_Dig_Stylus CollectionPhysical( // Byte 3 in report - battery value UsagePage_BatterySystem Usage_BS_AbsoluteStateOfCharge LogicalMinimum_i8(0) LogicalMaximum_i8(100) ReportCount(1) ReportSize(8) Input(Var|Abs) // Byte 4 in report - charging state Usage_BS_Charging LogicalMinimum_i8(0) LogicalMaximum_i8(1) ReportCount(1) ReportSize(8) Input(Var|Abs) ) ) }; SEC(HID_BPF_RDESC_FIXUP) int BPF_PROG(ack05_fix_rdesc, struct hid_bpf_ctx *hctx) { __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */); __s32 rdesc_size = hctx->size; if (!data) return 0; /* EPERM check */ if (rdesc_size == VENDOR_DESCRIPTOR_LENGTH) { /* * The vendor fixed rdesc is appended after the current one, * to keep the output reports working. */ __builtin_memcpy(data + rdesc_size, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor)); return sizeof(fixed_rdesc_vendor) + rdesc_size; } hid_set_name(hctx->hid, "Disabled by HID-BPF Hanvon Ugee Shortcut Remote"); __builtin_memcpy(data, disabled_rdesc, sizeof(disabled_rdesc)); return sizeof(disabled_rdesc); } static int HID_BPF_ASYNC_FUN(switch_to_raw_mode)(struct hid_bpf_ctx *hid) { static __u8 magic_0[32] = {0x02, 0xb0, 0x04, 0x00, 0x00}; int err; /* * The proprietary driver sends the 3 following packets after the * above one. * These don't seem to have any effect, so we don't send them to save * some processing time. * * static __u8 magic_1[32] = {0x02, 0xb4, 0x01, 0x00, 0x01}; * static __u8 magic_2[32] = {0x02, 0xb4, 0x01, 0x00, 0xff}; * static __u8 magic_3[32] = {0x02, 0xb8, 0x04, 0x00, 0x00}; */ err = hid_bpf_hw_output_report(hid, magic_0, sizeof(magic_0)); if (err < 0) return err; return 0; } SEC(HID_BPF_DEVICE_EVENT) int BPF_PROG(ack05_fix_events, struct hid_bpf_ctx *hctx) { __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PAD_REPORT_LENGTH); int ret = 0; if (!data) return 0; /* EPERM check */ if (data[0] != VENDOR_REPORT_ID) return 0; /* reconnect event */ if (data[1] == 0xf8 && data[2] == 02 && data[3] == 0x01) HID_BPF_ASYNC_DELAYED_CALL(switch_to_raw_mode, hctx, 10); /* button event */ if (data[1] == RAW_PAD_REPORT_ID) { data[0] = data[1]; if (data[7] == 0x02) data[7] = 0xff; ret = 8; } else if (data[1] == RAW_BATTERY_REPORT_ID) { data[0] = data[1]; ret = 5; } return ret; } HID_BPF_OPS(xppen_ack05_remote) = { .hid_device_event = (void *)ack05_fix_events, .hid_rdesc_fixup = (void *)ack05_fix_rdesc, }; SEC("syscall") int probe(struct hid_bpf_probe_args *ctx) { switch (ctx->rdesc_size) { case PAD_WIRED_DESCRIPTOR_LENGTH: case PAD_DONGLE_DESCRIPTOR_LENGTH: case STYLUS_DESCRIPTOR_LENGTH: case VENDOR_DESCRIPTOR_LENGTH: ctx->retval = 0; break; default: ctx->retval = -EINVAL; break; } if (ctx->rdesc_size == VENDOR_DESCRIPTOR_LENGTH) { struct hid_bpf_ctx *hctx = hid_bpf_allocate_context(ctx->hid); if (!hctx) { ctx->retval = -EINVAL; return 0; } ctx->retval = HID_BPF_ASYNC_INIT(switch_to_raw_mode) || switch_to_raw_mode(hctx); hid_bpf_release_context(hctx); } return 0; } char _license[] SEC("license") = "GPL";