Merge branch 'for-7.1/hid-bpf' into for-linus

- sync of HID-BPF with udev-hid-bpf (Benjamin Tissoires, Muhammed Rishal)
This commit is contained in:
Jiri Kosina
2026-04-16 21:09:41 +02:00
11 changed files with 3865 additions and 22 deletions

View File

@@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2025 Benjamin Tissoires
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include "hid_report_helpers.h"
#include "hid_usages.h"
#include <bpf/bpf_tracing.h>
HID_BPF_CONFIG(
HID_DEVICE(BUS_ANY, HID_GROUP_MULTITOUCH_WIN_8, HID_VID_ANY, HID_PID_ANY),
);
EXPORT_UDEV_PROP(HID_DIGITIZER_PAD_TYPE, 32);
__u8 hw_req_buf[1024];
/* to be filled by udev-hid-bpf */
struct hid_rdesc_descriptor HID_REPORT_DESCRIPTOR;
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
struct hid_rdesc_report *pad_type_feature = NULL;
struct hid_rdesc_field *pad_type = NULL;
struct hid_rdesc_report *feature;
struct hid_bpf_ctx *hid_ctx;
char *pad_type_str = "";
int ret;
hid_bpf_for_each_feature_report(&HID_REPORT_DESCRIPTOR, feature) {
struct hid_rdesc_field *field;
hid_bpf_for_each_field(feature, field) {
if (field->usage_page == HidUsagePage_Digitizers &&
field->usage_id == HidUsage_Dig_PadType) {
pad_type = field;
pad_type_feature = feature;
break;
}
}
if (pad_type)
break;
}
if (!pad_type || !pad_type_feature) {
ctx->retval = -EINVAL;
return 0;
}
hid_ctx = hid_bpf_allocate_context(ctx->hid);
if (!hid_ctx)
return -1; /* EPERM check */
hw_req_buf[0] = pad_type_feature->report_id;
ret = hid_bpf_hw_request(hid_ctx, hw_req_buf, sizeof(hw_req_buf),
HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
hid_bpf_release_context(hid_ctx);
if (ret < 0) {
ctx->retval = ret;
return 0;
}
ctx->retval = 0;
switch (EXTRACT_BITS(hw_req_buf, pad_type)) {
case 0:
pad_type_str = "Clickpad";
break;
case 1:
pad_type_str = "Pressurepad";
break;
case 2:
pad_type_str = "Discrete";
break;
default:
pad_type_str = "Unknown";
}
UDEV_PROP_SPRINTF(HID_DIGITIZER_PAD_TYPE, "%s", pad_type_str);
return 0;
}
char _license[] SEC("license") = "GPL";

View File

@@ -0,0 +1,492 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2024 Red Hat, Inc
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include "hid_report_helpers.h"
#include <bpf/bpf_tracing.h>
#define VID_HUION 0x256C
#define PID_KEYDIAL_K20_BLUETOOTH 0x8251
HID_BPF_CONFIG(
HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_HUION, PID_KEYDIAL_K20_BLUETOOTH),
);
/* This is the same device as in 0010-Huion__KeydialK20 but connected via Bluetooth.
* It does not need (to support?) switching to a vendor mode so we just modify the
* existing mode.
*
* By default it exports two hidraw nodes, only the second one sends events.
*
* This is the first hidraw node which we disable:
*
* # Keydial mini-050
* # Report descriptor length: 114 bytes
* # Bytes // Field Name Offset
* # ----------------------------------------------------------------------------------
* # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 0
* # 🭬 0x09, 0x0e, // Usage (System Multi-Axis Controller) 2
* # 0xa1, 0x01, // Collection (Application) 4
* # 0x85, 0x03, // Report ID (3) 6
* # 🮥 0x05, 0x0d, // Usage Page (Digitizers) 8
* # 0x75, 0x08, // Report Size (8) 10
* # 0x95, 0x01, // Report Count (1) 12
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 14
* # 🭬 0x09, 0x21, // Usage (Puck) 16
* # 0xa1, 0x02, // Collection (Logical) 18
* # 0x15, 0x00, // Logical Minimum (0) 20
* # 0x25, 0x01, // Logical Maximum (1) 22
* # 0x75, 0x01, // Report Size (1) 24
* # 0x95, 0x01, // Report Count (1) 26
* # 0xa1, 0x00, // Collection (Physical) 28
* # 🮥 0x05, 0x09, // Usage Page (Button) 30
* # 🭬 0x09, 0x01, // Usage (Button 1) 32
* # 0x81, 0x02, // Input (Data,Var,Abs) 34
* # 🮥 0x05, 0x0d, // Usage Page (Digitizers) 36
* # 🭬 0x09, 0x33, // Usage (Touch) 38
* # 0x81, 0x02, // Input (Data,Var,Abs) 40
* # 0x95, 0x06, // Report Count (6) 42
* # 0x81, 0x03, // Input (Cnst,Var,Abs) 44
* # 0xa1, 0x02, // Collection (Logical) 46
* # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 48
* # 🭬 0x09, 0x37, // Usage (Dial) 50
* # 0x16, 0x00, 0x80, // Logical Minimum (32768) 52
* # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 55
* # 0x75, 0x10, // Report Size (16) 58
* # 0x95, 0x01, // Report Count (1) 60
* # 0x81, 0x06, // Input (Data,Var,Rel) 62
* # 0x35, 0x00, // Physical Minimum (0) 64
* # 0x46, 0x10, 0x0e, // Physical Maximum (3600) 66
* # 0x15, 0x00, // Logical Minimum (0) 69
* # 0x26, 0x10, 0x0e, // Logical Maximum (3600) 71
* # 🭬 0x09, 0x48, // Usage (Resolution Multiplier) 74
* # 0xb1, 0x02, // Feature (Data,Var,Abs) 76
* # 0x45, 0x00, // Physical Maximum (0) 78
* # 0xc0, // End Collection 80
* # 0x75, 0x08, // Report Size (8) 81
* # 0x95, 0x01, // Report Count (1) 83
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 85
* # 0x75, 0x08, // Report Size (8) 87
* # 0x95, 0x01, // Report Count (1) 89
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 91
* # 0x75, 0x08, // Report Size (8) 93
* # 0x95, 0x01, // Report Count (1) 95
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 97
* # 0x75, 0x08, // Report Size (8) 99
* # 0x95, 0x01, // Report Count (1) 101
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 103
* # 0x75, 0x08, // Report Size (8) 105
* # 0x95, 0x01, // Report Count (1) 107
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 109
* # 0xc0, // End Collection 111
* # 0xc0, // End Collection 112
* # 0xc0, // End Collection 113
* R: 114 05 01 09 0e a1 01 85 03 05 0d 75 08 95 01 81 01 09 21 a1 02 15 00 25 01 75 01 95 01 a1 00 05 09 09 01 81 02 05 0d 09 33 81 02 95 06 81 03 a1 02 05 01 09 37 16 00 80 26 ff 7f 75 10 95 01 81 06 35 00 46 10 0e 15 00 26 10 0e 09 48 b1 02 45 00 c0 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 c0 c0 c0
* N: Keydial mini-050
* I: 5 256c 8251
*
* The second hidraw node is what sends events:
*
* # Keydial mini-050
* # Report descriptor length: 160 bytes
* # Bytes // Field Name Offset
* # ----------------------------------------------------------------------------------
* # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 0
* # 🭬 0x09, 0x06, // Usage (Keyboard) 2
* # 0xa1, 0x01, // Collection (Application) 4
* # 0x85, 0x01, // Report ID (1) 6
* # 🮥 0x05, 0x07, // Usage Page (Keyboard/Keypad) 8
* # 🭬 0x19, 0xe0, // Usage Minimum (224) 10
* # 🭬 0x29, 0xe7, // Usage Maximum (231) 12
* # 0x15, 0x00, // Logical Minimum (0) 14
* # 0x25, 0x01, // Logical Maximum (1) 16
* # 0x75, 0x01, // Report Size (1) 18
* # 0x95, 0x08, // Report Count (8) 20
* # 0x81, 0x02, // Input (Data,Var,Abs) 22
* # 0x95, 0x01, // Report Count (1) 24
* # 0x75, 0x08, // Report Size (8) 26
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 28
* # 0x95, 0x05, // Report Count (5) 30
* # 0x75, 0x01, // Report Size (1) 32
* # 🮥 0x05, 0x08, // Usage Page (LED) 34
* # 🭬 0x19, 0x01, // Usage Minimum (1) 36
* # 🭬 0x29, 0x05, // Usage Maximum (5) 38
* # 0x91, 0x02, // Output (Data,Var,Abs) 40
* # 0x95, 0x01, // Report Count (1) 42
* # 0x75, 0x03, // Report Size (3) 44
* # 0x91, 0x01, // Output (Cnst,Arr,Abs) 46
* # 0x95, 0x06, // Report Count (6) 48
* # 0x75, 0x08, // Report Size (8) 50
* # 0x15, 0x00, // Logical Minimum (0) 52
* # 0x25, 0xf1, // Logical Maximum (241) 54
* # 🮥 0x05, 0x07, // Usage Page (Keyboard/Keypad) 56
* # 🭬 0x19, 0x00, // Usage Minimum (0) 58
* # 🭬 0x29, 0xf1, // Usage Maximum (241) 60
* # 0x81, 0x00, // Input (Data,Arr,Abs) 62
* # 0xc0, // End Collection 64
* # 🮥 0x05, 0x0c, // Usage Page (Consumer) 65
* # 🭬 0x09, 0x01, // Usage (Consumer Control) 67
* # 0xa1, 0x01, // Collection (Application) 69
* # 0x85, 0x02, // Report ID (2) 71
* # 🮥 0x05, 0x0c, // Usage Page (Consumer) 73
* # 🭬 0x19, 0x00, // Usage Minimum (0) 75
* # 🭬 0x2a, 0x80, 0x03, // Usage Maximum (896) 77
* # 0x15, 0x00, // Logical Minimum (0) 80
* # 0x26, 0x80, 0x03, // Logical Maximum (896) 82
* # 0x75, 0x10, // Report Size (16) 85
* # 0x95, 0x01, // Report Count (1) 87
* # 0x81, 0x00, // Input (Data,Arr,Abs) 89
* # 0xc0, // End Collection 91
* # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 92
* # 🭬 0x09, 0x02, // Usage (Mouse) 94
* # 0xa1, 0x01, // Collection (Application) 96
* # 🭬 0x09, 0x01, // Usage (Pointer) 98
* # 0x85, 0x05, // Report ID (5) 100
* # 0xa1, 0x00, // Collection (Physical) 102
* # 🮥 0x05, 0x09, // Usage Page (Button) 104
* # 🭬 0x19, 0x01, // Usage Minimum (1) 106
* # 🭬 0x29, 0x05, // Usage Maximum (5) 108
* # 0x15, 0x00, // Logical Minimum (0) 110
* # 0x25, 0x01, // Logical Maximum (1) 112
* # 0x95, 0x05, // Report Count (5) 114
* # 0x75, 0x01, // Report Size (1) 116
* # 0x81, 0x02, // Input (Data,Var,Abs) 118
* # 0x95, 0x01, // Report Count (1) 120
* # 0x75, 0x03, // Report Size (3) 122
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 124
* # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 126
* # 🭬 0x09, 0x30, // Usage (X) 128
* # 🭬 0x09, 0x31, // Usage (Y) 130
* # 0x16, 0x01, 0x80, // Logical Minimum (32769) 132
* # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 135
* # 0x75, 0x10, // Report Size (16) 138
* # 0x95, 0x02, // Report Count (2) 140
* # 0x81, 0x06, // Input (Data,Var,Rel) 142
* # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 144
* # 🭬 0x09, 0x38, // Usage (Wheel) 146
* # 0x15, 0x81, // Logical Minimum (129) 148
* # 0x25, 0x7f, // Logical Maximum (127) 150
* # 0x95, 0x01, // Report Count (1) 152
* # 0x75, 0x08, // Report Size (8) 154
* # 0x81, 0x06, // Input (Data,Var,Rel) 156
* # 0xc0, // End Collection 158
* # 0xc0, // End Collection 159
* R: 160 05 01 09 06 a1 01 85 01 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 95 01 75 08 81 01 95 05 75 01 05 08 19 01 29 05 91 02 95 01 75 03 91 01 95 06 75 08 15 00 25 f1 05 07 19 00 29 f1 81 00 c0 05 0c 09 01 a1 01 85 02 05 0c 19 00 2a 80 03 15 00 26 80 03 75 10 95 01 81 00 c0 05 01 09 02 a1 01 09 01 85 05 a1 00 05 09 19 01 29 05 15 00 25 01 95 05 75 01 81 02 95 01 75 03 81 01 05 01 09 30 09 31 16 01 80 26 ff 7f 75 10 95 02 81 06 05 01 09 38 15 81 25 7f 95 01 75 08 81 06 c0 c0
* N: Keydial mini-050
* I: 5 256c 8251
* # Report descriptor:
* # ------- Input Report -------
* # Report ID: 1
* # | Report size: 72 bits
* # Bit: 8 Usage: 0007/00e0: Keyboard/Keypad / Keyboard LeftControl Logical Range: 0..=1
* # Bit: 9 Usage: 0007/00e1: Keyboard/Keypad / Keyboard LeftShift Logical Range: 0..=1
* # Bit: 10 Usage: 0007/00e2: Keyboard/Keypad / Keyboard LeftAlt Logical Range: 0..=1
* # Bit: 11 Usage: 0007/00e3: Keyboard/Keypad / Keyboard Left GUI Logical Range: 0..=1
* # Bit: 12 Usage: 0007/00e4: Keyboard/Keypad / Keyboard RightControl Logical Range: 0..=1
* # Bit: 13 Usage: 0007/00e5: Keyboard/Keypad / Keyboard RightShift Logical Range: 0..=1
* # Bit: 14 Usage: 0007/00e6: Keyboard/Keypad / Keyboard RightAlt Logical Range: 0..=1
* # Bit: 15 Usage: 0007/00e7: Keyboard/Keypad / Keyboard Right GUI Logical Range: 0..=1
* # Bits: 16..=23 ######### Padding
* # Bits: 24..=71 Usages: Logical Range: 0..=241
* # 0007/0000: <unknown>
* # 0007/0001: Keyboard/Keypad / ErrorRollOver
* # 0007/0002: Keyboard/Keypad / POSTFail
* # 0007/0003: Keyboard/Keypad / ErrorUndefined
* # 0007/0004: Keyboard/Keypad / Keyboard A
* # ... use --full to see all usages
* # ------- Input Report -------
* # Report ID: 2
* # | Report size: 24 bits
* # Bits: 8..=23 Usages: Logical Range: 0..=896
* # 000c/0000: <unknown>
* # 000c/0001: Consumer / Consumer Control
* # 000c/0002: Consumer / Numeric Key Pad
* # 000c/0003: Consumer / Programmable Buttons
* # 000c/0004: Consumer / Microphone
* # ... use --full to see all usages
* # ------- Input Report -------
* # Report ID: 5
* # | Report size: 56 bits
* # Bit: 8 Usage: 0009/0001: Button / Button 1 Logical Range: 0..=1
* # Bit: 9 Usage: 0009/0002: Button / Button 2 Logical Range: 0..=1
* # Bit: 10 Usage: 0009/0003: Button / Button 3 Logical Range: 0..=1
* # Bit: 11 Usage: 0009/0004: Button / Button 4 Logical Range: 0..=1
* # Bit: 12 Usage: 0009/0005: Button / Button 5 Logical Range: 0..=1
* # Bits: 13..=15 ######### Padding
* # Bits: 16..=31 Usage: 0001/0030: Generic Desktop / X Logical Range: 32769..=32767
* # Bits: 32..=47 Usage: 0001/0031: Generic Desktop / Y Logical Range: 32769..=32767
* # Bits: 48..=55 Usage: 0001/0038: Generic Desktop / Wheel Logical Range: 129..=127
* # ------- Output Report -------
* # Report ID: 1
* # | Report size: 16 bits
* # Bit: 8 Usage: 0008/0001: LED / Num Lock Logical Range: 0..=1
* # Bit: 9 Usage: 0008/0002: LED / Caps Lock Logical Range: 0..=1
* # Bit: 10 Usage: 0008/0003: LED / Scroll Lock Logical Range: 0..=1
* # Bit: 11 Usage: 0008/0004: LED / Compose Logical Range: 0..=1
* # Bit: 12 Usage: 0008/0005: LED / Kana Logical Range: 0..=1
* # Bits: 13..=15 ######### Padding
* ##############################################################################
* # Event nodes:
* # - /dev/input/event12: "Keydial mini-050 Keyboard"
* # - /dev/input/event14: "Keydial mini-050 Mouse"
* ##############################################################################
* # Recorded events below in format:
* # E: <seconds>.<microseconds> <length-in-bytes> [bytes ...]
* #
*
* - Report ID 1 sends keyboard shortcuts when pressing the buttons, e.g.
*
* # Report ID: 1 /
* # Keyboard LeftControl: 0 |Keyboard LeftShift: 0 |Keyboard LeftAlt: 0 |Keyboard Left GUI: 0 |Keyboard RightControl: 0 |Keyboard RightShift: 0 |Keyboard RightAlt: 0 |Keyboard Right GUI: 0 |<8 bits padding> |0007/0000: 0| Keyboard K: 14| 0007/0000: 0| 0007/0000: 0| 0007/0000: 0| 0007/0000: 0
* E: 000000.000292 9 01 00 00 00 0e 00 00 00 00
*
* - Report ID 2 sends the button inside the wheel/dial thing
* # Report ID: 2 /
* # Play/Pause: 205
* E: 000134.347845 3 02 cd 00
* # Report ID: 2 /
* # 000c/0000: 0
* E: 000134.444965 3 02 00 00
*
* - Report ID 5 sends the wheel relative events (always a double-event with the second as zero)
* # Report ID: 5 /
* # Button 1: 0 |Button 2: 0 |Button 3: 0 |Button 4: 0 |Button 5: 0 |<3 bits padding> |X: 0 |Y: 0 |Wheel: 255
* E: 000064.859915 7 05 00 00 00 00 00 ff
* # Report ID: 5 /
* # Button 1: 0 |Button 2: 0 |Button 3: 0 |Button 4: 0 |Button 5: 0 |<3 bits padding> |X: 0 |Y: 0 |Wheel: 0
* E: 000064.882009 7 05 00 00 00 00 00 00
*/
#define BT_PAD_REPORT_DESCRIPTOR_LENGTH 160
#define BT_PUCK_REPORT_DESCRIPTOR_LENGTH 114 // This one doesn't send events
#define BT_PAD_KBD_REPORT_ID 1
#define BT_PAD_CC_REPORT_ID 2
#define BT_PAD_MOUSE_REPORT_ID 5
#define BT_PAD_KBD_REPORT_LENGTH 9
#define BT_PAD_CC_REPORT_LENGTH 3
#define BT_PAD_MOUSE_REPORT_LENGTH 7
#define OUR_REPORT_ID 11 /* "randomly" picked report ID for our reports */
__u32 last_button_state = 0;
static const __u8 disabled_rdesc_puck[] = {
FixedSizeVendorReport(BT_PUCK_REPORT_DESCRIPTOR_LENGTH)
};
static const __u8 fixed_rdesc_pad[] = {
UsagePage_GenericDesktop
Usage_GD_Keypad
CollectionApplication(
// Byte 0
ReportId(OUR_REPORT_ID)
UsagePage_Digitizers
Usage_Dig_TabletFunctionKeys
CollectionPhysical(
// Byte 1 is a button so we look like a tablet
Usage_Dig_BarrelSwitch // BTN_STYLUS, needed so we get to be a tablet pad
ReportCount(1)
ReportSize(1)
Input(Var|Abs)
ReportCount(7) // Padding
Input(Const)
// Bytes 2/3 - x/y just exist so we get to be a tablet pad
UsagePage_GenericDesktop
Usage_GD_X
Usage_GD_Y
LogicalMinimum_i8(0x0)
LogicalMaximum_i8(0x1)
ReportCount(2)
ReportSize(8)
Input(Var|Abs)
// Bytes 4-7 are the button state for 19 buttons + pad out to u32
// We send the first 10 buttons as buttons 1-10 which is BTN_0 -> BTN_9
UsagePage_Button
UsageMinimum_i8(1)
UsageMaximum_i8(10)
LogicalMinimum_i8(0x0)
LogicalMaximum_i8(0x1)
ReportCount(10)
ReportSize(1)
Input(Var|Abs)
// We send the other 9 buttons as buttons 0x31 and above -> BTN_A - BTN_TL2
UsageMinimum_i8(0x31)
UsageMaximum_i8(0x3a)
ReportCount(9)
ReportSize(1)
Input(Var|Abs)
ReportCount(13)
ReportSize(1)
Input(Const) // padding
// Byte 8 is the wheel
UsagePage_GenericDesktop
Usage_GD_Wheel
LogicalMinimum_i8(-1)
LogicalMaximum_i8(1)
ReportCount(1)
ReportSize(8)
Input(Var|Rel)
)
// Make sure we match our original report length
FixedSizeVendorReport(BT_PAD_KBD_REPORT_LENGTH)
)
};
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(k20_bt_fix_rdesc, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
__s32 rdesc_size = hctx->size;
if (!data)
return 0; /* EPERM check */
if (rdesc_size == BT_PAD_REPORT_DESCRIPTOR_LENGTH) {
__builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
return sizeof(fixed_rdesc_pad);
}
if (rdesc_size == BT_PUCK_REPORT_DESCRIPTOR_LENGTH) {
// This hidraw node doesn't send anything and can be ignored
__builtin_memcpy(data, disabled_rdesc_puck, sizeof(disabled_rdesc_puck));
return sizeof(disabled_rdesc_puck);
}
return 0;
}
SEC(HID_BPF_DEVICE_EVENT)
int BPF_PROG(k20_bt_fix_events, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 12 /* size */);
struct pad_report {
__u8 report_id;
__u8 btn_stylus:1;
__u8 pad:7;
__u8 x;
__u8 y;
__u32 buttons;
__u8 wheel;
} __packed * pad_report = (struct pad_report *)data;
if (!data)
return 0; /* EPERM check */
/* Report ID 1 - Keyboard events (button presses) */
if (data[0] == BT_PAD_KBD_REPORT_ID) {
const __u8 button_mapping[] = {
0x0e, /* Button 1: K */
0x0a, /* Button 2: G */
0x0f, /* Button 3: L */
0x4c, /* Button 4: Delete */
0x0c, /* Button 5: I */
0x07, /* Button 6: D */
0x05, /* Button 7: B */
0x08, /* Button 8: E */
0x16, /* Button 9: S */
0x1d, /* Button 10: Z */
0x06, /* Button 11: C */
0x19, /* Button 12: V */
0xff, /* Button 13: LeftControl */
0xff, /* Button 14: LeftAlt */
0xff, /* Button 15: LeftShift */
0x28, /* Button 16: Return Enter */
0x2c, /* Button 17: Spacebar */
0x11, /* Button 18: N */
};
__u8 modifiers = data[1];
__u32 buttons = 0;
if (modifiers & 0x01) { /* Control */
buttons |= BIT(12);
}
if (modifiers & 0x02) { /* Shift */
buttons |= BIT(14);
}
if (modifiers & 0x04) { /* Alt */
buttons |= BIT(13);
}
for (int i = 4; i < BT_PAD_KBD_REPORT_LENGTH; i++) {
if (!data[i])
break;
for (size_t b = 0; b < ARRAY_SIZE(button_mapping); b++) {
if (data[i] != 0xff && data[i] == button_mapping[b]) {
buttons |= BIT(b);
break;
}
}
}
last_button_state = buttons;
pad_report->report_id = OUR_REPORT_ID;
pad_report->btn_stylus = 0;
pad_report->x = 0;
pad_report->y = 0;
pad_report->buttons = buttons;
pad_report->wheel = 0;
return sizeof(struct pad_report);
}
/* Report ID 2 - Consumer control events (the button inside the wheel) */
if (data[0] == BT_PAD_CC_REPORT_ID) {
const __u8 PlayPause = 0xcd;
if (data[1] == PlayPause)
last_button_state |= BIT(18);
else
last_button_state &= ~BIT(18);
pad_report->report_id = OUR_REPORT_ID;
pad_report->btn_stylus = 0;
pad_report->x = 0;
pad_report->y = 0;
pad_report->buttons = last_button_state;
pad_report->wheel = 0;
return sizeof(struct pad_report);
}
/* Report ID 5 - Mouse events (wheel rotation) */
if (data[0] == BT_PAD_MOUSE_REPORT_ID) {
__u8 wheel_delta = data[6];
pad_report->report_id = OUR_REPORT_ID;
pad_report->btn_stylus = 0;
pad_report->x = 0;
pad_report->y = 0;
pad_report->buttons = last_button_state;
pad_report->wheel = wheel_delta;
return sizeof(struct pad_report);
}
return 0;
}
HID_BPF_OPS(keydial_k20_bluetooth) = {
.hid_device_event = (void *)k20_bt_fix_events,
.hid_rdesc_fixup = (void *)k20_bt_fix_rdesc,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
switch (ctx->rdesc_size) {
case BT_PAD_REPORT_DESCRIPTOR_LENGTH:
case BT_PUCK_REPORT_DESCRIPTOR_LENGTH:
ctx->retval = 0;
break;
default:
ctx->retval = -EINVAL;
}
return 0;
}
char _license[] SEC("license") = "GPL";

View File

@@ -462,7 +462,8 @@ int BPF_PROG(k20_fix_events, struct hid_bpf_ctx *hctx)
__u32 buttons;
__u8 wheel;
} __attribute__((packed)) *pad_report;
int i, b;
int i;
size_t b;
__u8 modifiers = data[1];
__u32 buttons = 0;

View File

@@ -34,7 +34,7 @@ int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
if (data[3] != 0x06)
return 0;
for (int idx = 0; idx < ARRAY_SIZE(offsets); idx++) {
for (size_t idx = 0; idx < ARRAY_SIZE(offsets); idx++) {
u8 offset = offsets[idx];
/* if Input (Cnst,Var,Abs) , make it Input (Data,Var,Abs) */

View File

@@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Fix for Trust Philips SPK6327 (145f:024b)
* Modifier keys report as Array (0x00) instead of Variable (0x02)
* causing LCtrl, LAlt, Super etc. to all act as LShift
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include <bpf/bpf_tracing.h>
#define VID_TRUST 0x145F
#define PID_SPK6327 0x024B
HID_BPF_CONFIG(
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_TRUST, PID_SPK6327)
);
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0, 4096);
if (!data)
return 0;
/* Fix modifier keys: Input Array (0x00) -> Input Variable (0x02) */
if (data[101] == 0x00)
data[101] = 0x02;
return 0;
}
HID_BPF_OPS(trust_spk6327) = {
.hid_rdesc_fixup = (void *)hid_fix_rdesc,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
/* Only apply to interface 1 (169 bytes) not interface 0 (62 bytes) */
if (ctx->rdesc_size == 169)
ctx->retval = 0;
else
ctx->retval = -EINVAL;
return 0;
}
char _license[] SEC("license") = "GPL";

View File

@@ -148,7 +148,7 @@ int probe(struct hid_bpf_probe_args *ctx)
{
struct hid_bpf_ctx *hid_ctx;
__u16 pid;
int i;
size_t i;
/* get a struct hid_device to access the actual pid of the device */
hid_ctx = hid_bpf_allocate_context(ctx->hid);

View File

@@ -173,7 +173,7 @@ int BPF_PROG(hid_device_event_xppen_deco_mini_4, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 8 /* size */);
__u8 button_mask = 0;
int d, b;
size_t d, b;
if (!data)
return 0; /* EPERM check */

View File

@@ -116,15 +116,14 @@ static int hid_bpf_async_find_empty_key(void)
if (!elem)
return -ENOMEM; /* should never happen */
bpf_spin_lock(&elem->lock);
{
guard(bpf_spin)(&elem->lock);
if (elem->state == HID_BPF_ASYNC_STATE_UNSET) {
elem->state = HID_BPF_ASYNC_STATE_INITIALIZING;
bpf_spin_unlock(&elem->lock);
return i;
if (elem->state == HID_BPF_ASYNC_STATE_UNSET) {
elem->state = HID_BPF_ASYNC_STATE_INITIALIZING;
return i;
}
}
bpf_spin_unlock(&elem->lock);
}
return -EINVAL;
@@ -175,18 +174,19 @@ static int hid_bpf_async_delayed_call(struct hid_bpf_ctx *hctx, u64 milliseconds
if (!elem)
return -EINVAL;
bpf_spin_lock(&elem->lock);
/* The wq must be:
* - HID_BPF_ASYNC_STATE_INITIALIZED -> it's been initialized and ready to be called
* - HID_BPF_ASYNC_STATE_RUNNING -> possible re-entry from the wq itself
*/
if (elem->state != HID_BPF_ASYNC_STATE_INITIALIZED &&
elem->state != HID_BPF_ASYNC_STATE_RUNNING) {
bpf_spin_unlock(&elem->lock);
return -EINVAL;
{
guard(bpf_spin)(&elem->lock);
/* The wq must be:
* - HID_BPF_ASYNC_STATE_INITIALIZED -> it's been initialized and ready to be called
* - HID_BPF_ASYNC_STATE_RUNNING -> possible re-entry from the wq itself
*/
if (elem->state != HID_BPF_ASYNC_STATE_INITIALIZED &&
elem->state != HID_BPF_ASYNC_STATE_RUNNING)
return -EINVAL;
elem->state = HID_BPF_ASYNC_STATE_STARTING;
}
elem->state = HID_BPF_ASYNC_STATE_STARTING;
bpf_spin_unlock(&elem->lock);
elem->hid = hctx->hid->id;

View File

@@ -7,7 +7,18 @@
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include <linux/errno.h>
#include "hid_report_descriptor_helpers.h"
/* Compiler attributes */
#ifndef __packed
#define __packed __attribute__((packed))
#endif
#ifndef __maybe_unused
#define __maybe_unused __attribute__((__unused__))
#endif
extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
unsigned int offset,
@@ -40,6 +51,86 @@ extern int bpf_wq_set_callback(struct bpf_wq *wq,
#define HID_MAX_DESCRIPTOR_SIZE 4096
#define HID_IGNORE_EVENT -1
/**
* Use: _cleanup_(somefunction) struct foo *bar;
*/
#define _cleanup_(_x) __attribute__((cleanup(_x)))
/**
* Use: _release_(foo) *bar;
*
* This requires foo_releasep() to be present, use DEFINE_RELEASE_CLEANUP_FUNC.
*/
#define _release_(_type) struct _type __attribute__((cleanup(_type##_releasep)))
/**
* Define a cleanup function for the struct type foo with a matching
* foo_release(). Use:
* DEFINE_RELEASE_CLEANUP_FUNC(foo)
* _unref_(foo) struct foo *bar;
*/
#define DEFINE_RELEASE_CLEANUP_FUNC(_type) \
static inline void _type##_releasep(struct _type **_p) { \
if (*_p) \
_type##_release(*_p); \
} \
struct __useless_struct_to_allow_trailing_semicolon__
/* for being able to have a cleanup function */
#define hid_bpf_ctx_release hid_bpf_release_context
DEFINE_RELEASE_CLEANUP_FUNC(hid_bpf_ctx);
/*
* Kernel-style guard macros adapted for BPF
* Based on include/linux/cleanup.h from the Linux kernel
*
* These provide automatic lock/unlock using __attribute__((cleanup))
* similar to how _release_() works for contexts.
*/
/**
* DEFINE_GUARD(name, type, lock, unlock):
* Define a guard for automatic lock/unlock using the same pattern as _release_()
* @name: identifier for the guard (e.g., bpf_spin)
* @type: lock variable type (e.g., struct bpf_spin_lock)
* @lock: lock function name (e.g., bpf_spin_lock)
* @unlock: unlock function name (e.g., bpf_spin_unlock)
*
* guard(name):
* Declare and lock in one statement - lock held until end of scope
*
* Example:
* DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock)
*
* void foo(struct bpf_spin_lock *lock) {
* guard(bpf_spin)(lock);
* // lock held until end of scope
* }
*/
/* Guard helper struct - stores lock pointer for cleanup */
#define DEFINE_GUARD(_name, _type, _lock, _unlock) \
struct _name##_guard { \
_type *lock; \
}; \
static inline void _name##_guard_cleanup(struct _name##_guard *g) { \
if (g && g->lock) \
_unlock(g->lock); \
} \
static inline struct _name##_guard _name##_guard_init(_type *l) { \
if (l) \
_lock(l); \
return (struct _name##_guard){.lock = l}; \
} \
struct __useless_struct_to_allow_trailing_semicolon__
#define guard(_name) \
struct _name##_guard COMBINE(guard, __LINE__) __attribute__((cleanup(_name##_guard_cleanup))) = \
_name##_guard_init
/* Define BPF spinlock guard */
DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock);
/* extracted from <linux/input.h> */
#define BUS_ANY 0x00
#define BUS_PCI 0x01
@@ -183,4 +274,234 @@ extern int bpf_wq_set_callback(struct bpf_wq *wq,
_EXPAND(_ARG, __VA_ARGS__) \
} _device_ids SEC(".hid_bpf_config")
/* Equivalency macros for bpf_htons and friends which are
* Big Endian only - HID needs little endian so these are the
* corresponding macros for that. See bpf/bpf_endian.h
*/
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define __hid_bpf_le16_to_cpu(x) (x)
# define __hid_bpf_le32_to_cpu(x) (x)
# define __hid_bpf_le64_to_cpu(x) (x)
# define __hid_bpf_cpu_to_le16(x) (x)
# define __hid_bpf_cpu_to_le32(x) (x)
# define __hid_bpf_cpu_to_le64(x) (x)
# define __hid_bpf_constant_le16_to_cpu(x) (x)
# define __hid_bpf_constant_le32_to_cpu(x) (x)
# define __hid_bpf_constant_le64_to_cpu(x) (x)
# define __hid_bpf_constant_cpu_to_le16(x) (x)
# define __hid_bpf_constant_cpu_to_le32(x) (x)
# define __hid_bpf_constant_cpu_to_le64(x) (x)
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define __hid_bpf_le16_to_cpu(x) __builtin_bswap16(x)
# define __hid_bpf_le32_to_cpu(x) __builtin_bswap32(x)
# define __hid_bpf_le64_to_cpu(x) __builtin_bswap64(x)
# define __hid_bpf_cpu_to_le16(x) __builtin_bswap16(x)
# define __hid_bpf_cpu_to_le32(x) __builtin_bswap32(x)
# define __hid_bpf_cpu_to_le64(x) __builtin_bswap64(x)
# define __hid_bpf_constant_le16_to_cpu(x) __bpf_swab16(x)
# define __hid_bpf_constant_le32_to_cpu(x) __bpf_swab32(x)
# define __hid_bpf_constant_le64_to_cpu(x) __bpf_swab64(x)
# define __hid_bpf_constant_cpu_to_le16(x) __bpf_swab16(x)
# define __hid_bpf_constant_cpu_to_le32(x) __bpf_swab32(x)
# define __hid_bpf_constant_cpu_to_le64(x) __bpf_swab64(x)
#else
# error "Invalid __BYTE_ORDER__"
#endif
#define hid_bpf_le16_to_cpu(x) \
(__builtin_constant_p(x) ? \
__hid_bpf_constant_le16_to_cpu(x) : __hid_bpf_le16_to_cpu(x))
#define hid_bpf_le32_to_cpu(x) \
(__builtin_constant_p(x) ? \
__hid_bpf_constant_le32_to_cpu(x) : __hid_bpf_le32_to_cpu(x))
#define hid_bpf_le64_to_cpu(x) \
(__builtin_constant_p(x) ? \
__hid_bpf_constant_le64_to_cpu(x) : __hid_bpf_le64_to_cpu(x))
#define hid_bpf_cpu_to_le16(x) \
(__builtin_constant_p(x) ? \
__hid_bpf_constant_cpu_to_le16(x) : __hid_bpf_cpu_to_le16(x))
#define hid_bpf_cpu_to_le32(x) \
(__builtin_constant_p(x) ? \
__hid_bpf_constant_cpu_to_le32(x) : __hid_bpf_cpu_to_le32(x))
#define hid_bpf_cpu_to_le64(x) \
(__builtin_constant_p(x) ? \
__hid_bpf_constant_cpu_to_le64(x) : __hid_bpf_cpu_to_le64(x))
#define hid_bpf_be16_to_cpu(x) bpf_ntohs(x)
#define hid_bpf_be32_to_cpu(x) bpf_ntohl(x)
#define hid_bpf_be64_to_cpu(x) bpf_be64_to_cpu(x)
#define hid_bpf_cpu_to_be16(x) bpf_htons(x)
#define hid_bpf_cpu_to_be32(x) bpf_htonl(x)
#define hid_bpf_cpu_to_be64(x) bpf_cpu_to_be64(x)
/*
* The following macros are helpers for exporting udev properties:
*
* EXPORT_UDEV_PROP(name, len) generates:
* - a map with a single element UDEV_PROP_##name, of size len
* - a const global declaration of that len: SIZEOF_##name
*
* udev_prop_ptr(name) retrieves the data pointer behind the map.
*
* UDEV_PROP_SPRINTF(name, fmt, ...) writes data into the udev property.
*
* Can be used as such:
* EXPORT_UDEV_PROP(HID_FOO, 32);
*
* SEC("syscall")
* int probe(struct hid_bpf_probe_args *ctx)
* {
* const char *foo = "foo";
* UDEV_PROP_SPRINTF(HID_FOO, "%s", foo);
*
* return 0;
* }
*/
#define EXPORT_UDEV_PROP(name, len) \
const __u32 SIZEOF_##name = len; \
struct COMBINE(udev_prop, __LINE__) { \
__uint(type, BPF_MAP_TYPE_ARRAY); \
__uint(max_entries, 1); \
__type(key, __u32); \
__type(value, __u8[len]); \
} UDEV_PROP_##name SEC(".maps");
#define udev_prop_ptr(name) \
bpf_map_lookup_elem(&UDEV_PROP_##name, &(__u32){0})
#define UDEV_PROP_SPRINTF(name, fmt, ...) \
BPF_SNPRINTF(udev_prop_ptr(name), SIZEOF_##name, fmt, ##__VA_ARGS__)
static inline __maybe_unused __u16 field_start_byte(struct hid_rdesc_field *field)
{
return field->bits_start / 8;
}
static inline __maybe_unused __u16 field_end_byte(struct hid_rdesc_field *field)
{
if (!field->bits_end)
return 0;
return (__u16)(field->bits_end - 1) / 8;
}
static __maybe_unused __u32 extract_bits(__u8 *buffer, const size_t size, struct hid_rdesc_field *field)
{
__s32 nbits = field->bits_end - field->bits_start;
__u32 start = field_start_byte(field);
__u32 end = field_end_byte(field);
__u8 base_shift = field->bits_start % 8;
if (nbits <= 0 || nbits > 32 || start >= size || end >= size)
return 0;
/* Fast path for byte-aligned standard-sized reads */
if (base_shift == 0) {
/* 8-bit aligned read */
if (nbits == 8 && start < size)
return buffer[start];
/* 16-bit aligned read - use separate variables for verifier */
if (nbits == 16) {
__u32 off0 = start;
__u32 off1 = start + 1;
if (off0 < size && off1 < size) {
return buffer[off0] |
((__u32)buffer[off1] << 8);
}
}
/* 32-bit aligned read - use separate variables for verifier */
if (nbits == 32) {
__u32 off0 = start;
__u32 off1 = start + 1;
__u32 off2 = start + 2;
__u32 off3 = start + 3;
if (off0 < size && off1 < size &&
off2 < size && off3 < size) {
return buffer[off0] |
((__u32)buffer[off1] << 8) |
((__u32)buffer[off2] << 16) |
((__u32)buffer[off3] << 24);
}
}
}
/* General case: bit manipulation for unaligned or non-standard sizes */
int mask = 0xffffffff >> (32 - nbits);
__u64 value = 0;
__u32 i;
bpf_for (i, start, end + 1) {
value |= (__u64)buffer[i] << ((i - start) * 8);
}
return (value >> base_shift) & mask;
}
#define EXTRACT_BITS(buffer, field) extract_bits(buffer, sizeof(buffer), field)
/* Base macro for iterating over HID arrays with bounds checking.
* Follows the bpf_for pattern from libbpf.
*/
#define __hid_bpf_for_each_array(array, num_elements, max_elements, var) \
for ( \
/* initialize and define destructor */ \
struct bpf_iter_num ___it __attribute__((aligned(8), \
cleanup(bpf_iter_num_destroy))), \
/* ___p pointer is necessary to call bpf_iter_num_new() *once* */ \
*___p __attribute__((unused)) = ( \
/* always initialize iterator; if bounds fail, iterate 0 times */ \
bpf_iter_num_new(&___it, 0, \
(num_elements) > (max_elements) ? \
0 : (num_elements)), \
/* workaround for Clang bug */ \
(void)bpf_iter_num_destroy, (void *)0); \
({ \
/* iteration step */ \
int *___t = bpf_iter_num_next(&___it); \
int ___i; \
/* termination and bounds check, assign var */ \
(___t && (___i = *___t, ___i >= 0 && ___i < (num_elements)) && \
((num_elements) <= (max_elements)) && \
(var = &(array)[___i], 1)); \
}); \
)
/* Iterate over input reports in a descriptor */
#define hid_bpf_for_each_input_report(descriptor, report_var) \
__hid_bpf_for_each_array((descriptor)->input_reports, \
(descriptor)->num_input_reports, \
HID_MAX_REPORTS, report_var)
/* Iterate over feature reports in a descriptor */
#define hid_bpf_for_each_feature_report(descriptor, report_var) \
__hid_bpf_for_each_array((descriptor)->feature_reports, \
(descriptor)->num_feature_reports, \
HID_MAX_REPORTS, report_var)
/* Iterate over output reports in a descriptor */
#define hid_bpf_for_each_output_report(descriptor, report_var) \
__hid_bpf_for_each_array((descriptor)->output_reports, \
(descriptor)->num_output_reports, \
HID_MAX_REPORTS, report_var)
/* Iterate over fields in a report */
#define hid_bpf_for_each_field(report, field_var) \
__hid_bpf_for_each_array((report)->fields, (report)->num_fields, \
HID_MAX_FIELDS, field_var)
/* Iterate over collections in a field */
#define hid_bpf_for_each_collection(field, collection_var) \
__hid_bpf_for_each_array((field)->collections, (field)->num_collections, \
HID_MAX_COLLECTIONS, collection_var)
#endif /* __HID_BPF_HELPERS_H */

View File

@@ -0,0 +1,80 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2022 Benjamin Tissoires
*/
#ifndef __HID_REPORT_DESCRIPTOR_HELPERS_H
#define __HID_REPORT_DESCRIPTOR_HELPERS_H
#include "vmlinux.h"
/* Compiler attributes */
#ifndef __packed
#define __packed __attribute__((packed))
#endif
#ifndef __maybe_unused
#define __maybe_unused __attribute__((__unused__))
#endif
/* Report Descriptor Structures */
#define HID_MAX_COLLECTIONS 32
#define HID_MAX_FIELDS 64
#define HID_MAX_REPORTS 16
enum hid_rdesc_field_type {
HID_FIELD_VARIABLE = 0,
HID_FIELD_ARRAY = 1,
HID_FIELD_CONSTANT = 2,
};
struct hid_rdesc_collection {
__u16 usage_page;
__u16 usage_id;
__u8 collection_type;
} __packed;
struct hid_rdesc_field {
__u8 field_type; /* enum hid_rdesc_field_type */
__u8 num_collections;
__u16 bits_start;
__u16 bits_end;
__u16 usage_page;
union {
__u16 usage_id; /* For Variable fields */
struct __packed { /* For Array fields */
__u16 usage_minimum;
__u16 usage_maximum;
};
};
__s32 logical_minimum;
__s32 logical_maximum;
struct {
__u8 is_relative:1; /* Data is relative to previous value */
__u8 wraps:1; /* Value wraps around (e.g., rotary encoder) */
__u8 is_nonlinear:1; /* Non-linear relationship between logical/physical */
__u8 has_no_preferred_state:1; /* No rest position (e.g., free-floating joystick) */
__u8 has_null_state:1; /* Can report null/no-data values */
__u8 is_volatile:1; /* Volatile (Output/Feature) - NOT POPULATED, always 0 */
__u8 is_buffered_bytes:1; /* Fixed-size byte stream vs bitfield */
__u8 reserved:1; /* Reserved for future use */
} flags;
struct hid_rdesc_collection collections[HID_MAX_COLLECTIONS];
} __packed;
struct hid_rdesc_report {
__u8 report_id; /* 0 means no report ID */
__u16 size_in_bits;
__u8 num_fields;
struct hid_rdesc_field fields[HID_MAX_FIELDS];
} __packed;
struct hid_rdesc_descriptor {
__u8 num_input_reports;
__u8 num_output_reports;
__u8 num_feature_reports;
struct hid_rdesc_report input_reports[HID_MAX_REPORTS];
struct hid_rdesc_report output_reports[HID_MAX_REPORTS];
struct hid_rdesc_report feature_reports[HID_MAX_REPORTS];
} __packed;
#endif /* __HID_REPORT_DESCRIPTOR_HELPERS_H */

File diff suppressed because it is too large Load Diff