| // SPDX-License-Identifier: GPL-2.0-only |
| /* Copyright (c) 2025 Red Hat |
| */ |
| |
| #include "vmlinux.h" |
| #include "hid_bpf.h" |
| #include "hid_bpf_helpers.h" |
| #include "hid_report_helpers.h" |
| #include <bpf/bpf_tracing.h> |
| |
| #define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */ |
| #define PID_DECO_01_V3 0x0947 |
| |
| HID_BPF_CONFIG( |
| HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_DECO_01_V3), |
| ); |
| |
| /* |
| * Default report descriptor reports: |
| * - a report descriptor for the pad buttons, reported as key sequences |
| * - a report descriptor for the pen |
| * - a vendor-specific report descriptor |
| * |
| * The Pad report descriptor, see |
| * https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/issues/54 |
| * |
| * # 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 |
| * |
| * And key events for buttons top->bottom are: |
| * Buttons released: 06 00 00 00 00 00 00 00 |
| * Button1: 06 00 05 00 00 00 00 00 -> b |
| * Button2: 06 00 08 00 00 00 00 00 -> e |
| * Button3: 06 04 00 00 00 00 00 00 -> LAlt |
| * Button4: 06 00 2c 00 00 00 00 00 -> Space |
| * Button5: 06 01 16 00 00 00 00 00 -> LControl + s |
| * Button6: 06 01 1d 00 00 00 00 00 -> LControl + z |
| * Button7: 06 01 57 00 00 00 00 00 -> LControl + Keypad Plus |
| * Button8: 06 01 56 00 00 00 00 00 -> LControl + Keypad Dash |
| * |
| * When multiple buttons are pressed at the same time, the values used to |
| * identify the buttons are identical, but they appear in different bytes of the |
| * record. For example, when button 2 (0x08) and button 1 (0x05) are pressed, |
| * this is the report: |
| * |
| * Buttons 2 and 1: 06 00 08 05 00 00 00 00 -> e + b |
| * |
| * Buttons 1, 2, 4, 5 and 6 can be matched by finding their values in the |
| * report. |
| * |
| * Button 3 is pressed when the 3rd bit is 1. For example, pressing buttons 3 |
| * and 5 generates this report: |
| * |
| * Buttons 3 and 5: 06 05 16 00 00 00 00 00 -> LControl + LAlt + s |
| * -- -- |
| * | | |
| * | `- Button 5 (0x16) |
| * `- 0x05 = 0101. Button 3 is pressed |
| * ^ |
| * |
| * pad_buttons contains a list of buttons that can be matched in |
| * HID_BPF_DEVICE_EVENT. Button 3 as it has a dedicated bit. |
| * |
| * |
| * The Pen report descriptor announces a wrong tilt range: |
| * |
| * Report descriptor length: 109 bytes |
| * 0x05, 0x0d, // Usage Page (Digitizers) 0 |
| * 0x09, 0x02, // Usage (Pen) 2 |
| * 0xa1, 0x01, // Collection (Application) 4 |
| * 0x85, 0x07, // Report ID (7) 6 |
| * 0x09, 0x20, // Usage (Stylus) 8 |
| * 0xa1, 0x01, // Collection (Application) 10 |
| * 0x09, 0x42, // Usage (Tip Switch) 12 |
| * 0x09, 0x44, // Usage (Barrel Switch) 14 |
| * 0x09, 0x45, // Usage (Eraser) 16 |
| * 0x09, 0x3c, // Usage (Invert) 18 |
| * 0x15, 0x00, // Logical Minimum (0) 20 |
| * 0x25, 0x01, // Logical Maximum (1) 22 |
| * 0x75, 0x01, // Report Size (1) 24 |
| * 0x95, 0x04, // Report Count (4) 26 |
| * 0x81, 0x02, // Input (Data,Var,Abs) 28 |
| * 0x95, 0x01, // Report Count (1) 30 |
| * 0x81, 0x03, // Input (Cnst,Var,Abs) 32 |
| * 0x09, 0x32, // Usage (In Range) 34 |
| * 0x95, 0x01, // Report Count (1) 36 |
| * 0x81, 0x02, // Input (Data,Var,Abs) 38 |
| * 0x95, 0x02, // Report Count (2) 40 |
| * 0x81, 0x03, // Input (Cnst,Var,Abs) 42 |
| * 0x75, 0x10, // Report Size (16) 44 |
| * 0x95, 0x01, // Report Count (1) 46 |
| * 0x35, 0x00, // Physical Minimum (0) 48 |
| * 0xa4, // Push 50 |
| * 0x05, 0x01, // Usage Page (Generic Desktop) 51 |
| * 0x09, 0x30, // Usage (X) 53 |
| * 0x65, 0x13, // Unit (EnglishLinear: in) 55 |
| * 0x55, 0x0d, // Unit Exponent (-3) 57 |
| * 0x46, 0x10, 0x27, // Physical Maximum (10000) 59 |
| * 0x26, 0xff, 0x7f, // Logical Maximum (32767) 62 |
| * 0x81, 0x02, // Input (Data,Var,Abs) 65 |
| * 0x09, 0x31, // Usage (Y) 67 |
| * 0x46, 0x6a, 0x18, // Physical Maximum (6250) 69 |
| * 0x26, 0xff, 0x7f, // Logical Maximum (32767) 72 |
| * 0x81, 0x02, // Input (Data,Var,Abs) 75 |
| * 0xb4, // Pop 77 |
| * 0x09, 0x30, // Usage (X) 78 |
| * 0x45, 0x00, // Physical Maximum (0) 80 |
| * 0x26, 0xff, 0x3f, // Logical Maximum (16383) 82 |
| * 0x81, 0x42, // Input (Data,Var,Abs,Null) 85 |
| * 0x09, 0x3d, // Usage (Start) 87 |
| * 0x15, 0x81, // Logical Minimum (-127) 89 <- Change from -127 to -60 |
| * 0x25, 0x7f, // Logical Maximum (127) 91 <- Change from 127 to 60 |
| * 0x75, 0x08, // Report Size (8) 93 |
| * 0x95, 0x01, // Report Count (1) 95 |
| * 0x81, 0x02, // Input (Data,Var,Abs) 97 |
| * 0x09, 0x3e, // Usage (Select) 99 |
| * 0x15, 0x81, // Logical Minimum (-127) 101 <- Change from -127 to -60 |
| * 0x25, 0x7f, // Logical Maximum (127) 103 <- Change from 127 to 60 |
| * 0x81, 0x02, // Input (Data,Var,Abs) 105 |
| * 0xc0, // End Collection 107 |
| * 0xc0, // End Collection 108 |
| */ |
| |
| #define PEN_REPORT_DESCRIPTOR_LENGTH 109 |
| #define PAD_REPORT_DESCRIPTOR_LENGTH 102 |
| #define PAD_REPORT_LENGTH 8 |
| #define PAD_REPORT_ID 6 |
| #define PAD_NUM_BUTTONS 8 |
| |
| static const __u8 fixed_rdesc_pad[] = { |
| UsagePage_GenericDesktop |
| Usage_GD_Keypad |
| CollectionApplication( |
| // Byte 0 in report is the report ID |
| ReportId(PAD_REPORT_ID) |
| ReportCount(1) |
| ReportSize(8) |
| UsagePage_Digitizers |
| Usage_Dig_TabletFunctionKeys |
| CollectionPhysical( |
| // Byte 1 is the button state |
| UsagePage_Button |
| UsageMinimum_i8(0x01) |
| UsageMaximum_i8(PAD_NUM_BUTTONS) |
| LogicalMinimum_i8(0x0) |
| LogicalMaximum_i8(0x1) |
| ReportCount(PAD_NUM_BUTTONS) |
| ReportSize(1) |
| Input(Var|Abs) |
| // Byte 2 in report - just exists so we get to be a tablet pad |
| UsagePage_Digitizers |
| Usage_Dig_BarrelSwitch // BTN_STYLUS |
| ReportCount(1) |
| ReportSize(1) |
| Input(Var|Abs) |
| ReportCount(7) // padding |
| Input(Const) |
| // Bytes 3/4 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 5-7 are padding so we match the original report lengtth |
| ReportCount(3) |
| ReportSize(8) |
| Input(Const) |
| ) |
| ) |
| }; |
| |
| SEC(HID_BPF_RDESC_FIXUP) |
| int BPF_PROG(xppen_deco01v3_rdesc_fixup, struct hid_bpf_ctx *hctx) |
| { |
| __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */); |
| |
| const __u8 wrong_logical_range[] = {0x15, 0x81, 0x25, 0x7f}; |
| const __u8 correct_logical_range[] = {0x15, 0xc4, 0x25, 0x3c}; |
| |
| if (!data) |
| return 0; /* EPERM check */ |
| |
| switch (hctx->size) { |
| case PAD_REPORT_DESCRIPTOR_LENGTH: |
| __builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad)); |
| return sizeof(fixed_rdesc_pad); |
| case PEN_REPORT_DESCRIPTOR_LENGTH: |
| if (__builtin_memcmp(&data[89], wrong_logical_range, |
| sizeof(wrong_logical_range)) == 0) |
| __builtin_memcpy(&data[89], correct_logical_range, |
| sizeof(correct_logical_range)); |
| if (__builtin_memcmp(&data[101], wrong_logical_range, |
| sizeof(wrong_logical_range)) == 0) |
| __builtin_memcpy(&data[101], correct_logical_range, |
| sizeof(correct_logical_range)); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| SEC(HID_BPF_DEVICE_EVENT) |
| int BPF_PROG(xppen_deco01v3_device_event, struct hid_bpf_ctx *hctx) |
| { |
| static const __u8 pad_buttons[] = { 0x05, 0x08, 0x00, 0x2c, 0x16, 0x1d, 0x57, 0x56 }; |
| __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PAD_REPORT_LENGTH /* size */); |
| |
| if (!data) |
| return 0; /* EPERM check */ |
| |
| if (data[0] == PAD_REPORT_ID) { |
| __u8 button_mask = 0; |
| size_t d, b; |
| |
| /* data[1] stores the status of BTN_2 in the 3rd bit*/ |
| if (data[1] & BIT(2)) |
| button_mask |= BIT(2); |
| |
| /* The rest of the descriptor stores the buttons as in pad_buttons */ |
| for (d = 2; d < 8; d++) { |
| for (b = 0; b < sizeof(pad_buttons); b++) { |
| if (data[d] != 0 && data[d] == pad_buttons[b]) |
| button_mask |= BIT(b); |
| } |
| } |
| |
| __u8 report[8] = {PAD_REPORT_ID, button_mask, 0x00}; |
| |
| __builtin_memcpy(data, report, sizeof(report)); |
| } |
| return 0; |
| } |
| |
| HID_BPF_OPS(xppen_deco01v3) = { |
| .hid_rdesc_fixup = (void *)xppen_deco01v3_rdesc_fixup, |
| .hid_device_event = (void *)xppen_deco01v3_device_event, |
| }; |
| |
| SEC("syscall") |
| int probe(struct hid_bpf_probe_args *ctx) |
| { |
| switch (ctx->rdesc_size) { |
| case PAD_REPORT_DESCRIPTOR_LENGTH: |
| case PEN_REPORT_DESCRIPTOR_LENGTH: |
| ctx->retval = 0; |
| break; |
| default: |
| ctx->retval = -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| char _license[] SEC("license") = "GPL"; |