xref: /linux/drivers/hid/bpf/progs/Microsoft__Xbox-Elite-2.bpf.c (revision a1ff5a7d78a036d6c2178ee5acd6ba4946243800)
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