137 lines
5.7 KiB
C
137 lines
5.7 KiB
C
|
// 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 paddles on the back of the device as a single
|
||
|
* bitfield value of usage "Assign Selection".
|
||
|
*
|
||
|
* The kernel doesn't process the paddles usage properly and reports KEY_UNKNOWN.
|
||
|
*
|
||
|
* SDL doesn't know how to interpret 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 interpret 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(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;
|
||
|
}
|
||
|
|
||
|
HID_BPF_OPS(xbox_elite_2) = {
|
||
|
.hid_rdesc_fixup = (void *)hid_fix_rdesc,
|
||
|
};
|
||
|
|
||
|
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";
|