1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Copyright (c) 2024 Benjamin Tissoires
3 */
4
5 #include "vmlinux.h"
6 #include "hid_bpf.h"
7 #include "hid_bpf_helpers.h"
8 #include <bpf/bpf_tracing.h>
9
10 #define VID_MICROSOFT 0x045e
11 #define PID_XBOX_ELITE_2 0x0b22
12
13 HID_BPF_CONFIG(
14 HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_MICROSOFT, PID_XBOX_ELITE_2)
15 );
16
17 /*
18 * When using the Xbox Wireless Controller Elite 2 over Bluetooth,
19 * the device exports the paddles on the back of the device as a single
20 * bitfield value of usage "Assign Selection".
21 *
22 * The kernel doesn't process the paddles usage properly and reports KEY_UNKNOWN.
23 *
24 * SDL doesn't know how to interpret KEY_UNKNOWN and thus ignores the paddles.
25 *
26 * Given that over USB the kernel uses BTN_TRIGGER_HAPPY[5-8], we
27 * can tweak the report descriptor to make the kernel interpret it properly:
28 * - We need an application collection of gamepad (so we have to close the current
29 * Consumer Control one)
30 * - We need to change the usage to be buttons from 0x15 to 0x18
31 */
32
33 #define OFFSET_ASSIGN_SELECTION 211
34 #define ORIGINAL_RDESC_SIZE 464
35
36 const __u8 rdesc_assign_selection[] = {
37 0x0a, 0x99, 0x00, // Usage (Media Select Security) 211
38 0x15, 0x00, // Logical Minimum (0) 214
39 0x26, 0xff, 0x00, // Logical Maximum (255) 216
40 0x95, 0x01, // Report Count (1) 219
41 0x75, 0x04, // Report Size (4) 221
42 0x81, 0x02, // Input (Data,Var,Abs) 223
43 0x15, 0x00, // Logical Minimum (0) 225
44 0x25, 0x00, // Logical Maximum (0) 227
45 0x95, 0x01, // Report Count (1) 229
46 0x75, 0x04, // Report Size (4) 231
47 0x81, 0x03, // Input (Cnst,Var,Abs) 233
48 0x0a, 0x81, 0x00, // Usage (Assign Selection) 235
49 0x15, 0x00, // Logical Minimum (0) 238
50 0x26, 0xff, 0x00, // Logical Maximum (255) 240
51 0x95, 0x01, // Report Count (1) 243
52 0x75, 0x04, // Report Size (4) 245
53 0x81, 0x02, // Input (Data,Var,Abs) 247
54 };
55
56 /*
57 * we replace the above report descriptor extract
58 * with the one below.
59 * To make things equal in size, we take out a larger
60 * portion than just the "Assign Selection" range, because
61 * we need to insert a new application collection to force
62 * the kernel to use BTN_TRIGGER_HAPPY[4-7].
63 */
64 const __u8 fixed_rdesc_assign_selection[] = {
65 0x0a, 0x99, 0x00, // Usage (Media Select Security) 211
66 0x15, 0x00, // Logical Minimum (0) 214
67 0x26, 0xff, 0x00, // Logical Maximum (255) 216
68 0x95, 0x01, // Report Count (1) 219
69 0x75, 0x04, // Report Size (4) 221
70 0x81, 0x02, // Input (Data,Var,Abs) 223
71 /* 0x15, 0x00, */ // Logical Minimum (0) ignored
72 0x25, 0x01, // Logical Maximum (1) 225
73 0x95, 0x04, // Report Count (4) 227
74 0x75, 0x01, // Report Size (1) 229
75 0x81, 0x03, // Input (Cnst,Var,Abs) 231
76 0xc0, // End Collection 233
77 0x05, 0x01, // Usage Page (Generic Desktop) 234
78 0x0a, 0x05, 0x00, // Usage (Game Pad) 236
79 0xa1, 0x01, // Collection (Application) 239
80 0x05, 0x09, // Usage Page (Button) 241
81 0x19, 0x15, // Usage Minimum (21) 243
82 0x29, 0x18, // Usage Maximum (24) 245
83 /* 0x15, 0x00, */ // Logical Minimum (0) ignored
84 /* 0x25, 0x01, */ // Logical Maximum (1) ignored
85 /* 0x95, 0x01, */ // Report Size (1) ignored
86 /* 0x75, 0x04, */ // Report Count (4) ignored
87 0x81, 0x02, // Input (Data,Var,Abs) 247
88 };
89
90 _Static_assert(sizeof(rdesc_assign_selection) == sizeof(fixed_rdesc_assign_selection),
91 "Rdesc and fixed rdesc of different size");
92 _Static_assert(sizeof(rdesc_assign_selection) + OFFSET_ASSIGN_SELECTION < ORIGINAL_RDESC_SIZE,
93 "Rdesc at given offset is too big");
94
SEC(HID_BPF_RDESC_FIXUP)95 SEC(HID_BPF_RDESC_FIXUP)
96 int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
97 {
98 __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
99
100 if (!data)
101 return 0; /* EPERM check */
102
103 /* Check that the device is compatible */
104 if (__builtin_memcmp(data + OFFSET_ASSIGN_SELECTION,
105 rdesc_assign_selection,
106 sizeof(rdesc_assign_selection)))
107 return 0;
108
109 __builtin_memcpy(data + OFFSET_ASSIGN_SELECTION,
110 fixed_rdesc_assign_selection,
111 sizeof(fixed_rdesc_assign_selection));
112
113 return 0;
114 }
115
116 HID_BPF_OPS(xbox_elite_2) = {
117 .hid_rdesc_fixup = (void *)hid_fix_rdesc,
118 };
119
120 SEC("syscall")
probe(struct hid_bpf_probe_args * ctx)121 int probe(struct hid_bpf_probe_args *ctx)
122 {
123 /* only bind to the keyboard interface */
124 ctx->retval = ctx->rdesc_size != ORIGINAL_RDESC_SIZE;
125 if (ctx->retval)
126 ctx->retval = -EINVAL;
127
128 if (__builtin_memcmp(ctx->rdesc + OFFSET_ASSIGN_SELECTION,
129 rdesc_assign_selection,
130 sizeof(rdesc_assign_selection)))
131 ctx->retval = -EINVAL;
132
133 return 0;
134 }
135
136 char _license[] SEC("license") = "GPL";
137