1 // SPDX-License-Identifier: GPL-2.0-only 2 /* Copyright (c) 2024 Red Hat, Inc 3 */ 4 5 #include "vmlinux.h" 6 #include "hid_bpf.h" 7 #include "hid_bpf_helpers.h" 8 #include "hid_report_helpers.h" 9 #include <bpf/bpf_tracing.h> 10 11 #define HID_BPF_ASYNC_MAX_CTX 1 12 #include "hid_bpf_async.h" 13 14 #define VID_UGEE 0x28BD 15 /* same PID whether connected directly or through the provided dongle: */ 16 #define PID_ACK05_REMOTE 0x0202 17 18 19 HID_BPF_CONFIG( 20 HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ACK05_REMOTE), 21 ); 22 23 /* 24 * By default, the pad reports the buttons through a set of key sequences. 25 * 26 * The pad reports a classic keyboard report descriptor: 27 * # HANVON UGEE Shortcut Remote 28 * Report descriptor length: 102 bytes 29 * 0x05, 0x01, // Usage Page (Generic Desktop) 0 30 * 0x09, 0x02, // Usage (Mouse) 2 31 * 0xa1, 0x01, // Collection (Application) 4 32 * 0x85, 0x09, // Report ID (9) 6 33 * 0x09, 0x01, // Usage (Pointer) 8 34 * 0xa1, 0x00, // Collection (Physical) 10 35 * 0x05, 0x09, // Usage Page (Button) 12 36 * 0x19, 0x01, // UsageMinimum (1) 14 37 * 0x29, 0x03, // UsageMaximum (3) 16 38 * 0x15, 0x00, // Logical Minimum (0) 18 39 * 0x25, 0x01, // Logical Maximum (1) 20 40 * 0x95, 0x03, // Report Count (3) 22 41 * 0x75, 0x01, // Report Size (1) 24 42 * 0x81, 0x02, // Input (Data,Var,Abs) 26 43 * 0x95, 0x05, // Report Count (5) 28 44 * 0x81, 0x01, // Input (Cnst,Arr,Abs) 30 45 * 0x05, 0x01, // Usage Page (Generic Desktop) 32 46 * 0x09, 0x30, // Usage (X) 34 47 * 0x09, 0x31, // Usage (Y) 36 48 * 0x26, 0xff, 0x7f, // Logical Maximum (32767) 38 49 * 0x95, 0x02, // Report Count (2) 41 50 * 0x75, 0x10, // Report Size (16) 43 51 * 0x81, 0x02, // Input (Data,Var,Abs) 45 52 * 0x05, 0x0d, // Usage Page (Digitizers) 47 53 * 0x09, 0x30, // Usage (Tip Pressure) 49 54 * 0x26, 0xff, 0x07, // Logical Maximum (2047) 51 55 * 0x95, 0x01, // Report Count (1) 54 56 * 0x75, 0x10, // Report Size (16) 56 57 * 0x81, 0x02, // Input (Data,Var,Abs) 58 58 * 0xc0, // End Collection 60 59 * 0xc0, // End Collection 61 60 * 0x05, 0x01, // Usage Page (Generic Desktop) 62 61 * 0x09, 0x06, // Usage (Keyboard) 64 62 * 0xa1, 0x01, // Collection (Application) 66 63 * 0x85, 0x06, // Report ID (6) 68 64 * 0x05, 0x07, // Usage Page (Keyboard/Keypad) 70 65 * 0x19, 0xe0, // UsageMinimum (224) 72 66 * 0x29, 0xe7, // UsageMaximum (231) 74 67 * 0x15, 0x00, // Logical Minimum (0) 76 68 * 0x25, 0x01, // Logical Maximum (1) 78 69 * 0x75, 0x01, // Report Size (1) 80 70 * 0x95, 0x08, // Report Count (8) 82 71 * 0x81, 0x02, // Input (Data,Var,Abs) 84 72 * 0x05, 0x07, // Usage Page (Keyboard/Keypad) 86 73 * 0x19, 0x00, // UsageMinimum (0) 88 74 * 0x29, 0xff, // UsageMaximum (255) 90 75 * 0x26, 0xff, 0x00, // Logical Maximum (255) 92 76 * 0x75, 0x08, // Report Size (8) 95 77 * 0x95, 0x06, // Report Count (6) 97 78 * 0x81, 0x00, // Input (Data,Arr,Abs) 99 79 * 0xc0, // End Collection 101 80 * 81 * Each button gets assigned the following events: 82 * 83 * Buttons released: 06 00 00 00 00 00 00 00 84 * Button 1: 06 01 12 00 00 00 00 00 -> LControl + o 85 * Button 2: 06 01 11 00 00 00 00 00 -> LControl + n 86 * Button 3: 06 00 3e 00 00 00 00 00 -> F5 87 * Button 4: 06 02 00 00 00 00 00 00 -> LShift 88 * Button 5: 06 01 00 00 00 00 00 00 -> LControl 89 * Button 6: 06 04 00 00 00 00 00 00 -> LAlt 90 * Button 7: 06 01 16 00 00 00 00 00 -> LControl + s 91 * Button 8: 06 01 1d 00 00 00 00 00 -> LControl + z 92 * Button 9: 06 00 2c 00 00 00 00 00 -> Space 93 * Button 10: 06 03 1d 00 00 00 00 00 -> LControl + LShift + z 94 * Wheel: 06 01 57 00 00 00 00 00 -> clockwise rotation (LControl + Keypad Plus) 95 * Wheel: 06 01 56 00 00 00 00 00 -> counter-clockwise rotation 96 * (LControl + Keypad Minus) 97 * 98 * However, multiple buttons can be pressed at the same time, and when this happens, 99 * each button gets assigned a new slot in the Input (Data,Arr,Abs): 100 * 101 * Button 1 + 3: 06 01 12 3e 00 00 00 00 -> LControl + o + F5 102 * 103 * When a modifier is pressed (Button 4, 5, or 6), the assigned key is set to 00: 104 * 105 * Button 5 + 7: 06 01 00 16 00 00 00 00 -> LControl + s 106 * 107 * This is mostly fine, but with Button 8 and Button 10 sharing the same 108 * key value ("z"), there are cases where we can not know which is which. 109 * 110 */ 111 112 #define PAD_WIRED_DESCRIPTOR_LENGTH 102 113 #define PAD_DONGLE_DESCRIPTOR_LENGTH 177 114 #define STYLUS_DESCRIPTOR_LENGTH 109 115 #define VENDOR_DESCRIPTOR_LENGTH 36 116 #define PAD_REPORT_ID 6 117 #define RAW_PAD_REPORT_ID 0xf0 118 #define RAW_BATTERY_REPORT_ID 0xf2 119 #define VENDOR_REPORT_ID 2 120 #define PAD_REPORT_LENGTH 8 121 #define VENDOR_REPORT_LENGTH 12 122 123 __u16 last_button_state; 124 125 static const __u8 disabled_rdesc[] = { 126 // Make sure we match our original report length 127 FixedSizeVendorReport(VENDOR_REPORT_LENGTH) 128 }; 129 130 static const __u8 fixed_rdesc_vendor[] = { 131 UsagePage_GenericDesktop 132 Usage_GD_Keypad 133 CollectionApplication( 134 // -- Byte 0 in report 135 ReportId(RAW_PAD_REPORT_ID) 136 // Byte 1 in report - same than report ID 137 ReportCount(1) 138 ReportSize(8) 139 Input(Const) // padding (internal report ID) 140 LogicalMaximum_i8(0) 141 LogicalMaximum_i8(1) 142 UsagePage_Digitizers 143 Usage_Dig_TabletFunctionKeys 144 CollectionPhysical( 145 // Byte 2-3 is the button state 146 UsagePage_Button 147 UsageMinimum_i8(0x01) 148 UsageMaximum_i8(0x0a) 149 LogicalMinimum_i8(0x0) 150 LogicalMaximum_i8(0x1) 151 ReportCount(10) 152 ReportSize(1) 153 Input(Var|Abs) 154 Usage_i8(0x31) // will be mapped as BTN_A / BTN_SOUTH 155 ReportCount(1) 156 Input(Var|Abs) 157 ReportCount(5) // padding 158 Input(Const) 159 // Byte 4 in report - just exists so we get to be a tablet pad 160 Usage_Dig_BarrelSwitch // BTN_STYLUS 161 ReportCount(1) 162 ReportSize(1) 163 Input(Var|Abs) 164 ReportCount(7) // padding 165 Input(Const) 166 // Bytes 5/6 in report - just exists so we get to be a tablet pad 167 UsagePage_GenericDesktop 168 Usage_GD_X 169 Usage_GD_Y 170 ReportCount(2) 171 ReportSize(8) 172 Input(Var|Abs) 173 // Byte 7 in report is the dial 174 Usage_GD_Wheel 175 LogicalMinimum_i8(-1) 176 LogicalMaximum_i8(1) 177 ReportCount(1) 178 ReportSize(8) 179 Input(Var|Rel) 180 ) 181 // -- Byte 0 in report 182 ReportId(RAW_BATTERY_REPORT_ID) 183 // Byte 1 in report - same than report ID 184 ReportCount(1) 185 ReportSize(8) 186 Input(Const) // padding (internal report ID) 187 // Byte 2 in report - always 0x01 188 Input(Const) // padding (internal report ID) 189 UsagePage_Digitizers 190 /* 191 * We represent the device as a stylus to force the kernel to not 192 * directly query its battery state. Instead the kernel will rely 193 * only on the provided events. 194 */ 195 Usage_Dig_Stylus 196 CollectionPhysical( 197 // Byte 3 in report - battery value 198 UsagePage_BatterySystem 199 Usage_BS_AbsoluteStateOfCharge 200 LogicalMinimum_i8(0) 201 LogicalMaximum_i8(100) 202 ReportCount(1) 203 ReportSize(8) 204 Input(Var|Abs) 205 // Byte 4 in report - charging state 206 Usage_BS_Charging 207 LogicalMinimum_i8(0) 208 LogicalMaximum_i8(1) 209 ReportCount(1) 210 ReportSize(8) 211 Input(Var|Abs) 212 ) 213 ) 214 }; 215 216 SEC(HID_BPF_RDESC_FIXUP) 217 int BPF_PROG(ack05_fix_rdesc, struct hid_bpf_ctx *hctx) 218 { 219 __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */); 220 __s32 rdesc_size = hctx->size; 221 222 if (!data) 223 return 0; /* EPERM check */ 224 225 if (rdesc_size == VENDOR_DESCRIPTOR_LENGTH) { 226 /* 227 * The vendor fixed rdesc is appended after the current one, 228 * to keep the output reports working. 229 */ 230 __builtin_memcpy(data + rdesc_size, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor)); 231 return sizeof(fixed_rdesc_vendor) + rdesc_size; 232 } 233 234 hid_set_name(hctx->hid, "Disabled by HID-BPF Hanvon Ugee Shortcut Remote"); 235 236 __builtin_memcpy(data, disabled_rdesc, sizeof(disabled_rdesc)); 237 return sizeof(disabled_rdesc); 238 } 239 240 static int HID_BPF_ASYNC_FUN(switch_to_raw_mode)(struct hid_bpf_ctx *hid) 241 { 242 static __u8 magic_0[32] = {0x02, 0xb0, 0x04, 0x00, 0x00}; 243 int err; 244 245 /* 246 * The proprietary driver sends the 3 following packets after the 247 * above one. 248 * These don't seem to have any effect, so we don't send them to save 249 * some processing time. 250 * 251 * static __u8 magic_1[32] = {0x02, 0xb4, 0x01, 0x00, 0x01}; 252 * static __u8 magic_2[32] = {0x02, 0xb4, 0x01, 0x00, 0xff}; 253 * static __u8 magic_3[32] = {0x02, 0xb8, 0x04, 0x00, 0x00}; 254 */ 255 256 err = hid_bpf_hw_output_report(hid, magic_0, sizeof(magic_0)); 257 if (err < 0) 258 return err; 259 260 return 0; 261 } 262 263 SEC(HID_BPF_DEVICE_EVENT) 264 int BPF_PROG(ack05_fix_events, struct hid_bpf_ctx *hctx) 265 { 266 __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PAD_REPORT_LENGTH); 267 int ret = 0; 268 269 if (!data) 270 return 0; /* EPERM check */ 271 272 if (data[0] != VENDOR_REPORT_ID) 273 return 0; 274 275 /* reconnect event */ 276 if (data[1] == 0xf8 && data[2] == 02 && data[3] == 0x01) 277 HID_BPF_ASYNC_DELAYED_CALL(switch_to_raw_mode, hctx, 10); 278 279 /* button event */ 280 if (data[1] == RAW_PAD_REPORT_ID) { 281 data[0] = data[1]; 282 if (data[7] == 0x02) 283 data[7] = 0xff; 284 ret = 8; 285 } else if (data[1] == RAW_BATTERY_REPORT_ID) { 286 data[0] = data[1]; 287 ret = 5; 288 } 289 290 return ret; 291 } 292 293 HID_BPF_OPS(xppen_ack05_remote) = { 294 .hid_device_event = (void *)ack05_fix_events, 295 .hid_rdesc_fixup = (void *)ack05_fix_rdesc, 296 }; 297 298 SEC("syscall") 299 int probe(struct hid_bpf_probe_args *ctx) 300 { 301 switch (ctx->rdesc_size) { 302 case PAD_WIRED_DESCRIPTOR_LENGTH: 303 case PAD_DONGLE_DESCRIPTOR_LENGTH: 304 case STYLUS_DESCRIPTOR_LENGTH: 305 case VENDOR_DESCRIPTOR_LENGTH: 306 ctx->retval = 0; 307 break; 308 default: 309 ctx->retval = -EINVAL; 310 break; 311 } 312 313 if (ctx->rdesc_size == VENDOR_DESCRIPTOR_LENGTH) { 314 struct hid_bpf_ctx *hctx = hid_bpf_allocate_context(ctx->hid); 315 316 if (!hctx) { 317 ctx->retval = -EINVAL; 318 return 0; 319 } 320 321 ctx->retval = HID_BPF_ASYNC_INIT(switch_to_raw_mode) || 322 switch_to_raw_mode(hctx); 323 324 hid_bpf_release_context(hctx); 325 } 326 327 return 0; 328 } 329 330 char _license[] SEC("license") = "GPL"; 331