summaryrefslogtreecommitdiffstats
path: root/drivers/hid/bpf/progs/Microsoft__XBox-Elite-2.bpf.c
blob: c04abecab8ee9bda4cd3e6048b6f3a291cc7f0bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2024 Benjamin Tissoires
 */

#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include <bpf/bpf_tracing.h>

#define VID_MICROSOFT 0x045e
#define PID_XBOX_ELITE_2 0x0b22

HID_BPF_CONFIG(
	HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_MICROSOFT, PID_XBOX_ELITE_2)
);

/*
 * When using the XBox Wireless Controller Elite 2 over Bluetooth,
 * the device exports the paddle on the back of the device as a single
 * bitfield value of usage "Assign Selection".
 *
 * The kernel doesn't process those usages properly and report KEY_UNKNOWN
 * for it.
 *
 * SDL doesn't know how to interprete that KEY_UNKNOWN and thus ignores the paddles.
 *
 * Given that over USB the kernel uses BTN_TRIGGER_HAPPY[5-8], we
 * can tweak the report descriptor to make the kernel interprete it properly:
 * - we need an application collection of gamepad (so we have to close the current
 *   Consumer Control one)
 * - we need to change the usage to be buttons from 0x15 to 0x18
 */

#define OFFSET_ASSIGN_SELECTION		211
#define ORIGINAL_RDESC_SIZE		464

const __u8 rdesc_assign_selection[] = {
	0x0a, 0x99, 0x00,              //   Usage (Media Select Security)     211
	0x15, 0x00,                    //   Logical Minimum (0)               214
	0x26, 0xff, 0x00,              //   Logical Maximum (255)             216
	0x95, 0x01,                    //   Report Count (1)                  219
	0x75, 0x04,                    //   Report Size (4)                   221
	0x81, 0x02,                    //   Input (Data,Var,Abs)              223
	0x15, 0x00,                    //   Logical Minimum (0)               225
	0x25, 0x00,                    //   Logical Maximum (0)               227
	0x95, 0x01,                    //   Report Count (1)                  229
	0x75, 0x04,                    //   Report Size (4)                   231
	0x81, 0x03,                    //   Input (Cnst,Var,Abs)              233
	0x0a, 0x81, 0x00,              //   Usage (Assign Selection)          235
	0x15, 0x00,                    //   Logical Minimum (0)               238
	0x26, 0xff, 0x00,              //   Logical Maximum (255)             240
	0x95, 0x01,                    //   Report Count (1)                  243
	0x75, 0x04,                    //   Report Size (4)                   245
	0x81, 0x02,                    //   Input (Data,Var,Abs)              247
};

/*
 * we replace the above report descriptor extract
 * with the one below.
 * To make things equal in size, we take out a larger
 * portion than just the "Assign Selection" range, because
 * we need to insert a new application collection to force
 * the kernel to use BTN_TRIGGER_HAPPY[4-7].
 */
const __u8 fixed_rdesc_assign_selection[] = {
	0x0a, 0x99, 0x00,              //   Usage (Media Select Security)     211
	0x15, 0x00,                    //   Logical Minimum (0)               214
	0x26, 0xff, 0x00,              //   Logical Maximum (255)             216
	0x95, 0x01,                    //   Report Count (1)                  219
	0x75, 0x04,                    //   Report Size (4)                   221
	0x81, 0x02,                    //   Input (Data,Var,Abs)              223
	/* 0x15, 0x00, */              //   Logical Minimum (0)               ignored
	0x25, 0x01,                    //   Logical Maximum (1)               225
	0x95, 0x04,                    //   Report Count (4)                  227
	0x75, 0x01,                    //   Report Size (1)                   229
	0x81, 0x03,                    //   Input (Cnst,Var,Abs)              231
	0xc0,                          //  End Collection                     233
	0x05, 0x01,                    //  Usage Page (Generic Desktop)       234
	0x0a, 0x05, 0x00,              //  Usage (Game Pad)                   236
	0xa1, 0x01,                    //  Collection (Application)           239
	0x05, 0x09,                    //   Usage Page (Button)               241
	0x19, 0x15,                    //   Usage Minimum (21)                243
	0x29, 0x18,                    //   Usage Maximum (24)                245
	/* 0x15, 0x00, */              //  Logical Minimum (0)                ignored
	/* 0x25, 0x01, */              //  Logical Maximum (1)                ignored
	/* 0x95, 0x01, */              //  Report Size (1)                    ignored
	/* 0x75, 0x04, */              //  Report Count (4)                   ignored
	0x81, 0x02,                    //   Input (Data,Var,Abs)              247
};

_Static_assert(sizeof(rdesc_assign_selection) == sizeof(fixed_rdesc_assign_selection),
	       "Rdesc and fixed rdesc of different size");
_Static_assert(sizeof(rdesc_assign_selection) + OFFSET_ASSIGN_SELECTION < ORIGINAL_RDESC_SIZE,
	       "Rdesc at given offset is too big");

SEC("fmod_ret/hid_bpf_rdesc_fixup")
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
{
	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);

	if (!data)
		return 0; /* EPERM check */

	/* Check that the device is compatible */
	if (__builtin_memcmp(data + OFFSET_ASSIGN_SELECTION,
			     rdesc_assign_selection,
			     sizeof(rdesc_assign_selection)))
		return 0;

	__builtin_memcpy(data + OFFSET_ASSIGN_SELECTION,
			 fixed_rdesc_assign_selection,
			 sizeof(fixed_rdesc_assign_selection));

	return 0;
}

SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
	/* only bind to the keyboard interface */
	ctx->retval = ctx->rdesc_size != ORIGINAL_RDESC_SIZE;
	if (ctx->retval)
		ctx->retval = -EINVAL;

	if (__builtin_memcmp(ctx->rdesc + OFFSET_ASSIGN_SELECTION,
			     rdesc_assign_selection,
			     sizeof(rdesc_assign_selection)))
		ctx->retval = -EINVAL;

	return 0;
}

char _license[] SEC("license") = "GPL";