Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH 01/10] HID: bpf: Add support for the Inspiroy 2M
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
In-Reply-To: <20251118-wip-sync-udev-hid-bpf-v1-0-0f8105c54835@kernel.org>

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

* [PATCH 02/10] HID: bpf: add support for Huion Kamvas 13 (Gen 3) (model GS1333)
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
  To: Jiri Kosina
  Cc: linux-kernel, linux-input, Benjamin Tissoires, Nicholas LaPointe
In-Reply-To: <20251118-wip-sync-udev-hid-bpf-v1-0-0f8105c54835@kernel.org>

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

* [PATCH 03/10] HID: bpf: support for Huion Kamvas 16 Gen 3
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
In-Reply-To: <20251118-wip-sync-udev-hid-bpf-v1-0-0f8105c54835@kernel.org>

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

* [PATCH 04/10] HID: bpf: Add fixup for Logitech SpaceNavigator variants
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
  To: Jiri Kosina
  Cc: linux-kernel, linux-input, Benjamin Tissoires, Curran Muhlberger
In-Reply-To: <20251118-wip-sync-udev-hid-bpf-v1-0-0f8105c54835@kernel.org>

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

* [PATCH 05/10] HID: bpf: Add support for the Waltop Batteryless Tablet
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
In-Reply-To: <20251118-wip-sync-udev-hid-bpf-v1-0-0f8105c54835@kernel.org>

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

* [PATCH 06/10] HID: bpf: Add support for the XP-Pen Deco 01 V3
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: linux-kernel, linux-input, Benjamin Tissoires, Peter Hutterer
In-Reply-To: <20251118-wip-sync-udev-hid-bpf-v1-0-0f8105c54835@kernel.org>

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

* [PATCH 07/10] HID: bpf: Add support for XP-Pen Deco02
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: linux-kernel, linux-input, Benjamin Tissoires, Hannah Pittman
In-Reply-To: <20251118-wip-sync-udev-hid-bpf-v1-0-0f8105c54835@kernel.org>

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

* [PATCH 08/10] HID: bpf: add heuristics to the Huion Inspiroy 2S eraser button
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: linux-kernel, linux-input, Benjamin Tissoires, Peter Hutterer
In-Reply-To: <20251118-wip-sync-udev-hid-bpf-v1-0-0f8105c54835@kernel.org>

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

* [PATCH 09/10] HID: bpf: add the Huion Kamvas 27 Pro
From: Benjamin Tissoires @ 2025-11-18 17:16 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: linux-kernel, linux-input, Benjamin Tissoires, Peter Hutterer
In-Reply-To: <20251118-wip-sync-udev-hid-bpf-v1-0-0f8105c54835@kernel.org>

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

* [PATCH 10/10] HID: bpf: fix typo in HID usage table
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
In-Reply-To: <20251118-wip-sync-udev-hid-bpf-v1-0-0f8105c54835@kernel.org>

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

* Re: [PATCH v3 04/11] Input: ilitek_ts_i2c: fix warning with gpio controllers that sleep
From: Dmitry Torokhov @ 2025-11-18 19:22 UTC (permalink / raw)
  To: Josua Mayer
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Shawn Guo,
	Neil Armstrong, Jessica Zhang, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam, Laurent Pinchart,
	Lad Prabhakar, Thierry Reding, Jon Nettleton, Mikhail Anikin,
	Yazan Shhady, devicetree, linux-kernel, dri-devel, linux-input,
	imx, linux-arm-kernel
In-Reply-To: <20251117-imx8mp-hb-iiot-v3-4-bf1a4cf5fa8e@solid-run.com>

Hi Josua,

On Mon, Nov 17, 2025 at 01:28:46PM +0100, Josua Mayer wrote:
> The ilitek touchscreen driver uses the non-sleeping gpiod_set_value
> function for reset.
> 
> Switch to using gpiod_set_value_cansleep() when controlling reset_gpio to
> support GPIO providers that may sleep, such as I2C GPIO expanders.
> 
> This fixes noisy complaints in kernel log for gpio providers that do
> sleep.
> 
> Signed-off-by: Josua Mayer <josua@solid-run.com>
> ---
>  drivers/input/touchscreen/ilitek_ts_i2c.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/input/touchscreen/ilitek_ts_i2c.c b/drivers/input/touchscreen/ilitek_ts_i2c.c
> index 0dd632724a003..8c5a54b336816 100644
> --- a/drivers/input/touchscreen/ilitek_ts_i2c.c
> +++ b/drivers/input/touchscreen/ilitek_ts_i2c.c
> @@ -396,9 +396,9 @@ static const struct ilitek_protocol_map ptl_func_map[] = {
>  static void ilitek_reset(struct ilitek_ts_data *ts, int delay)
>  {
>  	if (ts->reset_gpio) {
> -		gpiod_set_value(ts->reset_gpio, 1);
> +		gpiod_set_value_cansleep(ts->reset_gpio, 1);
>  		mdelay(10);

This (and below) should be usleep_range/msleep/fsleep.

> -		gpiod_set_value(ts->reset_gpio, 0);
> +		gpiod_set_value_cansleep(ts->reset_gpio, 0);
>  		mdelay(delay);
>  	}
>  }
> 

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH] Input: pixcir_i2c_ts - add support for one-time total calibration
From: Dmitry Torokhov @ 2025-11-18 19:26 UTC (permalink / raw)
  To: Michal Vokáč; +Cc: linux-input, linux-kernel, Fabio Estevam
In-Reply-To: <20251112130019.1488005-1-michal.vokac@ysoft.com>

Hi Michal,

On Wed, Nov 12, 2025 at 02:00:19PM +0100, Michal Vokáč wrote:
> The Pixcir Tango controller has support for a one-time total calibration
> (manual calibration) procedure. Its purpose is to measure the capacitance
> offsets of the electrode system and to store these values into EEPROM.
> 
> During normal operation this calibration data is subtracted from the values
> measured. This calibration should be necessary only once in the product
> lifetime. It should be performed as part of the final adjustment after
> the panel is mounted in the product.
> 
> Add support for the calibration with sysfs interface.
> 
> Signed-off-by: Michal Vokáč <michal.vokac@ysoft.com>
> ---
>  drivers/input/touchscreen/pixcir_i2c_ts.c | 34 +++++++++++++++++++++++
>  1 file changed, 34 insertions(+)
> 
> diff --git a/drivers/input/touchscreen/pixcir_i2c_ts.c b/drivers/input/touchscreen/pixcir_i2c_ts.c
> index dad5786e82a4..2215e56b1458 100644
> --- a/drivers/input/touchscreen/pixcir_i2c_ts.c
> +++ b/drivers/input/touchscreen/pixcir_i2c_ts.c
> @@ -24,6 +24,7 @@
>   */
>  #define PIXCIR_REG_POWER_MODE	51
>  #define PIXCIR_REG_INT_MODE	52
> +#define PIXCIR_REG_SPECOP	58
>  
>  /*
>   * Power modes:
> @@ -82,6 +83,7 @@ struct pixcir_i2c_ts_data {
>  	const struct pixcir_i2c_chip_data *chip;
>  	struct touchscreen_properties prop;
>  	bool running;
> +	struct mutex sysfs_mutex;
>  };
>  
>  struct pixcir_report_data {
> @@ -462,6 +464,35 @@ static int pixcir_i2c_ts_resume(struct device *dev)
>  static DEFINE_SIMPLE_DEV_PM_OPS(pixcir_dev_pm_ops,
>  				pixcir_i2c_ts_suspend, pixcir_i2c_ts_resume);
>  
> +static ssize_t calibrate_store(struct device *dev,
> +			       struct device_attribute *attr,
> +			       const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct pixcir_i2c_ts_data *ts = i2c_get_clientdata(client);
> +	static const u8 cmd = 0x03;
> +	int error;
> +
> +	error = mutex_lock_interruptible(&ts->sysfs_mutex);
> +	if (error)
> +		return error;

Why do we need this mutex? i2c_smbus_write_byte_data() does take adapter
lock, why do we need this additional locking?

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH v8 01/10] HID: asus: simplify RGB init sequence
From: Denis Benato @ 2025-11-18 23:17 UTC (permalink / raw)
  To: Antheas Kapenekakis, Ilpo Järvinen
  Cc: platform-driver-x86, linux-input, LKML, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Luke D . Jones, Hans de Goede
In-Reply-To: <CAGwozwEen-gfuSKhRFXD3i7RpZUVWED+EJnA8R1aYYNs2SaWcA@mail.gmail.com>


On 11/18/25 14:07, Antheas Kapenekakis wrote:
> On Tue, 18 Nov 2025 at 13:23, Ilpo Järvinen
> <ilpo.jarvinen@linux.intel.com> wrote:
>> On Sat, 1 Nov 2025, Antheas Kapenekakis wrote:
>>
>>> Currently, RGB initialization forks depending on whether a device is
>>> NKEY. Then, NKEY devices are initialized using 0x5a, 0x5d, 0x5e
>>> endpoints, and non-NKEY devices with 0x5a and then a
>>> backlight check, which is omitted for NKEY devices.
>>>
>>> Remove the fork, using a common initialization sequence for both,
>>> where they are both only initialized with 0x5a, then checked for
>>> backlight support. This patch should not affect existing functionality.
>>>
>>> 0x5d and 0x5e endpoint initializations are performed by Windows
>>> userspace programs associated with different usages that reside under
>>> the vendor HID. Specifically, 0x5d is used by Armoury Crate, which
>>> controls RGB and 0x5e by an animation program for certain Asus laptops.
>>> Neither is used currently in the driver.
This patch is, as requested, not touching 0x5d and 0x5e so why mentioning them at all?

This text has become out of context IMHO.
>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>>> ---
>>>  drivers/hid/hid-asus.c | 56 ++++++++++++++----------------------------
>>>  1 file changed, 19 insertions(+), 37 deletions(-)
>>>
>>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
>>> index a444d41e53b6..7ea1037c3979 100644
>>> --- a/drivers/hid/hid-asus.c
>>> +++ b/drivers/hid/hid-asus.c
>>> @@ -638,50 +638,32 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
>>>       unsigned char kbd_func;
>>>       int ret;
>>>
>>> -     if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
>>> -             /* Initialize keyboard */
>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
>>> -             if (ret < 0)
>>> -                     return ret;
>>> -
>>> -             /* The LED endpoint is initialised in two HID */
>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
>>> -             if (ret < 0)
>>> -                     return ret;
>>> -
>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
>>> -             if (ret < 0)
>>> -                     return ret;
>> This removal of FEATURE_KBD_LED_REPORT_ID2 should definitely be in own
>> patch so a focused changelog can be written to it (who said it's
>> acceptable, etc. included) and it can be pinpointed with bisect if it
>> causes any issues (but I'm not entirely sure what Denis' stance is on this
>> removal so it's going to be subject to some further scrunity, but in any
>> case it must be in own patch if to be accepted).
> Yeah this is a fair approach.
You unified the init interface, but I see that there was divergence
that now is gone:  both FEATURE_KBD_LED_REPORT_ID1 and
FEATURE_KBD_LED_REPORT_ID2 are not used in the initialization
now: I can only see two reasons for this:
- you don't think those are needed
- they will cause problems with asus_kbd_init that is changed in patch 3

What's the given reason here?

You can pretty much re-introduce the if (quirk) { those_two_init }
after the asus_kbd_get_functions as I don't see any reasons to remove that.

If we can't verify with hardware using it that quirk I think we can modify
these to not fail if the HID device doesn't answer back with expected data:
I am cross-referencing patch 3 here.

>> I'm wondering though if QUIRK_ROG_NKEY_LEGACY could also cover this? At
>> least it would look a path of less resistance when it comes to Denis'
>> stance and would decouple the remove or not controversy from acceptance of
>> this series.
Since what I see here is a (qurks excluded) mostly unified path for
all devices I will have to review that, but I already said that if we find
some common denominator to newer devices that could help us
in changing the flow specifically to those I am also open to this
possibility.
> So a way forward would be on the simplify patch, to use an if rog
> statement to do ID1 and ID2 and then on the next patch replace the
> quirk in the if check with the legacy quirk. This is ok with me, as
> those are older devices.
After the fourth time of re-reading this I have no idea what you are
proposing here. Sorry.
> Antheas
I am slowly going through patches and sending comments while also
reviewing them with more detail.
>> --
>>  i.
>>
>>> -             if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
>>> -                     ret = asus_kbd_disable_oobe(hdev);
>>> -                     if (ret < 0)
>>> -                             return ret;
>>> -             }
>>> -
>>> -             if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
>>> -                     intf = to_usb_interface(hdev->dev.parent);
>>> -                     udev = interface_to_usbdev(intf);
>>> -                     validate_mcu_fw_version(hdev,
>>> -                             le16_to_cpu(udev->descriptor.idProduct));
>>> -             }
>>> +     ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
>>> +     if (ret < 0)
>>> +             return ret;
>>>
>>> -     } else {
>>> -             /* Initialize keyboard */
>>> -             ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
>>> -             if (ret < 0)
>>> -                     return ret;
>>> +     /* Get keyboard functions */
>>> +     ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
>>> +     if (ret < 0)
>>> +             return ret;
>>>
>>> -             /* Get keyboard functions */
>>> -             ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
>>> +     if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
>>> +             ret = asus_kbd_disable_oobe(hdev);
>>>               if (ret < 0)
>>>                       return ret;
>>> +     }
>>>
>>> -             /* Check for backlight support */
>>> -             if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
>>> -                     return -ENODEV;
>>> +     if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
>>> +             intf = to_usb_interface(hdev->dev.parent);
>>> +             udev = interface_to_usbdev(intf);
>>> +             validate_mcu_fw_version(
>>> +                     hdev, le16_to_cpu(udev->descriptor.idProduct));
>>>       }
>>>
>>> +     /* Check for backlight support */
>>> +     if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
>>> +             return -ENODEV;
>>> +
>>>       drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
>>>                                             sizeof(struct asus_kbd_leds),
>>>                                             GFP_KERNEL);
>>>

^ permalink raw reply

* Re: [PATCH v8 02/10] HID: asus: use same report_id in response
From: Denis Benato @ 2025-11-18 23:20 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Hans de Goede, Ilpo Järvinen
In-Reply-To: <20251101104712.8011-3-lkml@antheas.dev>


On 11/1/25 11:47, Antheas Kapenekakis wrote:
> Currently, asus_kbd_get_functions prods the device using feature
> report report_id, but then is hardcoded to check the response through
> FEATURE_KBD_REPORT_ID. This only works if report_id is that value
> (currently true). So, use report_id in the response as well to
> maintain functionality if that value changes in the future.
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
>  drivers/hid/hid-asus.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index 7ea1037c3979..4676b7f20caf 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -422,7 +422,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
>  	if (!readbuf)
>  		return -ENOMEM;
>  
> -	ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
> +	ret = hid_hw_raw_request(hdev, report_id, readbuf,
>  				 FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
>  				 HID_REQ_GET_REPORT);
>  	if (ret < 0) {
Why is this patch even here? This should have been sent as a fix on its own
months ago.

Reviewed-by: Denis Benato <benato.denis96@gmail.com>


^ permalink raw reply

* Re: [PATCH v3 1/4] firmware_loader: expand firmware error codes with up-to-date error
From: Russ Weight @ 2025-11-18 23:22 UTC (permalink / raw)
  To: Marco Felsch
  Cc: Dmitry Torokhov, Luis Chamberlain, Greg Kroah-Hartman,
	Rafael J. Wysocki, Andrew Morton, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Kamel Bouhara, Marco Felsch,
	Henrik Rydberg, Danilo Krummrich, linux-kernel, devicetree,
	linux-input
In-Reply-To: <20251111110110.io65cbslrv75lbby@pengutronix.de>

On Tue, Nov 11, 2025 at 12:01:10PM +0100, Marco Felsch wrote:
> On 25-10-16, Marco Felsch wrote:
> > Hi all,
> > 
> > On 25-09-20, Dmitry Torokhov wrote:
> > > On Wed, Aug 27, 2025 at 03:29:33PM -0600, Russ Weight wrote:
> > > > 
> > > > On Thu, Aug 21, 2025 at 07:26:36PM +0200, Marco Felsch wrote:
> > > > > Add FW_UPLOAD_ERR_DUPLICATE to allow drivers to inform the firmware_loader
> > > > > framework that the update is not required. This can be the case if the
> > > > > user provided firmware matches the current running firmware.
> > > > > 
> > > > > Sync lib/test_firmware.c accordingly.
> > > > > 
> > > > > Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
> > > > 
> > > > Reviewed-by: Russ Weight <russ.weight@linux.dev>
> > > 
> > > Does this mean I should merge this through input tree?
> > 
> > may I ask how this is planned to go further?
> 
> Gentle ping.

Marco - I would recommend adding the Reviewed-by tags that you
have received and then resubmitting the patch.

-- Russ

> 
> Regards,
>   Marco
> 
> 
> > 
> > Regards,
> >   Marco
> > 
> > > 
> > > Thanks.
> > > 
> > > -- 
> > > Dmitry
> 
> -- 
> #gernperDu 
> #CallMeByMyFirstName
> 
> Pengutronix e.K.                           |                             |
> Steuerwalder Str. 21                       | https://www.pengutronix.de/ |
> 31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
> Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-9    |

^ permalink raw reply

* Re: [PATCH v8 03/10] HID: asus: fortify keyboard handshake
From: Denis Benato @ 2025-11-18 23:43 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Hans de Goede, Ilpo Järvinen
In-Reply-To: <20251101104712.8011-4-lkml@antheas.dev>


On 11/1/25 11:47, Antheas Kapenekakis wrote:
> Handshaking with an Asus device involves sending it a feature report
> with the string "ASUS Tech.Inc." and then reading it back to verify the
> handshake was successful, under the feature ID the interaction will
> take place.
>
> Currently, the driver only does the first part. Add the readback to
> verify the handshake was successful. As this could cause breakages,
> allow the verification to fail with a dmesg error until we verify
> all devices work with it (they seem to).
>
> Since the response is more than 16 bytes, increase the buffer size
> to 64 as well to avoid overflow errors.
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
>  drivers/hid/hid-asus.c | 32 +++++++++++++++++++++++++++++---
>  1 file changed, 29 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index 4676b7f20caf..03f0d86936fc 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -48,7 +48,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>  #define FEATURE_REPORT_ID 0x0d
>  #define INPUT_REPORT_ID 0x5d
>  #define FEATURE_KBD_REPORT_ID 0x5a
> -#define FEATURE_KBD_REPORT_SIZE 16
> +#define FEATURE_KBD_REPORT_SIZE 64
>  #define FEATURE_KBD_LED_REPORT_ID1 0x5d
>  #define FEATURE_KBD_LED_REPORT_ID2 0x5e
>  
> @@ -393,14 +393,40 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
>  
>  static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
>  {
> +	/*
> +	 * The handshake is first sent as a set_report, then retrieved
> +	 * from a get_report. They should be equal.
> +	 */
>  	const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
>  		     0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
> +	u8 *readbuf;

__free(kfree) = NULL here? Would simplify the code.

>  	int ret;
>  
>  	ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
> -	if (ret < 0)
> -		hid_err(hdev, "Asus failed to send init command: %d\n", ret);
> +	if (ret < 0) {
> +		hid_err(hdev, "Asus failed to send handshake: %d\n", ret);
> +		return ret;
> +	}
> +
> +	readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
> +	if (!readbuf)
> +		return -ENOMEM;
> +
> +	ret = hid_hw_raw_request(hdev, report_id, readbuf,
> +				 FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
> +				 HID_REQ_GET_REPORT);
See comments on patch 1 (also reported below): not sure if others
report_id are going to answer, my guess is that we will have to try
if you choose to go that route.
> +	if (ret < 0) {
> +		hid_err(hdev, "Asus failed to receive handshake ack: %d\n", ret);
> +	} else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
> +		hid_warn(hdev, "Asus handshake returned invalid response: %*ph\n",
> +			FEATURE_KBD_REPORT_SIZE, readbuf);
> +		/*
> +		 * Do not return error if handshake is wrong until this is
> +		 * verified to work for all devices.
> +		 */
In review of patch 1 I requested this function to be called with more report_id
than just 0x5a so this will have to be checked against those values too.

In alternative you can fork based on the report_id, but having confirmation that
this is valid with those ids too would be of great help. Perhaps I can help you
with this asking to asus-linux users.
> +	}
>  
> +	kfree(readbuf);
>  	return ret;
>  }
>  

^ permalink raw reply

* Re: [PATCH v8 03/10] HID: asus: fortify keyboard handshake
From: Antheas Kapenekakis @ 2025-11-18 23:46 UTC (permalink / raw)
  To: Denis Benato
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Luke D . Jones, Hans de Goede,
	Ilpo Järvinen
In-Reply-To: <75fe4c0f-3303-4f3d-adc5-45487df3c80a@gmail.com>

On Wed, 19 Nov 2025 at 00:43, Denis Benato <benato.denis96@gmail.com> wrote:
>
>
> On 11/1/25 11:47, Antheas Kapenekakis wrote:
> > Handshaking with an Asus device involves sending it a feature report
> > with the string "ASUS Tech.Inc." and then reading it back to verify the
> > handshake was successful, under the feature ID the interaction will
> > take place.
> >
> > Currently, the driver only does the first part. Add the readback to
> > verify the handshake was successful. As this could cause breakages,
> > allow the verification to fail with a dmesg error until we verify
> > all devices work with it (they seem to).
> >
> > Since the response is more than 16 bytes, increase the buffer size
> > to 64 as well to avoid overflow errors.
> >
> > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > ---
> >  drivers/hid/hid-asus.c | 32 +++++++++++++++++++++++++++++---
> >  1 file changed, 29 insertions(+), 3 deletions(-)
> >
> > diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> > index 4676b7f20caf..03f0d86936fc 100644
> > --- a/drivers/hid/hid-asus.c
> > +++ b/drivers/hid/hid-asus.c
> > @@ -48,7 +48,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> >  #define FEATURE_REPORT_ID 0x0d
> >  #define INPUT_REPORT_ID 0x5d
> >  #define FEATURE_KBD_REPORT_ID 0x5a
> > -#define FEATURE_KBD_REPORT_SIZE 16
> > +#define FEATURE_KBD_REPORT_SIZE 64
> >  #define FEATURE_KBD_LED_REPORT_ID1 0x5d
> >  #define FEATURE_KBD_LED_REPORT_ID2 0x5e
> >
> > @@ -393,14 +393,40 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
> >
> >  static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
> >  {
> > +     /*
> > +      * The handshake is first sent as a set_report, then retrieved
> > +      * from a get_report. They should be equal.
> > +      */
> >       const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
> >                    0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
> > +     u8 *readbuf;
>
> __free(kfree) = NULL here? Would simplify the code.
>
> >       int ret;
> >
> >       ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
> > -     if (ret < 0)
> > -             hid_err(hdev, "Asus failed to send init command: %d\n", ret);
> > +     if (ret < 0) {
> > +             hid_err(hdev, "Asus failed to send handshake: %d\n", ret);
> > +             return ret;
> > +     }
> > +
> > +     readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
> > +     if (!readbuf)
> > +             return -ENOMEM;
> > +
> > +     ret = hid_hw_raw_request(hdev, report_id, readbuf,
> > +                              FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
> > +                              HID_REQ_GET_REPORT);
> See comments on patch 1 (also reported below): not sure if others
> report_id are going to answer, my guess is that we will have to try
> if you choose to go that route.
> > +     if (ret < 0) {
> > +             hid_err(hdev, "Asus failed to receive handshake ack: %d\n", ret);
> > +     } else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
> > +             hid_warn(hdev, "Asus handshake returned invalid response: %*ph\n",
> > +                     FEATURE_KBD_REPORT_SIZE, readbuf);
> > +             /*
> > +              * Do not return error if handshake is wrong until this is
> > +              * verified to work for all devices.
> > +              */
> In review of patch 1 I requested this function to be called with more report_id
> than just 0x5a so this will have to be checked against those values too.
>
> In alternative you can fork based on the report_id, but having confirmation that
> this is valid with those ids too would be of great help. Perhaps I can help you
> with this asking to asus-linux users.

Yes, it is valid to do a get_report for handshakes with 0x5d/0x5e, and
this patch is non-fatal. If it causes warnings to be printed that can
be dealt then.

Antheas

> > +     }
> >
> > +     kfree(readbuf);
> >       return ret;
> >  }
> >
>


^ permalink raw reply

* Re: [PATCH v8 03/10] HID: asus: fortify keyboard handshake
From: luke @ 2025-11-18 23:46 UTC (permalink / raw)
  To: Denis Benato
  Cc: Antheas Kapenekakis, platform-driver-x86, linux-input,
	linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Hans de Goede, Ilpo Järvinen
In-Reply-To: <75fe4c0f-3303-4f3d-adc5-45487df3c80a@gmail.com>



> On 19 Nov 2025, at 12:43, Denis Benato <benato.denis96@gmail.com> wrote:
> 
> 
> On 11/1/25 11:47, Antheas Kapenekakis wrote:
>> Handshaking with an Asus device involves sending it a feature report
>> with the string "ASUS Tech.Inc." and then reading it back to verify the
>> handshake was successful, under the feature ID the interaction will
>> take place.
>> 
>> Currently, the driver only does the first part. Add the readback to
>> verify the handshake was successful. As this could cause breakages,
>> allow the verification to fail with a dmesg error until we verify
>> all devices work with it (they seem to).
>> 
>> Since the response is more than 16 bytes, increase the buffer size
>> to 64 as well to avoid overflow errors.
>> 
>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>> ---
>> drivers/hid/hid-asus.c | 32 +++++++++++++++++++++++++++++---
>> 1 file changed, 29 insertions(+), 3 deletions(-)
>> 
>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
>> index 4676b7f20caf..03f0d86936fc 100644
>> --- a/drivers/hid/hid-asus.c
>> +++ b/drivers/hid/hid-asus.c
>> @@ -48,7 +48,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>> #define FEATURE_REPORT_ID 0x0d
>> #define INPUT_REPORT_ID 0x5d
>> #define FEATURE_KBD_REPORT_ID 0x5a
>> -#define FEATURE_KBD_REPORT_SIZE 16
>> +#define FEATURE_KBD_REPORT_SIZE 64
>> #define FEATURE_KBD_LED_REPORT_ID1 0x5d
>> #define FEATURE_KBD_LED_REPORT_ID2 0x5e
>> 
>> @@ -393,14 +393,40 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
>> 
>> static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
>> {
>> +	/*
>> +	 * The handshake is first sent as a set_report, then retrieved
>> +	 * from a get_report. They should be equal.
>> +	 */
>> 	const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
>> 		     0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
>> +	u8 *readbuf;
> 
> __free(kfree) = NULL here? Would simplify the code.
> 
>> 	int ret;
>> 
>> 	ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
>> -	if (ret < 0)
>> -		hid_err(hdev, "Asus failed to send init command: %d\n", ret);
>> +	if (ret < 0) {
>> +		hid_err(hdev, "Asus failed to send handshake: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
>> +	if (!readbuf)
>> +		return -ENOMEM;
>> +
>> +	ret = hid_hw_raw_request(hdev, report_id, readbuf,
>> +				 FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
>> +				 HID_REQ_GET_REPORT);
> See comments on patch 1 (also reported below): not sure if others
> report_id are going to answer, my guess is that we will have to try
> if you choose to go that route.
>> +	if (ret < 0) {
>> +		hid_err(hdev, "Asus failed to receive handshake ack: %d\n", ret);
>> +	} else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
>> +		hid_warn(hdev, "Asus handshake returned invalid response: %*ph\n",
>> +			FEATURE_KBD_REPORT_SIZE, readbuf);
>> +		/*
>> +		 * Do not return error if handshake is wrong until this is
>> +		 * verified to work for all devices.
>> +		 */
> In review of patch 1 I requested this function to be called with more report_id
> than just 0x5a so this will have to be checked against those values too.
> 
> In alternative you can fork based on the report_id, but having confirmation that
> this is valid with those ids too would be of great help. Perhaps I can help you
> with this asking to asus-linux users.

The handshake works for 0x5D and 0x5E also.

>> +	}
>> 
>> +	kfree(readbuf);
>> 	return ret;
>> }
>> 


^ permalink raw reply

* Re: [PATCH v8 04/10] HID: asus: prevent binding to all HID devices on ROG
From: Denis Benato @ 2025-11-19  0:38 UTC (permalink / raw)
  To: Antheas Kapenekakis, platform-driver-x86, linux-input
  Cc: linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Luke D . Jones, Hans de Goede, Ilpo Järvinen
In-Reply-To: <20251101104712.8011-5-lkml@antheas.dev>


On 11/1/25 11:47, Antheas Kapenekakis wrote:
> Currently, when hid-asus is not loaded, NKEY keyboards load as ~6
> event devices with a pretty ASUSTEK name. When it loads, it concatenates
> all applications per HID endpoint, renames them, and prints errors
> when some of them do not have an input device.
>
> Therefore, change probe so that this is no longer the case. Stop
> renaming the devices, omit the check for .input which causes errors
> on e.g., the Z13 for some hiddev only devices, and move RGB checks
> into probe.
I have an issue with this "therefore" related to the renaming of device:
you are basically doing here:

state a matter of fact.
Therefore, change that.

Why? the check for .input is clear why, the rename not so much.

I have a few more comments below about the rename.
> Reviewed-by: Luke D. Jones <luke@ljones.dev>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
>  drivers/hid/hid-asus.c | 52 ++++++++++++++++++++++++++++--------------
>  1 file changed, 35 insertions(+), 17 deletions(-)
>
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index 03f0d86936fc..726f5d8e22d1 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -47,6 +47,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>  #define T100CHI_MOUSE_REPORT_ID 0x06
>  #define FEATURE_REPORT_ID 0x0d
>  #define INPUT_REPORT_ID 0x5d
> +#define HID_USAGE_PAGE_VENDOR 0xff310000
>  #define FEATURE_KBD_REPORT_ID 0x5a
>  #define FEATURE_KBD_REPORT_SIZE 64
>  #define FEATURE_KBD_LED_REPORT_ID1 0x5d
> @@ -89,6 +90,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>  #define QUIRK_ROG_NKEY_KEYBOARD		BIT(11)
>  #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
>  #define QUIRK_ROG_ALLY_XPAD		BIT(13)
> +#define QUIRK_SKIP_REPORT_FIXUP		BIT(14)
>  
>  #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
>  						 QUIRK_NO_INIT_REPORTS | \
> @@ -125,7 +127,6 @@ struct asus_drvdata {
>  	struct input_dev *tp_kbd_input;
>  	struct asus_kbd_leds *kbd_backlight;
>  	const struct asus_touchpad_info *tp;
> -	bool enable_backlight;
>  	struct power_supply *battery;
>  	struct power_supply_desc battery_desc;
>  	int battery_capacity;
> @@ -316,7 +317,7 @@ static int asus_e1239t_event(struct asus_drvdata *drvdat, u8 *data, int size)
>  static int asus_event(struct hid_device *hdev, struct hid_field *field,
>  		      struct hid_usage *usage, __s32 value)
>  {
> -	if ((usage->hid & HID_USAGE_PAGE) == 0xff310000 &&
> +	if ((usage->hid & HID_USAGE_PAGE) == HID_USAGE_PAGE_VENDOR &&
>  	    (usage->hid & HID_USAGE) != 0x00 &&
>  	    (usage->hid & HID_USAGE) != 0xff && !usage->type) {
>  		hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n",
> @@ -931,11 +932,6 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
>  
>  	drvdata->input = input;
>  
> -	if (drvdata->enable_backlight &&
> -	    !asus_kbd_wmi_led_control_present(hdev) &&
> -	    asus_kbd_register_leds(hdev))
> -		hid_warn(hdev, "Failed to initialize backlight.\n");
> -
>  	return 0;
>  }
>  
> @@ -1008,15 +1004,6 @@ static int asus_input_mapping(struct hid_device *hdev,
>  			return -1;
>  		}
>  
> -		/*
> -		 * Check and enable backlight only on devices with UsagePage ==
> -		 * 0xff31 to avoid initializing the keyboard firmware multiple
> -		 * times on devices with multiple HID descriptors but same
> -		 * PID/VID.
> -		 */
> -		if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT)
> -			drvdata->enable_backlight = true;
> -
>  		set_bit(EV_REP, hi->input->evbit);
>  		return 1;
>  	}
> @@ -1133,8 +1120,10 @@ static int __maybe_unused asus_reset_resume(struct hid_device *hdev)
>  
>  static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
>  {
> -	int ret;
> +	struct hid_report_enum *rep_enum;
>  	struct asus_drvdata *drvdata;
> +	struct hid_report *rep;
> +	int ret, is_vendor = 0;
>  
Why is is_vendor an int? Don't we have bools?
>  	drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
>  	if (drvdata == NULL) {
> @@ -1218,12 +1207,37 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
>  		return ret;
>  	}
>  
> +	/* Check for vendor for RGB init and handle generic devices properly. */
> +	rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
> +	list_for_each_entry(rep, &rep_enum->report_list, list) {
> +		if ((rep->application & HID_USAGE_PAGE) == HID_USAGE_PAGE_VENDOR)
> +			is_vendor = true;
> +	}
> +
> +	/*
> +	 * For ROG keyboards, disable fixups except vendor devices.
> +	 */
multiline comment for no reason. Comma doesn't provide any value here.
> +	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD && !is_vendor)
> +		drvdata->quirks |= QUIRK_SKIP_REPORT_FIXUP;
> +
Doing this will skip the report fixup entirely while before
it was called in every case: are we really sure we want this?
Or do we want it only for specific devices?

It's my understanding that function is only useful on
keyboard devices, so before keyboard devices (all)
while now is_vendor keyboard devices, right?

What about keyboard devices that are not is_vendor
for which function isn't called anymore? 
>  	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
>  	if (ret) {
>  		hid_err(hdev, "Asus hw start failed: %d\n", ret);
>  		return ret;
>  	}
>  
> +	if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) &&
> +	    !asus_kbd_wmi_led_control_present(hdev) &&
> +	    asus_kbd_register_leds(hdev))
> +		hid_warn(hdev, "Failed to initialize backlight.\n");
> +
> +	/*
> +	 * For ROG keyboards, skip rename for consistency and ->input check as
> +	 * some devices do not have inputs.
> +	 */
> +	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD)
> +		return 0;
> +
This is a moot point: I have yet to see the real benefit of doing this,
but one thing is sure... having for the same driver multiple name
to basically the same interface across different lineups of
hardware is not something I would call "consistency".

As I said already I want you to either drop this or to present
a list of pros of doing this and to hear from Derek the plan
going forward to avoid breaking anything.
>  	/*
>  	 * Check that input registration succeeded. Checking that
>  	 * HID_CLAIMED_INPUT is set prevents a UAF when all input devices
> @@ -1352,6 +1366,10 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
>  		rdesc = new_rdesc;
>  	}
>  
> +	/* Vendor fixups should only apply to NKEY vendor devices. */
> +	if (drvdata->quirks & QUIRK_SKIP_REPORT_FIXUP)
> +		return rdesc;
> +
Uhm... no? Or at least it's not obvious why.

If this is the case why is the check not at the top of the function?

Beside please refrain from using "should" in this context unless
backed up by evidence or it's otherwise obvious as "should"
can have many different interpretations.
>  	if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD &&
>  			*rsize == 331 && rdesc[190] == 0x85 && rdesc[191] == 0x5a &&
>  			rdesc[204] == 0x95 && rdesc[205] == 0x05) {

^ permalink raw reply

* Re: [PATCH v8 05/10] HID: asus: initialize LED endpoint early for old NKEY keyboards
From: Denis Benato @ 2025-11-19  0:54 UTC (permalink / raw)
  To: Ilpo Järvinen, Antheas Kapenekakis
  Cc: platform-driver-x86, linux-input, LKML, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Luke D . Jones, Hans de Goede
In-Reply-To: <2fc1e683-0234-20b6-7448-bd0213c9bb37@linux.intel.com>


On 11/18/25 13:10, Ilpo Järvinen wrote:
> On Sat, 1 Nov 2025, Antheas Kapenekakis wrote:
>
>> These keyboards have always had initialization in the kernel for 0x5d.
>> At this point, it is hard to verify again and we risk regressions by
>> removing this. Therefore, initialize with 0x5d as well.
See patch 1: unless I missed something you can retain the two 
FEATURE_KBD_LED_REPORT_IDx behind the same exact quirk:
why are we adding a quirk to replace a quirk that was removed
in patch 1?

You are basically doing the pretty-much-but-not-quite
equivalent of what the driver was doing before.
>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>> ---
>>  drivers/hid/hid-asus.c | 15 +++++++++++++--
>>  1 file changed, 13 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
>> index 726f5d8e22d1..221c7195e885 100644
>> --- a/drivers/hid/hid-asus.c
>> +++ b/drivers/hid/hid-asus.c
>> @@ -91,6 +91,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>>  #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
>>  #define QUIRK_ROG_ALLY_XPAD		BIT(13)
>>  #define QUIRK_SKIP_REPORT_FIXUP		BIT(14)
>> +#define QUIRK_ROG_NKEY_LEGACY		BIT(15)
>>  
>>  #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
>>  						 QUIRK_NO_INIT_REPORTS | \
>> @@ -669,6 +670,16 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
>>  	if (ret < 0)
>>  		return ret;
>>  
>> +	if (drvdata->quirks & QUIRK_ROG_NKEY_LEGACY) {
>> +		/*
>> +		 * These keyboards might need 0x5d for shortcuts to work.
>> +		 * As it has been more than 5 years, it is hard to verify.
>> +		 */
>> +		ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
>> +		if (ret < 0)
>> +			return ret;
>> +	}
>> +
>>  	/* Get keyboard functions */
>>  	ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
>>  	if (ret < 0)
>> @@ -1409,10 +1420,10 @@ static const struct hid_device_id asus_devices[] = {
>>  	  QUIRK_USE_KBD_BACKLIGHT },
>>  	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>  	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
>> -	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>> +	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_LEGACY },
>>  	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>  	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
>> -	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>> +	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_LEGACY },
>>  	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>  	    USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
>>  	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> You should do FEATURE_KBD_LED_REPORT_ID1 refactoring together, not remove 
> + add back in different patches.
Granted I still have no idea why that was removed in the first place?
Then re-added but losing FEATURE_KBD_LED_REPORT_ID1 ?

What's the problem with FEATURE_KBD_LED_REPORT_ID1?

> I suppose the cleanest would be to add a new patch as first which moves
> asus_kbd_init() outside of if/else so you can make this refactoring of 
> FEATURE_KBD_LED_REPORT_ID1 in the 2nd patch.
Again I am missing the point in moving these...
> I note there's still contention with this series overall.
>
There are a few things that have pretty much the potential of making
some laptops act funny due to tinkering with initializations commands.

The rename will break some tools, but other than that, granted I have yet
to check the rest of the patchset, looks reasonable to me.

Perhaps I am not entirely happy with how things are worded in
a few instances, but it's a minor issue.

^ permalink raw reply

* Re: [PATCH v8 05/10] HID: asus: initialize LED endpoint early for old NKEY keyboards
From: Antheas Kapenekakis @ 2025-11-19  1:11 UTC (permalink / raw)
  To: Denis Benato
  Cc: Ilpo Järvinen, platform-driver-x86, linux-input, LKML,
	Jiri Kosina, Benjamin Tissoires, Corentin Chary, Luke D . Jones,
	Hans de Goede
In-Reply-To: <b505ffca-63a1-4c52-b940-cdfc507813fc@gmail.com>

On Wed, 19 Nov 2025 at 01:54, Denis Benato <benato.denis96@gmail.com> wrote:
>
>
> On 11/18/25 13:10, Ilpo Järvinen wrote:
> > On Sat, 1 Nov 2025, Antheas Kapenekakis wrote:
> >
> >> These keyboards have always had initialization in the kernel for 0x5d.
> >> At this point, it is hard to verify again and we risk regressions by
> >> removing this. Therefore, initialize with 0x5d as well.
> See patch 1: unless I missed something you can retain the two
> FEATURE_KBD_LED_REPORT_IDx behind the same exact quirk:
> why are we adding a quirk to replace a quirk that was removed
> in patch 1?
>
> You are basically doing the pretty-much-but-not-quite
> equivalent of what the driver was doing before.
> >> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> >> ---
> >>  drivers/hid/hid-asus.c | 15 +++++++++++++--
> >>  1 file changed, 13 insertions(+), 2 deletions(-)
> >>
> >> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> >> index 726f5d8e22d1..221c7195e885 100644
> >> --- a/drivers/hid/hid-asus.c
> >> +++ b/drivers/hid/hid-asus.c
> >> @@ -91,6 +91,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> >>  #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
> >>  #define QUIRK_ROG_ALLY_XPAD         BIT(13)
> >>  #define QUIRK_SKIP_REPORT_FIXUP             BIT(14)
> >> +#define QUIRK_ROG_NKEY_LEGACY               BIT(15)
> >>
> >>  #define I2C_KEYBOARD_QUIRKS                 (QUIRK_FIX_NOTEBOOK_REPORT | \
> >>                                               QUIRK_NO_INIT_REPORTS | \
> >> @@ -669,6 +670,16 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
> >>      if (ret < 0)
> >>              return ret;
> >>
> >> +    if (drvdata->quirks & QUIRK_ROG_NKEY_LEGACY) {
> >> +            /*
> >> +             * These keyboards might need 0x5d for shortcuts to work.
> >> +             * As it has been more than 5 years, it is hard to verify.
> >> +             */
> >> +            ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
> >> +            if (ret < 0)
> >> +                    return ret;
> >> +    }
> >> +
> >>      /* Get keyboard functions */
> >>      ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
> >>      if (ret < 0)
> >> @@ -1409,10 +1420,10 @@ static const struct hid_device_id asus_devices[] = {
> >>        QUIRK_USE_KBD_BACKLIGHT },
> >>      { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> >>          USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
> >> -      QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> >> +      QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_LEGACY },
> >>      { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> >>          USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
> >> -      QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> >> +      QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_LEGACY },
> >>      { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> >>          USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
> >>        QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> > You should do FEATURE_KBD_LED_REPORT_ID1 refactoring together, not remove
> > + add back in different patches.
> Granted I still have no idea why that was removed in the first place?
> Then re-added but losing FEATURE_KBD_LED_REPORT_ID1 ?
>
> What's the problem with FEATURE_KBD_LED_REPORT_ID1?
>
> > I suppose the cleanest would be to add a new patch as first which moves
> > asus_kbd_init() outside of if/else so you can make this refactoring of
> > FEATURE_KBD_LED_REPORT_ID1 in the 2nd patch.
> Again I am missing the point in moving these...
> > I note there's still contention with this series overall.
> >
> There are a few things that have pretty much the potential of making
> some laptops act funny due to tinkering with initializations commands.
>
> The rename will break some tools, but other than that, granted I have yet
> to check the rest of the patchset, looks reasonable to me.
>
> Perhaps I am not entirely happy with how things are worded in
> a few instances, but it's a minor issue.

Hi Denis,
please refrain from repeating yourself, in the same email and across
emails, and repeating comments that are already addressed by Ilpo. As
that makes it hard to track and respond to your comments.

As noted in this chain, the plan for the next version is to include
ID2 in this quirk and instead of removing it in the simplify patch,
use a gate by the ROG quirk that is then replaced by a gate from this
quirk.

This completely addresses your comment. The legacy quirk only applies
to a subset of devices, so it is not the same with this series
applied.

Antheas


^ permalink raw reply

* Re: [PATCH v8 05/10] HID: asus: initialize LED endpoint early for old NKEY keyboards
From: Denis Benato @ 2025-11-19  1:21 UTC (permalink / raw)
  To: Antheas Kapenekakis
  Cc: Ilpo Järvinen, platform-driver-x86, linux-input, LKML,
	Jiri Kosina, Benjamin Tissoires, Corentin Chary, Luke D . Jones,
	Hans de Goede
In-Reply-To: <CAGwozwEF8bhbhgYe9cyg0JgcKsXstNZubPV9HCapwxMvw_2OhQ@mail.gmail.com>


On 11/19/25 02:11, Antheas Kapenekakis wrote:
> On Wed, 19 Nov 2025 at 01:54, Denis Benato <benato.denis96@gmail.com> wrote:
>>
>> On 11/18/25 13:10, Ilpo Järvinen wrote:
>>> On Sat, 1 Nov 2025, Antheas Kapenekakis wrote:
>>>
>>>> These keyboards have always had initialization in the kernel for 0x5d.
>>>> At this point, it is hard to verify again and we risk regressions by
>>>> removing this. Therefore, initialize with 0x5d as well.
>> See patch 1: unless I missed something you can retain the two
>> FEATURE_KBD_LED_REPORT_IDx behind the same exact quirk:
>> why are we adding a quirk to replace a quirk that was removed
>> in patch 1?
>>
>> You are basically doing the pretty-much-but-not-quite
>> equivalent of what the driver was doing before.
>>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>>>> ---
>>>>  drivers/hid/hid-asus.c | 15 +++++++++++++--
>>>>  1 file changed, 13 insertions(+), 2 deletions(-)
>>>>
>>>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
>>>> index 726f5d8e22d1..221c7195e885 100644
>>>> --- a/drivers/hid/hid-asus.c
>>>> +++ b/drivers/hid/hid-asus.c
>>>> @@ -91,6 +91,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>>>>  #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
>>>>  #define QUIRK_ROG_ALLY_XPAD         BIT(13)
>>>>  #define QUIRK_SKIP_REPORT_FIXUP             BIT(14)
>>>> +#define QUIRK_ROG_NKEY_LEGACY               BIT(15)
>>>>
>>>>  #define I2C_KEYBOARD_QUIRKS                 (QUIRK_FIX_NOTEBOOK_REPORT | \
>>>>                                               QUIRK_NO_INIT_REPORTS | \
>>>> @@ -669,6 +670,16 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
>>>>      if (ret < 0)
>>>>              return ret;
>>>>
>>>> +    if (drvdata->quirks & QUIRK_ROG_NKEY_LEGACY) {
>>>> +            /*
>>>> +             * These keyboards might need 0x5d for shortcuts to work.
>>>> +             * As it has been more than 5 years, it is hard to verify.
>>>> +             */
>>>> +            ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
>>>> +            if (ret < 0)
>>>> +                    return ret;
>>>> +    }
>>>> +
>>>>      /* Get keyboard functions */
>>>>      ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
>>>>      if (ret < 0)
>>>> @@ -1409,10 +1420,10 @@ static const struct hid_device_id asus_devices[] = {
>>>>        QUIRK_USE_KBD_BACKLIGHT },
>>>>      { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>>>          USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
>>>> -      QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>>>> +      QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_LEGACY },
>>>>      { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>>>          USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
>>>> -      QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>>>> +      QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_LEGACY },
>>>>      { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>>>>          USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
>>>>        QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
>>> You should do FEATURE_KBD_LED_REPORT_ID1 refactoring together, not remove
>>> + add back in different patches.
>> Granted I still have no idea why that was removed in the first place?
>> Then re-added but losing FEATURE_KBD_LED_REPORT_ID1 ?
>>
>> What's the problem with FEATURE_KBD_LED_REPORT_ID1?
>>
>>> I suppose the cleanest would be to add a new patch as first which moves
>>> asus_kbd_init() outside of if/else so you can make this refactoring of
>>> FEATURE_KBD_LED_REPORT_ID1 in the 2nd patch.
>> Again I am missing the point in moving these...
>>> I note there's still contention with this series overall.
>>>
>> There are a few things that have pretty much the potential of making
>> some laptops act funny due to tinkering with initializations commands.
>>
>> The rename will break some tools, but other than that, granted I have yet
>> to check the rest of the patchset, looks reasonable to me.
>>
>> Perhaps I am not entirely happy with how things are worded in
>> a few instances, but it's a minor issue.
> Hi Denis,
> please refrain from repeating yourself, in the same email and across
> emails, and repeating comments that are already addressed by Ilpo. As
> that makes it hard to track and respond to your comments.
My repetitions are due to confusion with different aspects of what
you are doing, but I'll try to be less repetitive.

Also I don't think I have said the very same exact things as
Ilpo, unless I misunderstood something.
> As noted in this chain, the plan for the next version is to include
> ID2 in this quirk and instead of removing it in the simplify patch,
> use a gate by the ROG quirk that is then replaced by a gate from this
> quirk.
Yeah but I also asked (perhaps too many times?) what's up with _ID1
and the whole reasoning behind this because as it stands now
you are replacing X with Y, only to do Z without making clear to me
the reason of Y, therefore Z also suffers from the same problem.
> This completely addresses your comment. The legacy quirk only applies
> to a subset of devices, so it is not the same with this series
> applied.
And the reason for this difference is exactly what I am asking for.

I have been trying to suggest things to describe and actions
to take/specific code changes to ensure we agree...
> Antheas
>

^ permalink raw reply

* Re: [PATCH v8 05/10] HID: asus: initialize LED endpoint early for old NKEY keyboards
From: Antheas Kapenekakis @ 2025-11-19  1:27 UTC (permalink / raw)
  To: Denis Benato
  Cc: Ilpo Järvinen, platform-driver-x86, linux-input, LKML,
	Jiri Kosina, Benjamin Tissoires, Corentin Chary, Luke D . Jones,
	Hans de Goede
In-Reply-To: <e204d129-0ac8-48fb-a178-12466350079e@gmail.com>

On Wed, 19 Nov 2025 at 02:21, Denis Benato <benato.denis96@gmail.com> wrote:
>
>
> On 11/19/25 02:11, Antheas Kapenekakis wrote:
> > On Wed, 19 Nov 2025 at 01:54, Denis Benato <benato.denis96@gmail.com> wrote:
> >>
> >> On 11/18/25 13:10, Ilpo Järvinen wrote:
> >>> On Sat, 1 Nov 2025, Antheas Kapenekakis wrote:
> >>>
> >>>> These keyboards have always had initialization in the kernel for 0x5d.
> >>>> At this point, it is hard to verify again and we risk regressions by
> >>>> removing this. Therefore, initialize with 0x5d as well.
> >> See patch 1: unless I missed something you can retain the two
> >> FEATURE_KBD_LED_REPORT_IDx behind the same exact quirk:
> >> why are we adding a quirk to replace a quirk that was removed
> >> in patch 1?
> >>
> >> You are basically doing the pretty-much-but-not-quite
> >> equivalent of what the driver was doing before.
> >>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> >>>> ---
> >>>>  drivers/hid/hid-asus.c | 15 +++++++++++++--
> >>>>  1 file changed, 13 insertions(+), 2 deletions(-)
> >>>>
> >>>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> >>>> index 726f5d8e22d1..221c7195e885 100644
> >>>> --- a/drivers/hid/hid-asus.c
> >>>> +++ b/drivers/hid/hid-asus.c
> >>>> @@ -91,6 +91,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> >>>>  #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
> >>>>  #define QUIRK_ROG_ALLY_XPAD         BIT(13)
> >>>>  #define QUIRK_SKIP_REPORT_FIXUP             BIT(14)
> >>>> +#define QUIRK_ROG_NKEY_LEGACY               BIT(15)
> >>>>
> >>>>  #define I2C_KEYBOARD_QUIRKS                 (QUIRK_FIX_NOTEBOOK_REPORT | \
> >>>>                                               QUIRK_NO_INIT_REPORTS | \
> >>>> @@ -669,6 +670,16 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
> >>>>      if (ret < 0)
> >>>>              return ret;
> >>>>
> >>>> +    if (drvdata->quirks & QUIRK_ROG_NKEY_LEGACY) {
> >>>> +            /*
> >>>> +             * These keyboards might need 0x5d for shortcuts to work.
> >>>> +             * As it has been more than 5 years, it is hard to verify.
> >>>> +             */
> >>>> +            ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
> >>>> +            if (ret < 0)
> >>>> +                    return ret;
> >>>> +    }
> >>>> +
> >>>>      /* Get keyboard functions */
> >>>>      ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
> >>>>      if (ret < 0)
> >>>> @@ -1409,10 +1420,10 @@ static const struct hid_device_id asus_devices[] = {
> >>>>        QUIRK_USE_KBD_BACKLIGHT },
> >>>>      { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> >>>>          USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
> >>>> -      QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> >>>> +      QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_LEGACY },
> >>>>      { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> >>>>          USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
> >>>> -      QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> >>>> +      QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_LEGACY },
> >>>>      { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> >>>>          USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
> >>>>        QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> >>> You should do FEATURE_KBD_LED_REPORT_ID1 refactoring together, not remove
> >>> + add back in different patches.
> >> Granted I still have no idea why that was removed in the first place?
> >> Then re-added but losing FEATURE_KBD_LED_REPORT_ID1 ?
> >>
> >> What's the problem with FEATURE_KBD_LED_REPORT_ID1?
> >>
> >>> I suppose the cleanest would be to add a new patch as first which moves
> >>> asus_kbd_init() outside of if/else so you can make this refactoring of
> >>> FEATURE_KBD_LED_REPORT_ID1 in the 2nd patch.
> >> Again I am missing the point in moving these...
> >>> I note there's still contention with this series overall.
> >>>
> >> There are a few things that have pretty much the potential of making
> >> some laptops act funny due to tinkering with initializations commands.
> >>
> >> The rename will break some tools, but other than that, granted I have yet
> >> to check the rest of the patchset, looks reasonable to me.
> >>
> >> Perhaps I am not entirely happy with how things are worded in
> >> a few instances, but it's a minor issue.
> > Hi Denis,
> > please refrain from repeating yourself, in the same email and across
> > emails, and repeating comments that are already addressed by Ilpo. As
> > that makes it hard to track and respond to your comments.
> My repetitions are due to confusion with different aspects of what
> you are doing, but I'll try to be less repetitive.
>
> Also I don't think I have said the very same exact things as
> Ilpo, unless I misunderstood something.
> > As noted in this chain, the plan for the next version is to include
> > ID2 in this quirk and instead of removing it in the simplify patch,
> > use a gate by the ROG quirk that is then replaced by a gate from this
> > quirk.
> Yeah but I also asked (perhaps too many times?) what's up with _ID1
> and the whole reasoning behind this because as it stands now
> you are replacing X with Y, only to do Z without making clear to me
> the reason of Y, therefore Z also suffers from the same problem.
> > This completely addresses your comment. The legacy quirk only applies
> > to a subset of devices, so it is not the same with this series
> > applied.
> And the reason for this difference is exactly what I am asking for.
>
> I have been trying to suggest things to describe and actions
> to take/specific code changes to ensure we agree...

New devices do not need the 0x5d/0x5e inits. This is especially true
for those that do not have those endpoints. Specifically, devices
without Anime/an S? (I do not recall the name currently) animation
feature do not have a 0x5e endpoint, and devices without RGB do not
have a 0x5d endpoint. But they currently get sent these inits anyway.

For some old devices, they might need 0x5d to init the keyboard
shortcuts, but that is not certain. It is too late by this point to
verify though, so the legacy patch will keep those for the next revision.

Antheas

> > Antheas
> >
>


^ permalink raw reply

* Re: [PATCH v8 04/10] HID: asus: prevent binding to all HID devices on ROG
From: Antheas Kapenekakis @ 2025-11-19  1:45 UTC (permalink / raw)
  To: Denis Benato
  Cc: platform-driver-x86, linux-input, linux-kernel, Jiri Kosina,
	Benjamin Tissoires, Corentin Chary, Luke D . Jones,
	Ilpo Järvinen, Hans de Goede
In-Reply-To: <abeecb3b-8d51-4625-8743-1cfff355c0e9@gmail.com>

On Wed, 19 Nov 2025 at 01:38, Denis Benato <benato.denis96@gmail.com> wrote:
>
>
> On 11/1/25 11:47, Antheas Kapenekakis wrote:
> > Currently, when hid-asus is not loaded, NKEY keyboards load as ~6
> > event devices with a pretty ASUSTEK name. When it loads, it concatenates
> > all applications per HID endpoint, renames them, and prints errors
> > when some of them do not have an input device.
> >
> > Therefore, change probe so that this is no longer the case. Stop
> > renaming the devices, omit the check for .input which causes errors
> > on e.g., the Z13 for some hiddev only devices, and move RGB checks
> > into probe.
> I have an issue with this "therefore" related to the renaming of device:
> you are basically doing here:
>
> state a matter of fact.
> Therefore, change that.
>
> Why? the check for .input is clear why, the rename not so much.
>
> I have a few more comments below about the rename.
> > Reviewed-by: Luke D. Jones <luke@ljones.dev>
> > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > ---
> >  drivers/hid/hid-asus.c | 52 ++++++++++++++++++++++++++++--------------
> >  1 file changed, 35 insertions(+), 17 deletions(-)
> >
> > diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> > index 03f0d86936fc..726f5d8e22d1 100644
> > --- a/drivers/hid/hid-asus.c
> > +++ b/drivers/hid/hid-asus.c
> > @@ -47,6 +47,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> >  #define T100CHI_MOUSE_REPORT_ID 0x06
> >  #define FEATURE_REPORT_ID 0x0d
> >  #define INPUT_REPORT_ID 0x5d
> > +#define HID_USAGE_PAGE_VENDOR 0xff310000
> >  #define FEATURE_KBD_REPORT_ID 0x5a
> >  #define FEATURE_KBD_REPORT_SIZE 64
> >  #define FEATURE_KBD_LED_REPORT_ID1 0x5d
> > @@ -89,6 +90,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> >  #define QUIRK_ROG_NKEY_KEYBOARD              BIT(11)
> >  #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
> >  #define QUIRK_ROG_ALLY_XPAD          BIT(13)
> > +#define QUIRK_SKIP_REPORT_FIXUP              BIT(14)
> >
> >  #define I2C_KEYBOARD_QUIRKS                  (QUIRK_FIX_NOTEBOOK_REPORT | \
> >                                                QUIRK_NO_INIT_REPORTS | \
> > @@ -125,7 +127,6 @@ struct asus_drvdata {
> >       struct input_dev *tp_kbd_input;
> >       struct asus_kbd_leds *kbd_backlight;
> >       const struct asus_touchpad_info *tp;
> > -     bool enable_backlight;
> >       struct power_supply *battery;
> >       struct power_supply_desc battery_desc;
> >       int battery_capacity;
> > @@ -316,7 +317,7 @@ static int asus_e1239t_event(struct asus_drvdata *drvdat, u8 *data, int size)
> >  static int asus_event(struct hid_device *hdev, struct hid_field *field,
> >                     struct hid_usage *usage, __s32 value)
> >  {
> > -     if ((usage->hid & HID_USAGE_PAGE) == 0xff310000 &&
> > +     if ((usage->hid & HID_USAGE_PAGE) == HID_USAGE_PAGE_VENDOR &&
> >           (usage->hid & HID_USAGE) != 0x00 &&
> >           (usage->hid & HID_USAGE) != 0xff && !usage->type) {
> >               hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n",
> > @@ -931,11 +932,6 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
> >
> >       drvdata->input = input;
> >
> > -     if (drvdata->enable_backlight &&
> > -         !asus_kbd_wmi_led_control_present(hdev) &&
> > -         asus_kbd_register_leds(hdev))
> > -             hid_warn(hdev, "Failed to initialize backlight.\n");
> > -
> >       return 0;
> >  }
> >
> > @@ -1008,15 +1004,6 @@ static int asus_input_mapping(struct hid_device *hdev,
> >                       return -1;
> >               }
> >
> > -             /*
> > -              * Check and enable backlight only on devices with UsagePage ==
> > -              * 0xff31 to avoid initializing the keyboard firmware multiple
> > -              * times on devices with multiple HID descriptors but same
> > -              * PID/VID.
> > -              */
> > -             if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT)
> > -                     drvdata->enable_backlight = true;
> > -
> >               set_bit(EV_REP, hi->input->evbit);
> >               return 1;
> >       }
> > @@ -1133,8 +1120,10 @@ static int __maybe_unused asus_reset_resume(struct hid_device *hdev)
> >
> >  static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
> >  {
> > -     int ret;
> > +     struct hid_report_enum *rep_enum;
> >       struct asus_drvdata *drvdata;
> > +     struct hid_report *rep;
> > +     int ret, is_vendor = 0;
> >
> Why is is_vendor an int? Don't we have bools?
> >       drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
> >       if (drvdata == NULL) {
> > @@ -1218,12 +1207,37 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
> >               return ret;
> >       }
> >
> > +     /* Check for vendor for RGB init and handle generic devices properly. */
> > +     rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
> > +     list_for_each_entry(rep, &rep_enum->report_list, list) {
> > +             if ((rep->application & HID_USAGE_PAGE) == HID_USAGE_PAGE_VENDOR)
> > +                     is_vendor = true;
> > +     }
> > +
> > +     /*
> > +      * For ROG keyboards, disable fixups except vendor devices.
> > +      */
> multiline comment for no reason. Comma doesn't provide any value here.
> > +     if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD && !is_vendor)
> > +             drvdata->quirks |= QUIRK_SKIP_REPORT_FIXUP;
> > +
> Doing this will skip the report fixup entirely while before
> it was called in every case: are we really sure we want this?
> Or do we want it only for specific devices?
>
> It's my understanding that function is only useful on
> keyboard devices, so before keyboard devices (all)
> while now is_vendor keyboard devices, right?

ROG Keyboard devices have multiple HID endpoints. This driver only
hooks to the 0xff31 endpoint. So the rest of the endpoints should not
be modified. Except for minor fixups, see below.

> What about keyboard devices that are not is_vendor
> for which function isn't called anymore?
> >       ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
> >       if (ret) {
> >               hid_err(hdev, "Asus hw start failed: %d\n", ret);
> >               return ret;
> >       }
> >
> > +     if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) &&
> > +         !asus_kbd_wmi_led_control_present(hdev) &&
> > +         asus_kbd_register_leds(hdev))
> > +             hid_warn(hdev, "Failed to initialize backlight.\n");
> > +
> > +     /*
> > +      * For ROG keyboards, skip rename for consistency and ->input check as
> > +      * some devices do not have inputs.
> > +      */
> > +     if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD)
> > +             return 0;
> > +
> This is a moot point: I have yet to see the real benefit of doing this,
> but one thing is sure... having for the same driver multiple name
> to basically the same interface across different lineups of
> hardware is not something I would call "consistency".

The reason the rename exists here is because the devices this driver
applied to originally did not have proper names (e.g. I2C). The new
ROG devices do have proper names, so there is no reason to deviate
from those. It also eliminates a point of failure in which the
hid-asus driver is not loaded, if your point is consistency. Ie, as we
add more keyboard IDs, those would then not be renamed when hid-asus
starts to load them.

As for affected software (per your other email), yes it is only
Inputplumber when running on its fallback mode without access to its
OOT modules that disable this driver. Because it had an architectural
decision to rely on hardcoded evdev/phys names for most devices
instead of more canonical vid/pid/capability matches. The phys part is
especially problematic as it also hardcodes the bus node of a lot of
devices. There is a PR open to remove the matches for the Ally units
though.

If you want to know about other software that relies on names, SDL is
the main one. And the reason for that is so that it matches the kernel
driver. E.g., when a playstation 5 controller loads using
hid_playstation, it has a different mapping and name than when it
loads through hid core. My software also relies on it for WMI
keyboards, as those have a vid:pid of 0:0 so it is unavoidable.

SDL does not map keyboards so it is not affected. Moreover, as this
series makes it so the device has the same name as with the driver not
loaded, software such as SDL would have a fallback mapping for that
name already.

> As I said already I want you to either drop this or to present
> a list of pros of doing this and to hear from Derek the plan
> going forward to avoid breaking anything.
> >       /*
> >        * Check that input registration succeeded. Checking that
> >        * HID_CLAIMED_INPUT is set prevents a UAF when all input devices
> > @@ -1352,6 +1366,10 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
> >               rdesc = new_rdesc;
> >       }
> >
> > +     /* Vendor fixups should only apply to NKEY vendor devices. */
> > +     if (drvdata->quirks & QUIRK_SKIP_REPORT_FIXUP)
> > +             return rdesc;
> > +
> Uhm... no? Or at least it's not obvious why.
>
> If this is the case why is the check not at the top of the function?

Because the checks above apply to e.g., touchpad devices. This was
actually a bug with this series in previous versions. Only the report
fixups should be skipped. Specifically, with previous versions a HID
application mute was skipped, which caused certain keyboards to show
up as range finders if I recall (there is a comment for it in this
driver).

The report fixups that are below this check are only applicable to the
0xff31 devices that emit 0x5a events. Specifically, this driver
"abuses" the HID subsystem a bit to make the vendor report, which is
not a HID input device, appear as an input device, by mutating its
descriptor. But refactoring that would be too painful. At least with
this we make the fixup apply more precisely to only those devices.

I kindly ask you finish your comments until tomorrow evening, so I can
resend this series.

Thanks,
Antheas

> Beside please refrain from using "should" in this context unless
> backed up by evidence or it's otherwise obvious as "should"
> can have many different interpretations.
> >       if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD &&
> >                       *rsize == 331 && rdesc[190] == 0x85 && rdesc[191] == 0x5a &&
> >                       rdesc[204] == 0x95 && rdesc[205] == 0x05) {
>


^ permalink raw reply

* Re: [PATCH v8 03/10] HID: asus: fortify keyboard handshake
From: Denis Benato @ 2025-11-19  1:46 UTC (permalink / raw)
  To: luke
  Cc: Antheas Kapenekakis, platform-driver-x86, linux-input,
	linux-kernel, Jiri Kosina, Benjamin Tissoires, Corentin Chary,
	Hans de Goede, Ilpo Järvinen
In-Reply-To: <0F5019F3-2654-4C03-B7EF-5B7B83AF7B32@ljones.dev>


On 11/19/25 00:46, luke@ljones.dev wrote:
>
>> On 19 Nov 2025, at 12:43, Denis Benato <benato.denis96@gmail.com> wrote:
>>
>>
>> On 11/1/25 11:47, Antheas Kapenekakis wrote:
>>> Handshaking with an Asus device involves sending it a feature report
>>> with the string "ASUS Tech.Inc." and then reading it back to verify the
>>> handshake was successful, under the feature ID the interaction will
>>> take place.
>>>
>>> Currently, the driver only does the first part. Add the readback to
>>> verify the handshake was successful. As this could cause breakages,
>>> allow the verification to fail with a dmesg error until we verify
>>> all devices work with it (they seem to).
>>>
>>> Since the response is more than 16 bytes, increase the buffer size
>>> to 64 as well to avoid overflow errors.
>>>
>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>>> ---
>>> drivers/hid/hid-asus.c | 32 +++++++++++++++++++++++++++++---
>>> 1 file changed, 29 insertions(+), 3 deletions(-)
>>>
>>> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
>>> index 4676b7f20caf..03f0d86936fc 100644
>>> --- a/drivers/hid/hid-asus.c
>>> +++ b/drivers/hid/hid-asus.c
>>> @@ -48,7 +48,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
>>> #define FEATURE_REPORT_ID 0x0d
>>> #define INPUT_REPORT_ID 0x5d
>>> #define FEATURE_KBD_REPORT_ID 0x5a
>>> -#define FEATURE_KBD_REPORT_SIZE 16
>>> +#define FEATURE_KBD_REPORT_SIZE 64
>>> #define FEATURE_KBD_LED_REPORT_ID1 0x5d
>>> #define FEATURE_KBD_LED_REPORT_ID2 0x5e
>>>
>>> @@ -393,14 +393,40 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
>>>
>>> static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
>>> {
>>> +	/*
>>> +	 * The handshake is first sent as a set_report, then retrieved
>>> +	 * from a get_report. They should be equal.
>>> +	 */
>>> 	const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
>>> 		     0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
>>> +	u8 *readbuf;
>> __free(kfree) = NULL here? Would simplify the code.
>>
>>> 	int ret;
>>>
>>> 	ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
>>> -	if (ret < 0)
>>> -		hid_err(hdev, "Asus failed to send init command: %d\n", ret);
>>> +	if (ret < 0) {
>>> +		hid_err(hdev, "Asus failed to send handshake: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
>>> +	if (!readbuf)
>>> +		return -ENOMEM;
>>> +
>>> +	ret = hid_hw_raw_request(hdev, report_id, readbuf,
>>> +				 FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
>>> +				 HID_REQ_GET_REPORT);
>> See comments on patch 1 (also reported below): not sure if others
>> report_id are going to answer, my guess is that we will have to try
>> if you choose to go that route.
>>> +	if (ret < 0) {
>>> +		hid_err(hdev, "Asus failed to receive handshake ack: %d\n", ret);
>>> +	} else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
>>> +		hid_warn(hdev, "Asus handshake returned invalid response: %*ph\n",
>>> +			FEATURE_KBD_REPORT_SIZE, readbuf);
>>> +		/*
>>> +		 * Do not return error if handshake is wrong until this is
>>> +		 * verified to work for all devices.
>>> +		 */
>> In review of patch 1 I requested this function to be called with more report_id
>> than just 0x5a so this will have to be checked against those values too.
>>
>> In alternative you can fork based on the report_id, but having confirmation that
>> this is valid with those ids too would be of great help. Perhaps I can help you
>> with this asking to asus-linux users.
> The handshake works for 0x5D and 0x5E also.
Then this, like patch 2, should have been sent regardless of
this patchset, this whole discussion stalled two good patches
that we all agree are good and are totally independent from
the main issue?
>>> +	}
>>>
>>> +	kfree(readbuf);
>>> 	return ret;
>>> }
>>>

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox