linux/drivers/hid/bpf/progs/XPPen__ACK05.bpf.c
Peter Hutterer 55cd617566 HID: bpf: fix BTN_STYLUS for the XP Pen ACK05 remote
Usage_Dig_BarrelSwitch was applied in the UsagePage_Button
which incorrectly mapped to BTN_TOOL_PENCIL

Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/193
Fixes: 834da375 ("bpf: add a v6.11+ compatible BPF fixup for the XPPen ACK05 remote")
Link: https://patchwork.kernel.org/project/linux-input/patch/20250207-bpf-import-2025-02-07-v1-7-6048fdd5a206@kernel.org/

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
2025-04-24 11:36:53 +02:00

332 lines
12 KiB
C

// 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 HID_BPF_ASYNC_MAX_CTX 1
#include "hid_bpf_async.h"
#define VID_UGEE 0x28BD
/* same PID whether connected directly or through the provided dongle: */
#define PID_ACK05_REMOTE 0x0202
HID_BPF_CONFIG(
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ACK05_REMOTE),
);
/*
* By default, the pad reports the buttons through a set of key sequences.
*
* The pad reports a classic keyboard report descriptor:
* # HANVON UGEE Shortcut Remote
* Report descriptor length: 102 bytes
* 0x05, 0x01, // Usage Page (Generic Desktop) 0
* 0x09, 0x02, // Usage (Mouse) 2
* 0xa1, 0x01, // Collection (Application) 4
* 0x85, 0x09, // Report ID (9) 6
* 0x09, 0x01, // Usage (Pointer) 8
* 0xa1, 0x00, // Collection (Physical) 10
* 0x05, 0x09, // Usage Page (Button) 12
* 0x19, 0x01, // UsageMinimum (1) 14
* 0x29, 0x03, // UsageMaximum (3) 16
* 0x15, 0x00, // Logical Minimum (0) 18
* 0x25, 0x01, // Logical Maximum (1) 20
* 0x95, 0x03, // Report Count (3) 22
* 0x75, 0x01, // Report Size (1) 24
* 0x81, 0x02, // Input (Data,Var,Abs) 26
* 0x95, 0x05, // Report Count (5) 28
* 0x81, 0x01, // Input (Cnst,Arr,Abs) 30
* 0x05, 0x01, // Usage Page (Generic Desktop) 32
* 0x09, 0x30, // Usage (X) 34
* 0x09, 0x31, // Usage (Y) 36
* 0x26, 0xff, 0x7f, // Logical Maximum (32767) 38
* 0x95, 0x02, // Report Count (2) 41
* 0x75, 0x10, // Report Size (16) 43
* 0x81, 0x02, // Input (Data,Var,Abs) 45
* 0x05, 0x0d, // Usage Page (Digitizers) 47
* 0x09, 0x30, // Usage (Tip Pressure) 49
* 0x26, 0xff, 0x07, // Logical Maximum (2047) 51
* 0x95, 0x01, // Report Count (1) 54
* 0x75, 0x10, // Report Size (16) 56
* 0x81, 0x02, // Input (Data,Var,Abs) 58
* 0xc0, // End Collection 60
* 0xc0, // End Collection 61
* 0x05, 0x01, // Usage Page (Generic Desktop) 62
* 0x09, 0x06, // Usage (Keyboard) 64
* 0xa1, 0x01, // Collection (Application) 66
* 0x85, 0x06, // Report ID (6) 68
* 0x05, 0x07, // Usage Page (Keyboard/Keypad) 70
* 0x19, 0xe0, // UsageMinimum (224) 72
* 0x29, 0xe7, // UsageMaximum (231) 74
* 0x15, 0x00, // Logical Minimum (0) 76
* 0x25, 0x01, // Logical Maximum (1) 78
* 0x75, 0x01, // Report Size (1) 80
* 0x95, 0x08, // Report Count (8) 82
* 0x81, 0x02, // Input (Data,Var,Abs) 84
* 0x05, 0x07, // Usage Page (Keyboard/Keypad) 86
* 0x19, 0x00, // UsageMinimum (0) 88
* 0x29, 0xff, // UsageMaximum (255) 90
* 0x26, 0xff, 0x00, // Logical Maximum (255) 92
* 0x75, 0x08, // Report Size (8) 95
* 0x95, 0x06, // Report Count (6) 97
* 0x81, 0x00, // Input (Data,Arr,Abs) 99
* 0xc0, // End Collection 101
*
* Each button gets assigned the following events:
*
* Buttons released: 06 00 00 00 00 00 00 00
* Button 1: 06 01 12 00 00 00 00 00 -> LControl + o
* Button 2: 06 01 11 00 00 00 00 00 -> LControl + n
* Button 3: 06 00 3e 00 00 00 00 00 -> F5
* Button 4: 06 02 00 00 00 00 00 00 -> LShift
* Button 5: 06 01 00 00 00 00 00 00 -> LControl
* Button 6: 06 04 00 00 00 00 00 00 -> LAlt
* Button 7: 06 01 16 00 00 00 00 00 -> LControl + s
* Button 8: 06 01 1d 00 00 00 00 00 -> LControl + z
* Button 9: 06 00 2c 00 00 00 00 00 -> Space
* Button 10: 06 03 1d 00 00 00 00 00 -> LControl + LShift + z
* Wheel: 06 01 57 00 00 00 00 00 -> clockwise rotation (LControl + Keypad Plus)
* Wheel: 06 01 56 00 00 00 00 00 -> counter-clockwise rotation
* (LControl + Keypad Minus)
*
* However, multiple buttons can be pressed at the same time, and when this happens,
* each button gets assigned a new slot in the Input (Data,Arr,Abs):
*
* Button 1 + 3: 06 01 12 3e 00 00 00 00 -> LControl + o + F5
*
* When a modifier is pressed (Button 4, 5, or 6), the assigned key is set to 00:
*
* Button 5 + 7: 06 01 00 16 00 00 00 00 -> LControl + s
*
* This is mostly fine, but with Button 8 and Button 10 sharing the same
* key value ("z"), there are cases where we can not know which is which.
*
*/
#define PAD_WIRED_DESCRIPTOR_LENGTH 102
#define PAD_DONGLE_DESCRIPTOR_LENGTH 177
#define STYLUS_DESCRIPTOR_LENGTH 109
#define VENDOR_DESCRIPTOR_LENGTH 36
#define PAD_REPORT_ID 6
#define RAW_PAD_REPORT_ID 0xf0
#define RAW_BATTERY_REPORT_ID 0xf2
#define VENDOR_REPORT_ID 2
#define PAD_REPORT_LENGTH 8
#define VENDOR_REPORT_LENGTH 12
__u16 last_button_state;
static const __u8 disabled_rdesc[] = {
// Make sure we match our original report length
FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
};
static const __u8 fixed_rdesc_vendor[] = {
UsagePage_GenericDesktop
Usage_GD_Keypad
CollectionApplication(
// -- Byte 0 in report
ReportId(RAW_PAD_REPORT_ID)
// Byte 1 in report - same than report ID
ReportCount(1)
ReportSize(8)
Input(Const) // padding (internal report ID)
LogicalMaximum_i8(0)
LogicalMaximum_i8(1)
UsagePage_Digitizers
Usage_Dig_TabletFunctionKeys
CollectionPhysical(
// Byte 2-3 is the button state
UsagePage_Button
UsageMinimum_i8(0x01)
UsageMaximum_i8(0x0a)
LogicalMinimum_i8(0x0)
LogicalMaximum_i8(0x1)
ReportCount(10)
ReportSize(1)
Input(Var|Abs)
Usage_i8(0x31) // will be mapped as BTN_A / BTN_SOUTH
ReportCount(1)
Input(Var|Abs)
ReportCount(5) // padding
Input(Const)
// Byte 4 in report - just exists so we get to be a tablet pad
UsagePage_Digitizers
Usage_Dig_BarrelSwitch // BTN_STYLUS
ReportCount(1)
ReportSize(1)
Input(Var|Abs)
ReportCount(7) // padding
Input(Const)
// Bytes 5/6 in report - just exists so we get to be a tablet pad
UsagePage_GenericDesktop
Usage_GD_X
Usage_GD_Y
ReportCount(2)
ReportSize(8)
Input(Var|Abs)
// Byte 7 in report is the dial
Usage_GD_Wheel
LogicalMinimum_i8(-1)
LogicalMaximum_i8(1)
ReportCount(1)
ReportSize(8)
Input(Var|Rel)
)
// -- Byte 0 in report
ReportId(RAW_BATTERY_REPORT_ID)
// Byte 1 in report - same than report ID
ReportCount(1)
ReportSize(8)
Input(Const) // padding (internal report ID)
// Byte 2 in report - always 0x01
Input(Const) // padding (internal report ID)
UsagePage_Digitizers
/*
* We represent the device as a stylus to force the kernel to not
* directly query its battery state. Instead the kernel will rely
* only on the provided events.
*/
Usage_Dig_Stylus
CollectionPhysical(
// Byte 3 in report - battery value
UsagePage_BatterySystem
Usage_BS_AbsoluteStateOfCharge
LogicalMinimum_i8(0)
LogicalMaximum_i8(100)
ReportCount(1)
ReportSize(8)
Input(Var|Abs)
// Byte 4 in report - charging state
Usage_BS_Charging
LogicalMinimum_i8(0)
LogicalMaximum_i8(1)
ReportCount(1)
ReportSize(8)
Input(Var|Abs)
)
)
};
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(ack05_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 == VENDOR_DESCRIPTOR_LENGTH) {
/*
* The vendor fixed rdesc is appended after the current one,
* to keep the output reports working.
*/
__builtin_memcpy(data + rdesc_size, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
return sizeof(fixed_rdesc_vendor) + rdesc_size;
}
hid_set_name(hctx->hid, "Disabled by HID-BPF Hanvon Ugee Shortcut Remote");
__builtin_memcpy(data, disabled_rdesc, sizeof(disabled_rdesc));
return sizeof(disabled_rdesc);
}
static int HID_BPF_ASYNC_FUN(switch_to_raw_mode)(struct hid_bpf_ctx *hid)
{
static __u8 magic_0[32] = {0x02, 0xb0, 0x04, 0x00, 0x00};
int err;
/*
* The proprietary driver sends the 3 following packets after the
* above one.
* These don't seem to have any effect, so we don't send them to save
* some processing time.
*
* static __u8 magic_1[32] = {0x02, 0xb4, 0x01, 0x00, 0x01};
* static __u8 magic_2[32] = {0x02, 0xb4, 0x01, 0x00, 0xff};
* static __u8 magic_3[32] = {0x02, 0xb8, 0x04, 0x00, 0x00};
*/
err = hid_bpf_hw_output_report(hid, magic_0, sizeof(magic_0));
if (err < 0)
return err;
return 0;
}
SEC(HID_BPF_DEVICE_EVENT)
int BPF_PROG(ack05_fix_events, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PAD_REPORT_LENGTH);
int ret = 0;
if (!data)
return 0; /* EPERM check */
if (data[0] != VENDOR_REPORT_ID)
return 0;
/* reconnect event */
if (data[1] == 0xf8 && data[2] == 02 && data[3] == 0x01)
HID_BPF_ASYNC_DELAYED_CALL(switch_to_raw_mode, hctx, 10);
/* button event */
if (data[1] == RAW_PAD_REPORT_ID) {
data[0] = data[1];
if (data[7] == 0x02)
data[7] = 0xff;
ret = 8;
} else if (data[1] == RAW_BATTERY_REPORT_ID) {
data[0] = data[1];
ret = 5;
}
return ret;
}
HID_BPF_OPS(xppen_ack05_remote) = {
.hid_device_event = (void *)ack05_fix_events,
.hid_rdesc_fixup = (void *)ack05_fix_rdesc,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
switch (ctx->rdesc_size) {
case PAD_WIRED_DESCRIPTOR_LENGTH:
case PAD_DONGLE_DESCRIPTOR_LENGTH:
case STYLUS_DESCRIPTOR_LENGTH:
case VENDOR_DESCRIPTOR_LENGTH:
ctx->retval = 0;
break;
default:
ctx->retval = -EINVAL;
break;
}
if (ctx->rdesc_size == VENDOR_DESCRIPTOR_LENGTH) {
struct hid_bpf_ctx *hctx = hid_bpf_allocate_context(ctx->hid);
if (!hctx) {
ctx->retval = -EINVAL;
return 0;
}
ctx->retval = HID_BPF_ASYNC_INIT(switch_to_raw_mode) ||
switch_to_raw_mode(hctx);
hid_bpf_release_context(hctx);
}
return 0;
}
char _license[] SEC("license") = "GPL";