137 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/* Copyright (c) 2024 Benjamin Tissoires3 */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 0x045e11#define PID_XBOX_ELITE_2 0x0b2212 13HID_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 single20 * 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], we27 * 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 current29 * Consumer Control one)30 * - We need to change the usage to be buttons from 0x15 to 0x1831 */32 33#define OFFSET_ASSIGN_SELECTION 21134#define ORIGINAL_RDESC_SIZE 46435 36const __u8 rdesc_assign_selection[] = {37 0x0a, 0x99, 0x00, // Usage (Media Select Security) 21138 0x15, 0x00, // Logical Minimum (0) 21439 0x26, 0xff, 0x00, // Logical Maximum (255) 21640 0x95, 0x01, // Report Count (1) 21941 0x75, 0x04, // Report Size (4) 22142 0x81, 0x02, // Input (Data,Var,Abs) 22343 0x15, 0x00, // Logical Minimum (0) 22544 0x25, 0x00, // Logical Maximum (0) 22745 0x95, 0x01, // Report Count (1) 22946 0x75, 0x04, // Report Size (4) 23147 0x81, 0x03, // Input (Cnst,Var,Abs) 23348 0x0a, 0x81, 0x00, // Usage (Assign Selection) 23549 0x15, 0x00, // Logical Minimum (0) 23850 0x26, 0xff, 0x00, // Logical Maximum (255) 24051 0x95, 0x01, // Report Count (1) 24352 0x75, 0x04, // Report Size (4) 24553 0x81, 0x02, // Input (Data,Var,Abs) 24754};55 56/*57 * we replace the above report descriptor extract58 * with the one below.59 * To make things equal in size, we take out a larger60 * portion than just the "Assign Selection" range, because61 * we need to insert a new application collection to force62 * the kernel to use BTN_TRIGGER_HAPPY[4-7].63 */64const __u8 fixed_rdesc_assign_selection[] = {65 0x0a, 0x99, 0x00, // Usage (Media Select Security) 21166 0x15, 0x00, // Logical Minimum (0) 21467 0x26, 0xff, 0x00, // Logical Maximum (255) 21668 0x95, 0x01, // Report Count (1) 21969 0x75, 0x04, // Report Size (4) 22170 0x81, 0x02, // Input (Data,Var,Abs) 22371 /* 0x15, 0x00, */ // Logical Minimum (0) ignored72 0x25, 0x01, // Logical Maximum (1) 22573 0x95, 0x04, // Report Count (4) 22774 0x75, 0x01, // Report Size (1) 22975 0x81, 0x03, // Input (Cnst,Var,Abs) 23176 0xc0, // End Collection 23377 0x05, 0x01, // Usage Page (Generic Desktop) 23478 0x0a, 0x05, 0x00, // Usage (Game Pad) 23679 0xa1, 0x01, // Collection (Application) 23980 0x05, 0x09, // Usage Page (Button) 24181 0x19, 0x15, // Usage Minimum (21) 24382 0x29, 0x18, // Usage Maximum (24) 24583 /* 0x15, 0x00, */ // Logical Minimum (0) ignored84 /* 0x25, 0x01, */ // Logical Maximum (1) ignored85 /* 0x95, 0x01, */ // Report Size (1) ignored86 /* 0x75, 0x04, */ // Report Count (4) ignored87 0x81, 0x02, // Input (Data,Var,Abs) 24788};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 95SEC(HID_BPF_RDESC_FIXUP)96int 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 116HID_BPF_OPS(xbox_elite_2) = {117 .hid_rdesc_fixup = (void *)hid_fix_rdesc,118};119 120SEC("syscall")121int 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 136char _license[] SEC("license") = "GPL";137