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(hid_id: 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(hid: hctx); |
324 | |
325 | hid_bpf_release_context(ctx: hctx); |
326 | } |
327 | |
328 | return 0; |
329 | } |
330 | |
331 | char _license[] SEC("license" ) = "GPL" ; |
332 | |