* [PATCH 01/10] HID: bpf: Add support for the Inspiroy 2M
2025-11-18 17:16 [PATCH 00/10] HID: bpf: sync up with current udev-hid-bpf programs Benjamin Tissoires
@ 2025-11-18 17:16 ` Benjamin Tissoires
2025-11-18 17:16 ` [PATCH 02/10] HID: bpf: add support for Huion Kamvas 13 (Gen 3) (model GS1333) Benjamin Tissoires
` (9 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
To: Jiri Kosina
Cc: linux-kernel, linux-input, Benjamin Tissoires, Peter Hutterer,
Benjamin Tissoires
There are a differences in the report descriptor to the existing
Inspiroy 2S which makes having this as separate file a more efficient
approach than merging them together.
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@gmail.com>
Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/167
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
drivers/hid/bpf/progs/Huion__Inspiroy-2-M.bpf.c | 563 ++++++++++++++++++++++++
1 file changed, 563 insertions(+)
diff --git a/drivers/hid/bpf/progs/Huion__Inspiroy-2-M.bpf.c b/drivers/hid/bpf/progs/Huion__Inspiroy-2-M.bpf.c
new file mode 100644
index 0000000000000000000000000000000000000000..183d408d893a2adde7f12e8028449c491f4942a2
--- /dev/null
+++ b/drivers/hid/bpf/progs/Huion__Inspiroy-2-M.bpf.c
@@ -0,0 +1,563 @@
+// 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_INSPIROY_2_M 0x0067
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HUION, PID_INSPIROY_2_M),
+);
+
+/* Filled in by udev-hid-bpf */
+char UDEV_PROP_HUION_FIRMWARE_ID[64];
+
+/* The prefix of the firmware ID we expect for this device. The full firmware
+ * string has a date suffix, e.g. HUION_T21j_221221
+ */
+char EXPECTED_FIRMWARE_ID[] = "HUION_T21k_";
+
+/* How this BPF program works: the tablet has two modes, firmware mode and
+ * tablet mode. In firmware mode (out of the box) the tablet sends button events
+ * and the dial as keyboard combinations. In tablet mode it uses a vendor specific
+ * hid report to report everything instead.
+ * Depending on the mode some hid reports are never sent and the corresponding
+ * devices are mute.
+ *
+ * To switch the tablet use e.g. https://github.com/whot/huion-switcher
+ * or one of the tools from the digimend project
+ *
+ * This BPF works for both modes. The huion-switcher tool sets the
+ * HUION_FIRMWARE_ID udev property - if that is set then we disable the firmware
+ * pad and pen reports (by making them vendor collections that are ignored).
+ * If that property is not set we fix all hidraw nodes so the tablet works in
+ * either mode though the drawback is that the device will show up twice if
+ * you bind it to all event nodes
+ *
+ * Default report descriptor for the first exposed hidraw node:
+ *
+ * # HUION Huion Tablet_H641P
+ * # Report descriptor length: 18 bytes
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 0xFF00) 0
+ * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 3
+ * # 0xa1, 0x01, // Collection (Application) 5
+ * # 0x85, 0x08, // Report ID (8) 7
+ * # 0x75, 0x58, // Report Size (88) 9
+ * # 0x95, 0x01, // Report Count (1) 11
+ * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 13
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 15
+ * # 0xc0, // End Collection 17
+ * R: 18 06 00 ff 09 01 a1 01 85 08 75 58 95 01 09 01 81 02 c0
+ *
+ * This rdesc does nothing until the tablet is switched to raw mode, see
+ * https://github.com/whot/huion-switcher
+ *
+ *
+ * Second hidraw node is the Pen. This one sends events until the tablet is
+ * switched to raw mode, then it's mute.
+ *
+ * # Report descriptor length: 93 bytes
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 0
+ * # 0x09, 0x02, // Usage (Pen) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # 0x85, 0x0a, // Report ID (10) 6
+ * # 0x09, 0x20, // Usage (Stylus) 8
+ * # 0xa1, 0x01, // Collection (Application) 10
+ * # 0x09, 0x42, // Usage (Tip Switch) 12
+ * # 0x09, 0x44, // Usage (Barrel Switch) 14
+ * # 0x09, 0x45, // Usage (Eraser) 16
+ * # 0x09, 0x3c, // Usage (Invert) 18 <-- has no Invert eraser
+ * # 0x15, 0x00, // Logical Minimum (0) 20
+ * # 0x25, 0x01, // Logical Maximum (1) 22
+ * # 0x75, 0x01, // Report Size (1) 24
+ * # 0x95, 0x06, // Report Count (6) 26
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 28
+ * # 0x09, 0x32, // Usage (In Range) 30
+ * # 0x75, 0x01, // Report Size (1) 32
+ * # 0x95, 0x01, // Report Count (1) 34
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 36
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 38
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 40
+ * # 0x09, 0x30, // Usage (X) 42
+ * # 0x09, 0x31, // Usage (Y) 44
+ * # 0x55, 0x0d, // Unit Exponent (-3) 46 <-- change to -2
+ * # 0x65, 0x33, // Unit (EnglishLinear: in³) 48 <-- change in³ to in
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 50
+ * # 0x35, 0x00, // Physical Minimum (0) 53
+ * # 0x46, 0x00, 0x08, // Physical Maximum (2048) 55 <-- invalid size
+ * # 0x75, 0x10, // Report Size (16) 58
+ * # 0x95, 0x02, // Report Count (2) 60
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 62
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 64
+ * # 0x09, 0x30, // Usage (Tip Pressure) 66
+ * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 68
+ * # 0x75, 0x10, // Report Size (16) 71
+ * # 0x95, 0x01, // Report Count (1) 73
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 75
+ * # 0x09, 0x3d, // Usage (X Tilt) 77 <-- No tilt reported
+ * # 0x09, 0x3e, // Usage (Y Tilt) 79
+ * # 0x15, 0x81, // Logical Minimum (-127) 81
+ * # 0x25, 0x7f, // Logical Maximum (127) 83
+ * # 0x75, 0x08, // Report Size (8) 85
+ * # 0x95, 0x02, // Report Count (2) 87
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 89
+ * # 0xc0, // End Collection 91
+ * # 0xc0, // End Collection 92
+ * R: 93 05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 45 09 3c 15 00 25 01 7501 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 09 3d09 3e 15 81 25 7f 75 08 95 02 81 02 c0 c0
+ *
+ * Third hidraw node is the pad which sends a combination of keyboard shortcuts until
+ * the tablet is switched to raw mode, then it's mute:
+ *
+ * # Report descriptor length: 65 bytes
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 0
+ * # 0x09, 0x06, // Usage (Keyboard) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # 0x85, 0x03, // Report ID (3) 6
+ * # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 8
+ * # 0x19, 0xe0, // UsageMinimum (224) 10
+ * # 0x29, 0xe7, // UsageMaximum (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
+ * # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 24
+ * # 0x19, 0x00, // UsageMinimum (0) 26
+ * # 0x29, 0xff, // UsageMaximum (255) 28
+ * # 0x26, 0xff, 0x00, // Logical Maximum (255) 30
+ * # 0x75, 0x08, // Report Size (8) 33
+ * # 0x95, 0x06, // Report Count (6) 35
+ * # 0x81, 0x00, // Input (Data,Arr,Abs) 37
+ * # 0xc0, // End Collection 39
+ * # 0x05, 0x0c, // Usage Page (Consumer) 40
+ * # 0x09, 0x01, // Usage (Consumer Control) 42
+ * # 0xa1, 0x01, // Collection (Application) 44
+ * # 0x85, 0x04, // Report ID (4) 46
+ * # 0x19, 0x00, // UsageMinimum (0) 48
+ * # 0x2a, 0x3c, 0x02, // UsageMaximum (572) 50
+ * # 0x15, 0x00, // Logical Minimum (0) 53
+ * # 0x26, 0x3c, 0x02, // Logical Maximum (572) 55
+ * # 0x95, 0x01, // Report Count (1) 58
+ * # 0x75, 0x10, // Report Size (16) 60
+ * # 0x81, 0x00, // Input (Data,Arr,Abs) 62
+ * # 0xc0, // End Collection 64
+ * R: 65 05 01 09 06 a1 01 85 03 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 0507 19 00 29 ff 26 ff 00 75 08 95 06 81 00 c0 05 0c 09 01 a1 01 85 04 19 00 2a 3c02 15 00 26 3c 02 95 01 75 10 81 00 c0
+ * N: HUION Huion Tablet_H641P
+ */
+
+#define PAD_REPORT_DESCRIPTOR_LENGTH 133
+#define PEN_REPORT_DESCRIPTOR_LENGTH 93
+#define VENDOR_REPORT_DESCRIPTOR_LENGTH 36
+#define PAD_REPORT_ID 3
+#define PEN_REPORT_ID 10
+#define VENDOR_REPORT_ID 8
+#define PAD_REPORT_LENGTH 8
+#define PEN_REPORT_LENGTH 10
+#define VENDOR_REPORT_LENGTH 12
+
+
+__u16 last_button_state;
+
+static const __u8 fixed_rdesc_pad[] = {
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ // -- Byte 0 in report
+ ReportId(PAD_REPORT_ID)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ // Byte 1 in report - just exists so we get to be a tablet pad
+ Usage_Dig_BarrelSwitch // BTN_STYLUS
+ ReportCount(1)
+ ReportSize(1)
+ Input(Var|Abs)
+ ReportCount(7) // padding
+ Input(Const)
+ // Bytes 2/3 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 4 in report is the wheel
+ Usage_GD_Wheel
+ LogicalMinimum_i8(-1)
+ LogicalMaximum_i8(1)
+ ReportCount(1)
+ ReportSize(8)
+ Input(Var|Rel)
+ // Byte 5 is the button state
+ UsagePage_Button
+ UsageMinimum_i8(0x1)
+ UsageMaximum_i8(0x8)
+ LogicalMinimum_i8(0x1)
+ LogicalMaximum_i8(0x8)
+ ReportCount(1)
+ ReportSize(8)
+ Input(Arr|Abs)
+ )
+ // Make sure we match our original report length
+ FixedSizeVendorReport(PAD_REPORT_LENGTH)
+ )
+};
+
+static const __u8 fixed_rdesc_pen[] = {
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionApplication(
+ // -- Byte 0 in report
+ ReportId(PEN_REPORT_ID)
+ Usage_Dig_Pen
+ CollectionPhysical(
+ // -- Byte 1 in report
+ Usage_Dig_TipSwitch
+ Usage_Dig_BarrelSwitch
+ Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportSize(1)
+ ReportCount(3)
+ Input(Var|Abs)
+ ReportCount(4) // Padding
+ Input(Const)
+ Usage_Dig_InRange
+ ReportCount(1)
+ Input(Var|Abs)
+ ReportSize(16)
+ ReportCount(1)
+ PushPop(
+ UsagePage_GenericDesktop
+ Unit(cm)
+ UnitExponent(-1)
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(160)
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(32767)
+ Usage_GD_X
+ Input(Var|Abs) // Bytes 2+3
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(100)
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(32767)
+ Usage_GD_Y
+ Input(Var|Abs) // Bytes 4+5
+ )
+ UsagePage_Digitizers
+ Usage_Dig_TipPressure
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(8191)
+ Input(Var|Abs) // Byte 6+7
+ // Two bytes padding so we don't need to change the report at all
+ ReportSize(8)
+ ReportCount(2)
+ Input(Const) // Byte 6+7
+ )
+ )
+};
+
+static const __u8 fixed_rdesc_vendor[] = {
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionApplication(
+ // Byte 0
+ // We leave the pen on the vendor report ID
+ ReportId(VENDOR_REPORT_ID)
+ Usage_Dig_Pen
+ CollectionPhysical(
+ // Byte 1 are the buttons
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportSize(1)
+ Usage_Dig_TipSwitch
+ Usage_Dig_BarrelSwitch
+ Usage_Dig_SecondaryBarrelSwitch
+ ReportCount(3)
+ Input(Var|Abs)
+ ReportCount(4) // Padding
+ Input(Const)
+ Usage_Dig_InRange
+ ReportCount(1)
+ Input(Var|Abs)
+ ReportSize(16)
+ ReportCount(1)
+ PushPop(
+ UsagePage_GenericDesktop
+ Unit(cm)
+ UnitExponent(-1)
+ // Note: reported logical range differs
+ // from the pen report ID for x and y
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(32000)
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(160)
+ // Bytes 2/3 in report
+ Usage_GD_X
+ Input(Var|Abs)
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(20000)
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(100)
+ // Bytes 4/5 in report
+ Usage_GD_Y
+ Input(Var|Abs)
+ )
+ // Bytes 6/7 in report
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(8192)
+ Usage_Dig_TipPressure
+ Input(Var|Abs)
+ )
+ )
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ // Byte 0
+ ReportId(PAD_REPORT_ID)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ // Byte 1 are the buttons
+ 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
+ ReportCount(2)
+ ReportSize(8)
+ Input(Var|Abs)
+ // Bytes 4 and 5 are the button state
+ UsagePage_Button
+ UsageMinimum_i8(0x1)
+ UsageMaximum_i8(0xa)
+ LogicalMinimum_i8(0x0)
+ LogicalMaximum_i8(0x1)
+ ReportCount(10)
+ ReportSize(1)
+ Input(Var|Abs)
+ Usage_i8(0x31) // maps to BTN_SOUTH
+ ReportCount(1)
+ Input(Var|Abs)
+ ReportCount(5)
+ Input(Const)
+ // Byte 6 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(VENDOR_REPORT_LENGTH)
+ )
+};
+
+static const __u8 disabled_rdesc_pen[] = {
+ FixedSizeVendorReport(PEN_REPORT_LENGTH)
+};
+
+static const __u8 disabled_rdesc_pad[] = {
+ FixedSizeVendorReport(PAD_REPORT_LENGTH)
+};
+
+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 */, HID_MAX_DESCRIPTOR_SIZE /* size */);
+ __s32 rdesc_size = hctx->size;
+ __u8 have_fw_id;
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* If we have a firmware ID and it matches our expected prefix, we
+ * disable the default pad/pen nodes. They won't send events
+ * but cause duplicate devices.
+ */
+ have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,
+ EXPECTED_FIRMWARE_ID,
+ sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;
+ if (rdesc_size == PAD_REPORT_DESCRIPTOR_LENGTH) {
+ if (have_fw_id) {
+ __builtin_memcpy(data, disabled_rdesc_pad, sizeof(disabled_rdesc_pad));
+ return sizeof(disabled_rdesc_pad);
+ }
+
+ __builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
+ return sizeof(fixed_rdesc_pad);
+ }
+ if (rdesc_size == PEN_REPORT_DESCRIPTOR_LENGTH) {
+ if (have_fw_id) {
+ __builtin_memcpy(data, disabled_rdesc_pen, sizeof(disabled_rdesc_pen));
+ return sizeof(disabled_rdesc_pen);
+ }
+
+ __builtin_memcpy(data, fixed_rdesc_pen, sizeof(fixed_rdesc_pen));
+ return sizeof(fixed_rdesc_pen);
+ }
+ /* Always fix the vendor mode so the tablet will work even if nothing sets
+ * the udev property (e.g. huion-switcher run manually)
+ */
+ if (rdesc_size == VENDOR_REPORT_DESCRIPTOR_LENGTH) {
+ __builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
+ return sizeof(fixed_rdesc_vendor);
+ }
+ return 0;
+}
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(inspiroy_2_fix_events, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* Only sent if tablet is in default mode */
+ if (data[0] == PAD_REPORT_ID) {
+ /* Nicely enough, this device only supports one button down at a time so
+ * the reports are easy to match. Buttons numbered from the top
+ * Button released: 03 00 00 00 00 00 00 00
+ * Button 1: 03 00 05 00 00 00 00 00 -> b
+ * Button 2: 03 07 11 00 00 00 00 00 -> Ctrl Shift N
+ * Button 3: 03 00 08 00 00 00 00 00 -> e
+ * Button 4: 03 00 0c 00 00 00 00 00 -> i
+ * Button 5: 03 00 2c 00 00 00 00 00 -> space
+ * Button 6: 03 01 08 00 00 00 00 00 -> Ctrl E
+ * Button 7: 03 01 16 00 00 00 00 00 -> Ctrl S
+ * Button 8: 03 05 1d 00 00 00 00 00 -> Ctrl Alt Z
+ *
+ * Wheel down: 03 01 2d 00 00 00 00 00 -> Ctrl -
+ * Wheel up: 03 01 2e 00 00 00 00 00 -> Ctrl =
+ */
+ __u8 button = 0;
+ __u8 wheel = 0;
+
+ switch (data[1] << 8 | data[2]) {
+ case 0x0000:
+ break;
+ case 0x0005:
+ button = 1;
+ break;
+ case 0x0711:
+ button = 2;
+ break;
+ case 0x0008:
+ button = 3;
+ break;
+ case 0x000c:
+ button = 4;
+ break;
+ case 0x002c:
+ button = 5;
+ break;
+ case 0x0108:
+ button = 6;
+ break;
+ case 0x0116:
+ button = 7;
+ break;
+ case 0x051d:
+ button = 8;
+ break;
+ case 0x012d:
+ wheel = -1;
+ break;
+ case 0x012e:
+ wheel = 1;
+ break;
+ }
+
+ __u8 report[6] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, wheel, button};
+
+ __builtin_memcpy(data, report, sizeof(report));
+ return sizeof(report);
+ }
+
+ /* Nothing to do for the PEN_REPORT_ID, it's already mapped */
+
+ /* Only sent if tablet is in raw mode */
+ if (data[0] == VENDOR_REPORT_ID) {
+ /* Pad reports */
+ if (data[1] & 0x20) {
+ /* See fixed_rdesc_pad */
+ struct pad_report {
+ __u8 report_id;
+ __u8 btn_stylus;
+ __u8 x;
+ __u8 y;
+ __u16 buttons;
+ __u8 wheel;
+ } __attribute__((packed)) *pad_report;
+ __u8 wheel = 0;
+
+ /* Wheel report */
+ if (data[1] == 0xf1) {
+ if (data[5] == 2)
+ wheel = 0xff;
+ else
+ wheel = data[5];
+ } else {
+ /* data[4] and data[5] are the buttons, mapped correctly */
+ last_button_state = data[4] | (data[5] << 8);
+ wheel = 0; // wheel
+ }
+
+ pad_report = (struct pad_report *)data;
+
+ pad_report->report_id = PAD_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;
+
+ return sizeof(struct pad_report);
+ }
+
+ /* Pen reports need nothing done */
+ }
+
+ return 0;
+}
+
+HID_BPF_OPS(inspiroy_2) = {
+ .hid_device_event = (void *)inspiroy_2_fix_events,
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ switch (ctx->rdesc_size) {
+ case PAD_REPORT_DESCRIPTOR_LENGTH:
+ case PEN_REPORT_DESCRIPTOR_LENGTH:
+ case VENDOR_REPORT_DESCRIPTOR_LENGTH:
+ ctx->retval = 0;
+ break;
+ default:
+ ctx->retval = -EINVAL;
+ }
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 02/10] HID: bpf: add support for Huion Kamvas 13 (Gen 3) (model GS1333)
2025-11-18 17:16 [PATCH 00/10] HID: bpf: sync up with current udev-hid-bpf programs Benjamin Tissoires
2025-11-18 17:16 ` [PATCH 01/10] HID: bpf: Add support for the Inspiroy 2M Benjamin Tissoires
@ 2025-11-18 17:16 ` Benjamin Tissoires
2025-11-18 17:16 ` [PATCH 03/10] HID: bpf: support for Huion Kamvas 16 Gen 3 Benjamin Tissoires
` (8 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
To: Jiri Kosina
Cc: linux-kernel, linux-input, Benjamin Tissoires, Nicholas LaPointe
This assumes that the tablet has been switched into vendor mode (by
using huion-switcher[1], for example) and is sending events using
Huion's proprietary data format.
This has been tested using the PW600L pen, which does not have an
eraser. There is no expectation that a pen with an eraser will work at
this time.
[1] https://github.com/whot/huion-switcher
Signed-off-by: Nicholas LaPointe <nicholaslapointe8@gmail.com>
Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/162
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
drivers/hid/bpf/progs/Huion__Kamvas13Gen3.bpf.c | 1395 +++++++++++++++++++++++
1 file changed, 1395 insertions(+)
diff --git a/drivers/hid/bpf/progs/Huion__Kamvas13Gen3.bpf.c b/drivers/hid/bpf/progs/Huion__Kamvas13Gen3.bpf.c
new file mode 100644
index 0000000000000000000000000000000000000000..b63f9a48ea450a66fa202c6bcad09278dcfef3a8
--- /dev/null
+++ b/drivers/hid/bpf/progs/Huion__Kamvas13Gen3.bpf.c
@@ -0,0 +1,1395 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2025 Nicholas LaPointe
+ */
+
+#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_KAMVAS13_GEN3 0x2008
+
+#define VENDOR_DESCRIPTOR_LENGTH 36
+#define TABLET_DESCRIPTOR_LENGTH 368
+#define WHEEL_DESCRIPTOR_LENGTH 108
+
+#define VENDOR_REPORT_ID 8
+#define VENDOR_REPORT_LENGTH 14
+
+#define VENDOR_REPORT_SUBTYPE_PEN 0x08
+#define VENDOR_REPORT_SUBTYPE_PEN_OUT 0x00
+#define VENDOR_REPORT_SUBTYPE_BUTTONS 0x0e
+#define VENDOR_REPORT_SUBTYPE_WHEELS 0x0f
+
+/* For the reports that we create ourselves */
+#define CUSTOM_PAD_REPORT_ID 9
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_ANY, VID_HUION, PID_KAMVAS13_GEN3),
+);
+
+
+/*
+ * This tablet can send reports using one of two different data formats,
+ * depending on what "mode" the tablet is in.
+ *
+ * By default, the tablet will send reports that can be decoded using its
+ * included HID descriptors (descriptors 1 and 2, shown below).
+ * This mode will be called "firmware mode" throughout this file.
+ *
+ * The HID descriptor that describes pen events in firmware mode (descriptor 1)
+ * has multiple bugs:
+ * * "Secondary Tip Switch" instead of "Secondary Barrel Switch"
+ * * "Invert" instead of (or potentially shared with) third barrel button
+ * * Specified tablet area of 2048 in³ instead of 293.8 x 165.2mm
+ * * Specified tilt range of -90 to +90 instead of -60 to +60
+ *
+ * While these can be easily patched up by editing the descriptor, a larger
+ * problem with the firmware mode exists: it is impossible to tell which of the
+ * two wheels are being rotated (or having their central button pressed).
+ *
+ *
+ * By using a tool such as huion-switcher (https://github.com/whot/huion-switcher),
+ * the tablet can be made to send reports using a proprietary format that is not
+ * adequately described by its relevant descriptor (descriptor 0, shown below).
+ * This mode will be called "vendor mode" throughout this file.
+ *
+ * The reports sent while in vendor mode allow for proper decoding of the wheels.
+ *
+ * For simplicity and maximum functionality, this BPF focuses strictly on
+ * enabling one to make use of the vendor mode.
+ */
+
+/*
+ * DESCRIPTORS
+ * DESCRIPTOR 0
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page FF00) 0
+ * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 3
+ * # 0xa1, 0x01, // Collection (Application) 5
+ * # ┅ 0x85, 0x08, // Report ID (8) 7
+ * # 0x75, 0x68, // Report Size (104) 9
+ * # 0x95, 0x01, // Report Count (1) 11
+ * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 13
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 15
+ * # 0xc0, // End Collection 17
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page FF00) 18
+ * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 21
+ * # 0xa1, 0x01, // Collection (Application) 23
+ * # ┅ 0x85, 0x16, // Report ID (22) 25
+ * # 0x75, 0x08, // Report Size (8) 27
+ * # 0x95, 0x07, // Report Count (7) 29
+ * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 31
+ * # ║ 0xb1, 0x02, // Feature (Data,Var,Abs) 33
+ * # 0xc0, // End Collection 35
+ * R: 36 06 00 ff 09 01 a1 01 85 08 75 68 95 01 09 01 81 02 c0 06 00 ff 09 01 a1 01 85 16 75 08 95 07 09 01 b1 02 c0
+ * N: HUION Huion Tablet_GS1333
+ * I: 3 256c 2008
+ *
+ * DESCRIPTOR 1
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 0
+ * # 0x09, 0x02, // Usage (Pen) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # ┅ 0x85, 0x0a, // Report ID (10) 6
+ * # 0x09, 0x20, // Usage (Stylus) 8
+ * # 0xa1, 0x01, // Collection (Application) 10
+ * # 0x09, 0x42, // Usage (Tip Switch) 12
+ * # 0x09, 0x44, // Usage (Barrel Switch) 14
+ * # 0x09, 0x43, // Usage (Secondary Tip Switch) 16
+ * # 0x09, 0x3c, // Usage (Invert) 18
+ * # 0x09, 0x45, // Usage (Eraser) 20
+ * # 0x15, 0x00, // Logical Minimum (0) 22
+ * # 0x25, 0x01, // Logical Maximum (1) 24
+ * # 0x75, 0x01, // Report Size (1) 26
+ * # 0x95, 0x06, // Report Count (6) 28
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 30
+ * # 0x09, 0x32, // Usage (In Range) 32
+ * # 0x75, 0x01, // Report Size (1) 34
+ * # 0x95, 0x01, // Report Count (1) 36
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 38
+ * # ┇ 0x81, 0x03, // Input (Cnst,Var,Abs) 40
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 42
+ * # 0x09, 0x30, // Usage (X) 44
+ * # 0x09, 0x31, // Usage (Y) 46
+ * # 0x55, 0x0d, // Unit Exponent (-3) 48
+ * # 0x65, 0x33, // Unit (EnglishLinear: in³) 50
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 52
+ * # 0x35, 0x00, // Physical Minimum (0) 55
+ * # 0x46, 0x00, 0x08, // Physical Maximum (2048) 57
+ * # 0x75, 0x10, // Report Size (16) 60
+ * # 0x95, 0x02, // Report Count (2) 62
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 64
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 66
+ * # 0x09, 0x30, // Usage (Tip Pressure) 68
+ * # 0x26, 0xff, 0x3f, // Logical Maximum (16383) 70
+ * # 0x75, 0x10, // Report Size (16) 73
+ * # 0x95, 0x01, // Report Count (1) 75
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 77
+ * # 0x09, 0x3d, // Usage (X Tilt) 79
+ * # 0x09, 0x3e, // Usage (Y Tilt) 81
+ * # 0x15, 0xa6, // Logical Minimum (-90) 83
+ * # 0x25, 0x5a, // Logical Maximum (90) 85
+ * # 0x75, 0x08, // Report Size (8) 87
+ * # 0x95, 0x02, // Report Count (2) 89
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 91
+ * # 0xc0, // End Collection 93
+ * # 0xc0, // End Collection 94
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 95
+ * # 0x09, 0x04, // Usage (Touch Screen) 97
+ * # 0xa1, 0x01, // Collection (Application) 99
+ * # ┅ 0x85, 0x04, // Report ID (4) 101
+ * # 0x09, 0x22, // Usage (Finger) 103
+ * # 0xa1, 0x02, // Collection (Logical) 105
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 107
+ * # 0x95, 0x01, // Report Count (1) 109
+ * # 0x75, 0x06, // Report Size (6) 111
+ * # 0x09, 0x51, // Usage (Contact Identifier) 113
+ * # 0x15, 0x00, // Logical Minimum (0) 115
+ * # 0x25, 0x3f, // Logical Maximum (63) 117
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 119
+ * # 0x09, 0x42, // Usage (Tip Switch) 121
+ * # 0x25, 0x01, // Logical Maximum (1) 123
+ * # 0x75, 0x01, // Report Size (1) 125
+ * # 0x95, 0x01, // Report Count (1) 127
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 129
+ * # 0x75, 0x01, // Report Size (1) 131
+ * # 0x95, 0x01, // Report Count (1) 133
+ * # ┇ 0x81, 0x03, // Input (Cnst,Var,Abs) 135
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 137
+ * # 0x75, 0x10, // Report Size (16) 139
+ * # 0x55, 0x0e, // Unit Exponent (-2) 141
+ * # 0x65, 0x11, // Unit (SILinear: cm) 143
+ * # 0x09, 0x30, // Usage (X) 145
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 147
+ * # 0x35, 0x00, // Physical Minimum (0) 150
+ * # 0x46, 0x15, 0x0c, // Physical Maximum (3093) 152
+ * # ┇ 0x81, 0x42, // Input (Data,Var,Abs,Null) 155
+ * # 0x09, 0x31, // Usage (Y) 157
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 159
+ * # 0x46, 0xcb, 0x06, // Physical Maximum (1739) 162
+ * # ┇ 0x81, 0x42, // Input (Data,Var,Abs,Null) 165
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 167
+ * # 0x09, 0x30, // Usage (Tip Pressure) 169
+ * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 171
+ * # 0x75, 0x10, // Report Size (16) 174
+ * # 0x95, 0x01, // Report Count (1) 176
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 178
+ * # 0xc0, // End Collection 180
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 181
+ * # 0x09, 0x22, // Usage (Finger) 183
+ * # 0xa1, 0x02, // Collection (Logical) 185
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 187
+ * # 0x95, 0x01, // Report Count (1) 189
+ * # 0x75, 0x06, // Report Size (6) 191
+ * # 0x09, 0x51, // Usage (Contact Identifier) 193
+ * # 0x15, 0x00, // Logical Minimum (0) 195
+ * # 0x25, 0x3f, // Logical Maximum (63) 197
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 199
+ * # 0x09, 0x42, // Usage (Tip Switch) 201
+ * # 0x25, 0x01, // Logical Maximum (1) 203
+ * # 0x75, 0x01, // Report Size (1) 205
+ * # 0x95, 0x01, // Report Count (1) 207
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 209
+ * # 0x75, 0x01, // Report Size (1) 211
+ * # 0x95, 0x01, // Report Count (1) 213
+ * # ┇ 0x81, 0x03, // Input (Cnst,Var,Abs) 215
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 217
+ * # 0x75, 0x10, // Report Size (16) 219
+ * # 0x55, 0x0e, // Unit Exponent (-2) 221
+ * # 0x65, 0x11, // Unit (SILinear: cm) 223
+ * # 0x09, 0x30, // Usage (X) 225
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 227
+ * # 0x35, 0x00, // Physical Minimum (0) 230
+ * # 0x46, 0x15, 0x0c, // Physical Maximum (3093) 232
+ * # ┇ 0x81, 0x42, // Input (Data,Var,Abs,Null) 235
+ * # 0x09, 0x31, // Usage (Y) 237
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 239
+ * # 0x46, 0xcb, 0x06, // Physical Maximum (1739) 242
+ * # ┇ 0x81, 0x42, // Input (Data,Var,Abs,Null) 245
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 247
+ * # 0x09, 0x30, // Usage (Tip Pressure) 249
+ * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 251
+ * # 0x75, 0x10, // Report Size (16) 254
+ * # 0x95, 0x01, // Report Count (1) 256
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 258
+ * # 0xc0, // End Collection 260
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 261
+ * # 0x09, 0x56, // Usage (Scan Time) 263
+ * # 0x55, 0x00, // Unit Exponent (0) 265
+ * # 0x65, 0x00, // Unit (None) 267
+ * # 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) 269
+ * # 0x95, 0x01, // Report Count (1) 274
+ * # 0x75, 0x20, // Report Size (32) 276
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 278
+ * # 0x09, 0x54, // Usage (Contact Count) 280
+ * # 0x25, 0x7f, // Logical Maximum (127) 282
+ * # 0x95, 0x01, // Report Count (1) 284
+ * # 0x75, 0x08, // Report Size (8) 286
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 288
+ * # 0x75, 0x08, // Report Size (8) 290
+ * # 0x95, 0x08, // Report Count (8) 292
+ * # ┇ 0x81, 0x03, // Input (Cnst,Var,Abs) 294
+ * # ┅ 0x85, 0x05, // Report ID (5) 296
+ * # 0x09, 0x55, // Usage (Contact Count Maximum) 298
+ * # 0x25, 0x0a, // Logical Maximum (10) 300
+ * # 0x75, 0x08, // Report Size (8) 302
+ * # 0x95, 0x01, // Report Count (1) 304
+ * # ║ 0xb1, 0x02, // Feature (Data,Var,Abs) 306
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page FF00) 308
+ * # 0x09, 0xc5, // Usage (Vendor Usage 0xc5) 311
+ * # ┅ 0x85, 0x06, // Report ID (6) 313
+ * # 0x15, 0x00, // Logical Minimum (0) 315
+ * # 0x26, 0xff, 0x00, // Logical Maximum (255) 317
+ * # 0x75, 0x08, // Report Size (8) 320
+ * # 0x96, 0x00, 0x01, // Report Count (256) 322
+ * # ║ 0xb1, 0x02, // Feature (Data,Var,Abs) 325
+ * # 0xc0, // End Collection 327
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 328
+ * # 0x09, 0x06, // Usage (Keyboard) 330
+ * # 0xa1, 0x01, // Collection (Application) 332
+ * # ┅ 0x85, 0x03, // Report ID (3) 334
+ * # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 336
+ * # 0x19, 0xe0, // UsageMinimum (224) 338
+ * # 0x29, 0xe7, // UsageMaximum (231) 340
+ * # 0x15, 0x00, // Logical Minimum (0) 342
+ * # 0x25, 0x01, // Logical Maximum (1) 344
+ * # 0x75, 0x01, // Report Size (1) 346
+ * # 0x95, 0x08, // Report Count (8) 348
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 350
+ * # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 352
+ * # 0x19, 0x00, // UsageMinimum (0) 354
+ * # 0x29, 0xff, // UsageMaximum (255) 356
+ * # 0x26, 0xff, 0x00, // Logical Maximum (255) 358
+ * # 0x75, 0x08, // Report Size (8) 361
+ * # 0x95, 0x06, // Report Count (6) 363
+ * # ┇ 0x81, 0x00, // Input (Data,Arr,Abs) 365
+ * # 0xc0, // End Collection 367
+ * R: 368 05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 43 09 3c 09 45 15 00 25 01 75 01 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff 7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 3f 75 10 95 01 81 02 09 3d 09 3e 15 a6 25 5a 75 08 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 04 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 ff 7f 35 00 46 15 0c 81 42 09 31 26 ff 7f 46 cb 06 81 42 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 ff 7f 35 00 46 15 0c 81 42 09 31 26 ff 7f 46 cb 06 81 42 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 75 08 95 08 81 03 85 05 09 55 25 0a 75 08 95 01 b1 02 06 00 ff 09 c5 85 06 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 06 a1 01 85 03 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 05 07 19 00 29 ff 26 ff 00 75 08 95 06 81 00 c0
+ * N: HUION Huion Tablet_GS1333
+ * I: 3 256c 2008
+ *
+ * DESCRIPTOR 2
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 0
+ * # 0x09, 0x0e, // Usage (System Multi-Axis Controller) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # ┅ 0x85, 0x11, // Report ID (17) 6
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 8
+ * # 0x09, 0x21, // Usage (Puck) 10
+ * # 0xa1, 0x02, // Collection (Logical) 12
+ * # 0x15, 0x00, // Logical Minimum (0) 14
+ * # 0x25, 0x01, // Logical Maximum (1) 16
+ * # 0x75, 0x01, // Report Size (1) 18
+ * # 0x95, 0x01, // Report Count (1) 20
+ * # 0xa1, 0x00, // Collection (Physical) 22
+ * # 0x05, 0x09, // Usage Page (Button) 24
+ * # 0x09, 0x01, // Usage (Button 1) 26
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 28
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 30
+ * # 0x09, 0x33, // Usage (Touch) 32
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 34
+ * # 0x95, 0x06, // Report Count (6) 36
+ * # ┇ 0x81, 0x03, // Input (Cnst,Var,Abs) 38
+ * # 0xa1, 0x02, // Collection (Logical) 40
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 42
+ * # 0x09, 0x37, // Usage (Dial) 44
+ * # 0x16, 0x00, 0x80, // Logical Minimum (-32768) 46
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 49
+ * # 0x75, 0x10, // Report Size (16) 52
+ * # 0x95, 0x01, // Report Count (1) 54
+ * # ┇ 0x81, 0x06, // Input (Data,Var,Rel) 56
+ * # 0x35, 0x00, // Physical Minimum (0) 58
+ * # 0x46, 0x10, 0x0e, // Physical Maximum (3600) 60
+ * # 0x15, 0x00, // Logical Minimum (0) 63
+ * # 0x26, 0x10, 0x0e, // Logical Maximum (3600) 65
+ * # 0x09, 0x48, // Usage (Resolution Multiplier) 68
+ * # ║ 0xb1, 0x02, // Feature (Data,Var,Abs) 70
+ * # 0x45, 0x00, // Physical Maximum (0) 72
+ * # 0xc0, // End Collection 74
+ * # 0x75, 0x08, // Report Size (8) 75
+ * # 0x95, 0x01, // Report Count (1) 77
+ * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 79
+ * # 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
+ * # 0xc0, // End Collection 105
+ * # 0xc0, // End Collection 106
+ * # 0xc0, // End Collection 107
+ * R: 108 05 01 09 0e a1 01 85 11 05 0d 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: HUION Huion Tablet_GS1333
+ * I: 3 256c 2008
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * VENDOR MODE
+ * HUION_FIRMWARE_ID="HUION_M22c_240606"
+ * HUION_MAGIC_BYTES="140388e500108100ff3fd8130307008008004010"
+ *
+ * MAGIC BYTES
+ * [LogicalMaximum, X] [LogicalMaximum, Y] [LogicalMaximum, Pressure] [ LPI]
+ * 14 03 [ 88 e5] 00 [ 10 81] 00 [ ff 3f] [d8 13] 03 07 00 80 08 00 40 10
+ *
+ *
+ * HIDRAW 0
+ * DESCRIPTIONS
+ * report_subtype = (data[1] >> 4) & 0x0f
+ *
+ * REPORT SUBTYPES
+ * 0x0e Buttons
+ * (data[4] & 0x01) button 1
+ * (data[4] & 0x02) button 2
+ * (data[4] & 0x04) button 3
+ * (data[4] & 0x08) button 4
+ * (data[4] & 0x10) button 5
+ * (data[4] & 0x20) button 6 (top wheel button)
+ * (data[4] & 0x40) button 7 (bottom wheel button)
+ *
+ * All tablet buttons release with the same report:
+ * 08 e0 01 01 00 00 00 00 00 00 00 00 00 00
+ *
+ * Despite data[4] looking like a bit field, only one button
+ * can be unambiguously tracked at a time.
+ * (See NOTES ON SIMULTANEOUS BUTTON HOLDS at the end of this
+ * comment for examples of the confusion this can create.)
+ *
+ * All buttons, with the exceptions of 6 and 7, will repeatedly
+ * report a press event approximately every 225ms while held.
+ *
+ * 0x0f Wheels
+ * data[3] == 1: top wheel
+ * data[3] == 2: bottom wheel
+ * data[5] == 1: clockwise
+ * data[5] == 2: counter-clockwise
+ *
+ * 0x08/0x00 Pen
+ * report_subtype == 0x08: in-range
+ * report_subtype == 0x00: out-of-range
+ * For clarity, this is also equivalent to:
+ * (data[1] & 0x80) in-range
+ *
+ * Switches
+ * (data[1] & 0x01) tip switch
+ * (data[1] & 0x02) barrel switch
+ * (data[1] & 0x04) secondary barrel switch
+ * (data[1] & 0x08) third barrel switch
+ *
+ * Unfortunately, I don't have a pen with an eraser, so I can't
+ * confirm where the invert and eraser bits reside.
+ * If we guess using the definitions from HID descriptor 1,
+ * then they might be...
+ * (data[1] & 0x08) invert (conflicts with third barrel switch)
+ * (data[1] & 0x10) eraser
+ *
+ * data[2], data[3] X (little-endian, maximum 0xe588)
+ *
+ * data[4], data[5] Y (little-endian, maximum 0x8110)
+ *
+ * data[6], data[7] Pressure (little-endian, maximum 0x3fff)
+ *
+ * data[10] X tilt (signed, -60 to +60)
+ * data[11] Y tilt (signed, -60 to +60, inverted)
+ *
+ *
+ * EXAMPLE REPORTS
+ * Top wheel button, press, hold, then release
+ * E: 000000.000040 14 08 e0 01 01 20 00 00 00 00 00 00 00 00 00
+ * E: 000001.531559 14 08 e0 01 01 00 00 00 00 00 00 00 00 00 00
+ *
+ * Bottom wheel button, press, hold, then release
+ * E: 000002.787603 14 08 e0 01 01 40 00 00 00 00 00 00 00 00 00
+ * E: 000004.215609 14 08 e0 01 01 00 00 00 00 00 00 00 00 00 00
+ *
+ *
+ * Top wheel rotation, one detent CW
+ * E: 000194.003899 14 08 f1 01 01 00 01 00 00 00 00 00 00 00 00
+ *
+ * Top wheel rotation, one detent CCW
+ * E: 000194.997812 14 08 f1 01 01 00 02 00 00 00 00 00 00 00 00
+ *
+ * Bottom wheel rotation, one detent CW
+ * E: 000196.693840 14 08 f1 01 02 00 01 00 00 00 00 00 00 00 00
+ *
+ * Bottom wheel rotation, one detent CCW
+ * E: 000197.757895 14 08 f1 01 02 00 02 00 00 00 00 00 00 00 00
+ *
+ *
+ * Button 1, press, hold, then release
+ * E: 000000.000149 14 08 e0 01 01 01 00 00 00 00 00 00 00 00 00 < press
+ * E: 000000.447598 14 08 e0 01 01 01 00 00 00 00 00 00 00 00 00 < starting to auto-repeat, every ~225ms
+ * E: 000000.673586 14 08 e0 01 01 01 00 00 00 00 00 00 00 00 00
+ * E: 000000.900582 14 08 e0 01 01 01 00 00 00 00 00 00 00 00 00
+ * E: 000001.126703 14 08 e0 01 01 01 00 00 00 00 00 00 00 00 00
+ * E: 000001.347706 14 08 e0 01 01 01 00 00 00 00 00 00 00 00 00
+ * E: 000001.533721 14 08 e0 01 01 00 00 00 00 00 00 00 00 00 00 < release
+ *
+ * Button 2, press, hold, then release
+ * E: 000003.304735 14 08 e0 01 01 02 00 00 00 00 00 00 00 00 00 < press
+ * E: 000003.746743 14 08 e0 01 01 02 00 00 00 00 00 00 00 00 00 < starting to auto-repeat, every ~225ms
+ * E: 000003.973741 14 08 e0 01 01 02 00 00 00 00 00 00 00 00 00
+ * E: 000004.199832 14 08 e0 01 01 02 00 00 00 00 00 00 00 00 00
+ * E: 000004.426732 14 08 e0 01 01 02 00 00 00 00 00 00 00 00 00
+ * E: 000004.647738 14 08 e0 01 01 02 00 00 00 00 00 00 00 00 00
+ * E: 000004.874733 14 08 e0 01 01 02 00 00 00 00 00 00 00 00 00
+ * E: 000004.930713 14 08 e0 01 01 00 00 00 00 00 00 00 00 00 00 < release
+ *
+ * Button 3, press, hold, then release
+ * E: 000006.650346 14 08 e0 01 01 04 00 00 00 00 00 00 00 00 00 < press
+ * E: 000007.051782 14 08 e0 01 01 04 00 00 00 00 00 00 00 00 00 < starting to auto-repeat, every ~225ms
+ * E: 000007.273738 14 08 e0 01 01 04 00 00 00 00 00 00 00 00 00
+ * E: 000007.499794 14 08 e0 01 01 04 00 00 00 00 00 00 00 00 00
+ * E: 000007.726725 14 08 e0 01 01 04 00 00 00 00 00 00 00 00 00
+ * E: 000007.947765 14 08 e0 01 01 04 00 00 00 00 00 00 00 00 00
+ * E: 000008.174755 14 08 e0 01 01 04 00 00 00 00 00 00 00 00 00
+ * E: 000008.328786 14 08 e0 01 01 00 00 00 00 00 00 00 00 00 00 < release
+ *
+ * Button 4, press, hold, then release
+ * E: 000009.893820 14 08 e0 01 01 08 00 00 00 00 00 00 00 00 00 < press
+ * E: 000010.274781 14 08 e0 01 01 08 00 00 00 00 00 00 00 00 00 < starting to auto-repeat, every ~225ms
+ * E: 000010.500931 14 08 e0 01 01 08 00 00 00 00 00 00 00 00 00
+ * E: 000010.722777 14 08 e0 01 01 08 00 00 00 00 00 00 00 00 00
+ * E: 000010.948778 14 08 e0 01 01 08 00 00 00 00 00 00 00 00 00
+ * E: 000011.175799 14 08 e0 01 01 08 00 00 00 00 00 00 00 00 00
+ * E: 000011.401153 14 08 e0 01 01 08 00 00 00 00 00 00 00 00 00
+ * E: 000011.432114 14 08 e0 01 01 00 00 00 00 00 00 00 00 00 00 < release
+ *
+ * Button 5, press, hold, then release
+ * E: 000013.007778 14 08 e0 01 01 10 00 00 00 00 00 00 00 00 00 < press
+ * E: 000013.424741 14 08 e0 01 01 10 00 00 00 00 00 00 00 00 00 < starting to auto-repeat, every ~225ms
+ * E: 000013.651715 14 08 e0 01 01 10 00 00 00 00 00 00 00 00 00
+ * E: 000013.872763 14 08 e0 01 01 10 00 00 00 00 00 00 00 00 00
+ * E: 000014.099789 14 08 e0 01 01 10 00 00 00 00 00 00 00 00 00
+ * E: 000014.325734 14 08 e0 01 01 10 00 00 00 00 00 00 00 00 00
+ * E: 000014.438080 14 08 e0 01 01 00 00 00 00 00 00 00 00 00 00 < release
+ *
+ *
+ * Pen: Top-left, then out of range
+ * E: 000368.572184 14 08 80 00 00 00 00 00 00 00 00 fb ed 03 00
+ * E: 000368.573030 14 08 00 00 00 00 00 00 00 00 00 fb ed 03 00
+ *
+ * Pen: Bottom-right, then out of range
+ * E: 000544.433185 14 08 80 88 e5 10 81 00 00 00 00 00 00 03 00
+ * E: 000544.434183 14 08 00 88 e5 10 81 00 00 00 00 00 00 03 00
+ *
+ * Pen: Max Y tilt (tip of pen points down)
+ * E: 000002.231927 14 08 80 f5 5d 6c 36 00 00 00 00 09 3c 03 00
+ *
+ * Pen: Min Y Tilt (tip of pen points up)
+ * E: 000657.593338 14 08 80 5f 69 fa 2c 00 00 00 00 fe c4 03 00
+ *
+ * Pen: Max X tilt (tip of pen points left)
+ * E: 000742.246503 14 08 80 2a 4f c4 38 00 00 00 00 3c ed 03 00
+ *
+ * Pen: Min X Tilt (tip of pen points right)
+ * E: 000776.404446 14 08 00 18 85 7c 3b 00 00 00 00 c4 ed 03 00
+ *
+ * Pen: Tip switch, max pressure, then low pressure
+ * E: 001138.935675 14 08 81 d2 66 04 40 ff 3f 00 00 00 08 03 00
+ *
+ * E: 001142.403715 14 08 81 9d 69 47 3e 82 04 00 00 00 07 03 00
+ *
+ * Pen: Barrel switch
+ * E: 001210.645652 14 08 82 0d 72 ea 2b 00 00 00 00 db c4 03 00
+ *
+ * Pen: Secondary barrel switch
+ * E: 001211.519729 14 08 84 2c 71 51 2b 00 00 00 00 da c4 03 00
+ *
+ * Pen: Third switch
+ * E: 001212.443722 14 08 88 1d 72 df 2b 00 00 00 00 dc c4 03 00
+ *
+ *
+ * HIDRAW 1
+ * No reports
+ *
+ *
+ * HIDRAW 2
+ * No reports
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * FIRMWARE MODE
+ * HIDRAW 0
+ * No reports
+ *
+ *
+ * HIDRAW 1
+ * EXAMPLE REPORTS
+ * Top wheel button, *release*
+ * E: 000067.043739 8 03 00 00 00 00 00 00 00
+ *
+ * Bottom wheel button, *release*
+ * E: 000068.219161 8 03 00 00 00 00 00 00 00
+ *
+ *
+ * Button 1, press, then release
+ * E: 000163.767870 8 03 00 05 00 00 00 00 00
+ * E: 000165.969193 8 03 00 00 00 00 00 00 00
+ *
+ * Button 2, press, then release
+ * E: 000261.728935 8 03 05 11 00 00 00 00 00
+ * E: 000262.956220 8 03 00 00 00 00 00 00 00
+ *
+ * Button 3, press, then release
+ * E: 000289.127881 8 03 01 16 00 00 00 00 00
+ * E: 000290.014594 8 03 00 00 00 00 00 00 00
+ *
+ * Button 4, press, then release
+ * E: 000303.025839 8 03 00 2c 00 00 00 00 00
+ * E: 000303.994479 8 03 00 00 00 00 00 00 00
+ *
+ * Button 5, press, then release
+ * E: 000315.500835 8 03 05 1d 00 00 00 00 00
+ * E: 000316.603274 8 03 00 00 00 00 00 00 00
+ *
+ * BUTTON SUMMARY
+ * 1 E: 000163.767870 8 03 00 05 00 00 00 00 00 Keyboard: B
+ * 2 E: 000261.728935 8 03 05 11 00 00 00 00 00 Keyboard: LCtrl+LAlt N
+ * 3 E: 000289.127881 8 03 01 16 00 00 00 00 00 Keyboard: LCtrl S
+ * 4 E: 000303.025839 8 03 00 2c 00 00 00 00 00 Keyboard: Space
+ * 5 E: 000315.500835 8 03 05 1d 00 00 00 00 00 Keyboard: LCtrl+LAlt
+ *
+ * All buttons (including the wheel buttons) release the same way:
+ * 03 00 00 00 00 00 00 00
+ *
+ *
+ * Pen: Top-left, then out of range
+ * E: 000063.196828 10 0a c0 00 00 00 00 00 00 00 02
+ * E: 000063.197762 10 0a 00 00 00 00 00 00 00 00 02
+ *
+ * Pen: Bottom-right, then out of range
+ * E: 000197.123138 10 0a c0 ff 7f ff 7f 00 00 00 00
+ * E: 000197.124915 10 0a 00 ff 7f ff 7f 00 00 00 00
+ *
+ * Pen: Max Y Tilt (tip of pen points up)
+ * E: 000291.399541 10 0a c0 19 32 0b 58 00 00 00 3c
+ *
+ * Pen: Min Y tilt (tip of pen points down)
+ * E: 000340.888288 10 0a c0 85 40 89 6e 00 00 17 c4
+ *
+ * Pen: Max X tilt (tip of pen points left)
+ * E: 000165.575115 10 0a c0 a7 34 99 42 00 00 3c f4
+ *
+ * Pen: Min X Tilt (tip of pen points right)
+ * E: 000129.507883 10 0a c0 ea 4b 08 40 00 00 c4 1a
+ *
+ * Pen: Tip switch, max pressure, then low pressure
+ * E: 000242.077160 10 0a c1 7e 3c 12 31 ff 3f 03 fd
+ *
+ * E: 000339.139188 10 0a c1 ee 3a 9e 32 b5 00 06 f6
+ *
+ * Pen: Barrel switch
+ * E: 000037.949777 10 0a c2 5c 28 47 2a 00 00 f6 3c
+ *
+ * Pen: Secondary barrel switch
+ * E: 000038.320840 10 0a c4 e4 27 fd 29 00 00 f3 38
+ *
+ * Pen: Third switch
+ * E: 000038.923822 10 0a c8 97 27 5f 29 00 00 f2 33
+ *
+ *
+ * HIDRAW 2
+ * EXAMPLE REPORTS
+ * Either wheel rotation, one detent CW
+ * E: 000097.276573 9 11 00 01 00 00 00 00 00 00
+ *
+ * Either wheel rotation, one detent CCW
+ * E: 000153.416538 9 11 00 ff ff 00 00 00 00 00
+ *
+ * Either wheel rotation, increasing rotation speed CW
+ * (Note that the wheels on my particular tablet may be
+ * damaged, so the false rotation direction changes
+ * that can be observed might not happen on other units.)
+ * E: 000210.514925 9 11 00 01 00 00 00 00 00 00
+ * E: 000210.725718 9 11 00 01 00 00 00 00 00 00
+ * E: 000210.924009 9 11 00 01 00 00 00 00 00 00
+ * E: 000211.205629 9 11 00 01 00 00 00 00 00 00
+ * E: 000211.280521 9 11 00 0b 00 00 00 00 00 00
+ * E: 000211.340121 9 11 00 0e 00 00 00 00 00 00
+ * E: 000211.404018 9 11 00 0d 00 00 00 00 00 00
+ * E: 000211.462060 9 11 00 0e 00 00 00 00 00 00
+ * E: 000211.544886 9 11 00 0a 00 00 00 00 00 00
+ * E: 000211.606130 9 11 00 0d 00 00 00 00 00 00
+ * E: 000211.674560 9 11 00 0c 00 00 00 00 00 00
+ * E: 000211.712039 9 11 00 16 00 00 00 00 00 00
+ * E: 000211.748076 9 11 00 17 00 00 00 00 00 00
+ * E: 000211.786016 9 11 00 17 00 00 00 00 00 00
+ * E: 000211.832960 9 11 00 11 00 00 00 00 00 00
+ * E: 000211.874081 9 11 00 14 00 00 00 00 00 00
+ * E: 000211.925094 9 11 00 10 00 00 00 00 00 00
+ * E: 000211.959048 9 11 00 18 00 00 00 00 00 00
+ * E: 000212.006937 9 11 00 11 00 00 00 00 00 00
+ * E: 000212.050055 9 11 00 13 00 00 00 00 00 00
+ * E: 000212.091947 9 11 00 14 00 00 00 00 00 00
+ * E: 000212.122989 9 11 00 1a 00 00 00 00 00 00
+ * E: 000212.160866 9 11 00 16 00 00 00 00 00 00
+ * E: 000212.194002 9 11 00 19 00 00 00 00 00 00
+ * E: 000212.242249 9 11 00 11 00 00 00 00 00 00
+ * E: 000212.278061 9 11 00 18 00 00 00 00 00 00
+ * E: 000212.328899 9 11 00 10 00 00 00 00 00 00
+ * E: 000212.354005 9 11 00 22 00 00 00 00 00 00
+ * E: 000212.398995 9 11 00 12 00 00 00 00 00 00
+ * E: 000212.432050 9 11 00 19 00 00 00 00 00 00
+ * E: 000212.471164 9 11 00 16 00 00 00 00 00 00
+ * E: 000212.507047 9 11 00 17 00 00 00 00 00 00
+ * E: 000212.540964 9 11 00 19 00 00 00 00 00 00
+ * E: 000212.567942 9 11 00 1f 00 00 00 00 00 00
+ * E: 000212.610007 9 11 00 14 00 00 00 00 00 00
+ * E: 000212.641101 9 11 00 1b 00 00 00 00 00 00
+ * E: 000212.674113 9 11 00 19 00 00 00 00 00 00
+ * E: 000212.674909 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.677062 9 11 00 00 02 00 00 00 00 00
+ * E: 000212.679048 9 11 00 55 01 00 00 00 00 00
+ * E: 000212.682166 9 11 00 55 01 00 00 00 00 00
+ * E: 000212.682788 9 11 00 ff ff 00 00 00 00 00
+ * E: 000212.683899 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.685827 9 11 00 67 fe 00 00 00 00 00
+ * E: 000212.686941 9 11 00 00 08 00 00 00 00 00
+ * E: 000212.727840 9 11 00 14 00 00 00 00 00 00
+ * E: 000212.772884 9 11 00 13 00 00 00 00 00 00
+ * E: 000212.810975 9 11 00 16 00 00 00 00 00 00
+ * E: 000212.811793 9 11 00 00 08 00 00 00 00 00
+ * E: 000212.812683 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.813905 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.814909 9 11 00 00 04 00 00 00 00 00
+ * E: 000212.816942 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.817851 9 11 00 ff ff 00 00 00 00 00
+ * E: 000212.818752 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.819910 9 11 00 56 fd 00 00 00 00 00
+ * E: 000212.820781 9 11 00 ff ff 00 00 00 00 00
+ * E: 000212.821811 9 11 00 00 04 00 00 00 00 00
+ * E: 000212.822920 9 11 00 00 08 00 00 00 00 00
+ * E: 000212.823861 9 11 00 00 02 00 00 00 00 00
+ * E: 000212.828781 9 11 00 ba 00 00 00 00 00 00
+ * E: 000212.874097 9 11 00 12 00 00 00 00 00 00
+ * E: 000212.874872 9 11 00 00 fc 00 00 00 00 00
+ * E: 000212.876136 9 11 00 00 fc 00 00 00 00 00
+ * E: 000212.877036 9 11 00 00 f8 00 00 00 00 00
+ * E: 000212.877993 9 11 00 00 f8 00 00 00 00 00
+ * E: 000212.879748 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.880728 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.881956 9 11 00 00 04 00 00 00 00 00
+ * E: 000212.885065 9 11 00 ff ff 00 00 00 00 00
+ * E: 000212.917060 9 11 00 1a 00 00 00 00 00 00
+ * E: 000212.936458 9 11 00 2d 00 00 00 00 00 00
+ * E: 000212.957860 9 11 00 25 00 00 00 00 00 00
+ * E: 000212.984019 9 11 00 20 00 00 00 00 00 00
+ * E: 000213.017915 9 11 00 19 00 00 00 00 00 00
+ * E: 000213.039973 9 11 00 27 00 00 00 00 00 00
+ * E: 000213.065933 9 11 00 21 00 00 00 00 00 00
+ * E: 000213.085807 9 11 00 28 00 00 00 00 00 00
+ * E: 000213.108888 9 11 00 25 00 00 00 00 00 00
+ * E: 000213.129726 9 11 00 29 00 00 00 00 00 00
+ * E: 000213.172043 9 11 00 14 00 00 00 00 00 00
+ * E: 000213.195873 9 11 00 23 00 00 00 00 00 00
+ * E: 000213.222884 9 11 00 20 00 00 00 00 00 00
+ * E: 000213.243220 9 11 00 2a 00 00 00 00 00 00
+ * E: 000213.266778 9 11 00 24 00 00 00 00 00 00
+ * E: 000213.285951 9 11 00 2b 00 00 00 00 00 00
+ * E: 000213.306045 9 11 00 2a 00 00 00 00 00 00
+ * E: 000213.306796 9 11 00 ff ff 00 00 00 00 00
+ * E: 000213.307755 9 11 00 ff ff 00 00 00 00 00
+ * E: 000213.308820 9 11 00 ff ff 00 00 00 00 00
+ * E: 000213.309971 9 11 00 ff ff 00 00 00 00 00
+ * E: 000213.310980 9 11 00 01 00 00 00 00 00 00
+ * E: 000213.311853 9 11 00 01 00 00 00 00 00 00
+ * E: 000213.312861 9 11 00 aa 02 00 00 00 00 00
+ * E: 000213.313884 9 11 00 00 f8 00 00 00 00 00
+ * E: 000213.315111 9 11 00 ff ff 00 00 00 00 00
+ * E: 000213.315992 9 11 00 01 00 00 00 00 00 00
+ * E: 000213.316955 9 11 00 00 08 00 00 00 00 00
+ * E: 000213.346065 9 11 00 1d 00 00 00 00 00 00
+ * E: 000213.346963 9 11 00 ff ff 00 00 00 00 00
+ * E: 000213.347874 9 11 00 00 08 00 00 00 00 00
+ * E: 000213.348736 9 11 00 00 08 00 00 00 00 00
+ * E: 000213.349795 9 11 00 00 04 00 00 00 00 00
+ * E: 000213.350791 9 11 00 01 00 00 00 00 00 00
+ * E: 000213.351791 9 11 00 01 00 00 00 00 00 00
+ * E: 000213.352729 9 11 00 00 f8 00 00 00 00 00
+ * E: 000213.353811 9 11 00 01 00 00 00 00 00 00
+ * E: 000213.354755 9 11 00 00 f8 00 00 00 00 00
+ * E: 000213.355795 9 11 00 00 f8 00 00 00 00 00
+ * E: 000213.356813 9 11 00 01 00 00 00 00 00 00
+ * E: 000213.357817 9 11 00 00 04 00 00 00 00 00
+ * E: 000213.393838 9 11 00 17 00 00 00 00 00 00
+ * E: 000213.394719 9 11 00 00 04 00 00 00 00 00
+ * E: 000213.395682 9 11 00 00 08 00 00 00 00 00
+ * E: 000213.396679 9 11 00 00 04 00 00 00 00 00
+ * E: 000213.397651 9 11 00 00 fc 00 00 00 00 00
+ * E: 000213.398661 9 11 00 ff ff 00 00 00 00 00
+ * E: 000213.400308 9 11 00 56 fd 00 00 00 00 00
+ * E: 000213.400909 9 11 00 00 f8 00 00 00 00 00
+ * E: 000213.401837 9 11 00 01 00 00 00 00 00 00
+ *
+ * Either wheel rotation, increasing rotation speed CCW
+ * (Note that the wheels on my particular tablet may be
+ * damaged, so the false rotation direction changes
+ * that can be observed might not happen on other units.)
+ * E: 000040.527820 9 11 00 ff ff 00 00 00 00 00
+ * E: 000040.816644 9 11 00 ff ff 00 00 00 00 00
+ * E: 000040.880423 9 11 00 f3 ff 00 00 00 00 00
+ * E: 000040.882570 9 11 00 ff ff 00 00 00 00 00
+ * E: 000040.883381 9 11 00 ff ff 00 00 00 00 00
+ * E: 000040.885463 9 11 00 aa 02 00 00 00 00 00
+ * E: 000040.924106 9 11 00 ea ff 00 00 00 00 00
+ * E: 000041.006155 9 11 00 f6 ff 00 00 00 00 00
+ * E: 000041.085799 9 11 00 f6 ff 00 00 00 00 00
+ * E: 000041.168492 9 11 00 f6 ff 00 00 00 00 00
+ * E: 000041.233453 9 11 00 f3 ff 00 00 00 00 00
+ * E: 000041.296641 9 11 00 f3 ff 00 00 00 00 00
+ * E: 000041.370302 9 11 00 f5 ff 00 00 00 00 00
+ * E: 000041.437410 9 11 00 f4 ff 00 00 00 00 00
+ * E: 000041.474514 9 11 00 e9 ff 00 00 00 00 00
+ * E: 000041.522171 9 11 00 ef ff 00 00 00 00 00
+ * E: 000041.568160 9 11 00 ee ff 00 00 00 00 00
+ * E: 000041.608146 9 11 00 ec ff 00 00 00 00 00
+ * E: 000041.627132 9 11 00 d3 ff 00 00 00 00 00
+ * E: 000041.656151 9 11 00 e3 ff 00 00 00 00 00
+ * E: 000041.682264 9 11 00 e0 ff 00 00 00 00 00
+ * E: 000041.714186 9 11 00 e6 ff 00 00 00 00 00
+ * E: 000041.740339 9 11 00 e0 ff 00 00 00 00 00
+ * E: 000041.772087 9 11 00 e5 ff 00 00 00 00 00
+ * E: 000041.801093 9 11 00 e3 ff 00 00 00 00 00
+ * E: 000041.834051 9 11 00 e7 ff 00 00 00 00 00
+ * E: 000041.863094 9 11 00 e3 ff 00 00 00 00 00
+ * E: 000041.901016 9 11 00 ea ff 00 00 00 00 00
+ * E: 000041.901956 9 11 00 00 04 00 00 00 00 00
+ * E: 000041.902837 9 11 00 00 fe 00 00 00 00 00
+ * E: 000041.903927 9 11 00 01 00 00 00 00 00 00
+ * E: 000041.905066 9 11 00 01 00 00 00 00 00 00
+ * E: 000041.907214 9 11 00 00 fe 00 00 00 00 00
+ * E: 000041.909011 9 11 00 01 00 00 00 00 00 00
+ * E: 000041.909953 9 11 00 01 00 00 00 00 00 00
+ * E: 000041.910917 9 11 00 00 08 00 00 00 00 00
+ * E: 000041.913280 9 11 00 00 fe 00 00 00 00 00
+ * E: 000041.914121 9 11 00 56 fd 00 00 00 00 00
+ * E: 000041.915346 9 11 00 ff ff 00 00 00 00 00
+ * E: 000041.962101 9 11 00 ee ff 00 00 00 00 00
+ * E: 000041.964062 9 11 00 56 fd 00 00 00 00 00
+ * E: 000041.964978 9 11 00 00 fc 00 00 00 00 00
+ * E: 000041.968058 9 11 00 24 01 00 00 00 00 00
+ * E: 000041.968880 9 11 00 56 fd 00 00 00 00 00
+ * E: 000041.970977 9 11 00 aa 02 00 00 00 00 00
+ * E: 000041.971932 9 11 00 ff ff 00 00 00 00 00
+ * E: 000041.972943 9 11 00 01 00 00 00 00 00 00
+ * E: 000041.975291 9 11 00 ff ff 00 00 00 00 00
+ * E: 000041.978274 9 11 00 01 00 00 00 00 00 00
+ * E: 000042.035079 9 11 00 01 00 00 00 00 00 00
+ * E: 000042.041283 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.042057 9 11 00 00 04 00 00 00 00 00
+ * E: 000042.045169 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.051242 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.056099 9 11 00 63 ff 00 00 00 00 00
+ * E: 000042.106329 9 11 00 ef ff 00 00 00 00 00
+ * E: 000042.108601 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.116259 9 11 00 6b 00 00 00 00 00 00
+ * E: 000042.119140 9 11 00 55 01 00 00 00 00 00
+ * E: 000042.126101 9 11 00 88 ff 00 00 00 00 00
+ * E: 000042.158009 9 11 00 e6 ff 00 00 00 00 00
+ * E: 000042.172108 9 11 00 be ff 00 00 00 00 00
+ * E: 000042.207417 9 11 00 e8 ff 00 00 00 00 00
+ * E: 000042.223155 9 11 00 cc ff 00 00 00 00 00
+ * E: 000042.255185 9 11 00 e6 ff 00 00 00 00 00
+ * E: 000042.276280 9 11 00 d7 ff 00 00 00 00 00
+ * E: 000042.302128 9 11 00 e0 ff 00 00 00 00 00
+ * E: 000042.317423 9 11 00 c8 ff 00 00 00 00 00
+ * E: 000042.345226 9 11 00 e1 ff 00 00 00 00 00
+ * E: 000042.357243 9 11 00 bc ff 00 00 00 00 00
+ * E: 000042.381308 9 11 00 dc ff 00 00 00 00 00
+ * E: 000042.383180 9 11 00 dc fe 00 00 00 00 00
+ * E: 000042.412288 9 11 00 e3 ff 00 00 00 00 00
+ * E: 000042.451216 9 11 00 eb ff 00 00 00 00 00
+ * E: 000042.478372 9 11 00 e0 ff 00 00 00 00 00
+ * E: 000042.502116 9 11 00 dd ff 00 00 00 00 00
+ * E: 000042.520105 9 11 00 d3 ff 00 00 00 00 00
+ * E: 000042.540345 9 11 00 d6 ff 00 00 00 00 00
+ * E: 000042.541021 9 11 00 00 08 00 00 00 00 00
+ * E: 000042.542009 9 11 00 01 00 00 00 00 00 00
+ * E: 000042.543045 9 11 00 00 04 00 00 00 00 00
+ * E: 000042.544279 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.545097 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.546074 9 11 00 00 08 00 00 00 00 00
+ * E: 000042.547237 9 11 00 00 08 00 00 00 00 00
+ * E: 000042.548029 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.549304 9 11 00 00 f8 00 00 00 00 00
+ * E: 000042.553123 9 11 00 00 ff 00 00 00 00 00
+ * E: 000042.581186 9 11 00 e1 ff 00 00 00 00 00
+ * E: 000042.582238 9 11 00 00 f8 00 00 00 00 00
+ * E: 000042.583150 9 11 00 00 fc 00 00 00 00 00
+ * E: 000042.584273 9 11 00 00 f8 00 00 00 00 00
+ * E: 000042.585019 9 11 00 00 fc 00 00 00 00 00
+ * E: 000042.586059 9 11 00 01 00 00 00 00 00 00
+ * E: 000042.589012 9 11 00 67 fe 00 00 00 00 00
+ * E: 000042.590066 9 11 00 00 fc 00 00 00 00 00
+ * E: 000042.592916 9 11 00 dc fe 00 00 00 00 00
+ * E: 000042.621124 9 11 00 e1 ff 00 00 00 00 00
+ * E: 000042.622092 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.623069 9 11 00 01 00 00 00 00 00 00
+ * E: 000042.624030 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.625006 9 11 00 00 08 00 00 00 00 00
+ * E: 000042.626068 9 11 00 00 04 00 00 00 00 00
+ * E: 000042.626876 9 11 00 00 08 00 00 00 00 00
+ * E: 000042.628392 9 11 00 00 08 00 00 00 00 00
+ * E: 000042.628918 9 11 00 01 00 00 00 00 00 00
+ * E: 000042.630009 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.631934 9 11 00 00 fe 00 00 00 00 00
+ * E: 000042.656285 9 11 00 dd ff 00 00 00 00 00
+ * E: 000042.659870 9 11 00 cc 00 00 00 00 00 00
+ * E: 000042.666128 9 11 00 9d 00 00 00 00 00 00
+ * E: 000042.672458 9 11 00 80 ff 00 00 00 00 00
+ * E: 000042.696106 9 11 00 dc ff 00 00 00 00 00
+ * E: 000042.705129 9 11 00 61 00 00 00 00 00 00
+ * E: 000042.731303 9 11 00 e0 ff 00 00 00 00 00
+ * E: 000042.741278 9 11 00 ab ff 00 00 00 00 00
+ * E: 000042.788181 9 11 00 ee ff 00 00 00 00 00
+ * E: 000042.810441 9 11 00 db ff 00 00 00 00 00
+ * E: 000042.838073 9 11 00 e1 ff 00 00 00 00 00
+ * E: 000042.852235 9 11 00 c4 ff 00 00 00 00 00
+ * E: 000042.882290 9 11 00 e4 ff 00 00 00 00 00
+ *
+ * Either wheel button, press, hold, then release
+ * E: 000202.084982 9 11 02 00 00 00 00 00 00 00
+ * E: 000202.090172 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.094139 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.099172 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.105055 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.109132 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.114185 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.119212 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.124264 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.130147 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.135138 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.140072 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.145146 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.150157 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.155339 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.160064 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.165026 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.170037 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.175154 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.180044 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.186280 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.191281 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.196106 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.201083 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.206166 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.211084 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.216175 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.221036 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.226271 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.231150 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.235924 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.242046 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.247164 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.252359 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.257295 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.262167 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.267081 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.272175 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.277085 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.282596 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.287078 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.292191 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.298196 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.303004 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.308113 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.313079 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.318243 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.323309 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.328190 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.333050 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.338162 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.343022 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.348113 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.354133 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.359132 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.364053 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.369034 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.374144 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.379027 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.384238 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.389249 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.394049 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.398949 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.404203 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.410098 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.415237 9 11 00 00 00 00 00 00 00 00
+ *
+ *
+ * Top wheel button press and release while holding bottom wheel button
+ * (The reverse action (a bottom wheel button press while holding the top wheel button) is invisible.)
+ * E: 000071.126966 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.133117 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.137481 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.142036 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.147027 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.151988 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.157945 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.163657 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.168240 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.173109 9 11 02 00 00 00 00 00 00 00 < top wheel button press?
+ * E: 000071.178119 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.183046 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.187983 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.192996 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.198341 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.203122 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.208998 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.214037 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.218945 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.223835 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.228987 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.234082 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.239028 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.244307 9 11 00 00 00 00 00 00 00 00 < top wheel button release?
+ * E: 000071.245867 9 11 03 00 00 00 00 00 00 00 < continued hold of bottom button
+ * E: 000071.249959 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.255032 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.259972 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.265409 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.270156 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.275530 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.279975 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.285046 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.290906 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.296146 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.301288 9 11 03 00 00 00 00 00 00 00
+ *
+ * Top wheel button hold while top wheel rotate CCW
+ * (I did not test the other combinations of this)
+ * E: 000022.253144 9 11 03 00 00 00 00 00 00 00
+ * E: 000022.258157 9 11 03 00 00 00 00 00 00 00
+ * E: 000022.262011 9 11 00 ff ff 00 00 00 00 00
+ * E: 000022.264015 9 11 03 00 00 00 00 00 00 00
+ * E: 000022.268976 9 11 03 00 00 00 00 00 00 00
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * NOTES ON SIMULTANEOUS BUTTON HOLDS
+ * (applies to vendor mode only)
+ * Value replacements for ease of reading:
+ * .7 = 0x40 (button 7, a wheel button)
+ * .1 = 0x01 (button 1, a pad button)
+ * rr = 0x00 (no buttons pressed)
+ *
+ * Press 7
+ * Press 1
+ * Release 7
+ * Release 1
+ * B: 000000.000152 42 08 e0 01 01 .7 00 00 00 00 00 00 00 00 00
+ * B: 000000.781784 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000000.869845 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000001.095688 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000001.322635 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000001.543643 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000001.770652 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000001.885659 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00 release of 7
+ * B: 000001.993620 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000002.220671 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000002.446589 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000002.672559 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000002.765183 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00 release of 1
+ *
+ * Press 7
+ * Press 1
+ * Release 1
+ * Release 7
+ * B: 000017.071517 42 08 e0 01 01 .7 00 00 00 00 00 00 00 00 00
+ * B: 000018.270461 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000018.419486 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000018.646438 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000018.872493 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000019.094422 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000019.320488 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000020.360505 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00 release of 1 is not reported until 7 is released, then both are rapidly reported
+ * B: 000020.361091 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00
+ *
+ * Press 1
+ * Press 7
+ * Release 7
+ * Release 1
+ * B: 000031.516315 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000031.922299 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000032.144165 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000032.370262 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000032.396242 42 08 e0 01 01 .7 00 00 00 00 00 00 00 00 00
+ * B: 000032.597270 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000032.818187 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000033.045143 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000033.267535 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00 release of 7
+ * B: 000033.272602 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000033.494246 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000033.721266 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000033.947237 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000034.169294 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000034.183585 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00 release of 1
+ *
+ * Press 1
+ * Press 7
+ * Release 1
+ * Release 7
+ * B: 000056.628429 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000057.046348 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000057.272044 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000057.494434 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000057.601224 42 08 e0 01 01 .7 00 00 00 00 00 00 00 00 00
+ * B: 000057.719262 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000057.946941 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000058.172346 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000058.393994 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000059.434576 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00 release of 1 is not reported until 7 is released, then both are rapidly reported
+ * B: 000059.435857 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00
+ */
+
+
+/* Filled in by udev-hid-bpf */
+char UDEV_PROP_HUION_FIRMWARE_ID[64];
+
+char EXPECTED_FIRMWARE_ID[] = "HUION_M22c_";
+
+__u8 last_button_state;
+
+static const __u8 disabled_rdesc_tablet[] = {
+ FixedSizeVendorReport(28) /* Input report 4 */
+};
+
+static const __u8 disabled_rdesc_wheel[] = {
+ FixedSizeVendorReport(9) /* Input report 17 */
+};
+
+static const __u8 fixed_rdesc_vendor[] = {
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionApplication(
+ ReportId(VENDOR_REPORT_ID)
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionPhysical(
+ /*
+ * I have only examined the tablet's behavior while using
+ * the PW600L pen, which does not have an eraser.
+ * Because of this, I don't know where the Eraser and Invert
+ * bits will go, or if they work as one would expect.
+ *
+ * For the time being, there is no expectation that a pen
+ * with an eraser will work without modifications here.
+ */
+ ReportSize(1)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportCount(3)
+ Usage_Dig_TipSwitch
+ Usage_Dig_BarrelSwitch
+ Usage_Dig_SecondaryBarrelSwitch
+ Input(Var|Abs)
+ PushPop(
+ ReportCount(1)
+ UsagePage_Button
+ Usage_i8(0x4a) /* (BTN_STYLUS3 + 1) & 0xff */
+ Input(Var|Abs)
+ )
+ ReportCount(3)
+ Input(Const)
+ ReportCount(1)
+ Usage_Dig_InRange
+ Input(Var|Abs)
+ ReportSize(16)
+ ReportCount(1)
+ PushPop(
+ UsagePage_GenericDesktop
+ Unit(cm)
+ UnitExponent(-2)
+ LogicalMinimum_i16(0)
+ PhysicalMinimum_i16(0)
+ /*
+ * The tablet has a logical maximum of 58760 x 33040
+ * and a claimed resolution of 5080 LPI (200 L/mm)
+ * This works out to a physical maximum of
+ * 293.8 x 165.2mm, which matches Huion's advertised
+ * active area dimensions from
+ * https://www.huion.com/products/pen_display/Kamvas/kamvas-13-gen-3.html
+ */
+ LogicalMaximum_i16(58760)
+ PhysicalMaximum_i16(2938)
+ Usage_GD_X
+ Input(Var|Abs)
+ LogicalMaximum_i16(33040)
+ PhysicalMaximum_i16(1652)
+ Usage_GD_Y
+ Input(Var|Abs)
+ )
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(16383)
+ Usage_Dig_TipPressure
+ Input(Var|Abs)
+ ReportCount(1)
+ Input(Const)
+ ReportSize(8)
+ ReportCount(2)
+ PushPop(
+ Unit(deg)
+ UnitExponent(0)
+ LogicalMinimum_i8(-60)
+ PhysicalMinimum_i8(-60)
+ LogicalMaximum_i8(60)
+ PhysicalMaximum_i8(60)
+ Usage_Dig_XTilt
+ Usage_Dig_YTilt
+ Input(Var|Abs)
+ )
+ )
+ )
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ ReportId(CUSTOM_PAD_REPORT_ID)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ /*
+ * The first 3 bytes are somewhat vestigial and will
+ * always be set to zero. Their presence here is needed
+ * to ensure that this device will be detected as a
+ * tablet pad by software that otherwise wouldn't know
+ * any better.
+ */
+ /* (data[1] & 0x01) barrel switch */
+ ReportSize(1)
+ ReportCount(1)
+ Usage_Dig_BarrelSwitch
+ Input(Var|Abs)
+ ReportCount(7)
+ Input(Const)
+ /* data[2] X */
+ /* data[3] Y */
+ ReportSize(8)
+ ReportCount(2)
+ UsagePage_GenericDesktop
+ Usage_GD_X
+ Usage_GD_Y
+ Input(Var|Abs)
+ /*
+ * (data[4] & 0x01) button 1
+ * (data[4] & 0x02) button 2
+ * (data[4] & 0x04) button 3
+ * (data[4] & 0x08) button 4
+ * (data[4] & 0x10) button 5
+ * (data[4] & 0x20) button 6 (top wheel button)
+ * (data[4] & 0x40) button 7 (bottom wheel button)
+ */
+ ReportSize(1)
+ ReportCount(7)
+ UsagePage_Button
+ UsageMinimum_i8(1)
+ UsageMaximum_i8(7)
+ Input(Var|Abs)
+ ReportCount(1)
+ Input(Const)
+ /* data[5] top wheel (signed, positive clockwise) */
+ ReportSize(8)
+ ReportCount(1)
+ UsagePage_GenericDesktop
+ Usage_GD_Wheel
+ LogicalMinimum_i8(-1)
+ LogicalMaximum_i8(1)
+ Input(Var|Rel)
+ /* data[6] bottom wheel (signed, positive clockwise) */
+ UsagePage_Consumer
+ Usage_Con_ACPan
+ Input(Var|Rel)
+ )
+ /*
+ * The kernel will drop reports that are bigger than the
+ * largest report specified in the HID descriptor.
+ * Therefore, our modified descriptor needs to have at least one
+ * HID report that is as long as, or longer than, the largest
+ * report in the original descriptor.
+ *
+ * This macro expands to a no-op report that is padded to the
+ * provided length.
+ */
+ FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
+ )
+};
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_fix_rdesc_huion_kamvas13_gen3, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
+ __s32 rdesc_size = hid_ctx->size;
+ __u8 have_fw_id;
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,
+ EXPECTED_FIRMWARE_ID,
+ sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;
+
+ if (have_fw_id) {
+ /*
+ * Tablet should be in vendor mode.
+ * Disable the unused devices
+ */
+ if (rdesc_size == TABLET_DESCRIPTOR_LENGTH) {
+ __builtin_memcpy(data, disabled_rdesc_tablet,
+ sizeof(disabled_rdesc_tablet));
+ return sizeof(disabled_rdesc_tablet);
+ }
+
+ if (rdesc_size == WHEEL_DESCRIPTOR_LENGTH) {
+ __builtin_memcpy(data, disabled_rdesc_wheel,
+ sizeof(disabled_rdesc_wheel));
+ return sizeof(disabled_rdesc_wheel);
+ }
+ }
+
+ /*
+ * Regardless of which mode the tablet is in, always fix the vendor
+ * descriptor in case the udev property just happened to not be set
+ */
+ if (rdesc_size == VENDOR_DESCRIPTOR_LENGTH) {
+ __builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
+ return sizeof(fixed_rdesc_vendor);
+ }
+
+ return 0;
+}
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(hid_fix_event_huion_kamvas13_gen3, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, VENDOR_REPORT_LENGTH /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* Handle vendor reports only */
+ if (hid_ctx->size != VENDOR_REPORT_LENGTH)
+ return 0;
+ if (data[0] != VENDOR_REPORT_ID)
+ return 0;
+
+ __u8 report_subtype = (data[1] >> 4) & 0x0f;
+
+ if (report_subtype == VENDOR_REPORT_SUBTYPE_PEN ||
+ report_subtype == VENDOR_REPORT_SUBTYPE_PEN_OUT) {
+ /* Invert Y tilt */
+ data[11] = -data[11];
+
+ } else if (report_subtype == VENDOR_REPORT_SUBTYPE_BUTTONS ||
+ report_subtype == VENDOR_REPORT_SUBTYPE_WHEELS) {
+ struct pad_report {
+ __u8 report_id;
+ __u8 btn_stylus:1;
+ __u8 padding:7;
+ __u8 x;
+ __u8 y;
+ __u8 buttons;
+ __s8 top_wheel;
+ __s8 bottom_wheel;
+ } __attribute__((packed)) *pad_report;
+
+ __s8 top_wheel = 0;
+ __s8 bottom_wheel = 0;
+
+ switch (report_subtype) {
+ case VENDOR_REPORT_SUBTYPE_WHEELS:
+ /*
+ * The wheel direction byte is 1 for clockwise rotation
+ * and 2 for counter-clockwise.
+ * Change it to 1 and -1, respectively.
+ */
+ switch (data[3]) {
+ case 1:
+ top_wheel = (data[5] == 1) ? 1 : -1;
+ break;
+ case 2:
+ bottom_wheel = (data[5] == 1) ? 1 : -1;
+ break;
+ }
+ break;
+
+ case VENDOR_REPORT_SUBTYPE_BUTTONS:
+ /*
+ * If a button is already being held, ignore any new
+ * button event unless it's a release.
+ *
+ * The tablet only cleanly handles one button being held
+ * at a time, and trying to hold multiple buttons
+ * (particularly wheel+pad buttons) can result in sequences
+ * of reports that look like imaginary presses and releases.
+ *
+ * This is an imperfect way to filter out some of these
+ * reports.
+ */
+ if (last_button_state != 0x00 && data[4] != 0x00)
+ break;
+
+ last_button_state = data[4];
+ break;
+ }
+
+
+ pad_report = (struct pad_report *)data;
+
+ pad_report->report_id = CUSTOM_PAD_REPORT_ID;
+ pad_report->btn_stylus = 0;
+ pad_report->x = 0;
+ pad_report->y = 0;
+ pad_report->buttons = last_button_state;
+ pad_report->top_wheel = top_wheel;
+ pad_report->bottom_wheel = bottom_wheel;
+
+ return sizeof(struct pad_report);
+ }
+
+ return 0;
+}
+
+HID_BPF_OPS(huion_kamvas13_gen3) = {
+ .hid_device_event = (void *)hid_fix_event_huion_kamvas13_gen3,
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc_huion_kamvas13_gen3,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ switch (ctx->rdesc_size) {
+ case VENDOR_DESCRIPTOR_LENGTH:
+ case TABLET_DESCRIPTOR_LENGTH:
+ case WHEEL_DESCRIPTOR_LENGTH:
+ ctx->retval = 0;
+ break;
+ default:
+ ctx->retval = -EINVAL;
+ }
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 03/10] HID: bpf: support for Huion Kamvas 16 Gen 3
2025-11-18 17:16 [PATCH 00/10] HID: bpf: sync up with current udev-hid-bpf programs Benjamin Tissoires
2025-11-18 17:16 ` [PATCH 01/10] HID: bpf: Add support for the Inspiroy 2M Benjamin Tissoires
2025-11-18 17:16 ` [PATCH 02/10] HID: bpf: add support for Huion Kamvas 13 (Gen 3) (model GS1333) Benjamin Tissoires
@ 2025-11-18 17:16 ` Benjamin Tissoires
2025-11-18 17:16 ` [PATCH 04/10] HID: bpf: Add fixup for Logitech SpaceNavigator variants Benjamin Tissoires
` (7 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
To: Jiri Kosina
Cc: linux-kernel, linux-input, Benjamin Tissoires, Higgins Dragon,
Benjamin Tissoires
Another Huion Kamvas tablet support.
Again it's safer to duplicate the code source in a separate file to
ensure we are not breaking any existing device.
Signed-off-by: Higgins Dragon <higgins@higginsdragon.com>
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@gmail.com>
Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/207
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
drivers/hid/bpf/progs/Huion__Kamvas16Gen3.bpf.c | 724 ++++++++++++++++++++++++
1 file changed, 724 insertions(+)
diff --git a/drivers/hid/bpf/progs/Huion__Kamvas16Gen3.bpf.c b/drivers/hid/bpf/progs/Huion__Kamvas16Gen3.bpf.c
new file mode 100644
index 0000000000000000000000000000000000000000..ac66c6e65eb4954cbfa096dc9724b652b913accd
--- /dev/null
+++ b/drivers/hid/bpf/progs/Huion__Kamvas16Gen3.bpf.c
@@ -0,0 +1,724 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2025 Nicholas LaPointe
+ * Copyright (c) 2025 Higgins Dragon
+ */
+
+#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_KAMVAS16_GEN3 0x2009
+
+#define VENDOR_DESCRIPTOR_LENGTH 36
+#define TABLET_DESCRIPTOR_LENGTH 328
+#define WHEEL_DESCRIPTOR_LENGTH 200
+
+#define VENDOR_REPORT_ID 8
+#define VENDOR_REPORT_LENGTH 14
+
+#define VENDOR_REPORT_SUBTYPE_PEN 0x08
+#define VENDOR_REPORT_SUBTYPE_PEN_OUT 0x00
+#define VENDOR_REPORT_SUBTYPE_BUTTONS 0x0e
+#define VENDOR_REPORT_SUBTYPE_WHEELS 0x0f
+
+/* For the reports that we create ourselves */
+#define CUSTOM_PAD_REPORT_ID 9
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_ANY, VID_HUION, PID_KAMVAS16_GEN3),
+);
+
+/*
+ * This tablet can send reports using one of two different data formats,
+ * depending on what "mode" the tablet is in.
+ *
+ * By default, the tablet will send reports that can be decoded using its
+ * included HID descriptors (descriptors 1 and 2, shown below).
+ * This mode will be called "firmware mode" throughout this file.
+ *
+ * The HID descriptor that describes pen events in firmware mode (descriptor 1)
+ * has multiple bugs:
+ * * "Secondary Tip Switch" instead of "Secondary Barrel Switch"
+ * * "Invert" instead of (or potentially shared with) third barrel button
+ * * Specified tablet area of 2048 in³ instead of 293.8 x 165.2mm
+ * * Specified tilt range of -90 to +90 instead of -60 to +60
+ *
+ * While these can be easily patched up by editing the descriptor, a larger
+ * problem with the firmware mode exists: it is impossible to tell which of the
+ * two wheels are being rotated (or having their central button pressed).
+ *
+ *
+ * By using a tool such as huion-switcher (https://github.com/whot/huion-switcher),
+ * the tablet can be made to send reports using a proprietary format that is not
+ * adequately described by its relevant descriptor (descriptor 0, shown below).
+ * This mode will be called "vendor mode" throughout this file.
+ *
+ * The reports sent while in vendor mode allow for proper decoding of the wheels.
+ *
+ * For simplicity and maximum functionality, this BPF focuses strictly on
+ * enabling one to make use of the vendor mode.
+ */
+
+/*
+ * DESCRIPTORS
+ * DESCRIPTOR 0
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 0
+ * # 0x09, 0x01, // Usage (Vendor Usage 1) 3
+ * # 0xa1, 0x01, // Collection (Application) 5
+ * # 0x85, 0x08, // Report ID (8) 7
+ * # 0x75, 0x68, // Report Size (104) 9
+ * # 0x95, 0x01, // Report Count (1) 11
+ * # 0x09, 0x01, // Usage (Vendor Usage 1) 13
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 15
+ * # 0xc0, // End Collection 17
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 18
+ * # 0x09, 0x01, // Usage (Vendor Usage 1) 21
+ * # 0xa1, 0x01, // Collection (Application) 23
+ * # 0x85, 0x16, // Report ID (22) 25
+ * # 0x75, 0x08, // Report Size (8) 27
+ * # 0x95, 0x07, // Report Count (7) 29
+ * # 0x09, 0x01, // Usage (Vendor Usage 1) 31
+ * # 0xb1, 0x02, // Feature (Data,Var,Abs) 33
+ * # 0xc0, // End Collection 35
+ * #
+ * R: 36 06 00 ff 09 01 a1 01 85 08 75 68 95 01 09 01 81 02 c0 06 00 ff 09 01 a1 01 85 16 75 08 95 07 09 01 b1 02 c0
+ * N: HUION Huion Tablet_GS1563
+ * I: 3 256c 2009
+ *
+ *
+ * DESCRIPTOR 1
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 0
+ * # 0x09, 0x02, // Usage (Pen) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # 0x85, 0x0a, // Report ID (10) 6
+ * # 0x09, 0x20, // Usage (Stylus) 8
+ * # 0xa1, 0x01, // Collection (Application) 10
+ * # 0x09, 0x42, // Usage (Tip Switch) 12
+ * # 0x09, 0x44, // Usage (Barrel Switch) 14
+ * # 0x09, 0x43, // Usage (Secondary Tip Switch) 16
+ * # 0x09, 0x3c, // Usage (Invert) 18
+ * # 0x09, 0x45, // Usage (Eraser) 20
+ * # 0x15, 0x00, // Logical Minimum (0) 22
+ * # 0x25, 0x01, // Logical Maximum (1) 24
+ * # 0x75, 0x01, // Report Size (1) 26
+ * # 0x95, 0x06, // Report Count (6) 28
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 30
+ * # 0x09, 0x32, // Usage (In Range) 32
+ * # 0x75, 0x01, // Report Size (1) 34
+ * # 0x95, 0x01, // Report Count (1) 36
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 38
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 40
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 42
+ * # 0x09, 0x30, // Usage (X) 44
+ * # 0x09, 0x31, // Usage (Y) 46
+ * # 0x55, 0x0d, // Unit Exponent (-3) 48
+ * # 0x65, 0x33, // Unit (EnglishLinear: in³) 50
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 52
+ * # 0x35, 0x00, // Physical Minimum (0) 55
+ * # 0x46, 0x00, 0x08, // Physical Maximum (2048) 57
+ * # 0x75, 0x10, // Report Size (16) 60
+ * # 0x95, 0x02, // Report Count (2) 62
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 64
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 66
+ * # 0x09, 0x30, // Usage (Tip Pressure) 68
+ * # 0x26, 0xff, 0x3f, // Logical Maximum (16383) 70
+ * # 0x75, 0x10, // Report Size (16) 73
+ * # 0x95, 0x01, // Report Count (1) 75
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 77
+ * # 0x09, 0x3d, // Usage (X Tilt) 79
+ * # 0x09, 0x3e, // Usage (Y Tilt) 81
+ * # 0x15, 0xa6, // Logical Minimum (-90) 83
+ * # 0x25, 0x5a, // Logical Maximum (90) 85
+ * # 0x75, 0x08, // Report Size (8) 87
+ * # 0x95, 0x02, // Report Count (2) 89
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 91
+ * # 0xc0, // End Collection 93
+ * # 0xc0, // End Collection 94
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 95
+ * # 0x09, 0x04, // Usage (Touch Screen) 97
+ * # 0xa1, 0x01, // Collection (Application) 99
+ * # 0x85, 0x04, // Report ID (4) 101
+ * # 0x09, 0x22, // Usage (Finger) 103
+ * # 0xa1, 0x02, // Collection (Logical) 105
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 107
+ * # 0x95, 0x01, // Report Count (1) 109
+ * # 0x75, 0x06, // Report Size (6) 111
+ * # 0x09, 0x51, // Usage (Contact Id) 113
+ * # 0x15, 0x00, // Logical Minimum (0) 115
+ * # 0x25, 0x3f, // Logical Maximum (63) 117
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 119
+ * # 0x09, 0x42, // Usage (Tip Switch) 121
+ * # 0x25, 0x01, // Logical Maximum (1) 123
+ * # 0x75, 0x01, // Report Size (1) 125
+ * # 0x95, 0x01, // Report Count (1) 127
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 129
+ * # 0x75, 0x01, // Report Size (1) 131
+ * # 0x95, 0x01, // Report Count (1) 133
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 135
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 137
+ * # 0x75, 0x10, // Report Size (16) 139
+ * # 0x55, 0x0e, // Unit Exponent (-2) 141
+ * # 0x65, 0x11, // Unit (SILinear: cm) 143
+ * # 0x09, 0x30, // Usage (X) 145
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 147
+ * # 0x35, 0x00, // Physical Minimum (0) 150
+ * # 0x46, 0x15, 0x0c, // Physical Maximum (3093) 152
+ * # 0x81, 0x42, // Input (Data,Var,Abs,Null) 155
+ * # 0x09, 0x31, // Usage (Y) 157
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 159
+ * # 0x46, 0xcb, 0x06, // Physical Maximum (1739) 162
+ * # 0x81, 0x42, // Input (Data,Var,Abs,Null) 165
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 167
+ * # 0x09, 0x30, // Usage (Tip Pressure) 169
+ * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 171
+ * # 0x75, 0x10, // Report Size (16) 174
+ * # 0x95, 0x01, // Report Count (1) 176
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 178
+ * # 0xc0, // End Collection 180
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 181
+ * # 0x09, 0x22, // Usage (Finger) 183
+ * # 0xa1, 0x02, // Collection (Logical) 185
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 187
+ * # 0x95, 0x01, // Report Count (1) 189
+ * # 0x75, 0x06, // Report Size (6) 191
+ * # 0x09, 0x51, // Usage (Contact Id) 193
+ * # 0x15, 0x00, // Logical Minimum (0) 195
+ * # 0x25, 0x3f, // Logical Maximum (63) 197
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 199
+ * # 0x09, 0x42, // Usage (Tip Switch) 201
+ * # 0x25, 0x01, // Logical Maximum (1) 203
+ * # 0x75, 0x01, // Report Size (1) 205
+ * # 0x95, 0x01, // Report Count (1) 207
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 209
+ * # 0x75, 0x01, // Report Size (1) 211
+ * # 0x95, 0x01, // Report Count (1) 213
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 215
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 217
+ * # 0x75, 0x10, // Report Size (16) 219
+ * # 0x55, 0x0e, // Unit Exponent (-2) 221
+ * # 0x65, 0x11, // Unit (SILinear: cm) 223
+ * # 0x09, 0x30, // Usage (X) 225
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 227
+ * # 0x35, 0x00, // Physical Minimum (0) 230
+ * # 0x46, 0x15, 0x0c, // Physical Maximum (3093) 232
+ * # 0x81, 0x42, // Input (Data,Var,Abs,Null) 235
+ * # 0x09, 0x31, // Usage (Y) 237
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 239
+ * # 0x46, 0xcb, 0x06, // Physical Maximum (1739) 242
+ * # 0x81, 0x42, // Input (Data,Var,Abs,Null) 245
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 247
+ * # 0x09, 0x30, // Usage (Tip Pressure) 249
+ * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 251
+ * # 0x75, 0x10, // Report Size (16) 254
+ * # 0x95, 0x01, // Report Count (1) 256
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 258
+ * # 0xc0, // End Collection 260
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 261
+ * # 0x09, 0x56, // Usage (Scan Time) 263
+ * # 0x55, 0x00, // Unit Exponent (0) 265
+ * # 0x65, 0x00, // Unit (None) 267
+ * # 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) 269
+ * # 0x95, 0x01, // Report Count (1) 274
+ * # 0x75, 0x20, // Report Size (32) 276
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 278
+ * # 0x09, 0x54, // Usage (Contact Count) 280
+ * # 0x25, 0x7f, // Logical Maximum (127) 282
+ * # 0x95, 0x01, // Report Count (1) 284
+ * # 0x75, 0x08, // Report Size (8) 286
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 288
+ * # 0x75, 0x08, // Report Size (8) 290
+ * # 0x95, 0x08, // Report Count (8) 292
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 294
+ * # 0x85, 0x05, // Report ID (5) 296
+ * # 0x09, 0x55, // Usage (Contact Max) 298
+ * # 0x25, 0x0a, // Logical Maximum (10) 300
+ * # 0x75, 0x08, // Report Size (8) 302
+ * # 0x95, 0x01, // Report Count (1) 304
+ * # 0xb1, 0x02, // Feature (Data,Var,Abs) 306
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 308
+ * # 0x09, 0xc5, // Usage (Vendor Usage 0xc5) 311
+ * # 0x85, 0x06, // Report ID (6) 313
+ * # 0x15, 0x00, // Logical Minimum (0) 315
+ * # 0x26, 0xff, 0x00, // Logical Maximum (255) 317
+ * # 0x75, 0x08, // Report Size (8) 320
+ * # 0x96, 0x00, 0x01, // Report Count (256) 322
+ * # 0xb1, 0x02, // Feature (Data,Var,Abs) 325
+ * # 0xc0, // End Collection 327
+ * #
+ * R: 328 05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 43 09 3c 09 45 15 00 25 01 75 01 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff 7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 3f 75 10 95 01 81 02 09 3d 09 3e 15 a6 25 5a 75 08 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 04 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 ff 7f 35 00 46 15 0c 81 42 09 31 26 ff 7f 46 cb 06 81 42 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 ff 7f 35 00 46 15 0c 81 42 09 31 26 ff 7f 46 cb 06 81 42 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 75 08 95 08 81 03 85 05 09 55 25 0a 75 08 95 01 b1 02 06 00 ff 09 c5 85 06 15 00 26 ff 00 75 08 96 00 01 b1 02 c0
+ * N: HUION Huion Tablet_GS1563
+ * I: 3 256c 2009
+ *
+ * DESCRIPTOR 2
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 0
+ * # 0x09, 0x0e, // Usage (System Multi-Axis Controller) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # 0x85, 0x11, // Report ID (17) 6
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 8
+ * # 0x09, 0x21, // Usage (Puck) 10
+ * # 0xa1, 0x02, // Collection (Logical) 12
+ * # 0x15, 0x00, // Logical Minimum (0) 14
+ * # 0x25, 0x01, // Logical Maximum (1) 16
+ * # 0x75, 0x01, // Report Size (1) 18
+ * # 0x95, 0x01, // Report Count (1) 20
+ * # 0xa1, 0x00, // Collection (Physical) 22
+ * # 0x05, 0x09, // Usage Page (Button) 24
+ * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 26
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 28
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 30
+ * # 0x09, 0x33, // Usage (Touch) 32
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 34
+ * # 0x95, 0x06, // Report Count (6) 36
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 38
+ * # 0xa1, 0x02, // Collection (Logical) 40
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 42
+ * # 0x09, 0x37, // Usage (Dial) 44
+ * # 0x16, 0x00, 0x80, // Logical Minimum (-32768) 46
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 49
+ * # 0x75, 0x10, // Report Size (16) 52
+ * # 0x95, 0x01, // Report Count (1) 54
+ * # 0x81, 0x06, // Input (Data,Var,Rel) 56
+ * # 0x35, 0x00, // Physical Minimum (0) 58
+ * # 0x46, 0x10, 0x0e, // Physical Maximum (3600) 60
+ * # 0x15, 0x00, // Logical Minimum (0) 63
+ * # 0x26, 0x10, 0x0e, // Logical Maximum (3600) 65
+ * # 0x09, 0x48, // Usage (Resolution Multiplier) 68
+ * # 0xb1, 0x02, // Feature (Data,Var,Abs) 70
+ * # 0x45, 0x00, // Physical Maximum (0) 72
+ * # 0xc0, // End Collection 74
+ * # 0x75, 0x08, // Report Size (8) 75
+ * # 0x95, 0x01, // Report Count (1) 77
+ * # 0x81, 0x01, // Input (Cnst,Arr,Abs) 79
+ * # 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
+ * # 0xc0, // End Collection 105
+ * # 0xc0, // End Collection 106
+ * # 0xc0, // End Collection 107
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 108
+ * # 0x09, 0x06, // Usage (Keyboard) 110
+ * # 0xa1, 0x01, // Collection (Application) 112
+ * # 0x85, 0x03, // Report ID (3) 114
+ * # 0x05, 0x07, // Usage Page (Keyboard) 116
+ * # 0x19, 0xe0, // Usage Minimum (224) 118
+ * # 0x29, 0xe7, // Usage Maximum (231) 120
+ * # 0x15, 0x00, // Logical Minimum (0) 122
+ * # 0x25, 0x01, // Logical Maximum (1) 124
+ * # 0x75, 0x01, // Report Size (1) 126
+ * # 0x95, 0x08, // Report Count (8) 128
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 130
+ * # 0x05, 0x07, // Usage Page (Keyboard) 132
+ * # 0x19, 0x00, // Usage Minimum (0) 134
+ * # 0x29, 0xff, // Usage Maximum (255) 136
+ * # 0x26, 0xff, 0x00, // Logical Maximum (255) 138
+ * # 0x75, 0x08, // Report Size (8) 141
+ * # 0x95, 0x06, // Report Count (6) 143
+ * # 0x81, 0x00, // Input (Data,Arr,Abs) 145
+ * # 0xc0, // End Collection 147
+ * # 0x05, 0x0c, // Usage Page (Consumer Devices) 148
+ * # 0x09, 0x01, // Usage (Consumer Control) 150
+ * # 0xa1, 0x01, // Collection (Application) 152
+ * # 0x85, 0x04, // Report ID (4) 154
+ * # 0x19, 0x01, // Usage Minimum (1) 156
+ * # 0x2a, 0x9c, 0x02, // Usage Maximum (668) 158
+ * # 0x15, 0x01, // Logical Minimum (1) 161
+ * # 0x26, 0x9c, 0x02, // Logical Maximum (668) 163
+ * # 0x95, 0x01, // Report Count (1) 166
+ * # 0x75, 0x10, // Report Size (16) 168
+ * # 0x81, 0x00, // Input (Data,Arr,Abs) 170
+ * # 0xc0, // End Collection 172
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 173
+ * # 0x09, 0x80, // Usage (System Control) 175
+ * # 0xa1, 0x01, // Collection (Application) 177
+ * # 0x85, 0x05, // Report ID (5) 179
+ * # 0x19, 0x81, // Usage Minimum (129) 181
+ * # 0x29, 0x83, // Usage Maximum (131) 183
+ * # 0x15, 0x00, // Logical Minimum (0) 185
+ * # 0x25, 0x01, // Logical Maximum (1) 187
+ * # 0x75, 0x01, // Report Size (1) 189
+ * # 0x95, 0x03, // Report Count (3) 191
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 193
+ * # 0x95, 0x05, // Report Count (5) 195
+ * # 0x81, 0x01, // Input (Cnst,Arr,Abs) 197
+ * # 0xc0, // End Collection 199
+ * #
+ * R: 200 05 01 09 0e a1 01 85 11 05 0d 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 05 01 09 06 a1 01 85 03 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 05 07 19 00 29 ff 26 ff 00 75 08 95 06 81 00 c0 05 0c 09 01 a1 01 85 04 19 01 2a 9c 02 15 01 26 9c 02 95 01 75 10 81 00 c0 05 01 09 80 a1 01 85 05 19 81 29 83 15 00 25 01 75 01 95 03 81 02 95 05 81 01 c0
+ * N: HUION Huion Tablet_GS1563
+ * I: 3 256c 2009
+ *
+ *
+ *
+ * VENDOR MODE
+ * HUION_FIRMWARE_ID="HUION_M22d_241101"
+ * HUION_MAGIC_BYTES="1403201101ac9900ff3fd81305080080083c4010"
+ *
+ * MAGIC BYTES
+ * [LogicalMaximum, X ] [LogicalMaximum, Y ] [LogicalMaximum, Pressure] [ LPI]
+ * 14 03 [ 20 11 01] [ ac 99 00] [ ff 3f] [d8 13] 05 08 00 80 08 3c 40 10
+ *
+ * See Huion__Kamvas13Gen3.bpf.c for more details on detailed button/dial reports and caveats. It's very
+ * similar to the Kamvas 16 Gen 3.
+ */
+
+
+/* Filled in by udev-hid-bpf */
+char UDEV_PROP_HUION_FIRMWARE_ID[64];
+
+char EXPECTED_FIRMWARE_ID[] = "HUION_M22d_";
+
+__u8 last_button_state;
+
+static const __u8 disabled_rdesc_tablet[] = {
+ FixedSizeVendorReport(28) /* Input report 4 */
+};
+
+static const __u8 disabled_rdesc_wheel[] = {
+ FixedSizeVendorReport(9) /* Input report 17 */
+};
+
+static const __u8 fixed_rdesc_vendor[] = {
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionApplication(
+ ReportId(VENDOR_REPORT_ID)
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionPhysical(
+ /*
+ * I have only examined the tablet's behavior while using
+ * the PW600L pen, which does not have an eraser.
+ * Because of this, I don't know where the Eraser and Invert
+ * bits will go, or if they work as one would expect.
+ *
+ * For the time being, there is no expectation that a pen
+ * with an eraser will work without modifications here.
+ */
+ ReportSize(1)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportCount(3)
+ Usage_Dig_TipSwitch
+ Usage_Dig_BarrelSwitch
+ Usage_Dig_SecondaryBarrelSwitch
+ Input(Var|Abs)
+ PushPop(
+ ReportCount(1)
+ UsagePage_Button
+ Usage_i8(0x4a) /* (BTN_STYLUS3 + 1) & 0xff */
+ Input(Var|Abs)
+ )
+ ReportCount(3)
+ Input(Const)
+ ReportCount(1)
+ Usage_Dig_InRange
+ Input(Var|Abs)
+ ReportSize(16)
+ ReportCount(1)
+ PushPop(
+ UsagePage_GenericDesktop
+ Unit(cm)
+ UnitExponent(-2)
+ LogicalMinimum_i16(0)
+ PhysicalMinimum_i16(0)
+ /*
+ * The tablet has a logical maximum of 69920 x 39340
+ * and a claimed resolution of 5080 LPI (200 L/mm)
+ * This works out to a physical maximum of
+ * 349.6 x 196.7mm, which matches Huion's advertised
+ * (rounded) active area dimensions from
+ * https://www.huion.com/products/pen_display/Kamvas/kamvas-16-gen-3.html
+ *
+ * The Kamvas uses data[8] for the 3rd byte of the X-axis, and adding
+ * that after data[2] and data[3] makes a contiguous little-endian
+ * 24-bit value. (See BPF_PROG below)
+ */
+ ReportSize(24)
+ LogicalMaximum_i32(69920)
+ PhysicalMaximum_i16(3496)
+ Usage_GD_X
+ Input(Var|Abs)
+ ReportSize(16)
+ LogicalMaximum_i16(39340)
+ PhysicalMaximum_i16(1967)
+ Usage_GD_Y
+ Input(Var|Abs)
+ )
+ ReportSize(16)
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(16383)
+ Usage_Dig_TipPressure
+ Input(Var|Abs)
+ ReportSize(8)
+ ReportCount(1)
+ Input(Const)
+ ReportCount(2)
+ PushPop(
+ Unit(deg)
+ UnitExponent(0)
+ LogicalMinimum_i8(-60)
+ PhysicalMinimum_i8(-60)
+ LogicalMaximum_i8(60)
+ PhysicalMaximum_i8(60)
+ Usage_Dig_XTilt
+ Usage_Dig_YTilt
+ Input(Var|Abs)
+ )
+ )
+ )
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ ReportId(CUSTOM_PAD_REPORT_ID)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ /*
+ * The first 3 bytes are somewhat vestigial and will
+ * always be set to zero. Their presence here is needed
+ * to ensure that this device will be detected as a
+ * tablet pad by software that otherwise wouldn't know
+ * any better.
+ */
+ /* (data[1] & 0x01) barrel switch */
+ ReportSize(1)
+ ReportCount(1)
+ Usage_Dig_BarrelSwitch
+ Input(Var|Abs)
+ ReportCount(7)
+ Input(Const)
+ /* data[2] X */
+ /* data[3] Y */
+ ReportSize(8)
+ ReportCount(2)
+ UsagePage_GenericDesktop
+ Usage_GD_X
+ Usage_GD_Y
+ Input(Var|Abs)
+ /*
+ * (data[4] & 0x01) button 1
+ * (data[4] & 0x02) button 2
+ * (data[4] & 0x04) button 3
+ * (data[4] & 0x08) button 4
+ * (data[4] & 0x10) button 5
+ * (data[4] & 0x20) button 6
+ * (data[4] & 0x40) button 7 (top wheel button)
+ * (data[4] & 0x80) button 8 (bottom wheel button)
+ */
+ ReportSize(1)
+ ReportCount(8)
+ UsagePage_Button
+ UsageMinimum_i8(1)
+ UsageMaximum_i8(8)
+ Input(Var|Abs)
+ /* data[5] top wheel (signed, positive clockwise) */
+ ReportSize(8)
+ ReportCount(1)
+ UsagePage_GenericDesktop
+ Usage_GD_Wheel
+ LogicalMinimum_i8(-1)
+ LogicalMaximum_i8(1)
+ Input(Var|Rel)
+ /* data[6] bottom wheel (signed, positive clockwise) */
+ UsagePage_Consumer
+ Usage_Con_ACPan
+ Input(Var|Rel)
+ )
+ /*
+ * The kernel will drop reports that are bigger than the
+ * largest report specified in the HID descriptor.
+ * Therefore, our modified descriptor needs to have at least one
+ * HID report that is as long as, or longer than, the largest
+ * report in the original descriptor.
+ *
+ * This macro expands to a no-op report that is padded to the
+ * provided length.
+ */
+ FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
+ )
+};
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_fix_rdesc_huion_kamvas16_gen3, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
+ __s32 rdesc_size = hid_ctx->size;
+ __u8 have_fw_id;
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,
+ EXPECTED_FIRMWARE_ID,
+ sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;
+
+ if (have_fw_id) {
+ /*
+ * Tablet should be in vendor mode.
+ * Disable the unused devices
+ */
+ if (rdesc_size == TABLET_DESCRIPTOR_LENGTH) {
+ __builtin_memcpy(data, disabled_rdesc_tablet,
+ sizeof(disabled_rdesc_tablet));
+ return sizeof(disabled_rdesc_tablet);
+ }
+
+ if (rdesc_size == WHEEL_DESCRIPTOR_LENGTH) {
+ __builtin_memcpy(data, disabled_rdesc_wheel,
+ sizeof(disabled_rdesc_wheel));
+ return sizeof(disabled_rdesc_wheel);
+ }
+ }
+
+ /*
+ * Regardless of which mode the tablet is in, always fix the vendor
+ * descriptor in case the udev property just happened to not be set
+ */
+ if (rdesc_size == VENDOR_DESCRIPTOR_LENGTH) {
+ __builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
+ return sizeof(fixed_rdesc_vendor);
+ }
+
+ return 0;
+}
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(hid_fix_event_huion_kamvas16_gen3, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, VENDOR_REPORT_LENGTH /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* Handle vendor reports only */
+ if (hid_ctx->size != VENDOR_REPORT_LENGTH)
+ return 0;
+ if (data[0] != VENDOR_REPORT_ID)
+ return 0;
+
+ __u8 report_subtype = (data[1] >> 4) & 0x0f;
+
+ if (report_subtype == VENDOR_REPORT_SUBTYPE_PEN ||
+ report_subtype == VENDOR_REPORT_SUBTYPE_PEN_OUT) {
+ /* Invert Y tilt */
+ data[11] = -data[11];
+
+ /*
+ * Rearrange the bytes of the report so that
+ * [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
+ * will be arranged as
+ * [0, 1, 2, 3, 8, 4, 5, 6, 7, 9, 10, 11, 12, 13]
+ */
+ __u8 x_24 = data[8];
+
+ data[8] = data[7];
+ data[7] = data[6];
+ data[6] = data[5];
+ data[5] = data[4];
+
+ data[4] = x_24;
+
+ } else if (report_subtype == VENDOR_REPORT_SUBTYPE_BUTTONS ||
+ report_subtype == VENDOR_REPORT_SUBTYPE_WHEELS) {
+ struct pad_report {
+ __u8 report_id;
+ __u8 btn_stylus:1;
+ __u8 padding:7;
+ __u8 x;
+ __u8 y;
+ __u8 buttons;
+ __s8 top_wheel;
+ __s8 bottom_wheel;
+ } __attribute__((packed)) *pad_report;
+
+ __s8 top_wheel = 0;
+ __s8 bottom_wheel = 0;
+
+ switch (report_subtype) {
+ case VENDOR_REPORT_SUBTYPE_WHEELS:
+ /*
+ * The wheel direction byte is 1 for clockwise rotation
+ * and 2 for counter-clockwise.
+ * Change it to 1 and -1, respectively.
+ */
+ switch (data[3]) {
+ case 1:
+ top_wheel = (data[5] == 1) ? 1 : -1;
+ break;
+ case 2:
+ bottom_wheel = (data[5] == 1) ? 1 : -1;
+ break;
+ }
+ break;
+
+ case VENDOR_REPORT_SUBTYPE_BUTTONS:
+ /*
+ * If a button is already being held, ignore any new
+ * button event unless it's a release.
+ *
+ * The tablet only cleanly handles one button being held
+ * at a time, and trying to hold multiple buttons
+ * (particularly wheel+pad buttons) can result in sequences
+ * of reports that look like imaginary presses and releases.
+ *
+ * This is an imperfect way to filter out some of these
+ * reports.
+ */
+ if (last_button_state != 0x00 && data[4] != 0x00)
+ break;
+
+ last_button_state = data[4];
+ break;
+ }
+
+ pad_report = (struct pad_report *)data;
+
+ pad_report->report_id = CUSTOM_PAD_REPORT_ID;
+ pad_report->btn_stylus = 0;
+ pad_report->x = 0;
+ pad_report->y = 0;
+ pad_report->buttons = last_button_state;
+ pad_report->top_wheel = top_wheel;
+ pad_report->bottom_wheel = bottom_wheel;
+
+ return sizeof(struct pad_report);
+ }
+
+ return 0;
+}
+
+HID_BPF_OPS(huion_kamvas16_gen3) = {
+ .hid_device_event = (void *)hid_fix_event_huion_kamvas16_gen3,
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc_huion_kamvas16_gen3,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ switch (ctx->rdesc_size) {
+ case VENDOR_DESCRIPTOR_LENGTH:
+ case TABLET_DESCRIPTOR_LENGTH:
+ case WHEEL_DESCRIPTOR_LENGTH:
+ ctx->retval = 0;
+ break;
+ default:
+ ctx->retval = -EINVAL;
+ }
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 04/10] HID: bpf: Add fixup for Logitech SpaceNavigator variants
2025-11-18 17:16 [PATCH 00/10] HID: bpf: sync up with current udev-hid-bpf programs Benjamin Tissoires
` (2 preceding siblings ...)
2025-11-18 17:16 ` [PATCH 03/10] HID: bpf: support for Huion Kamvas 16 Gen 3 Benjamin Tissoires
@ 2025-11-18 17:16 ` Benjamin Tissoires
2025-11-18 17:16 ` [PATCH 05/10] HID: bpf: Add support for the Waltop Batteryless Tablet Benjamin Tissoires
` (6 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
To: Jiri Kosina
Cc: linux-kernel, linux-input, Benjamin Tissoires, Curran Muhlberger
The 3Dconnexion SpaceNavigator HID report descriptor declares its axis
data to be "relative" when it is actually "absolute". This quirk was
addressed in the kernel in 2.6.33, but some SpaceNavigator variants have
a slightly different report descriptor whose axis input items are at
different offsets than those assumed by the kernel fixup.
Add a BPF fixup to handle both sets of offsets for known SpaceNavigator
variants if the descriptor has not already been fixed by the kernel.
Signed-off-by: Curran Muhlberger <curran@cs.cornell.edu>
Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/181
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
.../hid/bpf/progs/Logitech__SpaceNavigator.bpf.c | 86 ++++++++++++++++++++++
1 file changed, 86 insertions(+)
diff --git a/drivers/hid/bpf/progs/Logitech__SpaceNavigator.bpf.c b/drivers/hid/bpf/progs/Logitech__SpaceNavigator.bpf.c
new file mode 100644
index 0000000000000000000000000000000000000000..b17719d6d9c71e7b538d75c7a7197b4e1fead213
--- /dev/null
+++ b/drivers/hid/bpf/progs/Logitech__SpaceNavigator.bpf.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2025 Curran Muhlberger
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_LOGITECH 0x046D
+#define PID_SPACENAVIGATOR 0xC626
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_ANY, VID_LOGITECH, PID_SPACENAVIGATOR)
+);
+
+/*
+ * The 3Dconnexion SpaceNavigator 3D Mouse is a multi-axis controller with 6
+ * axes (grouped as X,Y,Z and Rx,Ry,Rz). Axis data is absolute, but the report
+ * descriptor erroneously declares it to be relative. We fix the report
+ * descriptor to mark both axis collections as absolute.
+ *
+ * The kernel attempted to fix this in commit 24985cf68612 (HID: support
+ * Logitech/3DConnexion SpaceTraveler and SpaceNavigator), but the descriptor
+ * data offsets are incorrect for at least some SpaceNavigator units.
+ */
+
+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 */
+
+ /* Offset of Input item in X,Y,Z and Rx,Ry,Rz collections for all known
+ * firmware variants.
+ * - 2009 model: X,Y,Z @ 32-33, Rx,Ry,Rz @ 49-50 (fixup originally
+ * applied in kernel)
+ * - 2016 model (size==228): X,Y,Z @ 36-37, Rx,Ry,Rz @ 53-54
+ *
+ * The descriptor size of the 2009 model is not known, and there is evidence
+ * for at least two other variants (with sizes 202 & 217) besides the 2016
+ * model, so we try all known offsets regardless of descriptor size.
+ */
+ const u8 offsets[] = {32, 36, 49, 53};
+
+ for (size_t idx = 0; idx < ARRAY_SIZE(offsets); idx++) {
+ u8 offset = offsets[idx];
+
+ /* if Input (Data,Var,Rel) , make it Input (Data,Var,Abs) */
+ if (data[offset] == 0x81 && data[offset + 1] == 0x06)
+ data[offset + 1] = 0x02;
+ }
+
+ return 0;
+}
+
+HID_BPF_OPS(logitech_spacenavigator) = {
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ /* Ensure report descriptor size matches one of the known variants. */
+ if (ctx->rdesc_size != 202 &&
+ ctx->rdesc_size != 217 &&
+ ctx->rdesc_size != 228) {
+ ctx->retval = -EINVAL;
+ return 0;
+ }
+
+ /* Check whether the kernel has already applied the fix. */
+ if ((ctx->rdesc[32] == 0x81 && ctx->rdesc[33] == 0x02 &&
+ ctx->rdesc[49] == 0x81 && ctx->rdesc[50] == 0x02) ||
+ (ctx->rdesc[36] == 0x81 && ctx->rdesc[37] == 0x02 &&
+ ctx->rdesc[53] == 0x81 && ctx->rdesc[54] == 0x02))
+ ctx->retval = -EINVAL;
+ else
+ ctx->retval = 0;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 05/10] HID: bpf: Add support for the Waltop Batteryless Tablet
2025-11-18 17:16 [PATCH 00/10] HID: bpf: sync up with current udev-hid-bpf programs Benjamin Tissoires
` (3 preceding siblings ...)
2025-11-18 17:16 ` [PATCH 04/10] HID: bpf: Add fixup for Logitech SpaceNavigator variants Benjamin Tissoires
@ 2025-11-18 17:16 ` Benjamin Tissoires
2025-11-18 17:16 ` [PATCH 06/10] HID: bpf: Add support for the XP-Pen Deco 01 V3 Benjamin Tissoires
` (5 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
To: Jiri Kosina
Cc: linux-kernel, linux-input, Benjamin Tissoires, Peter Hutterer,
Jan Felix Langenbach
Several bugs as outlined in udev-hid-bpf#66 and udev-hid-bpf!198:
- pressure curve is far from linear
- tilt range is ±60, not ±127
- pressing the second button sets both tip down and
barrel switch
Fix the second button by adding a Secondary Barrel Switch in the
existing padding and check for the tip down/barrel switch down combo.
When both values become true at the same time, set the Secondary Barrel
Switch instead.
Implement a custom pressure curve that maps the hardware range 0-102
linearly to the logical range 0-1224, and maps the hardware range
103-2047 logarithmically to the logical range 1232-2047. This mapping
isn’t perfect, but it’s way more natural than the stock configuration.
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Signed-off-by: Jan Felix Langenbach <JanFelix.Langenbach@protonmail.com>
Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/200
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
.../hid/bpf/progs/WALTOP__Batteryless-Tablet.bpf.c | 321 +++++++++++++++++++++
1 file changed, 321 insertions(+)
diff --git a/drivers/hid/bpf/progs/WALTOP__Batteryless-Tablet.bpf.c b/drivers/hid/bpf/progs/WALTOP__Batteryless-Tablet.bpf.c
new file mode 100644
index 0000000000000000000000000000000000000000..156d75af516d7d0d49d0f5ee1ff5d3ebb720c7d9
--- /dev/null
+++ b/drivers/hid/bpf/progs/WALTOP__Batteryless-Tablet.bpf.c
@@ -0,0 +1,321 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2025 Red Hat
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_WALTOP 0x172F
+#define PID_BATTERYLESS_TABLET 0x0505
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_ANY, VID_WALTOP, PID_BATTERYLESS_TABLET)
+);
+
+#define EXPECTED_RDESC_SIZE 335
+#define PEN_REPORT_ID 16
+
+#define TIP_SWITCH BIT(0)
+#define BARREL_SWITCH BIT(1)
+#define SECONDARY_BARREL_SWITCH BIT(5)
+
+static __u8 last_button_state;
+
+static const __u8 fixed_rdesc[] = {
+ 0x05, 0x01, // Usage Page (Generic Desktop)
+ 0x09, 0x02, // Usage (Mouse)
+ 0xa1, 0x01, // Collection (Application)
+ 0x85, 0x01, // Report ID (1)
+ 0x09, 0x01, // Usage (Pointer)
+ 0xa1, 0x00, // Collection (Physical)
+ 0x05, 0x09, // Usage Page (Button)
+ 0x19, 0x01, // Usage Minimum (1)
+ 0x29, 0x05, // Usage Maximum (5)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x05, // Report Count (5)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0x75, 0x03, // Report Size (3)
+ 0x95, 0x01, // Report Count (1)
+ 0x81, 0x03, // Input (Cnst,Var,Abs)
+ 0x05, 0x01, // Usage Page (Generic Desktop)
+ 0x09, 0x30, // Usage (X)
+ 0x09, 0x31, // Usage (Y)
+ 0x09, 0x38, // Usage (Wheel)
+ 0x15, 0x81, // Logical Minimum (-127)
+ 0x25, 0x7f, // Logical Maximum (127)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x03, // Report Count (3)
+ 0x81, 0x06, // Input (Data,Var,Rel)
+ 0x05, 0x0c, // Usage Page (Consumer)
+ 0x15, 0x81, // Logical Minimum (-127)
+ 0x25, 0x7f, // Logical Maximum (127)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x01, // Report Count (1)
+ 0x0a, 0x38, 0x02, // Usage (AC Pan)
+ 0x81, 0x06, // Input (Data,Var,Rel)
+ 0xc0, // End Collection
+ 0xc0, // End Collection
+ 0x05, 0x0d, // Usage Page (Digitizers)
+ 0x09, 0x02, // Usage (Pen)
+ 0xa1, 0x01, // Collection (Application)
+ 0x85, 0x02, // Report ID (2)
+ 0x09, 0x20, // Usage (Stylus)
+ 0xa1, 0x00, // Collection (Physical)
+ 0x09, 0x00, // Usage (0x0000)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x09, // Report Count (9)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0x09, 0x3f, // Usage (Azimuth)
+ 0x09, 0x40, // Usage (Altitude)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x02, // Report Count (2)
+ 0xb1, 0x02, // Feature (Data,Var,Abs)
+ 0xc0, // End Collection
+ 0x85, 0x05, // Report ID (5)
+ 0x05, 0x0d, // Usage Page (Digitizers)
+ 0x09, 0x20, // Usage (Stylus)
+ 0xa1, 0x00, // Collection (Physical)
+ 0x09, 0x00, // Usage (0x0000)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x07, // Report Count (7)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0xc0, // End Collection
+ 0x85, 0x0a, // Report ID (10)
+ 0x05, 0x0d, // Usage Page (Digitizers)
+ 0x09, 0x20, // Usage (Stylus)
+ 0xa1, 0x00, // Collection (Physical)
+ 0x09, 0x00, // Usage (0x0000)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x07, // Report Count (7)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0xc0, // End Collection
+ 0x85, 0x10, // Report ID (16)
+ 0x09, 0x20, // Usage (Stylus)
+ 0xa1, 0x00, // Collection (Physical)
+ 0x09, 0x42, // Usage (Tip Switch)
+ 0x09, 0x44, // Usage (Barrel Switch)
+ 0x09, 0x3c, // Usage (Invert)
+ 0x09, 0x45, // Usage (Eraser)
+ 0x09, 0x32, // Usage (In Range)
+ 0x09, 0x5a, // Usage (Secondary Barrel Switch) <-- added
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x06, // Report Count (6) <--- changed from 5
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0x95, 0x02, // Report Count (2) <--- changed from 3
+ 0x81, 0x03, // Input (Cnst,Var,Abs)
+ 0x05, 0x01, // Usage Page (Generic Desktop)
+ 0x09, 0x30, // Usage (X)
+ 0x75, 0x10, // Report Size (16)
+ 0x95, 0x01, // Report Count (1)
+ 0xa4, // Push
+ 0x55, 0x0d, // Unit Exponent (-3)
+ 0x65, 0x33, // Unit (EnglishLinear: in³)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0x00, 0x7d, // Logical Maximum (32000)
+ 0x35, 0x00, // Physical Minimum (0)
+ 0x46, 0x00, 0x7d, // Physical Maximum (32000)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0x09, 0x31, // Usage (Y)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0x20, 0x4e, // Logical Maximum (20000)
+ 0x35, 0x00, // Physical Minimum (0)
+ 0x46, 0x20, 0x4e, // Physical Maximum (20000)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0x05, 0x0d, // Usage Page (Digitizers)
+ 0x09, 0x30, // Usage (Tip Pressure)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x07, // Logical Maximum (2047)
+ 0x35, 0x00, // Physical Minimum (0)
+ 0x46, 0xff, 0x07, // Physical Maximum (2047)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0x05, 0x0d, // Usage Page (Digitizers)
+ 0x09, 0x3d, // Usage (X Tilt)
+ 0x09, 0x3e, // Usage (Y Tilt)
+ 0x15, 0xc4, // Logical Minimum (-60) <- changed from -127
+ 0x25, 0x3c, // Logical Maximum (60) <- changed from 127
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x02, // Report Count (2)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0xc0, // End Collection
+ 0xc0, // End Collection
+ 0x05, 0x01, // Usage Page (Generic Desktop)
+ 0x09, 0x06, // Usage (Keyboard)
+ 0xa1, 0x01, // Collection (Application)
+ 0x85, 0x0d, // Report ID (13)
+ 0x05, 0x07, // Usage Page (Keyboard/Keypad)
+ 0x19, 0xe0, // Usage Minimum (224)
+ 0x29, 0xe7, // Usage Maximum (231)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x08, // Report Count (8)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x01, // Report Count (1)
+ 0x81, 0x01, // Input (Cnst,Arr,Abs)
+ 0x05, 0x07, // Usage Page (Keyboard/Keypad)
+ 0x19, 0x00, // Usage Minimum (0)
+ 0x29, 0x65, // Usage Maximum (101)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x65, // Logical Maximum (101)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x05, // Report Count (5)
+ 0x81, 0x00, // Input (Data,Arr,Abs)
+ 0xc0, // End Collection
+ 0x05, 0x0c, // Usage Page (Consumer)
+ 0x09, 0x01, // Usage (Consumer Control)
+ 0xa1, 0x01, // Collection (Application)
+ 0x85, 0x0c, // Report ID (12)
+ 0x09, 0xe9, // Usage (Volume Increment)
+ 0x09, 0xea, // Usage (Volume Decrement)
+ 0x09, 0xe2, // Usage (Mute)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x03, // Report Count (3)
+ 0x81, 0x06, // Input (Data,Var,Rel)
+ 0x75, 0x05, // Report Size (5)
+ 0x95, 0x01, // Report Count (1)
+ 0x81, 0x07, // Input (Cnst,Var,Rel)
+ 0xc0, // End Collection
+};
+
+static inline unsigned int bitwidth32(__u32 x)
+{
+ return 32 - __builtin_clzg(x, 32);
+}
+
+static inline unsigned int floor_log2_32(__u32 x)
+{
+ return bitwidth32(x) - 1;
+}
+
+/* Maps the interval [0, 2047] to itself using a scaled
+ * approximation of the function log2(x+1).
+ */
+static unsigned int scaled_log2(__u16 v)
+{
+ const unsigned int XMAX = 2047;
+ const unsigned int YMAX = 11; /* log2(2048) = 11 */
+
+ unsigned int x = v + 1;
+ unsigned int n = floor_log2_32(x);
+ unsigned int b = 1 << n;
+
+ /* Fixed-point fraction in [0, 1), linearly
+ * interpolated using delta-y = 1 and
+ * delta-x = (2b - b) = b.
+ */
+ unsigned int frac = (x - b) << YMAX;
+ unsigned int lerp = frac / b;
+ unsigned int log2 = (n << YMAX) + lerp;
+
+ return ((log2 * XMAX) / YMAX) >> YMAX;
+}
+
+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 */
+
+ __builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
+
+ return sizeof(fixed_rdesc);
+}
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(waltop_fix_events, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ __u8 report_id = data[0];
+
+ if (report_id != PEN_REPORT_ID)
+ return 0;
+
+ /* On this tablet if the secondary barrel switch is pressed,
+ * the tablet sends tip down and barrel down. Change this to
+ * just secondary barrel down when there is no ambiguity.
+ *
+ * It's possible that there is a bug in the firmware and the
+ * device intends to set invert + eraser instead (i.e. the
+ * pysical button is an eraser button) but since
+ * the pressure is always zero, said eraser button
+ * would be useless anyway.
+ *
+ * So let's just change the button to secondary barrel down.
+ */
+
+ __u8 tip_switch = data[1] & TIP_SWITCH;
+ __u8 barrel_switch = data[1] & BARREL_SWITCH;
+
+ __u8 tip_held = last_button_state & TIP_SWITCH;
+ __u8 barrel_held = last_button_state & BARREL_SWITCH;
+
+ if (tip_switch && barrel_switch && !tip_held && !barrel_held) {
+ data[1] &= ~(TIP_SWITCH | BARREL_SWITCH); /* release tip and barrel */
+ data[1] |= SECONDARY_BARREL_SWITCH; /* set secondary barrel switch */
+ }
+
+ last_button_state = data[1];
+
+ /* The pressure sensor on this tablet maps around half of the
+ * logical pressure range into the interval [0-100]. Further
+ * pressure causes the sensor value to increase exponentially
+ * up to a maximum value of 2047.
+ *
+ * The values 12 and 102 were chosen to have an integer slope
+ * with smooth transition between the two curves around the
+ * value 100.
+ */
+
+ __u16 pressure = (((__u16)data[6]) << 0) | (((__u16)data[7]) << 8);
+
+ if (pressure <= 102)
+ pressure *= 12;
+ else
+ pressure = scaled_log2(pressure);
+
+ data[6] = pressure >> 0;
+ data[7] = pressure >> 8;
+
+ return 0;
+}
+
+HID_BPF_OPS(waltop_batteryless) = {
+ .hid_device_event = (void *)waltop_fix_events,
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ if (ctx->rdesc_size == EXPECTED_RDESC_SIZE)
+ ctx->retval = 0;
+ else
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 06/10] HID: bpf: Add support for the XP-Pen Deco 01 V3
2025-11-18 17:16 [PATCH 00/10] HID: bpf: sync up with current udev-hid-bpf programs Benjamin Tissoires
` (4 preceding siblings ...)
2025-11-18 17:16 ` [PATCH 05/10] HID: bpf: Add support for the Waltop Batteryless Tablet Benjamin Tissoires
@ 2025-11-18 17:16 ` Benjamin Tissoires
2025-11-18 17:16 ` [PATCH 07/10] HID: bpf: Add support for XP-Pen Deco02 Benjamin Tissoires
` (4 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
To: Jiri Kosina; +Cc: linux-kernel, linux-input, Benjamin Tissoires, Peter Hutterer
This device needs a fix for the tilt range on the pen report descriptor
and the usual conversion of the pad keys from the firmware's hardcoded
keyboard shortcuts to actual pad buttons.
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/185
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
drivers/hid/bpf/progs/XPPen__Deco01V3.bpf.c | 305 ++++++++++++++++++++++++++++
1 file changed, 305 insertions(+)
diff --git a/drivers/hid/bpf/progs/XPPen__Deco01V3.bpf.c b/drivers/hid/bpf/progs/XPPen__Deco01V3.bpf.c
new file mode 100644
index 0000000000000000000000000000000000000000..2502fcc9ede69b856e2115a15307096951953571
--- /dev/null
+++ b/drivers/hid/bpf/progs/XPPen__Deco01V3.bpf.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2025 Red Hat
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include "hid_report_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */
+#define PID_DECO_01_V3 0x0947
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_DECO_01_V3),
+);
+
+/*
+ * Default report descriptor reports:
+ * - a report descriptor for the pad buttons, reported as key sequences
+ * - a report descriptor for the pen
+ * - a vendor-specific report descriptor
+ *
+ * The Pad report descriptor, see
+ * https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/issues/54
+ *
+ * # 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
+ *
+ * And key events for buttons top->bottom are:
+ * Buttons released: 06 00 00 00 00 00 00 00
+ * Button1: 06 00 05 00 00 00 00 00 -> b
+ * Button2: 06 00 08 00 00 00 00 00 -> e
+ * Button3: 06 04 00 00 00 00 00 00 -> LAlt
+ * Button4: 06 00 2c 00 00 00 00 00 -> Space
+ * Button5: 06 01 16 00 00 00 00 00 -> LControl + s
+ * Button6: 06 01 1d 00 00 00 00 00 -> LControl + z
+ * Button7: 06 01 57 00 00 00 00 00 -> LControl + Keypad Plus
+ * Button8: 06 01 56 00 00 00 00 00 -> LControl + Keypad Dash
+ *
+ * When multiple buttons are pressed at the same time, the values used to
+ * identify the buttons are identical, but they appear in different bytes of the
+ * record. For example, when button 2 (0x08) and button 1 (0x05) are pressed,
+ * this is the report:
+ *
+ * Buttons 2 and 1: 06 00 08 05 00 00 00 00 -> e + b
+ *
+ * Buttons 1, 2, 4, 5 and 6 can be matched by finding their values in the
+ * report.
+ *
+ * Button 3 is pressed when the 3rd bit is 1. For example, pressing buttons 3
+ * and 5 generates this report:
+ *
+ * Buttons 3 and 5: 06 05 16 00 00 00 00 00 -> LControl + LAlt + s
+ * -- --
+ * | |
+ * | `- Button 5 (0x16)
+ * `- 0x05 = 0101. Button 3 is pressed
+ * ^
+ *
+ * pad_buttons contains a list of buttons that can be matched in
+ * HID_BPF_DEVICE_EVENT. Button 3 as it has a dedicated bit.
+ *
+ *
+ * The Pen report descriptor announces a wrong tilt range:
+ *
+ * Report descriptor length: 109 bytes
+ * 0x05, 0x0d, // Usage Page (Digitizers) 0
+ * 0x09, 0x02, // Usage (Pen) 2
+ * 0xa1, 0x01, // Collection (Application) 4
+ * 0x85, 0x07, // Report ID (7) 6
+ * 0x09, 0x20, // Usage (Stylus) 8
+ * 0xa1, 0x01, // Collection (Application) 10
+ * 0x09, 0x42, // Usage (Tip Switch) 12
+ * 0x09, 0x44, // Usage (Barrel Switch) 14
+ * 0x09, 0x45, // Usage (Eraser) 16
+ * 0x09, 0x3c, // Usage (Invert) 18
+ * 0x15, 0x00, // Logical Minimum (0) 20
+ * 0x25, 0x01, // Logical Maximum (1) 22
+ * 0x75, 0x01, // Report Size (1) 24
+ * 0x95, 0x04, // Report Count (4) 26
+ * 0x81, 0x02, // Input (Data,Var,Abs) 28
+ * 0x95, 0x01, // Report Count (1) 30
+ * 0x81, 0x03, // Input (Cnst,Var,Abs) 32
+ * 0x09, 0x32, // Usage (In Range) 34
+ * 0x95, 0x01, // Report Count (1) 36
+ * 0x81, 0x02, // Input (Data,Var,Abs) 38
+ * 0x95, 0x02, // Report Count (2) 40
+ * 0x81, 0x03, // Input (Cnst,Var,Abs) 42
+ * 0x75, 0x10, // Report Size (16) 44
+ * 0x95, 0x01, // Report Count (1) 46
+ * 0x35, 0x00, // Physical Minimum (0) 48
+ * 0xa4, // Push 50
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 51
+ * 0x09, 0x30, // Usage (X) 53
+ * 0x65, 0x13, // Unit (EnglishLinear: in) 55
+ * 0x55, 0x0d, // Unit Exponent (-3) 57
+ * 0x46, 0x10, 0x27, // Physical Maximum (10000) 59
+ * 0x26, 0xff, 0x7f, // Logical Maximum (32767) 62
+ * 0x81, 0x02, // Input (Data,Var,Abs) 65
+ * 0x09, 0x31, // Usage (Y) 67
+ * 0x46, 0x6a, 0x18, // Physical Maximum (6250) 69
+ * 0x26, 0xff, 0x7f, // Logical Maximum (32767) 72
+ * 0x81, 0x02, // Input (Data,Var,Abs) 75
+ * 0xb4, // Pop 77
+ * 0x09, 0x30, // Usage (X) 78
+ * 0x45, 0x00, // Physical Maximum (0) 80
+ * 0x26, 0xff, 0x3f, // Logical Maximum (16383) 82
+ * 0x81, 0x42, // Input (Data,Var,Abs,Null) 85
+ * 0x09, 0x3d, // Usage (Start) 87
+ * 0x15, 0x81, // Logical Minimum (-127) 89 <- Change from -127 to -60
+ * 0x25, 0x7f, // Logical Maximum (127) 91 <- Change from 127 to 60
+ * 0x75, 0x08, // Report Size (8) 93
+ * 0x95, 0x01, // Report Count (1) 95
+ * 0x81, 0x02, // Input (Data,Var,Abs) 97
+ * 0x09, 0x3e, // Usage (Select) 99
+ * 0x15, 0x81, // Logical Minimum (-127) 101 <- Change from -127 to -60
+ * 0x25, 0x7f, // Logical Maximum (127) 103 <- Change from 127 to 60
+ * 0x81, 0x02, // Input (Data,Var,Abs) 105
+ * 0xc0, // End Collection 107
+ * 0xc0, // End Collection 108
+ */
+
+#define PEN_REPORT_DESCRIPTOR_LENGTH 109
+#define PAD_REPORT_DESCRIPTOR_LENGTH 102
+#define PAD_REPORT_LENGTH 8
+#define PAD_REPORT_ID 6
+#define PAD_NUM_BUTTONS 8
+
+static const __u8 fixed_rdesc_pad[] = {
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ // Byte 0 in report is the report ID
+ ReportId(PAD_REPORT_ID)
+ ReportCount(1)
+ ReportSize(8)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ // Byte 1 is the button state
+ UsagePage_Button
+ UsageMinimum_i8(0x01)
+ UsageMaximum_i8(PAD_NUM_BUTTONS)
+ LogicalMinimum_i8(0x0)
+ LogicalMaximum_i8(0x1)
+ ReportCount(PAD_NUM_BUTTONS)
+ ReportSize(1)
+ Input(Var|Abs)
+ // Byte 2 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 3/4 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 5-7 are padding so we match the original report lengtth
+ ReportCount(3)
+ ReportSize(8)
+ Input(Const)
+ )
+ )
+};
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(xppen_deco01v3_rdesc_fixup, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
+
+ const __u8 wrong_logical_range[] = {0x15, 0x81, 0x25, 0x7f};
+ const __u8 correct_logical_range[] = {0x15, 0xc4, 0x25, 0x3c};
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ switch (hctx->size) {
+ case PAD_REPORT_DESCRIPTOR_LENGTH:
+ __builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
+ return sizeof(fixed_rdesc_pad);
+ case PEN_REPORT_DESCRIPTOR_LENGTH:
+ if (__builtin_memcmp(&data[89], wrong_logical_range,
+ sizeof(wrong_logical_range)) == 0)
+ __builtin_memcpy(&data[89], correct_logical_range,
+ sizeof(correct_logical_range));
+ if (__builtin_memcmp(&data[101], wrong_logical_range,
+ sizeof(wrong_logical_range)) == 0)
+ __builtin_memcpy(&data[101], correct_logical_range,
+ sizeof(correct_logical_range));
+ break;
+ }
+
+ return 0;
+}
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(xppen_deco01v3_device_event, struct hid_bpf_ctx *hctx)
+{
+ static const __u8 pad_buttons[] = { 0x05, 0x08, 0x00, 0x2c, 0x16, 0x1d, 0x57, 0x56 };
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PAD_REPORT_LENGTH /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if (data[0] == PAD_REPORT_ID) {
+ __u8 button_mask = 0;
+ size_t d, b;
+
+ /* data[1] stores the status of BTN_2 in the 3rd bit*/
+ if (data[1] & BIT(2))
+ button_mask |= BIT(2);
+
+ /* The rest of the descriptor stores the buttons as in pad_buttons */
+ for (d = 2; d < 8; d++) {
+ for (b = 0; b < sizeof(pad_buttons); b++) {
+ if (data[d] != 0 && data[d] == pad_buttons[b])
+ button_mask |= BIT(b);
+ }
+ }
+
+ __u8 report[8] = {PAD_REPORT_ID, button_mask, 0x00};
+
+ __builtin_memcpy(data, report, sizeof(report));
+ }
+ return 0;
+}
+
+HID_BPF_OPS(xppen_deco01v3) = {
+ .hid_rdesc_fixup = (void *)xppen_deco01v3_rdesc_fixup,
+ .hid_device_event = (void *)xppen_deco01v3_device_event,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ switch (ctx->rdesc_size) {
+ case PAD_REPORT_DESCRIPTOR_LENGTH:
+ case PEN_REPORT_DESCRIPTOR_LENGTH:
+ ctx->retval = 0;
+ break;
+ default:
+ ctx->retval = -EINVAL;
+ }
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 07/10] HID: bpf: Add support for XP-Pen Deco02
2025-11-18 17:16 [PATCH 00/10] HID: bpf: sync up with current udev-hid-bpf programs Benjamin Tissoires
` (5 preceding siblings ...)
2025-11-18 17:16 ` [PATCH 06/10] HID: bpf: Add support for the XP-Pen Deco 01 V3 Benjamin Tissoires
@ 2025-11-18 17:16 ` Benjamin Tissoires
2025-11-18 17:16 ` [PATCH 08/10] HID: bpf: add heuristics to the Huion Inspiroy 2S eraser button Benjamin Tissoires
` (3 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
To: Jiri Kosina; +Cc: linux-kernel, linux-input, Benjamin Tissoires, Hannah Pittman
Modifies report to have tablet buttons report as buttons, rather than as
keyboard key combinations. The dial is also converted to a relative
input, using the dedicated bit previously reserved for modifier key
information.
Signed-off-by: Hannah Pittman <dev@hannahl.co.uk>
Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/203
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
drivers/hid/bpf/progs/XPPen__Deco02.bpf.c | 359 ++++++++++++++++++++++++++++++
1 file changed, 359 insertions(+)
diff --git a/drivers/hid/bpf/progs/XPPen__Deco02.bpf.c b/drivers/hid/bpf/progs/XPPen__Deco02.bpf.c
new file mode 100644
index 0000000000000000000000000000000000000000..4b2549031e56daf7e905db4ee1e59fcfdd8d4dae
--- /dev/null
+++ b/drivers/hid/bpf/progs/XPPen__Deco02.bpf.c
@@ -0,0 +1,359 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include "hid_report_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_UGEE 0x28BD
+#define PID_DECO_02 0x0803
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_DECO_02),
+);
+
+/*
+ * Devices are:
+ * - Pad input, including pen (This is the only one we are interested in)
+ * - Pen input as mouse
+ * - Vendor
+ *
+ * Descriptors on main device are:
+ * - 7: Pen
+ * - 6: Vendor settings? Unclear
+ * - 3: Keyboard (This is what we want to modify)
+ * - 5: Feature report
+ *
+ * This creates three event nodes:
+ * - XP-PEN DECO 02 Stylus
+ * - XP-PEN DECO 02
+ * - XP-PEN DECO 02 Keyboard (Again, what we want to modify)
+ *
+ * # Report descriptor length: 188 bytes
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 0
+ * # 0x09, 0x02, // Usage (Pen) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # 0x85, 0x07, // Report ID (7) 6
+ * # 0x09, 0x20, // Usage (Stylus) 8
+ * # 0xa1, 0x00, // Collection (Physical) 10
+ * # 0x09, 0x42, // Usage (Tip Switch) 12
+ * # 0x09, 0x44, // Usage (Barrel Switch) 14
+ * # 0x09, 0x45, // Usage (Eraser) 16
+ * # 0x09, 0x3c, // Usage (Invert) 18
+ * # 0x09, 0x32, // Usage (In Range) 20
+ * # 0x15, 0x00, // Logical Minimum (0) 22
+ * # 0x25, 0x01, // Logical Maximum (1) 24
+ * # 0x75, 0x01, // Report Size (1) 26
+ * # 0x95, 0x05, // Report Count (5) 28
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 30
+ * # 0x95, 0x03, // Report Count (3) 32
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 34
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 36
+ * # 0x09, 0x30, // Usage (X) 38
+ * # 0x15, 0x00, // Logical Minimum (0) 40
+ * # 0x26, 0x50, 0x57, // Logical Maximum (22352) 42
+ * # 0x55, 0x0d, // Unit Exponent (-3) 45
+ * # 0x65, 0x13, // Unit (EnglishLinear: in) 47
+ * # 0x35, 0x00, // Physical Minimum (0) 49
+ * # 0x46, 0x50, 0x57, // Physical Maximum (22352) 51
+ * # 0x75, 0x10, // Report Size (16) 54
+ * # 0x95, 0x01, // Report Count (1) 56
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 58
+ * # 0x09, 0x31, // Usage (Y) 60
+ * # 0x15, 0x00, // Logical Minimum (0) 62
+ * # 0x26, 0x92, 0x36, // Logical Maximum (13970) 64
+ * # 0x55, 0x0d, // Unit Exponent (-3) 67
+ * # 0x65, 0x13, // Unit (EnglishLinear: in) 69
+ * # 0x35, 0x00, // Physical Minimum (0) 71
+ * # 0x46, 0x92, 0x36, // Physical Maximum (13970) 73
+ * # 0x75, 0x10, // Report Size (16) 76
+ * # 0x95, 0x01, // Report Count (1) 78
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 80
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 82
+ * # 0x09, 0x30, // Usage (Tip Pressure) 84
+ * # 0x15, 0x00, // Logical Minimum (0) 86
+ * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 88
+ * # 0x75, 0x10, // Report Size (16) 91
+ * # 0x95, 0x01, // Report Count (1) 93
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 95
+ * # 0xc0, // End Collection 97
+ * # 0xc0, // End Collection 98
+ * # 0x09, 0x0e, // Usage (Device Configuration) 99
+ * # 0xa1, 0x01, // Collection (Application) 101
+ * # 0x85, 0x05, // Report ID (5) 103
+ * # 0x09, 0x23, // Usage (Device Settings) 105
+ * # 0xa1, 0x02, // Collection (Logical) 107
+ * # 0x09, 0x52, // Usage (Inputmode) 109
+ * # 0x09, 0x53, // Usage (Device Index) 111
+ * # 0x25, 0x0a, // Logical Maximum (10) 113
+ * # 0x75, 0x08, // Report Size (8) 115
+ * # 0x95, 0x02, // Report Count (2) 117
+ * # 0xb1, 0x02, // Feature (Data,Var,Abs) 119
+ * # 0xc0, // End Collection 121
+ * # 0xc0, // End Collection 122
+ * # 0x05, 0x0c, // Usage Page (Consumer Devices) 123
+ * # 0x09, 0x36, // Usage (Function Buttons) 125
+ * # 0xa1, 0x00, // Collection (Physical) 127
+ * # 0x85, 0x06, // Report ID (6) 129
+ * # 0x05, 0x09, // Usage Page (Button) 131
+ * # 0x19, 0x01, // Usage Minimum (1) 133
+ * # 0x29, 0x20, // Usage Maximum (32) 135
+ * # 0x15, 0x00, // Logical Minimum (0) 137
+ * # 0x25, 0x01, // Logical Maximum (1) 139
+ * # 0x95, 0x20, // Report Count (32) 141
+ * # 0x75, 0x01, // Report Size (1) 143
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 145
+ * # 0xc0, // End Collection 147
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 148
+ * # 0x09, 0x06, // Usage (Keyboard) 150
+ * # 0xa1, 0x01, // Collection (Application) 152
+ * # 0x85, 0x03, // Report ID (3) 154
+ * # 0x05, 0x07, // Usage Page (Keyboard) 156
+ * # 0x19, 0xe0, // Usage Minimum (224) 158
+ * # 0x29, 0xe7, // Usage Maximum (231) 160
+ * # 0x15, 0x00, // Logical Minimum (0) 162
+ * # 0x25, 0x01, // Logical Maximum (1) 164
+ * # 0x75, 0x01, // Report Size (1) 166
+ * # 0x95, 0x08, // Report Count (8) 168
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 170
+ * # 0x05, 0x07, // Usage Page (Keyboard) 172
+ * # 0x19, 0x00, // Usage Minimum (0) 174
+ * # 0x29, 0xff, // Usage Maximum (255) 176
+ * # 0x26, 0xff, 0x00, // Logical Maximum (255) 178
+ * # 0x75, 0x08, // Report Size (8) 181
+ * # 0x95, 0x06, // Report Count (6) 183
+ * # 0x81, 0x00, // Input (Data,Arr,Abs) 185
+ * # 0xc0, // End Collection 187
+ *
+ * Key events; top to bottom:
+ * Buttons released: 03 00 00 00 00 00 00 00
+ * Button1: 03 00 05 00 00 00 00 00 -> 'b and B'
+ * Button2: 03 00 2c 00 00 00 00 00 -> 'Spacebar'
+ * Button3: 03 00 08 00 00 00 00 00 -> 'e and E'
+ * Button4: 03 00 0c 00 00 00 00 00 -> 'i and I'
+ * Button5: 03 05 1d 00 00 00 00 00 -> LeftControl + LeftAlt + 'z and Z'
+ * Button6: 03 01 16 00 00 00 00 00 -> LeftControl + 's and S'
+ *
+ * Dial Events:
+ * Clockwise: 03 01 2e 00 00 00 00 00 -> LeftControl + '= and +'
+ * Anticlockwise: 03 01 2d 00 00 00 00 00 -> LeftControl + '- and (underscore)'
+ *
+ * NOTE: Input event descriptions begin at byte 2, and progressively build
+ * towards byte 7 as each new key is pressed maintaining the press order.
+ * For example:
+ * BTN1 followed by BTN2 is 03 00 05 2c 00 00 00 00
+ * BTN2 followed by BTN1 is 03 00 2c 05 00 00 00 00
+ *
+ * Releasing a button causes its byte to be freed, and the next item in the list
+ * is pushed forwards. Dial events are released immediately after an event is
+ * registered (i.e. after each "click"), so will continually appear pushed
+ * backwards in the report.
+ *
+ * When a button with a modifier key is pressed, the button identifier stacks in
+ * an abnormal way, where the highest modifier byte always supersedes others.
+ * In these cases, the button with the higher modifier is always last.
+ * For example:
+ * BTN6 followed by BTN5 is 03 05 1d 16 00 00 00 00
+ * BTN5 followed by BTN6 is 03 05 1d 16 00 00 00 00
+ * BTN5 followed by BTN1 is 03 05 05 1d 00 00 00 00
+ *
+ * For three button presses in order, demonstrating strictly above rules:
+ * BTN6, BTN1, BTN5 is 03 05 05 1d 16 00 00 00
+ * BTN5, BTN1, BTN6 is 03 05 05 1d 16 00 00 00
+ *
+ * In short, when BTN5/6 are pressed, the order of operations is lost, as they
+ * will always float to the end when pressed in combination with others.
+ *
+ * Fortunately, all states are recorded in the same way, with no overlaps.
+ * Byte 1 can be used as a spare for the wheel, since this is for mod keys.
+ */
+
+#define RDESC_SIZE_PAD 188
+#define REPORT_SIZE_PAD 8
+#define REPORT_ID_BUTTONS 3
+#define PAD_BUTTON_COUNT 6
+#define RDESC_KEYBOARD_OFFSET 148
+
+static const __u8 fixed_rdesc_pad[] = {
+ /* Copy of pen descriptor to avoid losing functionality */
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionApplication(
+ ReportId(7)
+ Usage_Dig_Stylus
+ CollectionPhysical(
+ Usage_Dig_TipSwitch
+ Usage_Dig_BarrelSwitch
+ Usage_Dig_Eraser
+ Usage_Dig_Invert
+ Usage_Dig_InRange
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportSize(1)
+ ReportCount(5)
+ Input(Var|Abs)
+ ReportCount(3)
+ Input(Const) /* Input (Const, Var, Abs) */
+ UsagePage_GenericDesktop
+ Usage_GD_X
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(22352)
+ UnitExponent(-3)
+ Unit(in) /* (EnglishLinear: in) */
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(22352)
+ ReportSize(16)
+ ReportCount(1)
+ Input(Var|Abs)
+ Usage_GD_Y
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(13970)
+ UnitExponent(-3)
+ Unit(in) /* (EnglishLinear: in) */
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(13970)
+ ReportSize(16)
+ ReportCount(1)
+ Input(Var|Abs)
+ UsagePage_Digitizers
+ Usage_Dig_TipPressure
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(8191)
+ ReportSize(16)
+ ReportCount(1)
+ Input(Var|Abs)
+ )
+ )
+
+ /* FIXES BEGIN */
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ ReportId(REPORT_ID_BUTTONS) /* Retain original ID on byte 0 */
+ ReportCount(1)
+ ReportSize(REPORT_SIZE_PAD)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ /* Byte 1: Dial state */
+ UsagePage_GenericDesktop
+ Usage_GD_Dial
+ LogicalMinimum_i8(-1)
+ LogicalMaximum_i8(1)
+ ReportCount(1)
+ ReportSize(REPORT_SIZE_PAD)
+ Input(Var|Rel)
+ /* Byte 2: Button state */
+ UsagePage_Button
+ ReportSize(1)
+ ReportCount(PAD_BUTTON_COUNT)
+ UsageMinimum_i8(0x01)
+ UsageMaximum_i8(PAD_BUTTON_COUNT) /* Number of buttons */
+ LogicalMinimum_i8(0x0)
+ LogicalMaximum_i8(0x1)
+ Input(Var|Abs)
+ /* Byte 3: Exists to be tablet pad */
+ UsagePage_Digitizers
+ Usage_Dig_BarrelSwitch
+ ReportCount(1)
+ ReportSize(1)
+ Input(Var|Abs)
+ ReportCount(7) /* Padding, to fill full report space */
+ Input(Const)
+ /* Byte 4/5: Exists to be a tablet pad */
+ UsagePage_GenericDesktop
+ Usage_GD_X
+ Usage_GD_Y
+ ReportCount(2)
+ ReportSize(8)
+ Input(Var|Abs)
+ /* Bytes 6/7: Padding, to match original length */
+ ReportCount(2)
+ ReportSize(8)
+ Input(Const)
+ )
+ FixedSizeVendorReport(RDESC_SIZE_PAD)
+ )
+};
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(xppen_deco02_rdesc_fixup, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0, HID_MAX_DESCRIPTOR_SIZE);
+
+ if (!data)
+ return 0; /* EPERM Check */
+
+ if (hctx->size == RDESC_SIZE_PAD) {
+ __builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
+ return sizeof(fixed_rdesc_pad);
+ }
+
+ return 0;
+}
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(xppen_deco02_device_event, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0, REPORT_SIZE_PAD);
+
+ if (!data || data[0] != REPORT_ID_BUTTONS)
+ return 0; /* EPERM or wrong report */
+
+ __u8 dial_code = 0;
+ __u8 button_mask = 0;
+ size_t d;
+
+ /* Start from 2; 0 is report ID, 1 is modifier keys, replaced by dial */
+ for (d = 2; d < 8; d++) {
+ switch (data[d]) {
+ case 0x2e:
+ dial_code = 1;
+ break;
+ case 0x2d:
+ dial_code = -1;
+ break;
+ /* below are buttons, top to bottom */
+ case 0x05:
+ button_mask |= BIT(0);
+ break;
+ case 0x2c:
+ button_mask |= BIT(1);
+ break;
+ case 0x08:
+ button_mask |= BIT(2);
+ break;
+ case 0x0c:
+ button_mask |= BIT(3);
+ break;
+ case 0x1d:
+ button_mask |= BIT(4);
+ break;
+ case 0x16:
+ button_mask |= BIT(05);
+ break;
+ default:
+ break;
+ }
+ }
+
+ __u8 report[8] = { REPORT_ID_BUTTONS, dial_code, button_mask, 0x00 };
+
+ __builtin_memcpy(data, report, sizeof(report));
+ return 0;
+}
+
+HID_BPF_OPS(xppen_deco02) = {
+ .hid_rdesc_fixup = (void *)xppen_deco02_rdesc_fixup,
+ .hid_device_event = (void *)xppen_deco02_device_event,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ ctx->retval = ctx->rdesc_size != RDESC_SIZE_PAD ? -EINVAL : 0;
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 08/10] HID: bpf: add heuristics to the Huion Inspiroy 2S eraser button
2025-11-18 17:16 [PATCH 00/10] HID: bpf: sync up with current udev-hid-bpf programs Benjamin Tissoires
` (6 preceding siblings ...)
2025-11-18 17:16 ` [PATCH 07/10] HID: bpf: Add support for XP-Pen Deco02 Benjamin Tissoires
@ 2025-11-18 17:16 ` Benjamin Tissoires
2025-11-18 17:16 ` [PATCH 09/10] HID: bpf: add the Huion Kamvas 27 Pro Benjamin Tissoires
` (2 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
To: Jiri Kosina; +Cc: linux-kernel, linux-input, Benjamin Tissoires, Peter Hutterer
When pressing the phsyical eraser button (remapped by us to the
Secondary Barrel Switch) while the tip is down, the device
gives us several false reports with a Tip Switch 0:
press| |release
SBS: [0 0 ... 1 1 1 ... 1 0 0 0 0 0 0 ...]
TS: [1 1 ... 1 0 1 ... 1 1 0 0 0 1 1 ...]
In both press/release the number of Tip Switch 0 reports can be up to 4
and *sometimes* the Tip Switch is released in the same report
as the button press/release event.
Paper over this by forcing the tip down for a few reports if it was down
before the button toggled.
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/195
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
drivers/hid/bpf/progs/Huion__Inspiroy-2-S.bpf.c | 29 +++++++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/drivers/hid/bpf/progs/Huion__Inspiroy-2-S.bpf.c b/drivers/hid/bpf/progs/Huion__Inspiroy-2-S.bpf.c
index 13f64fb49800b16f6d4d48c378f065fcdf51202a..79453362bf979f42559d6c57e9641a27cda0af40 100644
--- a/drivers/hid/bpf/progs/Huion__Inspiroy-2-S.bpf.c
+++ b/drivers/hid/bpf/progs/Huion__Inspiroy-2-S.bpf.c
@@ -163,6 +163,9 @@ char EXPECTED_FIRMWARE_ID[] = "HUION_T21j_";
__u8 last_button_state;
+__u8 last_tip_state;
+__u8 last_sec_barrel_state;
+__u8 force_tip_down_count;
static const __u8 fixed_rdesc_pad[] = {
UsagePage_GenericDesktop
@@ -522,9 +525,31 @@ int BPF_PROG(inspiroy_2_fix_events, struct hid_bpf_ctx *hctx)
pad_report->wheel = wheel;
return sizeof(struct pad_report);
- }
+ } else if (data[1] & 0x80) { /* Pen reports with InRange 1 */
+ __u8 tip_state = data[1] & 0x1;
+ __u8 sec_barrel_state = data[1] & 0x4;
+
+ if (force_tip_down_count > 0) {
+ data[1] |= 0x1;
+ --force_tip_down_count;
+ if (tip_state)
+ force_tip_down_count = 0;
+ }
- /* Pen reports need nothing done */
+ /* Tip was down and we just pressed or released the
+ * secondary barrel switch (the physical eraser
+ * button). The device will send up to 4
+ * reports with Tip Switch 0 and sometimes
+ * this report has Tip Switch 0.
+ */
+ if (last_tip_state &&
+ last_sec_barrel_state != sec_barrel_state) {
+ force_tip_down_count = 4;
+ data[1] |= 0x1;
+ }
+ last_tip_state = tip_state;
+ last_sec_barrel_state = sec_barrel_state;
+ }
}
return 0;
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 09/10] HID: bpf: add the Huion Kamvas 27 Pro
2025-11-18 17:16 [PATCH 00/10] HID: bpf: sync up with current udev-hid-bpf programs Benjamin Tissoires
` (7 preceding siblings ...)
2025-11-18 17:16 ` [PATCH 08/10] HID: bpf: add heuristics to the Huion Inspiroy 2S eraser button Benjamin Tissoires
@ 2025-11-18 17:16 ` Benjamin Tissoires
2025-11-18 17:16 ` [PATCH 10/10] HID: bpf: fix typo in HID usage table Benjamin Tissoires
2025-11-20 15:46 ` [PATCH 00/10] HID: bpf: sync up with current udev-hid-bpf programs Jiri Kosina
10 siblings, 0 replies; 12+ messages in thread
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
To: Jiri Kosina; +Cc: linux-kernel, linux-input, Benjamin Tissoires, Peter Hutterer
Same issues with a secondary tip switch instead of secondary barrel
switch as the Kamvas 19.
Copy the stable Kamvas 19 support back into testing and add the vid/pid
for the Kamvas 27.
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/189
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c b/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c
index 489cb4fcc2cd2702e4d996ad9435ba0ca454b6d9..5f43e407184854d860eeed4e857acee8bcbd272d 100644
--- a/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c
+++ b/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c
@@ -9,12 +9,15 @@
#define VID_HUION 0x256C
#define PID_KAMVAS_PRO_19 0x006B
+#define PID_KAMVAS_PRO_27 0x006c
#define NAME_KAMVAS_PRO_19 "HUION Huion Tablet_GT1902"
+#define NAME_KAMVAS_PRO_27 "HUION Huion Tablet_GT2701"
#define TEST_PREFIX "uhid test "
HID_BPF_CONFIG(
HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, VID_HUION, PID_KAMVAS_PRO_19),
+ HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, VID_HUION, PID_KAMVAS_PRO_27),
);
bool prev_was_out_of_range;
@@ -351,7 +354,8 @@ int probe(struct hid_bpf_probe_args *ctx)
if (!__builtin_memcmp(name, TEST_PREFIX, sizeof(TEST_PREFIX) - 1))
name += sizeof(TEST_PREFIX) - 1;
- if (__builtin_memcmp(name, NAME_KAMVAS_PRO_19, sizeof(NAME_KAMVAS_PRO_19)))
+ if (__builtin_memcmp(name, NAME_KAMVAS_PRO_19, sizeof(NAME_KAMVAS_PRO_19)) &&
+ __builtin_memcmp(name, NAME_KAMVAS_PRO_27, sizeof(NAME_KAMVAS_PRO_27)))
ctx->retval = -EINVAL;
hid_bpf_release_context(hctx);
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 10/10] HID: bpf: fix typo in HID usage table
2025-11-18 17:16 [PATCH 00/10] HID: bpf: sync up with current udev-hid-bpf programs Benjamin Tissoires
` (8 preceding siblings ...)
2025-11-18 17:16 ` [PATCH 09/10] HID: bpf: add the Huion Kamvas 27 Pro Benjamin Tissoires
@ 2025-11-18 17:16 ` Benjamin Tissoires
2025-11-20 15:46 ` [PATCH 00/10] HID: bpf: sync up with current udev-hid-bpf programs Jiri Kosina
10 siblings, 0 replies; 12+ messages in thread
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
To: Jiri Kosina
Cc: linux-kernel, linux-input, Benjamin Tissoires, Colin Ian King,
Benjamin Tissoires
We could go to the USB consortium, but it's probably
easier that way.
And update HID usage table json generated file from
https://usb.org/sites/default/files/hut1_6.pdf updated: 01/30/2025
Reported-by: Colin Ian King <colin.i.king@gmail.com>
Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/191
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@gmail.com>
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
drivers/hid/bpf/progs/hid_report_helpers.h | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/drivers/hid/bpf/progs/hid_report_helpers.h b/drivers/hid/bpf/progs/hid_report_helpers.h
index 9b2a48e4a311b39ca20e7d9819beef8fb70315a6..9944ff54d31d27d664be554afed2ce3710b48c75 100644
--- a/drivers/hid/bpf/progs/hid_report_helpers.h
+++ b/drivers/hid/bpf/progs/hid_report_helpers.h
@@ -143,8 +143,11 @@
* report with Report ID 0xac of the given size in bytes.
* The size is inclusive of the 1 byte Report ID prefix.
*
- * HID-BPF requires that at least one report has
- * the same size as the original report from the device.
+ * The kernel discards any HID reports that are larger
+ * than the largest report in a HID report descriptor.
+ * Thus at least one report must have (at least)
+ * the same size as the largest original report from
+ * the device.
* The easy way to ensure that is to add this
* macro as the last element of your CollectionApplication
* other reports can be of any size less than this.
@@ -295,6 +298,7 @@
#define Usage_GD_SystemSpeakerMute Usage_i8(0xa7)
#define Usage_GD_SystemHibernate Usage_i8(0xa8)
#define Usage_GD_SystemMicrophoneMute Usage_i8(0xa9)
+#define Usage_GD_SystemAccessibilityBinding Usage_i8(0xaa)
#define Usage_GD_SystemDisplayInvert Usage_i8(0xb0)
#define Usage_GD_SystemDisplayInternal Usage_i8(0xb1)
#define Usage_GD_SystemDisplayExternal Usage_i8(0xb2)
@@ -2669,7 +2673,7 @@
#define Usage_BS_iDeviceName Usage_i8(0x88)
#define Usage_BS_iDeviceChemistry Usage_i8(0x89)
#define Usage_BS_ManufacturerData Usage_i8(0x8a)
-#define Usage_BS_Rechargable Usage_i8(0x8b)
+#define Usage_BS_Rechargeable Usage_i8(0x8b)
#define Usage_BS_WarningCapacityLimit Usage_i8(0x8c)
#define Usage_BS_CapacityGranularity1 Usage_i8(0x8d)
#define Usage_BS_CapacityGranularity2 Usage_i8(0x8e)
--
2.51.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH 00/10] HID: bpf: sync up with current udev-hid-bpf programs
2025-11-18 17:16 [PATCH 00/10] HID: bpf: sync up with current udev-hid-bpf programs Benjamin Tissoires
` (9 preceding siblings ...)
2025-11-18 17:16 ` [PATCH 10/10] HID: bpf: fix typo in HID usage table Benjamin Tissoires
@ 2025-11-20 15:46 ` Jiri Kosina
10 siblings, 0 replies; 12+ messages in thread
From: Jiri Kosina @ 2025-11-20 15:46 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: linux-kernel, linux-input, Peter Hutterer, Nicholas LaPointe,
Higgins Dragon, Curran Muhlberger, Jan Felix Langenbach,
Hannah Pittman, Colin Ian King
On Tue, 18 Nov 2025, Benjamin Tissoires wrote:
> As I need to get a new release of udev-hid-bpf, it would be good to
> mark the currently "testing" HID-BPF programs into stable.
>
> So I've taken all of them, formatted them to the LKML format and sent
> here.
>
> Again, no need to backport any of those into stable, we are putting them
> here mostly for code archiving purpose and have a public central point
> for them.
>
> Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
> ---
> Benjamin Tissoires (10):
> HID: bpf: Add support for the Inspiroy 2M
> HID: bpf: add support for Huion Kamvas 13 (Gen 3) (model GS1333)
> HID: bpf: support for Huion Kamvas 16 Gen 3
> HID: bpf: Add fixup for Logitech SpaceNavigator variants
> HID: bpf: Add support for the Waltop Batteryless Tablet
> HID: bpf: Add support for the XP-Pen Deco 01 V3
> HID: bpf: Add support for XP-Pen Deco02
> HID: bpf: add heuristics to the Huion Inspiroy 2S eraser button
> HID: bpf: add the Huion Kamvas 27 Pro
> HID: bpf: fix typo in HID usage table
Now in hid.git#for-6.19/hid-bpf, thanks!
--
Jiri Kosina
SUSE Labs
^ permalink raw reply [flat|nested] 12+ messages in thread