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 UsagePage_Digitizers 161 Usage_Dig_BarrelSwitch // BTN_STYLUS 162 ReportCount(1) 163 ReportSize(1) 164 Input(Var|Abs) 165 ReportCount(7) // padding 166 Input(Const) 167 // Bytes 5/6 in report - just exists so we get to be a tablet pad 168 UsagePage_GenericDesktop 169 Usage_GD_X 170 Usage_GD_Y 171 ReportCount(2) 172 ReportSize(8) 173 Input(Var|Abs) 174 // Byte 7 in report is the dial 175 Usage_GD_Wheel 176 LogicalMinimum_i8(-1) 177 LogicalMaximum_i8(1) 178 ReportCount(1) 179 ReportSize(8) 180 Input(Var|Rel) 181 ) 182 // -- Byte 0 in report 183 ReportId(RAW_BATTERY_REPORT_ID) 184 // Byte 1 in report - same than report ID 185 ReportCount(1) 186 ReportSize(8) 187 Input(Const) // padding (internal report ID) 188 // Byte 2 in report - always 0x01 189 Input(Const) // padding (internal report ID) 190 UsagePage_Digitizers 191 /* 192 * We represent the device as a stylus to force the kernel to not 193 * directly query its battery state. Instead the kernel will rely 194 * only on the provided events. 195 */ 196 Usage_Dig_Stylus 197 CollectionPhysical( 198 // Byte 3 in report - battery value 199 UsagePage_BatterySystem 200 Usage_BS_AbsoluteStateOfCharge 201 LogicalMinimum_i8(0) 202 LogicalMaximum_i8(100) 203 ReportCount(1) 204 ReportSize(8) 205 Input(Var|Abs) 206 // Byte 4 in report - charging state 207 Usage_BS_Charging 208 LogicalMinimum_i8(0) 209 LogicalMaximum_i8(1) 210 ReportCount(1) 211 ReportSize(8) 212 Input(Var|Abs) 213 ) 214 ) 215 }; 216 217 SEC(HID_BPF_RDESC_FIXUP) 218 int BPF_PROG(ack05_fix_rdesc, struct hid_bpf_ctx *hctx) 219 { 220 __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */); 221 __s32 rdesc_size = hctx->size; 222 223 if (!data) 224 return 0; /* EPERM check */ 225 226 if (rdesc_size == VENDOR_DESCRIPTOR_LENGTH) { 227 /* 228 * The vendor fixed rdesc is appended after the current one, 229 * to keep the output reports working. 230 */ 231 __builtin_memcpy(data + rdesc_size, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor)); 232 return sizeof(fixed_rdesc_vendor) + rdesc_size; 233 } 234 235 hid_set_name(hctx->hid, "Disabled by HID-BPF Hanvon Ugee Shortcut Remote"); 236 237 __builtin_memcpy(data, disabled_rdesc, sizeof(disabled_rdesc)); 238 return sizeof(disabled_rdesc); 239 } 240 241 static int HID_BPF_ASYNC_FUN(switch_to_raw_mode)(struct hid_bpf_ctx *hid) 242 { 243 static __u8 magic_0[32] = {0x02, 0xb0, 0x04, 0x00, 0x00}; 244 int err; 245 246 /* 247 * The proprietary driver sends the 3 following packets after the 248 * above one. 249 * These don't seem to have any effect, so we don't send them to save 250 * some processing time. 251 * 252 * static __u8 magic_1[32] = {0x02, 0xb4, 0x01, 0x00, 0x01}; 253 * static __u8 magic_2[32] = {0x02, 0xb4, 0x01, 0x00, 0xff}; 254 * static __u8 magic_3[32] = {0x02, 0xb8, 0x04, 0x00, 0x00}; 255 */ 256 257 err = hid_bpf_hw_output_report(hid, magic_0, sizeof(magic_0)); 258 if (err < 0) 259 return err; 260 261 return 0; 262 } 263 264 SEC(HID_BPF_DEVICE_EVENT) 265 int BPF_PROG(ack05_fix_events, struct hid_bpf_ctx *hctx) 266 { 267 __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PAD_REPORT_LENGTH); 268 int ret = 0; 269 270 if (!data) 271 return 0; /* EPERM check */ 272 273 if (data[0] != VENDOR_REPORT_ID) 274 return 0; 275 276 /* reconnect event */ 277 if (data[1] == 0xf8 && data[2] == 02 && data[3] == 0x01) 278 HID_BPF_ASYNC_DELAYED_CALL(switch_to_raw_mode, hctx, 10); 279 280 /* button event */ 281 if (data[1] == RAW_PAD_REPORT_ID) { 282 data[0] = data[1]; 283 if (data[7] == 0x02) 284 data[7] = 0xff; 285 ret = 8; 286 } else if (data[1] == RAW_BATTERY_REPORT_ID) { 287 data[0] = data[1]; 288 ret = 5; 289 } 290 291 return ret; 292 } 293 294 HID_BPF_OPS(xppen_ack05_remote) = { 295 .hid_device_event = (void *)ack05_fix_events, 296 .hid_rdesc_fixup = (void *)ack05_fix_rdesc, 297 }; 298 299 SEC("syscall") 300 int probe(struct hid_bpf_probe_args *ctx) 301 { 302 switch (ctx->rdesc_size) { 303 case PAD_WIRED_DESCRIPTOR_LENGTH: 304 case PAD_DONGLE_DESCRIPTOR_LENGTH: 305 case STYLUS_DESCRIPTOR_LENGTH: 306 case VENDOR_DESCRIPTOR_LENGTH: 307 ctx->retval = 0; 308 break; 309 default: 310 ctx->retval = -EINVAL; 311 break; 312 } 313 314 if (ctx->rdesc_size == VENDOR_DESCRIPTOR_LENGTH) { 315 struct hid_bpf_ctx *hctx = hid_bpf_allocate_context(ctx->hid); 316 317 if (!hctx) { 318 ctx->retval = -EINVAL; 319 return 0; 320 } 321 322 ctx->retval = HID_BPF_ASYNC_INIT(switch_to_raw_mode) || 323 switch_to_raw_mode(hctx); 324 325 hid_bpf_release_context(hctx); 326 } 327 328 return 0; 329 } 330 331 char _license[] SEC("license") = "GPL"; 332