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
SEC(HID_BPF_RDESC_FIXUP)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
HID_BPF_ASYNC_FUN(switch_to_raw_mode)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
SEC(HID_BPF_DEVICE_EVENT)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")
probe(struct hid_bpf_probe_args * ctx)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