From 656c95a040cccf0962605b97ecc1e826b699c1ca Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 29 Jun 2026 15:41:28 +0200 Subject: [PATCH 001/295] [redhat] enable LEDS_CLASS_MULTICOLOR for RHEL as well JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: RHEL only, https://gitlab.com/cki-project/kernel-ark commit 7a047516fda405a2032d2bf38281f60092599484 Author: Benjamin Tissoires Date: Tue Jun 9 11:17:01 2026 +0200 [redhat] enable LEDS_CLASS_MULTICOLOR for RHEL as well `HID_LOGITECH` depends on `LEDS_CLASS_MULTICOLOR` since upstream commit 4465f4fa21e0 ("HID: Kconfig: Add LEDS_CLASS_MULTICOLOR dependency to HID_LOGITECH"). The problem is all related HID logitech drivers are also depending on HID_LOGITECH, so not having it set means that we remove support for all working Logitech devices. Copy the Fedora setting into RHEL. To ensure the changes did not propagated to other symbols, I've checkout out centos-stream-10 tree on the current main, ran `make dist-configs` before and after the changes, and then compared the produced configs and ensured there were no diffs with: ```bash for config in $(ls redhat/configs/no_multicolor_leds/); \ do diff -u --ignore-matching-lines="^#" \ --ignore-matching-lines=".*CONFIG_LEDS_CLASS_MULTICOLOR.*" \ redhat/configs/no_multicolor_leds/$config \ redhat/configs/multicolor_leds/$config; \ done ``` Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- redhat/configs/common/generic/CONFIG_LEDS_CLASS_MULTICOLOR | 1 + .../rhel/{ => automotive}/generic/CONFIG_LEDS_CLASS_MULTICOLOR | 0 redhat/configs/rhel/generic/CONFIG_HID_LENOVO_GO | 1 + redhat/configs/rhel/generic/CONFIG_HID_LENOVO_GO_S | 1 + redhat/configs/rhel/generic/CONFIG_HID_OXP | 1 + redhat/configs/rhel/generic/CONFIG_HID_PLAYSTATION | 1 + redhat/configs/rhel/generic/CONFIG_LEDS_BLINKM_MULTICOLOR | 1 + redhat/configs/rhel/generic/CONFIG_LEDS_GROUP_MULTICOLOR | 1 + redhat/configs/rhel/generic/CONFIG_LEDS_KTD202X | 1 + redhat/configs/rhel/generic/CONFIG_LEDS_LP5812 | 1 + redhat/configs/rhel/generic/CONFIG_LEDS_LP5860_CORE | 1 + redhat/configs/rhel/generic/CONFIG_LEDS_LP5860_SPI | 1 + redhat/configs/rhel/generic/CONFIG_LEDS_NCP5623 | 1 + redhat/configs/rhel/generic/CONFIG_LEDS_PWM_MULTICOLOR | 1 + redhat/configs/rhel/generic/CONFIG_LEDS_QCOM_LPG | 1 + redhat/configs/rhel/generic/CONFIG_SILICOM_PLATFORM | 1 + 16 files changed, 15 insertions(+) create mode 100644 redhat/configs/common/generic/CONFIG_LEDS_CLASS_MULTICOLOR rename redhat/configs/rhel/{ => automotive}/generic/CONFIG_LEDS_CLASS_MULTICOLOR (100%) create mode 100644 redhat/configs/rhel/generic/CONFIG_HID_LENOVO_GO create mode 100644 redhat/configs/rhel/generic/CONFIG_HID_LENOVO_GO_S create mode 100644 redhat/configs/rhel/generic/CONFIG_HID_OXP create mode 100644 redhat/configs/rhel/generic/CONFIG_HID_PLAYSTATION create mode 100644 redhat/configs/rhel/generic/CONFIG_LEDS_BLINKM_MULTICOLOR create mode 100644 redhat/configs/rhel/generic/CONFIG_LEDS_GROUP_MULTICOLOR create mode 100644 redhat/configs/rhel/generic/CONFIG_LEDS_KTD202X create mode 100644 redhat/configs/rhel/generic/CONFIG_LEDS_LP5812 create mode 100644 redhat/configs/rhel/generic/CONFIG_LEDS_LP5860_CORE create mode 100644 redhat/configs/rhel/generic/CONFIG_LEDS_LP5860_SPI create mode 100644 redhat/configs/rhel/generic/CONFIG_LEDS_NCP5623 create mode 100644 redhat/configs/rhel/generic/CONFIG_LEDS_PWM_MULTICOLOR create mode 100644 redhat/configs/rhel/generic/CONFIG_LEDS_QCOM_LPG create mode 100644 redhat/configs/rhel/generic/CONFIG_SILICOM_PLATFORM diff --git a/redhat/configs/common/generic/CONFIG_LEDS_CLASS_MULTICOLOR b/redhat/configs/common/generic/CONFIG_LEDS_CLASS_MULTICOLOR new file mode 100644 index 000000000000..3d05c10b5d24 --- /dev/null +++ b/redhat/configs/common/generic/CONFIG_LEDS_CLASS_MULTICOLOR @@ -0,0 +1 @@ +CONFIG_LEDS_CLASS_MULTICOLOR=m diff --git a/redhat/configs/rhel/generic/CONFIG_LEDS_CLASS_MULTICOLOR b/redhat/configs/rhel/automotive/generic/CONFIG_LEDS_CLASS_MULTICOLOR similarity index 100% rename from redhat/configs/rhel/generic/CONFIG_LEDS_CLASS_MULTICOLOR rename to redhat/configs/rhel/automotive/generic/CONFIG_LEDS_CLASS_MULTICOLOR diff --git a/redhat/configs/rhel/generic/CONFIG_HID_LENOVO_GO b/redhat/configs/rhel/generic/CONFIG_HID_LENOVO_GO new file mode 100644 index 000000000000..da7c0596700b --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_HID_LENOVO_GO @@ -0,0 +1 @@ +# CONFIG_HID_LENOVO_GO is not set diff --git a/redhat/configs/rhel/generic/CONFIG_HID_LENOVO_GO_S b/redhat/configs/rhel/generic/CONFIG_HID_LENOVO_GO_S new file mode 100644 index 000000000000..2b7260ed84b3 --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_HID_LENOVO_GO_S @@ -0,0 +1 @@ +# CONFIG_HID_LENOVO_GO_S is not set diff --git a/redhat/configs/rhel/generic/CONFIG_HID_OXP b/redhat/configs/rhel/generic/CONFIG_HID_OXP new file mode 100644 index 000000000000..e7a1f19afb1e --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_HID_OXP @@ -0,0 +1 @@ +# CONFIG_HID_OXP is not set diff --git a/redhat/configs/rhel/generic/CONFIG_HID_PLAYSTATION b/redhat/configs/rhel/generic/CONFIG_HID_PLAYSTATION new file mode 100644 index 000000000000..446ce2c2d744 --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_HID_PLAYSTATION @@ -0,0 +1 @@ +# CONFIG_HID_PLAYSTATION is not set diff --git a/redhat/configs/rhel/generic/CONFIG_LEDS_BLINKM_MULTICOLOR b/redhat/configs/rhel/generic/CONFIG_LEDS_BLINKM_MULTICOLOR new file mode 100644 index 000000000000..60b29a0a8f48 --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_LEDS_BLINKM_MULTICOLOR @@ -0,0 +1 @@ +# CONFIG_LEDS_BLINKM_MULTICOLOR is not set diff --git a/redhat/configs/rhel/generic/CONFIG_LEDS_GROUP_MULTICOLOR b/redhat/configs/rhel/generic/CONFIG_LEDS_GROUP_MULTICOLOR new file mode 100644 index 000000000000..27ffc7277549 --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_LEDS_GROUP_MULTICOLOR @@ -0,0 +1 @@ +# CONFIG_LEDS_GROUP_MULTICOLOR is not set diff --git a/redhat/configs/rhel/generic/CONFIG_LEDS_KTD202X b/redhat/configs/rhel/generic/CONFIG_LEDS_KTD202X new file mode 100644 index 000000000000..c68244e9b3db --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_LEDS_KTD202X @@ -0,0 +1 @@ +# CONFIG_LEDS_KTD202X is not set diff --git a/redhat/configs/rhel/generic/CONFIG_LEDS_LP5812 b/redhat/configs/rhel/generic/CONFIG_LEDS_LP5812 new file mode 100644 index 000000000000..4e761b33fe14 --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_LEDS_LP5812 @@ -0,0 +1 @@ +# CONFIG_LEDS_LP5812 is not set diff --git a/redhat/configs/rhel/generic/CONFIG_LEDS_LP5860_CORE b/redhat/configs/rhel/generic/CONFIG_LEDS_LP5860_CORE new file mode 100644 index 000000000000..61c9f3f1e63c --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_LEDS_LP5860_CORE @@ -0,0 +1 @@ +# CONFIG_LEDS_LP5860_CORE is not set diff --git a/redhat/configs/rhel/generic/CONFIG_LEDS_LP5860_SPI b/redhat/configs/rhel/generic/CONFIG_LEDS_LP5860_SPI new file mode 100644 index 000000000000..efe8ab61c264 --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_LEDS_LP5860_SPI @@ -0,0 +1 @@ +# CONFIG_LEDS_LP5860_SPI is not set diff --git a/redhat/configs/rhel/generic/CONFIG_LEDS_NCP5623 b/redhat/configs/rhel/generic/CONFIG_LEDS_NCP5623 new file mode 100644 index 000000000000..57a2b1ee1cdf --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_LEDS_NCP5623 @@ -0,0 +1 @@ +# CONFIG_LEDS_NCP5623 is not set diff --git a/redhat/configs/rhel/generic/CONFIG_LEDS_PWM_MULTICOLOR b/redhat/configs/rhel/generic/CONFIG_LEDS_PWM_MULTICOLOR new file mode 100644 index 000000000000..fb52f72e1ad7 --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_LEDS_PWM_MULTICOLOR @@ -0,0 +1 @@ +# CONFIG_LEDS_PWM_MULTICOLOR is not set diff --git a/redhat/configs/rhel/generic/CONFIG_LEDS_QCOM_LPG b/redhat/configs/rhel/generic/CONFIG_LEDS_QCOM_LPG new file mode 100644 index 000000000000..916e021ce4e4 --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_LEDS_QCOM_LPG @@ -0,0 +1 @@ +# CONFIG_LEDS_QCOM_LPG is not set diff --git a/redhat/configs/rhel/generic/CONFIG_SILICOM_PLATFORM b/redhat/configs/rhel/generic/CONFIG_SILICOM_PLATFORM new file mode 100644 index 000000000000..f9485fecebe6 --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_SILICOM_PLATFORM @@ -0,0 +1 @@ +# CONFIG_SILICOM_PLATFORM is not set From ee6a0377d47c8bfeb0134383b9bdc28b785d59d7 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 9 Jun 2026 09:31:07 +0200 Subject: [PATCH 002/295] HID: hid-lg-g15: Use standard multicolor LED API JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.15 commit a3a064146c507ee5207dcf7223cd7a3d7864e085 Author: Kate Hsuan Date: Fri Jan 31 22:02:41 2025 +0800 HID: hid-lg-g15: Use standard multicolor LED API Replace the custom "color" sysfs attribute with the standard multicolor LED API. This also removes the code for the custom "color" sysfs attribute, the "color" sysfs attribute was never documented so hopefully, it is not used by anyone. If we get complaints, we can re-add the "color" sysfs attribute as a compatibility wrapper setting the subleds intensity. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lg-g15.c | 146 +++++++++++++++++---------------------- 1 file changed, 65 insertions(+), 81 deletions(-) diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c index 53e7b90f9cc3..f8605656257b 100644 --- a/drivers/hid/hid-lg-g15.c +++ b/drivers/hid/hid-lg-g15.c @@ -8,11 +8,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include "hid-ids.h" @@ -44,9 +46,13 @@ enum lg_g15_led_type { }; struct lg_g15_led { - struct led_classdev cdev; + union { + struct led_classdev cdev; + struct led_classdev_mc mcdev; + }; enum led_brightness brightness; enum lg_g15_led_type led; + /* Used to store initial color intensities before subled_info is allocated */ u8 red, green, blue; }; @@ -229,15 +235,15 @@ static int lg_g510_kbd_led_write(struct lg_g15_data *g15, struct lg_g15_led *g15_led, enum led_brightness brightness) { + struct mc_subled *subleds = g15_led->mcdev.subled_info; int ret; + led_mc_calc_color_components(&g15_led->mcdev, brightness); + g15->transfer_buf[0] = 5 + g15_led->led; - g15->transfer_buf[1] = - DIV_ROUND_CLOSEST(g15_led->red * brightness, 255); - g15->transfer_buf[2] = - DIV_ROUND_CLOSEST(g15_led->green * brightness, 255); - g15->transfer_buf[3] = - DIV_ROUND_CLOSEST(g15_led->blue * brightness, 255); + g15->transfer_buf[1] = subleds[0].brightness; + g15->transfer_buf[2] = subleds[1].brightness; + g15->transfer_buf[3] = subleds[2].brightness; ret = hid_hw_raw_request(g15->hdev, LG_G510_FEATURE_BACKLIGHT_RGB + g15_led->led, @@ -258,8 +264,9 @@ static int lg_g510_kbd_led_write(struct lg_g15_data *g15, static int lg_g510_kbd_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) { + struct led_classdev_mc *mc = lcdev_to_mccdev(led_cdev); struct lg_g15_led *g15_led = - container_of(led_cdev, struct lg_g15_led, cdev); + container_of(mc, struct lg_g15_led, mcdev); struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent); int ret; @@ -276,82 +283,20 @@ static int lg_g510_kbd_led_set(struct led_classdev *led_cdev, static enum led_brightness lg_g510_kbd_led_get(struct led_classdev *led_cdev) { + struct led_classdev_mc *mc = lcdev_to_mccdev(led_cdev); struct lg_g15_led *g15_led = - container_of(led_cdev, struct lg_g15_led, cdev); + container_of(mc, struct lg_g15_led, mcdev); return g15_led->brightness; } -static ssize_t color_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct lg_g15_led *g15_led = - container_of(led_cdev, struct lg_g15_led, cdev); - struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent); - unsigned long value; - int ret; - - if (count < 7 || (count == 8 && buf[7] != '\n') || count > 8) - return -EINVAL; - - if (buf[0] != '#') - return -EINVAL; - - ret = kstrtoul(buf + 1, 16, &value); - if (ret) - return ret; - - mutex_lock(&g15->mutex); - g15_led->red = (value & 0xff0000) >> 16; - g15_led->green = (value & 0x00ff00) >> 8; - g15_led->blue = (value & 0x0000ff); - ret = lg_g510_kbd_led_write(g15, g15_led, g15_led->brightness); - mutex_unlock(&g15->mutex); - - return (ret < 0) ? ret : count; -} - -static ssize_t color_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct lg_g15_led *g15_led = - container_of(led_cdev, struct lg_g15_led, cdev); - struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent); - ssize_t ret; - - mutex_lock(&g15->mutex); - ret = sprintf(buf, "#%02x%02x%02x\n", - g15_led->red, g15_led->green, g15_led->blue); - mutex_unlock(&g15->mutex); - - return ret; -} - -static DEVICE_ATTR_RW(color); - -static struct attribute *lg_g510_kbd_led_attrs[] = { - &dev_attr_color.attr, - NULL, -}; - -static const struct attribute_group lg_g510_kbd_led_group = { - .attrs = lg_g510_kbd_led_attrs, -}; - -static const struct attribute_group *lg_g510_kbd_led_groups[] = { - &lg_g510_kbd_led_group, - NULL, -}; - static void lg_g510_leds_sync_work(struct work_struct *work) { struct lg_g15_data *g15 = container_of(work, struct lg_g15_data, work); + struct lg_g15_led *g15_led = &g15->leds[LG_G15_KBD_BRIGHTNESS]; mutex_lock(&g15->mutex); - lg_g510_kbd_led_write(g15, &g15->leds[LG_G15_KBD_BRIGHTNESS], - g15->leds[LG_G15_KBD_BRIGHTNESS].brightness); + lg_g510_kbd_led_write(g15, g15_led, g15_led->brightness); mutex_unlock(&g15->mutex); } @@ -667,8 +612,46 @@ static void lg_g15_input_close(struct input_dev *dev) hid_hw_close(hdev); } +static void lg_g15_setup_led_rgb(struct lg_g15_data *g15, int index) +{ + int i; + struct mc_subled *subled_info; + + g15->leds[index].mcdev.led_cdev.brightness_set_blocking = + lg_g510_kbd_led_set; + g15->leds[index].mcdev.led_cdev.brightness_get = + lg_g510_kbd_led_get; + g15->leds[index].mcdev.led_cdev.max_brightness = 255; + g15->leds[index].mcdev.num_colors = 3; + + subled_info = devm_kcalloc(&g15->hdev->dev, 3, sizeof(*subled_info), GFP_KERNEL); + if (!subled_info) + return; + + for (i = 0; i < 3; i++) { + switch (i + 1) { + case LED_COLOR_ID_RED: + subled_info[i].color_index = LED_COLOR_ID_RED; + subled_info[i].intensity = g15->leds[index].red; + break; + case LED_COLOR_ID_GREEN: + subled_info[i].color_index = LED_COLOR_ID_GREEN; + subled_info[i].intensity = g15->leds[index].green; + break; + case LED_COLOR_ID_BLUE: + subled_info[i].color_index = LED_COLOR_ID_BLUE; + subled_info[i].intensity = g15->leds[index].blue; + break; + } + subled_info[i].channel = i; + } + g15->leds[index].mcdev.subled_info = subled_info; +} + static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name) { + int ret; + g15->leds[i].led = i; g15->leds[i].cdev.name = name; @@ -685,6 +668,7 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name) } else { g15->leds[i].cdev.max_brightness = 1; } + ret = devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev); break; case LG_G510: case LG_G510_USB_AUDIO: @@ -697,12 +681,11 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name) g15->leds[i].cdev.name = "g15::power_on_backlight_val"; fallthrough; case LG_G15_KBD_BRIGHTNESS: - g15->leds[i].cdev.brightness_set_blocking = - lg_g510_kbd_led_set; - g15->leds[i].cdev.brightness_get = - lg_g510_kbd_led_get; - g15->leds[i].cdev.max_brightness = 255; - g15->leds[i].cdev.groups = lg_g510_kbd_led_groups; + /* register multicolor LED */ + lg_g15_setup_led_rgb(g15, i); + ret = devm_led_classdev_multicolor_register_ext(&g15->hdev->dev, + &g15->leds[i].mcdev, + NULL); break; default: g15->leds[i].cdev.brightness_set_blocking = @@ -710,11 +693,12 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name) g15->leds[i].cdev.brightness_get = lg_g510_mkey_led_get; g15->leds[i].cdev.max_brightness = 1; + ret = devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev); } break; } - return devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev); + return ret; } /* Common input device init code shared between keyboards and Z-10 speaker handling */ From 35a3e86d686f767d8bcedaac6257242c18a56491 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 9 Jun 2026 09:32:29 +0200 Subject: [PATCH 003/295] HID: Kconfig: Add LEDS_CLASS_MULTICOLOR dependency to HID_LOGITECH JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.15 commit 4465f4fa21e0e54c10896db3ed49dbd5a9aad3fd Author: Kate Hsuan Date: Tue Feb 11 13:02:40 2025 +0800 HID: Kconfig: Add LEDS_CLASS_MULTICOLOR dependency to HID_LOGITECH The test bot found an issue with building hid-lg-g15. All errors (new ones prefixed by >>): powerpc-linux-ld: drivers/hid/hid-lg-g15.o: in function `lg_g510_kbd_led_write': >> drivers/hid/hid-lg-g15.c:241:(.text+0x768): undefined reference to `led_mc_calc_color_components' powerpc-linux-ld: drivers/hid/hid-lg-g15.o: in function `lg_g15_register_led': >> drivers/hid/hid-lg-g15.c:686:(.text+0xa9c): undefined reference to `devm_led_classdev_multicolor_register_ext' Since multicolor LED APIs manage the keyboard backlight settings of hid-lg-g15, the LEDS_CLASS_MULTICOLOR dependency was added to HID_LOGITECH. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202502110032.VZ0J024X-lkp@intel.com/ Fixes: a3a064146c50 ("HID: hid-lg-g15: Use standard multicolor LED API") Signed-off-by: Kate Hsuan Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 4d47b8d285dc..b934523593d9 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -628,6 +628,7 @@ config HID_LOGITECH tristate "Logitech devices" depends on USB_HID depends on LEDS_CLASS + depends on LEDS_CLASS_MULTICOLOR default !EXPERT help Support for Logitech devices that are not fully compliant with HID standard. From dfd12a9656fd1cda38fd19709a7a344239f75c97 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:38 +0200 Subject: [PATCH 004/295] HID: Intel-thc-hid: Intel-thc: Use str_true_false() helper JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 1860b13beca829c056179c63530eebfec9a3efb4 Author: Liu Song Date: Thu Jul 24 10:36:26 2025 +0800 HID: Intel-thc-hid: Intel-thc: Use str_true_false() helper Remove hard-coded strings by using the str_true_false() helper function. Signed-off-by: Liu Song Reviewed-by: Even Xu Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c index e1cb9b117ebc..636a68306501 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "intel-thc-dev.h" #include "intel-thc-hw.h" @@ -664,7 +665,7 @@ int thc_interrupt_quiesce(const struct thc_device *dev, bool int_quiesce) if (ret) { dev_err_once(dev->dev, "Timeout while waiting THC idle, target quiesce state = %s\n", - int_quiesce ? "true" : "false"); + str_true_false(int_quiesce)); return ret; } From 0f6887170d70013dcf964ac874e0262da2f7db7b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:39 +0200 Subject: [PATCH 005/295] HID: uclogic: Use str_true_false() helper JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit b31e3bd7447a6e826c27bd2b1eeb26b8af73551c Author: Liu Song Date: Thu Jul 24 10:38:16 2025 +0800 HID: uclogic: Use str_true_false() helper Remove hard-coded strings by using the str_true_false() helper function. Signed-off-by: Liu Song Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-uclogic-params.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 016d1f08bd1d..4c4bac6f792b 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -20,6 +20,7 @@ #include #include #include +#include /** * uclogic_params_pen_inrange_to_str() - Convert a pen in-range reporting type @@ -59,7 +60,7 @@ static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev, size_t i; hid_dbg(hdev, "\t.usage_invalid = %s\n", - (pen->usage_invalid ? "true" : "false")); + str_true_false(pen->usage_invalid)); hid_dbg(hdev, "\t.desc_ptr = %p\n", pen->desc_ptr); hid_dbg(hdev, "\t.desc_size = %u\n", pen->desc_size); hid_dbg(hdev, "\t.id = %u\n", pen->id); @@ -74,9 +75,9 @@ static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev, hid_dbg(hdev, "\t.inrange = %s\n", uclogic_params_pen_inrange_to_str(pen->inrange)); hid_dbg(hdev, "\t.fragmented_hires = %s\n", - (pen->fragmented_hires ? "true" : "false")); + str_true_false(pen->fragmented_hires)); hid_dbg(hdev, "\t.tilt_y_flipped = %s\n", - (pen->tilt_y_flipped ? "true" : "false")); + str_true_false(pen->tilt_y_flipped)); } /** @@ -119,8 +120,7 @@ void uclogic_params_hid_dbg(const struct hid_device *hdev, { size_t i; - hid_dbg(hdev, ".invalid = %s\n", - params->invalid ? "true" : "false"); + hid_dbg(hdev, ".invalid = %s\n", str_true_false(params->invalid)); hid_dbg(hdev, ".desc_ptr = %p\n", params->desc_ptr); hid_dbg(hdev, ".desc_size = %u\n", params->desc_size); hid_dbg(hdev, ".pen = {\n"); From bb7acd90fed4d171d6f8333b1ac03568f123ff09 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:40 +0200 Subject: [PATCH 006/295] HID: pidff: Remove unhelpful pidff_set_actuators helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit a02c78f708515b236086bf0a9ad4ac7884e78546 Author: Tomasz Pakuła Date: Wed Aug 13 22:09:50 2025 +0200 HID: pidff: Remove unhelpful pidff_set_actuators helper Abstracts away too little of the functionality and replaces a nice, defined value with a magic bool. There's no actual need for it. Signed-off-by: Tomasz Pakuła Reviewed-by: Oleg Makarenko Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 711eefff853b..1d6dd9edf947 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -605,16 +605,6 @@ static void pidff_set_device_control(struct pidff_device *pidff, int field) hid_hw_wait(pidff->hid); } -/* - * Modify actuators state - */ -static void pidff_set_actuators(struct pidff_device *pidff, bool enable) -{ - hid_dbg(pidff->hid, "%s actuators\n", enable ? "Enable" : "Disable"); - pidff_set_device_control(pidff, - enable ? PID_ENABLE_ACTUATORS : PID_DISABLE_ACTUATORS); -} - /* * Reset the device, stop all effects, enable actuators */ @@ -626,7 +616,7 @@ static void pidff_reset(struct pidff_device *pidff) pidff->effect_count = 0; pidff_set_device_control(pidff, PID_STOP_ALL_EFFECTS); - pidff_set_actuators(pidff, 1); + pidff_set_device_control(pidff, PID_ENABLE_ACTUATORS); } /* From 30133a7e5ddfde6fe3ba31ee93817f340980b620 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:41 +0200 Subject: [PATCH 007/295] HID: pidff: Remove unneeded debug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 6513cfdd838e18c9d2289b0d19474936e37ee5cb Author: Tomasz Pakuła Date: Wed Aug 13 22:09:51 2025 +0200 HID: pidff: Remove unneeded debug All the envelope settings work correctly and even if we wanted to debug something about the envelope report, we would not only need the attack level but it's length and fade properties to have a full image. Signed-off-by: Tomasz Pakuła Reviewed-by: Oleg Makarenko Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 1d6dd9edf947..8ca82401365e 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -339,10 +339,6 @@ static void pidff_set_envelope_report(struct pidff_device *pidff, pidff_set_time(&pidff->set_envelope[PID_FADE_TIME], envelope->fade_length); - hid_dbg(pidff->hid, "attack %u => %d\n", - envelope->attack_level, - pidff->set_envelope[PID_ATTACK_LEVEL].value[0]); - hid_hw_request(pidff->hid, pidff->reports[PID_SET_ENVELOPE], HID_REQ_SET_REPORT); } From f9e362ac67c57950110e7a726a5896ce8c3e2002 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:41 +0200 Subject: [PATCH 008/295] HID: pidff: Use ARRAY_SIZE macro instead of sizeof MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 8de2cef6d0de8cdddee8d653a18791525af26fad Author: Tomasz Pakuła Date: Wed Aug 13 22:09:52 2025 +0200 HID: pidff: Use ARRAY_SIZE macro instead of sizeof Could lead to issues when arrays won't be 8 bit fields Signed-off-by: Tomasz Pakuła Reviewed-by: Oleg Makarenko Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 46 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 8ca82401365e..2b1ef64c129e 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -158,20 +158,20 @@ struct pidff_usage { struct pidff_device { struct hid_device *hid; - struct hid_report *reports[sizeof(pidff_reports)]; + struct hid_report *reports[ARRAY_SIZE(pidff_reports)]; - struct pidff_usage set_effect[sizeof(pidff_set_effect)]; - struct pidff_usage set_envelope[sizeof(pidff_set_envelope)]; - struct pidff_usage set_condition[sizeof(pidff_set_condition)]; - struct pidff_usage set_periodic[sizeof(pidff_set_periodic)]; - struct pidff_usage set_constant[sizeof(pidff_set_constant)]; - struct pidff_usage set_ramp[sizeof(pidff_set_ramp)]; + struct pidff_usage set_effect[ARRAY_SIZE(pidff_set_effect)]; + struct pidff_usage set_envelope[ARRAY_SIZE(pidff_set_envelope)]; + struct pidff_usage set_condition[ARRAY_SIZE(pidff_set_condition)]; + struct pidff_usage set_periodic[ARRAY_SIZE(pidff_set_periodic)]; + struct pidff_usage set_constant[ARRAY_SIZE(pidff_set_constant)]; + struct pidff_usage set_ramp[ARRAY_SIZE(pidff_set_ramp)]; - struct pidff_usage device_gain[sizeof(pidff_device_gain)]; - struct pidff_usage block_load[sizeof(pidff_block_load)]; - struct pidff_usage pool[sizeof(pidff_pool)]; - struct pidff_usage effect_operation[sizeof(pidff_effect_operation)]; - struct pidff_usage block_free[sizeof(pidff_block_free)]; + struct pidff_usage device_gain[ARRAY_SIZE(pidff_device_gain)]; + struct pidff_usage block_load[ARRAY_SIZE(pidff_block_load)]; + struct pidff_usage pool[ARRAY_SIZE(pidff_pool)]; + struct pidff_usage effect_operation[ARRAY_SIZE(pidff_effect_operation)]; + struct pidff_usage block_free[ARRAY_SIZE(pidff_block_free)]; /* * Special field is a field that is not composed of @@ -194,10 +194,10 @@ struct pidff_device { /* Special field in effect_operation */ struct hid_field *effect_operation_status; - int control_id[sizeof(pidff_device_control)]; - int type_id[sizeof(pidff_effect_types)]; - int status_id[sizeof(pidff_block_load_status)]; - int operation_id[sizeof(pidff_effect_operation_status)]; + int control_id[ARRAY_SIZE(pidff_device_control)]; + int type_id[ARRAY_SIZE(pidff_effect_types)]; + int status_id[ARRAY_SIZE(pidff_block_load_status)]; + int operation_id[ARRAY_SIZE(pidff_effect_operation_status)]; int pid_id[PID_EFFECTS_MAX]; @@ -583,7 +583,7 @@ static void pidff_set_device_control(struct pidff_device *pidff, int field) hid_dbg(pidff->hid, "DEVICE_CONTROL is a bitmask\n"); /* Clear current bitmask */ - for (i = 0; i < sizeof(pidff_device_control); i++) { + for (i = 0; i < ARRAY_SIZE(pidff_device_control); i++) { index = pidff->control_id[i]; if (index < 1) continue; @@ -999,7 +999,7 @@ static int pidff_check_usage(int usage) { int i; - for (i = 0; i < sizeof(pidff_reports); i++) + for (i = 0; i < ARRAY_SIZE(pidff_reports); i++) if (usage == (HID_UP_PID | pidff_reports[i])) return i; @@ -1117,7 +1117,7 @@ static int pidff_find_special_keys(int *keys, struct hid_field *fld, #define PIDFF_FIND_SPECIAL_KEYS(keys, field, name) \ pidff_find_special_keys(pidff->keys, pidff->field, pidff_ ## name, \ - sizeof(pidff_ ## name)) + ARRAY_SIZE(pidff_ ## name)) /* * Find and check the special fields @@ -1192,7 +1192,7 @@ static int pidff_find_special_fields(struct pidff_device *pidff) if (PIDFF_FIND_SPECIAL_KEYS(status_id, block_load_status, block_load_status) != - sizeof(pidff_block_load_status)) { + ARRAY_SIZE(pidff_block_load_status)) { hid_err(pidff->hid, "block load status identifiers not found\n"); return -1; @@ -1200,7 +1200,7 @@ static int pidff_find_special_fields(struct pidff_device *pidff) if (PIDFF_FIND_SPECIAL_KEYS(operation_id, effect_operation_status, effect_operation_status) != - sizeof(pidff_effect_operation_status)) { + ARRAY_SIZE(pidff_effect_operation_status)) { hid_err(pidff->hid, "effect operation identifiers not found\n"); return -1; } @@ -1216,7 +1216,7 @@ static int pidff_find_effects(struct pidff_device *pidff, { int i; - for (i = 0; i < sizeof(pidff_effect_types); i++) { + for (i = 0; i < ARRAY_SIZE(pidff_effect_types); i++) { int pidff_type = pidff->type_id[i]; if (pidff->set_effect_type->usage[pidff_type].hid != @@ -1266,7 +1266,7 @@ static int pidff_find_effects(struct pidff_device *pidff, #define PIDFF_FIND_FIELDS(name, report, strict) \ pidff_find_fields(pidff->name, pidff_ ## name, \ pidff->reports[report], \ - sizeof(pidff_ ## name), strict) + ARRAY_SIZE(pidff_ ## name), strict) /* * Fill and check the pidff_usages From b990964352f797f2a5b5660a098a5511182496b8 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:42 +0200 Subject: [PATCH 009/295] HID: pidff: Treat PID_REQUIRED_REPORTS as count, not max MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit c7ad7812fb8c9319a5148b3c8f14b0b0b3d7e015 Author: Tomasz Pakuła Date: Wed Aug 13 22:09:53 2025 +0200 HID: pidff: Treat PID_REQUIRED_REPORTS as count, not max It's naming suggests it's a count of the records required by the USB PID standard and this driver. Signed-off-by: Tomasz Pakuła Reviewed-by: Oleg Makarenko Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 2b1ef64c129e..d4f49a931edc 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -33,7 +33,7 @@ #define PID_DEVICE_CONTROL 6 #define PID_CREATE_NEW_EFFECT 7 -#define PID_REQUIRED_REPORTS 7 +#define PID_REQUIRED_REPORTS 8 #define PID_SET_ENVELOPE 8 #define PID_SET_CONDITION 9 @@ -1056,7 +1056,7 @@ static int pidff_reports_ok(struct pidff_device *pidff) { int i; - for (i = 0; i <= PID_REQUIRED_REPORTS; i++) { + for (i = 0; i < PID_REQUIRED_REPORTS; i++) { if (!pidff->reports[i]) { hid_dbg(pidff->hid, "%d missing\n", i); return 0; From 2cb094451e51beb6a0ac100afd30d1a6abc511a6 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:43 +0200 Subject: [PATCH 010/295] HID: pidff: Better quirk assigment when searching for fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 7f3d7bc0df4bdc23d31cf0f90b6e20c45129465e Author: Tomasz Pakuła Date: Wed Aug 13 22:09:54 2025 +0200 HID: pidff: Better quirk assigment when searching for fields Assign quirks directly when they're discovered. Way easier to understand without relying on return values. Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 59 +++++++++++----------------------- 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index d4f49a931edc..0f51ea5c6d39 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -943,7 +943,8 @@ static void pidff_set_autocenter(struct input_dev *dev, u16 magnitude) * Find fields from a report and fill a pidff_usage */ static int pidff_find_fields(struct pidff_usage *usage, const u8 *table, - struct hid_report *report, int count, int strict) + struct hid_report *report, int count, int strict, + u32 *quirks) { if (!report) { pr_debug("%s, null report\n", __func__); @@ -951,7 +952,6 @@ static int pidff_find_fields(struct pidff_usage *usage, const u8 *table, } int i, j, k, found; - int return_value = 0; for (k = 0; k < count; k++) { found = 0; @@ -979,17 +979,17 @@ static int pidff_find_fields(struct pidff_usage *usage, const u8 *table, if (!found && table[k] == pidff_set_effect[PID_START_DELAY]) { pr_debug("Delay field not found, but that's OK\n"); pr_debug("Setting MISSING_DELAY quirk\n"); - return_value |= HID_PIDFF_QUIRK_MISSING_DELAY; + *quirks |= HID_PIDFF_QUIRK_MISSING_DELAY; } else if (!found && table[k] == pidff_set_condition[PID_PARAM_BLOCK_OFFSET]) { pr_debug("PBO field not found, but that's OK\n"); pr_debug("Setting MISSING_PBO quirk\n"); - return_value |= HID_PIDFF_QUIRK_MISSING_PBO; + *quirks |= HID_PIDFF_QUIRK_MISSING_PBO; } else if (!found && strict) { pr_debug("failed to locate %d\n", k); return -1; } } - return return_value; + return 0; } /* @@ -1266,26 +1266,17 @@ static int pidff_find_effects(struct pidff_device *pidff, #define PIDFF_FIND_FIELDS(name, report, strict) \ pidff_find_fields(pidff->name, pidff_ ## name, \ pidff->reports[report], \ - ARRAY_SIZE(pidff_ ## name), strict) + ARRAY_SIZE(pidff_ ## name), strict, &pidff->quirks) /* * Fill and check the pidff_usages */ static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev) { - int status = 0; - - /* Save info about the device not having the DELAY ffb field. */ - status = PIDFF_FIND_FIELDS(set_effect, PID_SET_EFFECT, 1); - if (status == -1) { + if (PIDFF_FIND_FIELDS(set_effect, PID_SET_EFFECT, 1)) { hid_err(pidff->hid, "unknown set_effect report layout\n"); return -ENODEV; } - pidff->quirks |= status; - - if (status & HID_PIDFF_QUIRK_MISSING_DELAY) - hid_dbg(pidff->hid, "Adding MISSING_DELAY quirk\n"); - PIDFF_FIND_FIELDS(block_load, PID_BLOCK_LOAD, 0); if (!pidff->block_load[PID_EFFECT_BLOCK_INDEX].value) { @@ -1319,39 +1310,25 @@ static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev) "has periodic effect but no envelope\n"); } - if (test_bit(FF_CONSTANT, dev->ffbit) && - PIDFF_FIND_FIELDS(set_constant, PID_SET_CONSTANT, 1)) { + if (PIDFF_FIND_FIELDS(set_constant, PID_SET_CONSTANT, 1) && + test_and_clear_bit(FF_CONSTANT, dev->ffbit)) hid_warn(pidff->hid, "unknown constant effect layout\n"); - clear_bit(FF_CONSTANT, dev->ffbit); - } - if (test_bit(FF_RAMP, dev->ffbit) && - PIDFF_FIND_FIELDS(set_ramp, PID_SET_RAMP, 1)) { + if (PIDFF_FIND_FIELDS(set_ramp, PID_SET_RAMP, 1) && + test_and_clear_bit(FF_RAMP, dev->ffbit)) hid_warn(pidff->hid, "unknown ramp effect layout\n"); - clear_bit(FF_RAMP, dev->ffbit); - } - if (test_bit(FF_SPRING, dev->ffbit) || - test_bit(FF_DAMPER, dev->ffbit) || - test_bit(FF_FRICTION, dev->ffbit) || - test_bit(FF_INERTIA, dev->ffbit)) { - status = PIDFF_FIND_FIELDS(set_condition, PID_SET_CONDITION, 1); - - if (status < 0) { + if (PIDFF_FIND_FIELDS(set_condition, PID_SET_CONDITION, 1)) { + if (test_and_clear_bit(FF_SPRING, dev->ffbit) || + test_and_clear_bit(FF_DAMPER, dev->ffbit) || + test_and_clear_bit(FF_FRICTION, dev->ffbit) || + test_and_clear_bit(FF_INERTIA, dev->ffbit)) hid_warn(pidff->hid, "unknown condition effect layout\n"); - clear_bit(FF_SPRING, dev->ffbit); - clear_bit(FF_DAMPER, dev->ffbit); - clear_bit(FF_FRICTION, dev->ffbit); - clear_bit(FF_INERTIA, dev->ffbit); - } - pidff->quirks |= status; } - if (test_bit(FF_PERIODIC, dev->ffbit) && - PIDFF_FIND_FIELDS(set_periodic, PID_SET_PERIODIC, 1)) { + if (PIDFF_FIND_FIELDS(set_periodic, PID_SET_PERIODIC, 1) && + test_and_clear_bit(FF_PERIODIC, dev->ffbit)) hid_warn(pidff->hid, "unknown periodic effect layout\n"); - clear_bit(FF_PERIODIC, dev->ffbit); - } PIDFF_FIND_FIELDS(pool, PID_POOL, 0); From 131b6b65d6cf851a9abda9b5ea4d9d30a2c69ed9 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:44 +0200 Subject: [PATCH 011/295] HID: pidff: Simplify HID field/usage searching logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit b974b372a9b0aa409ccf05dae8a267d4486f37b4 Author: Tomasz Pakuła Date: Wed Aug 13 22:09:55 2025 +0200 HID: pidff: Simplify HID field/usage searching logic Some deduplication and splitting into separate functions. This is now way easier to comprehend and parse mentally. Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 105 +++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 43 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 0f51ea5c6d39..fdade4e3c50a 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -939,6 +939,43 @@ static void pidff_set_autocenter(struct input_dev *dev, u16 magnitude) pidff_autocenter(dev->ff->private, magnitude); } +/* + * Find specific usage in a given hid_field + */ +static int pidff_find_usage(struct hid_field *fld, unsigned int usage_code) +{ + for (int i = 0; i < fld->maxusage; i++) { + if (fld->usage[i].hid == usage_code) + return i; + } + return -1; +} + +/* + * Find hid_field with a specific usage. Return the usage index as well + */ +static int pidff_find_field_with_usage(int *usage_index, + struct hid_report *report, + unsigned int usage_code) +{ + for (int i = 0; i < report->maxfield; i++) { + struct hid_field *fld = report->field[i]; + + if (fld->maxusage != fld->report_count) { + pr_debug("maxusage and report_count do not match, skipping\n"); + continue; + } + + int index = pidff_find_usage(fld, usage_code); + + if (index >= 0) { + *usage_index = index; + return i; + } + } + return -1; +} + /* * Find fields from a report and fill a pidff_usage */ @@ -946,46 +983,38 @@ static int pidff_find_fields(struct pidff_usage *usage, const u8 *table, struct hid_report *report, int count, int strict, u32 *quirks) { + const u8 block_offset = pidff_set_condition[PID_PARAM_BLOCK_OFFSET]; + const u8 delay = pidff_set_effect[PID_START_DELAY]; + if (!report) { pr_debug("%s, null report\n", __func__); return -1; } - int i, j, k, found; + for (int i = 0; i < count; i++) { + int index; + int found = pidff_find_field_with_usage(&index, report, + HID_UP_PID | table[i]); - for (k = 0; k < count; k++) { - found = 0; - for (i = 0; i < report->maxfield; i++) { - if (report->field[i]->maxusage != - report->field[i]->report_count) { - pr_debug("maxusage and report_count do not match, skipping\n"); - continue; - } - for (j = 0; j < report->field[i]->maxusage; j++) { - if (report->field[i]->usage[j].hid == - (HID_UP_PID | table[k])) { - pr_debug("found %d at %d->%d\n", - k, i, j); - usage[k].field = report->field[i]; - usage[k].value = - &report->field[i]->value[j]; - found = 1; - break; - } - } - if (found) - break; + if (found >= 0) { + pr_debug("found %d at %d->%d\n", i, found, index); + usage[i].field = report->field[found]; + usage[i].value = &report->field[found]->value[index]; + continue; } - if (!found && table[k] == pidff_set_effect[PID_START_DELAY]) { + + if (table[i] == delay) { pr_debug("Delay field not found, but that's OK\n"); pr_debug("Setting MISSING_DELAY quirk\n"); *quirks |= HID_PIDFF_QUIRK_MISSING_DELAY; - } else if (!found && table[k] == pidff_set_condition[PID_PARAM_BLOCK_OFFSET]) { + + } else if (table[i] == block_offset) { pr_debug("PBO field not found, but that's OK\n"); pr_debug("Setting MISSING_PBO quirk\n"); *quirks |= HID_PIDFF_QUIRK_MISSING_PBO; - } else if (!found && strict) { - pr_debug("failed to locate %d\n", k); + + } else if (strict) { + pr_debug("failed to locate %d\n", i); return -1; } } @@ -1054,9 +1083,7 @@ static void pidff_find_reports(struct hid_device *hid, int report_type, */ static int pidff_reports_ok(struct pidff_device *pidff) { - int i; - - for (i = 0; i < PID_REQUIRED_REPORTS; i++) { + for (int i = 0; i < PID_REQUIRED_REPORTS; i++) { if (!pidff->reports[i]) { hid_dbg(pidff->hid, "%d missing\n", i); return 0; @@ -1077,9 +1104,7 @@ static struct hid_field *pidff_find_special_field(struct hid_report *report, return NULL; } - int i; - - for (i = 0; i < report->maxfield; i++) { + for (int i = 0; i < report->maxfield; i++) { if (report->field[i]->logical == (HID_UP_PID | usage) && report->field[i]->report_count > 0) { if (!enforce_min || @@ -1099,18 +1124,12 @@ static struct hid_field *pidff_find_special_field(struct hid_report *report, static int pidff_find_special_keys(int *keys, struct hid_field *fld, const u8 *usagetable, int count) { - - int i, j; int found = 0; - for (i = 0; i < count; i++) { - for (j = 0; j < fld->maxusage; j++) { - if (fld->usage[j].hid == (HID_UP_PID | usagetable[i])) { - keys[i] = j + 1; - found++; - break; - } - } + for (int i = 0; i < count; i++) { + keys[i] = pidff_find_usage(fld, HID_UP_PID | usagetable[i]) + 1; + if (keys[i]) + found++; } return found; } From 7e28f555fa6966a0d294e6e919b8808482126baf Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:44 +0200 Subject: [PATCH 012/295] HID: pidff: Add support for AXES_ENABLE field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 1d72e7bd340b552160df0baca1a85fd1992abe49 Author: Tomasz Pakuła Date: Wed Aug 13 22:09:56 2025 +0200 HID: pidff: Add support for AXES_ENABLE field AXES_ENABLE can be used in place of DIRECTION_ENABLE to indicate, which FFB-enabled axes will be affected by a given effect. EFFECT_DIRECTION enables all and uses the first direction only while AXES_ENABLE is a bitmask and bit indexes are the same as the defined GD usages in the EFFECT_DIRECTION array. Each axis can have it's own direction in this case. Search for AXES_ENABLE, set AXES_ENABLE for all axes if DIRECTION_ENABLE is not used. Search for specific axes in the direction array. Save their indexes. This let us know what axes are actually available on the device and which bit in the AXES_ENABLE field corresponds to which axis. Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 91 ++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 4 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index fdade4e3c50a..361aece2d7a1 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -51,6 +51,7 @@ static const u8 pidff_reports[] = { /* PID special fields */ #define PID_EFFECT_TYPE 0x25 +#define PID_AXES_ENABLE 0x55 #define PID_DIRECTION 0x57 #define PID_EFFECT_OPERATION_ARRAY 0x78 #define PID_BLOCK_LOAD_STATUS 0x8b @@ -150,6 +151,31 @@ static const u8 pidff_effect_operation_status[] = { 0x79, 0x7b }; /* Polar direction 90 degrees (East) */ #define PIDFF_FIXED_WHEEL_DIRECTION 0x4000 +/* AXES_ENABLE and DIRECTION axes */ +enum pid_axes { + PID_AXIS_X, + PID_AXIS_Y, + PID_AXIS_Z, + PID_AXIS_RX, + PID_AXIS_RY, + PID_AXIS_RZ, + PID_AXIS_SLIDER, + PID_AXIS_DIAL, + PID_AXIS_WHEEL, + PID_AXES_COUNT, +}; +static const u8 pidff_direction_axis[] = { + HID_USAGE & HID_GD_X, + HID_USAGE & HID_GD_Y, + HID_USAGE & HID_GD_Z, + HID_USAGE & HID_GD_RX, + HID_USAGE & HID_GD_RY, + HID_USAGE & HID_GD_RZ, + HID_USAGE & HID_GD_SLIDER, + HID_USAGE & HID_GD_DIAL, + HID_USAGE & HID_GD_WHEEL, +}; + struct pidff_usage { struct hid_field *field; s32 *value; @@ -184,6 +210,7 @@ struct pidff_device { /* Special fields in set_effect */ struct hid_field *set_effect_type; struct hid_field *effect_direction; + struct hid_field *axes_enable; /* Special field in device_control */ struct hid_field *device_control; @@ -198,11 +225,13 @@ struct pidff_device { int type_id[ARRAY_SIZE(pidff_effect_types)]; int status_id[ARRAY_SIZE(pidff_block_load_status)]; int operation_id[ARRAY_SIZE(pidff_effect_operation_status)]; + int direction_axis_id[ARRAY_SIZE(pidff_direction_axis)]; int pid_id[PID_EFFECTS_MAX]; u32 quirks; u8 effect_count; + u8 axis_count; }; static int pidff_is_effect_conditional(struct ff_effect *effect) @@ -306,14 +335,37 @@ static void pidff_set_effect_direction(struct pidff_device *pidff, struct ff_effect *effect) { u16 direction = effect->direction; + int direction_enable = 1; /* Use fixed direction if needed */ if (pidff->quirks & HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION && pidff_is_effect_conditional(effect)) direction = PIDFF_FIXED_WHEEL_DIRECTION; + pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = direction_enable; pidff->effect_direction->value[0] = pidff_rescale(direction, U16_MAX, pidff->effect_direction); + + if (direction_enable) + return; + + /* + * For use with improved FFB API + * We want to read the selected axes and their direction from the effect + * struct and only enable those. For now, enable all axes. + * + */ + for (int i = 0; i < PID_AXES_COUNT; i++) { + /* HID index starts with 1 */ + int index = pidff->direction_axis_id[i] - 1; + + if (index < 0) + continue; + + pidff->axes_enable->value[index] = 1; + pidff->effect_direction->value[index] = pidff_rescale( + direction, U16_MAX, pidff->effect_direction); + } } /* @@ -411,7 +463,6 @@ static void pidff_set_effect_report(struct pidff_device *pidff, effect->trigger.interval); pidff->set_effect[PID_GAIN].value[0] = pidff->set_effect[PID_GAIN].field->logical_maximum; - pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = 1; pidff_set_effect_direction(pidff, effect); @@ -1122,12 +1173,13 @@ static struct hid_field *pidff_find_special_field(struct hid_report *report, * Fill a pidff->*_id struct table */ static int pidff_find_special_keys(int *keys, struct hid_field *fld, - const u8 *usagetable, int count) + const u8 *usagetable, int count, + unsigned int usage_page) { int found = 0; for (int i = 0; i < count; i++) { - keys[i] = pidff_find_usage(fld, HID_UP_PID | usagetable[i]) + 1; + keys[i] = pidff_find_usage(fld, usage_page | usagetable[i]) + 1; if (keys[i]) found++; } @@ -1136,7 +1188,11 @@ static int pidff_find_special_keys(int *keys, struct hid_field *fld, #define PIDFF_FIND_SPECIAL_KEYS(keys, field, name) \ pidff_find_special_keys(pidff->keys, pidff->field, pidff_ ## name, \ - ARRAY_SIZE(pidff_ ## name)) + ARRAY_SIZE(pidff_ ## name), HID_UP_PID) + +#define PIDFF_FIND_GENERAL_DESKTOP(keys, field, name) \ + pidff_find_special_keys(pidff->keys, pidff->field, pidff_ ## name, \ + ARRAY_SIZE(pidff_ ## name), HID_UP_GENDESK) /* * Find and check the special fields @@ -1151,6 +1207,9 @@ static int pidff_find_special_fields(struct pidff_device *pidff) pidff->set_effect_type = pidff_find_special_field(pidff->reports[PID_SET_EFFECT], PID_EFFECT_TYPE, 1); + pidff->axes_enable = + pidff_find_special_field(pidff->reports[PID_SET_EFFECT], + PID_AXES_ENABLE, 0); pidff->effect_direction = pidff_find_special_field(pidff->reports[PID_SET_EFFECT], PID_DIRECTION, 0); @@ -1224,6 +1283,30 @@ static int pidff_find_special_fields(struct pidff_device *pidff) return -1; } + if (!pidff->axes_enable) + hid_info(pidff->hid, "axes enable field not found!\n"); + else + hid_dbg(pidff->hid, "axes enable report count: %u\n", + pidff->axes_enable->report_count); + + uint found = PIDFF_FIND_GENERAL_DESKTOP(direction_axis_id, axes_enable, + direction_axis); + + pidff->axis_count = found; + hid_dbg(pidff->hid, "found direction axes: %u", found); + + for (int i = 0; i < sizeof(pidff_direction_axis); i++) { + if (!pidff->direction_axis_id[i]) + continue; + + hid_dbg(pidff->hid, "axis %d, usage: 0x%04x, index: %d", i + 1, + pidff_direction_axis[i], pidff->direction_axis_id[i]); + } + + if (pidff->axes_enable && found != pidff->axes_enable->report_count) + hid_warn(pidff->hid, "axes_enable: %u != direction axes: %u", + pidff->axes_enable->report_count, found); + return 0; } From b628db29be3a22c99d6151917410be307a9993c6 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:45 +0200 Subject: [PATCH 013/295] HID: pidff: Update debug messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 1abfcd86e38c3466acefd0069bf6b3c2514fb886 Author: Tomasz Pakuła Date: Wed Aug 13 22:09:57 2025 +0200 HID: pidff: Update debug messages Signed-off-by: Tomasz Pakuła Better indicate what operation is requested on a given effect (play or stop). Previously, we only had the info about requesting playback but this could be misleading when the looop count is 0. Add debug print that shows what device control command was actually sent to the device. Print out its hex hid usage. Make field_index const to make sure it won't be changed by mistake later. Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 361aece2d7a1..f6f6edf2f603 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -623,8 +623,7 @@ static void pidff_set_gain_report(struct pidff_device *pidff, u16 gain) */ static void pidff_set_device_control(struct pidff_device *pidff, int field) { - int i, index; - int field_index = pidff->control_id[field]; + const int field_index = pidff->control_id[field]; if (field_index < 1) return; @@ -634,8 +633,9 @@ static void pidff_set_device_control(struct pidff_device *pidff, int field) hid_dbg(pidff->hid, "DEVICE_CONTROL is a bitmask\n"); /* Clear current bitmask */ - for (i = 0; i < ARRAY_SIZE(pidff_device_control); i++) { - index = pidff->control_id[i]; + for (int i = 0; i < ARRAY_SIZE(pidff_device_control); i++) { + int index = pidff->control_id[i]; + if (index < 1) continue; @@ -650,6 +650,8 @@ static void pidff_set_device_control(struct pidff_device *pidff, int field) hid_hw_request(pidff->hid, pidff->reports[PID_DEVICE_CONTROL], HID_REQ_SET_REPORT); hid_hw_wait(pidff->hid); + hid_dbg(pidff->hid, "Device control command 0x%02x sent", + pidff_device_control[field]); } /* @@ -751,6 +753,9 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n) { pidff->effect_operation[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id; + hid_dbg(pidff->hid, "%s PID effect %d", n == 0 ? "stopping" : "playing", + pid_id); + if (n == 0) { pidff->effect_operation_status->value[0] = pidff->operation_id[PID_EFFECT_STOP]; @@ -772,6 +777,8 @@ static int pidff_playback(struct input_dev *dev, int effect_id, int value) { struct pidff_device *pidff = dev->ff->private; + hid_dbg(pidff->hid, "requesting %s on FF effect %d", + value == 0 ? "stop" : "playback", effect_id); pidff_playback_pid(pidff, pidff->pid_id[effect_id], value); return 0; } From 5ec3ff93c74fddc56009374960b2788870e695d4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:46 +0200 Subject: [PATCH 014/295] HID: pidff: Rework pidff_upload_effect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 7fbaa031b94182a9c9e58310935a2f74265ef78d Author: Tomasz Pakuła Date: Wed Aug 13 22:09:58 2025 +0200 HID: pidff: Rework pidff_upload_effect One of the more complicated functions. Expunge some of the logic to separate functions (FF -> PID id conversion) Add a macro for envelope check to make it more readable in the upload function. All this made it possible to to expunge common code from the big switch statement and reduce the overall function size considerably. Now it can fit on one screen. Move the effect_cout logic from report functions to upload/erase functions. Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 243 ++++++++++++++++----------------- 1 file changed, 115 insertions(+), 128 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index f6f6edf2f603..6050948de194 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -142,7 +142,7 @@ static const u8 pidff_effect_types[] = { #define PID_BLOCK_LOAD_SUCCESS 0 #define PID_BLOCK_LOAD_FULL 1 #define PID_BLOCK_LOAD_ERROR 2 -static const u8 pidff_block_load_status[] = { 0x8c, 0x8d, 0x8e}; +static const u8 pidff_block_load_status[] = { 0x8c, 0x8d, 0x8e }; #define PID_EFFECT_START 0 #define PID_EFFECT_STOP 1 @@ -242,6 +242,62 @@ static int pidff_is_effect_conditional(struct ff_effect *effect) effect->type == FF_FRICTION; } +/* + * Get PID effect index from FF effect type. + * Return 0 if invalid. + */ +static int pidff_effect_ff_to_pid(struct ff_effect *effect) +{ + switch (effect->type) { + case FF_CONSTANT: + return PID_CONSTANT; + case FF_RAMP: + return PID_RAMP; + case FF_SPRING: + return PID_SPRING; + case FF_DAMPER: + return PID_DAMPER; + case FF_INERTIA: + return PID_INERTIA; + case FF_FRICTION: + return PID_FRICTION; + case FF_PERIODIC: + switch (effect->u.periodic.waveform) { + case FF_SQUARE: + return PID_SQUARE; + case FF_TRIANGLE: + return PID_TRIANGLE; + case FF_SINE: + return PID_SINE; + case FF_SAW_UP: + return PID_SAW_UP; + case FF_SAW_DOWN: + return PID_SAW_DOWN; + } + } + pr_err("invalid effect type\n"); + return -EINVAL; +} + +/* + * Get effect id in the device descriptor. + * Return 0 if invalid. + */ +static int pidff_get_effect_type_id(struct pidff_device *pidff, + struct ff_effect *effect) +{ + int id = pidff_effect_ff_to_pid(effect); + + if (id < 0) + return 0; + + if (effect->type == FF_PERIODIC && + pidff->quirks & HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY) + id = PID_SINE; + + return pidff->type_id[id]; +} + /* * Clamp value for a given field */ @@ -387,12 +443,12 @@ static void pidff_set_envelope_report(struct pidff_device *pidff, pidff->set_envelope[PID_FADE_LEVEL].field); pidff_set_time(&pidff->set_envelope[PID_ATTACK_TIME], - envelope->attack_length); + envelope->attack_length); pidff_set_time(&pidff->set_envelope[PID_FADE_TIME], - envelope->fade_length); + envelope->fade_length); hid_hw_request(pidff->hid, pidff->reports[PID_SET_ENVELOPE], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -401,7 +457,7 @@ static void pidff_set_envelope_report(struct pidff_device *pidff, static int pidff_needs_set_envelope(struct ff_envelope *envelope, struct ff_envelope *old) { - bool needs_new_envelope; + int needs_new_envelope; needs_new_envelope = envelope->attack_level != 0 || envelope->fade_level != 0 || @@ -409,8 +465,7 @@ static int pidff_needs_set_envelope(struct ff_envelope *envelope, envelope->fade_length != 0; if (!needs_new_envelope) - return false; - + return 0; if (!old) return needs_new_envelope; @@ -423,8 +478,8 @@ static int pidff_needs_set_envelope(struct ff_envelope *envelope, /* * Send constant force report to the device */ -static void pidff_set_constant_force_report(struct pidff_device *pidff, - struct ff_effect *effect) +static void pidff_set_constant_report(struct pidff_device *pidff, + struct ff_effect *effect) { pidff->set_constant[PID_EFFECT_BLOCK_INDEX].value[0] = pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; @@ -432,7 +487,7 @@ static void pidff_set_constant_force_report(struct pidff_device *pidff, effect->u.constant.level); hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONSTANT], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -583,8 +638,8 @@ static int pidff_needs_set_condition(struct ff_effect *effect, /* * Send ramp force report to the device */ -static void pidff_set_ramp_force_report(struct pidff_device *pidff, - struct ff_effect *effect) +static void pidff_set_ramp_report(struct pidff_device *pidff, + struct ff_effect *effect) { pidff->set_ramp[PID_EFFECT_BLOCK_INDEX].value[0] = pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; @@ -593,7 +648,7 @@ static void pidff_set_ramp_force_report(struct pidff_device *pidff, pidff_set_signed(&pidff->set_ramp[PID_RAMP_END], effect->u.ramp.end_level); hid_hw_request(pidff->hid, pidff->reports[PID_SET_RAMP], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -703,9 +758,6 @@ static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum) { int j; - if (!pidff->effect_count) - pidff_reset(pidff); - pidff->create_new_effect_type->value[0] = efnum; hid_hw_request(pidff->hid, pidff->reports[PID_CREATE_NEW_EFFECT], HID_REQ_SET_REPORT); @@ -725,8 +777,6 @@ static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum) hid_dbg(pidff->hid, "device reported free memory: %d bytes\n", pidff->block_load[PID_RAM_POOL_AVAILABLE].value ? pidff->block_load[PID_RAM_POOL_AVAILABLE].value[0] : -1); - - pidff->effect_count++; return 0; } if (pidff->block_load_status->value[0] == @@ -767,7 +817,7 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n) } hid_hw_request(pidff->hid, pidff->reports[PID_EFFECT_OPERATION], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -791,10 +841,7 @@ static void pidff_erase_pid(struct pidff_device *pidff, int pid_id) { pidff->block_free[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id; hid_hw_request(pidff->hid, pidff->reports[PID_BLOCK_FREE], - HID_REQ_SET_REPORT); - - if (pidff->effect_count > 0) - pidff->effect_count--; + HID_REQ_SET_REPORT); } /* @@ -816,139 +863,79 @@ static int pidff_erase_effect(struct input_dev *dev, int effect_id) pidff_playback_pid(pidff, pid_id, 0); pidff_erase_pid(pidff, pid_id); + if (pidff->effect_count > 0) + pidff->effect_count--; + + hid_dbg(pidff->hid, "current effect count: %d", pidff->effect_count); return 0; } +#define PIDFF_SET_REPORT_IF_NEEDED(type, effect, old) \ + ({ if (!old || pidff_needs_set_## type(effect, old)) \ + pidff_set_ ##type## _report(pidff, effect); }) + +#define PIDFF_SET_ENVELOPE_IF_NEEDED(type, effect, old) \ + ({ if (pidff_needs_set_envelope(&effect->u.type.envelope, \ + old ? &old->u.type.envelope : NULL)) \ + pidff_set_envelope_report(pidff, &effect->u.type.envelope); }) + /* * Effect upload handler */ -static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect, +static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *new, struct ff_effect *old) { struct pidff_device *pidff = dev->ff->private; - int type_id; - int error; + const int type_id = pidff_get_effect_type_id(pidff, new); - pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0; - if (old) { - pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = - pidff->pid_id[effect->id]; + if (!type_id) { + hid_err(pidff->hid, "effect type not supported\n"); + return -EINVAL; } - switch (effect->type) { + if (!pidff->effect_count) + pidff_reset(pidff); + + if (!old) { + int error = pidff_request_effect_upload(pidff, type_id); + + if (error) + return error; + + pidff->effect_count++; + hid_dbg(pidff->hid, "current effect count: %d", pidff->effect_count); + pidff->pid_id[new->id] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + } + + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->pid_id[new->id]; + + PIDFF_SET_REPORT_IF_NEEDED(effect, new, old); + switch (new->type) { case FF_CONSTANT: - if (!old) { - error = pidff_request_effect_upload(pidff, - pidff->type_id[PID_CONSTANT]); - if (error) - return error; - } - if (!old || pidff_needs_set_effect(effect, old)) - pidff_set_effect_report(pidff, effect); - if (!old || pidff_needs_set_constant(effect, old)) - pidff_set_constant_force_report(pidff, effect); - if (pidff_needs_set_envelope(&effect->u.constant.envelope, - old ? &old->u.constant.envelope : NULL)) - pidff_set_envelope_report(pidff, &effect->u.constant.envelope); + PIDFF_SET_REPORT_IF_NEEDED(constant, new, old); + PIDFF_SET_ENVELOPE_IF_NEEDED(constant, new, old); break; case FF_PERIODIC: - if (!old) { - switch (effect->u.periodic.waveform) { - case FF_SQUARE: - type_id = PID_SQUARE; - break; - case FF_TRIANGLE: - type_id = PID_TRIANGLE; - break; - case FF_SINE: - type_id = PID_SINE; - break; - case FF_SAW_UP: - type_id = PID_SAW_UP; - break; - case FF_SAW_DOWN: - type_id = PID_SAW_DOWN; - break; - default: - hid_err(pidff->hid, "invalid waveform\n"); - return -EINVAL; - } - - if (pidff->quirks & HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY) - type_id = PID_SINE; - - error = pidff_request_effect_upload(pidff, - pidff->type_id[type_id]); - if (error) - return error; - } - if (!old || pidff_needs_set_effect(effect, old)) - pidff_set_effect_report(pidff, effect); - if (!old || pidff_needs_set_periodic(effect, old)) - pidff_set_periodic_report(pidff, effect); - if (pidff_needs_set_envelope(&effect->u.periodic.envelope, - old ? &old->u.periodic.envelope : NULL)) - pidff_set_envelope_report(pidff, &effect->u.periodic.envelope); + PIDFF_SET_REPORT_IF_NEEDED(periodic, new, old); + PIDFF_SET_ENVELOPE_IF_NEEDED(periodic, new, old); break; case FF_RAMP: - if (!old) { - error = pidff_request_effect_upload(pidff, - pidff->type_id[PID_RAMP]); - if (error) - return error; - } - if (!old || pidff_needs_set_effect(effect, old)) - pidff_set_effect_report(pidff, effect); - if (!old || pidff_needs_set_ramp(effect, old)) - pidff_set_ramp_force_report(pidff, effect); - if (pidff_needs_set_envelope(&effect->u.ramp.envelope, - old ? &old->u.ramp.envelope : NULL)) - pidff_set_envelope_report(pidff, &effect->u.ramp.envelope); + PIDFF_SET_REPORT_IF_NEEDED(ramp, new, old); + PIDFF_SET_ENVELOPE_IF_NEEDED(ramp, new, old); break; case FF_SPRING: case FF_DAMPER: case FF_INERTIA: case FF_FRICTION: - if (!old) { - switch (effect->type) { - case FF_SPRING: - type_id = PID_SPRING; - break; - case FF_DAMPER: - type_id = PID_DAMPER; - break; - case FF_INERTIA: - type_id = PID_INERTIA; - break; - case FF_FRICTION: - type_id = PID_FRICTION; - break; - } - error = pidff_request_effect_upload(pidff, - pidff->type_id[type_id]); - if (error) - return error; - } - if (!old || pidff_needs_set_effect(effect, old)) - pidff_set_effect_report(pidff, effect); - if (!old || pidff_needs_set_condition(effect, old)) - pidff_set_condition_report(pidff, effect); + PIDFF_SET_REPORT_IF_NEEDED(condition, new, old); break; - - default: - hid_err(pidff->hid, "invalid type\n"); - return -EINVAL; } - - if (!old) - pidff->pid_id[effect->id] = - pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; - hid_dbg(pidff->hid, "uploaded\n"); - return 0; } From 338c298a58d4fa8fdddff08c70a9611b3199bc64 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:46 +0200 Subject: [PATCH 015/295] HID: pidff: Separate check for infinite duration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit c774a9d78dba045e6d044e7a7e4fbb6deb0e9cbf Author: Tomasz Pakuła Date: Wed Aug 13 22:09:59 2025 +0200 HID: pidff: Separate check for infinite duration It will be used in a few more places so this makes sure it will always work the same. Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 6050948de194..3fd51ad5cf56 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -242,6 +242,11 @@ static int pidff_is_effect_conditional(struct ff_effect *effect) effect->type == FF_FRICTION; } +static int pidff_is_duration_infinite(u16 duration) +{ + return duration == FF_INFINITE || duration == PID_INFINITE; +} + /* * Get PID effect index from FF effect type. * Return 0 if invalid. @@ -374,12 +379,8 @@ static void pidff_set_time(struct pidff_usage *usage, u16 time) static void pidff_set_duration(struct pidff_usage *usage, u16 duration) { - /* Infinite value conversion from Linux API -> PID */ - if (duration == FF_INFINITE) - duration = PID_INFINITE; - /* PID defines INFINITE as the max possible value for duration field */ - if (duration == PID_INFINITE) { + if (pidff_is_duration_infinite(duration)) { usage->value[0] = (1U << usage->field->report_size) - 1; return; } From a07ce1c380b48f9ae75c0bc29bb275d7170b3d29 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:47 +0200 Subject: [PATCH 016/295] HID: pidff: Remove Anssi's email address from info msg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 82b2496227337b4651f32f336645ccc4c14054a3 Author: Tomasz Pakuła Date: Wed Aug 13 22:10:01 2025 +0200 HID: pidff: Remove Anssi's email address from info msg Some users might try to contact him about issues and he's no longer active when it comes to the driver development/fixes. Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 3fd51ad5cf56..ebebac5c4384 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -1559,7 +1559,7 @@ int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks) ff->set_autocenter = pidff_set_autocenter; ff->playback = pidff_playback; - hid_info(dev, "Force feedback for USB HID PID devices by Anssi Hannula \n"); + hid_info(dev, "Force feedback for USB HID PID devices by Anssi Hannula\n"); hid_dbg(dev, "Active quirks mask: 0x%08x\n", pidff->quirks); hid_device_io_stop(hid); From 605773609d672da5d5cd05d7bb6e9123ce8e528b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:48 +0200 Subject: [PATCH 017/295] HID: pidff: Define all cardinal directions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 63cc21dd8be7cff663309750bb609d5b2e888c8b Author: Tomasz Pakuła Date: Wed Aug 13 22:10:02 2025 +0200 HID: pidff: Define all cardinal directions Will be used by ff-effect based autocentering Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index ebebac5c4384..7f4c1186a44d 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -148,8 +148,12 @@ static const u8 pidff_block_load_status[] = { 0x8c, 0x8d, 0x8e }; #define PID_EFFECT_STOP 1 static const u8 pidff_effect_operation_status[] = { 0x79, 0x7b }; -/* Polar direction 90 degrees (East) */ -#define PIDFF_FIXED_WHEEL_DIRECTION 0x4000 +#define PID_DIRECTION_NORTH 0x0000 +#define PID_DIRECTION_EAST 0x4000 +#define PID_DIRECTION_SOUTH 0x8000 +#define PID_DIRECTION_WEST 0xc000 + +#define PIDFF_FIXED_WHEEL_DIRECTION PID_DIRECTION_EAST /* AXES_ENABLE and DIRECTION axes */ enum pid_axes { From 863fa23b439e591877047c9c9554469c45afe267 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:49 +0200 Subject: [PATCH 018/295] HID: pidff: clang-format pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit ae42428fb4e3d2eed344f0d6fcfa778bc8b8f80a Author: Tomasz Pakuła Date: Wed Aug 13 22:10:03 2025 +0200 HID: pidff: clang-format pass Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 55 ++++++++++++++++------------------ 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 7f4c1186a44d..50a8924edfcc 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -9,12 +9,11 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include "hid-pidff.h" +#include #include +#include #include #include -#include -#include - #define PID_EFFECTS_MAX 64 #define PID_INFINITE U16_MAX @@ -321,7 +320,7 @@ static s32 pidff_clamp(s32 i, struct hid_field *field) static int pidff_rescale(int i, int max, struct hid_field *field) { return i * (field->logical_maximum - field->logical_minimum) / max + - field->logical_minimum; + field->logical_minimum; } /* @@ -367,18 +366,18 @@ static void pidff_set_signed(struct pidff_usage *usage, s16 value) else { if (value < 0) usage->value[0] = - pidff_rescale(-value, -S16_MIN, usage->field); + pidff_rescale(-value, -S16_MIN, usage->field); else usage->value[0] = - pidff_rescale(value, S16_MAX, usage->field); + pidff_rescale(value, S16_MAX, usage->field); } pr_debug("calculated from %d to %d\n", value, usage->value[0]); } static void pidff_set_time(struct pidff_usage *usage, u16 time) { - usage->value[0] = pidff_clamp( - pidff_rescale_time(time, usage->field), usage->field); + usage->value[0] = pidff_clamp(pidff_rescale_time(time, usage->field), + usage->field); } static void pidff_set_duration(struct pidff_usage *usage, u16 duration) @@ -516,11 +515,11 @@ static void pidff_set_effect_report(struct pidff_device *pidff, pidff->create_new_effect_type->value[0]; pidff_set_duration(&pidff->set_effect[PID_DURATION], - effect->replay.length); + effect->replay.length); pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = effect->trigger.button; pidff_set_time(&pidff->set_effect[PID_TRIGGER_REPEAT_INT], - effect->trigger.interval); + effect->trigger.interval); pidff->set_effect[PID_GAIN].value[0] = pidff->set_effect[PID_GAIN].field->logical_maximum; @@ -529,10 +528,10 @@ static void pidff_set_effect_report(struct pidff_device *pidff, /* Omit setting delay field if it's missing */ if (!(pidff->quirks & HID_PIDFF_QUIRK_MISSING_DELAY)) pidff_set_time(&pidff->set_effect[PID_START_DELAY], - effect->replay.delay); + effect->replay.delay); hid_hw_request(pidff->hid, pidff->reports[PID_SET_EFFECT], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -562,10 +561,10 @@ static void pidff_set_periodic_report(struct pidff_device *pidff, effect->u.periodic.offset); pidff_set(&pidff->set_periodic[PID_PHASE], effect->u.periodic.phase); pidff_set_time(&pidff->set_periodic[PID_PERIOD], - effect->u.periodic.period); + effect->u.periodic.period); hid_hw_request(pidff->hid, pidff->reports[PID_SET_PERIODIC], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -612,7 +611,7 @@ static void pidff_set_condition_report(struct pidff_device *pidff, pidff_set(&pidff->set_condition[PID_DEAD_BAND], effect->u.condition[i].deadband); hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONDITION], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } } @@ -675,7 +674,7 @@ static void pidff_set_gain_report(struct pidff_device *pidff, u16 gain) pidff_set(&pidff->device_gain[PID_DEVICE_GAIN_FIELD], gain); hid_hw_request(pidff->hid, pidff->reports[PID_DEVICE_GAIN], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -761,21 +760,19 @@ static void pidff_fetch_pool(struct pidff_device *pidff) */ static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum) { - int j; - pidff->create_new_effect_type->value[0] = efnum; hid_hw_request(pidff->hid, pidff->reports[PID_CREATE_NEW_EFFECT], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); hid_dbg(pidff->hid, "create_new_effect sent, type: %d\n", efnum); pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0; pidff->block_load_status->value[0] = 0; hid_hw_wait(pidff->hid); - for (j = 0; j < 60; j++) { + for (int i = 0; i < 60; i++) { hid_dbg(pidff->hid, "pid_block_load requested\n"); hid_hw_request(pidff->hid, pidff->reports[PID_BLOCK_LOAD], - HID_REQ_GET_REPORT); + HID_REQ_GET_REPORT); hid_hw_wait(pidff->hid); if (pidff->block_load_status->value[0] == pidff->status_id[PID_BLOCK_LOAD_SUCCESS]) { @@ -857,8 +854,8 @@ static int pidff_erase_effect(struct input_dev *dev, int effect_id) struct pidff_device *pidff = dev->ff->private; int pid_id = pidff->pid_id[effect_id]; - hid_dbg(pidff->hid, "starting to erase %d/%d\n", - effect_id, pidff->pid_id[effect_id]); + hid_dbg(pidff->hid, "starting to erase %d/%d\n", effect_id, + pidff->pid_id[effect_id]); /* * Wait for the queue to clear. We do not want @@ -978,7 +975,7 @@ static void pidff_autocenter(struct pidff_device *pidff, u16 magnitude) pidff->set_effect[PID_START_DELAY].value[0] = 0; hid_hw_request(pidff->hid, pidff->reports[PID_SET_EFFECT], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -1269,7 +1266,7 @@ static int pidff_find_special_fields(struct pidff_device *pidff) if (PIDFF_FIND_SPECIAL_KEYS(status_id, block_load_status, block_load_status) != - ARRAY_SIZE(pidff_block_load_status)) { + ARRAY_SIZE(pidff_block_load_status)) { hid_err(pidff->hid, "block load status identifiers not found\n"); return -1; @@ -1277,7 +1274,7 @@ static int pidff_find_special_fields(struct pidff_device *pidff) if (PIDFF_FIND_SPECIAL_KEYS(operation_id, effect_operation_status, effect_operation_status) != - ARRAY_SIZE(pidff_effect_operation_status)) { + ARRAY_SIZE(pidff_effect_operation_status)) { hid_err(pidff->hid, "effect operation identifiers not found\n"); return -1; } @@ -1482,8 +1479,8 @@ static int pidff_check_autocenter(struct pidff_device *pidff, int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks) { struct pidff_device *pidff; - struct hid_input *hidinput = list_entry(hid->inputs.next, - struct hid_input, list); + struct hid_input *hidinput = + list_entry(hid->inputs.next, struct hid_input, list); struct input_dev *dev = hidinput->input; struct ff_device *ff; int max_effects; @@ -1570,7 +1567,7 @@ int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks) return 0; - fail: +fail: hid_device_io_stop(hid); kfree(pidff); From 097ac671bb9c8a1f703f39aea28e955af293a777 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:49 +0200 Subject: [PATCH 019/295] HID: universal-pidff: clang-format pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 5b9cae8c6472e44702b2c19e78055248fab94043 Author: Tomasz Pakuła Date: Wed Aug 13 22:10:04 2025 +0200 HID: universal-pidff: clang-format pass Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-universal-pidff.c | 59 ++++++++++++++++--------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/drivers/hid/hid-universal-pidff.c b/drivers/hid/hid-universal-pidff.c index 70fce0f88e82..549dac555d40 100644 --- a/drivers/hid/hid-universal-pidff.c +++ b/drivers/hid/hid-universal-pidff.c @@ -8,12 +8,12 @@ * Copyright (c) 2024, 2025 Tomasz Pakuła */ -#include -#include -#include -#include #include "hid-ids.h" #include "usbhid/hid-pidff.h" +#include +#include +#include +#include #define JOY_RANGE (BTN_DEAD - BTN_JOYSTICK + 1) @@ -21,8 +21,10 @@ * Map buttons manually to extend the default joystick button limit */ static int universal_pidff_input_mapping(struct hid_device *hdev, - struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, - unsigned long **bit, int *max) + struct hid_input *hi, + struct hid_field *field, + struct hid_usage *usage, + unsigned long **bit, int *max) { if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON) return 0; @@ -126,65 +128,64 @@ static int universal_pidff_input_configured(struct hid_device *hdev, if (!test_bit(axis, input->absbit)) continue; - input_set_abs_params(input, axis, - input->absinfo[axis].minimum, - input->absinfo[axis].maximum, - axis == ABS_X ? 0 : 8, 0); + input_set_abs_params(input, axis, input->absinfo[axis].minimum, + input->absinfo[axis].maximum, + axis == ABS_X ? 0 : 8, 0); } /* Remove fuzz and deadzone from the second joystick axis */ if (hdev->vendor == USB_VENDOR_ID_FFBEAST && hdev->product == USB_DEVICE_ID_FFBEAST_JOYSTICK) input_set_abs_params(input, ABS_Y, - input->absinfo[ABS_Y].minimum, - input->absinfo[ABS_Y].maximum, 0, 0); + input->absinfo[ABS_Y].minimum, + input->absinfo[ABS_Y].maximum, 0, 0); return 0; } static const struct hid_device_id universal_pidff_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3), - .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3_2), - .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5), - .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5_2), - .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9), - .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9_2), - .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12), - .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12_2), - .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21), - .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21_2), - .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C5) }, { HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C12) }, { HID_USB_DEVICE(USB_VENDOR_ID_VRS, USB_DEVICE_ID_VRS_DFP), - .driver_data = HID_PIDFF_QUIRK_PERMISSIVE_CONTROL }, + .driver_data = HID_PIDFF_QUIRK_PERMISSIVE_CONTROL }, { HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_JOYSTICK), }, { HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_RUDDER), }, { HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_WHEEL) }, { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V10), - .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, + .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12), - .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, + .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE), - .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, + .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE_2), - .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, + .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_LITE_STAR_GT987), - .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, + .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_INVICTA) }, { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_FORTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_LA_PRIMA) }, { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_TONY_KANAAN) }, - { } + {} }; MODULE_DEVICE_TABLE(hid, universal_pidff_devices); From 73d70d5dabc1293066999bebb5fccd327fd89f9f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:16:50 +0200 Subject: [PATCH 020/295] HID: pidff: Reduce PID_EFFECT_OPERATION spam MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 13120abdb0d70431fb0c0cda29bec184aee782a6 Author: Tomasz Pakuła Date: Wed Aug 13 22:10:05 2025 +0200 HID: pidff: Reduce PID_EFFECT_OPERATION spam Keep track of effect's loop_count to reduce the spam of ffb play commands coming from some games. This should speed up normal magnitude etc updates and slightly increase max possible FFB refresh rate. Helps games like Dirt Rally 2.0, F1 2023, WRC from KT Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 36 ++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 50a8924edfcc..0342c0a3f476 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -184,6 +184,12 @@ struct pidff_usage { s32 *value; }; +struct pidff_effect { + int pid_id; + int is_infinite; + unsigned int loop_count; +}; + struct pidff_device { struct hid_device *hid; @@ -202,6 +208,8 @@ struct pidff_device { struct pidff_usage effect_operation[ARRAY_SIZE(pidff_effect_operation)]; struct pidff_usage block_free[ARRAY_SIZE(pidff_block_free)]; + struct pidff_effect effect[PID_EFFECTS_MAX]; + /* * Special field is a field that is not composed of * usage<->value pairs that pidff_usage values are @@ -230,8 +238,6 @@ struct pidff_device { int operation_id[ARRAY_SIZE(pidff_effect_operation_status)]; int direction_axis_id[ARRAY_SIZE(pidff_direction_axis)]; - int pid_id[PID_EFFECTS_MAX]; - u32 quirks; u8 effect_count; u8 axis_count; @@ -798,6 +804,12 @@ static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum) return -EIO; } +static int pidff_needs_playback(struct pidff_device *pidff, int effect_id, int n) +{ + return pidff->effect[effect_id].is_infinite || + pidff->effect[effect_id].loop_count != n; +} + /* * Play the effect with PID id n times */ @@ -829,9 +841,14 @@ static int pidff_playback(struct input_dev *dev, int effect_id, int value) { struct pidff_device *pidff = dev->ff->private; + if (!pidff_needs_playback(pidff, effect_id, value)) + return 0; + hid_dbg(pidff->hid, "requesting %s on FF effect %d", value == 0 ? "stop" : "playback", effect_id); - pidff_playback_pid(pidff, pidff->pid_id[effect_id], value); + + pidff->effect[effect_id].loop_count = value; + pidff_playback_pid(pidff, pidff->effect[effect_id].pid_id, value); return 0; } @@ -852,10 +869,9 @@ static void pidff_erase_pid(struct pidff_device *pidff, int pid_id) static int pidff_erase_effect(struct input_dev *dev, int effect_id) { struct pidff_device *pidff = dev->ff->private; - int pid_id = pidff->pid_id[effect_id]; + int pid_id = pidff->effect[effect_id].pid_id; - hid_dbg(pidff->hid, "starting to erase %d/%d\n", effect_id, - pidff->pid_id[effect_id]); + hid_dbg(pidff->hid, "starting to erase %d/%d\n", effect_id, pid_id); /* * Wait for the queue to clear. We do not want @@ -906,12 +922,16 @@ static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *new, pidff->effect_count++; hid_dbg(pidff->hid, "current effect count: %d", pidff->effect_count); - pidff->pid_id[new->id] = + pidff->effect[new->id].loop_count = 0; + pidff->effect[new->id].pid_id = pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; } + pidff->effect[new->id].is_infinite = + pidff_is_duration_infinite(new->replay.length); + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = - pidff->pid_id[new->id]; + pidff->effect[new->id].pid_id; PIDFF_SET_REPORT_IF_NEEDED(effect, new, old); switch (new->type) { From 18955c141f44a57aff11f9675c77ce240f7d3d78 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:17:35 +0200 Subject: [PATCH 021/295] HID: i2c-hid: Make elan touch controllers power on after panel is enabled JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit cbdd16b818eef876dd2de9d503fe7397a0666cbe Author: Pin-yen Lin Date: Mon Aug 18 19:49:34 2025 +0800 HID: i2c-hid: Make elan touch controllers power on after panel is enabled Introduce a new HID quirk to indicate that this device has to be enabled after the panel's backlight is enabled, and update the driver data for the elan devices to enable this quirk. This cannot be a I2C HID quirk because the kernel needs to acknowledge this before powering up the device and read the VID/PID. When this quirk is enabled, register .panel_enabled()/.panel_disabling() instead for the panel follower. Also rename the *panel_prepare* functions into *panel_follower* because they could be called in other situations now. Fixes: bd3cba00dcc63 ("HID: i2c-hid: elan: Add support for Elan eKTH6915 i2c-hid touchscreens") Fixes: d06651bebf99e ("HID: i2c-hid: elan: Add elan-ekth6a12nay timing") Reviewed-by: Douglas Anderson Signed-off-by: Pin-yen Lin Acked-by: Jiri Kosina Signed-off-by: Douglas Anderson Link: https://lore.kernel.org/r/20250818115015.2909525-2-treapking@chromium.org Signed-off-by: Benjamin Tissoires --- drivers/hid/i2c-hid/i2c-hid-core.c | 46 ++++++++++++++++----------- drivers/hid/i2c-hid/i2c-hid-of-elan.c | 11 ++++++- include/linux/hid.h | 2 ++ 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 3257aa87be89..cc265e8aa145 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -112,9 +112,9 @@ struct i2c_hid { struct i2chid_ops *ops; struct drm_panel_follower panel_follower; - struct work_struct panel_follower_prepare_work; + struct work_struct panel_follower_work; bool is_panel_follower; - bool prepare_work_finished; + bool panel_follower_work_finished; }; static const struct i2c_hid_quirks { @@ -1118,10 +1118,10 @@ err_power_down: return ret; } -static void ihid_core_panel_prepare_work(struct work_struct *work) +static void ihid_core_panel_follower_work(struct work_struct *work) { struct i2c_hid *ihid = container_of(work, struct i2c_hid, - panel_follower_prepare_work); + panel_follower_work); struct hid_device *hid = ihid->hid; int ret; @@ -1138,7 +1138,7 @@ static void ihid_core_panel_prepare_work(struct work_struct *work) if (ret) dev_warn(&ihid->client->dev, "Power on failed: %d\n", ret); else - WRITE_ONCE(ihid->prepare_work_finished, true); + WRITE_ONCE(ihid->panel_follower_work_finished, true); /* * The work APIs provide a number of memory ordering guarantees @@ -1147,12 +1147,12 @@ static void ihid_core_panel_prepare_work(struct work_struct *work) * guarantee that a write that happened in the work is visible after * cancel_work_sync(). We'll add a write memory barrier here to match * with i2c_hid_core_panel_unpreparing() to ensure that our write to - * prepare_work_finished is visible there. + * panel_follower_work_finished is visible there. */ smp_wmb(); } -static int i2c_hid_core_panel_prepared(struct drm_panel_follower *follower) +static int i2c_hid_core_panel_follower_resume(struct drm_panel_follower *follower) { struct i2c_hid *ihid = container_of(follower, struct i2c_hid, panel_follower); @@ -1160,29 +1160,36 @@ static int i2c_hid_core_panel_prepared(struct drm_panel_follower *follower) * Powering on a touchscreen can be a slow process. Queue the work to * the system workqueue so we don't block the panel's power up. */ - WRITE_ONCE(ihid->prepare_work_finished, false); - schedule_work(&ihid->panel_follower_prepare_work); + WRITE_ONCE(ihid->panel_follower_work_finished, false); + schedule_work(&ihid->panel_follower_work); return 0; } -static int i2c_hid_core_panel_unpreparing(struct drm_panel_follower *follower) +static int i2c_hid_core_panel_follower_suspend(struct drm_panel_follower *follower) { struct i2c_hid *ihid = container_of(follower, struct i2c_hid, panel_follower); - cancel_work_sync(&ihid->panel_follower_prepare_work); + cancel_work_sync(&ihid->panel_follower_work); - /* Match with ihid_core_panel_prepare_work() */ + /* Match with ihid_core_panel_follower_work() */ smp_rmb(); - if (!READ_ONCE(ihid->prepare_work_finished)) + if (!READ_ONCE(ihid->panel_follower_work_finished)) return 0; return i2c_hid_core_suspend(ihid, true); } -static const struct drm_panel_follower_funcs i2c_hid_core_panel_follower_funcs = { - .panel_prepared = i2c_hid_core_panel_prepared, - .panel_unpreparing = i2c_hid_core_panel_unpreparing, +static const struct drm_panel_follower_funcs + i2c_hid_core_panel_follower_prepare_funcs = { + .panel_prepared = i2c_hid_core_panel_follower_resume, + .panel_unpreparing = i2c_hid_core_panel_follower_suspend, +}; + +static const struct drm_panel_follower_funcs + i2c_hid_core_panel_follower_enable_funcs = { + .panel_enabled = i2c_hid_core_panel_follower_resume, + .panel_disabling = i2c_hid_core_panel_follower_suspend, }; static int i2c_hid_core_register_panel_follower(struct i2c_hid *ihid) @@ -1190,7 +1197,10 @@ static int i2c_hid_core_register_panel_follower(struct i2c_hid *ihid) struct device *dev = &ihid->client->dev; int ret; - ihid->panel_follower.funcs = &i2c_hid_core_panel_follower_funcs; + if (ihid->hid->initial_quirks | HID_QUIRK_POWER_ON_AFTER_BACKLIGHT) + ihid->panel_follower.funcs = &i2c_hid_core_panel_follower_enable_funcs; + else + ihid->panel_follower.funcs = &i2c_hid_core_panel_follower_prepare_funcs; /* * If we're not in control of our own power up/power down then we can't @@ -1245,7 +1255,7 @@ int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops, init_waitqueue_head(&ihid->wait); mutex_init(&ihid->cmd_lock); mutex_init(&ihid->reset_lock); - INIT_WORK(&ihid->panel_follower_prepare_work, ihid_core_panel_prepare_work); + INIT_WORK(&ihid->panel_follower_work, ihid_core_panel_follower_work); /* we need to allocate the command buffer without knowing the maximum * size of the reports. Let's use HID_MIN_BUFFER_SIZE, then we do the diff --git a/drivers/hid/i2c-hid/i2c-hid-of-elan.c b/drivers/hid/i2c-hid/i2c-hid-of-elan.c index 3fcff6daa0d3..0215f217f6d8 100644 --- a/drivers/hid/i2c-hid/i2c-hid-of-elan.c +++ b/drivers/hid/i2c-hid/i2c-hid-of-elan.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,7 @@ struct elan_i2c_hid_chip_data { unsigned int post_power_delay_ms; u16 hid_descriptor_address; const char *main_supply_name; + bool power_after_backlight; }; struct i2c_hid_of_elan { @@ -97,6 +99,7 @@ static int i2c_hid_of_elan_probe(struct i2c_client *client) { struct i2c_hid_of_elan *ihid_elan; int ret; + u32 quirks = 0; ihid_elan = devm_kzalloc(&client->dev, sizeof(*ihid_elan), GFP_KERNEL); if (!ihid_elan) @@ -131,8 +134,12 @@ static int i2c_hid_of_elan_probe(struct i2c_client *client) } } + if (ihid_elan->chip_data->power_after_backlight) + quirks = HID_QUIRK_POWER_ON_AFTER_BACKLIGHT; + ret = i2c_hid_core_probe(client, &ihid_elan->ops, - ihid_elan->chip_data->hid_descriptor_address, 0); + ihid_elan->chip_data->hid_descriptor_address, + quirks); if (ret) goto err_deassert_reset; @@ -150,6 +157,7 @@ static const struct elan_i2c_hid_chip_data elan_ekth6915_chip_data = { .post_gpio_reset_on_delay_ms = 300, .hid_descriptor_address = 0x0001, .main_supply_name = "vcc33", + .power_after_backlight = true, }; static const struct elan_i2c_hid_chip_data elan_ekth6a12nay_chip_data = { @@ -157,6 +165,7 @@ static const struct elan_i2c_hid_chip_data elan_ekth6a12nay_chip_data = { .post_gpio_reset_on_delay_ms = 300, .hid_descriptor_address = 0x0001, .main_supply_name = "vcc33", + .power_after_backlight = true, }; static const struct elan_i2c_hid_chip_data ilitek_ili9882t_chip_data = { diff --git a/include/linux/hid.h b/include/linux/hid.h index 2cc4f1e4ea96..c32425b5d011 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -364,6 +364,7 @@ struct hid_item { * | @HID_QUIRK_HAVE_SPECIAL_DRIVER: * | @HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE: * | @HID_QUIRK_IGNORE_SPECIAL_DRIVER + * | @HID_QUIRK_POWER_ON_AFTER_BACKLIGHT * | @HID_QUIRK_FULLSPEED_INTERVAL: * | @HID_QUIRK_NO_INIT_REPORTS: * | @HID_QUIRK_NO_IGNORE: @@ -391,6 +392,7 @@ struct hid_item { #define HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE BIT(20) #define HID_QUIRK_NOINVERT BIT(21) #define HID_QUIRK_IGNORE_SPECIAL_DRIVER BIT(22) +#define HID_QUIRK_POWER_ON_AFTER_BACKLIGHT BIT(23) #define HID_QUIRK_FULLSPEED_INTERVAL BIT(28) #define HID_QUIRK_NO_INIT_REPORTS BIT(29) #define HID_QUIRK_NO_IGNORE BIT(30) From fbfba1b6efd55f5d85dcb864dd2467ba9021c503 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:17:36 +0200 Subject: [PATCH 022/295] HID: i2c-hid: Fix test in i2c_hid_core_register_panel_follower() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 5c76c794bf29399394ebacaa5af8436b8bed0d46 Author: Dan Carpenter Date: Wed Aug 27 15:57:31 2025 +0300 HID: i2c-hid: Fix test in i2c_hid_core_register_panel_follower() Bitwise AND was intended instead of OR. With the current code the condition is always true. Fixes: cbdd16b818ee ("HID: i2c-hid: Make elan touch controllers power on after panel is enabled") Signed-off-by: Dan Carpenter Reviewed-by: Douglas Anderson Reviewed-by: Pin-yen Lin Acked-by: Jiri Kosina Signed-off-by: Douglas Anderson Link: https://lore.kernel.org/r/aK8Au3CgZSTvfEJ6@stanley.mountain Signed-off-by: Benjamin Tissoires --- drivers/hid/i2c-hid/i2c-hid-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index cc265e8aa145..63f46a2e5788 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -1197,7 +1197,7 @@ static int i2c_hid_core_register_panel_follower(struct i2c_hid *ihid) struct device *dev = &ihid->client->dev; int ret; - if (ihid->hid->initial_quirks | HID_QUIRK_POWER_ON_AFTER_BACKLIGHT) + if (ihid->hid->initial_quirks & HID_QUIRK_POWER_ON_AFTER_BACKLIGHT) ihid->panel_follower.funcs = &i2c_hid_core_panel_follower_enable_funcs; else ihid->panel_follower.funcs = &i2c_hid_core_panel_follower_prepare_funcs; From f77610fd4395d3d6dc868aa87302446c8f495a39 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:17:37 +0200 Subject: [PATCH 023/295] HID: core: factor out hid_set_group() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 4e411a3f7bba10579bbeec3af77ae2a05c9e4259 Author: Benjamin Tissoires Date: Thu Aug 21 16:38:13 2025 +0200 HID: core: factor out hid_set_group() When we load a bpf, we can change the report descriptor. However, the current implementation doesn't change the group meaning that we can not rebind a device from hid-generic to hid-multitouch. This is a preparatory patch to force a rescan of the device after the bpf has been loaded. Signed-off-by: Benjamin Tissoires Reviewed-by: Peter Hutterer Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index d962aafcf4d2..0bf9ec4c810e 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2708,6 +2708,20 @@ static bool hid_check_device_match(struct hid_device *hdev, return !hid_ignore_special_drivers && !(hdev->quirks & HID_QUIRK_IGNORE_SPECIAL_DRIVER); } +static void hid_set_group(struct hid_device *hdev) +{ + int ret; + + if (hid_ignore_special_drivers) { + hdev->group = HID_GROUP_GENERIC; + } else if (!hdev->group && + !(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) { + ret = hid_scan_report(hdev); + if (ret) + hid_warn(hdev, "bad device descriptor (%d)\n", ret); + } +} + static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv) { const struct hid_device_id *id; @@ -2903,14 +2917,7 @@ int hid_add_device(struct hid_device *hdev) /* * Scan generic devices for group information */ - if (hid_ignore_special_drivers) { - hdev->group = HID_GROUP_GENERIC; - } else if (!hdev->group && - !(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) { - ret = hid_scan_report(hdev); - if (ret) - hid_warn(hdev, "bad device descriptor (%d)\n", ret); - } + hid_set_group(hdev); hdev->id = atomic_inc_return(&id); From 023163d642861c2a4ec3fe86909fcde84e8ea713 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:17:38 +0200 Subject: [PATCH 024/295] HID: bpf: rescan the device for the group after a load/unload JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 4c2c5ff9f3d72fb41f884f7c493ae9df83379340 Author: Benjamin Tissoires Date: Thu Aug 21 16:38:14 2025 +0200 HID: bpf: rescan the device for the group after a load/unload When a BPF gets loaded, it was previously not possible to bind a hid-generic device to hid-multitouch because the group was never updated. This change forces a rescan of the report descriptor after a bpf is loaded/unloaded so we set up the proper group. This was detected while Peter was trying to fix a Viewsonic device: the HID device sending multiotuch data through a proprietary collection was handled by hid-generic, and we don't have any way of attaching it to hid-multitouch because the pre-scanning wasn't able to see the Contact ID HID usage. Suggested-by: Peter Hutterer Reviewed-by: Peter Hutterer Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 0bf9ec4c810e..d8fe9e6941b4 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -943,6 +943,15 @@ static int hid_scan_report(struct hid_device *hid) parser->device = hid; hid->group = HID_GROUP_GENERIC; + /* + * In case we are re-scanning after a BPF has been loaded, + * we need to use the bpf report descriptor, not the original one. + */ + if (hid->bpf_rdesc && hid->bpf_rsize) { + start = hid->bpf_rdesc; + end = start + hid->bpf_rsize; + } + /* * The parsing is simpler than the one in hid_open_report() as we should * be robust against hid errors. Those errors will be raised by @@ -2728,6 +2737,12 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv) int ret; if (!hdev->bpf_rsize) { + /* we keep a reference to the currently scanned report descriptor */ + const __u8 *original_rdesc = hdev->bpf_rdesc; + + if (!original_rdesc) + original_rdesc = hdev->dev_rdesc; + /* in case a bpf program gets detached, we need to free the old one */ hid_free_bpf_rdesc(hdev); @@ -2737,6 +2752,12 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv) /* call_hid_bpf_rdesc_fixup will always return a valid pointer */ hdev->bpf_rdesc = call_hid_bpf_rdesc_fixup(hdev, hdev->dev_rdesc, &hdev->bpf_rsize); + + /* the report descriptor changed, we need to re-scan it */ + if (original_rdesc != hdev->bpf_rdesc) { + hdev->group = 0; + hid_set_group(hdev); + } } if (!hid_check_device_match(hdev, hdrv, &id)) From 41640b6566f4baa4066e8bf3e72d23e09895980e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:17:38 +0200 Subject: [PATCH 025/295] HID: pidff: Fix possible null pointer dereference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit d927909d4d7a57fc27684e0761fa7ca1991ba115 Author: Tomasz Pakuła Date: Sat Aug 23 21:45:16 2025 +0200 HID: pidff: Fix possible null pointer dereference As reported by Dan Carpenter, if the axes_enable field wasn't found, trying to find the axes themselves will result in a null pointer dereference. This could only occur with a broken PID descriptor, but it's worth protecting from. Exit early if the axes_enable wasn't found AND add a gate to the pidff_find_special_keys to exit early if the passed HID field is null. This will protect again null dereferencing in the future and properly return 0 found special keys. Fixes: 1d72e7bd340b ("HID: pidff: Add support for AXES_ENABLE field") Reported-by: Dan Carpenter Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 0342c0a3f476..edd61ef50e16 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -1194,6 +1194,9 @@ static int pidff_find_special_keys(int *keys, struct hid_field *fld, { int found = 0; + if (!fld) + return 0; + for (int i = 0; i < count; i++) { keys[i] = pidff_find_usage(fld, usage_page | usagetable[i]) + 1; if (keys[i]) @@ -1299,11 +1302,13 @@ static int pidff_find_special_fields(struct pidff_device *pidff) return -1; } - if (!pidff->axes_enable) + if (!pidff->axes_enable) { hid_info(pidff->hid, "axes enable field not found!\n"); - else - hid_dbg(pidff->hid, "axes enable report count: %u\n", - pidff->axes_enable->report_count); + return 0; + } + + hid_dbg(pidff->hid, "axes enable report count: %u\n", + pidff->axes_enable->report_count); uint found = PIDFF_FIND_GENERAL_DESKTOP(direction_axis_id, axes_enable, direction_axis); @@ -1311,7 +1316,7 @@ static int pidff_find_special_fields(struct pidff_device *pidff) pidff->axis_count = found; hid_dbg(pidff->hid, "found direction axes: %u", found); - for (int i = 0; i < sizeof(pidff_direction_axis); i++) { + for (int i = 0; i < ARRAY_SIZE(pidff_direction_axis); i++) { if (!pidff->direction_axis_id[i]) continue; From 2e2cc73e0bed26ab83bbdf2e54732824986aecd9 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:17:39 +0200 Subject: [PATCH 026/295] HID: add haptics page defines JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 5e0ae59159e3a07391a35865bb79ff335473fa79 Author: Angela Czubak Date: Mon Aug 18 23:08:42 2025 +0000 HID: add haptics page defines Introduce haptic usages as defined in HID Usage Tables specification. Add HID units for newton and gram. Signed-off-by: Angela Czubak Co-developed-by: Jonathan Denose Signed-off-by: Jonathan Denose Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- include/linux/hid.h | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/include/linux/hid.h b/include/linux/hid.h index c32425b5d011..301e2d35a38d 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -156,6 +156,7 @@ struct hid_item { #define HID_UP_TELEPHONY 0x000b0000 #define HID_UP_CONSUMER 0x000c0000 #define HID_UP_DIGITIZER 0x000d0000 +#define HID_UP_HAPTIC 0x000e0000 #define HID_UP_PID 0x000f0000 #define HID_UP_BATTERY 0x00850000 #define HID_UP_CAMERA 0x00900000 @@ -316,6 +317,28 @@ struct hid_item { #define HID_DG_TOOLSERIALNUMBER 0x000d005b #define HID_DG_LATENCYMODE 0x000d0060 +#define HID_HP_SIMPLECONTROLLER 0x000e0001 +#define HID_HP_WAVEFORMLIST 0x000e0010 +#define HID_HP_DURATIONLIST 0x000e0011 +#define HID_HP_AUTOTRIGGER 0x000e0020 +#define HID_HP_MANUALTRIGGER 0x000e0021 +#define HID_HP_AUTOTRIGGERASSOCIATEDCONTROL 0x000e0022 +#define HID_HP_INTENSITY 0x000e0023 +#define HID_HP_REPEATCOUNT 0x000e0024 +#define HID_HP_RETRIGGERPERIOD 0x000e0025 +#define HID_HP_WAVEFORMVENDORPAGE 0x000e0026 +#define HID_HP_WAVEFORMVENDORID 0x000e0027 +#define HID_HP_WAVEFORMCUTOFFTIME 0x000e0028 +#define HID_HP_WAVEFORMNONE 0x000e1001 +#define HID_HP_WAVEFORMSTOP 0x000e1002 +#define HID_HP_WAVEFORMCLICK 0x000e1003 +#define HID_HP_WAVEFORMBUZZCONTINUOUS 0x000e1004 +#define HID_HP_WAVEFORMRUMBLECONTINUOUS 0x000e1005 +#define HID_HP_WAVEFORMPRESS 0x000e1006 +#define HID_HP_WAVEFORMRELEASE 0x000e1007 +#define HID_HP_VENDORWAVEFORMMIN 0x000e2001 +#define HID_HP_VENDORWAVEFORMMAX 0x000e2fff + #define HID_BAT_ABSOLUTESTATEOFCHARGE 0x00850065 #define HID_BAT_CHARGING 0x00850044 @@ -425,6 +448,12 @@ struct hid_item { #define HID_REPORT_PROTOCOL 1 #define HID_BOOT_PROTOCOL 0 +/* + * HID units + */ +#define HID_UNIT_GRAM 0x0101 +#define HID_UNIT_NEWTON 0xe111 + /* * This is the global environment of the parser. This information is * persistent for main-items. The global environment can be saved and From 9359718aee0f758ecdf2259c4d21935bd683de7f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:17:40 +0200 Subject: [PATCH 027/295] Input: add FF_HAPTIC effect type JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 08a72a220e960e7f153a810fb633638afd0b7563 Author: Angela Czubak Date: Mon Aug 18 23:08:43 2025 +0000 Input: add FF_HAPTIC effect type FF_HAPTIC effect type can be used to trigger haptic feedback with HID simple haptic usages. Signed-off-by: Angela Czubak Co-developed-by: Jonathan Denose Signed-off-by: Jonathan Denose Acked-by: Dmitry Torokhov Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- include/uapi/linux/input.h | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h index 127119c287cf..6aa703fcfcfb 100644 --- a/include/uapi/linux/input.h +++ b/include/uapi/linux/input.h @@ -429,6 +429,24 @@ struct ff_rumble_effect { __u16 weak_magnitude; }; +/** + * struct ff_haptic_effect + * @hid_usage: hid_usage according to Haptics page (WAVEFORM_CLICK, etc.) + * @vendor_id: the waveform vendor ID if hid_usage is in the vendor-defined range + * @vendor_waveform_page: the vendor waveform page if hid_usage is in the vendor-defined range + * @intensity: strength of the effect as percentage + * @repeat_count: number of times to retrigger effect + * @retrigger_period: time before effect is retriggered (in ms) + */ +struct ff_haptic_effect { + __u16 hid_usage; + __u16 vendor_id; + __u8 vendor_waveform_page; + __u16 intensity; + __u16 repeat_count; + __u16 retrigger_period; +}; + /** * struct ff_effect - defines force feedback effect * @type: type of the effect (FF_CONSTANT, FF_PERIODIC, FF_RAMP, FF_SPRING, @@ -465,6 +483,7 @@ struct ff_effect { struct ff_periodic_effect periodic; struct ff_condition_effect condition[2]; /* One for each axis */ struct ff_rumble_effect rumble; + struct ff_haptic_effect haptic; } u; }; @@ -472,6 +491,7 @@ struct ff_effect { * Force feedback effect types */ +#define FF_HAPTIC 0x4f #define FF_RUMBLE 0x50 #define FF_PERIODIC 0x51 #define FF_CONSTANT 0x52 @@ -481,7 +501,7 @@ struct ff_effect { #define FF_INERTIA 0x56 #define FF_RAMP 0x57 -#define FF_EFFECT_MIN FF_RUMBLE +#define FF_EFFECT_MIN FF_HAPTIC #define FF_EFFECT_MAX FF_RAMP /* From dc1252e953e949c22113dcdac769d575d27a70b1 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:17:41 +0200 Subject: [PATCH 028/295] HID: haptic: introduce hid_haptic_device JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit b44779d44f7152c1b62091ab31514a62b4629d4c Author: Angela Czubak Date: Mon Aug 18 23:08:45 2025 +0000 HID: haptic: introduce hid_haptic_device Define a new structure that contains simple haptic device configuration as well as current state. Add functions that recognize auto trigger and manual trigger reports as well as save their addresses. Verify that the pressure unit is either grams or newtons. Mark the input device as a haptic touchpad if the unit is correct and the reports are found. Signed-off-by: Angela Czubak Co-developed-by: Jonathan Denose Signed-off-by: Jonathan Denose Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/Kconfig | 11 +++++ drivers/hid/Makefile | 1 + drivers/hid/hid-haptic.c | 72 ++++++++++++++++++++++++++++ drivers/hid/hid-haptic.h | 101 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 drivers/hid/hid-haptic.c create mode 100644 drivers/hid/hid-haptic.h diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index b934523593d9..7ff85c7200e5 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -92,6 +92,17 @@ config HID_GENERIC If unsure, say Y. +config HID_HAPTIC + tristate "Haptic touchpad support" + default n + help + Support for touchpads with force sensors and haptic actuators instead of a + traditional button. + Adds extra parsing and FF device for the hid multitouch driver. + It can be used for Elan 2703 haptic touchpad. + + If unsure, say N. + menu "Special HID drivers" config HID_A4TECH diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 10ae5dedbd84..361a7daedeb8 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -4,6 +4,7 @@ # hid-y := hid-core.o hid-input.o hid-quirks.o hid-$(CONFIG_DEBUG_FS) += hid-debug.o +hid-$(CONFIG_HID_HAPTIC) += hid-haptic.o obj-$(CONFIG_HID_BPF) += bpf/ diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c new file mode 100644 index 000000000000..d659a430c1a6 --- /dev/null +++ b/drivers/hid/hid-haptic.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID Haptic support for Linux + * + * Copyright (c) 2021 Angela Czubak + */ + +#include "hid-haptic.h" + +void hid_haptic_feature_mapping(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_field *field, struct hid_usage *usage) +{ + if (usage->hid == HID_HP_AUTOTRIGGER) { + if (usage->usage_index >= field->report_count) { + dev_err(&hdev->dev, + "HID_HP_AUTOTRIGGER out of range\n"); + return; + } + + hid_device_io_start(hdev); + hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); + hid_hw_wait(hdev); + hid_device_io_stop(hdev); + haptic->default_auto_trigger = + field->value[usage->usage_index]; + haptic->auto_trigger_report = field->report; + } +} +EXPORT_SYMBOL_GPL(hid_haptic_feature_mapping); + +bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic, + struct hid_input *hi, struct hid_field *field) +{ + if (field->unit == HID_UNIT_GRAM || field->unit == HID_UNIT_NEWTON) + return true; + return false; +} +EXPORT_SYMBOL_GPL(hid_haptic_check_pressure_unit); + +int hid_haptic_input_mapping(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + if (usage->hid == HID_HP_MANUALTRIGGER) { + haptic->manual_trigger_report = field->report; + /* we don't really want to map these fields */ + return -1; + } + + return 0; +} +EXPORT_SYMBOL_GPL(hid_haptic_input_mapping); + +int hid_haptic_input_configured(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_input *hi) +{ + + if (hi->application == HID_DG_TOUCHPAD) { + if (haptic->auto_trigger_report && + haptic->manual_trigger_report) { + __set_bit(INPUT_PROP_HAPTIC_TOUCHPAD, hi->input->propbit); + return 1; + } + return 0; + } + return -1; +} +EXPORT_SYMBOL_GPL(hid_haptic_input_configured); diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h new file mode 100644 index 000000000000..2e89addf5ec2 --- /dev/null +++ b/drivers/hid/hid-haptic.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * HID Haptic support for Linux + * + * Copyright (c) 2021 Angela Czubak + */ + +#include + +#define HID_HAPTIC_ORDINAL_WAVEFORMNONE 1 +#define HID_HAPTIC_ORDINAL_WAVEFORMSTOP 2 + +#define HID_HAPTIC_MODE_DEVICE 0 +#define HID_HAPTIC_MODE_HOST 1 + +struct hid_haptic_effect { + u8 *report_buf; + struct input_dev *input_dev; + struct work_struct work; + struct list_head control; + struct mutex control_mutex; +}; + +struct hid_haptic_effect_node { + struct list_head node; + struct file *file; +}; + +struct hid_haptic_device { + struct input_dev *input_dev; + struct hid_device *hdev; + struct hid_report *auto_trigger_report; + struct mutex auto_trigger_mutex; + struct workqueue_struct *wq; + struct hid_report *manual_trigger_report; + struct mutex manual_trigger_mutex; + size_t manual_trigger_report_len; + int pressed_state; + s32 pressure_sum; + s32 force_logical_minimum; + s32 force_physical_minimum; + s32 force_resolution; + u32 mode; + u32 default_auto_trigger; + u32 vendor_page; + u32 vendor_id; + u32 max_waveform_id; + u32 max_duration_id; + u16 *hid_usage_map; + u32 *duration_map; + u16 press_ordinal; + u16 release_ordinal; + struct hid_haptic_effect *effect; + struct hid_haptic_effect stop_effect; +}; + +#if IS_ENABLED(CONFIG_HID_HAPTIC) +void hid_haptic_feature_mapping(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_field *field, struct hid_usage + *usage); +bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic, + struct hid_input *hi, struct hid_field *field); +int hid_haptic_input_mapping(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max); +int hid_haptic_input_configured(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_input *hi); +#else +static inline +void hid_haptic_feature_mapping(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_field *field, struct hid_usage + *usage) +{} +static inline +bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic, + struct hid_input *hi, struct hid_field *field) +{ + return false; +} +static inline +int hid_haptic_input_mapping(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + return 0; +} +static inline +int hid_haptic_input_configured(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_input *hi) +{ + return 0; +} +#endif From 7ba2f5e30fc20c13d7d9ba60f88a5fcf7f450bb3 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:17:41 +0200 Subject: [PATCH 029/295] HID: input: allow mapping of haptic output JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 7a56e7b211e5b6e78cd87786ea4c5a62231d1da2 Author: Angela Czubak Date: Mon Aug 18 23:08:46 2025 +0000 HID: input: allow mapping of haptic output This change makes it possible to parse output reports by input mapping functions by HID drivers. Signed-off-by: Angela Czubak Co-developed-by: Jonathan Denose Signed-off-by: Jonathan Denose Reviewed-by: Dmitry Torokhov Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-input.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 2c743e35c1d3..206d8582cf70 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -686,9 +686,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel if (field->report_count < 1) goto ignore; - /* only LED usages are supported in output fields */ + /* only LED and HAPTIC usages are supported in output fields */ if (field->report_type == HID_OUTPUT_REPORT && - (usage->hid & HID_USAGE_PAGE) != HID_UP_LED) { + (usage->hid & HID_USAGE_PAGE) != HID_UP_LED && + (usage->hid & HID_USAGE_PAGE) != HID_UP_HAPTIC) { goto ignore; } From 8ee62fb7f97d0c1002616221f300b379152cc783 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:17:42 +0200 Subject: [PATCH 030/295] HID: haptic: initialize haptic device JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 344ff358495706abc7aac1e0c59a5bb16b350307 Author: Angela Czubak Date: Mon Aug 18 23:08:47 2025 +0000 HID: haptic: initialize haptic device Add hid_haptic_init(). Parse autotrigger report to retrieve ordinals for press and release waveforms. Implement force feedback functions. Signed-off-by: Angela Czubak Co-developed-by: Jonathan Denose Signed-off-by: Jonathan Denose Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-haptic.c | 438 +++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-haptic.h | 5 + 2 files changed, 443 insertions(+) diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c index d659a430c1a6..ef09b4039f33 100644 --- a/drivers/hid/hid-haptic.c +++ b/drivers/hid/hid-haptic.c @@ -5,12 +5,16 @@ * Copyright (c) 2021 Angela Czubak */ +#include + #include "hid-haptic.h" void hid_haptic_feature_mapping(struct hid_device *hdev, struct hid_haptic_device *haptic, struct hid_field *field, struct hid_usage *usage) { + u16 usage_hid; + if (usage->hid == HID_HP_AUTOTRIGGER) { if (usage->usage_index >= field->report_count) { dev_err(&hdev->dev, @@ -25,6 +29,20 @@ void hid_haptic_feature_mapping(struct hid_device *hdev, haptic->default_auto_trigger = field->value[usage->usage_index]; haptic->auto_trigger_report = field->report; + } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ORDINAL) { + usage_hid = usage->hid & HID_USAGE; + switch (field->logical) { + case HID_HP_WAVEFORMLIST: + if (usage_hid > haptic->max_waveform_id) + haptic->max_waveform_id = usage_hid; + break; + case HID_HP_DURATIONLIST: + if (usage_hid > haptic->max_duration_id) + haptic->max_duration_id = usage_hid; + break; + default: + break; + } } } EXPORT_SYMBOL_GPL(hid_haptic_feature_mapping); @@ -70,3 +88,423 @@ int hid_haptic_input_configured(struct hid_device *hdev, return -1; } EXPORT_SYMBOL_GPL(hid_haptic_input_configured); + +static void parse_auto_trigger_field(struct hid_haptic_device *haptic, + struct hid_field *field) +{ + int count = field->report_count; + int n; + u16 usage_hid; + + for (n = 0; n < count; n++) { + switch (field->usage[n].hid & HID_USAGE_PAGE) { + case HID_UP_ORDINAL: + usage_hid = field->usage[n].hid & HID_USAGE; + switch (field->logical) { + case HID_HP_WAVEFORMLIST: + haptic->hid_usage_map[usage_hid] = field->value[n]; + if (field->value[n] == + (HID_HP_WAVEFORMPRESS & HID_USAGE)) { + haptic->press_ordinal = usage_hid; + } else if (field->value[n] == + (HID_HP_WAVEFORMRELEASE & HID_USAGE)) { + haptic->release_ordinal = usage_hid; + } + break; + case HID_HP_DURATIONLIST: + haptic->duration_map[usage_hid] = + field->value[n]; + break; + default: + break; + } + break; + case HID_UP_HAPTIC: + switch (field->usage[n].hid) { + case HID_HP_WAVEFORMVENDORID: + haptic->vendor_id = field->value[n]; + break; + case HID_HP_WAVEFORMVENDORPAGE: + haptic->vendor_page = field->value[n]; + break; + default: + break; + } + break; + default: + /* Should not really happen */ + break; + } + } +} + +static void fill_effect_buf(struct hid_haptic_device *haptic, + struct ff_haptic_effect *effect, + struct hid_haptic_effect *haptic_effect, + int waveform_ordinal) +{ + struct hid_report *rep = haptic->manual_trigger_report; + struct hid_usage *usage; + struct hid_field *field; + s32 value; + int i, j; + u8 *buf = haptic_effect->report_buf; + + mutex_lock(&haptic->manual_trigger_mutex); + for (i = 0; i < rep->maxfield; i++) { + field = rep->field[i]; + /* Ignore if report count is out of bounds. */ + if (field->report_count < 1) + continue; + + for (j = 0; j < field->maxusage; j++) { + usage = &field->usage[j]; + + switch (usage->hid) { + case HID_HP_INTENSITY: + if (effect->intensity > 100) { + value = field->logical_maximum; + } else { + value = field->logical_minimum + + effect->intensity * + (field->logical_maximum - + field->logical_minimum) / 100; + } + break; + case HID_HP_REPEATCOUNT: + value = effect->repeat_count; + break; + case HID_HP_RETRIGGERPERIOD: + value = effect->retrigger_period; + break; + case HID_HP_MANUALTRIGGER: + value = waveform_ordinal; + break; + default: + break; + } + + field->value[j] = value; + } + } + + hid_output_report(rep, buf); + mutex_unlock(&haptic->manual_trigger_mutex); +} + +static int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *effect, + struct ff_effect *old) +{ + struct ff_device *ff = dev->ff; + struct hid_haptic_device *haptic = ff->private; + int i, ordinal = 0; + + /* If vendor range, check vendor id and page */ + if (effect->u.haptic.hid_usage >= (HID_HP_VENDORWAVEFORMMIN & HID_USAGE) && + effect->u.haptic.hid_usage <= (HID_HP_VENDORWAVEFORMMAX & HID_USAGE) && + (effect->u.haptic.vendor_id != haptic->vendor_id || + effect->u.haptic.vendor_waveform_page != haptic->vendor_page)) + return -EINVAL; + + /* Check hid_usage */ + for (i = 1; i <= haptic->max_waveform_id; i++) { + if (haptic->hid_usage_map[i] == effect->u.haptic.hid_usage) { + ordinal = i; + break; + } + } + if (ordinal < 1) + return -EINVAL; + + /* Fill the buffer for the effect id */ + fill_effect_buf(haptic, &effect->u.haptic, &haptic->effect[effect->id], + ordinal); + + return 0; +} + +static int play_effect(struct hid_device *hdev, struct hid_haptic_device *haptic, + struct hid_haptic_effect *effect) +{ + int ret; + + ret = hid_hw_output_report(hdev, effect->report_buf, + haptic->manual_trigger_report_len); + if (ret < 0) { + ret = hid_hw_raw_request(hdev, + haptic->manual_trigger_report->id, + effect->report_buf, + haptic->manual_trigger_report_len, + HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); + } + + return ret; +} + +static void haptic_work_handler(struct work_struct *work) +{ + + struct hid_haptic_effect *effect = container_of(work, + struct hid_haptic_effect, + work); + struct input_dev *dev = effect->input_dev; + struct hid_device *hdev = input_get_drvdata(dev); + struct hid_haptic_device *haptic = dev->ff->private; + + mutex_lock(&haptic->manual_trigger_mutex); + if (effect != &haptic->stop_effect) + play_effect(hdev, haptic, &haptic->stop_effect); + + play_effect(hdev, haptic, effect); + mutex_unlock(&haptic->manual_trigger_mutex); + +} + +static int hid_haptic_playback(struct input_dev *dev, int effect_id, int value) +{ + struct hid_haptic_device *haptic = dev->ff->private; + + if (value) + queue_work(haptic->wq, &haptic->effect[effect_id].work); + else + queue_work(haptic->wq, &haptic->stop_effect.work); + + return 0; +} + +static void effect_set_default(struct ff_effect *effect) +{ + effect->type = FF_HAPTIC; + effect->id = -1; + effect->u.haptic.hid_usage = HID_HP_WAVEFORMNONE & HID_USAGE; + effect->u.haptic.intensity = 100; + effect->u.haptic.retrigger_period = 0; + effect->u.haptic.repeat_count = 0; +} + +static int hid_haptic_erase(struct input_dev *dev, int effect_id) +{ + struct hid_haptic_device *haptic = dev->ff->private; + struct ff_effect effect; + int ordinal; + + effect_set_default(&effect); + + if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE)) { + ordinal = haptic->release_ordinal; + if (!ordinal) + ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE; + else + effect.u.haptic.hid_usage = HID_HP_WAVEFORMRELEASE & + HID_USAGE; + fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id], + ordinal); + } else if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE)) { + ordinal = haptic->press_ordinal; + if (!ordinal) + ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE; + else + effect.u.haptic.hid_usage = HID_HP_WAVEFORMPRESS & + HID_USAGE; + fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id], + ordinal); + } + + return 0; +} + +static void hid_haptic_destroy(struct ff_device *ff) +{ + struct hid_haptic_device *haptic = ff->private; + struct hid_device *hdev = haptic->hdev; + int r; + + if (hdev) + put_device(&hdev->dev); + + kfree(haptic->stop_effect.report_buf); + haptic->stop_effect.report_buf = NULL; + + if (haptic->effect) { + for (r = 0; r < ff->max_effects; r++) + kfree(haptic->effect[r].report_buf); + kfree(haptic->effect); + } + haptic->effect = NULL; + + destroy_workqueue(haptic->wq); + haptic->wq = NULL; + + kfree(haptic->duration_map); + haptic->duration_map = NULL; + + kfree(haptic->hid_usage_map); + haptic->hid_usage_map = NULL; + + module_put(THIS_MODULE); +} + +int hid_haptic_init(struct hid_device *hdev, + struct hid_haptic_device **haptic_ptr) +{ + struct hid_haptic_device *haptic = *haptic_ptr; + struct input_dev *dev = NULL; + struct hid_input *hidinput; + struct ff_device *ff; + int ret = 0, r; + struct ff_haptic_effect stop_effect = { + .hid_usage = HID_HP_WAVEFORMSTOP & HID_USAGE, + }; + const char *prefix = "hid-haptic"; + char *name; + int (*flush)(struct input_dev *dev, struct file *file); + int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); + + haptic->hdev = hdev; + haptic->max_waveform_id = max(2u, haptic->max_waveform_id); + haptic->max_duration_id = max(2u, haptic->max_duration_id); + + haptic->hid_usage_map = kcalloc(haptic->max_waveform_id + 1, + sizeof(u16), GFP_KERNEL); + if (!haptic->hid_usage_map) { + ret = -ENOMEM; + goto exit; + } + haptic->duration_map = kcalloc(haptic->max_duration_id + 1, + sizeof(u32), GFP_KERNEL); + if (!haptic->duration_map) { + ret = -ENOMEM; + goto usage_map; + } + + if (haptic->max_waveform_id != haptic->max_duration_id) + dev_warn(&hdev->dev, + "Haptic duration and waveform lists have different max id (%u and %u).\n", + haptic->max_duration_id, haptic->max_waveform_id); + + haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMNONE] = + HID_HP_WAVEFORMNONE & HID_USAGE; + haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMSTOP] = + HID_HP_WAVEFORMSTOP & HID_USAGE; + + for (r = 0; r < haptic->auto_trigger_report->maxfield; r++) + parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]); + + list_for_each_entry(hidinput, &hdev->inputs, list) { + if (hidinput->application == HID_DG_TOUCHPAD) { + dev = hidinput->input; + break; + } + } + + if (!dev) { + dev_err(&hdev->dev, "Failed to find the input device\n"); + ret = -ENODEV; + goto duration_map; + } + + haptic->input_dev = dev; + haptic->manual_trigger_report_len = + hid_report_len(haptic->manual_trigger_report); + mutex_init(&haptic->manual_trigger_mutex); + name = kmalloc(strlen(prefix) + strlen(hdev->name) + 2, GFP_KERNEL); + if (name) { + sprintf(name, "%s %s", prefix, hdev->name); + haptic->wq = create_singlethread_workqueue(name); + kfree(name); + } + if (!haptic->wq) { + ret = -ENOMEM; + goto duration_map; + } + haptic->effect = kcalloc(FF_MAX_EFFECTS, + sizeof(struct hid_haptic_effect), GFP_KERNEL); + if (!haptic->effect) { + ret = -ENOMEM; + goto output_queue; + } + for (r = 0; r < FF_MAX_EFFECTS; r++) { + haptic->effect[r].report_buf = + hid_alloc_report_buf(haptic->manual_trigger_report, + GFP_KERNEL); + if (!haptic->effect[r].report_buf) { + dev_err(&hdev->dev, + "Failed to allocate a buffer for an effect.\n"); + ret = -ENOMEM; + goto buffer_free; + } + haptic->effect[r].input_dev = dev; + INIT_WORK(&haptic->effect[r].work, haptic_work_handler); + } + haptic->stop_effect.report_buf = + hid_alloc_report_buf(haptic->manual_trigger_report, + GFP_KERNEL); + if (!haptic->stop_effect.report_buf) { + dev_err(&hdev->dev, + "Failed to allocate a buffer for stop effect.\n"); + ret = -ENOMEM; + goto buffer_free; + } + haptic->stop_effect.input_dev = dev; + INIT_WORK(&haptic->stop_effect.work, haptic_work_handler); + fill_effect_buf(haptic, &stop_effect, &haptic->stop_effect, + HID_HAPTIC_ORDINAL_WAVEFORMSTOP); + + input_set_capability(dev, EV_FF, FF_HAPTIC); + + flush = dev->flush; + event = dev->event; + ret = input_ff_create(dev, FF_MAX_EFFECTS); + if (ret) { + dev_err(&hdev->dev, "Failed to create ff device.\n"); + goto stop_buffer_free; + } + + ff = dev->ff; + ff->private = haptic; + ff->upload = hid_haptic_upload_effect; + ff->playback = hid_haptic_playback; + ff->erase = hid_haptic_erase; + ff->destroy = hid_haptic_destroy; + if (!try_module_get(THIS_MODULE)) { + dev_err(&hdev->dev, "Failed to increase module count.\n"); + goto input_free; + } + if (!get_device(&hdev->dev)) { + dev_err(&hdev->dev, "Failed to get hdev device.\n"); + module_put(THIS_MODULE); + goto input_free; + } + return 0; + +input_free: + input_ff_destroy(dev); + /* Do not let double free happen, input_ff_destroy will call + * hid_haptic_destroy. + */ + *haptic_ptr = NULL; + /* Restore dev flush and event */ + dev->flush = flush; + dev->event = event; + return ret; +stop_buffer_free: + kfree(haptic->stop_effect.report_buf); + haptic->stop_effect.report_buf = NULL; +buffer_free: + while (--r >= 0) + kfree(haptic->effect[r].report_buf); + kfree(haptic->effect); + haptic->effect = NULL; +output_queue: + destroy_workqueue(haptic->wq); + haptic->wq = NULL; +duration_map: + kfree(haptic->duration_map); + haptic->duration_map = NULL; +usage_map: + kfree(haptic->hid_usage_map); + haptic->hid_usage_map = NULL; +exit: + return ret; +} +EXPORT_SYMBOL_GPL(hid_haptic_init); diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h index 2e89addf5ec2..25e0e2cef939 100644 --- a/drivers/hid/hid-haptic.h +++ b/drivers/hid/hid-haptic.h @@ -69,6 +69,7 @@ int hid_haptic_input_mapping(struct hid_device *hdev, int hid_haptic_input_configured(struct hid_device *hdev, struct hid_haptic_device *haptic, struct hid_input *hi); +int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr); #else static inline void hid_haptic_feature_mapping(struct hid_device *hdev, @@ -98,4 +99,8 @@ int hid_haptic_input_configured(struct hid_device *hdev, { return 0; } +int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr) +{ + return 0; +} #endif From 1b9ac6af6c3b19ebd6ca445ae9982e864b4e204f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:17:43 +0200 Subject: [PATCH 031/295] HID: input: calculate resolution for pressure JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 7657bf0b7cfb63ded95a6fe4e1cf106cf386b6cc Author: Angela Czubak Date: Mon Aug 18 23:08:48 2025 +0000 HID: input: calculate resolution for pressure Assume that if the pressure is given in newtons it should be normalized to grams. If the pressure has no unit do not calculate resolution. Signed-off-by: Angela Czubak Co-developed-by: Jonathan Denose Signed-off-by: Jonathan Denose Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-input.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 206d8582cf70..e56e7de53279 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -303,6 +303,19 @@ __s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code) } break; + case ABS_PRESSURE: + case ABS_MT_PRESSURE: + if (field->unit == HID_UNIT_NEWTON) { + /* Convert to grams, 1 newton is 101.97 grams */ + prev = physical_extents; + physical_extents *= 10197; + if (physical_extents < prev) + return 0; + unit_exponent -= 2; + } else if (field->unit != HID_UNIT_GRAM) { + return 0; + } + break; default: return 0; } From 8265b3698b996d0fb15793e6d77271f355237906 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:17:44 +0200 Subject: [PATCH 032/295] HID: haptic: add functions handling events JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit a77efca3aa85e382b52b587ca9976c0f8e11d49c Author: Angela Czubak Date: Mon Aug 18 23:08:49 2025 +0000 HID: haptic: add functions handling events Implement hid_haptic_handle_press_release() which generates haptic feedback as well as saves the pressed state of the haptic device. Add functions to increase and reset the state of the pressure detected by the device. Signed-off-by: Angela Czubak Co-developed-by: Jonathan Denose Signed-off-by: Jonathan Denose Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-haptic.c | 20 +++++++++++++++++++- drivers/hid/hid-haptic.h | 15 +++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c index ef09b4039f33..c02af820051c 100644 --- a/drivers/hid/hid-haptic.c +++ b/drivers/hid/hid-haptic.c @@ -50,8 +50,13 @@ EXPORT_SYMBOL_GPL(hid_haptic_feature_mapping); bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic, struct hid_input *hi, struct hid_field *field) { - if (field->unit == HID_UNIT_GRAM || field->unit == HID_UNIT_NEWTON) + if (field->unit == HID_UNIT_GRAM || field->unit == HID_UNIT_NEWTON) { + haptic->force_logical_minimum = field->logical_minimum; + haptic->force_physical_minimum = field->physical_minimum; + haptic->force_resolution = input_abs_get_res(hi->input, + ABS_MT_PRESSURE); return true; + } return false; } EXPORT_SYMBOL_GPL(hid_haptic_check_pressure_unit); @@ -508,3 +513,16 @@ exit: return ret; } EXPORT_SYMBOL_GPL(hid_haptic_init); + +void hid_haptic_pressure_reset(struct hid_haptic_device *haptic) +{ + haptic->pressure_sum = 0; +} +EXPORT_SYMBOL_GPL(hid_haptic_pressure_reset); + +void hid_haptic_pressure_increase(struct hid_haptic_device *haptic, + __s32 pressure) +{ + haptic->pressure_sum += pressure; +} +EXPORT_SYMBOL_GPL(hid_haptic_pressure_increase); diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h index 25e0e2cef939..abdd7d710c0b 100644 --- a/drivers/hid/hid-haptic.h +++ b/drivers/hid/hid-haptic.h @@ -70,6 +70,10 @@ int hid_haptic_input_configured(struct hid_device *hdev, struct hid_haptic_device *haptic, struct hid_input *hi); int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr); +void hid_haptic_handle_press_release(struct hid_haptic_device *haptic); +void hid_haptic_pressure_reset(struct hid_haptic_device *haptic); +void hid_haptic_pressure_increase(struct hid_haptic_device *haptic, + __s32 pressure); #else static inline void hid_haptic_feature_mapping(struct hid_device *hdev, @@ -103,4 +107,15 @@ int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_p { return 0; } +static inline +void hid_haptic_handle_press_release(struct hid_haptic_device *haptic) +{} +static inline +void hid_haptic_pressure_reset(struct hid_haptic_device *haptic) +{} +static inline +void hid_haptic_pressure_increase(struct hid_haptic_device *haptic, + __s32 pressure) +{} #endif + From 07e4507ddbdfdebd2b47b0129072c4d71c4a3c84 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 3 Jun 2026 17:17:44 +0200 Subject: [PATCH 033/295] HID: haptic: add hid_haptic_switch_mode JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit ff66b8eebbab37146ef1399d93332f82b752990b Author: Angela Czubak Date: Mon Aug 18 23:08:51 2025 +0000 HID: haptic: add hid_haptic_switch_mode Function hid_haptic_switch_mode() can be used to switch between device-controlled mode and host-controlled mode. Uploading a WAVEFORMPRESS or WAVEFORMRELEASE effect triggers host-controlled mode if the device is in device-controlled mode. Signed-off-by: Angela Czubak Co-developed-by: Jonathan Denose Signed-off-by: Jonathan Denose Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-haptic.c | 66 +++++++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c index c02af820051c..aa090684c1f2 100644 --- a/drivers/hid/hid-haptic.c +++ b/drivers/hid/hid-haptic.c @@ -5,6 +5,7 @@ * Copyright (c) 2021 Angela Czubak */ +#include #include #include "hid-haptic.h" @@ -197,12 +198,46 @@ static void fill_effect_buf(struct hid_haptic_device *haptic, mutex_unlock(&haptic->manual_trigger_mutex); } +static void switch_mode(struct hid_device *hdev, struct hid_haptic_device *haptic, + int mode) +{ + struct hid_report *rep = haptic->auto_trigger_report; + struct hid_field *field; + s32 value; + int i, j; + + if (mode == HID_HAPTIC_MODE_HOST) + value = HID_HAPTIC_ORDINAL_WAVEFORMSTOP; + else + value = haptic->default_auto_trigger; + + mutex_lock(&haptic->auto_trigger_mutex); + for (i = 0; i < rep->maxfield; i++) { + field = rep->field[i]; + /* Ignore if report count is out of bounds. */ + if (field->report_count < 1) + continue; + + for (j = 0; j < field->maxusage; j++) { + if (field->usage[j].hid == HID_HP_AUTOTRIGGER) + field->value[j] = value; + } + } + + /* send the report */ + hid_hw_request(hdev, rep, HID_REQ_SET_REPORT); + mutex_unlock(&haptic->auto_trigger_mutex); + haptic->mode = mode; +} + static int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old) { + struct hid_device *hdev = input_get_drvdata(dev); struct ff_device *ff = dev->ff; struct hid_haptic_device *haptic = ff->private; int i, ordinal = 0; + bool switch_modes = false; /* If vendor range, check vendor id and page */ if (effect->u.haptic.hid_usage >= (HID_HP_VENDORWAVEFORMMIN & HID_USAGE) && @@ -225,6 +260,16 @@ static int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *eff fill_effect_buf(haptic, &effect->u.haptic, &haptic->effect[effect->id], ordinal); + if (effect->u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE) || + effect->u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE)) + switch_modes = true; + + /* If device is in autonomous mode, and the uploaded effect signals userspace + * wants control of the device, change modes + */ + if (switch_modes && haptic->mode == HID_HAPTIC_MODE_DEVICE) + switch_mode(hdev, haptic, HID_HAPTIC_MODE_HOST); + return 0; } @@ -290,6 +335,7 @@ static void effect_set_default(struct ff_effect *effect) static int hid_haptic_erase(struct input_dev *dev, int effect_id) { struct hid_haptic_device *haptic = dev->ff->private; + struct hid_device *hdev = input_get_drvdata(dev); struct ff_effect effect; int ordinal; @@ -297,20 +343,25 @@ static int hid_haptic_erase(struct input_dev *dev, int effect_id) if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE)) { ordinal = haptic->release_ordinal; - if (!ordinal) + if (!ordinal) { ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE; - else - effect.u.haptic.hid_usage = HID_HP_WAVEFORMRELEASE & - HID_USAGE; + if (haptic->mode == HID_HAPTIC_MODE_HOST) + switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE); + } else + effect.u.haptic.hid_usage = HID_HP_WAVEFORMRELEASE & HID_USAGE; + fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id], ordinal); } else if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE)) { ordinal = haptic->press_ordinal; - if (!ordinal) + if (!ordinal) { ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE; + if (haptic->mode == HID_HAPTIC_MODE_HOST) + switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE); + } else - effect.u.haptic.hid_usage = HID_HP_WAVEFORMPRESS & - HID_USAGE; + effect.u.haptic.hid_usage = HID_HP_WAVEFORMPRESS & HID_USAGE; + fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id], ordinal); } @@ -392,6 +443,7 @@ int hid_haptic_init(struct hid_device *hdev, haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMSTOP] = HID_HP_WAVEFORMSTOP & HID_USAGE; + mutex_init(&haptic->auto_trigger_mutex); for (r = 0; r < haptic->auto_trigger_report->maxfield; r++) parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]); From a6ae4e850c9a1bae246719e348a3aa8da55fa46c Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 16:58:40 +0200 Subject: [PATCH 034/295] HID: multitouch: add haptic multitouch support JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 Conflict: hid-multitouch.c: 46f781e0d151 was already backported commit 8d0bf7908b5a6532a18ee658337d498158672862 Author: Angela Czubak Date: Mon Aug 18 23:08:52 2025 +0000 HID: multitouch: add haptic multitouch support If CONFIG_HID_HAPTIC is enabled, and the device is recognized to have simple haptic capabilities, try initializing the haptic device, check input frames for pressure and handle it using hid_haptic_* API. Signed-off-by: Angela Czubak Co-developed-by: Jonathan Denose Signed-off-by: Jonathan Denose Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-haptic.h | 16 ++++++++---- drivers/hid/hid-multitouch.c | 47 ++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h index abdd7d710c0b..c6539ac04c1d 100644 --- a/drivers/hid/hid-haptic.h +++ b/drivers/hid/hid-haptic.h @@ -103,19 +103,25 @@ int hid_haptic_input_configured(struct hid_device *hdev, { return 0; } +static inline +void hid_haptic_reset(struct hid_device *hdev, struct hid_haptic_device *haptic) +{} +static inline int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr) { return 0; } static inline -void hid_haptic_handle_press_release(struct hid_haptic_device *haptic) -{} +void hid_haptic_handle_press_release(struct hid_haptic_device *haptic) {} static inline -void hid_haptic_pressure_reset(struct hid_haptic_device *haptic) -{} +bool hid_haptic_handle_input(struct hid_haptic_device *haptic) +{ + return false; +} +static inline +void hid_haptic_pressure_reset(struct hid_haptic_device *haptic) {} static inline void hid_haptic_pressure_increase(struct hid_haptic_device *haptic, __s32 pressure) {} #endif - diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index a9ff84f0bd9b..179dc316b4b5 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -49,6 +49,8 @@ MODULE_LICENSE("GPL"); #include "hid-ids.h" +#include "hid-haptic.h" + /* quirks to control the device */ #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) #define MT_QUIRK_SLOT_IS_CONTACTID BIT(1) @@ -167,6 +169,7 @@ struct mt_report_data { struct mt_device { struct mt_class mtclass; /* our mt device class */ struct timer_list release_timer; /* to release sticky fingers */ + struct hid_haptic_device *haptic; /* haptic related configuration */ struct hid_device *hdev; /* hid_device we're attached to */ unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_RUNNING) * first 8 bits are reserved for keeping the slot @@ -176,6 +179,7 @@ struct mt_device { __u8 inputmode_value; /* InputMode HID feature value */ __u8 maxcontacts; bool is_buttonpad; /* is this device a button pad? */ + bool is_haptic_touchpad; /* is this device a haptic touchpad? */ bool serial_maybe; /* need to check for serial protocol */ struct list_head applications; @@ -536,6 +540,8 @@ static void mt_feature_mapping(struct hid_device *hdev, mt_get_feature(hdev, field->report); break; } + + hid_haptic_feature_mapping(hdev, td->haptic, field, usage); } static void set_abs(struct input_dev *input, unsigned int code, @@ -891,6 +897,9 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi, case HID_DG_TIPPRESSURE: set_abs(hi->input, ABS_MT_PRESSURE, field, cls->sn_pressure); + td->is_haptic_touchpad = + hid_haptic_check_pressure_unit(td->haptic, + hi, field); MT_STORE_FIELD(p); return 1; case HID_DG_SCANTIME: @@ -1012,6 +1021,8 @@ static void mt_sync_frame(struct mt_device *td, struct mt_application *app, app->num_received = 0; app->left_button_state = 0; + if (td->is_haptic_touchpad) + hid_haptic_pressure_reset(td->haptic); } static int mt_compute_timestamp(struct mt_application *app, __s32 value) @@ -1163,6 +1174,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input, minor = minor >> 1; } + if (td->is_haptic_touchpad) + hid_haptic_pressure_increase(td->haptic, *slot->p); + x = hdev->quirks & HID_QUIRK_X_INVERT ? input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->x : *slot->x; @@ -1366,6 +1380,9 @@ static int mt_touch_input_configured(struct hid_device *hdev, if (cls->is_indirect) app->mt_flags |= INPUT_MT_POINTER; + if (td->is_haptic_touchpad) + app->mt_flags |= INPUT_MT_TOTAL_FORCE; + if (app->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP) app->mt_flags |= INPUT_MT_DROP_UNUSED; @@ -1401,6 +1418,7 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct mt_device *td = hid_get_drvdata(hdev); struct mt_application *application; struct mt_report_data *rdata; + int ret; rdata = mt_find_report_data(td, field->report); if (!rdata) { @@ -1463,6 +1481,11 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, if (field->physical == HID_DG_STYLUS) hi->application = HID_DG_STYLUS; + ret = hid_haptic_input_mapping(hdev, td->haptic, hi, field, usage, bit, + max); + if (ret != 0) + return ret; + /* let hid-core decide for the others */ return 0; } @@ -1685,6 +1708,14 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) struct hid_report *report; int ret; + if (td->is_haptic_touchpad && (td->mtclass.name == MT_CLS_WIN_8 || + td->mtclass.name == MT_CLS_WIN_8_FORCE_MULTI_INPUT)) { + if (hid_haptic_input_configured(hdev, td->haptic, hi) == 0) + td->is_haptic_touchpad = false; + } else { + td->is_haptic_touchpad = false; + } + list_for_each_entry(report, &hi->reports, hidinput_list) { rdata = mt_find_report_data(td, report); if (!rdata) { @@ -1829,6 +1860,11 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) dev_err(&hdev->dev, "cannot allocate multitouch data\n"); return -ENOMEM; } + td->haptic = devm_kzalloc(&hdev->dev, sizeof(*(td->haptic)), GFP_KERNEL); + if (!td->haptic) + return -ENOMEM; + + td->haptic->hdev = hdev; td->hdev = hdev; td->mtclass = *mtclass; td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; @@ -1897,6 +1933,17 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); + if (td->is_haptic_touchpad) { + if (hid_haptic_init(hdev, &td->haptic)) { + dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n", + hdev->name); + td->is_haptic_touchpad = false; + devm_kfree(&hdev->dev, td->haptic); + } + } else { + devm_kfree(&hdev->dev, td->haptic); + } + return 0; } From 9b866fb6ecba0e3f4ce66a48a5fa181e1fc605f8 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:21 +0200 Subject: [PATCH 035/295] HID: playstation: Make use of bitfield macros JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 0d52aafb8bcee01b11ec8f3b46050540585431eb Author: Cristian Ciocaltea Date: Wed Jun 25 00:56:45 2025 +0300 HID: playstation: Make use of bitfield macros Improve code readability and make it more robust by replacing open coded bit operations with the equivalent bitfield macros. While at it, also fix the vertical alignment for some of the bitfield constants. Signed-off-by: Cristian Ciocaltea Reviewed-by: Benjamin Tissoires Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 77 ++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 657e9ae1be1e..bd73c592a8a3 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -5,6 +5,7 @@ * Copyright (c) 2020-2022 Sony Interactive Entertainment */ +#include #include #include #include @@ -111,34 +112,45 @@ struct ps_led_info { #define DS_BUTTONS2_MIC_MUTE BIT(2) /* Status field of DualSense input report. */ -#define DS_STATUS_BATTERY_CAPACITY GENMASK(3, 0) -#define DS_STATUS_CHARGING GENMASK(7, 4) -#define DS_STATUS_CHARGING_SHIFT 4 +#define DS_STATUS_BATTERY_CAPACITY GENMASK(3, 0) +#define DS_STATUS_CHARGING GENMASK(7, 4) /* Feature version from DualSense Firmware Info report. */ -#define DS_FEATURE_VERSION(major, minor) ((major & 0xff) << 8 | (minor & 0xff)) - +#define DS_FEATURE_VERSION_MINOR GENMASK(7, 0) +#define DS_FEATURE_VERSION_MAJOR GENMASK(15, 8) +#define DS_FEATURE_VERSION(major, minor) (FIELD_PREP(DS_FEATURE_VERSION_MAJOR, major) | \ + FIELD_PREP(DS_FEATURE_VERSION_MINOR, minor)) /* * Status of a DualSense touch point contact. * Contact IDs, with highest bit set are 'inactive' * and any associated data is then invalid. */ -#define DS_TOUCH_POINT_INACTIVE BIT(7) +#define DS_TOUCH_POINT_INACTIVE BIT(7) +#define DS_TOUCH_POINT_X_LO GENMASK(7, 0) +#define DS_TOUCH_POINT_X_HI GENMASK(11, 8) +#define DS_TOUCH_POINT_X(hi, lo) (FIELD_PREP(DS_TOUCH_POINT_X_HI, hi) | \ + FIELD_PREP(DS_TOUCH_POINT_X_LO, lo)) +#define DS_TOUCH_POINT_Y_LO GENMASK(3, 0) +#define DS_TOUCH_POINT_Y_HI GENMASK(11, 4) +#define DS_TOUCH_POINT_Y(hi, lo) (FIELD_PREP(DS_TOUCH_POINT_Y_HI, hi) | \ + FIELD_PREP(DS_TOUCH_POINT_Y_LO, lo)) /* Magic value required in tag field of Bluetooth output report. */ -#define DS_OUTPUT_TAG 0x10 +#define DS_OUTPUT_TAG 0x10 +#define DS_OUTPUT_SEQ_TAG GENMASK(3, 0) +#define DS_OUTPUT_SEQ_NO GENMASK(7, 4) /* Flags for DualSense output report. */ -#define DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION BIT(0) -#define DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT BIT(1) -#define DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE BIT(0) -#define DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE BIT(1) -#define DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE BIT(2) -#define DS_OUTPUT_VALID_FLAG1_RELEASE_LEDS BIT(3) -#define DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE BIT(4) -#define DS_OUTPUT_VALID_FLAG2_LIGHTBAR_SETUP_CONTROL_ENABLE BIT(1) -#define DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2 BIT(2) -#define DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE BIT(4) -#define DS_OUTPUT_LIGHTBAR_SETUP_LIGHT_OUT BIT(1) +#define DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION BIT(0) +#define DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT BIT(1) +#define DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE BIT(0) +#define DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE BIT(1) +#define DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE BIT(2) +#define DS_OUTPUT_VALID_FLAG1_RELEASE_LEDS BIT(3) +#define DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE BIT(4) +#define DS_OUTPUT_VALID_FLAG2_LIGHTBAR_SETUP_CONTROL_ENABLE BIT(1) +#define DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2 BIT(2) +#define DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE BIT(4) +#define DS_OUTPUT_LIGHTBAR_SETUP_LIGHT_OUT BIT(1) /* DualSense hardware limits */ #define DS_ACC_RES_PER_G 8192 @@ -315,7 +327,9 @@ struct dualsense_output_report { * Contact IDs, with highest bit set are 'inactive' * and any associated data is then invalid. */ -#define DS4_TOUCH_POINT_INACTIVE BIT(7) +#define DS4_TOUCH_POINT_INACTIVE BIT(7) +#define DS4_TOUCH_POINT_X(hi, lo) DS_TOUCH_POINT_X(hi, lo) +#define DS4_TOUCH_POINT_Y(hi, lo) DS_TOUCH_POINT_Y(hi, lo) /* Status field of DualShock4 input report. */ #define DS4_STATUS0_BATTERY_CAPACITY GENMASK(3, 0) @@ -1194,7 +1208,8 @@ static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_ * Highest 4-bit is a sequence number, which needs to be increased * every report. Lowest 4-bit is tag and can be zero for now. */ - bt->seq_tag = (ds->output_seq << 4) | 0x0; + bt->seq_tag = FIELD_PREP(DS_OUTPUT_SEQ_NO, ds->output_seq) | + FIELD_PREP(DS_OUTPUT_SEQ_TAG, 0x0); if (++ds->output_seq == 16) ds->output_seq = 0; @@ -1439,19 +1454,18 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r input_mt_report_slot_state(ds->touchpad, MT_TOOL_FINGER, active); if (active) { - int x = (point->x_hi << 8) | point->x_lo; - int y = (point->y_hi << 4) | point->y_lo; - - input_report_abs(ds->touchpad, ABS_MT_POSITION_X, x); - input_report_abs(ds->touchpad, ABS_MT_POSITION_Y, y); + input_report_abs(ds->touchpad, ABS_MT_POSITION_X, + DS_TOUCH_POINT_X(point->x_hi, point->x_lo)); + input_report_abs(ds->touchpad, ABS_MT_POSITION_Y, + DS_TOUCH_POINT_Y(point->y_hi, point->y_lo)); } } input_mt_sync_frame(ds->touchpad); input_report_key(ds->touchpad, BTN_LEFT, ds_report->buttons[2] & DS_BUTTONS2_TOUCHPAD); input_sync(ds->touchpad); - battery_data = ds_report->status & DS_STATUS_BATTERY_CAPACITY; - charging_status = (ds_report->status & DS_STATUS_CHARGING) >> DS_STATUS_CHARGING_SHIFT; + battery_data = FIELD_GET(DS_STATUS_BATTERY_CAPACITY, ds_report->status); + charging_status = FIELD_GET(DS_STATUS_CHARGING, ds_report->status); switch (charging_status) { case 0x0: @@ -2353,11 +2367,10 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * input_mt_report_slot_state(ds4->touchpad, MT_TOOL_FINGER, active); if (active) { - int x = (point->x_hi << 8) | point->x_lo; - int y = (point->y_hi << 4) | point->y_lo; - - input_report_abs(ds4->touchpad, ABS_MT_POSITION_X, x); - input_report_abs(ds4->touchpad, ABS_MT_POSITION_Y, y); + input_report_abs(ds4->touchpad, ABS_MT_POSITION_X, + DS4_TOUCH_POINT_X(point->x_hi, point->x_lo)); + input_report_abs(ds4->touchpad, ABS_MT_POSITION_Y, + DS4_TOUCH_POINT_Y(point->y_hi, point->y_lo)); } } input_mt_sync_frame(ds4->touchpad); From 592e39307f1e7c640fb1a04682eaa47916e54bb7 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:22 +0200 Subject: [PATCH 036/295] HID: playstation: Add spaces around arithmetic operators JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 6c6af4c4dfd3ca734b3507361476ec1774dca477 Author: Cristian Ciocaltea Date: Wed Jun 25 00:56:46 2025 +0300 HID: playstation: Add spaces around arithmetic operators Get rid of several checkpatch.pl complaints: CHECK: spaces preferred around that '*' (ctx:VxV) CHECK: spaces preferred around that '/' (ctx:VxV) Signed-off-by: Cristian Ciocaltea Reviewed-by: Benjamin Tissoires Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 42 ++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index bd73c592a8a3..2d4c221e2dee 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -154,9 +154,9 @@ struct ps_led_info { /* DualSense hardware limits */ #define DS_ACC_RES_PER_G 8192 -#define DS_ACC_RANGE (4*DS_ACC_RES_PER_G) +#define DS_ACC_RANGE (4 * DS_ACC_RES_PER_G) #define DS_GYRO_RES_PER_DEG_S 1024 -#define DS_GYRO_RANGE (2048*DS_GYRO_RES_PER_DEG_S) +#define DS_GYRO_RANGE (2048 * DS_GYRO_RES_PER_DEG_S) #define DS_TOUCHPAD_WIDTH 1920 #define DS_TOUCHPAD_HEIGHT 1080 @@ -363,9 +363,9 @@ struct dualsense_output_report { /* DualShock4 hardware limits */ #define DS4_ACC_RES_PER_G 8192 -#define DS4_ACC_RANGE (4*DS_ACC_RES_PER_G) +#define DS4_ACC_RANGE (4 * DS_ACC_RES_PER_G) #define DS4_GYRO_RES_PER_DEG_S 1024 -#define DS4_GYRO_RANGE (2048*DS_GYRO_RES_PER_DEG_S) +#define DS4_GYRO_RANGE (2048 * DS_GYRO_RES_PER_DEG_S) #define DS4_LIGHTBAR_MAX_BLINK 255 /* 255 centiseconds */ #define DS4_TOUCHPAD_WIDTH 1920 #define DS4_TOUCHPAD_HEIGHT 942 @@ -1015,19 +1015,19 @@ static int dualsense_get_calibration_data(struct dualsense *ds) speed_2x = (gyro_speed_plus + gyro_speed_minus); ds->gyro_calib_data[0].abs_code = ABS_RX; ds->gyro_calib_data[0].bias = 0; - ds->gyro_calib_data[0].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S; + ds->gyro_calib_data[0].sens_numer = speed_2x * DS_GYRO_RES_PER_DEG_S; ds->gyro_calib_data[0].sens_denom = abs(gyro_pitch_plus - gyro_pitch_bias) + abs(gyro_pitch_minus - gyro_pitch_bias); ds->gyro_calib_data[1].abs_code = ABS_RY; ds->gyro_calib_data[1].bias = 0; - ds->gyro_calib_data[1].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S; + ds->gyro_calib_data[1].sens_numer = speed_2x * DS_GYRO_RES_PER_DEG_S; ds->gyro_calib_data[1].sens_denom = abs(gyro_yaw_plus - gyro_yaw_bias) + abs(gyro_yaw_minus - gyro_yaw_bias); ds->gyro_calib_data[2].abs_code = ABS_RZ; ds->gyro_calib_data[2].bias = 0; - ds->gyro_calib_data[2].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S; + ds->gyro_calib_data[2].sens_numer = speed_2x * DS_GYRO_RES_PER_DEG_S; ds->gyro_calib_data[2].sens_denom = abs(gyro_roll_plus - gyro_roll_bias) + abs(gyro_roll_minus - gyro_roll_bias); @@ -1053,19 +1053,19 @@ static int dualsense_get_calibration_data(struct dualsense *ds) range_2g = acc_x_plus - acc_x_minus; ds->accel_calib_data[0].abs_code = ABS_X; ds->accel_calib_data[0].bias = acc_x_plus - range_2g / 2; - ds->accel_calib_data[0].sens_numer = 2*DS_ACC_RES_PER_G; + ds->accel_calib_data[0].sens_numer = 2 * DS_ACC_RES_PER_G; ds->accel_calib_data[0].sens_denom = range_2g; range_2g = acc_y_plus - acc_y_minus; ds->accel_calib_data[1].abs_code = ABS_Y; ds->accel_calib_data[1].bias = acc_y_plus - range_2g / 2; - ds->accel_calib_data[1].sens_numer = 2*DS_ACC_RES_PER_G; + ds->accel_calib_data[1].sens_numer = 2 * DS_ACC_RES_PER_G; ds->accel_calib_data[1].sens_denom = range_2g; range_2g = acc_z_plus - acc_z_minus; ds->accel_calib_data[2].abs_code = ABS_Z; ds->accel_calib_data[2].bias = acc_z_plus - range_2g / 2; - ds->accel_calib_data[2].sens_numer = 2*DS_ACC_RES_PER_G; + ds->accel_calib_data[2].sens_numer = 2 * DS_ACC_RES_PER_G; ds->accel_calib_data[2].sens_denom = range_2g; /* @@ -1883,19 +1883,19 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4) speed_2x = (gyro_speed_plus + gyro_speed_minus); ds4->gyro_calib_data[0].abs_code = ABS_RX; ds4->gyro_calib_data[0].bias = 0; - ds4->gyro_calib_data[0].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S; + ds4->gyro_calib_data[0].sens_numer = speed_2x * DS4_GYRO_RES_PER_DEG_S; ds4->gyro_calib_data[0].sens_denom = abs(gyro_pitch_plus - gyro_pitch_bias) + abs(gyro_pitch_minus - gyro_pitch_bias); ds4->gyro_calib_data[1].abs_code = ABS_RY; ds4->gyro_calib_data[1].bias = 0; - ds4->gyro_calib_data[1].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S; + ds4->gyro_calib_data[1].sens_numer = speed_2x * DS4_GYRO_RES_PER_DEG_S; ds4->gyro_calib_data[1].sens_denom = abs(gyro_yaw_plus - gyro_yaw_bias) + abs(gyro_yaw_minus - gyro_yaw_bias); ds4->gyro_calib_data[2].abs_code = ABS_RZ; ds4->gyro_calib_data[2].bias = 0; - ds4->gyro_calib_data[2].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S; + ds4->gyro_calib_data[2].sens_numer = speed_2x * DS4_GYRO_RES_PER_DEG_S; ds4->gyro_calib_data[2].sens_denom = abs(gyro_roll_plus - gyro_roll_bias) + abs(gyro_roll_minus - gyro_roll_bias); @@ -1906,19 +1906,19 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4) range_2g = acc_x_plus - acc_x_minus; ds4->accel_calib_data[0].abs_code = ABS_X; ds4->accel_calib_data[0].bias = acc_x_plus - range_2g / 2; - ds4->accel_calib_data[0].sens_numer = 2*DS4_ACC_RES_PER_G; + ds4->accel_calib_data[0].sens_numer = 2 * DS4_ACC_RES_PER_G; ds4->accel_calib_data[0].sens_denom = range_2g; range_2g = acc_y_plus - acc_y_minus; ds4->accel_calib_data[1].abs_code = ABS_Y; ds4->accel_calib_data[1].bias = acc_y_plus - range_2g / 2; - ds4->accel_calib_data[1].sens_numer = 2*DS4_ACC_RES_PER_G; + ds4->accel_calib_data[1].sens_numer = 2 * DS4_ACC_RES_PER_G; ds4->accel_calib_data[1].sens_denom = range_2g; range_2g = acc_z_plus - acc_z_minus; ds4->accel_calib_data[2].abs_code = ABS_Z; ds4->accel_calib_data[2].bias = acc_z_plus - range_2g / 2; - ds4->accel_calib_data[2].sens_numer = 2*DS4_ACC_RES_PER_G; + ds4->accel_calib_data[2].sens_numer = 2 * DS4_ACC_RES_PER_G; ds4->accel_calib_data[2].sens_denom = range_2g; transfer_failed: @@ -2060,8 +2060,10 @@ static int dualshock4_led_set_blink(struct led_classdev *led, unsigned long *del ds4->lightbar_blink_off = 50; } else { /* Blink delays in centiseconds. */ - ds4->lightbar_blink_on = min_t(unsigned long, *delay_on/10, DS4_LIGHTBAR_MAX_BLINK); - ds4->lightbar_blink_off = min_t(unsigned long, *delay_off/10, DS4_LIGHTBAR_MAX_BLINK); + ds4->lightbar_blink_on = min_t(unsigned long, *delay_on / 10, + DS4_LIGHTBAR_MAX_BLINK); + ds4->lightbar_blink_off = min_t(unsigned long, *delay_off / 10, + DS4_LIGHTBAR_MAX_BLINK); } ds4->update_lightbar_blink = true; @@ -2341,7 +2343,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * /* Convert timestamp (in 5.33us unit) to timestamp_us */ sensor_timestamp = le16_to_cpu(ds4_report->sensor_timestamp); if (!ds4->sensor_timestamp_initialized) { - ds4->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp*16, 3); + ds4->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp * 16, 3); ds4->sensor_timestamp_initialized = true; } else { uint16_t delta; @@ -2350,7 +2352,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * delta = (U16_MAX - ds4->prev_sensor_timestamp + sensor_timestamp + 1); else delta = sensor_timestamp - ds4->prev_sensor_timestamp; - ds4->sensor_timestamp_us += DIV_ROUND_CLOSEST(delta*16, 3); + ds4->sensor_timestamp_us += DIV_ROUND_CLOSEST(delta * 16, 3); } ds4->prev_sensor_timestamp = sensor_timestamp; input_event(ds4->sensors, EV_MSC, MSC_TIMESTAMP, ds4->sensor_timestamp_us); From cee6067cdf76f23eec4b0547d5fa27e3d7d77d2b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:22 +0200 Subject: [PATCH 037/295] HID: playstation: Simplify locking with guard() and scoped_guard() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit a38d070ffe338710037f6a45767542ce249c61a0 Author: Cristian Ciocaltea Date: Wed Jun 25 00:56:47 2025 +0300 HID: playstation: Simplify locking with guard() and scoped_guard() Use guard() and scoped_guard() infrastructure instead of explicitly acquiring and releasing spinlocks and mutexes to simplify the code and ensure that all locks are released properly. Signed-off-by: Cristian Ciocaltea Reviewed-by: Benjamin Tissoires Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 214 +++++++++++++++------------------- 1 file changed, 92 insertions(+), 122 deletions(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 2d4c221e2dee..471ef402ea41 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -566,26 +567,25 @@ static int ps_devices_list_add(struct ps_device *dev) { struct ps_device *entry; - mutex_lock(&ps_devices_lock); + guard(mutex)(&ps_devices_lock); + list_for_each_entry(entry, &ps_devices_list, list) { if (!memcmp(entry->mac_address, dev->mac_address, sizeof(dev->mac_address))) { hid_err(dev->hdev, "Duplicate device found for MAC address %pMR.\n", dev->mac_address); - mutex_unlock(&ps_devices_lock); return -EEXIST; } } list_add_tail(&dev->list, &ps_devices_list); - mutex_unlock(&ps_devices_lock); return 0; } static int ps_devices_list_remove(struct ps_device *dev) { - mutex_lock(&ps_devices_lock); + guard(mutex)(&ps_devices_lock); + list_del(&dev->list); - mutex_unlock(&ps_devices_lock); return 0; } @@ -649,13 +649,12 @@ static int ps_battery_get_property(struct power_supply *psy, struct ps_device *dev = power_supply_get_drvdata(psy); uint8_t battery_capacity; int battery_status; - unsigned long flags; int ret = 0; - spin_lock_irqsave(&dev->lock, flags); - battery_capacity = dev->battery_capacity; - battery_status = dev->battery_status; - spin_unlock_irqrestore(&dev->lock, flags); + scoped_guard(spinlock_irqsave, &dev->lock) { + battery_capacity = dev->battery_capacity; + battery_status = dev->battery_status; + } switch (psp) { case POWER_SUPPLY_PROP_STATUS: @@ -1173,19 +1172,17 @@ static int dualsense_player_led_set_brightness(struct led_classdev *led, enum le { struct hid_device *hdev = to_hid_device(led->dev->parent); struct dualsense *ds = hid_get_drvdata(hdev); - unsigned long flags; unsigned int led_index; - spin_lock_irqsave(&ds->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds->base.lock) { + led_index = led - ds->player_leds; + if (value == LED_OFF) + ds->player_leds_state &= ~BIT(led_index); + else + ds->player_leds_state |= BIT(led_index); - led_index = led - ds->player_leds; - if (value == LED_OFF) - ds->player_leds_state &= ~BIT(led_index); - else - ds->player_leds_state |= BIT(led_index); - - ds->update_player_leds = true; - spin_unlock_irqrestore(&ds->base.lock, flags); + ds->update_player_leds = true; + } dualsense_schedule_work(ds); @@ -1234,12 +1231,9 @@ static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_ static inline void dualsense_schedule_work(struct dualsense *ds) { - unsigned long flags; - - spin_lock_irqsave(&ds->base.lock, flags); + guard(spinlock_irqsave)(&ds->base.lock); if (ds->output_worker_initialized) schedule_work(&ds->output_worker); - spin_unlock_irqrestore(&ds->base.lock, flags); } /* @@ -1337,7 +1331,6 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r int battery_status; uint32_t sensor_timestamp; bool btn_mic_state; - unsigned long flags; int i; /* @@ -1399,10 +1392,10 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r */ btn_mic_state = !!(ds_report->buttons[2] & DS_BUTTONS2_MIC_MUTE); if (btn_mic_state && !ds->last_btn_mic_state) { - spin_lock_irqsave(&ps_dev->lock, flags); - ds->update_mic_mute = true; - ds->mic_muted = !ds->mic_muted; /* toggle */ - spin_unlock_irqrestore(&ps_dev->lock, flags); + scoped_guard(spinlock_irqsave, &ps_dev->lock) { + ds->update_mic_mute = true; + ds->mic_muted = !ds->mic_muted; /* toggle */ + } /* Schedule updating of microphone state at hardware level. */ dualsense_schedule_work(ds); @@ -1495,10 +1488,10 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r battery_status = POWER_SUPPLY_STATUS_UNKNOWN; } - spin_lock_irqsave(&ps_dev->lock, flags); - ps_dev->battery_capacity = battery_capacity; - ps_dev->battery_status = battery_status; - spin_unlock_irqrestore(&ps_dev->lock, flags); + scoped_guard(spinlock_irqsave, &ps_dev->lock) { + ps_dev->battery_capacity = battery_capacity; + ps_dev->battery_status = battery_status; + } return 0; } @@ -1507,16 +1500,15 @@ static int dualsense_play_effect(struct input_dev *dev, void *data, struct ff_ef { struct hid_device *hdev = input_get_drvdata(dev); struct dualsense *ds = hid_get_drvdata(hdev); - unsigned long flags; if (effect->type != FF_RUMBLE) return 0; - spin_lock_irqsave(&ds->base.lock, flags); - ds->update_rumble = true; - ds->motor_left = effect->u.rumble.strong_magnitude / 256; - ds->motor_right = effect->u.rumble.weak_magnitude / 256; - spin_unlock_irqrestore(&ds->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds->base.lock) { + ds->update_rumble = true; + ds->motor_left = effect->u.rumble.strong_magnitude / 256; + ds->motor_right = effect->u.rumble.weak_magnitude / 256; + } dualsense_schedule_work(ds); return 0; @@ -1525,11 +1517,9 @@ static int dualsense_play_effect(struct input_dev *dev, void *data, struct ff_ef static void dualsense_remove(struct ps_device *ps_dev) { struct dualsense *ds = container_of(ps_dev, struct dualsense, base); - unsigned long flags; - spin_lock_irqsave(&ds->base.lock, flags); - ds->output_worker_initialized = false; - spin_unlock_irqrestore(&ds->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds->base.lock) + ds->output_worker_initialized = false; cancel_work_sync(&ds->output_worker); } @@ -1561,14 +1551,12 @@ static int dualsense_reset_leds(struct dualsense *ds) static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t green, uint8_t blue) { - unsigned long flags; - - spin_lock_irqsave(&ds->base.lock, flags); - ds->update_lightbar = true; - ds->lightbar_red = red; - ds->lightbar_green = green; - ds->lightbar_blue = blue; - spin_unlock_irqrestore(&ds->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds->base.lock) { + ds->update_lightbar = true; + ds->lightbar_red = red; + ds->lightbar_green = green; + ds->lightbar_blue = blue; + } dualsense_schedule_work(ds); } @@ -1755,7 +1743,6 @@ err: static void dualshock4_dongle_calibration_work(struct work_struct *work) { struct dualshock4 *ds4 = container_of(work, struct dualshock4, dongle_hotplug_worker); - unsigned long flags; enum dualshock4_dongle_state dongle_state; int ret; @@ -1774,9 +1761,8 @@ static void dualshock4_dongle_calibration_work(struct work_struct *work) dongle_state = DONGLE_CONNECTED; } - spin_lock_irqsave(&ds4->base.lock, flags); - ds4->dongle_state = dongle_state; - spin_unlock_irqrestore(&ds4->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds4->base.lock) + ds4->dongle_state = dongle_state; } static int dualshock4_get_calibration_data(struct dualshock4 *ds4) @@ -2050,26 +2036,23 @@ static int dualshock4_led_set_blink(struct led_classdev *led, unsigned long *del { struct hid_device *hdev = to_hid_device(led->dev->parent); struct dualshock4 *ds4 = hid_get_drvdata(hdev); - unsigned long flags; - spin_lock_irqsave(&ds4->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds4->base.lock) { + if (!*delay_on && !*delay_off) { + /* Default to 1 Hz (50 centiseconds on, 50 centiseconds off). */ + ds4->lightbar_blink_on = 50; + ds4->lightbar_blink_off = 50; + } else { + /* Blink delays in centiseconds. */ + ds4->lightbar_blink_on = min_t(unsigned long, *delay_on / 10, + DS4_LIGHTBAR_MAX_BLINK); + ds4->lightbar_blink_off = min_t(unsigned long, *delay_off / 10, + DS4_LIGHTBAR_MAX_BLINK); + } - if (!*delay_on && !*delay_off) { - /* Default to 1 Hz (50 centiseconds on, 50 centiseconds off). */ - ds4->lightbar_blink_on = 50; - ds4->lightbar_blink_off = 50; - } else { - /* Blink delays in centiseconds. */ - ds4->lightbar_blink_on = min_t(unsigned long, *delay_on / 10, - DS4_LIGHTBAR_MAX_BLINK); - ds4->lightbar_blink_off = min_t(unsigned long, *delay_off / 10, - DS4_LIGHTBAR_MAX_BLINK); + ds4->update_lightbar_blink = true; } - ds4->update_lightbar_blink = true; - - spin_unlock_irqrestore(&ds4->base.lock, flags); - dualshock4_schedule_work(ds4); /* Report scaled values back to LED subsystem */ @@ -2083,37 +2066,34 @@ static int dualshock4_led_set_brightness(struct led_classdev *led, enum led_brig { struct hid_device *hdev = to_hid_device(led->dev->parent); struct dualshock4 *ds4 = hid_get_drvdata(hdev); - unsigned long flags; unsigned int led_index; - spin_lock_irqsave(&ds4->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds4->base.lock) { + led_index = led - ds4->lightbar_leds; + switch (led_index) { + case 0: + ds4->lightbar_red = value; + break; + case 1: + ds4->lightbar_green = value; + break; + case 2: + ds4->lightbar_blue = value; + break; + case 3: + ds4->lightbar_enabled = !!value; - led_index = led - ds4->lightbar_leds; - switch (led_index) { - case 0: - ds4->lightbar_red = value; - break; - case 1: - ds4->lightbar_green = value; - break; - case 2: - ds4->lightbar_blue = value; - break; - case 3: - ds4->lightbar_enabled = !!value; - - /* brightness = 0 also cancels blinking in Linux. */ - if (!ds4->lightbar_enabled) { - ds4->lightbar_blink_off = 0; - ds4->lightbar_blink_on = 0; - ds4->update_lightbar_blink = true; + /* brightness = 0 also cancels blinking in Linux. */ + if (!ds4->lightbar_enabled) { + ds4->lightbar_blink_off = 0; + ds4->lightbar_blink_on = 0; + ds4->update_lightbar_blink = true; + } } + + ds4->update_lightbar = true; } - ds4->update_lightbar = true; - - spin_unlock_irqrestore(&ds4->base.lock, flags); - dualshock4_schedule_work(ds4); return 0; @@ -2244,7 +2224,6 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * uint8_t battery_capacity, num_touch_reports, value; int battery_status, i, j; uint16_t sensor_timestamp; - unsigned long flags; bool is_minimal = false; /* @@ -2422,10 +2401,10 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * battery_status = POWER_SUPPLY_STATUS_DISCHARGING; } - spin_lock_irqsave(&ps_dev->lock, flags); - ps_dev->battery_capacity = battery_capacity; - ps_dev->battery_status = battery_status; - spin_unlock_irqrestore(&ps_dev->lock, flags); + scoped_guard(spinlock_irqsave, &ps_dev->lock) { + ps_dev->battery_capacity = battery_capacity; + ps_dev->battery_status = battery_status; + } return 0; } @@ -2443,7 +2422,6 @@ static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_r */ if (data[0] == DS4_INPUT_REPORT_USB && size == DS4_INPUT_REPORT_USB_SIZE) { struct dualshock4_input_report_common *ds4_report = (struct dualshock4_input_report_common *)&data[1]; - unsigned long flags; connected = ds4_report->status[1] & DS4_STATUS1_DONGLE_STATE ? false : true; @@ -2452,9 +2430,8 @@ static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_r dualshock4_set_default_lightbar_colors(ds4); - spin_lock_irqsave(&ps_dev->lock, flags); - ds4->dongle_state = DONGLE_CALIBRATING; - spin_unlock_irqrestore(&ps_dev->lock, flags); + scoped_guard(spinlock_irqsave, &ps_dev->lock) + ds4->dongle_state = DONGLE_CALIBRATING; schedule_work(&ds4->dongle_hotplug_worker); @@ -2466,9 +2443,8 @@ static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_r ds4->dongle_state == DONGLE_DISABLED) && !connected) { hid_info(ps_dev->hdev, "DualShock 4 USB dongle: controller disconnected\n"); - spin_lock_irqsave(&ps_dev->lock, flags); - ds4->dongle_state = DONGLE_DISCONNECTED; - spin_unlock_irqrestore(&ps_dev->lock, flags); + scoped_guard(spinlock_irqsave, &ps_dev->lock) + ds4->dongle_state = DONGLE_DISCONNECTED; /* Return 0, so hidraw can get the report. */ return 0; @@ -2490,16 +2466,15 @@ static int dualshock4_play_effect(struct input_dev *dev, void *data, struct ff_e { struct hid_device *hdev = input_get_drvdata(dev); struct dualshock4 *ds4 = hid_get_drvdata(hdev); - unsigned long flags; if (effect->type != FF_RUMBLE) return 0; - spin_lock_irqsave(&ds4->base.lock, flags); - ds4->update_rumble = true; - ds4->motor_left = effect->u.rumble.strong_magnitude / 256; - ds4->motor_right = effect->u.rumble.weak_magnitude / 256; - spin_unlock_irqrestore(&ds4->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds4->base.lock) { + ds4->update_rumble = true; + ds4->motor_left = effect->u.rumble.strong_magnitude / 256; + ds4->motor_right = effect->u.rumble.weak_magnitude / 256; + } dualshock4_schedule_work(ds4); return 0; @@ -2508,11 +2483,9 @@ static int dualshock4_play_effect(struct input_dev *dev, void *data, struct ff_e static void dualshock4_remove(struct ps_device *ps_dev) { struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base); - unsigned long flags; - spin_lock_irqsave(&ds4->base.lock, flags); - ds4->output_worker_initialized = false; - spin_unlock_irqrestore(&ds4->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds4->base.lock) + ds4->output_worker_initialized = false; cancel_work_sync(&ds4->output_worker); @@ -2522,12 +2495,9 @@ static void dualshock4_remove(struct ps_device *ps_dev) static inline void dualshock4_schedule_work(struct dualshock4 *ds4) { - unsigned long flags; - - spin_lock_irqsave(&ds4->base.lock, flags); + guard(spinlock_irqsave)(&ds4->base.lock); if (ds4->output_worker_initialized) schedule_work(&ds4->output_worker); - spin_unlock_irqrestore(&ds4->base.lock, flags); } static void dualshock4_set_bt_poll_interval(struct dualshock4 *ds4, uint8_t interval) From 045a0321408bd49a7d80c7d4e6cbc5f7ea15e037 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:23 +0200 Subject: [PATCH 038/295] HID: playstation: Replace uint{32,16,8}_t with u{32,16,8} JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 134a40c9d6d9bf27a6743e65eebdb81985880712 Author: Cristian Ciocaltea Date: Wed Jun 25 00:56:48 2025 +0300 HID: playstation: Replace uint{32,16,8}_t with u{32,16,8} And get rid of the following checkpatch.pl complaints: CHECK: Prefer kernel type 'u32' over 'uint32_t' CHECK: Prefer kernel type 'u16' over 'uint16_t' CHECK: Prefer kernel type 'u8' over 'uint8_t' Signed-off-by: Cristian Ciocaltea Reviewed-by: Benjamin Tissoires Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 272 +++++++++++++++++----------------- 1 file changed, 136 insertions(+), 136 deletions(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 471ef402ea41..0ffff85d2505 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -40,17 +40,17 @@ struct ps_device { struct hid_device *hdev; spinlock_t lock; - uint32_t player_id; + u32 player_id; struct power_supply_desc battery_desc; struct power_supply *battery; - uint8_t battery_capacity; + u8 battery_capacity; int battery_status; const char *input_dev_name; /* Name of primary input device. */ - uint8_t mac_address[6]; /* Note: stored in little endian order. */ - uint32_t hw_version; - uint32_t fw_version; + u8 mac_address[6]; /* Note: stored in little endian order. */ + u32 hw_version; + u32 fw_version; int (*parse_report)(struct ps_device *dev, struct hid_report *report, u8 *data, int size); void (*remove)(struct ps_device *dev); @@ -168,7 +168,7 @@ struct dualsense { struct input_dev *touchpad; /* Update version is used as a feature/capability version. */ - uint16_t update_version; + u16 update_version; /* Calibration data for accelerometer and gyroscope. */ struct ps_calibration_data accel_calib_data[3]; @@ -176,21 +176,21 @@ struct dualsense { /* Timestamp for sensor data */ bool sensor_timestamp_initialized; - uint32_t prev_sensor_timestamp; - uint32_t sensor_timestamp_us; + u32 prev_sensor_timestamp; + u32 sensor_timestamp_us; /* Compatible rumble state */ bool use_vibration_v2; bool update_rumble; - uint8_t motor_left; - uint8_t motor_right; + u8 motor_left; + u8 motor_right; /* RGB lightbar */ struct led_classdev_mc lightbar; bool update_lightbar; - uint8_t lightbar_red; - uint8_t lightbar_green; - uint8_t lightbar_blue; + u8 lightbar_red; + u8 lightbar_green; + u8 lightbar_blue; /* Microphone */ bool update_mic_mute; @@ -199,90 +199,90 @@ struct dualsense { /* Player leds */ bool update_player_leds; - uint8_t player_leds_state; + u8 player_leds_state; struct led_classdev player_leds[5]; struct work_struct output_worker; bool output_worker_initialized; void *output_report_dmabuf; - uint8_t output_seq; /* Sequence number for output report. */ + u8 output_seq; /* Sequence number for output report. */ }; struct dualsense_touch_point { - uint8_t contact; - uint8_t x_lo; - uint8_t x_hi:4, y_lo:4; - uint8_t y_hi; + u8 contact; + u8 x_lo; + u8 x_hi:4, y_lo:4; + u8 y_hi; } __packed; static_assert(sizeof(struct dualsense_touch_point) == 4); /* Main DualSense input report excluding any BT/USB specific headers. */ struct dualsense_input_report { - uint8_t x, y; - uint8_t rx, ry; - uint8_t z, rz; - uint8_t seq_number; - uint8_t buttons[4]; - uint8_t reserved[4]; + u8 x, y; + u8 rx, ry; + u8 z, rz; + u8 seq_number; + u8 buttons[4]; + u8 reserved[4]; /* Motion sensors */ __le16 gyro[3]; /* x, y, z */ __le16 accel[3]; /* x, y, z */ __le32 sensor_timestamp; - uint8_t reserved2; + u8 reserved2; /* Touchpad */ struct dualsense_touch_point points[2]; - uint8_t reserved3[12]; - uint8_t status; - uint8_t reserved4[10]; + u8 reserved3[12]; + u8 status; + u8 reserved4[10]; } __packed; /* Common input report size shared equals the size of the USB report minus 1 byte for ReportID. */ static_assert(sizeof(struct dualsense_input_report) == DS_INPUT_REPORT_USB_SIZE - 1); /* Common data between DualSense BT/USB main output report. */ struct dualsense_output_report_common { - uint8_t valid_flag0; - uint8_t valid_flag1; + u8 valid_flag0; + u8 valid_flag1; /* For DualShock 4 compatibility mode. */ - uint8_t motor_right; - uint8_t motor_left; + u8 motor_right; + u8 motor_left; /* Audio controls */ - uint8_t reserved[4]; - uint8_t mute_button_led; + u8 reserved[4]; + u8 mute_button_led; - uint8_t power_save_control; - uint8_t reserved2[28]; + u8 power_save_control; + u8 reserved2[28]; /* LEDs and lightbar */ - uint8_t valid_flag2; - uint8_t reserved3[2]; - uint8_t lightbar_setup; - uint8_t led_brightness; - uint8_t player_leds; - uint8_t lightbar_red; - uint8_t lightbar_green; - uint8_t lightbar_blue; + u8 valid_flag2; + u8 reserved3[2]; + u8 lightbar_setup; + u8 led_brightness; + u8 player_leds; + u8 lightbar_red; + u8 lightbar_green; + u8 lightbar_blue; } __packed; static_assert(sizeof(struct dualsense_output_report_common) == 47); struct dualsense_output_report_bt { - uint8_t report_id; /* 0x31 */ - uint8_t seq_tag; - uint8_t tag; + u8 report_id; /* 0x31 */ + u8 seq_tag; + u8 tag; struct dualsense_output_report_common common; - uint8_t reserved[24]; + u8 reserved[24]; __le32 crc32; } __packed; static_assert(sizeof(struct dualsense_output_report_bt) == DS_OUTPUT_REPORT_BT_SIZE); struct dualsense_output_report_usb { - uint8_t report_id; /* 0x02 */ + u8 report_id; /* 0x02 */ struct dualsense_output_report_common common; - uint8_t reserved[15]; + u8 reserved[15]; } __packed; static_assert(sizeof(struct dualsense_output_report_usb) == DS_OUTPUT_REPORT_USB_SIZE); @@ -292,8 +292,8 @@ static_assert(sizeof(struct dualsense_output_report_usb) == DS_OUTPUT_REPORT_USB * This structure hide the differences between the two to simplify sending output reports. */ struct dualsense_output_report { - uint8_t *data; /* Start of data */ - uint8_t len; /* Size of output report */ + u8 *data; /* Start of data */ + u8 len; /* Size of output report */ /* Points to Bluetooth data payload in case for a Bluetooth report else NULL. */ struct dualsense_output_report_bt *bt; @@ -395,26 +395,26 @@ struct dualshock4 { /* Timestamp for sensor data */ bool sensor_timestamp_initialized; - uint32_t prev_sensor_timestamp; - uint32_t sensor_timestamp_us; + u32 prev_sensor_timestamp; + u32 sensor_timestamp_us; /* Bluetooth poll interval */ bool update_bt_poll_interval; - uint8_t bt_poll_interval; + u8 bt_poll_interval; bool update_rumble; - uint8_t motor_left; - uint8_t motor_right; + u8 motor_left; + u8 motor_right; /* Lightbar leds */ bool update_lightbar; bool update_lightbar_blink; bool lightbar_enabled; /* For use by global LED control. */ - uint8_t lightbar_red; - uint8_t lightbar_green; - uint8_t lightbar_blue; - uint8_t lightbar_blink_on; /* In increments of 10ms. */ - uint8_t lightbar_blink_off; /* In increments of 10ms. */ + u8 lightbar_red; + u8 lightbar_green; + u8 lightbar_blue; + u8 lightbar_blink_on; /* In increments of 10ms. */ + u8 lightbar_blink_off; /* In increments of 10ms. */ struct led_classdev lightbar_leds[4]; struct work_struct output_worker; @@ -423,88 +423,88 @@ struct dualshock4 { }; struct dualshock4_touch_point { - uint8_t contact; - uint8_t x_lo; - uint8_t x_hi:4, y_lo:4; - uint8_t y_hi; + u8 contact; + u8 x_lo; + u8 x_hi:4, y_lo:4; + u8 y_hi; } __packed; static_assert(sizeof(struct dualshock4_touch_point) == 4); struct dualshock4_touch_report { - uint8_t timestamp; + u8 timestamp; struct dualshock4_touch_point points[2]; } __packed; static_assert(sizeof(struct dualshock4_touch_report) == 9); /* Main DualShock4 input report excluding any BT/USB specific headers. */ struct dualshock4_input_report_common { - uint8_t x, y; - uint8_t rx, ry; - uint8_t buttons[3]; - uint8_t z, rz; + u8 x, y; + u8 rx, ry; + u8 buttons[3]; + u8 z, rz; /* Motion sensors */ __le16 sensor_timestamp; - uint8_t sensor_temperature; + u8 sensor_temperature; __le16 gyro[3]; /* x, y, z */ __le16 accel[3]; /* x, y, z */ - uint8_t reserved2[5]; + u8 reserved2[5]; - uint8_t status[2]; - uint8_t reserved3; + u8 status[2]; + u8 reserved3; } __packed; static_assert(sizeof(struct dualshock4_input_report_common) == 32); struct dualshock4_input_report_usb { - uint8_t report_id; /* 0x01 */ + u8 report_id; /* 0x01 */ struct dualshock4_input_report_common common; - uint8_t num_touch_reports; + u8 num_touch_reports; struct dualshock4_touch_report touch_reports[3]; - uint8_t reserved[3]; + u8 reserved[3]; } __packed; static_assert(sizeof(struct dualshock4_input_report_usb) == DS4_INPUT_REPORT_USB_SIZE); struct dualshock4_input_report_bt { - uint8_t report_id; /* 0x11 */ - uint8_t reserved[2]; + u8 report_id; /* 0x11 */ + u8 reserved[2]; struct dualshock4_input_report_common common; - uint8_t num_touch_reports; + u8 num_touch_reports; struct dualshock4_touch_report touch_reports[4]; /* BT has 4 compared to 3 for USB */ - uint8_t reserved2[2]; + u8 reserved2[2]; __le32 crc32; } __packed; static_assert(sizeof(struct dualshock4_input_report_bt) == DS4_INPUT_REPORT_BT_SIZE); /* Common data between Bluetooth and USB DualShock4 output reports. */ struct dualshock4_output_report_common { - uint8_t valid_flag0; - uint8_t valid_flag1; + u8 valid_flag0; + u8 valid_flag1; - uint8_t reserved; + u8 reserved; - uint8_t motor_right; - uint8_t motor_left; + u8 motor_right; + u8 motor_left; - uint8_t lightbar_red; - uint8_t lightbar_green; - uint8_t lightbar_blue; - uint8_t lightbar_blink_on; - uint8_t lightbar_blink_off; + u8 lightbar_red; + u8 lightbar_green; + u8 lightbar_blue; + u8 lightbar_blink_on; + u8 lightbar_blink_off; } __packed; struct dualshock4_output_report_usb { - uint8_t report_id; /* 0x5 */ + u8 report_id; /* 0x5 */ struct dualshock4_output_report_common common; - uint8_t reserved[21]; + u8 reserved[21]; } __packed; static_assert(sizeof(struct dualshock4_output_report_usb) == DS4_OUTPUT_REPORT_USB_SIZE); struct dualshock4_output_report_bt { - uint8_t report_id; /* 0x11 */ - uint8_t hw_control; - uint8_t audio_control; + u8 report_id; /* 0x11 */ + u8 hw_control; + u8 audio_control; struct dualshock4_output_report_common common; - uint8_t reserved[61]; + u8 reserved[61]; __le32 crc32; } __packed; static_assert(sizeof(struct dualshock4_output_report_bt) == DS4_OUTPUT_REPORT_BT_SIZE); @@ -515,8 +515,8 @@ static_assert(sizeof(struct dualshock4_output_report_bt) == DS4_OUTPUT_REPORT_BT * This structure hide the differences between the two to simplify sending output reports. */ struct dualshock4_output_report { - uint8_t *data; /* Start of data */ - uint8_t len; /* Size of output report */ + u8 *data; /* Start of data */ + u8 len; /* Size of output report */ /* Points to Bluetooth data payload in case for a Bluetooth report else NULL. */ struct dualshock4_output_report_bt *bt; @@ -555,7 +555,7 @@ static const struct {int x; int y; } ps_gamepad_hat_mapping[] = { static int dualshock4_get_calibration_data(struct dualshock4 *ds4); static inline void dualsense_schedule_work(struct dualsense *ds); static inline void dualshock4_schedule_work(struct dualshock4 *ds4); -static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t green, uint8_t blue); +static void dualsense_set_lightbar(struct dualsense *ds, u8 red, u8 green, u8 blue); static void dualshock4_set_default_lightbar_colors(struct dualshock4 *ds4); /* @@ -647,7 +647,7 @@ static int ps_battery_get_property(struct power_supply *psy, union power_supply_propval *val) { struct ps_device *dev = power_supply_get_drvdata(psy); - uint8_t battery_capacity; + u8 battery_capacity; int battery_status; int ret = 0; @@ -710,9 +710,9 @@ static int ps_device_register_battery(struct ps_device *dev) } /* Compute crc32 of HID data and compare against expected CRC. */ -static bool ps_check_crc32(uint8_t seed, uint8_t *data, size_t len, uint32_t report_crc) +static bool ps_check_crc32(u8 seed, u8 *data, size_t len, u32 report_crc) { - uint32_t crc; + u32 crc; crc = crc32_le(0xFFFFFFFF, &seed, 1); crc = ~crc32_le(crc, data, len); @@ -758,7 +758,7 @@ static struct input_dev *ps_gamepad_create(struct hid_device *hdev, return gamepad; } -static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *buf, size_t size, +static int ps_get_report(struct hid_device *hdev, u8 report_id, u8 *buf, size_t size, bool check_crc) { int ret; @@ -782,8 +782,8 @@ static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *bu if (hdev->bus == BUS_BLUETOOTH && check_crc) { /* Last 4 bytes contains crc32. */ - uint8_t crc_offset = size - 4; - uint32_t report_crc = get_unaligned_le32(&buf[crc_offset]); + u8 crc_offset = size - 4; + u32 report_crc = get_unaligned_le32(&buf[crc_offset]); if (!ps_check_crc32(PS_FEATURE_CRC32_SEED, buf, crc_offset, report_crc)) { hid_err(hdev, "CRC check failed for reportID=%d\n", report_id); @@ -976,7 +976,7 @@ static int dualsense_get_calibration_data(struct dualsense *ds) int range_2g; int ret = 0; int i; - uint8_t *buf; + u8 *buf; buf = kzalloc(DS_FEATURE_REPORT_CALIBRATION_SIZE, GFP_KERNEL); if (!buf) @@ -1090,7 +1090,7 @@ err_free: static int dualsense_get_firmware_info(struct dualsense *ds) { - uint8_t *buf; + u8 *buf; int ret; buf = kzalloc(DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE, GFP_KERNEL); @@ -1123,7 +1123,7 @@ err_free: static int dualsense_get_mac_address(struct dualsense *ds) { - uint8_t *buf; + u8 *buf; int ret = 0; buf = kzalloc(DS_FEATURE_REPORT_PAIRING_INFO_SIZE, GFP_KERNEL); @@ -1149,7 +1149,7 @@ static int dualsense_lightbar_set_brightness(struct led_classdev *cdev, { struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); struct dualsense *ds = container_of(mc_cdev, struct dualsense, lightbar); - uint8_t red, green, blue; + u8 red, green, blue; led_mc_calc_color_components(mc_cdev, brightness); red = mc_cdev->subled_info[0].brightness; @@ -1247,8 +1247,8 @@ static void dualsense_send_output_report(struct dualsense *ds, /* Bluetooth packets need to be signed with a CRC in the last 4 bytes. */ if (report->bt) { - uint32_t crc; - uint8_t seed = PS_OUTPUT_CRC32_SEED; + u32 crc; + u8 seed = PS_OUTPUT_CRC32_SEED; crc = crc32_le(0xFFFFFFFF, &seed, 1); crc = ~crc32_le(crc, report->data, report->len - 4); @@ -1327,9 +1327,9 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r struct hid_device *hdev = ps_dev->hdev; struct dualsense *ds = container_of(ps_dev, struct dualsense, base); struct dualsense_input_report *ds_report; - uint8_t battery_data, battery_capacity, charging_status, value; + u8 battery_data, battery_capacity, charging_status, value; int battery_status; - uint32_t sensor_timestamp; + u32 sensor_timestamp; bool btn_mic_state; int i; @@ -1344,7 +1344,7 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r } else if (hdev->bus == BUS_BLUETOOTH && report->id == DS_INPUT_REPORT_BT && size == DS_INPUT_REPORT_BT_SIZE) { /* Last 4 bytes of input report contain crc32 */ - uint32_t report_crc = get_unaligned_le32(&data[size - 4]); + u32 report_crc = get_unaligned_le32(&data[size - 4]); if (!ps_check_crc32(PS_INPUT_CRC32_SEED, data, size - 4, report_crc)) { hid_err(hdev, "DualSense input CRC's check failed\n"); @@ -1427,7 +1427,7 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r ds->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp, 3); ds->sensor_timestamp_initialized = true; } else { - uint32_t delta; + u32 delta; if (ds->prev_sensor_timestamp > sensor_timestamp) delta = (U32_MAX - ds->prev_sensor_timestamp + sensor_timestamp + 1); @@ -1527,7 +1527,7 @@ static void dualsense_remove(struct ps_device *ps_dev) static int dualsense_reset_leds(struct dualsense *ds) { struct dualsense_output_report report; - uint8_t *buf; + u8 *buf; buf = kzalloc(sizeof(struct dualsense_output_report_bt), GFP_KERNEL); if (!buf) @@ -1549,7 +1549,7 @@ static int dualsense_reset_leds(struct dualsense *ds) return 0; } -static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t green, uint8_t blue) +static void dualsense_set_lightbar(struct dualsense *ds, u8 red, u8 green, u8 blue) { scoped_guard(spinlock_irqsave, &ds->base.lock) { ds->update_lightbar = true; @@ -1577,7 +1577,7 @@ static void dualsense_set_player_leds(struct dualsense *ds) BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0) }; - uint8_t player_id = ds->base.player_id % ARRAY_SIZE(player_ids); + u8 player_id = ds->base.player_id % ARRAY_SIZE(player_ids); ds->update_player_leds = true; ds->player_leds_state = player_ids[player_id]; @@ -1588,7 +1588,7 @@ static struct ps_device *dualsense_create(struct hid_device *hdev) { struct dualsense *ds; struct ps_device *ps_dev; - uint8_t max_output_report_size; + u8 max_output_report_size; int i, ret; static const struct ps_led_info player_leds_info[] = { @@ -1779,7 +1779,7 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4) int range_2g; int ret = 0; int i; - uint8_t *buf; + u8 *buf; if (ds4->base.hdev->bus == BUS_USB) { int retries; @@ -1945,7 +1945,7 @@ transfer_failed: static int dualshock4_get_firmware_info(struct dualshock4 *ds4) { - uint8_t *buf; + u8 *buf; int ret; buf = kzalloc(DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE, GFP_KERNEL); @@ -1973,7 +1973,7 @@ err_free: static int dualshock4_get_mac_address(struct dualshock4 *ds4) { struct hid_device *hdev = ds4->base.hdev; - uint8_t *buf; + u8 *buf; int ret = 0; if (hdev->bus == BUS_USB) { @@ -2192,8 +2192,8 @@ static void dualshock4_output_worker(struct work_struct *work) /* Bluetooth packets need additional flags as well as a CRC in the last 4 bytes. */ if (report.bt) { - uint32_t crc; - uint8_t seed = PS_OUTPUT_CRC32_SEED; + u32 crc; + u8 seed = PS_OUTPUT_CRC32_SEED; /* Hardware control flags need to set to let the device know * there is HID data as well as CRC. @@ -2221,9 +2221,9 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base); struct dualshock4_input_report_common *ds4_report; struct dualshock4_touch_report *touch_reports; - uint8_t battery_capacity, num_touch_reports, value; + u8 battery_capacity, num_touch_reports, value; int battery_status, i, j; - uint16_t sensor_timestamp; + u16 sensor_timestamp; bool is_minimal = false; /* @@ -2241,7 +2241,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * } else if (hdev->bus == BUS_BLUETOOTH && report->id == DS4_INPUT_REPORT_BT && size == DS4_INPUT_REPORT_BT_SIZE) { struct dualshock4_input_report_bt *bt = (struct dualshock4_input_report_bt *)data; - uint32_t report_crc = get_unaligned_le32(&bt->crc32); + u32 report_crc = get_unaligned_le32(&bt->crc32); /* Last 4 bytes of input report contains CRC. */ if (!ps_check_crc32(PS_INPUT_CRC32_SEED, data, size - 4, report_crc)) { @@ -2325,7 +2325,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * ds4->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp * 16, 3); ds4->sensor_timestamp_initialized = true; } else { - uint16_t delta; + u16 delta; if (ds4->prev_sensor_timestamp > sensor_timestamp) delta = (U16_MAX - ds4->prev_sensor_timestamp + sensor_timestamp + 1); @@ -2370,7 +2370,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * * - 15: charge error */ if (ds4_report->status[0] & DS4_STATUS0_CABLE_STATE) { - uint8_t battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY; + u8 battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY; if (battery_data < 10) { /* Take the mid-point for each battery capacity value, @@ -2391,7 +2391,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * battery_status = POWER_SUPPLY_STATUS_UNKNOWN; } } else { - uint8_t battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY; + u8 battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY; if (battery_data < 10) battery_capacity = battery_data * 10 + 5; @@ -2500,7 +2500,7 @@ static inline void dualshock4_schedule_work(struct dualshock4 *ds4) schedule_work(&ds4->output_worker); } -static void dualshock4_set_bt_poll_interval(struct dualshock4 *ds4, uint8_t interval) +static void dualshock4_set_bt_poll_interval(struct dualshock4 *ds4, u8 interval) { ds4->bt_poll_interval = interval; ds4->update_bt_poll_interval = true; @@ -2520,7 +2520,7 @@ static void dualshock4_set_default_lightbar_colors(struct dualshock4 *ds4) { 0x20, 0x00, 0x20 } /* Pink */ }; - uint8_t player_id = ds4->base.player_id % ARRAY_SIZE(player_colors); + u8 player_id = ds4->base.player_id % ARRAY_SIZE(player_colors); ds4->lightbar_enabled = true; ds4->lightbar_red = player_colors[player_id][0]; @@ -2535,7 +2535,7 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev) { struct dualshock4 *ds4; struct ps_device *ps_dev; - uint8_t max_output_report_size; + u8 max_output_report_size; int i, ret; /* The DualShock4 has an RGB lightbar, which the original hid-sony driver From fac0993d3f29abe6529282b592bf8d6c0415401e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:24 +0200 Subject: [PATCH 039/295] HID: playstation: Correct spelling in comment sections JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 70db9aa76d29663047a84f337091f438be3c8a4f Author: Cristian Ciocaltea Date: Wed Jun 25 00:56:49 2025 +0300 HID: playstation: Correct spelling in comment sections Handle a couple of spelling complaints from checkpatch.pl: CHECK: 'connectd' may be misspelled - perhaps 'connected'? CHECK: 'Comptabile' may be misspelled - perhaps 'Compatible'? Signed-off-by: Cristian Ciocaltea Reviewed-by: Benjamin Tissoires Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 0ffff85d2505..ceaaa5dacddc 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -338,7 +338,7 @@ struct dualsense_output_report { /* Battery status within batery_status field. */ #define DS4_BATTERY_STATUS_FULL 11 /* Status1 bit2 contains dongle connection state: - * 0 = connectd + * 0 = connected * 1 = disconnected */ #define DS4_STATUS1_DONGLE_STATE BIT(2) @@ -2171,7 +2171,7 @@ static void dualshock4_output_worker(struct work_struct *work) if (ds4->update_lightbar) { common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED; - /* Comptabile behavior with hid-sony, which used a dummy global LED to + /* Compatible behavior with hid-sony, which used a dummy global LED to * allow enabling/disabling the lightbar. The global LED maps to * lightbar_enabled. */ From fa64e215d51f75ab6d7291875d01227f738210ee Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:25 +0200 Subject: [PATCH 040/295] HID: playstation: Fix all alignment and line length issues JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 56d7f285bfaa38fad082c1b50e20c1b57b2a8e18 Author: Cristian Ciocaltea Date: Wed Jun 25 00:56:50 2025 +0300 HID: playstation: Fix all alignment and line length issues Format code to address the following checkpatch.pl reports: CHECK: Alignment should match open parenthesis CHECK: line length of exceeds 100 columns CHECK: Please don't use multiple blank lines Signed-off-by: Cristian Ciocaltea Reviewed-by: Benjamin Tissoires Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 163 +++++++++++++++++++--------------- 1 file changed, 89 insertions(+), 74 deletions(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index ceaaa5dacddc..e5a594e5eae7 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -572,7 +572,7 @@ static int ps_devices_list_add(struct ps_device *dev) list_for_each_entry(entry, &ps_devices_list, list) { if (!memcmp(entry->mac_address, dev->mac_address, sizeof(dev->mac_address))) { hid_err(dev->hdev, "Duplicate device found for MAC address %pMR.\n", - dev->mac_address); + dev->mac_address); return -EEXIST; } } @@ -607,7 +607,8 @@ static void ps_device_release_player_id(struct ps_device *dev) dev->player_id = U32_MAX; } -static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, const char *name_suffix) +static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, + const char *name_suffix) { struct input_dev *input_dev; @@ -622,8 +623,8 @@ static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, const ch input_dev->uniq = hdev->uniq; if (name_suffix) { - input_dev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", hdev->name, - name_suffix); + input_dev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", + hdev->name, name_suffix); if (!input_dev->name) return ERR_PTR(-ENOMEM); } else { @@ -643,8 +644,8 @@ static enum power_supply_property ps_power_supply_props[] = { }; static int ps_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) + enum power_supply_property psp, + union power_supply_propval *val) { struct ps_device *dev = power_supply_get_drvdata(psy); u8 battery_capacity; @@ -688,7 +689,7 @@ static int ps_device_register_battery(struct ps_device *dev) dev->battery_desc.num_properties = ARRAY_SIZE(ps_power_supply_props); dev->battery_desc.get_property = ps_battery_get_property; dev->battery_desc.name = devm_kasprintf(&dev->hdev->dev, GFP_KERNEL, - "ps-controller-battery-%pMR", dev->mac_address); + "ps-controller-battery-%pMR", dev->mac_address); if (!dev->battery_desc.name) return -ENOMEM; @@ -720,8 +721,9 @@ static bool ps_check_crc32(u8 seed, u8 *data, size_t len, u32 report_crc) return crc == report_crc; } -static struct input_dev *ps_gamepad_create(struct hid_device *hdev, - int (*play_effect)(struct input_dev *, void *, struct ff_effect *)) +static struct input_dev * +ps_gamepad_create(struct hid_device *hdev, + int (*play_effect)(struct input_dev *, void *, struct ff_effect *)) { struct input_dev *gamepad; unsigned int i; @@ -758,8 +760,8 @@ static struct input_dev *ps_gamepad_create(struct hid_device *hdev, return gamepad; } -static int ps_get_report(struct hid_device *hdev, u8 report_id, u8 *buf, size_t size, - bool check_crc) +static int ps_get_report(struct hid_device *hdev, u8 report_id, u8 *buf, + size_t size, bool check_crc) { int ret; @@ -795,17 +797,20 @@ static int ps_get_report(struct hid_device *hdev, u8 report_id, u8 *buf, size_t } static int ps_led_register(struct ps_device *ps_dev, struct led_classdev *led, - const struct ps_led_info *led_info) + const struct ps_led_info *led_info) { int ret; if (led_info->name) { - led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL, - "%s:%s:%s", ps_dev->input_dev_name, led_info->color, led_info->name); + led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL, "%s:%s:%s", + ps_dev->input_dev_name, led_info->color, + led_info->name); } else { - /* Backwards compatible mode for hid-sony, but not compliant with LED class spec. */ - led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL, - "%s:%s", ps_dev->input_dev_name, led_info->color); + /* Backwards compatible mode for hid-sony, but not compliant + * with LED class spec. + */ + led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL, "%s:%s", + ps_dev->input_dev_name, led_info->color); } if (!led->name) @@ -829,7 +834,7 @@ static int ps_led_register(struct ps_device *ps_dev, struct led_classdev *led, /* Register a DualSense/DualShock4 RGB lightbar represented by a multicolor LED. */ static int ps_lightbar_register(struct ps_device *ps_dev, struct led_classdev_mc *lightbar_mc_dev, - int (*brightness_set)(struct led_classdev *, enum led_brightness)) + int (*brightness_set)(struct led_classdev *, enum led_brightness)) { struct hid_device *hdev = ps_dev->hdev; struct mc_subled *mc_led_info; @@ -850,7 +855,7 @@ static int ps_lightbar_register(struct ps_device *ps_dev, struct led_classdev_mc led_cdev = &lightbar_mc_dev->led_cdev; led_cdev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s:rgb:indicator", - ps_dev->input_dev_name); + ps_dev->input_dev_name); if (!led_cdev->name) return -ENOMEM; led_cdev->brightness = 255; @@ -866,8 +871,8 @@ static int ps_lightbar_register(struct ps_device *ps_dev, struct led_classdev_mc return 0; } -static struct input_dev *ps_sensors_create(struct hid_device *hdev, int accel_range, int accel_res, - int gyro_range, int gyro_res) +static struct input_dev *ps_sensors_create(struct hid_device *hdev, int accel_range, + int accel_res, int gyro_range, int gyro_res) { struct input_dev *sensors; int ret; @@ -903,8 +908,8 @@ static struct input_dev *ps_sensors_create(struct hid_device *hdev, int accel_ra return sensors; } -static struct input_dev *ps_touchpad_create(struct hid_device *hdev, int width, int height, - unsigned int num_contacts) +static struct input_dev *ps_touchpad_create(struct hid_device *hdev, int width, + int height, unsigned int num_contacts) { struct input_dev *touchpad; int ret; @@ -932,8 +937,7 @@ static struct input_dev *ps_touchpad_create(struct hid_device *hdev, int width, } static ssize_t firmware_version_show(struct device *dev, - struct device_attribute - *attr, char *buf) + struct device_attribute *attr, char *buf) { struct hid_device *hdev = to_hid_device(dev); struct ps_device *ps_dev = hid_get_drvdata(hdev); @@ -944,8 +948,7 @@ static ssize_t firmware_version_show(struct device *dev, static DEVICE_ATTR_RO(firmware_version); static ssize_t hardware_version_show(struct device *dev, - struct device_attribute - *attr, char *buf) + struct device_attribute *attr, char *buf) { struct hid_device *hdev = to_hid_device(dev); struct ps_device *ps_dev = hid_get_drvdata(hdev); @@ -983,7 +986,7 @@ static int dualsense_get_calibration_data(struct dualsense *ds) return -ENOMEM; ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_CALIBRATION, buf, - DS_FEATURE_REPORT_CALIBRATION_SIZE, true); + DS_FEATURE_REPORT_CALIBRATION_SIZE, true); if (ret) { hid_err(ds->base.hdev, "Failed to retrieve DualSense calibration info: %d\n", ret); goto err_free; @@ -1037,8 +1040,9 @@ static int dualsense_get_calibration_data(struct dualsense *ds) */ for (i = 0; i < ARRAY_SIZE(ds->gyro_calib_data); i++) { if (ds->gyro_calib_data[i].sens_denom == 0) { - hid_warn(hdev, "Invalid gyro calibration data for axis (%d), disabling calibration.", - ds->gyro_calib_data[i].abs_code); + hid_warn(hdev, + "Invalid gyro calibration data for axis (%d), disabling calibration.", + ds->gyro_calib_data[i].abs_code); ds->gyro_calib_data[i].bias = 0; ds->gyro_calib_data[i].sens_numer = DS_GYRO_RANGE; ds->gyro_calib_data[i].sens_denom = S16_MAX; @@ -1074,8 +1078,9 @@ static int dualsense_get_calibration_data(struct dualsense *ds) */ for (i = 0; i < ARRAY_SIZE(ds->accel_calib_data); i++) { if (ds->accel_calib_data[i].sens_denom == 0) { - hid_warn(hdev, "Invalid accelerometer calibration data for axis (%d), disabling calibration.", - ds->accel_calib_data[i].abs_code); + hid_warn(hdev, + "Invalid accelerometer calibration data for axis (%d), disabling calibration.", + ds->accel_calib_data[i].abs_code); ds->accel_calib_data[i].bias = 0; ds->accel_calib_data[i].sens_numer = DS_ACC_RANGE; ds->accel_calib_data[i].sens_denom = S16_MAX; @@ -1087,7 +1092,6 @@ err_free: return ret; } - static int dualsense_get_firmware_info(struct dualsense *ds) { u8 *buf; @@ -1098,7 +1102,7 @@ static int dualsense_get_firmware_info(struct dualsense *ds) return -ENOMEM; ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_FIRMWARE_INFO, buf, - DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE, true); + DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE, true); if (ret) { hid_err(ds->base.hdev, "Failed to retrieve DualSense firmware info: %d\n", ret); goto err_free; @@ -1131,7 +1135,7 @@ static int dualsense_get_mac_address(struct dualsense *ds) return -ENOMEM; ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_PAIRING_INFO, buf, - DS_FEATURE_REPORT_PAIRING_INFO_SIZE, true); + DS_FEATURE_REPORT_PAIRING_INFO_SIZE, true); if (ret) { hid_err(ds->base.hdev, "Failed to retrieve DualSense pairing info: %d\n", ret); goto err_free; @@ -1145,7 +1149,7 @@ err_free: } static int dualsense_lightbar_set_brightness(struct led_classdev *cdev, - enum led_brightness brightness) + enum led_brightness brightness) { struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); struct dualsense *ds = container_of(mc_cdev, struct dualsense, lightbar); @@ -1189,8 +1193,8 @@ static int dualsense_player_led_set_brightness(struct led_classdev *led, enum le return 0; } -static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_output_report *rp, - void *buf) +static void dualsense_init_output_report(struct dualsense *ds, + struct dualsense_output_report *rp, void *buf) { struct hid_device *hdev = ds->base.hdev; @@ -1241,7 +1245,7 @@ static inline void dualsense_schedule_work(struct dualsense *ds) * for Bluetooth reports. */ static void dualsense_send_output_report(struct dualsense *ds, - struct dualsense_output_report *report) + struct dualsense_output_report *report) { struct hid_device *hdev = ds->base.hdev; @@ -1322,7 +1326,7 @@ static void dualsense_output_worker(struct work_struct *work) } static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *report, - u8 *data, int size) + u8 *data, int size) { struct hid_device *hdev = ps_dev->hdev; struct dualsense *ds = container_of(ps_dev, struct dualsense, base); @@ -1339,10 +1343,10 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r * the full report using reportID 49. */ if (hdev->bus == BUS_USB && report->id == DS_INPUT_REPORT_USB && - size == DS_INPUT_REPORT_USB_SIZE) { + size == DS_INPUT_REPORT_USB_SIZE) { ds_report = (struct dualsense_input_report *)&data[1]; } else if (hdev->bus == BUS_BLUETOOTH && report->id == DS_INPUT_REPORT_BT && - size == DS_INPUT_REPORT_BT_SIZE) { + size == DS_INPUT_REPORT_BT_SIZE) { /* Last 4 bytes of input report contain crc32 */ u32 report_crc = get_unaligned_le32(&data[size - 4]); @@ -1677,7 +1681,7 @@ static struct ps_device *dualsense_create(struct hid_device *hdev) ps_dev->input_dev_name = dev_name(&ds->gamepad->dev); ds->sensors = ps_sensors_create(hdev, DS_ACC_RANGE, DS_ACC_RES_PER_G, - DS_GYRO_RANGE, DS_GYRO_RES_PER_DEG_S); + DS_GYRO_RANGE, DS_GYRO_RES_PER_DEG_S); if (IS_ERR(ds->sensors)) { ret = PTR_ERR(ds->sensors); goto err; @@ -1731,7 +1735,7 @@ static struct ps_device *dualsense_create(struct hid_device *hdev) * can change behavior. */ hid_info(hdev, "Registered DualSense controller hw_version=0x%08x fw_version=0x%08x\n", - ds->base.hw_version, ds->base.fw_version); + ds->base.hw_version, ds->base.fw_version); return &ds->base; @@ -1754,7 +1758,8 @@ static void dualshock4_dongle_calibration_work(struct work_struct *work) * DS4 hotplug is detect from sony_raw_event as any issues * are likely resolved then (the dongle is quite stupid). */ - hid_err(ds4->base.hdev, "DualShock 4 USB dongle: calibration failed, disabling device\n"); + hid_err(ds4->base.hdev, + "DualShock 4 USB dongle: calibration failed, disabling device\n"); dongle_state = DONGLE_DISABLED; } else { hid_info(ds4->base.hdev, "DualShock 4 USB dongle: calibration completed\n"); @@ -1798,14 +1803,17 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4) */ for (retries = 0; retries < 3; retries++) { ret = ps_get_report(hdev, DS4_FEATURE_REPORT_CALIBRATION, buf, - DS4_FEATURE_REPORT_CALIBRATION_SIZE, true); + DS4_FEATURE_REPORT_CALIBRATION_SIZE, true); if (ret) { if (retries < 2) { - hid_warn(hdev, "Retrying DualShock 4 get calibration report (0x02) request\n"); + hid_warn(hdev, + "Retrying DualShock 4 get calibration report (0x02) request\n"); continue; } - hid_warn(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret); + hid_warn(hdev, + "Failed to retrieve DualShock4 calibration info: %d\n", + ret); ret = -EILSEQ; kfree(buf); goto transfer_failed; @@ -1821,7 +1829,7 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4) } ret = ps_get_report(hdev, DS4_FEATURE_REPORT_CALIBRATION_BT, buf, - DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE, true); + DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE, true); if (ret) { hid_warn(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret); @@ -1916,8 +1924,9 @@ transfer_failed: for (i = 0; i < ARRAY_SIZE(ds4->gyro_calib_data); i++) { if (ds4->gyro_calib_data[i].sens_denom == 0) { ds4->gyro_calib_data[i].abs_code = ABS_RX + i; - hid_warn(hdev, "Invalid gyro calibration data for axis (%d), disabling calibration.", - ds4->gyro_calib_data[i].abs_code); + hid_warn(hdev, + "Invalid gyro calibration data for axis (%d), disabling calibration.", + ds4->gyro_calib_data[i].abs_code); ds4->gyro_calib_data[i].bias = 0; ds4->gyro_calib_data[i].sens_numer = DS4_GYRO_RANGE; ds4->gyro_calib_data[i].sens_denom = S16_MAX; @@ -1932,8 +1941,9 @@ transfer_failed: for (i = 0; i < ARRAY_SIZE(ds4->accel_calib_data); i++) { if (ds4->accel_calib_data[i].sens_denom == 0) { ds4->accel_calib_data[i].abs_code = ABS_X + i; - hid_warn(hdev, "Invalid accelerometer calibration data for axis (%d), disabling calibration.", - ds4->accel_calib_data[i].abs_code); + hid_warn(hdev, + "Invalid accelerometer calibration data for axis (%d), disabling calibration.", + ds4->accel_calib_data[i].abs_code); ds4->accel_calib_data[i].bias = 0; ds4->accel_calib_data[i].sens_numer = DS4_ACC_RANGE; ds4->accel_calib_data[i].sens_denom = S16_MAX; @@ -1956,7 +1966,7 @@ static int dualshock4_get_firmware_info(struct dualshock4 *ds4) * lacks CRC support, so must be disabled in ps_get_report. */ ret = ps_get_report(ds4->base.hdev, DS4_FEATURE_REPORT_FIRMWARE_INFO, buf, - DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE, false); + DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE, false); if (ret) { hid_err(ds4->base.hdev, "Failed to retrieve DualShock4 firmware info: %d\n", ret); goto err_free; @@ -1982,7 +1992,7 @@ static int dualshock4_get_mac_address(struct dualshock4 *ds4) return -ENOMEM; ret = ps_get_report(hdev, DS4_FEATURE_REPORT_PAIRING_INFO, buf, - DS4_FEATURE_REPORT_PAIRING_INFO_SIZE, false); + DS4_FEATURE_REPORT_PAIRING_INFO_SIZE, false); if (ret) { hid_err(hdev, "Failed to retrieve DualShock4 pairing info: %d\n", ret); goto err_free; @@ -1995,9 +2005,9 @@ static int dualshock4_get_mac_address(struct dualshock4 *ds4) return -EINVAL; ret = sscanf(hdev->uniq, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", - &ds4->base.mac_address[5], &ds4->base.mac_address[4], - &ds4->base.mac_address[3], &ds4->base.mac_address[2], - &ds4->base.mac_address[1], &ds4->base.mac_address[0]); + &ds4->base.mac_address[5], &ds4->base.mac_address[4], + &ds4->base.mac_address[3], &ds4->base.mac_address[2], + &ds4->base.mac_address[1], &ds4->base.mac_address[0]); if (ret != sizeof(ds4->base.mac_address)) return -EINVAL; @@ -2032,7 +2042,7 @@ static enum led_brightness dualshock4_led_get_brightness(struct led_classdev *le } static int dualshock4_led_set_blink(struct led_classdev *led, unsigned long *delay_on, - unsigned long *delay_off) + unsigned long *delay_off) { struct hid_device *hdev = to_hid_device(led->dev->parent); struct dualshock4 *ds4 = hid_get_drvdata(hdev); @@ -2100,7 +2110,7 @@ static int dualshock4_led_set_brightness(struct led_classdev *led, enum led_brig } static void dualshock4_init_output_report(struct dualshock4 *ds4, - struct dualshock4_output_report *rp, void *buf) + struct dualshock4_output_report *rp, void *buf) { struct hid_device *hdev = ds4->base.hdev; @@ -2215,7 +2225,7 @@ static void dualshock4_output_worker(struct work_struct *work) } static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *report, - u8 *data, int size) + u8 *data, int size) { struct hid_device *hdev = ps_dev->hdev; struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base); @@ -2232,14 +2242,15 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * * the full report using reportID 17. */ if (hdev->bus == BUS_USB && report->id == DS4_INPUT_REPORT_USB && - size == DS4_INPUT_REPORT_USB_SIZE) { - struct dualshock4_input_report_usb *usb = (struct dualshock4_input_report_usb *)data; + size == DS4_INPUT_REPORT_USB_SIZE) { + struct dualshock4_input_report_usb *usb = + (struct dualshock4_input_report_usb *)data; ds4_report = &usb->common; num_touch_reports = usb->num_touch_reports; touch_reports = usb->touch_reports; } else if (hdev->bus == BUS_BLUETOOTH && report->id == DS4_INPUT_REPORT_BT && - size == DS4_INPUT_REPORT_BT_SIZE) { + size == DS4_INPUT_REPORT_BT_SIZE) { struct dualshock4_input_report_bt *bt = (struct dualshock4_input_report_bt *)data; u32 report_crc = get_unaligned_le32(&bt->crc32); @@ -2410,7 +2421,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * } static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_report *report, - u8 *data, int size) + u8 *data, int size) { struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base); bool connected = false; @@ -2421,7 +2432,8 @@ static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_r * parsing code. */ if (data[0] == DS4_INPUT_REPORT_USB && size == DS4_INPUT_REPORT_USB_SIZE) { - struct dualshock4_input_report_common *ds4_report = (struct dualshock4_input_report_common *)&data[1]; + struct dualshock4_input_report_common *ds4_report = + (struct dualshock4_input_report_common *)&data[1]; connected = ds4_report->status[1] & DS4_STATUS1_DONGLE_STATE ? false : true; @@ -2548,11 +2560,14 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev) * existing applications (e.g. Android). Nothing matches against MAC address. */ static const struct ps_led_info lightbar_leds_info[] = { - { NULL, "red", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness }, - { NULL, "green", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness }, - { NULL, "blue", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness }, - { NULL, "global", 1, dualshock4_led_get_brightness, dualshock4_led_set_brightness, - dualshock4_led_set_blink }, + { NULL, "red", 255, dualshock4_led_get_brightness, + dualshock4_led_set_brightness }, + { NULL, "green", 255, dualshock4_led_get_brightness, + dualshock4_led_set_brightness }, + { NULL, "blue", 255, dualshock4_led_get_brightness, + dualshock4_led_set_brightness }, + { NULL, "global", 1, dualshock4_led_get_brightness, + dualshock4_led_set_brightness, dualshock4_led_set_blink }, }; ds4 = devm_kzalloc(&hdev->dev, sizeof(*ds4), GFP_KERNEL); @@ -2622,7 +2637,7 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev) ps_dev->input_dev_name = dev_name(&ds4->gamepad->dev); ds4->sensors = ps_sensors_create(hdev, DS4_ACC_RANGE, DS4_ACC_RES_PER_G, - DS4_GYRO_RANGE, DS4_GYRO_RES_PER_DEG_S); + DS4_GYRO_RANGE, DS4_GYRO_RES_PER_DEG_S); if (IS_ERR(ds4->sensors)) { ret = PTR_ERR(ds4->sensors); goto err; @@ -2661,7 +2676,7 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev) * can change behavior. */ hid_info(hdev, "Registered DualShock4 controller hw_version=0x%08x fw_version=0x%08x\n", - ds4->base.hw_version, ds4->base.fw_version); + ds4->base.hw_version, ds4->base.fw_version); return &ds4->base; err: @@ -2670,7 +2685,7 @@ err: } static int ps_raw_event(struct hid_device *hdev, struct hid_report *report, - u8 *data, int size) + u8 *data, int size) { struct ps_device *dev = hid_get_drvdata(hdev); From a37b887766bc602220a43448e04acef3e8b5a3af Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:26 +0200 Subject: [PATCH 041/295] HID: playstation: Document spinlock_t usage JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit d9812f06be437c64e00fd557ed374100c97077cc Author: Cristian Ciocaltea Date: Wed Jun 25 00:56:51 2025 +0300 HID: playstation: Document spinlock_t usage Document the usage of 'lock' member in struct ps_device and silent checkpatch.pl complaint: CHECK: spinlock_t definition without comment Signed-off-by: Cristian Ciocaltea Reviewed-by: Benjamin Tissoires Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index e5a594e5eae7..86f8c0f7e59a 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -38,7 +38,7 @@ enum PS_TYPE { struct ps_device { struct list_head list; struct hid_device *hdev; - spinlock_t lock; + spinlock_t lock; /* Sync between event handler and workqueue */ u32 player_id; From 3b218b015a3a42bc1b783e4d94b77ad17bae327f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:27 +0200 Subject: [PATCH 042/295] HID: playstation: Prefer kzalloc(sizeof(*buf)...) JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 400c6bbc7b48a011c18c765cd1fbb396113e2f63 Author: Cristian Ciocaltea Date: Wed Jun 25 00:56:52 2025 +0300 HID: playstation: Prefer kzalloc(sizeof(*buf)...) Use the shorter variant as suggested by checkpatch.pl: CHECK: Prefer kzalloc(sizeof(*buf)...) over kzalloc(sizeof(struct dualsense_output_report_bt)...) This also improves further maintainability. Signed-off-by: Cristian Ciocaltea Reviewed-by: Benjamin Tissoires Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 86f8c0f7e59a..69acd4587352 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -1531,9 +1531,9 @@ static void dualsense_remove(struct ps_device *ps_dev) static int dualsense_reset_leds(struct dualsense *ds) { struct dualsense_output_report report; - u8 *buf; + struct dualsense_output_report_bt *buf; - buf = kzalloc(sizeof(struct dualsense_output_report_bt), GFP_KERNEL); + buf = kzalloc(sizeof(*buf), GFP_KERNEL); if (!buf) return -ENOMEM; From 6576ccdb38f0c5a29313d6a1c101a7b5a240b73f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:29 +0200 Subject: [PATCH 043/295] HID: playstation: Redefine DualSense input report status field JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit d7b744fa977b4c402c25ba723b4981a8420ad15a Author: Cristian Ciocaltea Date: Wed Jun 25 00:56:53 2025 +0300 HID: playstation: Redefine DualSense input report status field The 'status' member of struct dualsense_input_report is currently used to store just the battery data, despite the fact that hardware is capable to report two extra bytes of status information. In preparation to make use of some of the additional data, redefine the field type as a 3-byte array. Moreover, to preserve consistency with the related DS_STATUS[0..2]_* registers in datasheet, rename DS_STATUS_* bitfield macros accordingly. Signed-off-by: Cristian Ciocaltea Reviewed-by: Benjamin Tissoires Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 69acd4587352..c256ad5cfc04 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -112,9 +112,9 @@ struct ps_led_info { #define DS_BUTTONS2_TOUCHPAD BIT(1) #define DS_BUTTONS2_MIC_MUTE BIT(2) -/* Status field of DualSense input report. */ -#define DS_STATUS_BATTERY_CAPACITY GENMASK(3, 0) -#define DS_STATUS_CHARGING GENMASK(7, 4) +/* Battery status field of DualSense input report. */ +#define DS_STATUS0_BATTERY_CAPACITY GENMASK(3, 0) +#define DS_STATUS0_CHARGING GENMASK(7, 4) /* Feature version from DualSense Firmware Info report. */ #define DS_FEATURE_VERSION_MINOR GENMASK(7, 0) @@ -235,8 +235,8 @@ struct dualsense_input_report { struct dualsense_touch_point points[2]; u8 reserved3[12]; - u8 status; - u8 reserved4[10]; + u8 status[3]; + u8 reserved4[8]; } __packed; /* Common input report size shared equals the size of the USB report minus 1 byte for ReportID. */ static_assert(sizeof(struct dualsense_input_report) == DS_INPUT_REPORT_USB_SIZE - 1); @@ -1461,8 +1461,8 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r input_report_key(ds->touchpad, BTN_LEFT, ds_report->buttons[2] & DS_BUTTONS2_TOUCHPAD); input_sync(ds->touchpad); - battery_data = FIELD_GET(DS_STATUS_BATTERY_CAPACITY, ds_report->status); - charging_status = FIELD_GET(DS_STATUS_CHARGING, ds_report->status); + battery_data = FIELD_GET(DS_STATUS0_BATTERY_CAPACITY, ds_report->status[0]); + charging_status = FIELD_GET(DS_STATUS0_CHARGING, ds_report->status[0]); switch (charging_status) { case 0x0: From 32b7276101257a53b45f07592ffe183876f78b96 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:30 +0200 Subject: [PATCH 044/295] HID: playstation: Support DualSense audio jack hotplug detection JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit b1b4806c0c528e51c648dbaf8cd9f7027c1c11b7 Author: Cristian Ciocaltea Date: Wed Jun 25 00:56:54 2025 +0300 HID: playstation: Support DualSense audio jack hotplug detection The default audio output path on DualSense controller hardware is set to headphones, regardless of whether they are actually inserted or not. Detect when the plugged state of the 3.5mm audio jack changes and toggle audio output between headphones and internal speaker, as required. The latter is achieved by essentially routing the right channel of the audio source to the mono speaker. Additionally, adjust the speaker volume since its default level is too low and, therefore, cannot generate any audible sound. It's worth noting the audio functionality is currently not supported for Bluetooth, hence it's limited to USB connectivity. Signed-off-by: Cristian Ciocaltea Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 91 +++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index c256ad5cfc04..cbf8b6dd0205 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -112,9 +112,13 @@ struct ps_led_info { #define DS_BUTTONS2_TOUCHPAD BIT(1) #define DS_BUTTONS2_MIC_MUTE BIT(2) -/* Battery status field of DualSense input report. */ +/* Status fields of DualSense input report. */ #define DS_STATUS0_BATTERY_CAPACITY GENMASK(3, 0) #define DS_STATUS0_CHARGING GENMASK(7, 4) +#define DS_STATUS1_HP_DETECT BIT(0) +#define DS_STATUS1_MIC_DETECT BIT(1) +#define DS_STATUS1_JACK_DETECT (DS_STATUS1_HP_DETECT | DS_STATUS1_MIC_DETECT) +#define DS_STATUS1_MIC_MUTE BIT(2) /* Feature version from DualSense Firmware Info report. */ #define DS_FEATURE_VERSION_MINOR GENMASK(7, 0) @@ -143,13 +147,19 @@ struct ps_led_info { /* Flags for DualSense output report. */ #define DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION BIT(0) #define DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT BIT(1) +#define DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE BIT(5) +#define DS_OUTPUT_VALID_FLAG0_MIC_VOLUME_ENABLE BIT(6) +#define DS_OUTPUT_VALID_FLAG0_AUDIO_CONTROL_ENABLE BIT(7) #define DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE BIT(0) #define DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE BIT(1) #define DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE BIT(2) #define DS_OUTPUT_VALID_FLAG1_RELEASE_LEDS BIT(3) #define DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE BIT(4) +#define DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE BIT(7) #define DS_OUTPUT_VALID_FLAG2_LIGHTBAR_SETUP_CONTROL_ENABLE BIT(1) #define DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2 BIT(2) +#define DS_OUTPUT_AUDIO_FLAGS_OUTPUT_PATH_SEL GENMASK(5, 4) +#define DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN GENMASK(2, 0) #define DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE BIT(4) #define DS_OUTPUT_LIGHTBAR_SETUP_LIGHT_OUT BIT(1) @@ -192,6 +202,11 @@ struct dualsense { u8 lightbar_green; u8 lightbar_blue; + /* Audio Jack plugged state */ + u8 plugged_state; + u8 prev_plugged_state; + bool prev_plugged_state_valid; + /* Microphone */ bool update_mic_mute; bool mic_muted; @@ -251,11 +266,15 @@ struct dualsense_output_report_common { u8 motor_left; /* Audio controls */ - u8 reserved[4]; + u8 headphone_volume; /* 0x0 - 0x7f */ + u8 speaker_volume; /* 0x0 - 0xff */ + u8 mic_volume; /* 0x0 - 0x40 */ + u8 audio_control; u8 mute_button_led; u8 power_save_control; - u8 reserved2[28]; + u8 reserved2[27]; + u8 audio_control2; /* LEDs and lightbar */ u8 valid_flag2; @@ -1303,6 +1322,46 @@ static void dualsense_output_worker(struct work_struct *work) ds->update_player_leds = false; } + if (ds->plugged_state != ds->prev_plugged_state) { + u8 val = ds->plugged_state & DS_STATUS1_HP_DETECT; + + if (val != (ds->prev_plugged_state & DS_STATUS1_HP_DETECT)) { + common->valid_flag0 = DS_OUTPUT_VALID_FLAG0_AUDIO_CONTROL_ENABLE; + /* + * _--------> Output path setup in audio_flag0 + * / _------> Headphone (HP) Left channel sink + * | / _----> Headphone (HP) Right channel sink + * | | / _--> Internal Speaker (SP) sink + * | | | / + * | | | | L/R - Left/Right channel source + * 0 L-R X X - Unrouted (muted) channel source + * 1 L-L X + * 2 L-L R + * 3 X-X R + */ + if (val) { + /* Mute SP and route L+R channels to HP */ + common->audio_control = 0; + } else { + /* Mute HP and route R channel to SP */ + common->audio_control = + FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS_OUTPUT_PATH_SEL, 0x3); + /* + * Set SP hardware volume to 100%. + * Note the accepted range seems to be [0x3d..0x64] + */ + common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE; + common->speaker_volume = 0x64; + /* Set SP preamp gain to ~30% */ + common->valid_flag1 = DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE; + common->audio_control2 = + FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN, 0x2); + } + } + + ds->prev_plugged_state = ds->plugged_state; + } + if (ds->update_mic_mute) { common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE; common->mute_button_led = ds->mic_muted; @@ -1406,6 +1465,32 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r } ds->last_btn_mic_state = btn_mic_state; + /* + * Parse HP/MIC plugged state data for USB use case, since Bluetooth + * audio is currently not supported. + */ + if (hdev->bus == BUS_USB) { + value = ds_report->status[1] & DS_STATUS1_JACK_DETECT; + + if (!ds->prev_plugged_state_valid) { + /* Initial handling of the plugged state report */ + scoped_guard(spinlock_irqsave, &ps_dev->lock) { + ds->plugged_state = (~value) & DS_STATUS1_JACK_DETECT; + ds->prev_plugged_state_valid = true; + } + } + + if (value != ds->plugged_state) { + scoped_guard(spinlock_irqsave, &ps_dev->lock) { + ds->prev_plugged_state = ds->plugged_state; + ds->plugged_state = value; + } + + /* Schedule audio routing towards active endpoint. */ + dualsense_schedule_work(ds); + } + } + /* Parse and calibrate gyroscope data. */ for (i = 0; i < ARRAY_SIZE(ds_report->gyro); i++) { int raw_data = (short)le16_to_cpu(ds_report->gyro[i]); From 727d8dcf0ae0d8151f9520c71907594b89111347 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:32 +0200 Subject: [PATCH 045/295] HID: playstation: Support DualSense audio jack event reporting JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit e1c24d545b8d0f4f04e01258f1ac5d2934c6a08d Author: Cristian Ciocaltea Date: Wed Jun 25 00:56:55 2025 +0300 HID: playstation: Support DualSense audio jack event reporting The DualSense controller complies with v1.0 of the USB Audio Class spec (UAC1), hence it cannot advertise any jack detection capability. However, this feature can be implemented in the generic USB audio driver via quirks, i.e. by configuring an input handler to receive hotplug events from the HID driver. When operating in USB mode, register a dedicated input device for the audio jack and use it to report all headphone and headset mic insert events. Signed-off-by: Cristian Ciocaltea Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index cbf8b6dd0205..27433d52539e 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -176,6 +176,7 @@ struct dualsense { struct input_dev *gamepad; struct input_dev *sensors; struct input_dev *touchpad; + struct input_dev *jack; /* Update version is used as a feature/capability version. */ u16 update_version; @@ -955,6 +956,25 @@ static struct input_dev *ps_touchpad_create(struct hid_device *hdev, int width, return touchpad; } +static struct input_dev *ps_headset_jack_create(struct hid_device *hdev) +{ + struct input_dev *jack; + int ret; + + jack = ps_allocate_input_dev(hdev, "Headset Jack"); + if (IS_ERR(jack)) + return ERR_CAST(jack); + + input_set_capability(jack, EV_SW, SW_HEADPHONE_INSERT); + input_set_capability(jack, EV_SW, SW_MICROPHONE_INSERT); + + ret = input_register_device(jack); + if (ret) + return ERR_PTR(ret); + + return jack; +} + static ssize_t firmware_version_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1357,8 +1377,15 @@ static void dualsense_output_worker(struct work_struct *work) common->audio_control2 = FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN, 0x2); } + + input_report_switch(ds->jack, SW_HEADPHONE_INSERT, val); } + val = ds->plugged_state & DS_STATUS1_MIC_DETECT; + if (val != (ds->prev_plugged_state & DS_STATUS1_MIC_DETECT)) + input_report_switch(ds->jack, SW_MICROPHONE_INSERT, val); + + input_sync(ds->jack); ds->prev_plugged_state = ds->plugged_state; } @@ -1778,6 +1805,15 @@ static struct ps_device *dualsense_create(struct hid_device *hdev) goto err; } + /* Bluetooth audio is currently not supported. */ + if (hdev->bus == BUS_USB) { + ds->jack = ps_headset_jack_create(hdev); + if (IS_ERR(ds->jack)) { + ret = PTR_ERR(ds->jack); + goto err; + } + } + ret = ps_device_register_battery(ps_dev); if (ret) goto err; From 236c6bc444d4545c0bb84618b90f74f91aa64c17 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:34 +0200 Subject: [PATCH 046/295] selftests/hid: update vmtest.sh for virtme-ng JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit be66a27b4f391ecd9cfb9394feb4369c4d39f94f Author: Benjamin Tissoires Date: Thu Aug 21 16:29:55 2025 +0200 selftests/hid: update vmtest.sh for virtme-ng This commit is a rewrite almost from scratch of vmtest.sh. By relying on virtme-ng, we get rid of boot2container, reducing the total bootup time (and network requirements). That means that we are relying on the programs being installed on the host, but that shouldn't be an issue. The generation of the kconfig is also now handled by virtme-ng, so that's one less thing to worry. I used tools/testing/selftests/vsock/vmtest.sh as a base and modified it to look mostly like my previous script: - removed the custom ssh handling - make use of vng for compiling, which allows to bring remote compilation (and potentially remote compilation on a remote container) - change the verbosity logic by having 2 levels: - first one shows the tests outputs - second level also shows the VM logs - instead of only running the compiled kernel when it is built, if we are in the kernel tree, use the kernel artifacts there (and complain if they are not built) - adapted the tests list to match the HID subsystem tests Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/vmtest.sh | 714 ++++++++++++++++---------- 1 file changed, 446 insertions(+), 268 deletions(-) diff --git a/tools/testing/selftests/hid/vmtest.sh b/tools/testing/selftests/hid/vmtest.sh index db534e9099a8..ecbd57f775a0 100755 --- a/tools/testing/selftests/hid/vmtest.sh +++ b/tools/testing/selftests/hid/vmtest.sh @@ -1,296 +1,474 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2025 Red Hat +# Copyright (c) 2025 Meta Platforms, Inc. and affiliates +# +# Dependencies: +# * virtme-ng +# * busybox-static (used by virtme-ng) +# * qemu (used by virtme-ng) -set -u -set -e +readonly SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" +readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../) -# This script currently only works for x86_64 -ARCH="$(uname -m)" -case "${ARCH}" in -x86_64) - QEMU_BINARY=qemu-system-x86_64 - BZIMAGE="arch/x86/boot/bzImage" - ;; -*) - echo "Unsupported architecture" - exit 1 - ;; -esac -SCRIPT_DIR="$(dirname $(realpath $0))" -OUTPUT_DIR="$SCRIPT_DIR/results" -KCONFIG_REL_PATHS=("${SCRIPT_DIR}/config" "${SCRIPT_DIR}/config.common" "${SCRIPT_DIR}/config.${ARCH}") -B2C_URL="https://gitlab.freedesktop.org/gfx-ci/boot2container/-/raw/main/vm2c.py" -NUM_COMPILE_JOBS="$(nproc)" -LOG_FILE_BASE="$(date +"hid_selftests.%Y-%m-%d_%H-%M-%S")" -LOG_FILE="${LOG_FILE_BASE}.log" -EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status" -CONTAINER_IMAGE="registry.freedesktop.org/bentiss/hid/fedora/39:2023-11-22.1" +source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh -TARGETS="${TARGETS:=$(basename ${SCRIPT_DIR})}" -DEFAULT_COMMAND="pip3 install hid-tools; make -C tools/testing/selftests TARGETS=${TARGETS} run_tests" +readonly HID_BPF_TEST="${SCRIPT_DIR}"/hid_bpf +readonly HIDRAW_TEST="${SCRIPT_DIR}"/hidraw +readonly HID_BPF_PROGS="${KERNEL_CHECKOUT}/drivers/hid/bpf/progs" +readonly SSH_GUEST_PORT=22 +readonly WAIT_PERIOD=3 +readonly WAIT_PERIOD_MAX=60 +readonly WAIT_TOTAL=$(( WAIT_PERIOD * WAIT_PERIOD_MAX )) +readonly QEMU_PIDFILE=$(mktemp /tmp/qemu_hid_vmtest_XXXX.pid) -usage() -{ - cat <] -- [] +readonly QEMU_OPTS="\ + --pidfile ${QEMU_PIDFILE} \ +" +readonly KERNEL_CMDLINE="" +readonly LOG=$(mktemp /tmp/hid_vmtest_XXXX.log) +readonly TEST_NAMES=(vm_hid_bpf vm_hidraw vm_pytest) +readonly TEST_DESCS=( + "Run hid_bpf tests in the VM." + "Run hidraw tests in the VM." + "Run the hid-tools test-suite in the VM." +) - is the command you would normally run when you are in -the source kernel direcory. e.g: +VERBOSE=0 +SHELL_MODE=0 +BUILD_HOST="" +BUILD_HOST_PODMAN_CONTAINER_NAME="" - $0 -- ./tools/testing/selftests/hid/hid_bpf +usage() { + local name + local desc + local i -If no command is specified and a debug shell (-s) is not requested, -"${DEFAULT_COMMAND}" will be run by default. + echo + echo "$0 [OPTIONS] [TEST]... [-- tests-args]" + echo "If no TEST argument is given, all tests will be run." + echo + echo "Options" + echo " -b: build the kernel from the current source tree and use it for guest VMs" + echo " -H: hostname for remote build host (used with -b)" + echo " -p: podman container name for remote build host (used with -b)" + echo " Example: -H beefyserver -p vng" + echo " -q: set the path to or name of qemu binary" + echo " -s: start a shell in the VM instead of running tests" + echo " -v: more verbose output (can be repeated multiple times)" + echo + echo "Available tests" -If you build your kernel using KBUILD_OUTPUT= or O= options, these -can be passed as environment variables to the script: - - O= $0 -- ./tools/testing/selftests/hid/hid_bpf - -or - - KBUILD_OUTPUT= $0 -- ./tools/testing/selftests/hid/hid_bpf - -Options: - - -u) Update the boot2container script to a newer version. - -d) Update the output directory (default: ${OUTPUT_DIR}) - -b) Run only the build steps for the kernel and the selftests - -j) Number of jobs for compilation, similar to -j in make - (default: ${NUM_COMPILE_JOBS}) - -s) Instead of powering off the VM, start an interactive - shell. If is specified, the shell runs after - the command finishes executing -EOF -} - -download() -{ - local file="$1" - - echo "Downloading $file..." >&2 - curl -Lsf "$file" -o "${@:2}" -} - -recompile_kernel() -{ - local kernel_checkout="$1" - local make_command="$2" - - cd "${kernel_checkout}" - - ${make_command} olddefconfig - ${make_command} headers - ${make_command} -} - -update_selftests() -{ - local kernel_checkout="$1" - local selftests_dir="${kernel_checkout}/tools/testing/selftests/hid" - - cd "${selftests_dir}" - ${make_command} -} - -run_vm() -{ - local run_dir="$1" - local b2c="$2" - local kernel_bzimage="$3" - local command="$4" - local post_command="" - - cd "${run_dir}" - - if ! which "${QEMU_BINARY}" &> /dev/null; then - cat < ${OUTPUT_DIR}/${EXIT_STATUS_FILE} - - set -e - - ${post_command} -} - -is_rel_path() -{ - local path="$1" - - [[ ${path:0:1} != "/" ]] -} - -do_update_kconfig() -{ - local kernel_checkout="$1" - local kconfig_file="$2" - - rm -f "$kconfig_file" 2> /dev/null - - for config in "${KCONFIG_REL_PATHS[@]}"; do - local kconfig_src="${config}" - cat "$kconfig_src" >> "$kconfig_file" + for ((i = 0; i < ${#TEST_NAMES[@]}; i++)); do + name=${TEST_NAMES[${i}]} + desc=${TEST_DESCS[${i}]} + printf "\t%-35s%-35s\n" "${name}" "${desc}" done + echo + + exit 1 } -update_kconfig() -{ - local kernel_checkout="$1" - local kconfig_file="$2" +die() { + echo "$*" >&2 + exit "${KSFT_FAIL}" +} - if [[ -f "${kconfig_file}" ]]; then - local local_modified="$(stat -c %Y "${kconfig_file}")" +vm_ssh() { + # vng --ssh-client keeps shouting "Warning: Permanently added 'virtme-ng%22' + # (ED25519) to the list of known hosts.", + # So replace the command with what's actually called and add the "-q" option + stdbuf -oL ssh -q \ + -F ${HOME}/.cache/virtme-ng/.ssh/virtme-ng-ssh.conf \ + -l root virtme-ng%${SSH_GUEST_PORT} \ + "$@" + return $? +} - for config in "${KCONFIG_REL_PATHS[@]}"; do - local kconfig_src="${config}" - local src_modified="$(stat -c %Y "${kconfig_src}")" - # Only update the config if it has been updated after the - # previously cached config was created. This avoids - # unnecessarily compiling the kernel and selftests. - if [[ "${src_modified}" -gt "${local_modified}" ]]; then - do_update_kconfig "$kernel_checkout" "$kconfig_file" - # Once we have found one outdated configuration - # there is no need to check other ones. +cleanup() { + if [[ -s "${QEMU_PIDFILE}" ]]; then + pkill -SIGTERM -F "${QEMU_PIDFILE}" > /dev/null 2>&1 + fi + + # If failure occurred during or before qemu start up, then we need + # to clean this up ourselves. + if [[ -e "${QEMU_PIDFILE}" ]]; then + rm "${QEMU_PIDFILE}" + fi +} + +check_args() { + local found + + for arg in "$@"; do + found=0 + for name in "${TEST_NAMES[@]}"; do + if [[ "${name}" = "${arg}" ]]; then + found=1 break fi done - else - do_update_kconfig "$kernel_checkout" "$kconfig_file" - fi -} -main() -{ - local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" - local kernel_checkout=$(realpath "${script_dir}"/../../../../) - # By default the script searches for the kernel in the checkout directory but - # it also obeys environment variables O= and KBUILD_OUTPUT= - local kernel_bzimage="${kernel_checkout}/${BZIMAGE}" - local command="${DEFAULT_COMMAND}" - local update_b2c="no" - local debug_shell="no" - local build_only="no" - - while getopts ':hsud:j:b' opt; do - case ${opt} in - u) - update_b2c="yes" - ;; - d) - OUTPUT_DIR="$OPTARG" - ;; - j) - NUM_COMPILE_JOBS="$OPTARG" - ;; - s) - command="/bin/sh" - debug_shell="yes" - ;; - b) - build_only="yes" - ;; - h) + if [[ "${found}" -eq 0 ]]; then + echo "${arg} is not an available test" >&2 usage - exit 0 - ;; - \? ) - echo "Invalid Option: -$OPTARG" - usage - exit 1 - ;; - : ) - echo "Invalid Option: -$OPTARG requires an argument" - usage - exit 1 - ;; - esac + fi done - shift $((OPTIND -1)) - # trap 'catch "$?"' EXIT - if [[ "${build_only}" == "no" && "${debug_shell}" == "no" ]]; then - if [[ $# -eq 0 ]]; then - echo "No command specified, will run ${DEFAULT_COMMAND} in the vm" - else - command="$@" - - if [[ "${command}" == "/bin/bash" || "${command}" == "bash" ]] - then - debug_shell="yes" - fi + for arg in "$@"; do + if ! command -v > /dev/null "test_${arg}"; then + echo "Test ${arg} not found" >&2 + usage fi + done +} + +check_deps() { + for dep in vng ${QEMU} busybox pkill ssh pytest; do + if [[ ! -x $(command -v "${dep}") ]]; then + echo -e "skip: dependency ${dep} not found!\n" + exit "${KSFT_SKIP}" + fi + done + + if [[ ! -x $(command -v "${HID_BPF_TEST}") ]]; then + printf "skip: %s not found!" "${HID_BPF_TEST}" + printf " Please build the kselftest hid_bpf target.\n" + exit "${KSFT_SKIP}" fi - local kconfig_file="${OUTPUT_DIR}/latest.config" - local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}" - - # Figure out where the kernel is being built. - # O takes precedence over KBUILD_OUTPUT. - if [[ "${O:=""}" != "" ]]; then - if is_rel_path "${O}"; then - O="$(realpath "${PWD}/${O}")" - fi - kernel_bzimage="${O}/${BZIMAGE}" - make_command="${make_command} O=${O}" - elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then - if is_rel_path "${KBUILD_OUTPUT}"; then - KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")" - fi - kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}" - make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}" - fi - - local b2c="${OUTPUT_DIR}/vm2c.py" - - echo "Output directory: ${OUTPUT_DIR}" - - mkdir -p "${OUTPUT_DIR}" - update_kconfig "${kernel_checkout}" "${kconfig_file}" - - recompile_kernel "${kernel_checkout}" "${make_command}" - update_selftests "${kernel_checkout}" "${make_command}" - - if [[ "${build_only}" == "no" ]]; then - if [[ "${update_b2c}" == "no" && ! -f "${b2c}" ]]; then - echo "vm2c script not found in ${b2c}" - update_b2c="yes" - fi - - if [[ "${update_b2c}" == "yes" ]]; then - download $B2C_URL $b2c - chmod +x $b2c - fi - - run_vm "${kernel_checkout}" $b2c "${kernel_bzimage}" "${command}" - if [[ "${debug_shell}" != "yes" ]]; then - echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}" - fi - - exit $(cat ${OUTPUT_DIR}/${EXIT_STATUS_FILE}) + if [[ ! -x $(command -v "${HIDRAW_TEST}") ]]; then + printf "skip: %s not found!" "${HIDRAW_TEST}" + printf " Please build the kselftest hidraw target.\n" + exit "${KSFT_SKIP}" fi } -main "$@" +check_vng() { + local tested_versions + local version + local ok + + tested_versions=("1.36" "1.37") + version="$(vng --version)" + + ok=0 + for tv in "${tested_versions[@]}"; do + if [[ "${version}" == *"${tv}"* ]]; then + ok=1 + break + fi + done + + if [[ ! "${ok}" -eq 1 ]]; then + printf "warning: vng version '%s' has not been tested and may " "${version}" >&2 + printf "not function properly.\n\tThe following versions have been tested: " >&2 + echo "${tested_versions[@]}" >&2 + fi +} + +handle_build() { + if [[ ! "${BUILD}" -eq 1 ]]; then + return + fi + + if [[ ! -d "${KERNEL_CHECKOUT}" ]]; then + echo "-b requires vmtest.sh called from the kernel source tree" >&2 + exit 1 + fi + + pushd "${KERNEL_CHECKOUT}" &>/dev/null + + if ! vng --kconfig --config "${SCRIPT_DIR}"/config; then + die "failed to generate .config for kernel source tree (${KERNEL_CHECKOUT})" + fi + + local vng_args=("-v" "--config" "${SCRIPT_DIR}/config" "--build") + + if [[ -n "${BUILD_HOST}" ]]; then + vng_args+=("--build-host" "${BUILD_HOST}") + fi + + if [[ -n "${BUILD_HOST_PODMAN_CONTAINER_NAME}" ]]; then + vng_args+=("--build-host-exec-prefix" \ + "podman exec -ti ${BUILD_HOST_PODMAN_CONTAINER_NAME}") + fi + + if ! vng "${vng_args[@]}"; then + die "failed to build kernel from source tree (${KERNEL_CHECKOUT})" + fi + + if ! make -j$(nproc) -C "${HID_BPF_PROGS}"; then + die "failed to build HID bpf objects from source tree (${HID_BPF_PROGS})" + fi + + if ! make -j$(nproc) -C "${SCRIPT_DIR}"; then + die "failed to build HID selftests from source tree (${SCRIPT_DIR})" + fi + + popd &>/dev/null +} + +vm_start() { + local logfile=/dev/null + local verbose_opt="" + local kernel_opt="" + local qemu + + qemu=$(command -v "${QEMU}") + + if [[ "${VERBOSE}" -eq 2 ]]; then + verbose_opt="--verbose" + logfile=/dev/stdout + fi + + # If we are running from within the kernel source tree, use the kernel source tree + # as the kernel to boot, otherwise use the currently running kernel. + if [[ "$(realpath "$(pwd)")" == "${KERNEL_CHECKOUT}"* ]]; then + kernel_opt="${KERNEL_CHECKOUT}" + fi + + vng \ + --run \ + ${kernel_opt} \ + ${verbose_opt} \ + --qemu-opts="${QEMU_OPTS}" \ + --qemu="${qemu}" \ + --user root \ + --append "${KERNEL_CMDLINE}" \ + --ssh "${SSH_GUEST_PORT}" \ + --rw &> ${logfile} & + + local vng_pid=$! + local elapsed=0 + + while [[ ! -s "${QEMU_PIDFILE}" ]]; do + if ! kill -0 "${vng_pid}" 2>/dev/null; then + echo "vng process (PID ${vng_pid}) exited early, check logs for details" >&2 + die "failed to boot VM" + fi + + if [[ ${elapsed} -ge ${WAIT_TOTAL} ]]; then + echo "Timed out after ${WAIT_TOTAL} seconds waiting for VM to boot" >&2 + die "failed to boot VM" + fi + + sleep 1 + elapsed=$((elapsed + 1)) + done +} + +vm_wait_for_ssh() { + local i + + i=0 + while true; do + if [[ ${i} -gt ${WAIT_PERIOD_MAX} ]]; then + die "Timed out waiting for guest ssh" + fi + if vm_ssh -- true; then + break + fi + i=$(( i + 1 )) + sleep ${WAIT_PERIOD} + done +} + +vm_mount_bpffs() { + vm_ssh -- mount bpffs -t bpf /sys/fs/bpf +} + +__log_stdin() { + stdbuf -oL awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0; fflush() }' +} + +__log_args() { + echo "$*" | awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0 }' +} + +log() { + local verbose="$1" + shift + + local prefix="$1" + + shift + local redirect= + if [[ ${verbose} -le 0 ]]; then + redirect=/dev/null + else + redirect=/dev/stdout + fi + + if [[ "$#" -eq 0 ]]; then + __log_stdin | tee -a "${LOG}" > ${redirect} + else + __log_args "$@" | tee -a "${LOG}" > ${redirect} + fi +} + +log_setup() { + log $((VERBOSE-1)) "setup" "$@" +} + +log_host() { + local testname=$1 + + shift + log $((VERBOSE-1)) "test:${testname}:host" "$@" +} + +log_guest() { + local testname=$1 + + shift + log ${VERBOSE} "# test:${testname}" "$@" +} + +test_vm_hid_bpf() { + local testname="${FUNCNAME[0]#test_}" + + vm_ssh -- "${HID_BPF_TEST}" \ + 2>&1 | log_guest "${testname}" + + return ${PIPESTATUS[0]} +} + +test_vm_hidraw() { + local testname="${FUNCNAME[0]#test_}" + + vm_ssh -- "${HIDRAW_TEST}" \ + 2>&1 | log_guest "${testname}" + + return ${PIPESTATUS[0]} +} + +test_vm_pytest() { + local testname="${FUNCNAME[0]#test_}" + + shift + + vm_ssh -- pytest ${SCRIPT_DIR}/tests --color=yes "$@" \ + 2>&1 | log_guest "${testname}" + + return ${PIPESTATUS[0]} +} + +run_test() { + local vm_oops_cnt_before + local vm_warn_cnt_before + local vm_oops_cnt_after + local vm_warn_cnt_after + local name + local rc + + vm_oops_cnt_before=$(vm_ssh -- dmesg | grep -c -i 'Oops') + vm_error_cnt_before=$(vm_ssh -- dmesg --level=err | wc -l) + + name=$(echo "${1}" | awk '{ print $1 }') + eval test_"${name}" "$@" + rc=$? + + vm_oops_cnt_after=$(vm_ssh -- dmesg | grep -i 'Oops' | wc -l) + if [[ ${vm_oops_cnt_after} -gt ${vm_oops_cnt_before} ]]; then + echo "FAIL: kernel oops detected on vm" | log_host "${name}" + rc=$KSFT_FAIL + fi + + vm_error_cnt_after=$(vm_ssh -- dmesg --level=err | wc -l) + if [[ ${vm_error_cnt_after} -gt ${vm_error_cnt_before} ]]; then + echo "FAIL: kernel error detected on vm" | log_host "${name}" + vm_ssh -- dmesg --level=err | log_host "${name}" + rc=$KSFT_FAIL + fi + + return "${rc}" +} + +QEMU="qemu-system-$(uname -m)" + +while getopts :hvsbq:H:p: o +do + case $o in + v) VERBOSE=$((VERBOSE+1));; + s) SHELL_MODE=1;; + b) BUILD=1;; + q) QEMU=$OPTARG;; + H) BUILD_HOST=$OPTARG;; + p) BUILD_HOST_PODMAN_CONTAINER_NAME=$OPTARG;; + h|*) usage;; + esac +done +shift $((OPTIND-1)) + +trap cleanup EXIT + +PARAMS="" + +if [[ ${#} -eq 0 ]]; then + ARGS=("${TEST_NAMES[@]}") +else + ARGS=() + COUNT=0 + for arg in $@; do + COUNT=$((COUNT+1)) + if [[ x"$arg" == x"--" ]]; then + break + fi + ARGS+=($arg) + done + shift $COUNT + PARAMS="$@" +fi + +if [[ "${SHELL_MODE}" -eq 0 ]]; then + check_args "${ARGS[@]}" + echo "1..${#ARGS[@]}" +fi +check_deps +check_vng +handle_build + +log_setup "Booting up VM" +vm_start +vm_wait_for_ssh +vm_mount_bpffs +log_setup "VM booted up" + +if [[ "${SHELL_MODE}" -eq 1 ]]; then + log_setup "Starting interactive shell in VM" + echo "Starting shell in VM. Use 'exit' to quit and shutdown the VM." + CURRENT_DIR="$(pwd)" + vm_ssh -t -- "cd '${CURRENT_DIR}' && exec bash -l" + exit "$KSFT_PASS" +fi + +cnt_pass=0 +cnt_fail=0 +cnt_skip=0 +cnt_total=0 +for arg in "${ARGS[@]}"; do + run_test "${arg}" "${PARAMS}" + rc=$? + if [[ ${rc} -eq $KSFT_PASS ]]; then + cnt_pass=$(( cnt_pass + 1 )) + echo "ok ${cnt_total} ${arg}" + elif [[ ${rc} -eq $KSFT_SKIP ]]; then + cnt_skip=$(( cnt_skip + 1 )) + echo "ok ${cnt_total} ${arg} # SKIP" + elif [[ ${rc} -eq $KSFT_FAIL ]]; then + cnt_fail=$(( cnt_fail + 1 )) + echo "not ok ${cnt_total} ${arg} # exit=$rc" + fi + cnt_total=$(( cnt_total + 1 )) +done + +echo "SUMMARY: PASS=${cnt_pass} SKIP=${cnt_skip} FAIL=${cnt_fail}" +echo "Log: ${LOG}" + +if [ $((cnt_pass + cnt_skip)) -eq ${cnt_total} ]; then + exit "$KSFT_PASS" +else + exit "$KSFT_FAIL" +fi From 2cb165f11e13a0c93ec055071b48532098add04a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:36 +0200 Subject: [PATCH 047/295] selftests/hid: hidraw: add more coverage for hidraw ioctls JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit bb6c861a290f2d6b3df2b176d2c2491f1a242265 Author: Benjamin Tissoires Date: Fri Sep 12 18:58:49 2025 +0200 selftests/hid: hidraw: add more coverage for hidraw ioctls Try to ensure all ioctls are having at least one test. Most of the scaffholding has been generated by claude-4-sonnet and then carefully reviewed. Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/hid_common.h | 6 + tools/testing/selftests/hid/hidraw.c | 346 +++++++++++++++++++++++ 2 files changed, 352 insertions(+) diff --git a/tools/testing/selftests/hid/hid_common.h b/tools/testing/selftests/hid/hid_common.h index f77f69c6657d..8085519c47cb 100644 --- a/tools/testing/selftests/hid/hid_common.h +++ b/tools/testing/selftests/hid/hid_common.h @@ -230,6 +230,12 @@ static int uhid_event(struct __test_metadata *_metadata, int fd) break; case UHID_SET_REPORT: UHID_LOG("UHID_SET_REPORT from uhid-dev"); + + answer.type = UHID_SET_REPORT_REPLY; + answer.u.set_report_reply.id = ev.u.set_report.id; + answer.u.set_report_reply.err = 0; /* success */ + + uhid_write(_metadata, fd, &answer); break; default: TH_LOG("Invalid event from uhid-dev: %u", ev.type); diff --git a/tools/testing/selftests/hid/hidraw.c b/tools/testing/selftests/hid/hidraw.c index 821db37ba4bb..6d61d03e2ef0 100644 --- a/tools/testing/selftests/hid/hidraw.c +++ b/tools/testing/selftests/hid/hidraw.c @@ -2,6 +2,9 @@ /* Copyright (c) 2022-2024 Red Hat */ #include "hid_common.h" +#include +#include +#include /* for older kernels */ #ifndef HIDIOCREVOKE @@ -215,6 +218,349 @@ TEST_F(hidraw, write_event_revoked) pthread_mutex_unlock(&uhid_output_mtx); } +/* + * Test HIDIOCGRDESCSIZE ioctl to get report descriptor size + */ +TEST_F(hidraw, ioctl_rdescsize) +{ + int desc_size = 0; + int err; + + /* call HIDIOCGRDESCSIZE ioctl */ + err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESCSIZE ioctl failed"); + + /* verify the size matches our test report descriptor */ + ASSERT_EQ(desc_size, sizeof(rdesc)) + TH_LOG("expected size %zu, got %d", sizeof(rdesc), desc_size); +} + +/* + * Test HIDIOCGRDESC ioctl to get report descriptor data + */ +TEST_F(hidraw, ioctl_rdesc) +{ + struct hidraw_report_descriptor desc; + int err; + + /* get the full report descriptor */ + desc.size = sizeof(rdesc); + err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed"); + + /* verify the descriptor data matches our test descriptor */ + ASSERT_EQ(memcmp(desc.value, rdesc, sizeof(rdesc)), 0) + TH_LOG("report descriptor data mismatch"); +} + +/* + * Test HIDIOCGRDESC ioctl with smaller buffer size + */ +TEST_F(hidraw, ioctl_rdesc_small_buffer) +{ + struct hidraw_report_descriptor desc; + int err; + size_t small_size = sizeof(rdesc) / 2; /* request half the descriptor size */ + + /* get partial report descriptor */ + desc.size = small_size; + err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed with small buffer"); + + /* verify we got the first part of the descriptor */ + ASSERT_EQ(memcmp(desc.value, rdesc, small_size), 0) + TH_LOG("partial report descriptor data mismatch"); +} + +/* + * Test HIDIOCGRAWINFO ioctl to get device information + */ +TEST_F(hidraw, ioctl_rawinfo) +{ + struct hidraw_devinfo devinfo; + int err; + + /* get device info */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWINFO, &devinfo); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRAWINFO ioctl failed"); + + /* verify device info matches our test setup */ + ASSERT_EQ(devinfo.bustype, BUS_USB) + TH_LOG("expected bustype 0x03, got 0x%x", devinfo.bustype); + ASSERT_EQ(devinfo.vendor, 0x0001) + TH_LOG("expected vendor 0x0001, got 0x%x", devinfo.vendor); + ASSERT_EQ(devinfo.product, 0x0a37) + TH_LOG("expected product 0x0a37, got 0x%x", devinfo.product); +} + +/* + * Test HIDIOCGFEATURE ioctl to get feature report + */ +TEST_F(hidraw, ioctl_gfeature) +{ + __u8 buf[10] = {0}; + int err; + + /* set report ID 1 in first byte */ + buf[0] = 1; + + /* get feature report */ + err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf); + ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGFEATURE ioctl failed, got %d", err); + + /* verify we got the expected feature data */ + ASSERT_EQ(buf[0], feature_data[0]) + TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]); + ASSERT_EQ(buf[1], feature_data[1]) + TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]); +} + +/* + * Test HIDIOCGFEATURE ioctl with invalid report ID + */ +TEST_F(hidraw, ioctl_gfeature_invalid) +{ + __u8 buf[10] = {0}; + int err; + + /* set invalid report ID (not 1) */ + buf[0] = 2; + + /* try to get feature report */ + err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE should have failed with invalid report ID"); + ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno); +} + +/* + * Test HIDIOCSFEATURE ioctl to set feature report + */ +TEST_F(hidraw, ioctl_sfeature) +{ + __u8 buf[10] = {0}; + int err; + + /* prepare feature report data */ + buf[0] = 1; /* report ID */ + buf[1] = 0x42; + buf[2] = 0x24; + + /* set feature report */ + err = ioctl(self->hidraw_fd, HIDIOCSFEATURE(3), buf); + ASSERT_EQ(err, 3) TH_LOG("HIDIOCSFEATURE ioctl failed, got %d", err); + + /* + * Note: The uhid mock doesn't validate the set report data, + * so we just verify the ioctl succeeds + */ +} + +/* + * Test HIDIOCGINPUT ioctl to get input report + */ +TEST_F(hidraw, ioctl_ginput) +{ + __u8 buf[10] = {0}; + int err; + + /* set report ID 1 in first byte */ + buf[0] = 1; + + /* get input report */ + err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf); + ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGINPUT ioctl failed, got %d", err); + + /* verify we got the expected input data */ + ASSERT_EQ(buf[0], feature_data[0]) + TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]); + ASSERT_EQ(buf[1], feature_data[1]) + TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]); +} + +/* + * Test HIDIOCGINPUT ioctl with invalid report ID + */ +TEST_F(hidraw, ioctl_ginput_invalid) +{ + __u8 buf[10] = {0}; + int err; + + /* set invalid report ID (not 1) */ + buf[0] = 2; + + /* try to get input report */ + err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGINPUT should have failed with invalid report ID"); + ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno); +} + +/* + * Test HIDIOCSINPUT ioctl to set input report + */ +TEST_F(hidraw, ioctl_sinput) +{ + __u8 buf[10] = {0}; + int err; + + /* prepare input report data */ + buf[0] = 1; /* report ID */ + buf[1] = 0x55; + buf[2] = 0xAA; + + /* set input report */ + err = ioctl(self->hidraw_fd, HIDIOCSINPUT(3), buf); + ASSERT_EQ(err, 3) TH_LOG("HIDIOCSINPUT ioctl failed, got %d", err); + + /* + * Note: The uhid mock doesn't validate the set report data, + * so we just verify the ioctl succeeds + */ +} + +/* + * Test HIDIOCGOUTPUT ioctl to get output report + */ +TEST_F(hidraw, ioctl_goutput) +{ + __u8 buf[10] = {0}; + int err; + + /* set report ID 1 in first byte */ + buf[0] = 1; + + /* get output report */ + err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf); + ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGOUTPUT ioctl failed, got %d", err); + + /* verify we got the expected output data */ + ASSERT_EQ(buf[0], feature_data[0]) + TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]); + ASSERT_EQ(buf[1], feature_data[1]) + TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]); +} + +/* + * Test HIDIOCGOUTPUT ioctl with invalid report ID + */ +TEST_F(hidraw, ioctl_goutput_invalid) +{ + __u8 buf[10] = {0}; + int err; + + /* set invalid report ID (not 1) */ + buf[0] = 2; + + /* try to get output report */ + err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGOUTPUT should have failed with invalid report ID"); + ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno); +} + +/* + * Test HIDIOCSOUTPUT ioctl to set output report + */ +TEST_F(hidraw, ioctl_soutput) +{ + __u8 buf[10] = {0}; + int err; + + /* prepare output report data */ + buf[0] = 1; /* report ID */ + buf[1] = 0x33; + buf[2] = 0xCC; + + /* set output report */ + err = ioctl(self->hidraw_fd, HIDIOCSOUTPUT(3), buf); + ASSERT_EQ(err, 3) TH_LOG("HIDIOCSOUTPUT ioctl failed, got %d", err); + + /* + * Note: The uhid mock doesn't validate the set report data, + * so we just verify the ioctl succeeds + */ +} + +/* + * Test HIDIOCGRAWNAME ioctl to get device name string + */ +TEST_F(hidraw, ioctl_rawname) +{ + char name[256] = {0}; + char expected_name[64]; + int err; + + /* get device name */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(name)), name); + ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWNAME ioctl failed, got %d", err); + + /* construct expected name based on device id */ + snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id); + + /* verify the name matches expected pattern */ + ASSERT_EQ(strcmp(name, expected_name), 0) + TH_LOG("expected name '%s', got '%s'", expected_name, name); +} + +/* + * Test HIDIOCGRAWPHYS ioctl to get device physical address string + */ +TEST_F(hidraw, ioctl_rawphys) +{ + char phys[256] = {0}; + char expected_phys[64]; + int err; + + /* get device physical address */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWPHYS(sizeof(phys)), phys); + ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWPHYS ioctl failed, got %d", err); + + /* construct expected phys based on device id */ + snprintf(expected_phys, sizeof(expected_phys), "%d", self->hid.dev_id); + + /* verify the phys matches expected value */ + ASSERT_EQ(strcmp(phys, expected_phys), 0) + TH_LOG("expected phys '%s', got '%s'", expected_phys, phys); +} + +/* + * Test HIDIOCGRAWUNIQ ioctl to get device unique identifier string + */ +TEST_F(hidraw, ioctl_rawuniq) +{ + char uniq[256] = {0}; + int err; + + /* get device unique identifier */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWUNIQ(sizeof(uniq)), uniq); + ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWUNIQ ioctl failed, got %d", err); + + /* uniq is typically empty in our test setup */ + ASSERT_EQ(strlen(uniq), 0) TH_LOG("expected empty uniq, got '%s'", uniq); +} + +/* + * Test device string ioctls with small buffer sizes + */ +TEST_F(hidraw, ioctl_strings_small_buffer) +{ + char small_buf[8] = {0}; + char expected_name[64]; + int err; + + /* test HIDIOCGRAWNAME with small buffer */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(small_buf)), small_buf); + ASSERT_EQ(err, sizeof(small_buf)) + TH_LOG("HIDIOCGRAWNAME with small buffer failed, got %d", err); + + /* construct expected truncated name */ + snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id); + + /* verify we got truncated name (first 8 chars, no null terminator guaranteed) */ + ASSERT_EQ(strncmp(small_buf, expected_name, sizeof(small_buf)), 0) + TH_LOG("expected truncated name to match first %zu chars", sizeof(small_buf)); + + /* Note: hidraw driver doesn't guarantee null termination when buffer is too small */ +} + int main(int argc, char **argv) { return test_harness_run(argc, argv); From 0cc319a8bf811da60e83ed35c5105e7f88b84014 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:38 +0200 Subject: [PATCH 048/295] selftests/hid: hidraw: forge wrong ioctls and tests them JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 8c62074fa824db0878a039e40f8424c3c3284f42 Author: Benjamin Tissoires Date: Fri Sep 12 18:58:50 2025 +0200 selftests/hid: hidraw: forge wrong ioctls and tests them We also need coverage for when the malicious user is not using the proper ioctls definitions and tries to work around the driver. Most of the scaffholding has been generated by claude-4-sonnet and then carefully reviewed. Suggested-by: Arnd Bergmann Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/hidraw.c | 127 +++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/tools/testing/selftests/hid/hidraw.c b/tools/testing/selftests/hid/hidraw.c index 6d61d03e2ef0..d625772f8b7c 100644 --- a/tools/testing/selftests/hid/hidraw.c +++ b/tools/testing/selftests/hid/hidraw.c @@ -332,6 +332,133 @@ TEST_F(hidraw, ioctl_gfeature_invalid) ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno); } +/* + * Test ioctl with incorrect nr bits + */ +TEST_F(hidraw, ioctl_invalid_nr) +{ + char buf[256] = {0}; + int err; + unsigned int bad_cmd; + + /* + * craft an ioctl command with wrong _IOC_NR bits + */ + bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x00, sizeof(buf)); /* 0 is not valid */ + + /* test the ioctl */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-write with wrong _IOC_NR (0) should have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-write _IOC_NR (0), got errno %d", errno); + + /* + * craft an ioctl command with wrong _IOC_NR bits + */ + bad_cmd = _IOC(_IOC_READ, 'H', 0x00, sizeof(buf)); /* 0 is not valid */ + + /* test the ioctl */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-only with wrong _IOC_NR (0) should have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-only _IOC_NR (0), got errno %d", errno); + + /* also test with bigger number */ + bad_cmd = _IOC(_IOC_READ, 'H', 0x42, sizeof(buf)); /* 0x42 is not valid as well */ + + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-only with wrong _IOC_NR (0x42) should have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-only _IOC_NR (0x42), got errno %d", errno); + + /* also test with bigger number: 0x42 is not valid as well */ + bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x42, sizeof(buf)); + + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-write with wrong _IOC_NR (0x42) should have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-write _IOC_NR (0x42), got errno %d", errno); +} + +/* + * Test ioctl with incorrect type bits + */ +TEST_F(hidraw, ioctl_invalid_type) +{ + char buf[256] = {0}; + int err; + unsigned int bad_cmd; + + /* + * craft an ioctl command with wrong _IOC_TYPE bits + */ + bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'I', 0x01, sizeof(buf)); /* 'I' should be 'H' */ + + /* test the ioctl */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl with wrong _IOC_TYPE (I) should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_NR, got errno %d", errno); +} + +/* + * Test HIDIOCGFEATURE ioctl with incorrect _IOC_DIR bits + */ +TEST_F(hidraw, ioctl_gfeature_invalid_dir) +{ + __u8 buf[10] = {0}; + int err; + unsigned int bad_cmd; + + /* set report ID 1 in first byte */ + buf[0] = 1; + + /* + * craft an ioctl command with wrong _IOC_DIR bits + * HIDIOCGFEATURE should have _IOC_WRITE|_IOC_READ, let's use only _IOC_WRITE + */ + bad_cmd = _IOC(_IOC_WRITE, 'H', 0x07, sizeof(buf)); /* should be _IOC_WRITE|_IOC_READ */ + + /* try to get feature report with wrong direction bits */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno); + + /* also test with only _IOC_READ */ + bad_cmd = _IOC(_IOC_READ, 'H', 0x07, sizeof(buf)); /* should be _IOC_WRITE|_IOC_READ */ + + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno); +} + +/* + * Test read-only ioctl with incorrect _IOC_DIR bits + */ +TEST_F(hidraw, ioctl_readonly_invalid_dir) +{ + char buf[256] = {0}; + int err; + unsigned int bad_cmd; + + /* + * craft an ioctl command with wrong _IOC_DIR bits + * HIDIOCGRAWNAME should have _IOC_READ, let's use _IOC_WRITE + */ + bad_cmd = _IOC(_IOC_WRITE, 'H', 0x04, sizeof(buf)); /* should be _IOC_READ */ + + /* try to get device name with wrong direction bits */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno); + + /* also test with _IOC_WRITE|_IOC_READ */ + bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x04, sizeof(buf)); /* should be only _IOC_READ */ + + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno); +} + /* * Test HIDIOCSFEATURE ioctl to set feature report */ From ae9926ffa43a9263e63d100107ec0cfec11ae367 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:40 +0200 Subject: [PATCH 049/295] HID: hidraw: tighten ioctl command parsing JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 75d5546f60b36900051d75ee623fceccbeb6750c Author: Benjamin Tissoires Date: Fri Sep 12 18:58:51 2025 +0200 HID: hidraw: tighten ioctl command parsing The handling for variable-length ioctl commands in hidraw_ioctl() is rather complex and the check for the data direction is incomplete. Simplify this code by factoring out the various ioctls grouped by dir and size, and using a switch() statement with the size masked out, to ensure the rest of the command is correctly matched. Fixes: 9188e79ec3fd ("HID: add phys and name ioctls to hidraw") Reported-by: Arnd Bergmann Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hidraw.c | 262 +++++++++++++++++++----------------- include/uapi/linux/hidraw.h | 2 + 2 files changed, 143 insertions(+), 121 deletions(-) diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index c887f48756f4..bbd6f23bce78 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -394,15 +394,127 @@ static int hidraw_revoke(struct hidraw_list *list) return 0; } -static long hidraw_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) +static long hidraw_fixed_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd, + void __user *arg) +{ + struct hid_device *hid = dev->hid; + + switch (cmd) { + case HIDIOCGRDESCSIZE: + if (put_user(hid->rsize, (int __user *)arg)) + return -EFAULT; + break; + + case HIDIOCGRDESC: + { + __u32 len; + + if (get_user(len, (int __user *)arg)) + return -EFAULT; + + if (len > HID_MAX_DESCRIPTOR_SIZE - 1) + return -EINVAL; + + if (copy_to_user(arg + offsetof( + struct hidraw_report_descriptor, + value[0]), + hid->rdesc, + min(hid->rsize, len))) + return -EFAULT; + + break; + } + case HIDIOCGRAWINFO: + { + struct hidraw_devinfo dinfo; + + dinfo.bustype = hid->bus; + dinfo.vendor = hid->vendor; + dinfo.product = hid->product; + if (copy_to_user(arg, &dinfo, sizeof(dinfo))) + return -EFAULT; + break; + } + case HIDIOCREVOKE: + { + struct hidraw_list *list = file->private_data; + + if (arg) + return -EINVAL; + + return hidraw_revoke(list); + } + default: + /* + * None of the above ioctls can return -EAGAIN, so + * use it as a marker that we need to check variable + * length ioctls. + */ + return -EAGAIN; + } + + return 0; +} + +static long hidraw_rw_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd, + void __user *user_arg) +{ + int len = _IOC_SIZE(cmd); + + switch (cmd & ~IOCSIZE_MASK) { + case HIDIOCSFEATURE(0): + return hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT); + case HIDIOCGFEATURE(0): + return hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT); + case HIDIOCSINPUT(0): + return hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT); + case HIDIOCGINPUT(0): + return hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT); + case HIDIOCSOUTPUT(0): + return hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT); + case HIDIOCGOUTPUT(0): + return hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT); + } + + return -EINVAL; +} + +static long hidraw_ro_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd, + void __user *user_arg) +{ + struct hid_device *hid = dev->hid; + int len = _IOC_SIZE(cmd); + int field_len; + + switch (cmd & ~IOCSIZE_MASK) { + case HIDIOCGRAWNAME(0): + field_len = strlen(hid->name) + 1; + if (len > field_len) + len = field_len; + return copy_to_user(user_arg, hid->name, len) ? -EFAULT : len; + case HIDIOCGRAWPHYS(0): + field_len = strlen(hid->phys) + 1; + if (len > field_len) + len = field_len; + return copy_to_user(user_arg, hid->phys, len) ? -EFAULT : len; + case HIDIOCGRAWUNIQ(0): + field_len = strlen(hid->uniq) + 1; + if (len > field_len) + len = field_len; + return copy_to_user(user_arg, hid->uniq, len) ? -EFAULT : len; + } + + return -EINVAL; +} + +static long hidraw_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct inode *inode = file_inode(file); unsigned int minor = iminor(inode); - long ret = 0; struct hidraw *dev; struct hidraw_list *list = file->private_data; - void __user *user_arg = (void __user*) arg; + void __user *user_arg = (void __user *)arg; + int ret; down_read(&minors_rwsem); dev = hidraw_table[minor]; @@ -411,124 +523,32 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd, goto out; } - switch (cmd) { - case HIDIOCGRDESCSIZE: - if (put_user(dev->hid->rsize, (int __user *)arg)) - ret = -EFAULT; - break; - - case HIDIOCGRDESC: - { - __u32 len; - - if (get_user(len, (int __user *)arg)) - ret = -EFAULT; - else if (len > HID_MAX_DESCRIPTOR_SIZE - 1) - ret = -EINVAL; - else if (copy_to_user(user_arg + offsetof( - struct hidraw_report_descriptor, - value[0]), - dev->hid->rdesc, - min(dev->hid->rsize, len))) - ret = -EFAULT; - break; - } - case HIDIOCGRAWINFO: - { - struct hidraw_devinfo dinfo; - - dinfo.bustype = dev->hid->bus; - dinfo.vendor = dev->hid->vendor; - dinfo.product = dev->hid->product; - if (copy_to_user(user_arg, &dinfo, sizeof(dinfo))) - ret = -EFAULT; - break; - } - case HIDIOCREVOKE: - { - if (user_arg) - ret = -EINVAL; - else - ret = hidraw_revoke(list); - break; - } - default: - { - struct hid_device *hid = dev->hid; - if (_IOC_TYPE(cmd) != 'H') { - ret = -EINVAL; - break; - } - - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) { - int len = _IOC_SIZE(cmd); - ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT); - break; - } - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) { - int len = _IOC_SIZE(cmd); - ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT); - break; - } - - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSINPUT(0))) { - int len = _IOC_SIZE(cmd); - ret = hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT); - break; - } - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGINPUT(0))) { - int len = _IOC_SIZE(cmd); - ret = hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT); - break; - } - - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSOUTPUT(0))) { - int len = _IOC_SIZE(cmd); - ret = hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT); - break; - } - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGOUTPUT(0))) { - int len = _IOC_SIZE(cmd); - ret = hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT); - break; - } - - /* Begin Read-only ioctls. */ - if (_IOC_DIR(cmd) != _IOC_READ) { - ret = -EINVAL; - break; - } - - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) { - int len = strlen(hid->name) + 1; - if (len > _IOC_SIZE(cmd)) - len = _IOC_SIZE(cmd); - ret = copy_to_user(user_arg, hid->name, len) ? - -EFAULT : len; - break; - } - - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) { - int len = strlen(hid->phys) + 1; - if (len > _IOC_SIZE(cmd)) - len = _IOC_SIZE(cmd); - ret = copy_to_user(user_arg, hid->phys, len) ? - -EFAULT : len; - break; - } - - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWUNIQ(0))) { - int len = strlen(hid->uniq) + 1; - if (len > _IOC_SIZE(cmd)) - len = _IOC_SIZE(cmd); - ret = copy_to_user(user_arg, hid->uniq, len) ? - -EFAULT : len; - break; - } - } - - ret = -ENOTTY; + if (_IOC_TYPE(cmd) != 'H') { + ret = -EINVAL; + goto out; } + + if (_IOC_NR(cmd) > HIDIOCTL_LAST || _IOC_NR(cmd) == 0) { + ret = -ENOTTY; + goto out; + } + + ret = hidraw_fixed_size_ioctl(file, dev, cmd, user_arg); + if (ret != -EAGAIN) + goto out; + + switch (_IOC_DIR(cmd)) { + case (_IOC_READ | _IOC_WRITE): + ret = hidraw_rw_variable_size_ioctl(file, dev, cmd, user_arg); + break; + case _IOC_READ: + ret = hidraw_ro_variable_size_ioctl(file, dev, cmd, user_arg); + break; + default: + /* Any other IOC_DIR is wrong */ + ret = -EINVAL; + } + out: up_read(&minors_rwsem); return ret; diff --git a/include/uapi/linux/hidraw.h b/include/uapi/linux/hidraw.h index d5ee269864e0..ebd701b3c18d 100644 --- a/include/uapi/linux/hidraw.h +++ b/include/uapi/linux/hidraw.h @@ -48,6 +48,8 @@ struct hidraw_devinfo { #define HIDIOCGOUTPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0C, len) #define HIDIOCREVOKE _IOW('H', 0x0D, int) /* Revoke device access */ +#define HIDIOCTL_LAST _IOC_NR(HIDIOCREVOKE) + #define HIDRAW_FIRST_MINOR 0 #define HIDRAW_MAX_DEVICES 64 /* number of reports to buffer */ From bf20576629853109f6d53ae603cc798215deb5b4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:42 +0200 Subject: [PATCH 050/295] HID: core: Change hid_driver to use a const char* for name JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit d1dd75c6500c74b91c5286fd3277710371d3e3ca Author: Rahul Rameshbabu Date: Sat Sep 13 16:12:54 2025 +0000 HID: core: Change hid_driver to use a const char* for name name is never mutated by the core HID stack. Making name a const char* simplifies passing the string from Rust to C. Otherwise, it becomes difficult to pass a 'static lifetime CStr from Rust to a char*, rather than a const char*, due to lack of guarantee that the underlying data of the CStr will not be mutated by the C code. Signed-off-by: Rahul Rameshbabu Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- include/linux/hid.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/hid.h b/include/linux/hid.h index 301e2d35a38d..e1b673ad7457 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -847,7 +847,7 @@ struct hid_usage_id { * zero from them. */ struct hid_driver { - char *name; + const char *name; const struct hid_device_id *id_table; struct list_head dyn_list; From abee9415d11f872c6e2064927ff6220eba011fdb Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:44 +0200 Subject: [PATCH 051/295] HID: intel-thc-hid: intel-quicki2c: support ACPI config for advanced features JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 0b1fca9dce44d0d2a8f4c55df1e4149744934a91 Author: Xinpeng Sun Date: Wed Sep 17 09:53:44 2025 +0800 HID: intel-thc-hid: intel-quicki2c: support ACPI config for advanced features There is a new BIOS enhancement that adds the capability to configure the following two features of I2C subsystem introduced in commit 1ed0b48 ("Intel-thc: Introduce max input size control") and commit 3f2a921 ("Intel-thc: Introduce interrupt delay control"): - Max input size control - Interrupt delay control As BIOS is used for the configuration of these two features, change driver data usage to indicate hardware capability, and add corresponding ACPI configuration support in QuickI2C driver. Signed-off-by: Xinpeng Sun Tested-by: Rui Zhang Reviewed-by: Even Xu Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../intel-quicki2c/pci-quicki2c.c | 39 +++++++++++++++---- .../intel-quicki2c/quicki2c-dev.h | 24 +++++++++++- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c index a2643ae790d6..8433a991e7f4 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c @@ -23,6 +23,7 @@ static struct quicki2c_ddata ptl_ddata = { .max_detect_size = MAX_RX_DETECT_SIZE_PTL, + .max_interrupt_delay = MAX_RX_INTERRUPT_DELAY, }; /* THC QuickI2C ACPI method to get device properties */ @@ -200,6 +201,21 @@ static int quicki2c_get_acpi_resources(struct quicki2c_device *qcdev) return -EOPNOTSUPP; } + if (qcdev->ddata) { + qcdev->i2c_max_frame_size_enable = i2c_config.FSEN; + qcdev->i2c_int_delay_enable = i2c_config.INDE; + + if (i2c_config.FSVL <= qcdev->ddata->max_detect_size) + qcdev->i2c_max_frame_size = i2c_config.FSVL; + else + qcdev->i2c_max_frame_size = qcdev->ddata->max_detect_size; + + if (i2c_config.INDV <= qcdev->ddata->max_interrupt_delay) + qcdev->i2c_int_delay = i2c_config.INDV; + else + qcdev->i2c_int_delay = qcdev->ddata->max_interrupt_delay; + } + return 0; } @@ -441,17 +457,24 @@ static void quicki2c_dma_adv_enable(struct quicki2c_device *qcdev) * max input length <= THC detect capability, enable the feature with device * max input length. */ - if (qcdev->ddata->max_detect_size >= - le16_to_cpu(qcdev->dev_desc.max_input_len)) { - thc_i2c_set_rx_max_size(qcdev->thc_hw, - le16_to_cpu(qcdev->dev_desc.max_input_len)); + if (qcdev->i2c_max_frame_size_enable) { + if (qcdev->i2c_max_frame_size >= + le16_to_cpu(qcdev->dev_desc.max_input_len)) { + thc_i2c_set_rx_max_size(qcdev->thc_hw, + le16_to_cpu(qcdev->dev_desc.max_input_len)); + } else { + dev_warn(qcdev->dev, + "Max frame size is smaller than hid max input length!"); + thc_i2c_set_rx_max_size(qcdev->thc_hw, + le16_to_cpu(qcdev->i2c_max_frame_size)); + } thc_i2c_rx_max_size_enable(qcdev->thc_hw, true); } /* If platform supports interrupt delay feature, enable it with given delay */ - if (qcdev->ddata->interrupt_delay) { + if (qcdev->i2c_int_delay_enable) { thc_i2c_set_rx_int_delay(qcdev->thc_hw, - qcdev->ddata->interrupt_delay); + qcdev->i2c_int_delay * 10); thc_i2c_rx_int_delay_enable(qcdev->thc_hw, true); } } @@ -464,10 +487,10 @@ static void quicki2c_dma_adv_enable(struct quicki2c_device *qcdev) */ static void quicki2c_dma_adv_disable(struct quicki2c_device *qcdev) { - if (qcdev->ddata->max_detect_size) + if (qcdev->i2c_max_frame_size_enable) thc_i2c_rx_max_size_enable(qcdev->thc_hw, false); - if (qcdev->ddata->interrupt_delay) + if (qcdev->i2c_int_delay_enable) thc_i2c_rx_int_delay_enable(qcdev->thc_hw, false); } diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h index 4e60a7de4727..2cb5471a8133 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h @@ -40,6 +40,8 @@ /* PTL Max packet size detection capability is 255 Bytes */ #define MAX_RX_DETECT_SIZE_PTL 255 +/* Max interrupt delay capability is 2.56ms */ +#define MAX_RX_INTERRUPT_DELAY 256 /* Default interrupt delay is 1ms, suitable for most devices */ #define DEFAULT_INTERRUPT_DELAY_US (1 * USEC_PER_MSEC) @@ -103,6 +105,10 @@ struct quicki2c_subip_acpi_parameter { * @HMTD: High Speed Mode Plus (3.4Mbits/sec) Serial Data Line Transmit HOLD Period * @HMRD: High Speed Mode Plus (3.4Mbits/sec) Serial Data Line Receive HOLD Period * @HMSL: Maximum length (in ic_clk_cycles) of suppressed spikes in High Speed Mode + * @FSEN: Maximum Frame Size Feature Enable Control + * @FSVL: Maximum Frame Size Value (unit in Bytes) + * @INDE: Interrupt Delay Feature Enable Control + * @INDV: Interrupt Delay Value (unit in 10 us) * * Those properties get from QUICKI2C_ACPI_METHOD_NAME_ISUB method, used for * I2C timing configure. @@ -129,17 +135,22 @@ struct quicki2c_subip_acpi_config { u64 HMTD; u64 HMRD; u64 HMSL; + + u64 FSEN; + u64 FSVL; + u64 INDE; + u64 INDV; u8 reserved; }; /** * struct quicki2c_ddata - Driver specific data for quicki2c device * @max_detect_size: Identify max packet size detect for rx - * @interrupt_delay: Identify interrupt detect delay for rx + * @interrupt_delay: Identify max interrupt detect delay for rx */ struct quicki2c_ddata { u32 max_detect_size; - u32 interrupt_delay; + u32 max_interrupt_delay; }; struct device; @@ -172,6 +183,10 @@ struct acpi_device; * @report_len: The length of input/output report packet * @reset_ack_wq: Workqueue for waiting reset response from device * @reset_ack: Indicate reset response received or not + * @i2c_max_frame_size_enable: Indicate max frame size feature enabled or not + * @i2c_max_frame_size: Max RX frame size (unit in Bytes) + * @i2c_int_delay_enable: Indicate interrupt delay feature enabled or not + * @i2c_int_delay: Interrupt detection delay value (unit in 10 us) */ struct quicki2c_device { struct device *dev; @@ -202,6 +217,11 @@ struct quicki2c_device { wait_queue_head_t reset_ack_wq; bool reset_ack; + + u32 i2c_max_frame_size_enable; + u32 i2c_max_frame_size; + u32 i2c_int_delay_enable; + u32 i2c_int_delay; }; #endif /* _QUICKI2C_DEV_H_ */ From 62f40ba9b001d9f350b60b1d112ba0f052ec7842 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:46 +0200 Subject: [PATCH 052/295] HID: playstation: Update SP preamp gain comment line JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit d335230cb29b7f578dba0b770d97cd320c83892b Author: Cristian Ciocaltea Date: Tue Sep 23 00:29:40 2025 +0300 HID: playstation: Update SP preamp gain comment line Adjusting speaker pre-amp gain to 0x2 indicates +6dB, hence update the related comment line accordingly. Suggested-by: Roderick Colenbrander Signed-off-by: Cristian Ciocaltea Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 27433d52539e..40d259adc2e2 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -1372,7 +1372,7 @@ static void dualsense_output_worker(struct work_struct *work) */ common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE; common->speaker_volume = 0x64; - /* Set SP preamp gain to ~30% */ + /* Set SP preamp gain to +6dB */ common->valid_flag1 = DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE; common->audio_control2 = FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN, 0x2); From 1e20e65ca0db78d74780fb841cf7c881e57678bf Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:49 +0200 Subject: [PATCH 053/295] HID: playstation: Silence sparse warnings for locking context imbalances JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 3969f77f5dd5c69d513a33f350128e61e0e1ab00 Author: Cristian Ciocaltea Date: Tue Sep 23 00:29:41 2025 +0300 HID: playstation: Silence sparse warnings for locking context imbalances It seems sparse has some difficulties with guard() handling or when mixing it with scoped_guard(), as it shows a bunch lock related warnings: hid-playstation.c:1230:32: warning: context imbalance in 'dualsense_player_led_set_brightness' - wrong count at exit hid-playstation.c:1414:12: warning: context imbalance in 'dualsense_parse_report' - wrong count at exit hid-playstation.c:1615:12: warning: context imbalance in 'dualsense_play_effect' - different lock contexts for basic block hid-playstation.c:1668:13: warning: context imbalance in 'dualsense_set_lightbar' - wrong count at exit hid-playstation.c:1680:13: warning: context imbalance in 'dualsense_set_player_leds' - wrong count at exit hid-playstation.c:2185:33: warning: context imbalance in 'dualshock4_led_set_blink' - wrong count at exit hid-playstation.c:2226:33: warning: context imbalance in 'dualshock4_led_set_brightness' - wrong count at exit hid-playstation.c:2596:12: warning: context imbalance in 'dualshock4_play_effect' - different lock contexts for basic block hid-playstation.c:2634:13: warning: context imbalance in 'dualshock4_set_bt_poll_interval' - wrong count at exit hid-playstation.c:2642:13: warning: context imbalance in 'dualshock4_set_default_lightbar_colors' - wrong count at exit Silent them by switching from guard() to scoped_guard() in {dualsense|dualshock4}_schedule_work(). Reported-by: Benjamin Tissoires Suggested-by: Benjamin Tissoires Signed-off-by: Cristian Ciocaltea Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 40d259adc2e2..ec577a221178 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -1274,9 +1274,10 @@ static void dualsense_init_output_report(struct dualsense *ds, static inline void dualsense_schedule_work(struct dualsense *ds) { - guard(spinlock_irqsave)(&ds->base.lock); - if (ds->output_worker_initialized) - schedule_work(&ds->output_worker); + /* Using scoped_guard() instead of guard() to make sparse happy */ + scoped_guard(spinlock_irqsave, &ds->base.lock) + if (ds->output_worker_initialized) + schedule_work(&ds->output_worker); } /* @@ -2628,9 +2629,10 @@ static void dualshock4_remove(struct ps_device *ps_dev) static inline void dualshock4_schedule_work(struct dualshock4 *ds4) { - guard(spinlock_irqsave)(&ds4->base.lock); - if (ds4->output_worker_initialized) - schedule_work(&ds4->output_worker); + /* Using scoped_guard() instead of guard() to make sparse happy */ + scoped_guard(spinlock_irqsave, &ds4->base.lock) + if (ds4->output_worker_initialized) + schedule_work(&ds4->output_worker); } static void dualshock4_set_bt_poll_interval(struct dualshock4 *ds4, u8 interval) From d33c3791d75e228d3d68b97f8488189cbbda1200 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:03:50 +0200 Subject: [PATCH 054/295] HID: playstation: Switch to scoped_guard() in {dualsense|dualshock4}_output_worker() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 8aa035a8407f6b6e999afa33839e38267fdc8790 Author: Cristian Ciocaltea Date: Tue Sep 23 00:29:42 2025 +0300 HID: playstation: Switch to scoped_guard() in {dualsense|dualshock4}_output_worker() Those functions were initially excepted from using the scoped_guard() infrastructure as they contain too many long statements, while adding yet another level of indentation seemed to lower readability without bringing an immediate benefit. However, consistency should be more important, hence do the switch and get rid of the remaining explicit acquires & releases of the spinlocks. Signed-off-by: Cristian Ciocaltea Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 258 +++++++++++++++++----------------- 1 file changed, 130 insertions(+), 128 deletions(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index ec577a221178..128aa6abd10b 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -1308,107 +1308,112 @@ static void dualsense_output_worker(struct work_struct *work) struct dualsense *ds = container_of(work, struct dualsense, output_worker); struct dualsense_output_report report; struct dualsense_output_report_common *common; - unsigned long flags; dualsense_init_output_report(ds, &report, ds->output_report_dmabuf); common = report.common; - spin_lock_irqsave(&ds->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds->base.lock) { + if (ds->update_rumble) { + /* Select classic rumble style haptics and enable it. */ + common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT; + if (ds->use_vibration_v2) + common->valid_flag2 |= DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2; + else + common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION; + common->motor_left = ds->motor_left; + common->motor_right = ds->motor_right; + ds->update_rumble = false; + } - if (ds->update_rumble) { - /* Select classic rumble style haptics and enable it. */ - common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT; - if (ds->use_vibration_v2) - common->valid_flag2 |= DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2; - else - common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION; - common->motor_left = ds->motor_left; - common->motor_right = ds->motor_right; - ds->update_rumble = false; - } + if (ds->update_lightbar) { + common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE; + common->lightbar_red = ds->lightbar_red; + common->lightbar_green = ds->lightbar_green; + common->lightbar_blue = ds->lightbar_blue; - if (ds->update_lightbar) { - common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE; - common->lightbar_red = ds->lightbar_red; - common->lightbar_green = ds->lightbar_green; - common->lightbar_blue = ds->lightbar_blue; + ds->update_lightbar = false; + } - ds->update_lightbar = false; - } + if (ds->update_player_leds) { + common->valid_flag1 |= + DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE; + common->player_leds = ds->player_leds_state; - if (ds->update_player_leds) { - common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE; - common->player_leds = ds->player_leds_state; + ds->update_player_leds = false; + } - ds->update_player_leds = false; - } + if (ds->plugged_state != ds->prev_plugged_state) { + u8 val = ds->plugged_state & DS_STATUS1_HP_DETECT; - if (ds->plugged_state != ds->prev_plugged_state) { - u8 val = ds->plugged_state & DS_STATUS1_HP_DETECT; - - if (val != (ds->prev_plugged_state & DS_STATUS1_HP_DETECT)) { - common->valid_flag0 = DS_OUTPUT_VALID_FLAG0_AUDIO_CONTROL_ENABLE; - /* - * _--------> Output path setup in audio_flag0 - * / _------> Headphone (HP) Left channel sink - * | / _----> Headphone (HP) Right channel sink - * | | / _--> Internal Speaker (SP) sink - * | | | / - * | | | | L/R - Left/Right channel source - * 0 L-R X X - Unrouted (muted) channel source - * 1 L-L X - * 2 L-L R - * 3 X-X R - */ - if (val) { - /* Mute SP and route L+R channels to HP */ - common->audio_control = 0; - } else { - /* Mute HP and route R channel to SP */ - common->audio_control = - FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS_OUTPUT_PATH_SEL, 0x3); + if (val != (ds->prev_plugged_state & DS_STATUS1_HP_DETECT)) { + common->valid_flag0 = DS_OUTPUT_VALID_FLAG0_AUDIO_CONTROL_ENABLE; /* - * Set SP hardware volume to 100%. - * Note the accepted range seems to be [0x3d..0x64] + * _--------> Output path setup in audio_flag0 + * / _------> Headphone (HP) Left channel sink + * | / _----> Headphone (HP) Right channel sink + * | | / _--> Internal Speaker (SP) sink + * | | | / + * | | | | L/R - Left/Right channel source + * 0 L-R X X - Unrouted (muted) channel source + * 1 L-L X + * 2 L-L R + * 3 X-X R */ - common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE; - common->speaker_volume = 0x64; - /* Set SP preamp gain to +6dB */ - common->valid_flag1 = DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE; - common->audio_control2 = - FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN, 0x2); + if (val) { + /* Mute SP and route L+R channels to HP */ + common->audio_control = 0; + } else { + /* Mute HP and route R channel to SP */ + common->audio_control = + FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS_OUTPUT_PATH_SEL, + 0x3); + /* + * Set SP hardware volume to 100%. + * Note the accepted range seems to be [0x3d..0x64] + */ + common->valid_flag0 |= + DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE; + common->speaker_volume = 0x64; + /* Set SP preamp gain to +6dB */ + common->valid_flag1 = + DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE; + common->audio_control2 = + FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN, + 0x2); + } + + input_report_switch(ds->jack, SW_HEADPHONE_INSERT, val); } - input_report_switch(ds->jack, SW_HEADPHONE_INSERT, val); + val = ds->plugged_state & DS_STATUS1_MIC_DETECT; + if (val != (ds->prev_plugged_state & DS_STATUS1_MIC_DETECT)) + input_report_switch(ds->jack, SW_MICROPHONE_INSERT, val); + + input_sync(ds->jack); + ds->prev_plugged_state = ds->plugged_state; } - val = ds->plugged_state & DS_STATUS1_MIC_DETECT; - if (val != (ds->prev_plugged_state & DS_STATUS1_MIC_DETECT)) - input_report_switch(ds->jack, SW_MICROPHONE_INSERT, val); + if (ds->update_mic_mute) { + common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE; + common->mute_button_led = ds->mic_muted; - input_sync(ds->jack); - ds->prev_plugged_state = ds->plugged_state; - } + if (ds->mic_muted) { + /* Disable microphone */ + common->valid_flag1 |= + DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE; + common->power_save_control |= DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE; + } else { + /* Enable microphone */ + common->valid_flag1 |= + DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE; + common->power_save_control &= + ~DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE; + } - if (ds->update_mic_mute) { - common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE; - common->mute_button_led = ds->mic_muted; - - if (ds->mic_muted) { - /* Disable microphone */ - common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE; - common->power_save_control |= DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE; - } else { - /* Enable microphone */ - common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE; - common->power_save_control &= ~DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE; + ds->update_mic_mute = false; } - - ds->update_mic_mute = false; } - spin_unlock_irqrestore(&ds->base.lock, flags); - dualsense_send_output_report(ds, &report); } @@ -2266,61 +2271,58 @@ static void dualshock4_output_worker(struct work_struct *work) struct dualshock4 *ds4 = container_of(work, struct dualshock4, output_worker); struct dualshock4_output_report report; struct dualshock4_output_report_common *common; - unsigned long flags; dualshock4_init_output_report(ds4, &report, ds4->output_report_dmabuf); common = report.common; - spin_lock_irqsave(&ds4->base.lock, flags); - - /* - * Some 3rd party gamepads expect updates to rumble and lightbar - * together, and setting one may cancel the other. - * - * Let's maximise compatibility by always sending rumble and lightbar - * updates together, even when only one has been scheduled, resulting - * in: - * - * ds4->valid_flag0 >= 0x03 - * - * Hopefully this will maximise compatibility with third-party pads. - * - * Any further update bits, such as 0x04 for lightbar blinking, will - * be or'd on top of this like before. - */ - if (ds4->update_rumble || ds4->update_lightbar) { - ds4->update_rumble = true; /* 0x01 */ - ds4->update_lightbar = true; /* 0x02 */ - } - - if (ds4->update_rumble) { - /* Select classic rumble style haptics and enable it. */ - common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_MOTOR; - common->motor_left = ds4->motor_left; - common->motor_right = ds4->motor_right; - ds4->update_rumble = false; - } - - if (ds4->update_lightbar) { - common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED; - /* Compatible behavior with hid-sony, which used a dummy global LED to - * allow enabling/disabling the lightbar. The global LED maps to - * lightbar_enabled. + scoped_guard(spinlock_irqsave, &ds4->base.lock) { + /* + * Some 3rd party gamepads expect updates to rumble and lightbar + * together, and setting one may cancel the other. + * + * Let's maximise compatibility by always sending rumble and lightbar + * updates together, even when only one has been scheduled, resulting + * in: + * + * ds4->valid_flag0 >= 0x03 + * + * Hopefully this will maximise compatibility with third-party pads. + * + * Any further update bits, such as 0x04 for lightbar blinking, will + * be or'd on top of this like before. */ - common->lightbar_red = ds4->lightbar_enabled ? ds4->lightbar_red : 0; - common->lightbar_green = ds4->lightbar_enabled ? ds4->lightbar_green : 0; - common->lightbar_blue = ds4->lightbar_enabled ? ds4->lightbar_blue : 0; - ds4->update_lightbar = false; - } + if (ds4->update_rumble || ds4->update_lightbar) { + ds4->update_rumble = true; /* 0x01 */ + ds4->update_lightbar = true; /* 0x02 */ + } - if (ds4->update_lightbar_blink) { - common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED_BLINK; - common->lightbar_blink_on = ds4->lightbar_blink_on; - common->lightbar_blink_off = ds4->lightbar_blink_off; - ds4->update_lightbar_blink = false; - } + if (ds4->update_rumble) { + /* Select classic rumble style haptics and enable it. */ + common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_MOTOR; + common->motor_left = ds4->motor_left; + common->motor_right = ds4->motor_right; + ds4->update_rumble = false; + } - spin_unlock_irqrestore(&ds4->base.lock, flags); + if (ds4->update_lightbar) { + common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED; + /* Compatible behavior with hid-sony, which used a dummy global LED to + * allow enabling/disabling the lightbar. The global LED maps to + * lightbar_enabled. + */ + common->lightbar_red = ds4->lightbar_enabled ? ds4->lightbar_red : 0; + common->lightbar_green = ds4->lightbar_enabled ? ds4->lightbar_green : 0; + common->lightbar_blue = ds4->lightbar_enabled ? ds4->lightbar_blue : 0; + ds4->update_lightbar = false; + } + + if (ds4->update_lightbar_blink) { + common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED_BLINK; + common->lightbar_blink_on = ds4->lightbar_blink_on; + common->lightbar_blink_off = ds4->lightbar_blink_off; + ds4->update_lightbar_blink = false; + } + } /* Bluetooth packets need additional flags as well as a CRC in the last 4 bytes. */ if (report.bt) { From d1cb0f5234c8e9a927ca78ce0a112ebf51542ddb Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 9 Jun 2026 09:41:09 +0200 Subject: [PATCH 055/295] HID: lg-g15 - Add support for Logitech G13. JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit ba00292b965d41dca82fc7d238ff9b1c77f2b98c Author: Leo L. Schwab Date: Wed Sep 17 16:05:33 2025 -0700 HID: lg-g15 - Add support for Logitech G13. The Logitech G13 is a gaming keypad with general-purpose macro keys, four LED-backlit macro preset keys, five "menu" keys, backlight toggle key, an analog thumbstick, RGB LED backlight, and a monochrome LCD display. Support input event generation for all keys and the thumbstick, and expose all LEDs. Signed-off-by: Leo L. Schwab Reviewed-by: Hans de Goede Tested-by: Kate Hsuan Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-lg-g15.c | 446 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 432 insertions(+), 15 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 52ae7c29f9e0..11b972636b8e 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -879,6 +879,7 @@ #define USB_DEVICE_ID_LOGITECH_DUAL_ACTION 0xc216 #define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2 0xc218 #define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2 0xc219 +#define USB_DEVICE_ID_LOGITECH_G13 0xc21c #define USB_DEVICE_ID_LOGITECH_G15_LCD 0xc222 #define USB_DEVICE_ID_LOGITECH_G11 0xc225 #define USB_DEVICE_ID_LOGITECH_G15_V2_LCD 0xc227 diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c index f8605656257b..7b8df2d5b57f 100644 --- a/drivers/hid/hid-lg-g15.c +++ b/drivers/hid/hid-lg-g15.c @@ -26,7 +26,21 @@ #define LG_G510_FEATURE_BACKLIGHT_RGB 0x05 #define LG_G510_FEATURE_POWER_ON_RGB 0x06 +#define LG_G13_INPUT_REPORT 0x01 +#define LG_G13_FEATURE_M_KEYS_LEDS 0x05 +#define LG_G13_FEATURE_BACKLIGHT_RGB 0x07 +#define LG_G13_BACKLIGHT_HW_ON_BIT 23 + +/** + * g13_input_report.keybits[] is not 32-bit aligned, so we can't use the bitops macros. + * + * @ary: Pointer to array of u8s + * @b: Bit index into ary, LSB first. Not range checked. + */ +#define TEST_BIT(ary, b) ((1 << ((b) & 7)) & (ary)[(b) >> 3]) + enum lg_g15_model { + LG_G13, LG_G15, LG_G15_V2, LG_G510, @@ -45,6 +59,12 @@ enum lg_g15_led_type { LG_G15_LED_MAX }; +struct g13_input_report { + u8 report_id; /* Report ID is always set to 1. */ + u8 joy_x, joy_y; + u8 keybits[5]; +}; + struct lg_g15_led { union { struct led_classdev cdev; @@ -63,12 +83,188 @@ struct lg_g15_data { struct mutex mutex; struct work_struct work; struct input_dev *input; + struct input_dev *input_js; /* Separate joystick device for G13. */ struct hid_device *hdev; enum lg_g15_model model; struct lg_g15_led leds[LG_G15_LED_MAX]; bool game_mode_enabled; + bool backlight_disabled; /* true == HW backlight toggled *OFF* */ }; +/********* G13 LED functions ***********/ +/* + * G13 retains no state across power cycles, and always powers up with the backlight on, + * color #5AFF6E, all macro key LEDs off. + */ +static int lg_g13_get_leds_state(struct lg_g15_data *g15) +{ + u8 * const tbuf = g15->transfer_buf; + int ret, high; + + /* RGB backlight. */ + ret = hid_hw_raw_request(g15->hdev, LG_G13_FEATURE_BACKLIGHT_RGB, + tbuf, 5, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (ret != 5) { + hid_err(g15->hdev, "Error getting backlight brightness: %d\n", ret); + return (ret < 0) ? ret : -EIO; + } + + /* Normalize RGB intensities against the highest component. */ + high = max3(tbuf[1], tbuf[2], tbuf[3]); + if (high) { + g15->leds[LG_G15_KBD_BRIGHTNESS].red = + DIV_ROUND_CLOSEST(tbuf[1] * 255, high); + g15->leds[LG_G15_KBD_BRIGHTNESS].green = + DIV_ROUND_CLOSEST(tbuf[2] * 255, high); + g15->leds[LG_G15_KBD_BRIGHTNESS].blue = + DIV_ROUND_CLOSEST(tbuf[3] * 255, high); + g15->leds[LG_G15_KBD_BRIGHTNESS].brightness = high; + } else { + g15->leds[LG_G15_KBD_BRIGHTNESS].red = 255; + g15->leds[LG_G15_KBD_BRIGHTNESS].green = 255; + g15->leds[LG_G15_KBD_BRIGHTNESS].blue = 255; + g15->leds[LG_G15_KBD_BRIGHTNESS].brightness = 0; + } + + /* Macro LEDs. */ + ret = hid_hw_raw_request(g15->hdev, LG_G13_FEATURE_M_KEYS_LEDS, + tbuf, 5, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (ret != 5) { + hid_err(g15->hdev, "Error getting macro LED brightness: %d\n", ret); + return (ret < 0) ? ret : -EIO; + } + + for (int i = LG_G15_MACRO_PRESET1; i < LG_G15_LED_MAX; ++i) + g15->leds[i].brightness = !!(tbuf[1] & (1 << (i - LG_G15_MACRO_PRESET1))); + + /* + * Bit 23 of g13_input_report.keybits[] contains the backlight's + * current HW toggle state. Retrieve it from the device. + */ + ret = hid_hw_raw_request(g15->hdev, LG_G13_INPUT_REPORT, + tbuf, sizeof(struct g13_input_report), + HID_INPUT_REPORT, HID_REQ_GET_REPORT); + if (ret != sizeof(struct g13_input_report)) { + hid_err(g15->hdev, "Error getting backlight on/off state: %d\n", ret); + return (ret < 0) ? ret : -EIO; + } + g15->backlight_disabled = + !TEST_BIT(((struct g13_input_report *) tbuf)->keybits, + LG_G13_BACKLIGHT_HW_ON_BIT); + + return 0; +} + +static int lg_g13_kbd_led_write(struct lg_g15_data *g15, + struct lg_g15_led *g15_led, + enum led_brightness brightness) +{ + struct mc_subled const * const subleds = g15_led->mcdev.subled_info; + u8 * const tbuf = g15->transfer_buf; + int ret; + + guard(mutex)(&g15->mutex); + + led_mc_calc_color_components(&g15_led->mcdev, brightness); + + tbuf[0] = 5; + tbuf[1] = subleds[0].brightness; + tbuf[2] = subleds[1].brightness; + tbuf[3] = subleds[2].brightness; + tbuf[4] = 0; + + ret = hid_hw_raw_request(g15->hdev, LG_G13_FEATURE_BACKLIGHT_RGB, + tbuf, 5, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + if (ret != 5) { + hid_err(g15->hdev, "Error setting backlight brightness: %d\n", ret); + return (ret < 0) ? ret : -EIO; + } + + g15_led->brightness = brightness; + return 0; +} + +static int lg_g13_kbd_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) +{ + struct led_classdev_mc *mc = lcdev_to_mccdev(led_cdev); + struct lg_g15_led *g15_led = + container_of(mc, struct lg_g15_led, mcdev); + struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent); + + /* Ignore LED off on unregister / keyboard unplug */ + if (led_cdev->flags & LED_UNREGISTERING) + return 0; + + return lg_g13_kbd_led_write(g15, g15_led, brightness); +} + +static enum led_brightness lg_g13_kbd_led_get(struct led_classdev *led_cdev) +{ + struct led_classdev_mc const * const mc = lcdev_to_mccdev(led_cdev); + struct lg_g15_led const *g15_led = + container_of(mc, struct lg_g15_led, mcdev); + + return g15_led->brightness; +} + +static int lg_g13_mkey_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) +{ + struct lg_g15_led *g15_led = + container_of(led_cdev, struct lg_g15_led, cdev); + struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent); + int i, ret; + u8 * const tbuf = g15->transfer_buf; + u8 val, mask = 0; + + /* Ignore LED off on unregister / keyboard unplug */ + if (led_cdev->flags & LED_UNREGISTERING) + return 0; + + guard(mutex)(&g15->mutex); + + for (i = LG_G15_MACRO_PRESET1; i < LG_G15_LED_MAX; ++i) { + if (i == g15_led->led) + val = brightness; + else + val = g15->leds[i].brightness; + + if (val) + mask |= 1 << (i - LG_G15_MACRO_PRESET1); + } + + tbuf[0] = 5; + tbuf[1] = mask; + tbuf[2] = 0; + tbuf[3] = 0; + tbuf[4] = 0; + + ret = hid_hw_raw_request(g15->hdev, LG_G13_FEATURE_M_KEYS_LEDS, + tbuf, 5, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + if (ret != 5) { + hid_err(g15->hdev, "Error setting LED brightness: %d\n", ret); + return (ret < 0) ? ret : -EIO; + } + + g15_led->brightness = brightness; + return 0; +} + +static enum led_brightness lg_g13_mkey_led_get(struct led_classdev *led_cdev) +{ + /* + * G13 doesn't change macro key LEDs behind our back, so they're + * whatever we last set them to. + */ + struct lg_g15_led *g15_led = + container_of(led_cdev, struct lg_g15_led, cdev); + + return g15_led->brightness; +} + /******** G15 and G15 v2 LED functions ********/ static int lg_g15_update_led_brightness(struct lg_g15_data *g15) @@ -390,6 +586,8 @@ static int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15) int ret; switch (g15->model) { + case LG_G13: + return lg_g13_get_leds_state(g15); case LG_G15: case LG_G15_V2: return lg_g15_update_led_brightness(g15); @@ -417,6 +615,108 @@ static int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15) /******** Input functions ********/ +/* Table mapping keybits[] bit positions to event codes. */ +/* Note: Indices are discontinuous to aid readability. */ +static const u16 g13_keys_for_bits[] = { + /* Main keypad - keys G1 - G22 */ + [0] = KEY_MACRO1, + [1] = KEY_MACRO2, + [2] = KEY_MACRO3, + [3] = KEY_MACRO4, + [4] = KEY_MACRO5, + [5] = KEY_MACRO6, + [6] = KEY_MACRO7, + [7] = KEY_MACRO8, + [8] = KEY_MACRO9, + [9] = KEY_MACRO10, + [10] = KEY_MACRO11, + [11] = KEY_MACRO12, + [12] = KEY_MACRO13, + [13] = KEY_MACRO14, + [14] = KEY_MACRO15, + [15] = KEY_MACRO16, + [16] = KEY_MACRO17, + [17] = KEY_MACRO18, + [18] = KEY_MACRO19, + [19] = KEY_MACRO20, + [20] = KEY_MACRO21, + [21] = KEY_MACRO22, + + /* LCD menu buttons. */ + [24] = KEY_KBD_LCD_MENU5, /* "Next page" button */ + [25] = KEY_KBD_LCD_MENU1, /* Left-most */ + [26] = KEY_KBD_LCD_MENU2, + [27] = KEY_KBD_LCD_MENU3, + [28] = KEY_KBD_LCD_MENU4, /* Right-most */ + + /* Macro preset and record buttons; have red LEDs under them. */ + [29] = KEY_MACRO_PRESET1, + [30] = KEY_MACRO_PRESET2, + [31] = KEY_MACRO_PRESET3, + [32] = KEY_MACRO_RECORD_START, + + /* 33-35 handled by joystick device. */ + + /* Backlight toggle. */ + [37] = KEY_LIGHTS_TOGGLE, +}; + +#define G13_JS_KEYBITS_OFFSET 33 + +static const u16 g13_keys_for_bits_js[] = { + /* Joystick buttons */ + /* These keybits are at bit indices 33, 34, and 35. */ + BTN_BASE, /* Left side */ + BTN_BASE2, /* Bottom side */ + BTN_THUMB, /* Stick depress */ +}; + +static int lg_g13_event(struct lg_g15_data *g15, u8 const *data) +{ + struct g13_input_report const * const rep = (struct g13_input_report *) data; + int i, val; + bool backlight_disabled; + + /* + * Main macropad and menu keys. + * Emit key events defined for each bit position. + */ + for (i = 0; i < ARRAY_SIZE(g13_keys_for_bits); ++i) { + if (g13_keys_for_bits[i]) { + val = TEST_BIT(rep->keybits, i); + input_report_key(g15->input, g13_keys_for_bits[i], val); + } + } + input_sync(g15->input); + + /* + * Joystick. + * Emit button and deflection events. + */ + for (i = 0; i < ARRAY_SIZE(g13_keys_for_bits_js); ++i) { + val = TEST_BIT(rep->keybits, i + G13_JS_KEYBITS_OFFSET); + input_report_key(g15->input_js, g13_keys_for_bits_js[i], val); + } + input_report_abs(g15->input_js, ABS_X, rep->joy_x); + input_report_abs(g15->input_js, ABS_Y, rep->joy_y); + input_sync(g15->input_js); + + /* + * Bit 23 of keybits[] reports the current backlight on/off state. If + * it has changed from the last cached value, apply an update. + */ + backlight_disabled = !TEST_BIT(rep->keybits, LG_G13_BACKLIGHT_HW_ON_BIT); + if (backlight_disabled ^ g15->backlight_disabled) { + led_classdev_notify_brightness_hw_changed( + &g15->leds[LG_G15_KBD_BRIGHTNESS].mcdev.led_cdev, + backlight_disabled + ? 0 : g15->leds[LG_G15_KBD_BRIGHTNESS].brightness); + g15->backlight_disabled = backlight_disabled; + } + + return 0; +} + /* On the G15 Mark I Logitech has been quite creative with which bit is what */ static void lg_g15_handle_lcd_menu_keys(struct lg_g15_data *g15, u8 *data) { @@ -572,6 +872,10 @@ static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report, return 0; switch (g15->model) { + case LG_G13: + if (data[0] == 0x01 && size == sizeof(struct g13_input_report)) + return lg_g13_event(g15, data); + break; case LG_G15: if (data[0] == 0x02 && size == 9) return lg_g15_event(g15, data); @@ -616,13 +920,22 @@ static void lg_g15_setup_led_rgb(struct lg_g15_data *g15, int index) { int i; struct mc_subled *subled_info; + struct lg_g15_led * const gled = &g15->leds[index]; - g15->leds[index].mcdev.led_cdev.brightness_set_blocking = - lg_g510_kbd_led_set; - g15->leds[index].mcdev.led_cdev.brightness_get = - lg_g510_kbd_led_get; - g15->leds[index].mcdev.led_cdev.max_brightness = 255; - g15->leds[index].mcdev.num_colors = 3; + if (g15->model == LG_G13) { + gled->mcdev.led_cdev.brightness_set_blocking = + lg_g13_kbd_led_set; + gled->mcdev.led_cdev.brightness_get = + lg_g13_kbd_led_get; + gled->mcdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED; + } else { + gled->mcdev.led_cdev.brightness_set_blocking = + lg_g510_kbd_led_set; + gled->mcdev.led_cdev.brightness_get = + lg_g510_kbd_led_get; + } + gled->mcdev.led_cdev.max_brightness = 255; + gled->mcdev.num_colors = 3; subled_info = devm_kcalloc(&g15->hdev->dev, 3, sizeof(*subled_info), GFP_KERNEL); if (!subled_info) @@ -632,20 +945,20 @@ static void lg_g15_setup_led_rgb(struct lg_g15_data *g15, int index) switch (i + 1) { case LED_COLOR_ID_RED: subled_info[i].color_index = LED_COLOR_ID_RED; - subled_info[i].intensity = g15->leds[index].red; + subled_info[i].intensity = gled->red; break; case LED_COLOR_ID_GREEN: subled_info[i].color_index = LED_COLOR_ID_GREEN; - subled_info[i].intensity = g15->leds[index].green; + subled_info[i].intensity = gled->green; break; case LED_COLOR_ID_BLUE: subled_info[i].color_index = LED_COLOR_ID_BLUE; - subled_info[i].intensity = g15->leds[index].blue; + subled_info[i].intensity = gled->blue; break; } subled_info[i].channel = i; } - g15->leds[index].mcdev.subled_info = subled_info; + gled->mcdev.subled_info = subled_info; } static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name) @@ -656,6 +969,23 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name) g15->leds[i].cdev.name = name; switch (g15->model) { + case LG_G13: + if (i < LG_G15_BRIGHTNESS_MAX) { + /* RGB backlight. */ + lg_g15_setup_led_rgb(g15, i); + ret = devm_led_classdev_multicolor_register_ext(&g15->hdev->dev, + &g15->leds[i].mcdev, + NULL); + } else { + /* Macro keys */ + g15->leds[i].cdev.brightness_set_blocking = lg_g13_mkey_led_set; + g15->leds[i].cdev.brightness_get = lg_g13_mkey_led_get; + g15->leds[i].cdev.max_brightness = 1; + + ret = devm_led_classdev_register(&g15->hdev->dev, + &g15->leds[i].cdev); + } + break; case LG_G15: case LG_G15_V2: g15->leds[i].cdev.brightness_get = lg_g15_led_get; @@ -702,11 +1032,9 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name) } /* Common input device init code shared between keyboards and Z-10 speaker handling */ -static void lg_g15_init_input_dev(struct hid_device *hdev, struct input_dev *input, - const char *name) +static void lg_g15_init_input_dev_core(struct hid_device *hdev, struct input_dev *input, + char const *name) { - int i; - input->name = name; input->phys = hdev->phys; input->uniq = hdev->uniq; @@ -717,12 +1045,42 @@ static void lg_g15_init_input_dev(struct hid_device *hdev, struct input_dev *inp input->dev.parent = &hdev->dev; input->open = lg_g15_input_open; input->close = lg_g15_input_close; +} + +static void lg_g15_init_input_dev(struct hid_device *hdev, struct input_dev *input, + const char *name) +{ + int i; + + lg_g15_init_input_dev_core(hdev, input, name); /* Keys below the LCD, intended for controlling a menu on the LCD */ for (i = 0; i < 5; i++) input_set_capability(input, EV_KEY, KEY_KBD_LCD_MENU1 + i); } +static void lg_g13_init_input_dev(struct hid_device *hdev, + struct input_dev *input, const char *name, + struct input_dev *input_js, const char *name_js) +{ + /* Macropad. */ + lg_g15_init_input_dev_core(hdev, input, name); + for (int i = 0; i < ARRAY_SIZE(g13_keys_for_bits); ++i) { + if (g13_keys_for_bits[i]) + input_set_capability(input, EV_KEY, g13_keys_for_bits[i]); + } + + /* OBTW, we're a joystick, too... */ + lg_g15_init_input_dev_core(hdev, input_js, name_js); + for (int i = 0; i < ARRAY_SIZE(g13_keys_for_bits_js); ++i) + input_set_capability(input_js, EV_KEY, g13_keys_for_bits_js[i]); + + input_set_capability(input_js, EV_ABS, ABS_X); + input_set_abs_params(input_js, ABS_X, 0, 255, 0, 0); + input_set_capability(input_js, EV_ABS, ABS_Y); + input_set_abs_params(input_js, ABS_Y, 0, 255, 0, 0); +} + static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) { static const char * const led_names[] = { @@ -739,7 +1097,7 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) unsigned int connect_mask = 0; bool has_ff000000 = false; struct lg_g15_data *g15; - struct input_dev *input; + struct input_dev *input, *input_js; struct hid_report *rep; int ret, i, gkeys = 0; @@ -778,6 +1136,25 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) hid_set_drvdata(hdev, (void *)g15); switch (g15->model) { + case LG_G13: + /* + * The G13 has an analog thumbstick with nearby buttons. Some + * libraries and applications are known to ignore devices that + * don't "look like" a joystick, and a device with two ABS axes + * and 25+ macro keys would confuse them. + * + * Create an additional input device dedicated to appear as a + * simplified joystick (two ABS axes, three BTN buttons). + */ + input_js = devm_input_allocate_device(&hdev->dev); + if (!input_js) + return -ENOMEM; + g15->input_js = input_js; + input_set_drvdata(input_js, hdev); + + connect_mask = HID_CONNECT_HIDRAW; + gkeys = 25; + break; case LG_G15: INIT_WORK(&g15->work, lg_g15_leds_changed_work); /* @@ -859,6 +1236,38 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id) goto error_hw_stop; return 0; /* All done */ + } else if (g15->model == LG_G13) { + static char const * const g13_led_names[] = { + /* Backlight is shared between LCD and keys. */ + "g13:rgb:kbd_backlight", + NULL, /* Keep in sync with led_type enum */ + "g13:red:macro_preset_1", + "g13:red:macro_preset_2", + "g13:red:macro_preset_3", + "g13:red:macro_record", + }; + lg_g13_init_input_dev(hdev, + input, "Logitech G13 Gaming Keypad", + input_js, "Logitech G13 Thumbstick"); + ret = input_register_device(input); + if (ret) + goto error_hw_stop; + ret = input_register_device(input_js); + if (ret) + goto error_hw_stop; + + for (i = 0; i < ARRAY_SIZE(g13_led_names); ++i) { + if (g13_led_names[i]) { + ret = lg_g15_register_led(g15, i, g13_led_names[i]); + if (ret) + goto error_hw_stop; + } + } + led_classdev_notify_brightness_hw_changed( + &g15->leds[LG_G15_KBD_BRIGHTNESS].mcdev.led_cdev, + g15->backlight_disabled + ? 0 : g15->leds[LG_G15_KBD_BRIGHTNESS].brightness); + return 0; } /* Setup and register input device */ @@ -903,6 +1312,13 @@ error_hw_stop: } static const struct hid_device_id lg_g15_devices[] = { + /* + * The G13 is a macropad-only device with an LCD, LED backlighing, + * and joystick. + */ + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_G13), + .driver_data = LG_G13 }, /* The G11 is a G15 without the LCD, treat it as a G15 */ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G11), From 4f777c31737a7450ef463aeb3be686d24acf6f0d Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:09 +0200 Subject: [PATCH 056/295] HID: intel-thc-hid: intel-quicki2c: Fix wrong type casting JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 6c26c055523d915afb8d18e7277848eff66a3085 Author: Xinpeng Sun Date: Thu Oct 9 11:31:08 2025 +0800 HID: intel-thc-hid: intel-quicki2c: Fix wrong type casting The type definition of qcdev->i2c_max_frame_size is already u32, so remove the unnecessary type casting le16_to_cpu. Signed-off-by: Xinpeng Sun Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202509280841.pxmgBzKW-lkp@intel.com/ Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c index 8433a991e7f4..0156ab391778 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c @@ -466,7 +466,7 @@ static void quicki2c_dma_adv_enable(struct quicki2c_device *qcdev) dev_warn(qcdev->dev, "Max frame size is smaller than hid max input length!"); thc_i2c_set_rx_max_size(qcdev->thc_hw, - le16_to_cpu(qcdev->i2c_max_frame_size)); + qcdev->i2c_max_frame_size); } thc_i2c_rx_max_size_enable(qcdev->thc_hw, true); } From 742526c2523f5c16b6f9179944490866594c54f0 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:10 +0200 Subject: [PATCH 057/295] HID: cp2112: Add parameter validation to data length JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 362f21536966d7039da1de762f28f4ad44565acc Author: Deepak Sharma Date: Fri Sep 26 20:28:11 2025 +0530 HID: cp2112: Add parameter validation to data length Syzkaller reported a stack OOB access in cp2112_write_req caused by lack of parameter validation for the user input in I2C SMBUS ioctl in cp2112 driver Add the parameter validation for the data->block[0] to be bounded by I2C_SMBUS_BLOCK_MAX + the additional compatibility padding [jkosina@suse.com: fix whitespace damage] Reported-by: syzbot+7617e19c8a59edfbd879@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=7617e19c8a59edfbd879 Tested-by: syzbot+7617e19c8a59edfbd879@syzkaller.appspotmail.com Signed-off-by: Deepak Sharma Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-cp2112.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index b5f2b6356f51..8024eba85876 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -689,7 +689,14 @@ static int cp2112_xfer(struct i2c_adapter *adap, u16 addr, count = cp2112_write_read_req(buf, addr, read_length, command, NULL, 0); } else { - count = cp2112_write_req(buf, addr, command, + /* Copy starts from data->block[1] so the length can + * be at max I2C_SMBUS_CLOCK_MAX + 1 + */ + + if (data->block[0] > I2C_SMBUS_BLOCK_MAX + 1) + count = -EINVAL; + else + count = cp2112_write_req(buf, addr, command, data->block + 1, data->block[0]); } @@ -700,7 +707,14 @@ static int cp2112_xfer(struct i2c_adapter *adap, u16 addr, I2C_SMBUS_BLOCK_MAX, command, NULL, 0); } else { - count = cp2112_write_req(buf, addr, command, + /* data_length here is data->block[0] + 1 + * so make sure that the data->block[0] is + * less than or equals I2C_SMBUS_BLOCK_MAX + 1 + */ + if (data->block[0] > I2C_SMBUS_BLOCK_MAX + 1) + count = -EINVAL; + else + count = cp2112_write_req(buf, addr, command, data->block, data->block[0] + 1); } @@ -709,7 +723,14 @@ static int cp2112_xfer(struct i2c_adapter *adap, u16 addr, size = I2C_SMBUS_BLOCK_DATA; read_write = I2C_SMBUS_READ; - count = cp2112_write_read_req(buf, addr, I2C_SMBUS_BLOCK_MAX, + /* data_length is data->block[0] + 1, so + * so data->block[0] should be less than or + * equal to the I2C_SMBUS_BLOCK_MAX + 1 + */ + if (data->block[0] > I2C_SMBUS_BLOCK_MAX + 1) + count = -EINVAL; + else + count = cp2112_write_read_req(buf, addr, I2C_SMBUS_BLOCK_MAX, command, data->block, data->block[0] + 1); break; From aa496e585791d0a3570ad71146eef127e8b5d06a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 9 Jun 2026 09:41:31 +0200 Subject: [PATCH 058/295] HID: hid-lg-g15: Add hw_brightness_changed support for the G510 keyboard JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 8c09e8b38dcc82d3db3187ef2460cb4011616e58 Author: Hans de Goede Date: Wed Oct 1 23:19:41 2025 +0200 HID: hid-lg-g15: Add hw_brightness_changed support for the G510 keyboard Add hw_brightness_changed support for the G510 keyboard, so that e.g. GNOME will show an OSD notification when toggling the backlight on/off with the button the keyboard. Note that it is not possible to turn the backlight back on by writing /sys/class/leds/.../brightness it can only be turned on by pressing the button on the keyboard. To reflect this /sys/class/leds/.../brightness will always report the last brightness value independent of the on/off toggle built into the keyboard. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lg-g15.c | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c index 7b8df2d5b57f..1a88bc44ada4 100644 --- a/drivers/hid/hid-lg-g15.c +++ b/drivers/hid/hid-lg-g15.c @@ -26,6 +26,9 @@ #define LG_G510_FEATURE_BACKLIGHT_RGB 0x05 #define LG_G510_FEATURE_POWER_ON_RGB 0x06 +#define LG_G510_INPUT_MACRO_KEYS 0x03 +#define LG_G510_INPUT_KBD_BACKLIGHT 0x04 + #define LG_G13_INPUT_REPORT 0x01 #define LG_G13_FEATURE_M_KEYS_LEDS 0x05 #define LG_G13_FEATURE_BACKLIGHT_RGB 0x07 @@ -423,6 +426,20 @@ static int lg_g510_get_initial_led_brightness(struct lg_g15_data *g15, int i) g15->leds[i].brightness = 0; } + if (i) + return 0; + + ret = hid_hw_raw_request(g15->hdev, LG_G510_INPUT_KBD_BACKLIGHT, + g15->transfer_buf, 2, + HID_INPUT_REPORT, HID_REQ_GET_REPORT); + if (ret != 2) { + /* This can happen when a KVM switch is used, so only warn. */ + hid_warn(g15->hdev, "Error getting backlight state: %d\n", ret); + return 0; + } + + g15->backlight_disabled = g15->transfer_buf[1] & 0x04; + return 0; } @@ -849,14 +866,24 @@ static int lg_g510_event(struct lg_g15_data *g15, u8 *data) static int lg_g510_leds_event(struct lg_g15_data *g15, u8 *data) { + struct lg_g15_led *g15_led = &g15->leds[LG_G15_KBD_BRIGHTNESS]; bool backlight_disabled; + backlight_disabled = data[1] & 0x04; + if (backlight_disabled == g15->backlight_disabled) + return 0; + + led_classdev_notify_brightness_hw_changed( + &g15_led->mcdev.led_cdev, + backlight_disabled ? 0 : g15_led->brightness); + + g15->backlight_disabled = backlight_disabled; + /* * The G510 ignores backlight updates when the backlight is turned off * through the light toggle button on the keyboard, to work around this * we queue a workitem to sync values when the backlight is turned on. */ - backlight_disabled = data[1] & 0x04; if (!backlight_disabled) schedule_work(&g15->work); @@ -892,9 +919,9 @@ static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report, break; case LG_G510: case LG_G510_USB_AUDIO: - if (data[0] == 0x03 && size == 5) + if (data[0] == LG_G510_INPUT_MACRO_KEYS && size == 5) return lg_g510_event(g15, data); - if (data[0] == 0x04 && size == 2) + if (data[0] == LG_G510_INPUT_KBD_BACKLIGHT && size == 2) return lg_g510_leds_event(g15, data); break; } @@ -933,6 +960,8 @@ static void lg_g15_setup_led_rgb(struct lg_g15_data *g15, int index) lg_g510_kbd_led_set; gled->mcdev.led_cdev.brightness_get = lg_g510_kbd_led_get; + if (index == LG_G15_KBD_BRIGHTNESS) + g15->leds[index].mcdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED; } gled->mcdev.led_cdev.max_brightness = 255; gled->mcdev.num_colors = 3; From 77f03d865066a9b0a5144c2a732f7933af588eb1 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:17 +0200 Subject: [PATCH 059/295] HID: logitech-dj: Add support for a new lightspeed receiver iteration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 5329fc30cbeabbd8593e5afc3e0f7a5cf86c6ceb Author: Mavroudis Chatzilazaridis Date: Sat Sep 27 23:49:45 2025 +0000 HID: logitech-dj: Add support for a new lightspeed receiver iteration This lightspeed receiver uses 13 byte mouse reports without an ID. There are 5 additional vendor defined bytes appended to the report. The workaround for such cases has been adjusted to handle these larger reports. The keyboard reports have a distinct layout with differing minimums and maximums. Additionally, the LED reports now require a report ID of 1. New keyboard and mouse descriptors have been added to reflect these changes. The devices attached to this receiver now report the status of their battery in wireless mode, libratbag communicates with them and they can be configured with Piper. Fixes: 9d1bd9346241 ("HID: logitech-dj: Add support for a new lightspeed receiver iteration") Link: https://bugzilla.kernel.org/show_bug.cgi?id=218172 Link: https://bugzilla.kernel.org/show_bug.cgi?id=218094 Co-developed-by: Filipe Laíns Signed-off-by: Filipe Laíns Signed-off-by: Mavroudis Chatzilazaridis Tested-by: Stuart Hayhurst Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-logitech-dj.c | 118 ++++++++++++++++++++++++++++++++-- 2 files changed, 112 insertions(+), 7 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 11b972636b8e..ee6a329fcc6b 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -915,6 +915,7 @@ #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1 0xc539 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1 0xc53f #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2 0xc543 +#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3 0xc547 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY 0xc53a #define USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER 0xc548 #define USB_DEVICE_ID_SPACETRAVELLER 0xc623 diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index cce54dd9884a..d66f4807311a 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -116,6 +116,7 @@ enum recvr_type { recvr_type_dj, recvr_type_hidpp, recvr_type_gaming_hidpp, + recvr_type_gaming_hidpp_ls_1_3, recvr_type_mouse_only, recvr_type_27mhz, recvr_type_bluetooth, @@ -211,6 +212,44 @@ static const char kbd_descriptor[] = { 0xC0 }; +/* Gaming Keyboard descriptor (1) */ +static const char kbd_lightspeed_1_3_descriptor[] = { + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x06, /* Usage (Keyboard) */ + 0xA1, 0x01, /* Collection (Application) */ + 0x85, 0x01, /* Report ID (1) */ + 0x05, 0x07, /* Usage Page (Kbrd/Keypad) */ + 0x19, 0xE0, /* Usage Minimum (0xE0) */ + 0x29, 0xE7, /* Usage Maximum (0xE7) */ + 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) */ + 0x95, 0x70, /* Report Count (112) */ + 0x19, 0x04, /* Usage Minimum (0x04) */ + 0x29, 0x73, /* Usage Maximum (0x73) */ + 0x81, 0x02, /* Input (Data,Var,Abs) */ + 0x95, 0x05, /* Report Count (5) */ + 0x19, 0x87, /* Usage Minimum (0x87) */ + 0x29, 0x8B, /* Usage Maximum (0x8B) */ + 0x81, 0x02, /* Input (Data,Var,Abs) */ + 0x95, 0x03, /* Report Count (3) */ + 0x19, 0x90, /* Usage Minimum (0x90) */ + 0x29, 0x92, /* Usage Maximum (0x92) */ + 0x81, 0x02, /* Input (Data,Var,Abs) */ + 0x95, 0x05, /* Report Count (5) */ + 0x85, 0x0E, /* Report ID (14) */ + 0x05, 0x08, /* Usage Page (LEDs) */ + 0x19, 0x01, /* Usage Minimum (Num Lock) */ + 0x29, 0x05, /* Usage Maximum (Kana) */ + 0x91, 0x02, /* Output (Data,Var,Abs) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x03, /* Report Size (3) */ + 0x91, 0x03, /* Output (Const,Var,Abs) */ + 0xC0, /* End Collection */ +}; + /* Mouse descriptor (2) */ static const char mse_descriptor[] = { 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ @@ -415,6 +454,51 @@ static const char mse_high_res_descriptor[] = { 0xC0, /* END_COLLECTION */ }; +/* Gaming Mouse descriptor with vendor data (2) */ +static const char mse_high_res_ls_1_3_descriptor[] = { + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x02, /* Usage (Mouse) */ + 0xA1, 0x01, /* Collection (Application) */ + 0x85, 0x02, /* Report ID (2) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xA1, 0x00, /* Collection (Physical) */ + 0x95, 0x10, /* Report Count (16) */ + 0x75, 0x01, /* Report Size (1) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x05, 0x09, /* Usage Page (Button) */ + 0x19, 0x01, /* Usage Minimum (0x01) */ + 0x29, 0x10, /* Usage Maximum (0x10) */ + 0x81, 0x02, /* Input (Data,Var,Abs) */ + 0x95, 0x02, /* Report Count (2) */ + 0x75, 0x10, /* Report Size (16) */ + 0x16, 0x01, 0x80, /* Logical Minimum (-32767) */ + 0x26, 0xFF, 0x7F, /* Logical Maximum (32767) */ + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x30, /* Usage (X) */ + 0x09, 0x31, /* Usage (Y) */ + 0x81, 0x06, /* Input (Data,Var,Rel) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x08, /* Report Size (8) */ + 0x15, 0x81, /* Logical Minimum (-127) */ + 0x25, 0x7F, /* Logical Maximum (127) */ + 0x09, 0x38, /* Usage (Wheel) */ + 0x81, 0x06, /* Input (Data,Var,Rel) */ + 0x95, 0x01, /* Report Count (1) */ + 0x05, 0x0C, /* Usage Page (Consumer) */ + 0x0A, 0x38, 0x02, /* Usage (AC Pan) */ + 0x81, 0x06, /* Input (Data,Var,Rel) */ + 0xC0, /* End Collection */ + 0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined 0xFF00) */ + 0x09, 0xF1, /* Usage (0xF1) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x05, /* Report Count (5) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xFF, 0x00, /* Logical Maximum (255) */ + 0x81, 0x00, /* Input (Data,Array,Abs) */ + 0xC0, /* End Collection */ +}; + /* Consumer Control descriptor (3) */ static const char consumer_descriptor[] = { 0x05, 0x0C, /* USAGE_PAGE (Consumer Devices) */ @@ -520,9 +604,9 @@ static const char hidpp_descriptor[] = { /* Maximum size of all defined hid reports in bytes (including report id) */ #define MAX_REPORT_SIZE 8 -/* Make sure all descriptors are present here */ +/* Make sure the largest of each descriptor type is present here */ #define MAX_RDESC_SIZE \ - (sizeof(kbd_descriptor) + \ + (sizeof(kbd_lightspeed_1_3_descriptor) +\ sizeof(mse_bluetooth_descriptor) + \ sizeof(mse5_bluetooth_descriptor) + \ sizeof(consumer_descriptor) + \ @@ -1374,12 +1458,19 @@ static int logi_dj_ll_raw_request(struct hid_device *hid, return -EINVAL; if (djrcv_dev->type != recvr_type_dj && count >= 2) { + unsigned char led_report_id = 0; + if (!djrcv_dev->keyboard) { hid_warn(hid, "Received REPORT_TYPE_LEDS request before the keyboard interface was enumerated\n"); return 0; } + + /* This Lightspeed receiver expects LED reports with report ID 1 */ + if (djrcv_dev->type == recvr_type_gaming_hidpp_ls_1_3) + led_report_id = 1; + /* usbhid overrides the report ID and ignores the first byte */ - return hid_hw_raw_request(djrcv_dev->keyboard, 0, buf, count, + return hid_hw_raw_request(djrcv_dev->keyboard, led_report_id, buf, count, report_type, reqtype); } @@ -1426,7 +1517,11 @@ static int logi_dj_ll_parse(struct hid_device *hid) if (djdev->reports_supported & STD_KEYBOARD) { dbg_hid("%s: sending a kbd descriptor, reports_supported: %llx\n", __func__, djdev->reports_supported); - rdcat(rdesc, &rsize, kbd_descriptor, sizeof(kbd_descriptor)); + if (djdev->dj_receiver_dev->type == recvr_type_gaming_hidpp_ls_1_3) + rdcat(rdesc, &rsize, kbd_lightspeed_1_3_descriptor, + sizeof(kbd_lightspeed_1_3_descriptor)); + else + rdcat(rdesc, &rsize, kbd_descriptor, sizeof(kbd_descriptor)); } if (djdev->reports_supported & STD_MOUSE) { @@ -1436,6 +1531,9 @@ static int logi_dj_ll_parse(struct hid_device *hid) djdev->dj_receiver_dev->type == recvr_type_mouse_only) rdcat(rdesc, &rsize, mse_high_res_descriptor, sizeof(mse_high_res_descriptor)); + else if (djdev->dj_receiver_dev->type == recvr_type_gaming_hidpp_ls_1_3) + rdcat(rdesc, &rsize, mse_high_res_ls_1_3_descriptor, + sizeof(mse_high_res_ls_1_3_descriptor)); else if (djdev->dj_receiver_dev->type == recvr_type_27mhz) rdcat(rdesc, &rsize, mse_27mhz_descriptor, sizeof(mse_27mhz_descriptor)); @@ -1695,11 +1793,12 @@ static int logi_dj_raw_event(struct hid_device *hdev, } /* * Mouse-only receivers send unnumbered mouse data. The 27 MHz - * receiver uses 6 byte packets, the nano receiver 8 bytes. + * receiver uses 6 byte packets, the nano receiver 8 bytes, + * the lightspeed receiver (Pro X Superlight) 13 bytes. */ if (djrcv_dev->unnumbered_application == HID_GD_MOUSE && - size <= 8) { - u8 mouse_report[9]; + size <= 13){ + u8 mouse_report[14]; /* Prepend report id */ mouse_report[0] = REPORT_TYPE_MOUSE; @@ -1776,6 +1875,7 @@ static int logi_dj_probe(struct hid_device *hdev, case recvr_type_dj: no_dj_interfaces = 3; break; case recvr_type_hidpp: no_dj_interfaces = 2; break; case recvr_type_gaming_hidpp: no_dj_interfaces = 3; break; + case recvr_type_gaming_hidpp_ls_1_3: no_dj_interfaces = 3; break; case recvr_type_mouse_only: no_dj_interfaces = 2; break; case recvr_type_27mhz: no_dj_interfaces = 2; break; case recvr_type_bluetooth: no_dj_interfaces = 2; break; @@ -1987,6 +2087,10 @@ static const struct hid_device_id logi_dj_receivers[] = { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2), .driver_data = recvr_type_gaming_hidpp}, + { /* Logitech lightspeed receiver (0xc547) */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3), + .driver_data = recvr_type_gaming_hidpp_ls_1_3}, { /* Logitech 27 MHz HID++ 1.0 receiver (0xc513) */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER), From 31ab50f63e5eb782e66a5801d28965a836e9a836 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:18 +0200 Subject: [PATCH 060/295] HID: logitech-hidpp: Silence protocol errors on newer lightspeed receivers JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 55cafcac080a882fbeea1a49a7338211ff2494db Author: Mavroudis Chatzilazaridis Date: Thu Oct 2 19:24:37 2025 +0000 HID: logitech-hidpp: Silence protocol errors on newer lightspeed receivers When logitech-hidpp tries to communicate with an unreachable device paired to a 046d:c547 lightspeed receiver, the following message is printed to the console: hidpp_root_get_protocol_version: received protocol error 0x08 This occurs because this receiver returns 0x08 (HIDPP_ERROR_UNKNOWN_DEVICE) when a device is unreachable, compared to 0x09 (HIDPP_ERROR_RESOURCE_ERROR) that the older receivers return. This patch silences this harmless error by treating HIDPP_ERROR_UNKNOWN_DEVICE the same as HIDPP_ERROR_RESOURCE_ERROR in hidpp_root_get_protocol_version(). There are other checks for HIDPP_ERROR_RESOURCE_ERROR found in battery-related functions, however this receiver does not trigger them when the device is disconnected. Signed-off-by: Mavroudis Chatzilazaridis Tested-by: Stuart Hayhurst Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 5e763de4b94f..ff09e6f0ea44 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -971,7 +971,8 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) } /* the device might not be connected */ - if (ret == HIDPP_ERROR_RESOURCE_ERROR) + if (ret == HIDPP_ERROR_RESOURCE_ERROR || + ret == HIDPP_ERROR_UNKNOWN_DEVICE) return -EIO; if (ret > 0) { From 2511e4a7f753e7db3a5b5af8415f01e692a3220b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:19 +0200 Subject: [PATCH 061/295] HID: logitech-hidpp: Do not assume FAP in hidpp_send_message_sync() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit aba7963544d47d82cdf36602a6678a093af0299d Author: Mavroudis Chatzilazaridis Date: Thu Oct 2 19:30:58 2025 +0000 HID: logitech-hidpp: Do not assume FAP in hidpp_send_message_sync() Currently, hidpp_send_message_sync() retries sending the message when the device returns a busy error code, specifically HIDPP20_ERROR_BUSY, which has a different meaning under RAP. This ends up being a problem because this function is used for both FAP and RAP messages. This issue is not noticeable on older receivers with unreachable devices since they return HIDPP_ERROR_RESOURCE_ERROR (0x09), which is not equal to HIDPP20_ERROR_BUSY (0x08). However, newer receivers return HIDPP_ERROR_UNKNOWN_DEVICE (0x08) which happens to equal to HIDPP20_ERROR_BUSY, causing unnecessary retries when the device is not actually busy. This is resolved by checking if the error response is FAP or RAP and picking the respective ERROR_BUSY code. Fixes: 60165ab774cb ("HID: logitech-hidpp: rework one more time the retries attempts") Signed-off-by: Mavroudis Chatzilazaridis Tested-by: Stuart Hayhurst Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index ff09e6f0ea44..d5011a5d0890 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -352,10 +352,15 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp, do { ret = __do_hidpp_send_message_sync(hidpp, message, response); - if (ret != HIDPP20_ERROR_BUSY) + if (response->report_id == REPORT_ID_HIDPP_SHORT && + ret != HIDPP_ERROR_BUSY) + break; + if ((response->report_id == REPORT_ID_HIDPP_LONG || + response->report_id == REPORT_ID_HIDPP_VERY_LONG) && + ret != HIDPP20_ERROR_BUSY) break; - dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret); + dbg_hid("%s:got busy hidpp error %02X, retrying\n", __func__, ret); } while (--max_retries); mutex_unlock(&hidpp->send_mutex); From e80a5b7ba1ab18627f80556ec01ffb1bc23e957c Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:20 +0200 Subject: [PATCH 062/295] HID: core: Add printk_ratelimited variants to hid_warn() etc JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 1d64624243af8329b4b219d8c39e28ea448f9929 Author: Vicki Pfau Date: Mon Oct 6 18:05:31 2025 -0700 HID: core: Add printk_ratelimited variants to hid_warn() etc hid_warn_ratelimited() is needed. Add the others as part of the block. Signed-off-by: Vicki Pfau Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- include/linux/hid.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/include/linux/hid.h b/include/linux/hid.h index e1b673ad7457..a4ddb94e3ee5 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -1292,4 +1292,15 @@ void hid_quirks_exit(__u16 bus); #define hid_dbg_once(hid, fmt, ...) \ dev_dbg_once(&(hid)->dev, fmt, ##__VA_ARGS__) +#define hid_err_ratelimited(hid, fmt, ...) \ + dev_err_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__) +#define hid_notice_ratelimited(hid, fmt, ...) \ + dev_notice_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__) +#define hid_warn_ratelimited(hid, fmt, ...) \ + dev_warn_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__) +#define hid_info_ratelimited(hid, fmt, ...) \ + dev_info_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__) +#define hid_dbg_ratelimited(hid, fmt, ...) \ + dev_dbg_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__) + #endif From d99458dee310b352907898cd852060959b6e1614 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:21 +0200 Subject: [PATCH 063/295] HID: nintendo: Rate limit IMU compensation message JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit b8874720b2f33a06ff1d4cf3827e7ec1195cb360 Author: Vicki Pfau Date: Mon Oct 6 18:05:33 2025 -0700 HID: nintendo: Rate limit IMU compensation message Some controllers are very bad at updating the IMU, leading to these messages spamming the syslog. Rate-limiting them helps with this a bit. Signed-off-by: Vicki Pfau Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-nintendo.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index e3e54f1df44f..c2849a541f65 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -1455,10 +1455,10 @@ static void joycon_parse_imu_report(struct joycon_ctlr *ctlr, ctlr->imu_avg_delta_ms; ctlr->imu_timestamp_us += 1000 * ctlr->imu_avg_delta_ms; if (dropped_pkts > JC_IMU_DROPPED_PKT_WARNING) { - hid_warn(ctlr->hdev, + hid_warn_ratelimited(ctlr->hdev, "compensating for %u dropped IMU reports\n", dropped_pkts); - hid_warn(ctlr->hdev, + hid_warn_ratelimited(ctlr->hdev, "delta=%u avg_delta=%u\n", delta, ctlr->imu_avg_delta_ms); } From 13283c388eb16d9e415f6810c849a27817d271d5 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:22 +0200 Subject: [PATCH 064/295] HID: uclogic: Add support for the XP-PEN Artist 24 Pro JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit ee35448c89aaa2941fb3ef8496a6067e7838ea19 Author: Joshua Goins Date: Mon Sep 22 17:32:03 2025 -0400 HID: uclogic: Add support for the XP-PEN Artist 24 Pro The tablet is similar to the 22R Pro, but with a few annoying differences. Its descriptors are bigger because of the tablet's split coordinate system, I guess it's just that large. Thankfully, this is easy enough to support as all we have to do is shift bytes around. To help code re-use, I changed the signature of uclogic_params_init_ugee_xppen_pro to accept a pen descriptor so we didn't create yet-another initialization function. I have been testing this locally for a month or so and it works great, and also corroborated this with a few other testers. Since this touches my 22R Pro code, I have tested and checked that it didn't regress that device. Signed-off-by: Joshua Goins Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-uclogic-core.c | 19 +++++ drivers/hid/hid-uclogic-params.c | 47 +++++++++--- drivers/hid/hid-uclogic-params.h | 5 ++ drivers/hid/hid-uclogic-rdesc.c | 125 +++++++++++++++++++++++++++++++ drivers/hid/hid-uclogic-rdesc.h | 8 ++ 6 files changed, 194 insertions(+), 11 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index ee6a329fcc6b..5e6e87487205 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1421,6 +1421,7 @@ #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW 0x0933 #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06 0x0078 #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_22R_PRO 0x091b +#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_24_PRO 0x092d #define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074 #define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071 #define USB_DEVICE_ID_UGEE_TABLET_RAINBOW_CV720 0x0055 diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index 34fb03ae8ee2..90ebb81041ea 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -362,6 +362,23 @@ static int uclogic_raw_event_pen(struct uclogic_drvdata *drvdata, data[8] = pressure_low_byte; data[9] = pressure_high_byte; } + if (size == 12 && pen->fragmented_hires2) { + // 00 00 when on the left side, 01 00 in the right + // we move these to the end of the x coord (u16) to create a correct x coord (u32) + u8 lsb_low_byte = data[10]; + u8 lsb_high_byte = data[11]; + + // shift everything right by 2 bytes, to make space for the moved lsb + data[11] = data[9]; + data[10] = data[8]; + data[9] = data[7]; + data[8] = data[6]; + data[7] = data[5]; + data[6] = data[4]; + + data[4] = lsb_low_byte; + data[5] = lsb_high_byte; + } /* If we need to emulate in-range detection */ if (pen->inrange == UCLOGIC_PARAMS_PEN_INRANGE_NONE) { /* Set in-range bit */ @@ -604,6 +621,8 @@ static const struct hid_device_id uclogic_devices[] = { USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06) }, { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_22R_PRO) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, + USB_DEVICE_ID_UGEE_XPPEN_TABLET_24_PRO) }, { } }; MODULE_DEVICE_TABLE(hid, uclogic_devices); diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 4c4bac6f792b..e28176d9d9c9 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -1123,6 +1123,9 @@ static int uclogic_params_parse_ugee_v2_desc(const __u8 *str_desc, return -EINVAL; pen_x_lm = get_unaligned_le16(str_desc + 2); + if (str_desc_size > 12) + pen_x_lm += (u8)str_desc[12] << 16; + pen_y_lm = get_unaligned_le16(str_desc + 4); frame_num_buttons = str_desc[6]; *frame_type = str_desc[7]; @@ -1534,7 +1537,7 @@ cleanup: } /* - * uclogic_params_init_ugee_xppen_pro_22r() - Initializes a UGEE XP-Pen Pro 22R tablet device. + * uclogic_params_init_ugee_xppen_pro() - Initializes a UGEE XP-Pen Pro tablet device. * * @hdev: The HID device of the tablet interface to initialize and get * parameters from. Cannot be NULL. @@ -1545,15 +1548,17 @@ cleanup: * Returns: * Zero, if successful. A negative errno code on error. */ -static int uclogic_params_init_ugee_xppen_pro_22r(struct uclogic_params *params, - struct hid_device *hdev, - const u8 rdesc_frame_arr[], - const size_t rdesc_frame_size) +static int uclogic_params_init_ugee_xppen_pro(struct uclogic_params *params, + struct hid_device *hdev, + const u8 rdesc_pen_arr[], + const size_t rdesc_pen_size, + const u8 rdesc_frame_arr[], + const size_t rdesc_frame_size, + size_t str_desc_len) { int rc = 0; struct usb_interface *iface; __u8 bInterfaceNumber; - const int str_desc_len = 12; u8 *str_desc = NULL; __u8 *rdesc_pen = NULL; s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM]; @@ -1616,8 +1621,8 @@ static int uclogic_params_init_ugee_xppen_pro_22r(struct uclogic_params *params, /* Initialize the pen interface */ rdesc_pen = uclogic_rdesc_template_apply( - uclogic_rdesc_ugee_v2_pen_template_arr, - uclogic_rdesc_ugee_v2_pen_template_size, + rdesc_pen_arr, + rdesc_pen_size, desc_params, ARRAY_SIZE(desc_params)); if (!rdesc_pen) { rc = -ENOMEM; @@ -1625,7 +1630,7 @@ static int uclogic_params_init_ugee_xppen_pro_22r(struct uclogic_params *params, } p.pen.desc_ptr = rdesc_pen; - p.pen.desc_size = uclogic_rdesc_ugee_v2_pen_template_size; + p.pen.desc_size = rdesc_pen_size; p.pen.id = 0x02; p.pen.subreport_list[0].value = 0xf0; p.pen.subreport_list[0].id = UCLOGIC_RDESC_V1_FRAME_ID; @@ -1972,10 +1977,30 @@ int uclogic_params_init(struct uclogic_params *params, break; case VID_PID(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_22R_PRO): - rc = uclogic_params_init_ugee_xppen_pro_22r(&p, + rc = uclogic_params_init_ugee_xppen_pro(&p, hdev, + uclogic_rdesc_ugee_v2_pen_template_arr, + uclogic_rdesc_ugee_v2_pen_template_size, uclogic_rdesc_xppen_artist_22r_pro_frame_arr, - uclogic_rdesc_xppen_artist_22r_pro_frame_size); + uclogic_rdesc_xppen_artist_22r_pro_frame_size, + 12); + if (rc != 0) + goto cleanup; + + break; + case VID_PID(USB_VENDOR_ID_UGEE, + USB_DEVICE_ID_UGEE_XPPEN_TABLET_24_PRO): + rc = uclogic_params_init_ugee_xppen_pro(&p, + hdev, + uclogic_rdesc_xppen_artist_24_pro_pen_template_arr, + uclogic_rdesc_xppen_artist_24_pro_pen_template_size, + uclogic_rdesc_xppen_artist_24_pro_frame_arr, + uclogic_rdesc_xppen_artist_24_pro_frame_size, + 14); + + // The 24 Pro has a fragmented X Coord. + p.pen.fragmented_hires2 = true; + if (rc != 0) goto cleanup; diff --git a/drivers/hid/hid-uclogic-params.h b/drivers/hid/hid-uclogic-params.h index 6ec8643d2ee5..c84ff17fb5d5 100644 --- a/drivers/hid/hid-uclogic-params.h +++ b/drivers/hid/hid-uclogic-params.h @@ -103,6 +103,11 @@ struct uclogic_params_pen { * Only valid if "id" is not zero. */ bool tilt_y_flipped; + /* + * True, if reports include fragmented high resolution X coords. + * This moves bytes 10-11 to the LSB of the X coordinate. + */ + bool fragmented_hires2; }; /* diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c index 08a89c6aae3b..a1b31511b625 100644 --- a/drivers/hid/hid-uclogic-rdesc.c +++ b/drivers/hid/hid-uclogic-rdesc.c @@ -1237,6 +1237,131 @@ const __u8 uclogic_rdesc_xppen_artist_22r_pro_frame_arr[] = { const size_t uclogic_rdesc_xppen_artist_22r_pro_frame_size = sizeof(uclogic_rdesc_xppen_artist_22r_pro_frame_arr); +/* Fixed report descriptor template for XP-PEN 24 Pro reports + * Mostly identical to uclogic_rdesc_ugee_v2_pen_template_arr except that the X coordinate has to be + * 32-bits instead of 16-bits. + */ +const __u8 uclogic_rdesc_xppen_artist_24_pro_pen_template_arr[] = { + 0x05, 0x0d, /* Usage Page (Digitizers), */ + 0x09, 0x01, /* Usage (Digitizer), */ + 0xa1, 0x01, /* Collection (Application), */ + 0x85, 0x02, /* Report ID (2), */ + 0x09, 0x20, /* Usage (Stylus), */ + 0xa1, 0x00, /* Collection (Physical), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x09, 0x44, /* Usage (Barrel Switch), */ + 0x09, 0x46, /* Usage (Tablet Pick), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x14, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x09, 0x32, /* Usage (In Range), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x02, /* Report Count (2), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x75, 0x10, /* Report Size (16), */ + 0x95, 0x01, /* Report Count (1), */ + 0x35, 0x00, /* Physical Minimum (0), */ + 0xa4, /* Push, */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x30, /* Usage (X), */ + 0x65, 0x13, /* Unit (Inch), */ + 0x55, 0x0d, /* Unit Exponent (-3), */ + 0x27, UCLOGIC_RDESC_PEN_PH(X_LM), + /* Logical Maximum (PLACEHOLDER), */ + 0x47, UCLOGIC_RDESC_PEN_PH(X_PM), + /* Physical Maximum (PLACEHOLDER), */ + 0x75, 0x20, /* Report Size (32), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x10, /* Report Size (16), */ + 0x09, 0x31, /* Usage (Y), */ + 0x27, UCLOGIC_RDESC_PEN_PH(Y_LM), + /* Logical Maximum (PLACEHOLDER), */ + 0x47, UCLOGIC_RDESC_PEN_PH(Y_PM), + /* Physical Maximum (PLACEHOLDER), */ + 0x81, 0x02, /* Input (Variable), */ + 0xb4, /* Pop, */ + 0x09, 0x30, /* Usage (Tip Pressure), */ + 0x45, 0x00, /* Physical Maximum (0), */ + 0x27, UCLOGIC_RDESC_PEN_PH(PRESSURE_LM), + /* Logical Maximum (PLACEHOLDER), */ + 0x75, 0x0D, /* Report Size (13), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x01, /* Input (Constant), */ + 0x09, 0x3d, /* Usage (X Tilt), */ + 0x35, 0xC3, /* Physical Minimum (-61), */ + 0x45, 0x3C, /* Physical Maximum (60), */ + 0x15, 0xC3, /* Logical Minimum (-61), */ + 0x25, 0x3C, /* Logical Maximum (60), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x3e, /* Usage (Y Tilt), */ + 0x35, 0xC3, /* Physical Minimum (-61), */ + 0x45, 0x3C, /* Physical Maximum (60), */ + 0x15, 0xC3, /* Logical Minimum (-61), */ + 0x25, 0x3C, /* Logical Maximum (60), */ + 0x81, 0x02, /* Input (Variable), */ + 0xc0, /* End Collection, */ + 0xc0, /* End Collection */ +}; +const size_t uclogic_rdesc_xppen_artist_24_pro_pen_template_size = + sizeof(uclogic_rdesc_xppen_artist_24_pro_pen_template_arr); + +/* Fixed report descriptor for XP-Pen Arist 24 Pro frame */ +const __u8 uclogic_rdesc_xppen_artist_24_pro_frame_arr[] = { + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x07, /* Usage (Keypad), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, UCLOGIC_RDESC_V1_FRAME_ID, + /* Report ID (Virtual report), */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x39, /* Usage (Tablet Function Keys), */ + 0xA0, /* Collection (Physical), */ + 0x14, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x08, /* Report Count (8), */ + 0x81, 0x01, /* Input (Constant), */ + 0x05, 0x09, /* Usage Page (Button), */ + 0x19, 0x01, /* Usage Minimum (01h), */ + 0x29, 0x14, /* Usage Maximum (14h), */ + 0x95, 0x14, /* Report Count (20), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x14, /* Report Count (20), */ + 0x81, 0x01, /* Input (Constant), */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x38, /* Usage (Wheel), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x01, /* Report Count (1), */ + 0x15, 0xFF, /* Logical Minimum (-1), */ + 0x25, 0x08, /* Logical Maximum (8), */ + 0x81, 0x06, /* Input (Variable, Relative), */ + 0x05, 0x0C, /* Usage Page (Consumer Devices), */ + 0x0A, 0x38, 0x02, /* Usage (AC PAN), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x06, /* Input (Variable, Relative), */ + 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 16, /* Report Count (16), */ + 0x81, 0x01, /* Input (Constant), */ + 0xC0, /* End Collection */ + 0xC0, /* End Collection */ +}; + +const size_t uclogic_rdesc_xppen_artist_24_pro_frame_size = + sizeof(uclogic_rdesc_xppen_artist_24_pro_frame_arr); + /** * uclogic_rdesc_template_apply() - apply report descriptor parameters to a * report descriptor template, creating a report descriptor. Copies the diff --git a/drivers/hid/hid-uclogic-rdesc.h b/drivers/hid/hid-uclogic-rdesc.h index 644a35ff12f2..0619daa6849d 100644 --- a/drivers/hid/hid-uclogic-rdesc.h +++ b/drivers/hid/hid-uclogic-rdesc.h @@ -214,4 +214,12 @@ extern const size_t uclogic_rdesc_ugee_g5_frame_size; extern const __u8 uclogic_rdesc_xppen_artist_22r_pro_frame_arr[]; extern const size_t uclogic_rdesc_xppen_artist_22r_pro_frame_size; +/* Fixed report descriptor for XP-Pen Arist 24 Pro frame */ +extern const __u8 uclogic_rdesc_xppen_artist_24_pro_pen_template_arr[]; +extern const size_t uclogic_rdesc_xppen_artist_24_pro_pen_template_size; + +/* Fixed report descriptor for XP-Pen Arist 24 Pro frame */ +extern const __u8 uclogic_rdesc_xppen_artist_24_pro_frame_arr[]; +extern const size_t uclogic_rdesc_xppen_artist_24_pro_frame_size; + #endif /* _HID_UCLOGIC_RDESC_H */ From 5b47695e5316ac2b1f7cb1f0dac6b25604a5165b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:23 +0200 Subject: [PATCH 065/295] HID: Kconfig: Fix build error from CONFIG_HID_HAPTIC JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 083a4f3f3cc7d107728c8f297e4f6276f0876b2d Author: Jonathan Denose Date: Mon Oct 13 20:54:57 2025 +0000 HID: Kconfig: Fix build error from CONFIG_HID_HAPTIC Temporarily change CONFIG_HID_HAPTIC to be bool instead of tristate, until we implement a permanent solution. Recently the CONFIG_HID_HAPTIC Kconfig option was reported as causing the following build errors: MODPOST Module.symvers ERROR: modpost: "hid_haptic_init" [drivers/hid/hid-multitouch.ko] undefined! ERROR: modpost: "hid_haptic_pressure_increase" [drivers/hid/hid-multitouch.ko] undefined! ERROR: modpost: "hid_haptic_check_pressure_unit" [drivers/hid/hid-multitouch.ko] undefined! ERROR: modpost: "hid_haptic_input_configured" [drivers/hid/hid-multitouch.ko] undefined! ERROR: modpost: "hid_haptic_input_mapping" [drivers/hid/hid-multitouch.ko] undefined! ERROR: modpost: "hid_haptic_feature_mapping" [drivers/hid/hid-multitouch.ko] undefined! ERROR: modpost: "hid_haptic_pressure_reset" [drivers/hid/hid-multitouch.ko] undefined! make[3]: *** [/home/thl/var/linux.dev/scripts/Makefile.modpost:147: Module.symvers] Error 1 when the kernel is compiled with the following configuration: CONFIG_HID=y CONFIG_HID_MULTITOUCH=m CONFIG_HID_HAPTIC=m To resolve this, temporarily change the CONFIG_HID_HAPTIC option to be bool, until we arrive at a permanent solution to enable CONFIG_HID_HAPTIC to be tristate. For a more detailed discussion, see [1]. [1]: https://lore.kernel.org/linux-input/auypydfkhx2eg7vp764way4batdilzc35inqda3exwzs3tk3ff@oagat6g46zto/ Signed-off-by: Jonathan Denose Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 7ff85c7200e5..986de05a9787 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -93,7 +93,7 @@ config HID_GENERIC If unsure, say Y. config HID_HAPTIC - tristate "Haptic touchpad support" + bool "Haptic touchpad support" default n help Support for touchpads with force sensors and haptic actuators instead of a From d13aaf6a40e2ae7bc19b3349733fc43296048433 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:24 +0200 Subject: [PATCH 066/295] HID: hid-debug: Fix spelling mistake "Rechargable" -> "Rechargeable" JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit a7cdc2086c19e435d4cec3f9393b5f46899c0468 Author: Colin Ian King Date: Sun Sep 28 22:01:18 2025 +0100 HID: hid-debug: Fix spelling mistake "Rechargable" -> "Rechargeable" There is a spelling mistake in HID description. Fix it. Signed-off-by: Colin Ian King Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-debug.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index 7107071c7c51..337d2dc81b4c 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -2523,7 +2523,7 @@ static const struct hid_usage_entry hid_usage_table[] = { { 0x85, 0x0088, "iDeviceName" }, { 0x85, 0x0089, "iDeviceChemistry" }, { 0x85, 0x008a, "ManufacturerData" }, - { 0x85, 0x008b, "Rechargable" }, + { 0x85, 0x008b, "Rechargeable" }, { 0x85, 0x008c, "WarningCapacityLimit" }, { 0x85, 0x008d, "CapacityGranularity1" }, { 0x85, 0x008e, "CapacityGranularity2" }, From 52c8cda07560f93ed2c6194a9ebd6ec4fcdf2e5e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:26 +0200 Subject: [PATCH 067/295] selftests/hid: add tests for missing release on the Dell Synaptics JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit d9b3014a7f1425011909ad358dc0c8f187853a12 Author: Benjamin Tissoires Date: Wed Oct 8 16:06:59 2025 +0200 selftests/hid: add tests for missing release on the Dell Synaptics Add a simple test for the corner case not currently covered by the sticky fingers quirk. Because it's a corner case test, we only test this on a couple of devices, not on all of them because the value of adding the same test over and over is rather moot. Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../selftests/hid/tests/test_multitouch.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tools/testing/selftests/hid/tests/test_multitouch.py b/tools/testing/selftests/hid/tests/test_multitouch.py index 5d2ffa3d5977..ece0ba8e7d34 100644 --- a/tools/testing/selftests/hid/tests/test_multitouch.py +++ b/tools/testing/selftests/hid/tests/test_multitouch.py @@ -1752,6 +1752,52 @@ class TestWin8TSConfidence(BaseTest.TestWin8Multitouch): assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + @pytest.mark.skip_if_uhdev( + lambda uhdev: "Confidence" not in uhdev.fields, + "Device not compatible, missing Confidence usage", + ) + def test_mt_confidence_bad_multi_release(self): + """Check for the sticky finger being properly detected. + + We first inject 3 fingers, then release only the second. + After 100 ms, we should receive a generated event about the + 2 missing fingers being released. + """ + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + # send 3 touches + t0 = Touch(1, 50, 10) + t1 = Touch(2, 150, 100) + t2 = Touch(3, 250, 200) + r = uhdev.event([t0, t1, t2]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + # release the second + t1.tipswitch = False + r = uhdev.event([t1]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + # only the second is released + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] != -1 + assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + assert evdev.slots[2][libevdev.EV_ABS.ABS_MT_TRACKING_ID] != -1 + + # wait for the timer to kick in + time.sleep(0.2) + + events = uhdev.next_sync_events() + self.debug_reports([], uhdev, events) + + # now all 3 fingers are released + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + assert evdev.slots[2][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + class TestElanXPS9360(BaseTest.TestWin8Multitouch): def create_device(self): return Digitizer( @@ -2086,3 +2132,12 @@ class Testsynaptics_06cb_ce08(BaseTest.TestPTP): input_info=(BusType.I2C, 0x06CB, 0xCE08), rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 01 09 02 a1 01 85 18 09 01 a1 00 05 09 19 01 29 03 46 00 00 15 00 25 01 75 01 95 03 81 02 95 05 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 06 00 ff 09 02 a1 01 85 20 09 01 a1 00 09 03 15 00 26 ff 00 35 00 46 ff 00 75 08 95 05 81 02 c0 c0 05 0d 09 05 a1 01 85 03 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 08 09 55 09 59 75 04 95 02 25 0f b1 02 85 0d 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 06 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 14 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 14 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 45 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 45 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 03 b1 02 85 0e 09 07 15 00 26 ff 00 75 08 95 01 b1 02 c0", ) + +class Testsynaptics_06cb_ce26(TestWin8TSConfidence): + def create_device(self): + return PTP( + "uhid test synaptics_06cb_ce26", + max_contacts=5, + input_info=(BusType.I2C, 0x06CB, 0xCE26), + rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 05 a1 01 85 03 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 45 05 75 10 55 0e 65 11 09 30 35 00 46 64 04 95 01 81 02 46 a2 02 26 29 03 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 45 05 75 10 55 0e 65 11 09 30 35 00 46 64 04 95 01 81 02 46 a2 02 26 29 03 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 45 05 75 10 55 0e 65 11 09 30 35 00 46 64 04 95 01 81 02 46 a2 02 26 29 03 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 45 05 75 10 55 0e 65 11 09 30 35 00 46 64 04 95 01 81 02 46 a2 02 26 29 03 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 45 05 75 10 55 0e 65 11 09 30 35 00 46 64 04 95 01 81 02 46 a2 02 26 29 03 09 31 81 02 c0 05 0d 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 08 09 55 09 59 75 04 95 02 25 0f b1 02 85 0d 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 06 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 14 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 14 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 3d 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 3d 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 03 b1 02 85 0e 09 07 15 00 26 ff 00 75 08 95 01 b1 02 c0", + ) From 1f3eeb809daf5196b78653a94fc6de5ae6aa6399 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:27 +0200 Subject: [PATCH 068/295] HID: intel-thc-hid: Remove redundant pm_runtime_mark_last_busy() calls JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit c430f56be32ec098999158717d04e64da5edce06 Author: Sakari Ailus Date: Mon Oct 27 14:01:23 2025 +0200 HID: intel-thc-hid: Remove redundant pm_runtime_mark_last_busy() calls pm_runtime_put_autosuspend(), pm_runtime_put_sync_autosuspend(), pm_runtime_autosuspend() and pm_request_autosuspend() now include a call to pm_runtime_mark_last_busy(). Remove the now-reduntant explicit call to pm_runtime_mark_last_busy(). Signed-off-by: Sakari Ailus Reviewed-by: Even Xu Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c | 2 -- drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c | 1 - drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c | 2 -- drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c | 1 - 4 files changed, 6 deletions(-) diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c index 0156ab391778..cfda66ee4895 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c @@ -344,7 +344,6 @@ exit: if (try_recover(qcdev)) qcdev->state = QUICKI2C_DISABLED; - pm_runtime_mark_last_busy(qcdev->dev); pm_runtime_put_autosuspend(qcdev->dev); return IRQ_HANDLED; @@ -735,7 +734,6 @@ static int quicki2c_probe(struct pci_dev *pdev, const struct pci_device_id *id) /* Enable runtime power management */ pm_runtime_use_autosuspend(qcdev->dev); pm_runtime_set_autosuspend_delay(qcdev->dev, DEFAULT_AUTO_SUSPEND_DELAY_MS); - pm_runtime_mark_last_busy(qcdev->dev); pm_runtime_put_noidle(qcdev->dev); pm_runtime_put_autosuspend(qcdev->dev); diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c index 5c3ec95bb3fd..834a537b6780 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c @@ -72,7 +72,6 @@ static int quicki2c_hid_raw_request(struct hid_device *hid, break; } - pm_runtime_mark_last_busy(qcdev->dev); pm_runtime_put_autosuspend(qcdev->dev); return ret; diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c index 14cabd5dc6dd..ad6bd59963b2 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c @@ -339,7 +339,6 @@ end: if (try_recover(qsdev)) qsdev->state = QUICKSPI_DISABLED; - pm_runtime_mark_last_busy(qsdev->dev); pm_runtime_put_autosuspend(qsdev->dev); return IRQ_HANDLED; @@ -674,7 +673,6 @@ static int quickspi_probe(struct pci_dev *pdev, /* Enable runtime power management */ pm_runtime_use_autosuspend(qsdev->dev); pm_runtime_set_autosuspend_delay(qsdev->dev, DEFAULT_AUTO_SUSPEND_DELAY_MS); - pm_runtime_mark_last_busy(qsdev->dev); pm_runtime_put_noidle(qsdev->dev); pm_runtime_put_autosuspend(qsdev->dev); diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c index ad52e402c28a..82c72bfa2795 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c @@ -71,7 +71,6 @@ static int quickspi_hid_raw_request(struct hid_device *hid, break; } - pm_runtime_mark_last_busy(qsdev->dev); pm_runtime_put_autosuspend(qsdev->dev); return ret; From 04182d997d9ad1189abd8aed0fbeea2005a6b61f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:29 +0200 Subject: [PATCH 069/295] HID: lenovo: fixup Lenovo Yoga Slim 7x Keyboard rdesc JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit a45f15808fb753a14c6041fd1e5bef5d552bd2e3 Author: Lauri Tirkkonen Date: Sat Oct 18 15:35:15 2025 +0900 HID: lenovo: fixup Lenovo Yoga Slim 7x Keyboard rdesc The keyboard of this device has the following in its report description for Usage (Keyboard) in Collection (Application): # 0x15, 0x00, // Logical Minimum (0) 52 # 0x25, 0x65, // Logical Maximum (101) 54 # 0x05, 0x07, // Usage Page (Keyboard) 56 # 0x19, 0x00, // Usage Minimum (0) 58 # 0x29, 0xdd, // Usage Maximum (221) 60 # 0x81, 0x00, // Input (Data,Arr,Abs) 62 Since the Usage Min/Max range exceeds the Logical Min/Max range, keypresses outside the Logical range are not recognized. This includes, for example, the Japanese language keyboard variant's keys for |, _ and \. Fixup the report description to make the Logical range match the Usage range, fixing the interpretation of keypresses above 101 on this device. Signed-off-by: Lauri Tirkkonen Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-lenovo.c | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 5e6e87487205..aa3ceef3016e 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -718,6 +718,7 @@ #define USB_DEVICE_ID_ITE_LENOVO_YOGA2 0x8350 #define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a #define USB_DEVICE_ID_ITE_LENOVO_YOGA900 0x8396 +#define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD 0x8987 #define USB_DEVICE_ID_ITE8595 0x8595 #define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50 diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index 654879814f97..9cc3e029e9f6 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -148,6 +148,14 @@ static const __u8 lenovo_tpIIbtkbd_need_fixup_collection[] = { 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ }; +static const __u8 lenovo_yoga7x_kbd_need_fixup_collection[] = { + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x65, // Logical Maximum (101) + 0x05, 0x07, // Usage Page (Keyboard) + 0x19, 0x00, // Usage Minimum (0) + 0x29, 0xDD, // Usage Maximum (221) +}; + static const __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { @@ -177,6 +185,13 @@ static const __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc, rdesc[260] = 0x01; /* report count (2) = 0x01 */ } break; + case I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD: + if (*rsize == 176 && + memcmp(&rdesc[52], lenovo_yoga7x_kbd_need_fixup_collection, + sizeof(lenovo_yoga7x_kbd_need_fixup_collection)) == 0) { + rdesc[55] = rdesc[61]; // logical maximum = usage maximum + } + break; } return rdesc; } @@ -1538,6 +1553,8 @@ static const struct hid_device_id lenovo_devices[] = { USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X12_TAB) }, { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X12_TAB2) }, + { HID_DEVICE(BUS_I2C, HID_GROUP_GENERIC, + USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD) }, { } }; From 400f8153496bae7cfa81fc583bcd138a22cf51c4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:31 +0200 Subject: [PATCH 070/295] HID: apple: Add SONiX AK870 PRO to non_apple_keyboards quirk list JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 743c81cdc98fd4fef62a89eb70efff994112c2d9 Author: April Grimoire Date: Thu Oct 23 00:37:26 2025 +0800 HID: apple: Add SONiX AK870 PRO to non_apple_keyboards quirk list SONiX AK870 PRO keyboard pretends to be an apple keyboard by VID:PID, rendering function keys not treated properly. Despite being a SONiX USB DEVICE, it uses a different name, so adding it to the list. Signed-off-by: April Grimoire Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-apple.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 61404d7a43ee..57da4f86a9fa 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -355,6 +355,7 @@ static const struct apple_key_translation swapped_fn_leftctrl_keys[] = { static const struct apple_non_apple_keyboard non_apple_keyboards[] = { { "SONiX USB DEVICE" }, + { "SONiX AK870 PRO" }, { "Keychron" }, { "AONE" }, { "GANSS" }, From 3aac6045fb5a33fbde8ef79535013c40e143909a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:32 +0200 Subject: [PATCH 071/295] HID: amd_sfh: Stop sensor before starting JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 4d3a13afa8b64dc49293b3eab3e7beac11072c12 Author: Mario Limonciello (AMD) Date: Mon Oct 20 10:50:42 2025 -0500 HID: amd_sfh: Stop sensor before starting Titas reports that the accelerometer sensor on their laptop only works after a warm boot or unloading/reloading the amd-sfh kernel module. Presumably the sensor is in a bad state on cold boot and failing to start, so explicitly stop it before starting. Cc: stable@vger.kernel.org Fixes: 93ce5e0231d79 ("HID: amd_sfh: Implement SFH1.1 functionality") Reported-by: Titas Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220670 Tested-by: Titas Signed-off-by: Mario Limonciello (AMD) Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c index 0a9b44ce4904..b0bab2a1ddcc 100644 --- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c +++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c @@ -194,6 +194,8 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata) if (rc) goto cleanup; + mp2_ops->stop(privdata, cl_data->sensor_idx[i]); + amd_sfh_wait_for_response(privdata, cl_data->sensor_idx[i], DISABLE_SENSOR); writel(0, privdata->mmio + amd_get_p2c_val(privdata, 0)); mp2_ops->start(privdata, info); status = amd_sfh_wait_for_response From 61fda63fab9eed3a45f7e16e423b7ebfdcde9275 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:34 +0200 Subject: [PATCH 072/295] HID: hid-input: Extend Elan ignore battery quirk to USB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 534ca75e8e3b713514b3f2da85dab96831cf5b2a Author: Mario Limonciello (AMD) Date: Thu Oct 30 11:06:25 2025 -0500 HID: hid-input: Extend Elan ignore battery quirk to USB USB Elan devices have the same problem as the I2C ones with a fake battery device showing up. Reviewed-by: Hans de Goede Reported-by: André Barata Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220722 Signed-off-by: Mario Limonciello (AMD) Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-input.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index e56e7de53279..2bbb645c2ff4 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -399,10 +399,11 @@ static const struct hid_device_id hid_battery_quirks[] = { { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_CHROMEBOOK_TROGDOR_POMPOM), HID_BATTERY_QUIRK_AVOID_QUERY }, /* - * Elan I2C-HID touchscreens seem to all report a non present battery, - * set HID_BATTERY_QUIRK_IGNORE for all Elan I2C-HID devices. + * Elan HID touchscreens seem to all report a non present battery, + * set HID_BATTERY_QUIRK_IGNORE for all Elan I2C and USB HID devices. */ { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_IGNORE }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_IGNORE }, {} }; From 91faf4848b834de01eb807115558c736a2ed7c0e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:36 +0200 Subject: [PATCH 073/295] HID: nintendo: Reduce JC_SUBCMD_RATE_MAX_ATTEMPTS JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 2295657ac30a5d40564f76dcb107730feacceef1 Author: Willy Huang Date: Thu Oct 30 14:37:04 2025 +0800 HID: nintendo: Reduce JC_SUBCMD_RATE_MAX_ATTEMPTS The JC_SUBCMD_RATE_MAX_ATTEMPTS constant is currently set to 500. In a worst-case scenario where all attempts consistently fail, this could cause the loop to block for up to 60000 ms (500 * 60ms * 2, including the additional retry after a timeout). This change lowers the maximum potential blocking time to 3000 ms (25 * 60ms * 2), improving system responsiveness and efficiency. Signed-off-by: Willy Huang Reviewed-by: Daniel J. Ogorchock Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-nintendo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index c2849a541f65..342cd6893502 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -819,7 +819,7 @@ static void joycon_wait_for_input_report(struct joycon_ctlr *ctlr) #define JC_INPUT_REPORT_MAX_DELTA 17 #define JC_SUBCMD_TX_OFFSET_MS 4 #define JC_SUBCMD_VALID_DELTA_REQ 3 -#define JC_SUBCMD_RATE_MAX_ATTEMPTS 500 +#define JC_SUBCMD_RATE_MAX_ATTEMPTS 25 #define JC_SUBCMD_RATE_LIMITER_USB_MS 20 #define JC_SUBCMD_RATE_LIMITER_BT_MS 60 #define JC_SUBCMD_RATE_LIMITER_MS(ctlr) ((ctlr)->hdev->bus == BUS_USB ? JC_SUBCMD_RATE_LIMITER_USB_MS : JC_SUBCMD_RATE_LIMITER_BT_MS) From ea0801a80b78b2f0297e90ea700061d61513f550 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:38 +0200 Subject: [PATCH 074/295] HID: nintendo: add WQ_PERCPU to alloc_workqueue users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 06416555c883e3ffabcc62ad39617c23d6b2f012 Author: Marco Crivellari Date: Fri Nov 7 14:24:43 2025 +0100 HID: nintendo: add WQ_PERCPU to alloc_workqueue users Currently if a user enqueues a work item using schedule_delayed_work() the used wq is "system_wq" (per-cpu wq) while queue_delayed_work() use WORK_CPU_UNBOUND (used when a cpu is not specified). The same applies to schedule_work() that is using system_wq and queue_work(), that makes use again of WORK_CPU_UNBOUND. This lack of consistency cannot be addressed without refactoring the API. alloc_workqueue() treats all queues as per-CPU by default, while unbound workqueues must opt-in via WQ_UNBOUND. This default is suboptimal: most workloads benefit from unbound queues, allowing the scheduler to place worker threads where they’re needed and reducing noise when CPUs are isolated. This continues the effort to refactor workqueue APIs, which began with the introduction of new workqueues and a new alloc_workqueue flag in: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") This change adds a new WQ_PERCPU flag to explicitly request alloc_workqueue() to be per-cpu when WQ_UNBOUND has not been specified. With the introduction of the WQ_PERCPU flag (equivalent to !WQ_UNBOUND), any alloc_workqueue() caller that doesn’t explicitly specify WQ_UNBOUND must now use WQ_PERCPU. Once migration is complete, WQ_UNBOUND can be removed and unbound will become the implicit default. Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-nintendo.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index 342cd6893502..7ac9217d9096 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -2648,7 +2648,8 @@ static int nintendo_hid_probe(struct hid_device *hdev, init_waitqueue_head(&ctlr->wait); spin_lock_init(&ctlr->lock); ctlr->rumble_queue = alloc_workqueue("hid-nintendo-rumble_wq", - WQ_FREEZABLE | WQ_MEM_RECLAIM, 0); + WQ_FREEZABLE | WQ_MEM_RECLAIM | WQ_PERCPU, + 0); if (!ctlr->rumble_queue) { ret = -ENOMEM; goto err; From adac70ad2f33b8b692a1c86c5fa6147c6796bf2f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:40 +0200 Subject: [PATCH 075/295] Documentation: hid-alps: Fix packet format section headings JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit c6405fb9111d4fb25cc64cdebbe23d982a8a0e83 Author: Bagas Sanjaya Date: Fri Oct 24 17:39:33 2025 +0700 Documentation: hid-alps: Fix packet format section headings In "Packet Format" section, only "StickPointer data byte" subsection heading is shown as such in htmldocs output; "Touchpad data byte" one is in normal paragraph on the other hand. Properly format the headings to be consistent. Fixes: 2562756dde55 ("HID: add Alps I2C HID Touchpad-Stick support") Signed-off-by: Bagas Sanjaya Reviewed-by: Randy Dunlap Tested-by: Randy Dunlap Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- Documentation/hid/hid-alps.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/hid/hid-alps.rst b/Documentation/hid/hid-alps.rst index 94382bb0ada4..3a22254e4346 100644 --- a/Documentation/hid/hid-alps.rst +++ b/Documentation/hid/hid-alps.rst @@ -104,8 +104,10 @@ Read value is stored in Value Byte. Packet Format +------------- + Touchpad data byte ------------------- +~~~~~~~~~~~~~~~~~~ ======= ======= ======= ======= ======= ======= ======= ======= ===== @@ -156,7 +158,7 @@ Zsn_6-0(7bit): StickPointer data byte ----------------------- +~~~~~~~~~~~~~~~~~~~~~~ ======= ======= ======= ======= ======= ======= ======= ======= ===== - b7 b6 b5 b4 b3 b2 b1 b0 From bbe705f464dae2afd951ec7a9e58c753c7e8d9c8 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:06:42 +0200 Subject: [PATCH 076/295] Documentation: hid-alps: Format DataByte* subsection headings JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit e64e190044de8876b6434652388c13e457cd8a0c Author: Bagas Sanjaya Date: Fri Oct 24 17:39:34 2025 +0700 Documentation: hid-alps: Format DataByte* subsection headings "Command Read/Write" section has two DataByte* subsections describing command bytes format. Add markup to these subsection heading texts. Signed-off-by: Bagas Sanjaya Reviewed-by: Randy Dunlap Tested-by: Randy Dunlap Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- Documentation/hid/hid-alps.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/hid/hid-alps.rst b/Documentation/hid/hid-alps.rst index 3a22254e4346..4a22a357f00c 100644 --- a/Documentation/hid/hid-alps.rst +++ b/Documentation/hid/hid-alps.rst @@ -69,6 +69,7 @@ To read/write to RAM, need to send a command to the device. The command format is as below. DataByte(SET_REPORT) +~~~~~~~~~~~~~~~~~~~~ ===== ====================== Byte1 Command Byte @@ -89,6 +90,7 @@ Value Byte is writing data when you send the write commands. When you read RAM, there is no meaning. DataByte(GET_REPORT) +~~~~~~~~~~~~~~~~~~~~ ===== ====================== Byte1 Response Byte From e61712f0c91f99806b3c74bcd334bf3c52eb53b0 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:04 +0200 Subject: [PATCH 077/295] HID: winwing: Improve Orion2 throttle support JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit d43ae847cb53ef741232a259c1a05ba53ae9a821 Author: Ivan Gorinov Date: Sun Oct 12 20:50:54 2025 +0000 HID: winwing: Improve Orion2 throttle support Add support for Orion2 throttle configurations with more than 32 buttons on the grip handle (this means the device reports more than 80 buttons). Map additional button codes to KEY_MACRO1 .. KEY_MACRO28. Make the module simpler, removing report descriptor fixup. [jkosina@suse.com: fix changelog] Signed-off-by: Ivan Gorinov Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/Kconfig | 2 + drivers/hid/hid-winwing.c | 171 +++++++++++++++++++++++--------------- 2 files changed, 107 insertions(+), 66 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 986de05a9787..b0f4529ae739 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1318,6 +1318,8 @@ config HID_WINWING help Support for WinWing Orion2 throttle base with the following grips: + * TGRIP-15E + * TGRIP-15EX * TGRIP-16EX * TGRIP-18 diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c index d4afbbd27807..ab65dc12d1e0 100644 --- a/drivers/hid/hid-winwing.c +++ b/drivers/hid/hid-winwing.c @@ -37,6 +37,7 @@ struct winwing_drv_data { struct hid_device *hdev; __u8 *report_buf; struct mutex lock; + int map_more_buttons; unsigned int num_leds; struct winwing_led leds[]; }; @@ -81,12 +82,10 @@ static int winwing_init_led(struct hid_device *hdev, int ret; int i; - size_t data_size = struct_size(data, leds, 3); - - data = devm_kzalloc(&hdev->dev, data_size, GFP_KERNEL); + data = hid_get_drvdata(hdev); if (!data) - return -ENOMEM; + return -EINVAL; data->report_buf = devm_kmalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL); @@ -106,6 +105,7 @@ static int winwing_init_led(struct hid_device *hdev, "%s::%s", dev_name(&input->dev), info->led_name); + if (!led->cdev.name) return -ENOMEM; @@ -114,14 +114,98 @@ static int winwing_init_led(struct hid_device *hdev, return ret; } - hid_set_drvdata(hdev, data); - return ret; } +static int winwing_map_button(int button, int map_more_buttons) +{ + if (button < 1) + return KEY_RESERVED; + + if (button > 112) + return KEY_RESERVED; + + if (button <= 16) { + /* + * Grip buttons [1 .. 16] are mapped to + * key codes BTN_TRIGGER .. BTN_DEAD + */ + return (button - 1) + BTN_JOYSTICK; + } + + if (button >= 65) { + /* + * Base buttons [65 .. 112] are mapped to + * key codes BTN_TRIGGER_HAPPY17 .. KEY_MAX + */ + return (button - 65) + BTN_TRIGGER_HAPPY17; + } + + if (!map_more_buttons) { + /* + * Not mapping numbers [33 .. 64] which + * are not assigned to any real buttons + */ + if (button >= 33) + return KEY_RESERVED; + /* + * Grip buttons [17 .. 32] are mapped to + * BTN_TRIGGER_HAPPY1 .. BTN_TRIGGER_HAPPY16 + */ + return (button - 17) + BTN_TRIGGER_HAPPY1; + } + + if (button >= 49) { + /* + * Grip buttons [49 .. 64] are mapped to + * BTN_TRIGGER_HAPPY1 .. BTN_TRIGGER_HAPPY16 + */ + return (button - 49) + BTN_TRIGGER_HAPPY1; + } + + /* + * Grip buttons [17 .. 44] are mapped to + * key codes KEY_MACRO1 .. KEY_MACRO28; + * also mapping numbers [45 .. 48] which + * are not assigned to any real buttons. + */ + return (button - 17) + KEY_MACRO1; +} + +static int winwing_input_mapping(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + struct winwing_drv_data *data; + int code = KEY_RESERVED; + int button = 0; + + data = hid_get_drvdata(hdev); + + if (!data) + return -EINVAL; + + if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON) + return 0; + + if (field->application != HID_GD_JOYSTICK) + return 0; + + /* Button numbers start with 1 */ + button = usage->hid & HID_USAGE; + + code = winwing_map_button(button, data->map_more_buttons); + + hid_map_usage(hi, usage, bit, max, EV_KEY, code); + + return 1; +} + static int winwing_probe(struct hid_device *hdev, const struct hid_device_id *id) { + struct winwing_drv_data *data; + size_t data_size = struct_size(data, leds, 3); int ret; ret = hid_parse(hdev); @@ -130,6 +214,15 @@ static int winwing_probe(struct hid_device *hdev, return ret; } + data = devm_kzalloc(&hdev->dev, data_size, GFP_KERNEL); + + if (!data) + return -ENOMEM; + + data->map_more_buttons = id->driver_data; + + hid_set_drvdata(hdev, data); + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { hid_err(hdev, "hw start failed\n"); @@ -152,64 +245,11 @@ static int winwing_input_configured(struct hid_device *hdev, return ret; } -static const __u8 original_rdesc_buttons[] = { - 0x05, 0x09, 0x19, 0x01, 0x29, 0x6F, - 0x15, 0x00, 0x25, 0x01, 0x35, 0x00, - 0x45, 0x01, 0x75, 0x01, 0x95, 0x6F, - 0x81, 0x02, 0x75, 0x01, 0x95, 0x01, - 0x81, 0x01 -}; - -/* - * HID report descriptor shows 111 buttons, which exceeds maximum - * number of buttons (80) supported by Linux kernel HID subsystem. - * - * This module skips numbers 32-63, unused on some throttle grips. - */ - -static const __u8 *winwing_report_fixup(struct hid_device *hdev, __u8 *rdesc, - unsigned int *rsize) -{ - int sig_length = sizeof(original_rdesc_buttons); - int unused_button_numbers = 32; - - if (*rsize < 34) - return rdesc; - - if (memcmp(rdesc + 8, original_rdesc_buttons, sig_length) == 0) { - - /* Usage Maximum */ - rdesc[13] -= unused_button_numbers; - - /* Report Count for buttons */ - rdesc[25] -= unused_button_numbers; - - /* Report Count for padding [HID1_11, 6.2.2.9] */ - rdesc[31] += unused_button_numbers; - - hid_info(hdev, "winwing descriptor fixed\n"); - } - - return rdesc; -} - -static int winwing_raw_event(struct hid_device *hdev, - struct hid_report *report, u8 *raw_data, int size) -{ - if (size >= 15) { - /* Skip buttons 32 .. 63 */ - memmove(raw_data + 5, raw_data + 9, 6); - - /* Clear the padding */ - memset(raw_data + 11, 0, 4); - } - - return 0; -} - static const struct hid_device_id winwing_devices[] = { - { HID_USB_DEVICE(0x4098, 0xbe62) }, /* TGRIP-18 */ - { HID_USB_DEVICE(0x4098, 0xbe68) }, /* TGRIP-16EX */ + { HID_USB_DEVICE(0x4098, 0xbd65), .driver_data = 1 }, /* TGRIP-15E */ + { HID_USB_DEVICE(0x4098, 0xbd64), .driver_data = 1 }, /* TGRIP-15EX */ + { HID_USB_DEVICE(0x4098, 0xbe68), .driver_data = 0 }, /* TGRIP-16EX */ + { HID_USB_DEVICE(0x4098, 0xbe62), .driver_data = 0 }, /* TGRIP-18 */ {} }; @@ -218,10 +258,9 @@ MODULE_DEVICE_TABLE(hid, winwing_devices); static struct hid_driver winwing_driver = { .name = "winwing", .id_table = winwing_devices, - .probe = winwing_probe, .input_configured = winwing_input_configured, - .report_fixup = winwing_report_fixup, - .raw_event = winwing_raw_event, + .input_mapping = winwing_input_mapping, + .probe = winwing_probe, }; module_hid_driver(winwing_driver); From 69d010bb387544970ff93ac8262774e6ce634b07 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:05 +0200 Subject: [PATCH 078/295] HID: elecom: Add support for ELECOM M-XT3URBK (018F) JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit cdcbb8e8d10f656642380ee13516290437b52b36 Author: Naoki Ueki Date: Mon Nov 3 21:16:45 2025 +0900 HID: elecom: Add support for ELECOM M-XT3URBK (018F) The ELECOM M-XT3URBK trackball has an additional device ID (0x018F), which shares the same report descriptor as the existing device (0x00FB). However, the driver does not currently recognize this new ID, resulting in only five buttons being functional. This patch adds the new device ID so that all six buttons work properly. Signed-off-by: Naoki Ueki Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-elecom.c | 6 ++++-- drivers/hid/hid-ids.h | 3 ++- drivers/hid/hid-quirks.c | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c index 69771fd35006..981d1b6e9658 100644 --- a/drivers/hid/hid-elecom.c +++ b/drivers/hid/hid-elecom.c @@ -75,7 +75,8 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, */ mouse_button_fixup(hdev, rdesc, *rsize, 20, 28, 22, 14, 8); break; - case USB_DEVICE_ID_ELECOM_M_XT3URBK: + case USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB: + case USB_DEVICE_ID_ELECOM_M_XT3URBK_018F: case USB_DEVICE_ID_ELECOM_M_XT3DRBK: case USB_DEVICE_ID_ELECOM_M_XT4DRBK: /* @@ -119,7 +120,8 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, static const struct hid_device_id elecom_devices[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index aa3ceef3016e..3d697d37806b 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -449,7 +449,8 @@ #define USB_VENDOR_ID_ELECOM 0x056e #define USB_DEVICE_ID_ELECOM_BM084 0x0061 #define USB_DEVICE_ID_ELECOM_M_XGL20DLBK 0x00e6 -#define USB_DEVICE_ID_ELECOM_M_XT3URBK 0x00fb +#define USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB 0x00fb +#define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f #define USB_DEVICE_ID_ELECOM_M_XT3DRBK 0x00fc #define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd #define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 22760ac50f2d..c89a015686c0 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -410,7 +410,8 @@ static const struct hid_device_id hid_have_special_driver[] = { #if IS_ENABLED(CONFIG_HID_ELECOM) { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, From d7c61884bede5a626c144203e8a7754be1255db0 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:06 +0200 Subject: [PATCH 079/295] HID: corsair-void: Use %pe for printing PTR_ERR JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 4e127a74786fa9573a32c8aa4bbf69ef78c3232a Author: Stuart Hayhurst Date: Mon Nov 3 14:21:13 2025 +0000 HID: corsair-void: Use %pe for printing PTR_ERR Use %pe to print a PTR_ERR to silence a cocci warning Reported-by: kernel test robot Reported-by: Julia Lawall Closes: https://lore.kernel.org/r/202510300342.WtPn2jF3-lkp@intel.com/ Signed-off-by: Stuart Hayhurst Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-corsair-void.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-corsair-void.c b/drivers/hid/hid-corsair-void.c index fee134a7eba3..5e9a5b8f7f16 100644 --- a/drivers/hid/hid-corsair-void.c +++ b/drivers/hid/hid-corsair-void.c @@ -553,9 +553,8 @@ static void corsair_void_add_battery(struct corsair_void_drvdata *drvdata) if (IS_ERR(new_supply)) { hid_err(drvdata->hid_dev, - "failed to register battery '%s' (reason: %ld)\n", - drvdata->battery_desc.name, - PTR_ERR(new_supply)); + "failed to register battery '%s' (reason: %pe)\n", + drvdata->battery_desc.name, new_supply); return; } From 64dac62a379daff8555895ba2e8fc1b54b0fd716 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:06 +0200 Subject: [PATCH 080/295] HID: pidff: Fix needs_playback check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 commit 9d7b89a1028230315a8999cfea7795fbe84f62cc Author: Tomasz Pakuła Date: Mon Nov 3 21:02:43 2025 +0100 HID: pidff: Fix needs_playback check A small bug made it's way here when rewriting code to Linux quality. Currently, if an effect is not infinite and a program requests it's playback with the same number of loops, the play command won't be fired and if an effect is infinite, the spam will continue. We want every playback update for non-infinite effects and only some for infinite (detecting when a program requests stop with 0 which will be different than previous value which is usually 1 or 255). Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index edd61ef50e16..95377c5f6335 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -806,8 +806,8 @@ static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum) static int pidff_needs_playback(struct pidff_device *pidff, int effect_id, int n) { - return pidff->effect[effect_id].is_infinite || - pidff->effect[effect_id].loop_count != n; + return !pidff->effect[effect_id].is_infinite || + pidff->effect[effect_id].loop_count != n; } /* From da56bd186329791f59f17f5b2840bb191a60faef Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:07 +0200 Subject: [PATCH 081/295] HID: bpf: Add support for the Inspiroy 2M JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 501ea61e2933ee61933ff2f8dd2844e154dd2746 Author: Benjamin Tissoires Date: Tue Nov 18 18:16:22 2025 +0100 HID: bpf: Add support for the Inspiroy 2M 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 Signed-off-by: Benjamin Tissoires Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/167 Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../hid/bpf/progs/Huion__Inspiroy-2-M.bpf.c | 563 ++++++++++++++++++ 1 file changed, 563 insertions(+) create mode 100644 drivers/hid/bpf/progs/Huion__Inspiroy-2-M.bpf.c 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 000000000000..183d408d893a --- /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 + +#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"; From 8564e4943e982a9e8f189148ea2b625a681b273b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:08 +0200 Subject: [PATCH 082/295] HID: bpf: add support for Huion Kamvas 13 (Gen 3) (model GS1333) JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 5f2e058df65cac37aeeeb74fef650edd51fe1882 Author: Benjamin Tissoires Date: Tue Nov 18 18:16:23 2025 +0100 HID: bpf: add support for Huion Kamvas 13 (Gen 3) (model GS1333) 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 Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/162 Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../hid/bpf/progs/Huion__Kamvas13Gen3.bpf.c | 1395 +++++++++++++++++ 1 file changed, 1395 insertions(+) create mode 100644 drivers/hid/bpf/progs/Huion__Kamvas13Gen3.bpf.c 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 000000000000..b63f9a48ea45 --- /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 + +#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"; From 2c41d79ba2de09037a01b3d7965fad80f30b99a5 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:09 +0200 Subject: [PATCH 083/295] HID: bpf: support for Huion Kamvas 16 Gen 3 JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 0412be1b8125204a15930128c2a1ed73b0f59f84 Author: Benjamin Tissoires Date: Tue Nov 18 18:16:24 2025 +0100 HID: bpf: support for Huion Kamvas 16 Gen 3 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 Signed-off-by: Benjamin Tissoires Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/207 Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../hid/bpf/progs/Huion__Kamvas16Gen3.bpf.c | 724 ++++++++++++++++++ 1 file changed, 724 insertions(+) create mode 100644 drivers/hid/bpf/progs/Huion__Kamvas16Gen3.bpf.c 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 000000000000..ac66c6e65eb4 --- /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 + +#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"; From 04369ba7ce22261418fedcd0cdfbeb7bb6bb1e38 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:11 +0200 Subject: [PATCH 084/295] HID: bpf: Add fixup for Logitech SpaceNavigator variants JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 029dff1c31c488e85c666d5e7e992700cd57462b Author: Benjamin Tissoires Date: Tue Nov 18 18:16:25 2025 +0100 HID: bpf: Add fixup for Logitech SpaceNavigator variants 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 Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/181 Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../bpf/progs/Logitech__SpaceNavigator.bpf.c | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 drivers/hid/bpf/progs/Logitech__SpaceNavigator.bpf.c 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 000000000000..b17719d6d9c7 --- /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 + +#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"; From 6e2f8a2c54e32d2b4bb1ca96dad1742b474ce5ee Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:12 +0200 Subject: [PATCH 085/295] HID: bpf: Add support for the Waltop Batteryless Tablet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 040adbe80135af3de7d924ea39db4bfabf57b66f Author: Benjamin Tissoires Date: Tue Nov 18 18:16:26 2025 +0100 HID: bpf: Add support for the Waltop Batteryless Tablet 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 Signed-off-by: Jan Felix Langenbach Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/200 Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../progs/WALTOP__Batteryless-Tablet.bpf.c | 321 ++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 drivers/hid/bpf/progs/WALTOP__Batteryless-Tablet.bpf.c 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 000000000000..156d75af516d --- /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 + +#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"; From 02ba3b5e35bcc760b8620705a5f4ffedae720e4e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:14 +0200 Subject: [PATCH 086/295] HID: bpf: Add support for the XP-Pen Deco 01 V3 JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 8ba327d50263a026a41cc891fc8c09689c4b95e2 Author: Benjamin Tissoires Date: Tue Nov 18 18:16:27 2025 +0100 HID: bpf: Add support for the XP-Pen Deco 01 V3 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 Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/185 Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/progs/XPPen__Deco01V3.bpf.c | 305 ++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 drivers/hid/bpf/progs/XPPen__Deco01V3.bpf.c 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 000000000000..2502fcc9ede6 --- /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 + +#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"; From 31e5ef738e74a123d5c65fdb3436de077fc52679 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:16 +0200 Subject: [PATCH 087/295] HID: bpf: Add support for XP-Pen Deco02 JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 88b5468f2cc564d532999c2fa068e158f5123691 Author: Benjamin Tissoires Date: Tue Nov 18 18:16:28 2025 +0100 HID: bpf: Add support for XP-Pen Deco02 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 Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/203 Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/progs/XPPen__Deco02.bpf.c | 359 ++++++++++++++++++++++ 1 file changed, 359 insertions(+) create mode 100644 drivers/hid/bpf/progs/XPPen__Deco02.bpf.c 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 000000000000..4b2549031e56 --- /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 + +#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"; From 3e45298cc47318db9584ac1ea4df0bd27cf6f6ea Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:18 +0200 Subject: [PATCH 088/295] HID: bpf: add heuristics to the Huion Inspiroy 2S eraser button JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 5e3e8f1b44e420800f62c993878bf6ad43adb038 Author: Benjamin Tissoires Date: Tue Nov 18 18:16:29 2025 +0100 HID: bpf: add heuristics to the Huion Inspiroy 2S eraser button 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 Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/195 Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../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 13f64fb49800..79453362bf97 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; - /* Pen reports need nothing done */ + if (force_tip_down_count > 0) { + data[1] |= 0x1; + --force_tip_down_count; + if (tip_state) + force_tip_down_count = 0; + } + + /* 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; From cd47c7161b18a31492222161de91b96721be60bc Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:19 +0200 Subject: [PATCH 089/295] HID: bpf: add the Huion Kamvas 27 Pro JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 71570e8fb760027842c0e748c669d5bf87dfba65 Author: Benjamin Tissoires Date: Tue Nov 18 18:16:30 2025 +0100 HID: bpf: add the Huion Kamvas 27 Pro 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 Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/189 Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- 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 489cb4fcc2cd..5f43e4071848 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); From 3964ba3fefd89fbe24454f1a53829471fa64951a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:21 +0200 Subject: [PATCH 090/295] HID: bpf: fix typo in HID usage table JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 3b86c87f8dcbb8ba3b00d7adf7ccfff086f0f23e Author: Benjamin Tissoires Date: Tue Nov 18 18:16:31 2025 +0100 HID: bpf: fix typo in HID usage table 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 Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/191 Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- 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 9b2a48e4a311..9944ff54d31d 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) From 8a86157d7e945a2b33f2a402db9a6a8c30aa3268 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:24 +0200 Subject: [PATCH 091/295] HID: input: map HID_GD_Z to ABS_DISTANCE for stylus/pen JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 7953794f741e94d30df9dafaaa4c031c85b891d6 Author: Ping Cheng Date: Mon Oct 27 13:37:42 2025 -0700 HID: input: map HID_GD_Z to ABS_DISTANCE for stylus/pen HID_GD_Z is mapped to ABS_Z for stylus and pen in hid-input.c. But HID_GD_Z should be used to report ABS_DISTANCE for stylus and pen as described at: Documentation/input/event-codes.rst#n226 * ABS_DISTANCE: - Used to describe the distance of a tool from an interaction surface. This event should only be emitted while the tool is hovering, meaning in close proximity of the device and while the value of the BTN_TOUCH code is 0. If the input device may be used freely in three dimensions, consider ABS_Z instead. - BTN_TOOL_ should be set to 1 when the tool comes into detectable proximity and set to 0 when the tool leaves detectable proximity. BTN_TOOL_ signals the type of tool that is currently detected by the hardware and is otherwise independent of ABS_DISTANCE and/or BTN_TOUCH. This patch makes the correct mapping. The ABS_DISTANCE is currently not mapped by any HID usage in hid-generic driver. Signed-off-by: Ping Cheng Cc: stable@kernel.org Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-input.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 2bbb645c2ff4..32a96ae2946f 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -878,7 +878,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel switch (usage->hid) { /* These usage IDs map directly to the usage codes. */ - case HID_GD_X: case HID_GD_Y: case HID_GD_Z: + case HID_GD_X: case HID_GD_Y: case HID_GD_RX: case HID_GD_RY: case HID_GD_RZ: if (field->flags & HID_MAIN_ITEM_RELATIVE) map_rel(usage->hid & 0xf); @@ -886,6 +886,22 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel map_abs_clear(usage->hid & 0xf); break; + case HID_GD_Z: + /* HID_GD_Z is mapped to ABS_DISTANCE for stylus/pen */ + if (field->flags & HID_MAIN_ITEM_RELATIVE) { + map_rel(usage->hid & 0xf); + } else { + if (field->application == HID_DG_PEN || + field->physical == HID_DG_PEN || + field->logical == HID_DG_STYLUS || + field->physical == HID_DG_STYLUS || + field->application == HID_DG_DIGITIZER) + map_abs_clear(ABS_DISTANCE); + else + map_abs_clear(usage->hid & 0xf); + } + break; + case HID_GD_WHEEL: if (field->flags & HID_MAIN_ITEM_RELATIVE) { set_bit(REL_WHEEL, input->relbit); From 316325352f960d0b39af9d9d1c0ef3a34482fc5b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:26 +0200 Subject: [PATCH 092/295] selftests/hid-tablet: add ABS_DISTANCE test for stylus/pen JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 10c64d4ff42a9c59981382b0da44783ea76673b8 Author: Ping Cheng Date: Mon Oct 27 13:39:25 2025 -0700 selftests/hid-tablet: add ABS_DISTANCE test for stylus/pen For pen and stylus, the ABS_Z event reports ABS_DISTANCE values in the hid generic kernel driver. This test is to make sure that the assignment is properly done for all pen and stylus tools. Same as tilt, distance is an optional event. Signed-off-by: Benjamin Tissoires Signed-off-by: Ping Cheng Signed-off-by: Tatsunosuke Tobit Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../selftests/hid/tests/test_tablet.py | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tools/testing/selftests/hid/tests/test_tablet.py b/tools/testing/selftests/hid/tests/test_tablet.py index 50d5699812bb..5b9abb616db4 100644 --- a/tools/testing/selftests/hid/tests/test_tablet.py +++ b/tools/testing/selftests/hid/tests/test_tablet.py @@ -452,6 +452,7 @@ class Pen(object): def __init__(self, x, y): self.x = x self.y = y + self.distance = -10 self.tipswitch = False self.tippressure = 15 self.azimuth = 0 @@ -473,6 +474,7 @@ class Pen(object): for i in [ "x", "y", + "distance", "tippressure", "azimuth", "width", @@ -554,6 +556,7 @@ class PenDigitizer(base.UHIDTestDevice): pen.tipswitch = False pen.tippressure = 0 pen.azimuth = 0 + pen.distance = 0 pen.inrange = False pen.width = 0 pen.height = 0 @@ -868,6 +871,29 @@ class BaseTest: state machine.""" self._test_states(state_list, scribble, allow_intermediate_states=True) + @pytest.mark.skip_if_uhdev( + lambda uhdev: "Z" not in uhdev.fields, + "Device not compatible, missing Z usage", + ) + @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"]) + @pytest.mark.parametrize( + "state_list", + [pytest.param(v, id=k) for k, v in PenState.legal_transitions().items()], + ) + def test_z_reported_as_distance(self, state_list, scribble): + """Verify stylus Z values are reported as ABS_DISTANCE.""" + self._test_states(state_list, scribble, allow_intermediate_states=False) + + uhdev = self.uhdev + evdev = uhdev.get_evdev() + p = Pen(0, 0) + uhdev.move_to(p, PenState.PEN_IS_OUT_OF_RANGE, None) + p = Pen(100, 200) + uhdev.move_to(p, PenState.PEN_IS_IN_RANGE, None) + p.distance = -1 + events = self.post(uhdev, p, None) + assert evdev.value[libevdev.EV_ABS.ABS_DISTANCE] == -1 + class GXTP_pen(PenDigitizer): def event(self, pen, test_button): @@ -1292,6 +1318,35 @@ class Huion_Kamvas_Pro_19_256c_006b(PenDigitizer): return rs +class Wacom_2d1f_014b(PenDigitizer): + """ + Pen that reports distance values with HID_GD_Z usage. + """ + def __init__( + self, + name, + rdesc_str=None, + rdesc=None, + application="Pen", + physical="Stylus", + input_info=(BusType.USB, 0x2D1F, 0x014B), + evdev_name_suffix=None, + ): + super().__init__( + name, rdesc_str, rdesc, application, physical, input_info, evdev_name_suffix + ) + + def match_evdev_rule(self, application, evdev): + # there are 2 nodes created by the device, only one matters + return evdev.name.endswith("Stylus") + + def event(self, pen, test_button): + # this device reports the distance through Z + pen.z = pen.distance + + return super().event(pen, test_button) + + ################################################################################ # # Windows 7 compatible devices @@ -1504,3 +1559,19 @@ class TestHuion_Kamvas_Pro_19_256c_006b(BaseTest.TestTablet): rdesc="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", input_info=(BusType.USB, 0x256C, 0x006B), ) + + +################################################################################ +# +# Devices Reporting Distance +# +################################################################################ + + +class TestWacom_2d1f_014b(BaseTest.TestTablet): + def create_device(self): + return Wacom_2d1f_014b( + "uhid test Wacom 2d1f_014b", + rdesc="05 0d 09 02 a1 01 85 02 09 20 a1 00 09 42 09 44 09 45 09 3c 09 5a 09 32 15 00 25 01 75 01 95 06 81 02 95 02 81 03 05 01 09 30 26 88 3e 46 88 3e 65 11 55 0d 75 10 95 01 81 02 09 31 26 60 53 46 60 53 81 02 05 0d 09 30 26 ff 0f 45 00 65 00 55 00 81 02 06 00 ff 09 04 75 08 26 ff 00 46 ff 00 65 11 55 0e 81 02 05 0d 09 3d 75 10 16 d8 dc 26 28 23 36 d8 dc 46 28 23 65 14 81 02 09 3e 81 02 05 01 09 32 16 01 ff 25 00 36 01 ff 45 00 65 11 81 02 05 0d 09 56 15 00 27 ff ff 00 00 35 00 47 ff ff 00 00 66 01 10 55 0c 81 02 45 00 65 00 55 00 c0 09 00 75 08 26 ff 00 b1 12 85 03 09 00 95 12 b1 12 85 05 09 00 95 04 b1 02 85 06 09 00 95 24 b1 02 85 16 09 00 15 00 26 ff 00 95 06 b1 02 85 17 09 00 95 0c b1 02 85 19 09 00 95 01 b1 02 85 0a 09 00 75 08 95 01 15 10 26 ff 00 b1 02 85 1e 09 00 95 10 b1 02 c0 06 00 ff 09 00 a1 01 85 09 05 0d 09 20 a1 00 09 00 15 00 26 ff 00 75 08 95 10 81 02 c0 09 00 95 03 b1 12 c0 06 00 ff 09 02 a1 01 85 07 09 00 96 09 01 b1 02 85 08 09 00 95 03 81 02 09 00 b1 02 85 0e 09 00 96 0a 01 b1 02 c0 05 0d 09 02 a1 01 85 1a 09 20 a1 00 09 42 09 44 09 45 09 3c 09 5a 09 32 15 00 25 01 75 01 95 06 81 02 09 38 25 03 75 02 95 01 81 02 05 01 09 30 26 88 3e 46 88 3e 65 11 55 0d 75 10 95 01 81 02 09 31 26 60 53 46 60 53 81 02 05 0d 09 30 26 ff 0f 46 b0 0f 66 11 e1 55 02 81 02 06 00 ff 09 04 75 08 26 ff 00 46 ff 00 65 11 55 0e 81 02 05 0d 09 3d 75 10 16 d8 dc 26 28 23 36 d8 dc 46 28 23 65 14 81 02 09 3e 81 02 05 01 09 32 16 01 ff 25 00 36 01 ff 45 00 65 11 81 02 05 0d 09 56 15 00 27 ff ff 00 00 35 00 47 ff ff 00 00 66 01 10 55 0c 81 02 45 00 65 00 55 00 c0 c0 06 00 ff 09 00 a1 01 85 1b 05 0d 09 20 a1 00 09 00 26 ff 00 75 08 95 10 81 02 c0 c0", + input_info=(BusType.USB, 0x2D1F, 0x014B), + ) From 72047ab4aa664acee431a367665a8f2005d5492f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:28 +0200 Subject: [PATCH 093/295] HID: logitech-dj: Add support for G Pro X Superlight 2 receiver JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit ae9416f1f4adcc226dc7faa50a587ef338aed410 Author: Nathan Rossi Date: Sat Nov 8 14:13:00 2025 +0000 HID: logitech-dj: Add support for G Pro X Superlight 2 receiver The Logitech G Pro X Superlight 2 has a lightspeed receiver with a product id of 0xc54d, this receiver behaves like the receiver used in the original Logitech G Pro X Superlight (id 0xc547) including the 13 byte mouse reports. This change adds a definition for this receiver id, and a mapping for the recvr_type_gaming_hidpp_ls_1_3 type. With this change in place the receiver now reports the battery status of the connected mouse over wireless as well as exposing the HID interface needed for userspace to perform additional configuration with libratbag/Piper. Signed-off-by: Nathan Rossi Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-logitech-dj.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 3d697d37806b..399e9ec2fd5c 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -918,6 +918,7 @@ #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1 0xc53f #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2 0xc543 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3 0xc547 +#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4 0xc54d #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY 0xc53a #define USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER 0xc548 #define USB_DEVICE_ID_SPACETRAVELLER 0xc623 diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index d66f4807311a..d39a4c1073ad 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -2091,6 +2091,10 @@ static const struct hid_device_id logi_dj_receivers[] = { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3), .driver_data = recvr_type_gaming_hidpp_ls_1_3}, + { /* Logitech lightspeed receiver (0xc54d) */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4), + .driver_data = recvr_type_gaming_hidpp_ls_1_3}, { /* Logitech 27 MHz HID++ 1.0 receiver (0xc513) */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER), From c20e4338197e339d191e131d5a93cb7ac447fab4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:30 +0200 Subject: [PATCH 094/295] HID: logitech-dj: Remove duplicate error logging JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit ca389a55d8b2d86a817433bf82e0602b68c4d541 Author: Hans de Goede Date: Sat Nov 8 22:03:18 2025 +0100 HID: logitech-dj: Remove duplicate error logging logi_dj_recv_query_paired_devices() and logi_dj_recv_switch_to_dj_mode() both have 2 callers which all log an error if the function fails. Move the error logging to inside these 2 functions to remove the duplicated error logging in the callers. While at it also move the logi_dj_recv_send_report() call error handling in logi_dj_recv_switch_to_dj_mode() to directly after the call. That call only fails if the report cannot be found and in that case it does nothing, so the msleep() is not necessary on failures. Fixes: 6f20d3261265 ("HID: logitech-dj: Fix error handling in logi_dj_recv_switch_to_dj_mode()") Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-dj.c | 56 ++++++++++++++--------------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index d39a4c1073ad..58b5bc9cc54d 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -889,7 +889,6 @@ static void delayedwork_callback(struct work_struct *work) struct dj_workitem workitem; unsigned long flags; int count; - int retval; dbg_hid("%s\n", __func__); @@ -926,11 +925,7 @@ static void delayedwork_callback(struct work_struct *work) logi_dj_recv_destroy_djhid_device(djrcv_dev, &workitem); break; case WORKITEM_TYPE_UNKNOWN: - retval = logi_dj_recv_query_paired_devices(djrcv_dev); - if (retval) { - hid_err(djrcv_dev->hidpp, "%s: logi_dj_recv_query_paired_devices error: %d\n", - __func__, retval); - } + logi_dj_recv_query_paired_devices(djrcv_dev); break; case WORKITEM_TYPE_EMPTY: dbg_hid("%s: device list is empty\n", __func__); @@ -1323,8 +1318,10 @@ static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev) djrcv_dev->last_query = jiffies; - if (djrcv_dev->type != recvr_type_dj) - return logi_dj_recv_query_hidpp_devices(djrcv_dev); + if (djrcv_dev->type != recvr_type_dj) { + retval = logi_dj_recv_query_hidpp_devices(djrcv_dev); + goto out; + } dj_report = kzalloc(sizeof(struct dj_report), GFP_KERNEL); if (!dj_report) @@ -1334,6 +1331,10 @@ static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev) dj_report->report_type = REPORT_TYPE_CMD_GET_PAIRED_DEVICES; retval = logi_dj_recv_send_report(djrcv_dev, dj_report); kfree(dj_report); +out: + if (retval < 0) + hid_err(djrcv_dev->hidpp, "%s error:%d\n", __func__, retval); + return retval; } @@ -1359,6 +1360,8 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, (u8)timeout; retval = logi_dj_recv_send_report(djrcv_dev, dj_report); + if (retval) + goto out; /* * Ugly sleep to work around a USB 3.0 bug when the receiver is @@ -1367,11 +1370,6 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, * 50 msec should gives enough time to the receiver to be ready. */ msleep(50); - - if (retval) { - kfree(dj_report); - return retval; - } } /* @@ -1397,7 +1395,12 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, HIDPP_REPORT_SHORT_LENGTH, HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); +out: kfree(dj_report); + + if (retval < 0) + hid_err(hdev, "%s error:%d\n", __func__, retval); + return retval; } @@ -1935,11 +1938,8 @@ static int logi_dj_probe(struct hid_device *hdev, if (has_hidpp) { retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0); - if (retval < 0) { - hid_err(hdev, "%s: logi_dj_recv_switch_to_dj_mode returned error:%d\n", - __func__, retval); + if (retval < 0) goto switch_to_dj_mode_fail; - } } /* This is enabling the polling urb on the IN endpoint */ @@ -1957,15 +1957,11 @@ static int logi_dj_probe(struct hid_device *hdev, spin_lock_irqsave(&djrcv_dev->lock, flags); djrcv_dev->ready = true; spin_unlock_irqrestore(&djrcv_dev->lock, flags); - retval = logi_dj_recv_query_paired_devices(djrcv_dev); - if (retval < 0) { - hid_err(hdev, "%s: logi_dj_recv_query_paired_devices error:%d\n", - __func__, retval); - /* - * This can happen with a KVM, let the probe succeed, - * logi_dj_recv_queue_unknown_work will retry later. - */ - } + /* + * This can fail with a KVM. Ignore errors to let the probe + * succeed, logi_dj_recv_queue_unknown_work will retry later. + */ + logi_dj_recv_query_paired_devices(djrcv_dev); } return 0; @@ -1982,18 +1978,12 @@ hid_hw_start_fail: #ifdef CONFIG_PM static int logi_dj_reset_resume(struct hid_device *hdev) { - int retval; struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev); if (!djrcv_dev || djrcv_dev->hidpp != hdev) return 0; - retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0); - if (retval < 0) { - hid_err(hdev, "%s: logi_dj_recv_switch_to_dj_mode returned error:%d\n", - __func__, retval); - } - + logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0); return 0; } #endif From 3b68f4856f9b20423b4e3add488bb988b82028d8 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:32 +0200 Subject: [PATCH 095/295] HID: logitech-dj: Fix probe failure when used with KVM JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 36dcfa468525336fc33cfa88f2a5514fc9cc0666 Author: Hans de Goede Date: Sat Nov 8 22:03:19 2025 +0100 HID: logitech-dj: Fix probe failure when used with KVM Since commit 6f20d3261265 ("HID: logitech-dj: Fix error handling in logi_dj_recv_switch_to_dj_mode()") logi_dj_recv_switch_to_dj_mode() will return an error when the hid_hw_raw_request() call to enable [dis]connect events fails. This can happen when used with a KVM like the Aten CS1784a and the PC does not have the KVM focus when probe() runs, which causes probe() to fail after which the receiver will simply not work. The logi_dj_recv_query_paired_devices() call done at the end of probe() already ignores any errors for the KVM without focus case. When focus is restored and an input report is received this will trigger logi_dj_recv_queue_unknown_work() which retries the query_paired_devices() call from a workqueue. To fix the probe() failure let it ignore logi_dj_recv_switch_to_dj_mode() errors too, track if a successful logi_dj_recv_switch_to_dj_mode() was done and retry if necessary from logi_dj_recv_queue_unknown_work(). Queurying paired devices while not in dj-mode is not useful and this will be redone after the unknown work has retried setting dj-mode, so skip queurying paired devices when not in dj-mode yet. The new bool to track successful setting of the dj-mode will also cause setting dj-mode to be retried from the unknown work, if setting dj-mode failed after a reset_resume. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-dj.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 58b5bc9cc54d..44b716697510 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -149,6 +149,7 @@ struct dj_receiver_dev { struct kfifo notif_fifo; unsigned long last_query; /* in jiffies */ bool ready; + bool dj_mode; enum recvr_type type; unsigned int unnumbered_application; spinlock_t lock; @@ -641,6 +642,8 @@ static const u8 hid_reportid_size_map[NUMBER_OF_HID_REPORTS] = { static const struct hid_ll_driver logi_dj_ll_driver; static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev); +static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, + unsigned int timeout); static void delayedwork_callback(struct work_struct *work); static LIST_HEAD(dj_hdev_list); @@ -925,6 +928,9 @@ static void delayedwork_callback(struct work_struct *work) logi_dj_recv_destroy_djhid_device(djrcv_dev, &workitem); break; case WORKITEM_TYPE_UNKNOWN: + if (!djrcv_dev->dj_mode) + logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0); + logi_dj_recv_query_paired_devices(djrcv_dev); break; case WORKITEM_TYPE_EMPTY: @@ -1318,6 +1324,9 @@ static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev) djrcv_dev->last_query = jiffies; + if (!djrcv_dev->dj_mode) + return 0; + if (djrcv_dev->type != recvr_type_dj) { retval = logi_dj_recv_query_hidpp_devices(djrcv_dev); goto out; @@ -1401,6 +1410,7 @@ out: if (retval < 0) hid_err(hdev, "%s error:%d\n", __func__, retval); + djrcv_dev->dj_mode = retval >= 0; return retval; } @@ -1937,9 +1947,11 @@ static int logi_dj_probe(struct hid_device *hdev, } if (has_hidpp) { - retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0); - if (retval < 0) - goto switch_to_dj_mode_fail; + /* + * This can fail with a KVM. Ignore errors to let the probe + * succeed, logi_dj_recv_queue_unknown_work will retry later. + */ + logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0); } /* This is enabling the polling urb on the IN endpoint */ @@ -1957,17 +1969,13 @@ static int logi_dj_probe(struct hid_device *hdev, spin_lock_irqsave(&djrcv_dev->lock, flags); djrcv_dev->ready = true; spin_unlock_irqrestore(&djrcv_dev->lock, flags); - /* - * This can fail with a KVM. Ignore errors to let the probe - * succeed, logi_dj_recv_queue_unknown_work will retry later. - */ + /* This too can fail with a KVM, ignore errors. */ logi_dj_recv_query_paired_devices(djrcv_dev); } return 0; llopen_failed: -switch_to_dj_mode_fail: hid_hw_stop(hdev); hid_hw_start_fail: From 3a7dc54b62c837a160f43983b18a8a82a62975dd Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:33 +0200 Subject: [PATCH 096/295] HID: evision: Fix Report Descriptor for Evision Wireless Receiver 320f:226f JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit baa120439ac0c803a9962df838eeda28846bc93a Author: Terry Junge Date: Wed Nov 19 17:49:27 2025 -0800 HID: evision: Fix Report Descriptor for Evision Wireless Receiver 320f:226f The mouse portion of the device's Report Descriptor declares 5 buttons but only declares 3 usages (Button 1 through Button 3). As a result events for the 2 side buttons are not generated. Detect and repair the Report Descriptor if necessary by changing the Usage Maximum value from Button 3 to Button 5. [jkosina@suse.com: standardize changelog a little bit] Reported-by: Artem Closes: https://lore.kernel.org/all/CADYkRmrfhRf6VmQjc+su+mepyv=TsHc+aMcL6ryRZ5HTZ8pyFg@mail.gmail.com/ Signed-off-by: Terry Junge Tested-by: Artem Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/Kconfig | 1 + drivers/hid/hid-evision.c | 21 +++++++++++++++++++++ drivers/hid/hid-ids.h | 1 + 3 files changed, 23 insertions(+) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index b0f4529ae739..f6a3b68abb89 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -383,6 +383,7 @@ config HID_EVISION help Support for some EVision keyboards. Note that this is needed only when applying customization using userspace programs. + Support for some EVision devices requiring report descriptor fixups. config HID_EZKEY tristate "Ezkey BTC 8193 keyboard" diff --git a/drivers/hid/hid-evision.c b/drivers/hid/hid-evision.c index bb5997078491..3e7f43ab80bb 100644 --- a/drivers/hid/hid-evision.c +++ b/drivers/hid/hid-evision.c @@ -18,6 +18,10 @@ static int evision_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { + /* mapping only applies to USB_DEVICE_ID_EVISION_ICL01 */ + if (hdev->product != USB_DEVICE_ID_EVISION_ICL01) + return 0; + if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER) return 0; @@ -37,8 +41,24 @@ static int evision_input_mapping(struct hid_device *hdev, struct hid_input *hi, return 0; } +#define REP_DSC_SIZE 236 +#define USAGE_MAX_INDEX 59 + +static const __u8 *evision_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + if (hdev->product == USB_DEVICE_ID_EV_TELINK_RECEIVER && + *rsize == REP_DSC_SIZE && rdesc[USAGE_MAX_INDEX] == 0x29 && + rdesc[USAGE_MAX_INDEX + 1] == 3) { + hid_info(hdev, "fixing EVision:TeLink Receiver report descriptor\n"); + rdesc[USAGE_MAX_INDEX + 1] = 5; // change usage max from 3 to 5 + } + return rdesc; +} + static const struct hid_device_id evision_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_EVISION, USB_DEVICE_ID_EVISION_ICL01) }, + { HID_USB_DEVICE(USB_VENDOR_ID_EVISION, USB_DEVICE_ID_EV_TELINK_RECEIVER) }, { } }; MODULE_DEVICE_TABLE(hid, evision_devices); @@ -47,6 +67,7 @@ static struct hid_driver evision_driver = { .name = "evision", .id_table = evision_devices, .input_mapping = evision_input_mapping, + .report_fixup = evision_report_fixup, }; module_hid_driver(evision_driver); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 399e9ec2fd5c..d31711f1aaec 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -477,6 +477,7 @@ #define USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II 0x0118 #define USB_VENDOR_ID_EVISION 0x320f +#define USB_DEVICE_ID_EV_TELINK_RECEIVER 0x226f #define USB_DEVICE_ID_EVISION_ICL01 0x5041 #define USB_VENDOR_ID_FFBEAST 0x045b From 2005622b3e4232b6779d0b76426c8d2adfaa82a1 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:09:36 +0200 Subject: [PATCH 097/295] drivers: hid: renegotiate resolution multipliers with device after reset JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit d6f4941f1b4f3e701e422dfbfee024264294f91f Author: Benedek Kupper Date: Tue Oct 7 22:35:44 2025 +0200 drivers: hid: renegotiate resolution multipliers with device after reset The scroll resolution multipliers are set in the context of hidinput_connect(), which is only called at probe time: when the host changes the value on the device with a SET_REPORT(FEATURE), and the device accepts it, these multipliers are stored on the host side, and used to calculate the final scroll event values sent to userspace. After a USB suspend, the resume operation on many hubs and chipsets involve a USB reset signal as well. A reset on the device side clears all previous state information, including the value of the multiplier report. This reset is not handled by the multiplier handling logic, so what ends up happening is the host is still expecting high-resolution scroll events, but the device is reset to default resolution, making the effective, user-perceived scroll speed incredibly slow. The solution is to renegotiate the multiplier selection after each reset. This is not the only bug related to the high-resolution scrolling implementation in the kernel (the other one is https://bugzilla.kernel.org/show_bug.cgi?id=220144), but for this one, there is no device side workaround for, leading to poor user experience with our product: https://github.com/UltimateHackingKeyboard/firmware/issues/1155 https://github.com/UltimateHackingKeyboard/firmware/issues/1261 https://github.com/UltimateHackingKeyboard/firmware/pull/1355 This patch was tested by an affected user and has been reported to fix the issue (see discussion in 1355). Signed-off-by: Benedek Kupper Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-generic.c | 9 +++++++++ drivers/hid/hid-input.c | 7 +++++++ include/linux/hid.h | 1 + 3 files changed, 17 insertions(+) diff --git a/drivers/hid/hid-generic.c b/drivers/hid/hid-generic.c index 9e04c6d0fcc8..c2de916747de 100644 --- a/drivers/hid/hid-generic.c +++ b/drivers/hid/hid-generic.c @@ -70,6 +70,14 @@ static int hid_generic_probe(struct hid_device *hdev, return hid_hw_start(hdev, HID_CONNECT_DEFAULT); } +static int hid_generic_reset_resume(struct hid_device *hdev) +{ + if (hdev->claimed & HID_CLAIMED_INPUT) + hidinput_reset_resume(hdev); + + return 0; +} + static const struct hid_device_id hid_table[] = { { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, HID_ANY_ID, HID_ANY_ID) }, { } @@ -81,6 +89,7 @@ static struct hid_driver hid_generic = { .id_table = hid_table, .match = hid_generic_match, .probe = hid_generic_probe, + .reset_resume = hid_generic_reset_resume, }; module_hid_driver(hid_generic); diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 32a96ae2946f..2633fcd8f910 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -2416,6 +2416,13 @@ void hidinput_disconnect(struct hid_device *hid) } EXPORT_SYMBOL_GPL(hidinput_disconnect); +void hidinput_reset_resume(struct hid_device *hid) +{ + /* renegotiate host-device shared state after reset */ + hidinput_change_resolution_multipliers(hid); +} +EXPORT_SYMBOL_GPL(hidinput_reset_resume); + #ifdef CONFIG_HID_KUNIT_TEST #include "hid-input-test.c" #endif diff --git a/include/linux/hid.h b/include/linux/hid.h index a4ddb94e3ee5..dce862cafbbd 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -984,6 +984,7 @@ extern void hidinput_hid_event(struct hid_device *, struct hid_field *, struct h extern void hidinput_report_event(struct hid_device *hid, struct hid_report *report); extern int hidinput_connect(struct hid_device *hid, unsigned int force); extern void hidinput_disconnect(struct hid_device *); +void hidinput_reset_resume(struct hid_device *hid); struct hid_field *hid_find_field(struct hid_device *hdev, unsigned int report_type, unsigned int application, unsigned int usage); From e016d65d00007ea3f71531092e3b4e517398fd40 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:37 +0200 Subject: [PATCH 098/295] HID: usbhid: paper over wrong bNumDescriptor field JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit f28beb69c51517aec7067dfb2074e7c751542384 Author: Benjamin Tissoires Date: Mon Dec 15 12:57:21 2025 +0100 HID: usbhid: paper over wrong bNumDescriptor field Some faulty devices (ZWO EFWmini) have a wrong optional HID class descriptor count compared to the provided length. Given that we plainly ignore those optional descriptor, we can attempt to fix the provided number so we do not lock out those devices. Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-core.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index aac0051a2cf6..758eb21430cd 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -985,6 +985,7 @@ static int usbhid_parse(struct hid_device *hid) struct usb_device *dev = interface_to_usbdev (intf); struct hid_descriptor *hdesc; struct hid_class_descriptor *hcdesc; + __u8 fixed_opt_descriptors_size; u32 quirks = 0; unsigned int rsize = 0; char *rdesc; @@ -1015,7 +1016,21 @@ static int usbhid_parse(struct hid_device *hid) (hdesc->bNumDescriptors - 1) * sizeof(*hcdesc)) { dbg_hid("hid descriptor invalid, bLen=%hhu bNum=%hhu\n", hdesc->bLength, hdesc->bNumDescriptors); - return -EINVAL; + + /* + * Some devices may expose a wrong number of descriptors compared + * to the provided length. + * However, we ignore the optional hid class descriptors entirely + * so we can safely recompute the proper field. + */ + if (hdesc->bLength >= sizeof(*hdesc)) { + fixed_opt_descriptors_size = hdesc->bLength - sizeof(*hdesc); + + hid_warn(intf, "fixing wrong optional hid class descriptors count\n"); + hdesc->bNumDescriptors = fixed_opt_descriptors_size / sizeof(*hcdesc) + 1; + } else { + return -EINVAL; + } } hid->version = le16_to_cpu(hdesc->bcdHID); From 49ba4c53ad80a0b5ad9bbe6641d1c1a27d1451e0 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:38 +0200 Subject: [PATCH 099/295] HID: playstation: Center initial joystick axes to prevent spurious events JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit e9143268d259d98e111a649affa061acb8e13c5b Author: Siarhei Vishniakou Date: Tue Nov 11 15:45:19 2025 -0800 HID: playstation: Center initial joystick axes to prevent spurious events When a new PlayStation gamepad (DualShock 4 or DualSense) is initialized, the input subsystem sets the default value for its absolute axes (e.g., ABS_X, ABS_Y) to 0. However, the hardware's actual neutral/resting state for these joysticks is 128 (0x80). This creates a mismatch. When the first HID report arrives from the device, the driver sees the resting value of 128. The kernel compares this to its initial state of 0 and incorrectly interprets this as a delta (0 -> 128). Consequently, it generates EV_ABS events for this initial, non-existent movement. This behavior can fail userspace 'sanity check' tests (e.g., in Android CTS) that correctly assert no motion events should be generated from a device that is already at rest. This patch fixes the issue by explicitly setting the initial value of the main joystick axes (e.g., ABS_X, ABS_Y, ABS_RX, ABS_RY) to 128 (0x80) in the common ps_gamepad_create() function. This aligns the kernel's initial state with the hardware's expected neutral state, ensuring that the first report (at 128) produces no delta and thus, no spurious event. Signed-off-by: Siarhei Vishniakou Reviewed-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 128aa6abd10b..e4dfcf26b04e 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -753,11 +753,16 @@ ps_gamepad_create(struct hid_device *hdev, if (IS_ERR(gamepad)) return ERR_CAST(gamepad); + /* Set initial resting state for joysticks to 128 (center) */ input_set_abs_params(gamepad, ABS_X, 0, 255, 0, 0); + gamepad->absinfo[ABS_X].value = 128; input_set_abs_params(gamepad, ABS_Y, 0, 255, 0, 0); + gamepad->absinfo[ABS_Y].value = 128; input_set_abs_params(gamepad, ABS_Z, 0, 255, 0, 0); input_set_abs_params(gamepad, ABS_RX, 0, 255, 0, 0); + gamepad->absinfo[ABS_RX].value = 128; input_set_abs_params(gamepad, ABS_RY, 0, 255, 0, 0); + gamepad->absinfo[ABS_RY].value = 128; input_set_abs_params(gamepad, ABS_RZ, 0, 255, 0, 0); input_set_abs_params(gamepad, ABS_HAT0X, -1, 1, 0, 0); From 25898c88e65661deb0cdfe06c3d8be6c395cdbe3 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:38 +0200 Subject: [PATCH 100/295] HID: Intel-thc-hid: Intel-thc: fix dma_unmap_sg() nents value JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 0e13150c1a13a3a3d6184c24bfd080d5999945d1 Author: Thomas Fourier Date: Wed Dec 3 17:56:35 2025 +0100 HID: Intel-thc-hid: Intel-thc: fix dma_unmap_sg() nents value The `dma_unmap_sg()` functions should be called with the same nents as the `dma_map_sg()`, not the value the map function returned. Save the number of entries in struct thc_dma_configuration. Fixes: a688404b2e20 ("HID: intel-thc-hid: intel-thc: Add THC DMA interfaces") Signed-off-by: Thomas Fourier Reviewed-by: Even Xu Reviewed-by: Andy Shevchenko Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c | 4 +++- drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c index 82b8854843e0..a0c368aa7979 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c @@ -232,6 +232,7 @@ static int setup_dma_buffers(struct thc_device *dev, return 0; memset(config->sgls, 0, sizeof(config->sgls)); + memset(config->sgls_nent_pages, 0, sizeof(config->sgls_nent_pages)); memset(config->sgls_nent, 0, sizeof(config->sgls_nent)); cpu_addr = dma_alloc_coherent(dev->dev, prd_tbls_size, @@ -254,6 +255,7 @@ static int setup_dma_buffers(struct thc_device *dev, } count = dma_map_sg(dev->dev, config->sgls[i], nent, dir); + config->sgls_nent_pages[i] = nent; config->sgls_nent[i] = count; } @@ -299,7 +301,7 @@ static void release_dma_buffers(struct thc_device *dev, continue; dma_unmap_sg(dev->dev, config->sgls[i], - config->sgls_nent[i], + config->sgls_nent_pages[i], config->dir); sgl_free(config->sgls[i]); diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h index 78917400492c..541d33995baf 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h @@ -91,6 +91,7 @@ struct thc_prd_table { * @dir: Direction of DMA for this config * @prd_tbls: PRD tables for current DMA * @sgls: Array of pointers to scatter-gather lists + * @sgls_nent_pages: Number of pages per scatter-gather list * @sgls_nent: Actual number of entries per scatter-gather list * @prd_tbl_num: Actual number of PRD tables * @max_packet_size: Size of the buffer needed for 1 DMA message (1 PRD table) @@ -107,6 +108,7 @@ struct thc_dma_configuration { struct thc_prd_table *prd_tbls; struct scatterlist *sgls[PRD_TABLES_NUM]; + u8 sgls_nent_pages[PRD_TABLES_NUM]; u8 sgls_nent[PRD_TABLES_NUM]; u8 prd_tbl_num; From 74bb0f7f515d40990157a4c2515c9331a61a8add Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:39 +0200 Subject: [PATCH 101/295] HID: intel-ish-hid: Update ishtp bus match to support device ID table JIRA: https://issues.redhat.com/browse/RHEL-170872 JIRA: https://redhat.atlassian.net/browse/RHEL-183865 Upstream Status: since v6.19 commit daeed86b686855adda79f13729e0c9b0530990be Author: Zhang Lixu Date: Wed Dec 10 10:53:28 2025 +0800 HID: intel-ish-hid: Update ishtp bus match to support device ID table The ishtp_cl_bus_match() function previously only checked the first entry in the driver's device ID table. Update it to iterate over the entire table, allowing proper matching for drivers with multiple supported protocol GUIDs. Signed-off-by: Zhang Lixu Acked-by: Srinivas Pandruvada Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-ish-hid/ishtp/bus.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c index c6ce37244e49..c3915f3a060e 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.c +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -240,9 +240,17 @@ static int ishtp_cl_bus_match(struct device *dev, const struct device_driver *dr { struct ishtp_cl_device *device = to_ishtp_cl_device(dev); struct ishtp_cl_driver *driver = to_ishtp_cl_driver(drv); + struct ishtp_fw_client *client = device->fw_client; + const struct ishtp_device_id *id; - return(device->fw_client ? guid_equal(&driver->id[0].guid, - &device->fw_client->props.protocol_name) : 0); + if (client) { + for (id = driver->id; !guid_is_null(&id->guid); id++) { + if (guid_equal(&id->guid, &client->props.protocol_name)) + return 1; + } + } + + return 0; } /** From 0658e92dafd016776a23ba97018b98444209d7b9 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:40 +0200 Subject: [PATCH 102/295] HID: intel-ish-hid: Reset enum_devices_done before enumeration JIRA: https://issues.redhat.com/browse/RHEL-170872 JIRA: https://redhat.atlassian.net/browse/RHEL-183865 Upstream Status: since v6.19 commit 56e230723e3a818373bd62331bccb1c6d2b3881b Author: Zhang Lixu Date: Fri Dec 12 10:51:50 2025 +0800 HID: intel-ish-hid: Reset enum_devices_done before enumeration Some systems have enabled ISH without any sensors. In this case sending HOSTIF_DM_ENUM_DEVICES results in 0 sensors. This triggers ISH hardware reset on subsequent enumeration after S3/S4 resume. The enum_devices_done flag was not reset before sending the HOSTIF_DM_ENUM_DEVICES command. On subsequent enumeration calls (such as after S3/S4 resume), this flag retains its previous true value, causing the wait loop to be skipped and returning prematurely to hid_ishtp_cl_init(). If 0 HID devices are found, hid_ishtp_cl_init() skips getting HID device descriptors and sets init_done to true. When the delayed enumeration response arrives with init_done already true, the driver treats it as a bad packet and triggers an ISH hardware reset. Set enum_devices_done to false before sending the enumeration command, consistent with similar functions like ishtp_get_hid_descriptor() and ishtp_get_report_descriptor() which reset their respective flags. Signed-off-by: Zhang Lixu Acked-by: Srinivas Pandruvada Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-ish-hid/ishtp-hid-client.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c index f37b3bc2bb7d..6d64008f2ce0 100644 --- a/drivers/hid/intel-ish-hid/ishtp-hid-client.c +++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c @@ -495,6 +495,7 @@ static int ishtp_enum_enum_devices(struct ishtp_cl *hid_ishtp_cl) int rv; /* Send HOSTIF_DM_ENUM_DEVICES */ + client_data->enum_devices_done = false; memset(&msg, 0, sizeof(struct hostif_msg)); msg.hdr.command = HOSTIF_DM_ENUM_DEVICES; rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *)&msg, From daeee0467d0d9a7675fb798094231b4c5cf67ca7 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:41 +0200 Subject: [PATCH 103/295] HID: multitouch: add MT_QUIRK_STICKY_FINGERS to MT_CLS_VTL JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit ff3f234ff1dcd6d626a989151db067a1b7f0f215 Author: DaytonCL Date: Sun Dec 14 14:34:36 2025 +0100 HID: multitouch: add MT_QUIRK_STICKY_FINGERS to MT_CLS_VTL Some VTL-class touchpads (e.g. TOPS0102:00 35CC:0104) intermittently fail to release a finger contact. A previous slot remains logically active, accompanied by stale BTN_TOOL_DOUBLETAP state, causing gestures to stay latched and resulting in stuck two-finger scrolling and false right-clicks. Apply MT_QUIRK_STICKY_FINGERS to handle the unreleased contact correctly. Link: https://gitlab.freedesktop.org/libinput/libinput/-/issues/1225 Suggested-by: Benjamin Tissoires Tested-by: DaytonCL Signed-off-by: DaytonCL Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-multitouch.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 179dc316b4b5..a0c1ad5acb67 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -393,6 +393,7 @@ static const struct mt_class mt_classes[] = { { .name = MT_CLS_VTL, .quirks = MT_QUIRK_ALWAYS_VALID | MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_STICKY_FINGERS | MT_QUIRK_FORCE_GET_FEATURE, }, { .name = MT_CLS_GOOGLE, From db95093a41dc84a83a3758b32cf3eaad761f0061 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:43 +0200 Subject: [PATCH 104/295] HID: Intel-thc-hid: Intel-thc: Fix wrong register reading JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit f39006965dd37e7be823dba6ca484adccc7a4dff Author: Even Xu Date: Fri Dec 19 09:14:38 2025 +0800 HID: Intel-thc-hid: Intel-thc: Fix wrong register reading Correct the read register for the setting of max input size and interrupt delay. Fixes: 22da60f0304b ("HID: Intel-thc-hid: Intel-thc: Introduce interrupt delay control") Fixes: 45e92a093099 ("HID: Intel-thc-hid: Intel-thc: Introduce max input size control") Signed-off-by: Even Xu Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c index 636a68306501..7e220a4c5ded 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c @@ -1593,7 +1593,7 @@ int thc_i2c_set_rx_max_size(struct thc_device *dev, u32 max_rx_size) if (!max_rx_size) return -EOPNOTSUPP; - ret = regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, &val); + ret = regmap_read(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, &val); if (ret) return ret; @@ -1662,7 +1662,7 @@ int thc_i2c_set_rx_int_delay(struct thc_device *dev, u32 delay_us) if (!delay_us) return -EOPNOTSUPP; - ret = regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, &val); + ret = regmap_read(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, &val); if (ret) return ret; From 8a3eb3ecf83ebcc66d1c1404676b7b48c3c29fa4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:44 +0200 Subject: [PATCH 105/295] HID: bpf: drop dead NULL checks in kfuncs JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 8fe172fa305f14db815bd88133d2030e4a9e107e Author: Puranjay Mohan Date: Fri Jan 2 10:00:31 2026 -0800 HID: bpf: drop dead NULL checks in kfuncs As KF_TRUSTED_ARGS is now considered default for all kfuns, the verifier will not allow passing NULL pointers to these kfuns. These checks for NULL pointers can therefore be removed. Signed-off-by: Puranjay Mohan Link: https://lore.kernel.org/r/20260102180038.2708325-6-puranjay@kernel.org Signed-off-by: Alexei Starovoitov Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/hid_bpf_dispatch.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index 9a06f9b0e4ef..892aca026ffa 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -295,9 +295,6 @@ hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t rdwr { struct hid_bpf_ctx_kern *ctx_kern; - if (!ctx) - return NULL; - ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx); if (rdwr_buf_size + offset > ctx->allocated_size) @@ -364,7 +361,7 @@ __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, u32 report_len; /* check arguments */ - if (!ctx || !hid_ops || !buf) + if (!hid_ops) return -EINVAL; switch (rtype) { From 3237f7a0eea07705297bc5c66a7433f3ddd5a156 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:45 +0200 Subject: [PATCH 106/295] HID: bpf: fix bpf compilation with -fms-extensions JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit b7666c891cc7e1a157cd99bca737631c8be07504 Author: Benjamin Tissoires Date: Tue Jan 6 16:30:54 2026 +0100 HID: bpf: fix bpf compilation with -fms-extensions Similar to commit 835a50753579 ("selftests/bpf: Add -fms-extensions to bpf build flags") and commit 639f58a0f480 ("bpftool: Fix build warnings due to MS extensions") The kernel is now built with -fms-extensions, therefore generated vmlinux.h contains types like: struct slab { .. struct freelist_counters; }; Use -fms-extensions and -Wno-microsoft-anon-tag flags to build bpf programs that #include "vmlinux.h" Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/progs/Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/hid/bpf/progs/Makefile b/drivers/hid/bpf/progs/Makefile index ec1fc642fd63..66b8f38e591d 100644 --- a/drivers/hid/bpf/progs/Makefile +++ b/drivers/hid/bpf/progs/Makefile @@ -56,8 +56,10 @@ clean: %.bpf.o: %.bpf.c vmlinux.h $(BPFOBJ) | $(OUTPUT) $(call msg,BPF,$@) - $(Q)$(CLANG) -g -O2 --target=bpf -Wall -Werror $(INCLUDES) \ - -c $(filter %.c,$^) -o $@ && \ + $(Q)$(CLANG) -g -O2 --target=bpf -Wall -Werror $(INCLUDES) \ + -Wno-microsoft-anon-tag \ + -fms-extensions \ + -c $(filter %.c,$^) -o $@ && \ $(LLVM_STRIP) -g $@ vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR) From e18acaa04d48c8a95ac3c8fae8268050014ae6aa Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:47 +0200 Subject: [PATCH 107/295] selftests/hid: fix bpf compilations due to -fms-extensions JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit e03fb369b083ab66d72fb63fe7817e933c3d4a30 Author: Benjamin Tissoires Date: Tue Jan 6 16:30:55 2026 +0100 selftests/hid: fix bpf compilations due to -fms-extensions Similar to commit 835a50753579 ("selftests/bpf: Add -fms-extensions to bpf build flags") and commit 639f58a0f480 ("bpftool: Fix build warnings due to MS extensions") The kernel is now built with -fms-extensions, therefore generated vmlinux.h contains types like: struct slab { .. struct freelist_counters; }; Use -fms-extensions and -Wno-microsoft-anon-tag flags to build bpf programs that #include "vmlinux.h" Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/testing/selftests/hid/Makefile b/tools/testing/selftests/hid/Makefile index 2839d2612ce3..50ec9e0406ab 100644 --- a/tools/testing/selftests/hid/Makefile +++ b/tools/testing/selftests/hid/Makefile @@ -184,6 +184,8 @@ MENDIAN=$(if $(IS_LITTLE_ENDIAN),-mlittle-endian,-mbig-endian) CLANG_SYS_INCLUDES = $(call get_sys_includes,$(CLANG)) BPF_CFLAGS = -g -Werror -D__TARGET_ARCH_$(SRCARCH) $(MENDIAN) \ + -Wno-microsoft-anon-tag \ + -fms-extensions \ -I$(INCLUDE_DIR) CLANG_CFLAGS = $(CLANG_SYS_INCLUDES) \ From 85b9388c7f1a2ba178a48ba4781a3bf5b88e8057 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:48 +0200 Subject: [PATCH 108/295] hid: intel-thc-hid: Select SGL_ALLOC JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit dd76788f9ec4f33f0f43838cac1304265d944328 Author: Tim Zimmermann Date: Fri Nov 28 08:54:22 2025 +0100 hid: intel-thc-hid: Select SGL_ALLOC intel-thc-dma.c uses sgl_alloc() resulting in a build failure if CONFIG_SGL_ALLOC is not enabled Signed-off-by: Tim Zimmermann Reviewed-by: Even Xu Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-thc-hid/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/intel-thc-hid/Kconfig b/drivers/hid/intel-thc-hid/Kconfig index 0351d1137607..9d74e53b8c62 100644 --- a/drivers/hid/intel-thc-hid/Kconfig +++ b/drivers/hid/intel-thc-hid/Kconfig @@ -7,6 +7,7 @@ menu "Intel THC HID Support" config INTEL_THC_HID tristate "Intel Touch Host Controller" depends on ACPI + select SGL_ALLOC help THC (Touch Host Controller) is the name of the IP block in PCH that interfaces with Touch Devices (ex: touchscreen, touchpad etc.). It From 747cff58a467a9fb61b4ac31512165b2447694ba Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:50 +0200 Subject: [PATCH 109/295] HID: Intel-thc-hid: Intel-thc: Add safety check for reading DMA buffer JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 CVE: CVE-2026-23196 commit a9a917998d172ec117f9e9de1919174153c0ace4 Author: Even Xu Date: Fri Dec 26 11:39:53 2025 +0800 HID: Intel-thc-hid: Intel-thc: Add safety check for reading DMA buffer Add DMA buffer readiness check before reading DMA buffer to avoid unexpected NULL pointer accessing. Signed-off-by: Even Xu Tested-by: Rui Zhang Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c index a0c368aa7979..6ee675e0a738 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c @@ -575,6 +575,11 @@ static int read_dma_buffer(struct thc_device *dev, return -EINVAL; } + if (!read_config->prd_tbls || !read_config->sgls[prd_table_index]) { + dev_err_once(dev->dev, "PRD tables are not ready yet\n"); + return -EINVAL; + } + prd_tbl = &read_config->prd_tbls[prd_table_index]; mes_len = calc_message_len(prd_tbl, &nent); if (mes_len > read_config->max_packet_size) { From d8e58dd0f3f176442c6f146cb10bd5f3fb52de0e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:52 +0200 Subject: [PATCH 110/295] HID: quirks: Add another Chicony HP 5MP Cameras to hid_ignore_list JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit c06bc3557542307b9658fbd43cc946a14250347b Author: Chris Chiu Date: Fri Jan 2 06:56:43 2026 +0000 HID: quirks: Add another Chicony HP 5MP Cameras to hid_ignore_list Another Chicony Electronics HP 5MP Camera with USB ID 04F2:B882 reports a HID sensor interface that is not actually implemented. Add the device to the HID ignore list so the bogus sensor is never exposed to userspace. Then the system won't hang when runtime PM tries to wake the unresponsive device. Signed-off-by: Chris Chiu Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-quirks.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index d31711f1aaec..e8a1a86313b7 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -317,6 +317,7 @@ #define USB_DEVICE_ID_CHICONY_ACER_SWITCH12 0x1421 #define USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA 0xb824 #define USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA2 0xb82c +#define USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA3 0xb882 #define USB_VENDOR_ID_CHUNGHWAT 0x2247 #define USB_DEVICE_ID_CHUNGHWAT_MULTITOUCH 0x0001 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index c89a015686c0..3cf7971d4974 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -769,6 +769,7 @@ static const struct hid_device_id hid_ignore_list[] = { { HID_USB_DEVICE(USB_VENDOR_ID_BERKSHIRE, USB_DEVICE_ID_BERKSHIRE_PCWD) }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA) }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA3) }, { HID_USB_DEVICE(USB_VENDOR_ID_CIDC, 0x0103) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_RADIO_SI470X) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_RADIO_SI4713) }, From 2ae3901eb666aa8a3684756a084ce7c50c6efff4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:54 +0200 Subject: [PATCH 111/295] HID: multitouch: set INPUT_PROP_PRESSUREPAD based on Digitizer/Button Type JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 2b29a90131bbb3d404e5bf5102af5ce98ccf96fe Author: Peter Hutterer Date: Mon Dec 22 09:43:34 2025 +1000 HID: multitouch: set INPUT_PROP_PRESSUREPAD based on Digitizer/Button Type A Digitizer/Button Type value of 1 indicates the device is a pressurepad, see https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/touchpad-windows-precision-touchpad-collection#device-capabilities-feature-report Signed-off-by: Peter Hutterer Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-multitouch.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index a0c1ad5acb67..b1c3ef129058 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -81,6 +81,7 @@ MODULE_LICENSE("GPL"); #define MT_INPUTMODE_TOUCHPAD 0x03 #define MT_BUTTONTYPE_CLICKPAD 0 +#define MT_BUTTONTYPE_PRESSUREPAD 1 enum latency_mode { HID_LATENCY_NORMAL = 0, @@ -179,6 +180,7 @@ struct mt_device { __u8 inputmode_value; /* InputMode HID feature value */ __u8 maxcontacts; bool is_buttonpad; /* is this device a button pad? */ + bool is_pressurepad; /* is this device a pressurepad? */ bool is_haptic_touchpad; /* is this device a haptic touchpad? */ bool serial_maybe; /* need to check for serial protocol */ @@ -531,8 +533,14 @@ static void mt_feature_mapping(struct hid_device *hdev, } mt_get_feature(hdev, field->report); - if (field->value[usage->usage_index] == MT_BUTTONTYPE_CLICKPAD) + switch (field->value[usage->usage_index]) { + case MT_BUTTONTYPE_CLICKPAD: td->is_buttonpad = true; + break; + case MT_BUTTONTYPE_PRESSUREPAD: + td->is_pressurepad = true; + break; + } break; case 0xff0000c5: @@ -1394,6 +1402,8 @@ static int mt_touch_input_configured(struct hid_device *hdev, if (td->is_buttonpad) __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); + if (td->is_pressurepad) + __set_bit(INPUT_PROP_PRESSUREPAD, input->propbit); app->pending_palm_slots = devm_kcalloc(&hi->input->dev, BITS_TO_LONGS(td->maxcontacts), From de718372ded4fa9e92dc2a1bc96c0479f1f18e60 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:56 +0200 Subject: [PATCH 112/295] selftests/hid: require hidtools 0.12 JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 1d6628f7f279131ac9efb743bcea7080666d7574 Author: Peter Hutterer Date: Mon Dec 22 09:43:35 2025 +1000 selftests/hid: require hidtools 0.12 Not all our tests really require it but since it's likely pip-installed anyway it's trivial to require the new version, just in case we want to start cleaning up other bits. Signed-off-by: Peter Hutterer Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/tests/conftest.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/testing/selftests/hid/tests/conftest.py b/tools/testing/selftests/hid/tests/conftest.py index 1361ec981db6..985a535324b2 100644 --- a/tools/testing/selftests/hid/tests/conftest.py +++ b/tools/testing/selftests/hid/tests/conftest.py @@ -5,6 +5,7 @@ # Copyright (c) 2017 Benjamin Tissoires # Copyright (c) 2017 Red Hat, Inc. +from packaging.version import Version import platform import pytest import re @@ -14,6 +15,19 @@ from .base import HIDTestUdevRule from pathlib import Path +@pytest.fixture(autouse=True) +def hidtools_version_check(): + HIDTOOLS_VERSION = "0.12" + try: + import hidtools + + version = hidtools.__version__ # type: ignore + if Version(version) < Version(HIDTOOLS_VERSION): + pytest.skip(reason=f"have hidtools {version}, require >={HIDTOOLS_VERSION}") + except Exception: + pytest.skip(reason=f"hidtools >={HIDTOOLS_VERSION} required") + + # See the comment in HIDTestUdevRule, this doesn't set up but it will clean # up once the last test exited. @pytest.fixture(autouse=True, scope="session") From f05e7f1fb2208bba2080fd76f1ccc1b2ddd6523b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:12:58 +0200 Subject: [PATCH 113/295] selftests/hid: use a enum class for the different button types JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 4f36fdab084fcbe9c34bb51889e4b8c06d98fbaa Author: Peter Hutterer Date: Mon Dec 22 09:43:36 2025 +1000 selftests/hid: use a enum class for the different button types Instead of multiple spellings of a string-provided argument, let's make this a tad more type-safe and use an enum here. And while we do this fix the two wrong devices: - elan_04f3_313a (HP ZBook Fury 15) is discrete button pad - dell_044e_1220 (Dell Precision 7740) is a discrete button pad Equivalent hid-tools commit https://gitlab.freedesktop.org/libevdev/hid-tools/-/commit/8300a55bf4213c6a252cab8cb5b34c9ddb191625 Signed-off-by: Peter Hutterer Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- .../selftests/hid/tests/test_multitouch.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/hid/tests/test_multitouch.py b/tools/testing/selftests/hid/tests/test_multitouch.py index ece0ba8e7d34..a06a087f00b6 100644 --- a/tools/testing/selftests/hid/tests/test_multitouch.py +++ b/tools/testing/selftests/hid/tests/test_multitouch.py @@ -9,6 +9,7 @@ from . import base from hidtools.hut import HUT from hidtools.util import BusType +import enum import libevdev import logging import pytest @@ -232,11 +233,17 @@ class Digitizer(base.UHIDTestDevice): return 0 +class HIDButtonType(enum.IntEnum): + CLICKPAD = 0 + PRESSUREPAD = 1 + DISCRETE_BUTTONS = 2 + + class PTP(Digitizer): def __init__( self, name, - type="Click Pad", + buttontype=HIDButtonType.CLICKPAD, rdesc_str=None, rdesc=None, application="Touch Pad", @@ -244,11 +251,8 @@ class PTP(Digitizer): max_contacts=None, input_info=None, ): - self.type = type.lower().replace(" ", "") - if self.type == "clickpad": - self.buttontype = 0 - else: # pressurepad - self.buttontype = 1 + self.buttontype = buttontype + self.clickpad_state = False self.left_state = False self.right_state = False @@ -983,7 +987,7 @@ class BaseTest: uhdev = self.uhdev evdev = uhdev.get_evdev() - if uhdev.type == "clickpad": + if uhdev.buttontype == HIDButtonType.CLICKPAD: r = uhdev.event(click=True) events = uhdev.next_sync_events() self.debug_reports(r, uhdev, events) @@ -1918,7 +1922,7 @@ class Testdell_044e_1220(BaseTest.TestPTP): def create_device(self): return PTP( "uhid test dell_044e_1220", - type="pressurepad", + buttontype=HIDButtonType.DISCRETE_BUTTONS, rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 95 05 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 09 38 95 01 81 06 05 0c 0a 38 02 81 06 c0 c0 05 0d 09 05 a1 01 85 08 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 af 04 75 10 55 0e 65 11 09 30 35 00 46 e8 03 95 01 81 02 26 7b 02 46 12 02 09 31 81 02 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 05 0d 09 56 81 02 09 54 25 05 95 01 75 08 81 02 05 09 19 01 29 03 25 01 75 01 95 03 81 02 95 05 81 03 05 0d 85 09 09 55 75 08 95 01 25 05 b1 02 06 00 ff 85 0a 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 01 ff 09 01 a1 01 85 03 09 01 15 00 26 ff 00 95 1b 81 02 85 04 09 02 95 50 81 02 85 05 09 03 95 07 b1 02 85 06 09 04 81 02 c0 06 02 ff 09 01 a1 01 85 07 09 02 95 86 75 08 b1 02 c0 05 0d 09 0e a1 01 85 0b 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 0c 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0", ) @@ -2018,7 +2022,7 @@ class Testelan_04f3_313a(BaseTest.TestPTP): def create_device(self): return PTP( "uhid test elan_04f3_313a", - type="touchpad", + buttontype=HIDButtonType.DISCRETE_BUTTONS, input_info=(BusType.I2C, 0x04F3, 0x313A), rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 95 05 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 75 08 95 05 81 03 c0 06 00 ff 09 01 85 0e 09 c5 15 00 26 ff 00 75 08 95 04 b1 02 85 0a 09 c6 15 00 26 ff 00 75 08 95 04 b1 02 c0 06 00 ff 09 01 a1 01 85 5c 09 01 95 0b 75 08 81 06 85 0d 09 c5 15 00 26 ff 00 75 08 95 04 b1 02 85 0c 09 c6 96 80 03 75 08 b1 02 85 0b 09 c7 95 82 75 08 b1 02 c0 05 0d 09 05 a1 01 85 04 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 05 09 09 02 09 03 15 00 25 01 75 01 95 02 81 02 05 0d 95 01 75 04 25 0f 09 51 81 02 05 01 15 00 26 d7 0e 75 10 55 0d 65 11 09 30 35 00 46 44 2f 95 01 81 02 46 12 16 26 eb 06 26 eb 06 09 31 81 02 05 0d 15 00 25 64 95 03 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 25 01 75 01 95 08 81 03 09 c5 75 08 95 02 81 03 05 0d 85 02 09 55 09 59 75 04 95 02 25 0f b1 02 85 07 09 60 75 01 95 01 15 00 25 01 b1 02 95 0f b1 03 06 00 ff 06 00 ff 85 06 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 03 09 22 a1 00 09 52 15 00 25 0a 75 10 95 01 b1 02 c0 09 22 a1 00 85 05 09 57 09 58 75 01 95 02 25 01 b1 02 95 0e b1 03 c0 c0 05 01 09 02 a1 01 85 2a 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 95 05 81 03 05 01 09 30 09 31 15 81 25 7f 35 81 45 7f 55 00 65 13 75 08 95 02 81 06 75 08 95 05 81 03 c0 c0", ) @@ -2110,7 +2114,7 @@ class Testsipodev_0603_0002(BaseTest.TestPTP): def create_device(self): return PTP( "uhid test sipodev_0603_0002", - type="clickpad", + buttontype=HIDButtonType.CLICKPAD, rdesc="05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 02 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 80 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 05 a1 01 85 04 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 75 01 95 02 81 03 95 01 75 04 25 05 09 51 81 02 05 01 15 00 26 44 0a 75 0c 55 0e 65 11 09 30 35 00 46 ac 03 95 01 81 02 46 fe 01 26 34 05 75 0c 09 31 81 02 05 0d c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 0a 95 01 75 04 81 02 75 01 95 03 81 03 05 09 09 01 25 01 75 01 95 01 81 02 05 0d 85 0a 09 55 09 59 75 04 95 02 25 0f b1 02 85 0b 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 09 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 06 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 07 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 05 01 09 0c a1 01 85 08 15 00 25 01 09 c6 75 01 95 01 81 06 75 07 81 03 c0 05 01 09 80 a1 01 85 01 15 00 25 01 75 01 0a 81 00 0a 82 00 0a 83 00 95 03 81 06 95 05 81 01 c0 06 0c 00 09 01 a1 01 85 02 25 01 15 00 75 01 0a b5 00 0a b6 00 0a b7 00 0a cd 00 0a e2 00 0a a2 00 0a e9 00 0a ea 00 95 08 81 02 0a 83 01 0a 6f 00 0a 70 00 0a 88 01 0a 8a 01 0a 92 01 0a a8 02 0a 24 02 95 08 81 02 0a 21 02 0a 23 02 0a 96 01 0a 25 02 0a 26 02 0a 27 02 0a 23 02 0a b1 02 95 08 81 02 c0 06 00 ff 09 01 a1 01 85 05 15 00 26 ff 00 19 01 29 02 75 08 95 05 b1 02 c0", ) From b98e62a6ce7a19060de50e3291b7cc9ff2fb9674 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:13:00 +0200 Subject: [PATCH 114/295] selftests/hid: add a test for the Digitizer/Button Type pressurepad JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit f287ba5951a4b3a47305b64a3fcde5d0911adb9b Author: Peter Hutterer Date: Mon Dec 22 09:43:37 2025 +1000 selftests/hid: add a test for the Digitizer/Button Type pressurepad We have to resort to a bit of a hack: python-libevdev gets the properties from libevdev at module init time. If libevdev hasn't been rebuilt with the new property it won't be automatically populated. So we hack around this by constructing the property manually. Signed-off-by: Peter Hutterer Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- .../selftests/hid/tests/test_multitouch.py | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/hid/tests/test_multitouch.py b/tools/testing/selftests/hid/tests/test_multitouch.py index a06a087f00b6..fa4fb2054bd4 100644 --- a/tools/testing/selftests/hid/tests/test_multitouch.py +++ b/tools/testing/selftests/hid/tests/test_multitouch.py @@ -979,15 +979,36 @@ class BaseTest: assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_ORIENTATION, 90) in events class TestPTP(TestWin8Multitouch): + def test_buttontype(self): + """Check for the right ButtonType.""" + uhdev = self.uhdev + assert uhdev is not None + evdev = uhdev.get_evdev() + + # If libevdev.so is not yet compiled with INPUT_PROP_PRESSUREPAD + # python-libevdev won't have it either, let's fake it + if not getattr(libevdev, "INPUT_PROP_PRESSUREPAD", None): + prop = libevdev.InputProperty(name="INPUT_PROP_PRESSUREPAD", value=0x7) + libevdev.INPUT_PROP_PRESSUREPAD = prop + libevdev.props.append(prop) + + if uhdev.buttontype == HIDButtonType.CLICKPAD: + assert libevdev.INPUT_PROP_BUTTONPAD in evdev.properties + elif uhdev.buttontype == HIDButtonType.PRESSUREPAD: + assert libevdev.INPUT_PROP_PRESSUREPAD in evdev.properties + else: + assert libevdev.INPUT_PROP_PRESSUREPAD not in evdev.properties + assert libevdev.INPUT_PROP_BUTTONPAD not in evdev.properties + def test_ptp_buttons(self): """check for button reliability. - There are 2 types of touchpads: the click pads and the pressure pads. - Each should reliably report the BTN_LEFT events. + There are 3 types of touchpads: click pads + pressure pads and + those with discrete buttons. Each should reliably report the BTN_LEFT events. """ uhdev = self.uhdev evdev = uhdev.get_evdev() - if uhdev.buttontype == HIDButtonType.CLICKPAD: + if uhdev.buttontype in [HIDButtonType.CLICKPAD, HIDButtonType.PRESSUREPAD]: r = uhdev.event(click=True) events = uhdev.next_sync_events() self.debug_reports(r, uhdev, events) @@ -999,7 +1020,7 @@ class BaseTest: self.debug_reports(r, uhdev, events) assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) in events assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0 - else: + elif uhdev.buttontype == HIDButtonType.DISCRETE_BUTTONS: r = uhdev.event(left=True) events = uhdev.next_sync_events() self.debug_reports(r, uhdev, events) @@ -2062,6 +2083,16 @@ class Testite_06cb_2968(BaseTest.TestPTP): ) +class Testven_0488_108c(BaseTest.TestPTP): + def create_device(self): + return PTP( + "uhid test ven_0488_108c", + rdesc="05 01 09 02 a1 01 85 06 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 03 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 81 06 c0 c0 05 0d 09 05 a1 01 85 01 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 81 03 05 01 15 00 26 ba 0d 75 10 55 0e 65 11 09 30 35 00 46 d0 05 95 01 81 02 26 d0 06 46 bb 02 09 31 81 02 05 0d 95 01 75 10 26 ff 7f 46 ff 7f 09 30 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 81 03 05 01 15 00 26 ba 0d 75 10 55 0e 65 11 09 30 35 00 46 d0 05 95 01 81 02 26 d0 06 46 bb 02 09 31 81 02 05 0d 95 01 75 10 26 ff 7f 46 ff 7f 09 30 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 81 03 05 01 15 00 26 ba 0d 75 10 55 0e 65 11 09 30 35 00 46 d0 05 95 01 81 02 26 d0 06 46 bb 02 09 31 81 02 05 0d 95 01 75 10 26 ff 7f 46 ff 7f 09 30 81 02 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 05 0d 09 56 81 02 09 54 25 05 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 02 09 55 75 08 95 01 25 05 b1 02 09 59 b1 02 06 00 ff 85 03 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 05 0e 09 01 a1 02 85 13 09 23 15 00 25 64 75 08 95 01 b1 02 c0 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 05 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 01 ff 09 02 a1 01 09 00 85 07 15 00 26 ff 00 75 08 96 12 02 b1 02 c0 06 00 ff 09 01 a1 01 85 0d 15 00 26 ff 00 75 08 95 11 09 01 81 02 09 01 91 02 c0 05 0e 09 01 a1 01 85 11 09 35 15 00 26 ff 00 75 08 95 17 b1 02 c0 06 81 ff 09 01 a1 01 09 20 85 17 15 00 26 ff 00 75 08 95 3f 09 01 81 02 09 01 91 02 c0", + input_info=(0x18, 0x0488, 0x108C), + buttontype=HIDButtonType.PRESSUREPAD, + ) + + class Testn_trig_1b96_0c01(BaseTest.TestWin8Multitouch): def create_device(self): return Digitizer( From ad0ae757c39c791e1d447b87f453e1e0840120f4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:13:02 +0200 Subject: [PATCH 115/295] HID: i2c-hid: fix potential buffer overflow in i2c_hid_get_report() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 CVE: CVE-2026-23178 commit 2497ff38c530b1af0df5130ca9f5ab22c5e92f29 Author: Kwok Kin Ming Date: Thu Jan 1 02:18:26 2026 +0800 HID: i2c-hid: fix potential buffer overflow in i2c_hid_get_report() `i2c_hid_xfer` is used to read `recv_len + sizeof(__le16)` bytes of data into `ihid->rawbuf`. The former can come from the userspace in the hidraw driver and is only bounded by HID_MAX_BUFFER_SIZE(16384) by default (unless we also set `max_buffer_size` field of `struct hid_ll_driver` which we do not). The latter has size determined at runtime by the maximum size of different report types you could receive on any particular device and can be a much smaller value. Fix this by truncating `recv_len` to `ihid->bufsize - sizeof(__le16)`. The impact is low since access to hidraw devices requires root. Signed-off-by: Kwok Kin Ming Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/i2c-hid/i2c-hid-core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 63f46a2e5788..5a183af3d5c6 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -286,6 +286,7 @@ static int i2c_hid_get_report(struct i2c_hid *ihid, * In addition to report data device will supply data length * in the first 2 bytes of the response, so adjust . */ + recv_len = min(recv_len, ihid->bufsize - sizeof(__le16)); error = i2c_hid_xfer(ihid, ihid->cmdbuf, length, ihid->rawbuf, recv_len + sizeof(__le16)); if (error) { From 098ec704e38380c31b3c750b16f94984b004c4e0 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:13:04 +0200 Subject: [PATCH 116/295] HID: Intel-thc-hid: Intel-quicki2c: Use size_t for all length variables JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit dabc153c79890911b23c93c1dd65e15b53e87740 Author: Even Xu Date: Tue Dec 9 15:52:12 2025 +0800 HID: Intel-thc-hid: Intel-quicki2c: Use size_t for all length variables Convert all length-related variables in the QuickI2C protocol layer to use size_t type to follow kernel coding conventions. This includes: - All buffer length parameters and variables - Return values of quicki2c_encode_cmd() function which represents encoded command buffer size. - Return values of quicki2c_init_write_buf() function which represents process result: either prepared output buffer size or error code. This change improves type consistency and aligns with standard kernel practices for memory size representation, reducing potential issues with size calculations and comparisons. Signed-off-by: Even Xu Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../intel-quicki2c/quicki2c-dev.h | 2 +- .../intel-quicki2c/quicki2c-protocol.c | 55 +++++++++---------- .../intel-quicki2c/quicki2c-protocol.h | 4 +- 3 files changed, 28 insertions(+), 33 deletions(-) diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h index 2cb5471a8133..33a1e3db1cb2 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h @@ -213,7 +213,7 @@ struct quicki2c_device { u8 *report_descriptor; u8 *input_buf; u8 *report_buf; - u32 report_len; + size_t report_len; wait_queue_head_t reset_ack_wq; bool reset_ack; diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c index a63f8c833252..013cbbb39efd 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c @@ -13,11 +13,11 @@ #include "quicki2c-hid.h" #include "quicki2c-protocol.h" -static int quicki2c_init_write_buf(struct quicki2c_device *qcdev, u32 cmd, int cmd_len, - bool append_data_reg, u8 *data, int data_len, - u8 *write_buf, int write_buf_len) +static ssize_t quicki2c_init_write_buf(struct quicki2c_device *qcdev, u32 cmd, size_t cmd_len, + bool append_data_reg, u8 *data, size_t data_len, + u8 *write_buf, size_t write_buf_len) { - int buf_len, offset = 0; + size_t buf_len, offset = 0; buf_len = HIDI2C_REG_LEN + cmd_len; @@ -51,10 +51,10 @@ static int quicki2c_init_write_buf(struct quicki2c_device *qcdev, u32 cmd, int c return buf_len; } -static int quicki2c_encode_cmd(struct quicki2c_device *qcdev, u32 *cmd_buf, - u8 opcode, u8 report_type, u8 report_id) +static size_t quicki2c_encode_cmd(struct quicki2c_device *qcdev, u32 *cmd_buf, + u8 opcode, u8 report_type, u8 report_id) { - int cmd_len; + size_t cmd_len; *cmd_buf = FIELD_PREP(HIDI2C_CMD_OPCODE, opcode) | FIELD_PREP(HIDI2C_CMD_REPORT_TYPE, report_type); @@ -72,22 +72,20 @@ static int quicki2c_encode_cmd(struct quicki2c_device *qcdev, u32 *cmd_buf, } static int write_cmd_to_txdma(struct quicki2c_device *qcdev, int opcode, - int report_type, int report_id, u8 *buf, int buf_len) + int report_type, int report_id, u8 *buf, size_t buf_len) { - size_t write_buf_len; - int cmd_len, ret; + size_t cmd_len; + ssize_t len; u32 cmd; cmd_len = quicki2c_encode_cmd(qcdev, &cmd, opcode, report_type, report_id); - ret = quicki2c_init_write_buf(qcdev, cmd, cmd_len, buf ? true : false, buf, + len = quicki2c_init_write_buf(qcdev, cmd, cmd_len, buf ? true : false, buf, buf_len, qcdev->report_buf, qcdev->report_len); - if (ret < 0) - return ret; + if (len < 0) + return len; - write_buf_len = ret; - - return thc_dma_write(qcdev->thc_hw, qcdev->report_buf, write_buf_len); + return thc_dma_write(qcdev->thc_hw, qcdev->report_buf, len); } int quicki2c_set_power(struct quicki2c_device *qcdev, enum hidi2c_power_state power_state) @@ -126,13 +124,13 @@ int quicki2c_get_report_descriptor(struct quicki2c_device *qcdev) } int quicki2c_get_report(struct quicki2c_device *qcdev, u8 report_type, - unsigned int reportnum, void *buf, u32 buf_len) + unsigned int reportnum, void *buf, size_t buf_len) { struct hidi2c_report_packet *rpt; - size_t write_buf_len, read_len = 0; - int cmd_len, rep_type; + size_t cmd_len, read_len = 0; + int rep_type, ret; + ssize_t len; u32 cmd; - int ret; if (report_type == HID_INPUT_REPORT) { rep_type = HIDI2C_INPUT; @@ -145,25 +143,22 @@ int quicki2c_get_report(struct quicki2c_device *qcdev, u8 report_type, cmd_len = quicki2c_encode_cmd(qcdev, &cmd, HIDI2C_GET_REPORT, rep_type, reportnum); - ret = quicki2c_init_write_buf(qcdev, cmd, cmd_len, true, NULL, 0, + len = quicki2c_init_write_buf(qcdev, cmd, cmd_len, true, NULL, 0, qcdev->report_buf, qcdev->report_len); - if (ret < 0) - return ret; - - write_buf_len = ret; + if (len < 0) + return len; rpt = (struct hidi2c_report_packet *)qcdev->input_buf; - ret = thc_swdma_read(qcdev->thc_hw, qcdev->report_buf, write_buf_len, - NULL, rpt, &read_len); + ret = thc_swdma_read(qcdev->thc_hw, qcdev->report_buf, len, NULL, rpt, &read_len); if (ret) { - dev_err_once(qcdev->dev, "Get report failed, ret %d, read len (%zu vs %d)\n", + dev_err_once(qcdev->dev, "Get report failed, ret %d, read len (%zu vs %zu)\n", ret, read_len, buf_len); return ret; } if (HIDI2C_DATA_LEN(le16_to_cpu(rpt->len)) != buf_len || rpt->data[0] != reportnum) { - dev_err_once(qcdev->dev, "Invalid packet, len (%d vs %d) report id (%d vs %d)\n", + dev_err_once(qcdev->dev, "Invalid packet, len (%d vs %zu) report id (%d vs %d)\n", le16_to_cpu(rpt->len), buf_len, rpt->data[0], reportnum); return -EINVAL; } @@ -174,7 +169,7 @@ int quicki2c_get_report(struct quicki2c_device *qcdev, u8 report_type, } int quicki2c_set_report(struct quicki2c_device *qcdev, u8 report_type, - unsigned int reportnum, void *buf, u32 buf_len) + unsigned int reportnum, void *buf, size_t buf_len) { int rep_type; int ret; diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h index bf4908cce59c..db70e08c8b1c 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h @@ -10,9 +10,9 @@ struct quicki2c_device; int quicki2c_set_power(struct quicki2c_device *qcdev, enum hidi2c_power_state power_state); int quicki2c_get_report(struct quicki2c_device *qcdev, u8 report_type, - unsigned int reportnum, void *buf, u32 buf_len); + unsigned int reportnum, void *buf, size_t buf_len); int quicki2c_set_report(struct quicki2c_device *qcdev, u8 report_type, - unsigned int reportnum, void *buf, u32 buf_len); + unsigned int reportnum, void *buf, size_t buf_len); int quicki2c_get_device_descriptor(struct quicki2c_device *qcdev); int quicki2c_get_report_descriptor(struct quicki2c_device *qcdev); int quicki2c_reset(struct quicki2c_device *qcdev); From d6a3a97971d9e052959f314441a6253d07710900 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:13:06 +0200 Subject: [PATCH 117/295] HID: Intel-thc-hid: Intel-quicki2c: Use put_unaligned_le16 for __le16 writes JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 678cbaa24f09115352be669884f7775ccd28f757 Author: Even Xu Date: Tue Dec 9 15:52:13 2025 +0800 HID: Intel-thc-hid: Intel-quicki2c: Use put_unaligned_le16 for __le16 writes Replace memcpy operations with put_unaligned_le16() when writing 16-bit little-endian values to the write buffer. This change improves code clarity and ensures proper handling of unaligned memory access. Signed-off-by: Even Xu Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c index 013cbbb39efd..ab390ce79c21 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c @@ -41,9 +41,7 @@ static ssize_t quicki2c_init_write_buf(struct quicki2c_device *qcdev, u32 cmd, s } if (data && data_len) { - __le16 len = cpu_to_le16(data_len + HIDI2C_LENGTH_LEN); - - memcpy(write_buf + offset, &len, HIDI2C_LENGTH_LEN); + put_unaligned_le16(data_len + HIDI2C_LENGTH_LEN, write_buf + offset); offset += HIDI2C_LENGTH_LEN; memcpy(write_buf + offset, data, data_len); } From 91b81903abe368f5b915eb1ed7699da5edf0b046 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:13:08 +0200 Subject: [PATCH 118/295] HID: Intel-thc-hid: Intel-quicki2c: Support writing output report format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit e149af9ce108d7b9e39d731b93c9fb55fd9e7842 Author: Even Xu Date: Tue Dec 9 15:52:14 2025 +0800 HID: Intel-thc-hid: Intel-quicki2c: Support writing output report format There are two output formats requested in the HID-over-I2C specification: - Command format (set feature/set report): encoded command written to command register, followed by data written to data register - Output report format: all data written directly to output register Current quicki2c_init_write_buf() implementation only supports the command format. Extend quicki2c_init_write_buf() to automatically detect the output format based on the presence of command parameters and prepare the appropriate output buffer accordingly. Tested-by: Rui Zhang Signed-off-by: Even Xu Reviewed-by: Ilpo Järvinen Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../intel-quicki2c/quicki2c-protocol.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c index ab390ce79c21..a287d9ee09c3 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c @@ -30,13 +30,18 @@ static ssize_t quicki2c_init_write_buf(struct quicki2c_device *qcdev, u32 cmd, s if (buf_len > write_buf_len) return -EINVAL; - memcpy(write_buf, &qcdev->dev_desc.cmd_reg, HIDI2C_REG_LEN); - offset += HIDI2C_REG_LEN; - memcpy(write_buf + offset, &cmd, cmd_len); - offset += cmd_len; + if (cmd_len) { + memcpy(write_buf, &qcdev->dev_desc.cmd_reg, HIDI2C_REG_LEN); + offset += HIDI2C_REG_LEN; + memcpy(write_buf + offset, &cmd, cmd_len); + offset += cmd_len; - if (append_data_reg) { - memcpy(write_buf + offset, &qcdev->dev_desc.data_reg, HIDI2C_REG_LEN); + if (append_data_reg) { + memcpy(write_buf + offset, &qcdev->dev_desc.data_reg, HIDI2C_REG_LEN); + offset += HIDI2C_REG_LEN; + } + } else { + memcpy(write_buf, &qcdev->dev_desc.output_reg, HIDI2C_REG_LEN); offset += HIDI2C_REG_LEN; } From cb52616b6e2ffe4c711abee988e36ae7df1908f9 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:13:10 +0200 Subject: [PATCH 119/295] HID: Intel-thc-hid: Intel-quicki2c: Add output report support JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit a788b2057029d831ee610c064bb607984a00367d Author: Even Xu Date: Tue Dec 9 15:52:15 2025 +0800 HID: Intel-thc-hid: Intel-quicki2c: Add output report support Add support for HID output reports in the intel-quicki2c driver by implementing the output_report callback in the HID low-level driver interface. This enables proper communication with HID devices that require output report functionality, such as setting device configuration or updating device firmware. Tested-by: Rui Zhang Signed-off-by: Even Xu Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../intel-quicki2c/quicki2c-hid.c | 8 ++++++++ .../intel-quicki2c/quicki2c-protocol.c | 19 +++++++++++++++++++ .../intel-quicki2c/quicki2c-protocol.h | 1 + 3 files changed, 28 insertions(+) diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c index 834a537b6780..f9fcb398673b 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c @@ -82,6 +82,13 @@ static int quicki2c_hid_power(struct hid_device *hid, int lvl) return 0; } +static int quicki2c_hid_output_report(struct hid_device *hid, u8 *buf, size_t count) +{ + struct quicki2c_device *qcdev = hid->driver_data; + + return quicki2c_output_report(qcdev, buf, count); +} + static struct hid_ll_driver quicki2c_hid_ll_driver = { .parse = quicki2c_hid_parse, .start = quicki2c_hid_start, @@ -90,6 +97,7 @@ static struct hid_ll_driver quicki2c_hid_ll_driver = { .close = quicki2c_hid_close, .power = quicki2c_hid_power, .raw_request = quicki2c_hid_raw_request, + .output_report = quicki2c_hid_output_report, }; /** diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c index a287d9ee09c3..41271301215a 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c @@ -195,6 +195,25 @@ int quicki2c_set_report(struct quicki2c_device *qcdev, u8 report_type, return buf_len; } +int quicki2c_output_report(struct quicki2c_device *qcdev, void *buf, size_t buf_len) +{ + ssize_t len; + int ret; + + len = quicki2c_init_write_buf(qcdev, 0, 0, false, buf, buf_len, + qcdev->report_buf, qcdev->report_len); + if (len < 0) + return -EINVAL; + + ret = thc_dma_write(qcdev->thc_hw, qcdev->report_buf, len); + if (ret) { + dev_err(qcdev->dev, "Output Report failed, ret %d\n", ret); + return ret; + } + + return buf_len; +} + #define HIDI2C_RESET_TIMEOUT 5 int quicki2c_reset(struct quicki2c_device *qcdev) diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h index db70e08c8b1c..6642cefb8a67 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h @@ -13,6 +13,7 @@ int quicki2c_get_report(struct quicki2c_device *qcdev, u8 report_type, unsigned int reportnum, void *buf, size_t buf_len); int quicki2c_set_report(struct quicki2c_device *qcdev, u8 report_type, unsigned int reportnum, void *buf, size_t buf_len); +int quicki2c_output_report(struct quicki2c_device *qcdev, void *buf, size_t buf_len); int quicki2c_get_device_descriptor(struct quicki2c_device *qcdev); int quicki2c_get_report_descriptor(struct quicki2c_device *qcdev); int quicki2c_reset(struct quicki2c_device *qcdev); From 54ababafd9e1fda6e681b0f3623c16328c610914 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:13:12 +0200 Subject: [PATCH 120/295] HID: Intel-thc-hid: Intel-quicki2c: Add power management for touch device JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 6761f9ccd6247682430413274e7af955d39cfee4 Author: Even Xu Date: Mon Dec 22 09:41:34 2025 +0800 HID: Intel-thc-hid: Intel-quicki2c: Add power management for touch device Send POWER_SLEEP command to touch device when system enters into sleep mode or hibernate mode to save more power; and send POWER_ON command to take device back when system exits sleep mode. Signed-off-by: Even Xu Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../intel-thc-hid/intel-quicki2c/pci-quicki2c.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c index cfda66ee4895..f178017352ba 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c @@ -807,6 +807,12 @@ static int quicki2c_suspend(struct device *device) if (!qcdev) return -ENODEV; + if (!device_may_wakeup(qcdev->dev)) { + ret = quicki2c_set_power(qcdev, HIDI2C_SLEEP); + if (ret) + return ret; + } + /* * As I2C is THC subsystem, no register auto save/restore support, * need driver to do that explicitly for every D3 case. @@ -856,6 +862,9 @@ static int quicki2c_resume(struct device *device) if (ret) return ret; + if (!device_may_wakeup(qcdev->dev)) + return quicki2c_set_power(qcdev, HIDI2C_ON); + return 0; } @@ -913,6 +922,9 @@ static int quicki2c_poweroff(struct device *device) if (!qcdev) return -ENODEV; + /* Ignore the return value as platform will be poweroff soon */ + quicki2c_set_power(qcdev, HIDI2C_SLEEP); + ret = thc_interrupt_quiesce(qcdev->thc_hw, true); if (ret) return ret; @@ -966,7 +978,7 @@ static int quicki2c_restore(struct device *device) thc_change_ltr_mode(qcdev->thc_hw, THC_LTR_MODE_ACTIVE); - return 0; + return quicki2c_set_power(qcdev, HIDI2C_ON); } static int quicki2c_runtime_suspend(struct device *device) From ceb5443ea8700c2d9edcfdbf5c9e2895011fc630 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:13:14 +0200 Subject: [PATCH 121/295] HID: intel-ish-hid: loader: Add PRODUCT_FAMILY-based firmware matching JIRA: https://issues.redhat.com/browse/RHEL-170872 JIRA: https://redhat.atlassian.net/browse/RHEL-183865 Upstream Status: since v7.0 commit 043251b2dd1c2b0cd23f67830748493fd3d3ed0f Author: Vishnu Sankar Date: Mon Dec 22 17:05:11 2025 +0900 HID: intel-ish-hid: loader: Add PRODUCT_FAMILY-based firmware matching Add support for firmware filenames that include the CRC32 checksum of the DMI product_family field. Several OEMs ship ISH firmware variants shared across a product family while product_name or product_sku may differ. This intermediate matching granularity reduces duplication and improves firmware selection for vendor-customized platforms. The newly supported filename forms are checked before existing patterns: ish_${gen}_${vendor}_${family}_${name}_${sku}.bin ish_${gen}_${vendor}_${family}_${sku}.bin ish_${gen}_${vendor}_${family}_${name}.bin ish_${gen}_${vendor}_${family}.bin The legacy product_name/product_sku rules remain unchanged and continue to provide fallback matching. ISH_FW_FILENAME_LEN_MAX is changed to 72 to accommodate the product_family. Tested with X9 series and X1 series. Reviewed-by: Mark Pearson Tested-by: Richie Roy Jayme Signed-off-by: Vishnu Sankar Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-ish-hid/ishtp/loader.c | 58 +++++++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ishtp/loader.c b/drivers/hid/intel-ish-hid/ishtp/loader.c index f34086b29cf0..ffa2042bb316 100644 --- a/drivers/hid/intel-ish-hid/ishtp/loader.c +++ b/drivers/hid/intel-ish-hid/ishtp/loader.c @@ -195,13 +195,19 @@ static int prepare_dma_bufs(struct ishtp_device *dev, return 0; } +/* Patterns with PRODUCT_FAMILY */ +#define ISH_FW_FILE_VENDOR_FAMILY_NAME_SKU_FMT "intel/ish/ish_%s_%08x_%08x_%08x_%08x.bin" +#define ISH_FW_FILE_VENDOR_FAMILY_SKU_FMT "intel/ish/ish_%s_%08x_%08x_%08x.bin" +#define ISH_FW_FILE_VENDOR_FAMILY_NAME_FMT "intel/ish/ish_%s_%08x_%08x_%08x.bin" +#define ISH_FW_FILE_VENDOR_FAMILY_FMT "intel/ish/ish_%s_%08x_%08x.bin" + #define ISH_FW_FILE_VENDOR_NAME_SKU_FMT "intel/ish/ish_%s_%08x_%08x_%08x.bin" #define ISH_FW_FILE_VENDOR_SKU_FMT "intel/ish/ish_%s_%08x_%08x.bin" #define ISH_FW_FILE_VENDOR_NAME_FMT "intel/ish/ish_%s_%08x_%08x.bin" #define ISH_FW_FILE_VENDOR_FMT "intel/ish/ish_%s_%08x.bin" #define ISH_FW_FILE_DEFAULT_FMT "intel/ish/ish_%s.bin" -#define ISH_FW_FILENAME_LEN_MAX 56 +#define ISH_FW_FILENAME_LEN_MAX 72 #define ISH_CRC_INIT (~0u) #define ISH_CRC_XOROUT (~0u) @@ -228,6 +234,12 @@ static int _request_ish_firmware(const struct firmware **firmware_p, * for the given device in the following order, prioritizing custom firmware * with more precise matching patterns: * + * ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_FAMILY_CRC32) + * _$(PRODUCT_NAME_CRC32)_${PRODUCT_SKU_CRC32}.bin + * + * ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_FAMILY_CRC32)_${PRODUCT_SKU_CRC32}.bin + * ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_FAMILY_CRC32)_$(PRODUCT_NAME_CRC32).bin + * ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_FAMILY_CRC32).bin * ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_NAME_CRC32)_${PRODUCT_SKU_CRC32}.bin * ish_${fw_generation}_${SYS_VENDOR_CRC32}_${PRODUCT_SKU_CRC32}.bin * ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_NAME_CRC32).bin @@ -256,8 +268,9 @@ static int request_ish_firmware(const struct firmware **firmware_p, struct device *dev) { const char *gen, *sys_vendor, *product_name, *product_sku; + const char *product_family; struct ishtp_device *ishtp = dev_get_drvdata(dev); - u32 vendor_crc, name_crc, sku_crc; + u32 vendor_crc, name_crc, sku_crc, family_crc; char filename[ISH_FW_FILENAME_LEN_MAX]; int ret; @@ -265,14 +278,55 @@ static int request_ish_firmware(const struct firmware **firmware_p, sys_vendor = dmi_get_system_info(DMI_SYS_VENDOR); product_name = dmi_get_system_info(DMI_PRODUCT_NAME); product_sku = dmi_get_system_info(DMI_PRODUCT_SKU); + product_family = dmi_get_system_info(DMI_PRODUCT_FAMILY); if (sys_vendor) vendor_crc = crc32(ISH_CRC_INIT, sys_vendor, strlen(sys_vendor)) ^ ISH_CRC_XOROUT; + if (product_family) + family_crc = crc32(ISH_CRC_INIT, product_family, + strlen(product_family)) ^ ISH_CRC_XOROUT; if (product_name) name_crc = crc32(ISH_CRC_INIT, product_name, strlen(product_name)) ^ ISH_CRC_XOROUT; if (product_sku) sku_crc = crc32(ISH_CRC_INIT, product_sku, strlen(product_sku)) ^ ISH_CRC_XOROUT; + /* PRODUCT_FAMILY-extended matching */ + if (sys_vendor && product_family && product_name && product_sku) { + snprintf(filename, sizeof(filename), + ISH_FW_FILE_VENDOR_FAMILY_NAME_SKU_FMT, + gen, vendor_crc, family_crc, name_crc, sku_crc); + ret = _request_ish_firmware(firmware_p, filename, dev); + if (!ret) + return 0; + } + + if (sys_vendor && product_family && product_sku) { + snprintf(filename, sizeof(filename), + ISH_FW_FILE_VENDOR_FAMILY_SKU_FMT, + gen, vendor_crc, family_crc, sku_crc); + ret = _request_ish_firmware(firmware_p, filename, dev); + if (!ret) + return 0; + } + + if (sys_vendor && product_family && product_name) { + snprintf(filename, sizeof(filename), + ISH_FW_FILE_VENDOR_FAMILY_NAME_FMT, + gen, vendor_crc, family_crc, name_crc); + ret = _request_ish_firmware(firmware_p, filename, dev); + if (!ret) + return 0; + } + + if (sys_vendor && product_family) { + snprintf(filename, sizeof(filename), + ISH_FW_FILE_VENDOR_FAMILY_FMT, + gen, vendor_crc, family_crc); + ret = _request_ish_firmware(firmware_p, filename, dev); + if (!ret) + return 0; +} + if (sys_vendor && product_name && product_sku) { snprintf(filename, sizeof(filename), ISH_FW_FILE_VENDOR_NAME_SKU_FMT, gen, vendor_crc, name_crc, sku_crc); From b438084f5d9faf2be9a5d105dbf0b528884de4da Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:13:17 +0200 Subject: [PATCH 122/295] Documentation: hid: intel-ish-hid: Document PRODUCT_FAMILY firmware matching JIRA: https://issues.redhat.com/browse/RHEL-170872 JIRA: https://redhat.atlassian.net/browse/RHEL-183865 Upstream Status: since v7.0 commit 698362f1f4644ac5419b047be6c496afff00d851 Author: Vishnu Sankar Date: Mon Dec 22 17:05:12 2025 +0900 Documentation: hid: intel-ish-hid: Document PRODUCT_FAMILY firmware matching Document the ISH firmware filename matching rules, including the new PRODUCT_FAMILY-based patterns and their search order. This aligns the documentation with the driver behavior and provides clear guidance for vendors supplying custom ISH firmware. Signed-off-by: Vishnu Sankar Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- Documentation/hid/intel-ish-hid.rst | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Documentation/hid/intel-ish-hid.rst b/Documentation/hid/intel-ish-hid.rst index 2adc174fb576..068a5906b177 100644 --- a/Documentation/hid/intel-ish-hid.rst +++ b/Documentation/hid/intel-ish-hid.rst @@ -413,6 +413,10 @@ Vendors who wish to upstream their custom firmware should follow these guideline - The firmware filename should use one of the following patterns: + - ``ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}_${PRODUCT_NAME_CRC32}_${PRODUCT_SKU_CRC32}.bin`` + - ``ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}_${PRODUCT_SKU_CRC32}.bin`` + - ``ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}_${PRODUCT_NAME_CRC32}.bin`` + - ``ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}.bin`` - ``ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}_${PRODUCT_SKU_CRC32}.bin`` - ``ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_SKU_CRC32}.bin`` - ``ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}.bin`` @@ -420,16 +424,21 @@ Vendors who wish to upstream their custom firmware should follow these guideline - ``${intel_plat_gen}`` indicates the Intel platform generation (e.g., ``lnlm`` for Lunar Lake) and must not exceed 8 characters in length. - ``${SYS_VENDOR_CRC32}`` is the CRC32 checksum of the ``sys_vendor`` value from the DMI field ``DMI_SYS_VENDOR``. +- ``${PRODUCT_FAMILY_CRC32}`` is the CRC32 checksum of the ``product_family`` value from the DMI field ``DMI_PRODUCT_FAMILY``. - ``${PRODUCT_NAME_CRC32}`` is the CRC32 checksum of the ``product_name`` value from the DMI field ``DMI_PRODUCT_NAME``. - ``${PRODUCT_SKU_CRC32}`` is the CRC32 checksum of the ``product_sku`` value from the DMI field ``DMI_PRODUCT_SKU``. During system boot, the ISH Linux driver will attempt to load the firmware in the following order, prioritizing custom firmware with more precise matching patterns: -1. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}_${PRODUCT_SKU_CRC32}.bin`` -2. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_SKU_CRC32}.bin`` -3. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}.bin`` -4. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}.bin`` -5. ``intel/ish/ish_${intel_plat_gen}.bin`` +1. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}_${PRODUCT_NAME_CRC32}_${PRODUCT_SKU_CRC32}.bin`` +2. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}_${PRODUCT_SKU_CRC32}.bin`` +3. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}_${PRODUCT_NAME_CRC32}.bin`` +4. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}.bin`` +5. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}_${PRODUCT_SKU_CRC32}.bin`` +6. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_SKU_CRC32}.bin`` +7. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}.bin`` +8. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}.bin`` +9. ``intel/ish/ish_${intel_plat_gen}.bin`` The driver will load the first matching firmware and skip the rest. If no matching firmware is found, it will proceed to the next pattern in the specified order. If all searches fail, the default Intel firmware, listed last in the order above, will be loaded. From 153b88a611487be3923f726a16b796ae73b7f2ca Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:13:18 +0200 Subject: [PATCH 123/295] HID: Apply quirk HID_QUIRK_ALWAYS_POLL to Edifier QR30 (2d99:a101) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 85a866809333cd2bf8ddac93d9a3e3ba8e4f807d Author: Rodrigo Lugathe da Conceição Alves Date: Thu Nov 27 19:03:57 2025 -0300 HID: Apply quirk HID_QUIRK_ALWAYS_POLL to Edifier QR30 (2d99:a101) The USB speaker has a bug that causes it to reboot when changing the brightness using the physical knob. Add a new vendor and product ID entry in hid-ids.h, and register the corresponding device in hid-quirks.c with the required quirk. Signed-off-by: Rodrigo Lugathe da Conceição Alves Reviewed-by: Terry Junge Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 3 +++ drivers/hid/hid-quirks.c | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index e8a1a86313b7..10cb1bc717c1 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -439,6 +439,9 @@ #define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001 0xa001 #define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C002 0xc002 +#define USB_VENDOR_ID_EDIFIER 0x2d99 +#define USB_DEVICE_ID_EDIFIER_QR30 0xa101 /* EDIFIER Hal0 2.0 SE */ + #define USB_VENDOR_ID_ELAN 0x04f3 #define USB_DEVICE_ID_TOSHIBA_CLICK_L9W 0x0401 #define USB_DEVICE_ID_HP_X2 0x074d diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 3cf7971d4974..d6e42125d918 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -81,6 +81,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_PS3), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_WIIU), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER), HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET }, + { HID_USB_DEVICE(USB_VENDOR_ID_EDIFIER, USB_DEVICE_ID_EDIFIER_QR30), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_TS2700), HID_QUIRK_NOGET }, { HID_USB_DEVICE(USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II), HID_QUIRK_MULTI_INPUT }, From 7913fbc72539ea8cf7c1a2be6dae8b03c8fb72be Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:13:20 +0200 Subject: [PATCH 124/295] HID: quirks: work around VID/PID conflict for appledisplay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit c7fabe4ad9219866c203164a214c474c95b36bf2 Author: René Rebe Date: Fri Nov 28 13:46:41 2025 +0100 HID: quirks: work around VID/PID conflict for appledisplay For years I wondered why the Apple Cinema Display driver would not just work for me. Turns out the hidraw driver instantly takes it over. Fix by adding appledisplay VID/PIDs to hid_have_special_driver. Fixes: 069e8a65cd79 ("Driver for Apple Cinema Display") Signed-off-by: René Rebe Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-quirks.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index d6e42125d918..31b2a5d1cd98 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -233,6 +233,15 @@ static const struct hid_device_id hid_quirks[] = { * used as a driver. See hid_scan_report(). */ static const struct hid_device_id hid_have_special_driver[] = { +#if IS_ENABLED(CONFIG_APPLEDISPLAY) + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9218) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9219) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x921c) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x921d) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9222) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9226) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9236) }, +#endif #if IS_ENABLED(CONFIG_HID_A4TECH) { HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU) }, { HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_X5_005D) }, From 5d7d6902d075f4e777893c295ab70d4871a41020 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:13:23 +0200 Subject: [PATCH 125/295] HID: Elecom: Add support for ELECOM M-XT3DRBK (018C) JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 12adb969658ec39265eb8c7ea9e1856867fb9ceb Author: Arnoud Willemsen Date: Sun Dec 7 03:43:19 2025 +0100 HID: Elecom: Add support for ELECOM M-XT3DRBK (018C) Wireless/new version of the Elecom trackball mouse M-XT3DRBK has a product id that differs from the existing M-XT3DRBK. The report descriptor format also seems to have changed and matches other (newer?) models instead (except for six buttons instead of eight). This patch follows the same format as the patch for the M-XT3URBK (018F) by Naoki Ueki (Nov 3rd 2025) to enable the sixth mouse button. dmesg output: [ 292.074664] usb 1-2: new full-speed USB device number 7 using xhci_hcd [ 292.218667] usb 1-2: New USB device found, idVendor=056e, idProduct=018c, bcdDevice= 1.00 [ 292.218676] usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=0 [ 292.218679] usb 1-2: Product: ELECOM TrackBall Mouse [ 292.218681] usb 1-2: Manufacturer: ELECOM usbhid-dump output: 001:006:000:DESCRIPTOR 1765072638.050578 05 01 09 02 A1 01 09 01 A1 00 85 01 05 09 19 01 29 05 15 00 25 01 95 08 75 01 81 02 95 01 75 00 81 01 05 01 09 30 09 31 16 00 80 26 FF 7F 75 10 95 02 81 06 C0 A1 00 05 01 09 38 15 81 25 7F 75 08 95 01 81 06 C0 A1 00 05 0C 0A 38 02 95 01 75 08 15 81 25 7F 81 06 C0 C0 06 01 FF 09 00 A1 01 85 02 09 00 15 00 26 FF 00 75 08 95 07 81 02 C0 05 0C 09 01 A1 01 85 05 15 00 26 3C 02 19 00 2A 3C 02 75 10 95 01 81 00 C0 05 01 09 80 A1 01 85 03 19 81 29 83 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 C0 06 BC FF 09 88 A1 01 85 04 95 01 75 08 15 00 26 FF 00 19 00 2A FF 00 81 00 C0 06 02 FF 09 02 A1 01 85 06 09 02 15 00 26 FF 00 75 08 95 07 B1 02 C0 Signed-off-by: Arnoud Willemsen Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-elecom.c | 15 +++++++++++++-- drivers/hid/hid-ids.h | 3 ++- drivers/hid/hid-quirks.c | 3 ++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c index 981d1b6e9658..2003d2dcda7c 100644 --- a/drivers/hid/hid-elecom.c +++ b/drivers/hid/hid-elecom.c @@ -77,7 +77,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, break; case USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB: case USB_DEVICE_ID_ELECOM_M_XT3URBK_018F: - case USB_DEVICE_ID_ELECOM_M_XT3DRBK: + case USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC: case USB_DEVICE_ID_ELECOM_M_XT4DRBK: /* * Report descriptor format: @@ -102,6 +102,16 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, */ mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8); break; + case USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C: + /* + * Report descriptor format: + * 22: button bit count + * 30: padding bit count + * 24: button report size + * 16: button usage maximum + */ + mouse_button_fixup(hdev, rdesc, *rsize, 22, 30, 24, 16, 6); + break; case USB_DEVICE_ID_ELECOM_M_DT2DRBK: case USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C: /* @@ -122,7 +132,8 @@ static const struct hid_device_id elecom_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 10cb1bc717c1..9c2bf584d9f6 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -455,7 +455,8 @@ #define USB_DEVICE_ID_ELECOM_M_XGL20DLBK 0x00e6 #define USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB 0x00fb #define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f -#define USB_DEVICE_ID_ELECOM_M_XT3DRBK 0x00fc +#define USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC 0x00fc +#define USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C 0x018c #define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd #define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe #define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 31b2a5d1cd98..11438039cdb7 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -422,7 +422,8 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, From 01272ac45916ce9bbe614bac02baab866627a61c Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:13:25 +0200 Subject: [PATCH 126/295] HID: asus: Replace magic number with HID_UP_ASUSVENDOR constant JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 06501b557faec7c4aa1a3188e14c8c3d1e6e15de Author: Ionut Nechita Date: Wed Jan 7 17:42:18 2026 +0200 HID: asus: Replace magic number with HID_UP_ASUSVENDOR constant Use the existing HID_UP_ASUSVENDOR constant instead of the magic number 0xff310000 for better code clarity and maintainability. Reviewed-by: Denis Benato Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Ionut Nechita Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- 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 f9510d9492b1..a1f1354d6b08 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -316,7 +316,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_UP_ASUSVENDOR && (usage->hid & HID_USAGE) != 0x00 && (usage->hid & HID_USAGE) != 0xff && !usage->type) { hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n", From b310c8875d67674bbffbddb95296a0dbb55143da Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:14:07 +0200 Subject: [PATCH 127/295] HID: asus: Filter spurious HID vendor codes on ROG laptops JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 7fe565fb83ba7074dca1d25696f1d30116555528 Author: Ionut Nechita Date: Wed Jan 7 17:42:20 2026 +0200 HID: asus: Filter spurious HID vendor codes on ROG laptops On Asus ROG laptops, several HID vendor usage codes (0xea, 0xec, 0x02, 0x8a, 0x9e) are sent during normal operation without a clear purpose, generating unwanted "Unmapped Asus vendor usagepage code" warnings in dmesg. Add definitions for these codes and filter them out in asus_raw_event() to prevent kernel log spam. Tested on Asus ROG series laptops. Reviewed-by: Denis Benato Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Ionut Nechita Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-asus.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index a1f1354d6b08..4d0a7186fba2 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -56,6 +56,13 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define ROG_ALLY_X_MIN_MCU 313 #define ROG_ALLY_MIN_MCU 319 +/* Spurious HID codes sent by QUIRK_ROG_NKEY_KEYBOARD devices */ +#define ASUS_SPURIOUS_CODE_0XEA 0xea +#define ASUS_SPURIOUS_CODE_0XEC 0xec +#define ASUS_SPURIOUS_CODE_0X02 0x02 +#define ASUS_SPURIOUS_CODE_0X8A 0x8a +#define ASUS_SPURIOUS_CODE_0X9E 0x9e + #define SUPPORT_KBD_BACKLIGHT BIT(0) #define MAX_TOUCH_MAJOR 8 @@ -347,6 +354,21 @@ static int asus_raw_event(struct hid_device *hdev, if (report->id == FEATURE_KBD_LED_REPORT_ID1 || report->id == FEATURE_KBD_LED_REPORT_ID2) return -1; if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) { + /* + * ASUS ROG laptops send these codes during normal operation + * with no discernable reason. Filter them out to avoid + * unmapped warning messages. + */ + if (report->id == FEATURE_KBD_REPORT_ID) { + if (data[1] == ASUS_SPURIOUS_CODE_0XEA || + data[1] == ASUS_SPURIOUS_CODE_0XEC || + data[1] == ASUS_SPURIOUS_CODE_0X02 || + data[1] == ASUS_SPURIOUS_CODE_0X8A || + data[1] == ASUS_SPURIOUS_CODE_0X9E) { + return -1; + } + } + /* * G713 and G733 send these codes on some keypresses, depending on * the key pressed it can trigger a shutdown event if not caught. From 65ef18fadd83350edff60633734f1e5961b13c9d Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:14:10 +0200 Subject: [PATCH 128/295] HID: logitech: add HID++ support for Logitech MX Anywhere 3S JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit d7f6629bffdcb962d383ef8c9a30afef81e997fe Author: Dennis Marttinen Date: Sun Jan 4 13:00:51 2026 +0000 HID: logitech: add HID++ support for Logitech MX Anywhere 3S I've acquired a Logitech MX Anywhere 3S mouse, which supports HID++ over Bluetooth. Adding its PID 0xb037 to the allowlist enables the additional features, such as high-resolution scrolling. Tested working across multiple machines, with a mix of Intel and Mediatek Bluetooth chips. [jkosina@suse.com: standardize shortlog] Signed-off-by: Dennis Marttinen Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index d5011a5d0890..e871f1729d4b 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -4662,6 +4662,8 @@ static const struct hid_device_id hidpp_devices[] = { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb025) }, { /* MX Master 3S mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb034) }, + { /* MX Anywhere 3S mouse over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb037) }, { /* MX Anywhere 3SB mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb038) }, {} From 4df983e3c02feb6e43fb3b95e892c3f0b341ddef Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:14:11 +0200 Subject: [PATCH 129/295] HID: rapoo: Add support for side buttons on RAPOO 0x2015 mouse JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit b3b1c68fb726907ea35ac172906705fe62c7fdaf Author: Nguyen Dinh Dang Duong Date: Sat Jun 28 00:04:23 2025 +0700 HID: rapoo: Add support for side buttons on RAPOO 0x2015 mouse This patch adds support for handling the side buttons on the RAPOO 0x2015 wireless mouse. These buttons were previously not generating input events due to missing driver logic. The new code handles raw HID input report with Report ID 1 and maps the side buttons to KEY_BACK and KEY_FORWARD using the input subsystem. Tested on a RAPOO mouse with USB ID 24AE:2015. Signed-off-by: Nguyen Dinh Dang Duong Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/Kconfig | 6 +++ drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 3 ++ drivers/hid/hid-rapoo.c | 101 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+) create mode 100644 drivers/hid/hid-rapoo.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index f6a3b68abb89..9c6532babf4f 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1015,6 +1015,12 @@ config HID_PXRC To compile this driver as a module, choose M here: the module will be called hid-pxrc. +config HID_RAPOO + tristate "Rapoo non-fully HID-compliant devices" + help + Support for Rapoo devices that are not fully compliant with the + HID standard. + config HID_RAZER tristate "Razer non-fully HID-compliant devices" help diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 361a7daedeb8..e01838239ae6 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -113,6 +113,7 @@ obj-$(CONFIG_HID_PLANTRONICS) += hid-plantronics.o obj-$(CONFIG_HID_PLAYSTATION) += hid-playstation.o obj-$(CONFIG_HID_PRIMAX) += hid-primax.o obj-$(CONFIG_HID_PXRC) += hid-pxrc.o +obj-$(CONFIG_HID_RAPOO) += hid-rapoo.o obj-$(CONFIG_HID_RAZER) += hid-razer.o obj-$(CONFIG_HID_REDRAGON) += hid-redragon.o obj-$(CONFIG_HID_RETRODE) += hid-retrode.o diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 9c2bf584d9f6..943399e2b444 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1166,6 +1166,9 @@ #define I2C_PRODUCT_ID_RAYDIUM_4B33 0x4b33 #define I2C_PRODUCT_ID_RAYDIUM_3118 0x3118 +#define USB_VENDOR_ID_RAPOO 0x24ae +#define USB_DEVICE_ID_RAPOO_2_4G_RECEIVER 0x2015 + #define USB_VENDOR_ID_RAZER 0x1532 #define USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE 0x010D #define USB_DEVICE_ID_RAZER_BLACKWIDOW 0x010e diff --git a/drivers/hid/hid-rapoo.c b/drivers/hid/hid-rapoo.c new file mode 100644 index 000000000000..4c81f3086de4 --- /dev/null +++ b/drivers/hid/hid-rapoo.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" + +#define RAPOO_BTN_BACK 0x08 +#define RAPOO_BTN_FORWARD 0x10 + +static const struct hid_device_id rapoo_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_RAPOO, USB_DEVICE_ID_RAPOO_2_4G_RECEIVER) }, + { } +}; +MODULE_DEVICE_TABLE(hid, rapoo_devices); + +static int rapoo_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + struct input_dev *input; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "start failed\n"); + return ret; + } + + if (hdev->bus == BUS_USB) { + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + + if (intf->cur_altsetting->desc.bInterfaceNumber != 1) + return 0; + } + + input = devm_input_allocate_device(&hdev->dev); + if (!input) + return -ENOMEM; + + input->name = "Rapoo 2.4G Wireless Mouse"; + input->phys = "rapoo/input1"; + input->id.bustype = hdev->bus; + input->id.vendor = hdev->vendor; + input->id.product = hdev->product; + input->id.version = hdev->version; + + __set_bit(EV_KEY, input->evbit); + __set_bit(KEY_BACK, input->keybit); + __set_bit(KEY_FORWARD, input->keybit); + + ret = input_register_device(input); + if (ret) + return ret; + + hid_set_drvdata(hdev, input); + + return ret; +} + +static int rapoo_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) +{ + struct input_dev *input = hid_get_drvdata(hdev); + + if (!input) + return 0; + + if (report->id == 1 && size >= 2) { + u8 btn = data[1]; + + input_report_key(input, KEY_BACK, btn & RAPOO_BTN_BACK); + input_report_key(input, KEY_FORWARD, btn & RAPOO_BTN_FORWARD); + input_sync(input); + return 1; + } + + return 0; +} + +static struct hid_driver rapoo_driver = { + .name = "hid-rapoo", + .id_table = rapoo_devices, + .probe = rapoo_probe, + .raw_event = rapoo_raw_event, +}; + +module_hid_driver(rapoo_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nguyen Dinh Dang Duong "); +MODULE_DESCRIPTION("RAPOO 2.4G Wireless Device Driver"); + From edbab8c6ff429f07cf9ee4f3c17cc72fb8bd2393 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:14:12 +0200 Subject: [PATCH 130/295] HID: apple: Add "SONiX KN85 Keyboard" to the list of non-apple keyboards JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 7273acfd0aef106093a8ffa3b4973eb70e5a3799 Author: Joey Bednar Date: Wed Nov 12 06:06:23 2025 +0000 HID: apple: Add "SONiX KN85 Keyboard" to the list of non-apple keyboards The SoNiX KN85 keyboard identifies as the "Apple, Inc. Aluminium Keyboard" and is not recognized as a non-apple keyboard. Adding "SoNiX KN85 Keyboard" to the list of non-apple keyboards fixes the function keys. Signed-off-by: Joey Bednar Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-apple.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 57da4f86a9fa..233e367cce1d 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -354,6 +354,7 @@ static const struct apple_key_translation swapped_fn_leftctrl_keys[] = { }; static const struct apple_non_apple_keyboard non_apple_keyboards[] = { + { "SONiX KN85 Keyboard" }, { "SONiX USB DEVICE" }, { "SONiX AK870 PRO" }, { "Keychron" }, From 6e6afb7f9085a4b6bd1690a35f0bab49ccd61632 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:14:13 +0200 Subject: [PATCH 131/295] HID: playstation: Add missing check for input_ff_create_memless JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-45969 commit e6807641ac94e832988655a1c0e60ccc806b76dc Author: Haotian Zhang Date: Mon Nov 17 16:28:08 2025 +0800 HID: playstation: Add missing check for input_ff_create_memless The ps_gamepad_create() function calls input_ff_create_memless() without verifying its return value, which can lead to incorrect behavior or potential crashes when FF effects are triggered. Add a check for the return value of input_ff_create_memless(). Fixes: 51151098d7ab ("HID: playstation: add DualSense classic rumble support.") Signed-off-by: Haotian Zhang Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index e4dfcf26b04e..2ec6d4445e84 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -774,7 +774,9 @@ ps_gamepad_create(struct hid_device *hdev, #if IS_ENABLED(CONFIG_PLAYSTATION_FF) if (play_effect) { input_set_capability(gamepad, EV_FF, FF_RUMBLE); - input_ff_create_memless(gamepad, NULL, play_effect); + ret = input_ff_create_memless(gamepad, NULL, play_effect); + if (ret) + return ERR_PTR(ret); } #endif From 27947216a5a1cae89427f4c25c65cf92664c8b6f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:14:14 +0200 Subject: [PATCH 132/295] HID: multitouch: add quirks for Lenovo Yoga Book 9i JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 822bc5b3744b0b2c2c9678aa1d80b2cf04fdfabf Author: Brian Howard Date: Tue Dec 2 21:35:47 2025 -0500 HID: multitouch: add quirks for Lenovo Yoga Book 9i The Lenovo Yoga Book 9i is a dual-screen laptop, with a single composite USB device providing both touch and tablet interfaces for both screens. All inputs report through a single device, differentiated solely by report numbers. As there is no way for udev to differentiate the inputs based on USB vendor/product ID or interface numbers, custom naming is required to match against for downstream configuration. A firmware bug also results in an erroneous InRange message report being received after the stylus leaves proximity, blocking later touch events. Add required quirks for Gen 8 to Gen 10 models, including a new quirk providing for custom input device naming and dropping erroneous InRange reports. Signed-off-by: Brian Howard Tested-by: Brian Howard Tested-by: Kris Fredrick Reported-by: Andrei Shumailov Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220386 Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-multitouch.c | 72 ++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 943399e2b444..40364cd69f8e 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -841,6 +841,7 @@ #define USB_DEVICE_ID_LENOVO_X1_TAB3 0x60b5 #define USB_DEVICE_ID_LENOVO_X12_TAB 0x60fe #define USB_DEVICE_ID_LENOVO_X12_TAB2 0x61ae +#define USB_DEVICE_ID_LENOVO_YOGABOOK9I 0x6161 #define USB_DEVICE_ID_LENOVO_OPTICAL_USB_MOUSE_600E 0x600e #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_608D 0x608d #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6019 0x6019 diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index b1c3ef129058..f21850f7d89e 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -76,6 +76,7 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_DISABLE_WAKEUP BIT(21) #define MT_QUIRK_ORIENTATION_INVERT BIT(22) #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) +#define MT_QUIRK_YOGABOOK9I BIT(24) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 @@ -231,6 +232,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 #define MT_CLS_SMART_TECH 0x0113 #define MT_CLS_APPLE_TOUCHBAR 0x0114 +#define MT_CLS_YOGABOOK9I 0x0115 #define MT_CLS_SIS 0x0457 #define MT_DEFAULT_MAXCONTACT 10 @@ -427,6 +429,14 @@ static const struct mt_class mt_classes[] = { .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP | MT_QUIRK_ALWAYS_VALID | MT_QUIRK_CONTACT_CNT_ACCURATE, + }, + { .name = MT_CLS_YOGABOOK9I, + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_FORCE_MULTI_INPUT | + MT_QUIRK_SEPARATE_APP_REPORT | + MT_QUIRK_HOVERING | + MT_QUIRK_YOGABOOK9I, + .export_all_inputs = true }, { } }; @@ -1576,6 +1586,38 @@ static void mt_report(struct hid_device *hid, struct hid_report *report) if (rdata && rdata->is_mt_collection) return mt_touch_report(hid, rdata); + /* Lenovo Yoga Book 9i requires consuming and dropping certain bogus reports */ + if (rdata && rdata->application && + (rdata->application->quirks & MT_QUIRK_YOGABOOK9I)) { + + bool all_zero_report = true; + + for (int f = 0; f < report->maxfield && all_zero_report; f++) { + struct hid_field *fld = report->field[f]; + + for (int i = 0; i < fld->report_count; i++) { + unsigned int usage = fld->usage[i].hid; + + if (usage == HID_DG_INRANGE || + usage == HID_DG_TIPSWITCH || + usage == HID_DG_BARRELSWITCH || + usage == HID_DG_BARRELSWITCH2 || + usage == HID_DG_CONTACTID || + usage == HID_DG_TILT_X || + usage == HID_DG_TILT_Y) { + + if (fld->value[i] != 0) { + all_zero_report = false; + break; + } + } + } + } + + if (all_zero_report) + return; + } + if (field && field->hidinput && field->hidinput->input) input_sync(field->hidinput->input); } @@ -1772,6 +1814,30 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) break; } + /* Lenovo Yoga Book 9i requires custom naming to allow differentiation in udev */ + if (hi->report && td->mtclass.quirks & MT_QUIRK_YOGABOOK9I) { + switch (hi->report->id) { + case 48: + suffix = "Touchscreen Top"; + break; + case 56: + suffix = "Touchscreen Bottom"; + break; + case 20: + suffix = "Stylus Top"; + break; + case 40: + suffix = "Stylus Bottom"; + break; + case 80: + suffix = "Emulated Touchpad"; + break; + default: + suffix = ""; + break; + } + } + if (suffix) { hi->input->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", hdev->name, suffix); @@ -2277,6 +2343,12 @@ static const struct hid_device_id mt_devices[] = { USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X12_TAB2) }, + /* Lenovo Yoga Book 9i */ + { .driver_data = MT_CLS_YOGABOOK9I, + HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, + USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_YOGABOOK9I) }, + /* Logitech devices */ { .driver_data = MT_CLS_NSMU, HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_MULTITOUCH_WIN_8, From 58f1277474c763fb28a6c834e372a0adafb4c16a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:14:16 +0200 Subject: [PATCH 133/295] HID: pidff: Do not set out of range trigger button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit e01a029654f7fb67d7151365410aa22be4e63dbe Author: Tomasz Pakuła Date: Sat Nov 29 19:46:14 2025 +0100 HID: pidff: Do not set out of range trigger button Some games (mainly observed with Kylotonn's WRC Serises) set trigger button to a random value, or always the same one, out of range. I observed 307 and other values but, for example, my Moza R9 only exposes 128 buttons AND it's trigger button field is 8-bit. This causes errors to appear in dmesg. Only set the trigger button and trigger interval in the trigger button is in range of the field. Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 95377c5f6335..a4e700b40ba9 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -523,9 +523,19 @@ static void pidff_set_effect_report(struct pidff_device *pidff, pidff_set_duration(&pidff->set_effect[PID_DURATION], effect->replay.length); - pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = effect->trigger.button; - pidff_set_time(&pidff->set_effect[PID_TRIGGER_REPEAT_INT], - effect->trigger.interval); + /* Some games set this to random values that can be out of range */ + s32 trigger_button_max = + pidff->set_effect[PID_TRIGGER_BUTTON].field->logical_maximum; + if (effect->trigger.button <= trigger_button_max) { + pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = + effect->trigger.button; + pidff_set_time(&pidff->set_effect[PID_TRIGGER_REPEAT_INT], + effect->trigger.interval); + } else { + pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = 0; + pidff->set_effect[PID_TRIGGER_REPEAT_INT].value[0] = 0; + } + pidff->set_effect[PID_GAIN].value[0] = pidff->set_effect[PID_GAIN].field->logical_maximum; From 42ac63f3d75dd8d78833cb1965d9524d894afeca Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:14:17 +0200 Subject: [PATCH 134/295] HID: hid-pl: handle probe errors JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-43152 commit 3756a272d2cf356d2203da8474d173257f5f8521 Author: Oliver Neukum Date: Wed Nov 19 10:09:57 2025 +0100 HID: hid-pl: handle probe errors Errors in init must be reported back or we'll follow a NULL pointer the first time FF is used. Fixes: 20eb127906709 ("hid: force feedback driver for PantherLord USB/PS2 2in1 Adapter") Cc: stable@vger.kernel.org Signed-off-by: Oliver Neukum Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-pl.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-pl.c b/drivers/hid/hid-pl.c index 3c8827081dea..dc11d5322fc0 100644 --- a/drivers/hid/hid-pl.c +++ b/drivers/hid/hid-pl.c @@ -194,9 +194,14 @@ static int pl_probe(struct hid_device *hdev, const struct hid_device_id *id) goto err; } - plff_init(hdev); + ret = plff_init(hdev); + if (ret) + goto stop; return 0; + +stop: + hid_hw_stop(hdev); err: return ret; } From d090ef050b0ba59a4a064a86d640c40f9b9534f1 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:14:20 +0200 Subject: [PATCH 135/295] HID: sony: add support for Rock Band 4 PS4 and PS5 guitars JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit bbf992775faa66d6c94097c2be9a0d254681a419 Author: Rosalie Wanders Date: Fri Jan 2 22:12:28 2026 +0100 HID: sony: add support for Rock Band 4 PS4 and PS5 guitars This commit adds support for the PDP RiffMaster and the CRKD Gibson SG in both their PS4 and PS5 modes. These devices aren't mapped correctly without these changes, they also lack support for their whammy and tilt functionality which this patch adds support for by binding them to the left and right triggers. Signed-off-by: Rosalie Wanders Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/Kconfig | 4 +- drivers/hid/hid-ids.h | 8 +++ drivers/hid/hid-sony.c | 119 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 127 insertions(+), 4 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 9c6532babf4f..09d8b9fb23d0 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1090,7 +1090,7 @@ config HID_SIGMAMICRO - Rapoo V500 config HID_SONY - tristate "Sony PS2/3/4 accessories" + tristate "Sony PS2/3/4/5 accessories" depends on USB_HID depends on NEW_LEDS depends on LEDS_CLASS @@ -1100,12 +1100,12 @@ config HID_SONY Support for * Sony PS3 6-axis controllers - * Sony PS4 DualShock 4 controllers * Buzz controllers * Sony PS3 Blue-ray Disk Remote Control (Bluetooth) * Logitech Harmony adapter for Sony Playstation 3 (Bluetooth) * Guitar Hero Live PS3, Wii U and PS4 guitar dongles * Guitar Hero PS3 and PC guitar dongles + * Rock Band 4 PS4 and PS5 guitars config SONY_FF bool "Sony PS2/3/4 accessories force feedback support" diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 40364cd69f8e..6b762f53fbe7 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -364,6 +364,10 @@ #define USB_DEVICE_ID_PRODIKEYS_PCMIDI 0x2801 #define USB_DEVICE_ID_CREATIVE_SB0540 0x3100 +#define USB_VENDOR_ID_CRKD 0x3651 +#define USB_DEVICE_ID_CRKD_PS4_GIBSON_SG 0x1500 +#define USB_DEVICE_ID_CRKD_PS5_GIBSON_SG 0x1600 + #define USB_VENDOR_ID_CVTOUCH 0x1ff7 #define USB_DEVICE_ID_CVTOUCH_SCREEN 0x0013 @@ -1151,6 +1155,10 @@ #define USB_VENDOR_ID_POWERCOM 0x0d9f #define USB_DEVICE_ID_POWERCOM_UPS 0x0002 +#define USB_VENDOR_ID_PDP 0x0e6F +#define USB_DEVICE_ID_PDP_PS4_RIFFMASTER 0x024a +#define USB_DEVICE_ID_PDP_PS5_RIFFMASTER 0x0249 + #define USB_VENDOR_ID_PRODIGE 0x05af #define USB_DEVICE_ID_PRODIGE_CORDLESS 0x3062 diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index b966e4044238..e2f17a99fa42 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -14,6 +14,7 @@ * Copyright (c) 2020-2021 Pascal Giard * Copyright (c) 2020 Sanjay Govind * Copyright (c) 2021 Daniel Nguyen + * Copyright (c) 2026 Rosalie Wanders */ /* @@ -61,6 +62,8 @@ #define GH_GUITAR_CONTROLLER BIT(14) #define GHL_GUITAR_PS3WIIU BIT(15) #define GHL_GUITAR_PS4 BIT(16) +#define RB4_GUITAR_PS4 BIT(17) +#define RB4_GUITAR_PS5 BIT(18) #define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT) #define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT) @@ -418,6 +421,27 @@ static const unsigned int sixaxis_keymap[] = { [0x11] = BTN_MODE, /* PS */ }; +static const unsigned int rb4_absmap[] = { + [0x30] = ABS_X, + [0x31] = ABS_Y, +}; + +static const unsigned int rb4_keymap[] = { + [0x1] = BTN_WEST, /* Square */ + [0x2] = BTN_SOUTH, /* Cross */ + [0x3] = BTN_EAST, /* Circle */ + [0x4] = BTN_NORTH, /* Triangle */ + [0x5] = BTN_TL, /* L1 */ + [0x6] = BTN_TR, /* R1 */ + [0x7] = BTN_TL2, /* L2 */ + [0x8] = BTN_TR2, /* R2 */ + [0x9] = BTN_SELECT, /* Share */ + [0xa] = BTN_START, /* Options */ + [0xb] = BTN_THUMBL, /* L3 */ + [0xc] = BTN_THUMBR, /* R3 */ + [0xd] = BTN_MODE, /* PS */ +}; + static enum power_supply_property sony_battery_props[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_CAPACITY, @@ -484,6 +508,7 @@ struct sony_sc { spinlock_t lock; struct list_head list_node; struct hid_device *hdev; + struct input_dev *input_dev; struct input_dev *touchpad; struct input_dev *sensor_dev; struct led_classdev *leds[MAX_LEDS]; @@ -584,7 +609,7 @@ static int ghl_init_urb(struct sony_sc *sc, struct usb_device *usbdev, return 0; } -static int guitar_mapping(struct hid_device *hdev, struct hid_input *hi, +static int gh_guitar_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { @@ -599,6 +624,38 @@ static int guitar_mapping(struct hid_device *hdev, struct hid_input *hi, return 0; } +static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) + +{ + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) { + unsigned int key = usage->hid & HID_USAGE; + + if (key >= ARRAY_SIZE(rb4_keymap)) + return 0; + + key = rb4_keymap[key]; + hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key); + return 1; + } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) { + unsigned int abs = usage->hid & HID_USAGE; + + /* Let the HID parser deal with the HAT. */ + if (usage->hid == HID_GD_HATSWITCH) + return 0; + + if (abs >= ARRAY_SIZE(rb4_absmap)) + return 0; + + abs = rb4_absmap[abs]; + hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs); + return 1; + } + + return 0; +} + static const u8 *motion_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *rsize) { @@ -915,6 +972,40 @@ static void nsg_mrxu_parse_report(struct sony_sc *sc, u8 *rd, int size) input_sync(sc->touchpad); } +static void rb4_ps4_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size) +{ + /* + * Rock Band 4 PS4 guitars have whammy and + * tilt functionality, they're located at + * byte 44 and 45 respectively. + * + * We will map these values to the triggers + * because the guitars don't have anything + * mapped there. + */ + input_report_abs(sc->input_dev, ABS_Z, rd[44]); + input_report_abs(sc->input_dev, ABS_RZ, rd[45]); + + input_sync(sc->input_dev); +} + +static void rb4_ps5_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size) +{ + /* + * Rock Band 4 PS5 guitars have whammy and + * tilt functionality, they're located at + * byte 41 and 42 respectively. + * + * We will map these values to the triggers + * because the guitars don't have anything + * mapped there. + */ + input_report_abs(sc->input_dev, ABS_Z, rd[41]); + input_report_abs(sc->input_dev, ABS_RZ, rd[42]); + + input_sync(sc->input_dev); +} + static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *rd, int size) { @@ -950,6 +1041,12 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, } else if ((sc->quirks & NSG_MRXU_REMOTE) && rd[0] == 0x02) { nsg_mrxu_parse_report(sc, rd, size); return 1; + } else if ((sc->quirks & RB4_GUITAR_PS4) && rd[0] == 0x01 && size == 64) { + rb4_ps4_guitar_parse_report(sc, rd, size); + return 1; + } else if ((sc->quirks & RB4_GUITAR_PS5) && rd[0] == 0x01 && size == 64) { + rb4_ps5_guitar_parse_report(sc, rd, size); + return 1; } if (sc->defer_initialization) { @@ -999,7 +1096,13 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi, return sixaxis_mapping(hdev, hi, field, usage, bit, max); if (sc->quirks & GH_GUITAR_CONTROLLER) - return guitar_mapping(hdev, hi, field, usage, bit, max); + return gh_guitar_mapping(hdev, hi, field, usage, bit, max); + + if (sc->quirks & RB4_GUITAR_PS4) + return rb4_guitar_mapping(hdev, hi, field, usage, bit, max); + + if (sc->quirks & RB4_GUITAR_PS5) + return rb4_guitar_mapping(hdev, hi, field, usage, bit, max); /* Let hid-core decide for the others */ return 0; @@ -2016,6 +2119,8 @@ static int sony_input_configured(struct hid_device *hdev, } else if (sc->quirks & MOTION_CONTROLLER) { sony_init_output_report(sc, motion_send_output_report); + } else if (sc->quirks & (RB4_GUITAR_PS4 | RB4_GUITAR_PS5)) { + sc->input_dev = hidinput->input; } if (sc->quirks & SONY_LED_SUPPORT) { @@ -2271,6 +2376,16 @@ static const struct hid_device_id sony_devices[] = { /* Guitar Hero Live PS4 guitar dongles */ { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE), .driver_data = GHL_GUITAR_PS4 | GH_GUITAR_CONTROLLER }, + /* Rock Band 4 PS4 guitars */ + { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER), + .driver_data = RB4_GUITAR_PS4 }, + { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG), + .driver_data = RB4_GUITAR_PS4 }, + /* Rock Band 4 PS5 guitars */ + { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS5_RIFFMASTER), + .driver_data = RB4_GUITAR_PS5 }, + { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS5_GIBSON_SG), + .driver_data = RB4_GUITAR_PS5 }, { } }; MODULE_DEVICE_TABLE(hid, sony_devices); From df4b18f67948254f53c4e96017f9d80a7221bdc4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:14:22 +0200 Subject: [PATCH 136/295] HID: magicmouse: Do not crash on missing msc->input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-43140 commit 17abd396548035fbd6179ee1a431bd75d49676a7 Author: Günther Noack Date: Fri Jan 9 11:57:14 2026 +0100 HID: magicmouse: Do not crash on missing msc->input Fake USB devices can send their own report descriptors for which the input_mapping() hook does not get called. In this case, msc->input stays NULL, leading to a crash at a later time. Detect this condition in the input_configured() hook and reject the device. This is not supposed to happen with actual magic mouse devices, but can be provoked by imposing as a magic mouse USB device. Cc: stable@vger.kernel.org Signed-off-by: Günther Noack Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-magicmouse.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 7d4a25c6de0e..91f621ceb924 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -725,6 +725,11 @@ static int magicmouse_input_configured(struct hid_device *hdev, struct magicmouse_sc *msc = hid_get_drvdata(hdev); int ret; + if (!msc->input) { + hid_err(hdev, "magicmouse setup input failed (no input)"); + return -EINVAL; + } + ret = magicmouse_setup_input(msc->input, hdev); if (ret) { hid_err(hdev, "magicmouse setup input failed (%d)\n", ret); From c7d75b7aed5382f45a8f1de46fb4f70fa6f4bb09 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:14:25 +0200 Subject: [PATCH 137/295] HID: prodikeys: Check presence of pm->input_ep82 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-43251 commit cee8337e1bad168136aecfe6416ecd7d3aa7529a Author: Günther Noack Date: Fri Jan 9 11:58:08 2026 +0100 HID: prodikeys: Check presence of pm->input_ep82 Fake USB devices can send their own report descriptors for which the input_mapping() hook does not get called. In this case, pm->input_ep82 stays NULL, which leads to a crash later. This does not happen with the real device, but can be provoked by imposing as one. Cc: stable@vger.kernel.org Signed-off-by: Günther Noack Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-prodikeys.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/hid/hid-prodikeys.c b/drivers/hid/hid-prodikeys.c index 74bddb2c3e82..6e413df38358 100644 --- a/drivers/hid/hid-prodikeys.c +++ b/drivers/hid/hid-prodikeys.c @@ -378,6 +378,10 @@ static int pcmidi_handle_report4(struct pcmidi_snd *pm, u8 *data) bit_mask = (bit_mask << 8) | data[2]; bit_mask = (bit_mask << 8) | data[3]; + /* robustness in case input_mapping hook does not get called */ + if (!pm->input_ep82) + return 0; + /* break keys */ for (bit_index = 0; bit_index < 24; bit_index++) { if (!((0x01 << bit_index) & bit_mask)) { From e95a39a8672a1cb6c639ffbf26fd4d6501dad02a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:14:27 +0200 Subject: [PATCH 138/295] HID: logitech-hidpp: Check maxfield in hidpp_get_report_length() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-43136 commit 1547d41f9f19d691c2c9ce4c29f746297baef9e9 Author: Günther Noack Date: Fri Jan 9 13:25:58 2026 +0100 HID: logitech-hidpp: Check maxfield in hidpp_get_report_length() Do not crash when a report has no fields. Fake USB gadgets can send their own HID report descriptors and can define report structures without valid fields. This can be used to crash the kernel over USB. Cc: stable@vger.kernel.org Signed-off-by: Günther Noack Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index e871f1729d4b..d0a38eff9cfa 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -4314,7 +4314,7 @@ static int hidpp_get_report_length(struct hid_device *hdev, int id) re = &(hdev->report_enum[HID_OUTPUT_REPORT]); report = re->report_id_hash[id]; - if (!report) + if (!report || !report->maxfield) return 0; return report->field[0]->report_count + 1; From 11c7183abd9cdfe4528f75e98edf925d7d24d033 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:14:29 +0200 Subject: [PATCH 139/295] HID: sony: add support for bluetooth Rock Band 4 PS4 guitars JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 2e18e787edddd8a21b3022d55dd0f6be7c422191 Author: Rosalie Wanders Date: Sun Jan 11 22:00:50 2026 +0100 HID: sony: add support for bluetooth Rock Band 4 PS4 guitars This commit adds support for the PDP Jaguar and MadCatz Stratocaster. Signed-off-by: Rosalie Wanders Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 12 ++++++++---- drivers/hid/hid-sony.c | 24 +++++++++++++++--------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 6b762f53fbe7..bacaee49734d 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1107,6 +1107,11 @@ #define USB_DEVICE_ID_ORTEK_WKB2000 0x2000 #define USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S 0x8003 +#define USB_VENDOR_ID_PDP 0x0e6F +#define USB_DEVICE_ID_PDP_PS4_JAGUAR 0x0173 +#define USB_DEVICE_ID_PDP_PS4_RIFFMASTER 0x024a +#define USB_DEVICE_ID_PDP_PS5_RIFFMASTER 0x0249 + #define USB_VENDOR_ID_PLANTRONICS 0x047f #define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3210_SERIES 0xc055 #define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3220_SERIES 0xc056 @@ -1155,10 +1160,6 @@ #define USB_VENDOR_ID_POWERCOM 0x0d9f #define USB_DEVICE_ID_POWERCOM_UPS 0x0002 -#define USB_VENDOR_ID_PDP 0x0e6F -#define USB_DEVICE_ID_PDP_PS4_RIFFMASTER 0x024a -#define USB_DEVICE_ID_PDP_PS5_RIFFMASTER 0x0249 - #define USB_VENDOR_ID_PRODIGE 0x05af #define USB_DEVICE_ID_PRODIGE_CORDLESS 0x3062 @@ -1548,6 +1549,9 @@ #define USB_VENDOR_ID_RISO_KAGAKU 0x1294 /* Riso Kagaku Corp. */ #define USB_DEVICE_ID_RI_KA_WEBMAIL 0x1320 /* Webmail Notifier */ +#define USB_VENDOR_ID_MADCATZ 0x0738 +#define USB_DEVICE_ID_MADCATZ_PS4_STRATOCASTER 0x8261 + #define USB_VENDOR_ID_MULTIPLE_1781 0x1781 #define USB_DEVICE_ID_RAPHNET_4NES4SNES_OLD 0x0a9d #define USB_DEVICE_ID_PHOENIXRC 0x0898 diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index e2f17a99fa42..d0270ffec4f4 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -62,8 +62,9 @@ #define GH_GUITAR_CONTROLLER BIT(14) #define GHL_GUITAR_PS3WIIU BIT(15) #define GHL_GUITAR_PS4 BIT(16) -#define RB4_GUITAR_PS4 BIT(17) -#define RB4_GUITAR_PS5 BIT(18) +#define RB4_GUITAR_PS4_USB BIT(17) +#define RB4_GUITAR_PS4_BT BIT(18) +#define RB4_GUITAR_PS5 BIT(19) #define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT) #define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT) @@ -627,7 +628,6 @@ static int gh_guitar_mapping(struct hid_device *hdev, struct hid_input *hi, static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) - { if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) { unsigned int key = usage->hid & HID_USAGE; @@ -1041,7 +1041,10 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, } else if ((sc->quirks & NSG_MRXU_REMOTE) && rd[0] == 0x02) { nsg_mrxu_parse_report(sc, rd, size); return 1; - } else if ((sc->quirks & RB4_GUITAR_PS4) && rd[0] == 0x01 && size == 64) { + } else if ((sc->quirks & RB4_GUITAR_PS4_USB) && rd[0] == 0x01 && size == 64) { + rb4_ps4_guitar_parse_report(sc, rd, size); + return 1; + } else if ((sc->quirks & RB4_GUITAR_PS4_BT) && rd[0] == 0x01 && size == 78) { rb4_ps4_guitar_parse_report(sc, rd, size); return 1; } else if ((sc->quirks & RB4_GUITAR_PS5) && rd[0] == 0x01 && size == 64) { @@ -1098,7 +1101,7 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi, if (sc->quirks & GH_GUITAR_CONTROLLER) return gh_guitar_mapping(hdev, hi, field, usage, bit, max); - if (sc->quirks & RB4_GUITAR_PS4) + if (sc->quirks & (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT)) return rb4_guitar_mapping(hdev, hi, field, usage, bit, max); if (sc->quirks & RB4_GUITAR_PS5) @@ -2119,8 +2122,6 @@ static int sony_input_configured(struct hid_device *hdev, } else if (sc->quirks & MOTION_CONTROLLER) { sony_init_output_report(sc, motion_send_output_report); - } else if (sc->quirks & (RB4_GUITAR_PS4 | RB4_GUITAR_PS5)) { - sc->input_dev = hidinput->input; } if (sc->quirks & SONY_LED_SUPPORT) { @@ -2148,6 +2149,7 @@ static int sony_input_configured(struct hid_device *hdev, goto err_close; } + sc->input_dev = hidinput->input; return 0; err_close: hid_hw_close(hdev); @@ -2378,9 +2380,13 @@ static const struct hid_device_id sony_devices[] = { .driver_data = GHL_GUITAR_PS4 | GH_GUITAR_CONTROLLER }, /* Rock Band 4 PS4 guitars */ { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER), - .driver_data = RB4_GUITAR_PS4 }, + .driver_data = RB4_GUITAR_PS4_USB }, { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG), - .driver_data = RB4_GUITAR_PS4 }, + .driver_data = RB4_GUITAR_PS4_USB }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_JAGUAR), + .driver_data = RB4_GUITAR_PS4_BT }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_PS4_STRATOCASTER), + .driver_data = RB4_GUITAR_PS4_BT }, /* Rock Band 4 PS5 guitars */ { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS5_RIFFMASTER), .driver_data = RB4_GUITAR_PS5 }, From 0ff6a3951c86e287c586c21ea977f55dc505390e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:15:33 +0200 Subject: [PATCH 140/295] HID: i2c-hid: Add FocalTech FT8112 JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 3d9586f1f90c9101b1abf5b0e9d70ca45f5f16db Author: Daniel Peng Date: Mon Nov 17 17:40:41 2025 +0800 HID: i2c-hid: Add FocalTech FT8112 Information for touchscreen model HKO/RB116AS01-2 as below: - HID :FTSC1000 - slave address:0X38 - Interface:HID over I2C - Touch control lC:FT8112 - I2C ID: PNP0C50 Signed-off-by: Daniel Peng Acked-by: Jiri Kosina Reviewed-by: Douglas Anderson Link: https://patch.msgid.link/20251117094041.300083-2-Daniel_Peng@pegatron.corp-partner.google.com Signed-off-by: Dmitry Torokhov Signed-off-by: Benjamin Tissoires --- drivers/hid/i2c-hid/i2c-hid-of-elan.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/hid/i2c-hid/i2c-hid-of-elan.c b/drivers/hid/i2c-hid/i2c-hid-of-elan.c index 0215f217f6d8..b81fcc6ff49e 100644 --- a/drivers/hid/i2c-hid/i2c-hid-of-elan.c +++ b/drivers/hid/i2c-hid/i2c-hid-of-elan.c @@ -168,6 +168,13 @@ static const struct elan_i2c_hid_chip_data elan_ekth6a12nay_chip_data = { .power_after_backlight = true, }; +static const struct elan_i2c_hid_chip_data focaltech_ft8112_chip_data = { + .post_power_delay_ms = 10, + .post_gpio_reset_on_delay_ms = 150, + .hid_descriptor_address = 0x0001, + .main_supply_name = "vcc33", +}; + static const struct elan_i2c_hid_chip_data ilitek_ili9882t_chip_data = { .post_power_delay_ms = 1, .post_gpio_reset_on_delay_ms = 200, @@ -191,6 +198,7 @@ static const struct elan_i2c_hid_chip_data ilitek_ili2901_chip_data = { static const struct of_device_id elan_i2c_hid_of_match[] = { { .compatible = "elan,ekth6915", .data = &elan_ekth6915_chip_data }, { .compatible = "elan,ekth6a12nay", .data = &elan_ekth6a12nay_chip_data }, + { .compatible = "focaltech,ft8112", .data = &focaltech_ft8112_chip_data }, { .compatible = "ilitek,ili9882t", .data = &ilitek_ili9882t_chip_data }, { .compatible = "ilitek,ili2901", .data = &ilitek_ili2901_chip_data }, { } From a9e7ce72f1a29c9450e0b196c0f5a70b26cbbb43 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:15:34 +0200 Subject: [PATCH 141/295] HID: elecom: Add support for ELECOM HUGE Plus M-HT1MRBK JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit b8e5fdf0bd022cd5493a5987ef66f5a24f8352d8 Author: David Phillips Date: Fri Jan 23 12:56:09 2026 +0900 HID: elecom: Add support for ELECOM HUGE Plus M-HT1MRBK New model in the ELECOM HUGE trackball line that has 8 buttons but the report descriptor specifies only 5. The HUGE Plus supports connecting via Bluetooth, 2.4GHz wireless USB dongle, and directly via a USB-C cable. Each connection type reports a different device id, 01AA for cable, 01AB for USB dongle, and 01AC for Bluetooth. This patch adds these device IDs and applies the fixups similar to the other ELECOM devices to get all 8 buttons working for all 3 connection types. For reference, the usbhid-dump output: 001:013:001:DESCRIPTOR 1769085639.598405 05 01 09 02 A1 01 85 01 09 01 A1 00 05 09 19 01 29 05 15 00 25 01 75 01 95 05 81 02 75 03 95 01 81 01 05 01 09 30 09 31 16 01 80 26 FF 7F 75 10 95 02 81 06 09 38 15 81 25 7F 75 08 95 01 81 06 05 0C 0A 38 02 15 81 25 7F 75 08 95 01 81 06 C0 C0 05 0C 09 01 A1 01 85 02 15 01 26 8C 02 19 01 2A 8C 02 75 10 95 01 81 00 C0 05 01 09 80 A1 01 85 03 09 82 09 81 09 83 15 00 25 01 19 01 29 03 75 01 95 03 81 02 95 05 81 01 C0 06 01 FF 09 00 A1 01 85 08 09 00 15 00 26 FF 00 75 08 95 07 81 02 C0 06 02 FF 09 02 A1 01 85 06 09 02 15 00 26 FF 00 75 08 95 07 B1 02 C0 Signed-off-by: David Phillips Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/Kconfig | 1 + drivers/hid/hid-elecom.c | 16 ++++++++++++++++ drivers/hid/hid-ids.h | 3 +++ drivers/hid/hid-quirks.c | 3 +++ 4 files changed, 23 insertions(+) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 09d8b9fb23d0..8d2cc97fb0f4 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -369,6 +369,7 @@ config HID_ELECOM - EX-G Trackballs (M-XT3DRBK, M-XT3URBK) - DEFT Trackballs (M-DT1DRBK, M-DT1URBK, M-DT2DRBK, M-DT2URBK) - HUGE Trackballs (M-HT1DRBK, M-HT1URBK) + - HUGE Plus Trackball (M-HT1MRBK) config HID_ELO tristate "ELO USB 4000/4500 touchscreen" diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c index 2003d2dcda7c..37d88ce57f67 100644 --- a/drivers/hid/hid-elecom.c +++ b/drivers/hid/hid-elecom.c @@ -5,6 +5,7 @@ * - EX-G Trackballs (M-XT3DRBK, M-XT3URBK, M-XT4DRBK) * - DEFT Trackballs (M-DT1DRBK, M-DT1URBK, M-DT2DRBK, M-DT2URBK) * - HUGE Trackballs (M-HT1DRBK, M-HT1URBK) + * - HUGE Plus Trackball (M-HT1MRBK) * * Copyright (c) 2010 Richard Nauber * Copyright (c) 2016 Yuxuan Shui @@ -123,12 +124,25 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, */ mouse_button_fixup(hdev, rdesc, *rsize, 22, 30, 24, 16, 8); break; + case USB_DEVICE_ID_ELECOM_M_HT1MRBK: + case USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AB: + case USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC: + /* + * Report descriptor format: + * 24: button bit count + * 28: padding bit count + * 22: button report size + * 16: button usage maximum + */ + mouse_button_fixup(hdev, rdesc, *rsize, 24, 28, 22, 16, 8); + break; } return rdesc; } static const struct hid_device_id elecom_devices[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, @@ -142,6 +156,8 @@ static const struct hid_device_id elecom_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK_019B) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AB) }, { } }; MODULE_DEVICE_TABLE(hid, elecom_devices); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index bacaee49734d..074917d4cb4b 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -469,6 +469,9 @@ #define USB_DEVICE_ID_ELECOM_M_HT1URBK_019B 0x019b #define USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D 0x010d #define USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C 0x011c +#define USB_DEVICE_ID_ELECOM_M_HT1MRBK 0x01aa +#define USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AB 0x01ab +#define USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC 0x01ac #define USB_VENDOR_ID_DREAM_CHEEKY 0x1d34 #define USB_DEVICE_ID_DREAM_CHEEKY_WN 0x0004 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 11438039cdb7..3217e436c052 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -420,6 +420,7 @@ static const struct hid_device_id hid_have_special_driver[] = { #if IS_ENABLED(CONFIG_HID_ELECOM) { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, @@ -432,6 +433,8 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK_019B) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AB) }, #endif #if IS_ENABLED(CONFIG_HID_ELO) { HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0009) }, From 0a807f463e372f35af239ca181b75a02f61ffc67 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:15:35 +0200 Subject: [PATCH 142/295] HID: multitouch: add eGalaxTouch EXC3188 support JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 8e4ac86b2ddd36fe501e20ecfcc080e536df1f48 Author: Thorsten Schmelzer Date: Fri Jan 23 09:57:05 2026 +0100 HID: multitouch: add eGalaxTouch EXC3188 support Add support for the for the EXC3188 touchscreen from eGalaxy. Signed-off-by: Thorsten Schmelzer Signed-off-by: Michael Tretter Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-multitouch.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 074917d4cb4b..f105714098fd 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -441,6 +441,7 @@ #define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7349 0x7349 #define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_73F7 0x73f7 #define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001 0xa001 +#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C000 0xc000 #define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C002 0xc002 #define USB_VENDOR_ID_EDIFIER 0x2d99 diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index f21850f7d89e..7daa8f6d8187 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -2212,6 +2212,9 @@ static const struct hid_device_id mt_devices[] = { { .driver_data = MT_CLS_EGALAX_SERIAL, MT_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001) }, + { .driver_data = MT_CLS_EGALAX_SERIAL, + MT_USB_DEVICE(USB_VENDOR_ID_DWAV, + USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C000) }, { .driver_data = MT_CLS_EGALAX, MT_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C002) }, From 7d9c4cca9992c6ff660a17df8620671ec6f97699 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:15:57 +0200 Subject: [PATCH 143/295] HID: hid-alps: Use pm_ptr instead of #ifdef CONFIG_PM JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 5e130f58629a2819c9d053507ad368160d25eb65 Author: Bastien Nocera Date: Tue Jan 13 10:24:49 2026 +0100 HID: hid-alps: Use pm_ptr instead of #ifdef CONFIG_PM This increases build coverage and allows to drop an #ifdef. Signed-off-by: Bastien Nocera Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-alps.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index ba00f6e6324b..21e55f3d0d1b 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -840,10 +840,8 @@ static struct hid_driver alps_driver = { .raw_event = alps_raw_event, .input_mapping = alps_input_mapping, .input_configured = alps_input_configured, -#ifdef CONFIG_PM - .resume = alps_post_resume, - .reset_resume = alps_post_reset, -#endif + .resume = pm_ptr(alps_post_resume), + .reset_resume = pm_ptr(alps_post_reset), }; module_hid_driver(alps_driver); From 293f8093a009fb436e3b570c5b0311362f9e2495 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:15:58 +0200 Subject: [PATCH 144/295] HID: appletb-kbd: Use pm_ptr instead of #ifdef CONFIG_PM JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit d7192e4dfa3603626aeea8c8c05ee8fd1e885a5d Author: Bastien Nocera Date: Tue Jan 13 10:24:50 2026 +0100 HID: appletb-kbd: Use pm_ptr instead of #ifdef CONFIG_PM This increases build coverage and allows to drop an #ifdef. Signed-off-by: Bastien Nocera Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-appletb-kbd.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-appletb-kbd.c b/drivers/hid/hid-appletb-kbd.c index b00687e67ce8..a1db3b3d0667 100644 --- a/drivers/hid/hid-appletb-kbd.c +++ b/drivers/hid/hid-appletb-kbd.c @@ -466,7 +466,6 @@ static void appletb_kbd_remove(struct hid_device *hdev) hid_hw_stop(hdev); } -#ifdef CONFIG_PM static int appletb_kbd_suspend(struct hid_device *hdev, pm_message_t msg) { struct appletb_kbd *kbd = hid_get_drvdata(hdev); @@ -485,7 +484,6 @@ static int appletb_kbd_reset_resume(struct hid_device *hdev) return 0; } -#endif static const struct hid_device_id appletb_kbd_hid_ids[] = { /* MacBook Pro's 2018, 2019, with T2 chip: iBridge Display */ @@ -501,10 +499,8 @@ static struct hid_driver appletb_kbd_hid_driver = { .remove = appletb_kbd_remove, .event = appletb_kbd_hid_event, .input_configured = appletb_kbd_input_configured, -#ifdef CONFIG_PM - .suspend = appletb_kbd_suspend, - .reset_resume = appletb_kbd_reset_resume, -#endif + .suspend = pm_ptr(appletb_kbd_suspend), + .reset_resume = pm_ptr(appletb_kbd_reset_resume), .driver.dev_groups = appletb_kbd_groups, }; module_hid_driver(appletb_kbd_hid_driver); From 30649912cf27dc3a9b4bba0fadcd60b60f517cb5 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 09:38:07 +0200 Subject: [PATCH 145/295] HID: asus: Use pm_ptr instead of #ifdef CONFIG_PM JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 72f3d4c50cfa8ff3f54d8ac6c2e15d1962091368 Author: Bastien Nocera Date: Tue Jan 13 10:24:51 2026 +0100 HID: asus: Use pm_ptr instead of #ifdef CONFIG_PM This increases build coverage and allows to drop an #ifdef. Signed-off-by: Bastien Nocera Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-asus.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 4d0a7186fba2..3a590c878b02 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -1452,10 +1452,8 @@ static struct hid_driver asus_driver = { .remove = asus_remove, .input_mapping = asus_input_mapping, .input_configured = asus_input_configured, -#ifdef CONFIG_PM - .reset_resume = asus_reset_resume, - .resume = asus_resume, -#endif + .reset_resume = pm_ptr(asus_reset_resume), + .resume = pm_ptr(asus_resume), .event = asus_event, .raw_event = asus_raw_event }; From f874f3c53d1aa45542acd8f8559234c65997c4a7 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:16:00 +0200 Subject: [PATCH 146/295] HID: lenovo: Use pm_ptr instead of #ifdef CONFIG_PM JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 7e42d91c3b2eb959e1e9787ee808a1824aab0cb6 Author: Bastien Nocera Date: Tue Jan 13 10:24:52 2026 +0100 HID: lenovo: Use pm_ptr instead of #ifdef CONFIG_PM This increases build coverage and allows to drop an #ifdef. Signed-off-by: Bastien Nocera Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index 9cc3e029e9f6..a6b73e03c16b 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -1422,7 +1422,6 @@ err: return ret; } -#ifdef CONFIG_PM static int lenovo_reset_resume(struct hid_device *hdev) { switch (hdev->product) { @@ -1438,7 +1437,6 @@ static int lenovo_reset_resume(struct hid_device *hdev) return 0; } -#endif static void lenovo_remove_tpkbd(struct hid_device *hdev) { @@ -1570,9 +1568,7 @@ static struct hid_driver lenovo_driver = { .raw_event = lenovo_raw_event, .event = lenovo_event, .report_fixup = lenovo_report_fixup, -#ifdef CONFIG_PM - .reset_resume = lenovo_reset_resume, -#endif + .reset_resume = pm_ptr(lenovo_reset_resume), }; module_hid_driver(lenovo_driver); From a3296031b863bd675e45aa5a40df981d15104b7f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:16:01 +0200 Subject: [PATCH 147/295] HID: logitech-dj: Use pm_ptr instead of #ifdef CONFIG_PM JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit dcd2c3517807a438d9c22044c5570ac282e0cd8f Author: Bastien Nocera Date: Tue Jan 13 10:24:53 2026 +0100 HID: logitech-dj: Use pm_ptr instead of #ifdef CONFIG_PM This increases build coverage and allows to drop an #ifdef. Signed-off-by: Bastien Nocera Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-dj.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 44b716697510..3d8821d5b2d2 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -1983,7 +1983,6 @@ hid_hw_start_fail: return retval; } -#ifdef CONFIG_PM static int logi_dj_reset_resume(struct hid_device *hdev) { struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev); @@ -1994,7 +1993,6 @@ static int logi_dj_reset_resume(struct hid_device *hdev) logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0); return 0; } -#endif static void logi_dj_remove(struct hid_device *hdev) { @@ -2150,9 +2148,7 @@ static struct hid_driver logi_djreceiver_driver = { .probe = logi_dj_probe, .remove = logi_dj_remove, .raw_event = logi_dj_raw_event, -#ifdef CONFIG_PM - .reset_resume = logi_dj_reset_resume, -#endif + .reset_resume = pm_ptr(logi_dj_reset_resume), }; module_hid_driver(logi_djreceiver_driver); From 45fd066b18f48072a83c0002ba4764d3f5af7375 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:16:02 +0200 Subject: [PATCH 148/295] HID: nintendo: Use pm_ptr instead of #ifdef CONFIG_PM JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit d7b7ce3dc616d78ee4be329fc17cac97fe08efb7 Author: Bastien Nocera Date: Tue Jan 13 10:24:54 2026 +0100 HID: nintendo: Use pm_ptr instead of #ifdef CONFIG_PM This increases build coverage and allows to drop an #ifdef. Signed-off-by: Bastien Nocera Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-nintendo.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index 7ac9217d9096..29008c2cc530 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -2748,8 +2748,6 @@ static void nintendo_hid_remove(struct hid_device *hdev) hid_hw_stop(hdev); } -#ifdef CONFIG_PM - static int nintendo_hid_resume(struct hid_device *hdev) { struct joycon_ctlr *ctlr = hid_get_drvdata(hdev); @@ -2792,8 +2790,6 @@ static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message) return 0; } -#endif - static const struct hid_device_id nintendo_hid_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_PROCON) }, @@ -2827,11 +2823,8 @@ static struct hid_driver nintendo_hid_driver = { .probe = nintendo_hid_probe, .remove = nintendo_hid_remove, .raw_event = nintendo_hid_event, - -#ifdef CONFIG_PM - .resume = nintendo_hid_resume, - .suspend = nintendo_hid_suspend, -#endif + .resume = pm_ptr(nintendo_hid_resume), + .suspend = pm_ptr(nintendo_hid_suspend), }; static int __init nintendo_init(void) { From 68c78a03d8635a3c8ae271577b8c0281096e6d47 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:16:03 +0200 Subject: [PATCH 149/295] HID: picolcd_core: Use pm_ptr instead of #ifdef CONFIG_PM JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit c9b66161ccf8ef39e28f6852838b881b205c6759 Author: Bastien Nocera Date: Tue Jan 13 10:24:55 2026 +0100 HID: picolcd_core: Use pm_ptr instead of #ifdef CONFIG_PM This increases build coverage and allows to drop an #ifdef. Signed-off-by: Bastien Nocera Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-picolcd_core.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/hid/hid-picolcd_core.c b/drivers/hid/hid-picolcd_core.c index 297103be3381..6a88e6bc70f3 100644 --- a/drivers/hid/hid-picolcd_core.c +++ b/drivers/hid/hid-picolcd_core.c @@ -363,7 +363,6 @@ static int picolcd_raw_event(struct hid_device *hdev, return 1; } -#ifdef CONFIG_PM static int picolcd_suspend(struct hid_device *hdev, pm_message_t message) { if (PMSG_IS_AUTO(message)) @@ -401,7 +400,6 @@ static int picolcd_reset_resume(struct hid_device *hdev) picolcd_leds_set(hid_get_drvdata(hdev)); return 0; } -#endif /* initialize keypad input device */ static int picolcd_init_keys(struct picolcd_data *data, @@ -648,11 +646,9 @@ static struct hid_driver picolcd_driver = { .probe = picolcd_probe, .remove = picolcd_remove, .raw_event = picolcd_raw_event, -#ifdef CONFIG_PM - .suspend = picolcd_suspend, - .resume = picolcd_resume, - .reset_resume = picolcd_reset_resume, -#endif + .suspend = pm_ptr(picolcd_suspend), + .resume = pm_ptr(picolcd_resume), + .reset_resume = pm_ptr(picolcd_reset_resume), }; module_hid_driver(picolcd_driver); From 612dcb0ecc511fc5cd47d7a9d8f118e7c280fd02 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:16:05 +0200 Subject: [PATCH 150/295] HID: hid-sensor-hub: Use pm_ptr instead of #ifdef CONFIG_PM JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit b3e5bcffda372bea49555fef4e311687bed439bc Author: Bastien Nocera Date: Tue Jan 13 10:24:56 2026 +0100 HID: hid-sensor-hub: Use pm_ptr instead of #ifdef CONFIG_PM This increases build coverage and allows to drop an #ifdef. Signed-off-by: Bastien Nocera Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-sensor-hub.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 4c94c03cb573..0edec902be41 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -422,7 +422,6 @@ int sensor_hub_input_get_attribute_info(struct hid_sensor_hub_device *hsdev, } EXPORT_SYMBOL_GPL(sensor_hub_input_get_attribute_info); -#ifdef CONFIG_PM static int sensor_hub_suspend(struct hid_device *hdev, pm_message_t message) { struct sensor_hub_data *pdata = hid_get_drvdata(hdev); @@ -463,7 +462,6 @@ static int sensor_hub_reset_resume(struct hid_device *hdev) { return 0; } -#endif /* * Handle raw report as sent by device @@ -772,11 +770,9 @@ static struct hid_driver sensor_hub_driver = { .remove = sensor_hub_remove, .raw_event = sensor_hub_raw_event, .report_fixup = sensor_hub_report_fixup, -#ifdef CONFIG_PM - .suspend = sensor_hub_suspend, - .resume = sensor_hub_resume, - .reset_resume = sensor_hub_reset_resume, -#endif + .suspend = pm_ptr(sensor_hub_suspend), + .resume = pm_ptr(sensor_hub_resume), + .reset_resume = pm_ptr(sensor_hub_reset_resume), }; module_hid_driver(sensor_hub_driver); From 04a5e22a84b3b1117a27a16facc9c1d413f5961c Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:16:06 +0200 Subject: [PATCH 151/295] HID: uclogic: Use pm_ptr instead of #ifdef CONFIG_PM JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit d1de9bde979a62b3b331a801965d6e6fe8cbe17b Author: Bastien Nocera Date: Tue Jan 13 10:24:57 2026 +0100 HID: uclogic: Use pm_ptr instead of #ifdef CONFIG_PM This increases build coverage and allows to drop an #ifdef. Signed-off-by: Bastien Nocera Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-uclogic-core.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index 90ebb81041ea..bd7f93e96e4e 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -268,7 +268,6 @@ failure: return rc; } -#ifdef CONFIG_PM static int uclogic_resume(struct hid_device *hdev) { int rc; @@ -283,7 +282,6 @@ static int uclogic_resume(struct hid_device *hdev) return rc; } -#endif /** * uclogic_exec_event_hook - if the received event is hooked schedules the @@ -636,10 +634,8 @@ static struct hid_driver uclogic_driver = { .raw_event = uclogic_raw_event, .input_mapping = uclogic_input_mapping, .input_configured = uclogic_input_configured, -#ifdef CONFIG_PM - .resume = uclogic_resume, - .reset_resume = uclogic_resume, -#endif + .resume = pm_ptr(uclogic_resume), + .reset_resume = pm_ptr(uclogic_resume), }; module_hid_driver(uclogic_driver); From 3d4b7f1187f9237bc74973d1a710a3e29fc3946f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:16:08 +0200 Subject: [PATCH 152/295] HID: wacom: Use pm_ptr instead of #ifdef CONFIG_PM JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit a864d16be15625df365a5945f566efc2e36f73c3 Author: Bastien Nocera Date: Tue Jan 13 10:24:58 2026 +0100 HID: wacom: Use pm_ptr instead of #ifdef CONFIG_PM This increases build coverage and allows to drop an #ifdef. Signed-off-by: Bastien Nocera Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/wacom_sys.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 9a57504e51a1..afc900560706 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -2921,7 +2921,6 @@ static void wacom_remove(struct hid_device *hdev) wacom_release_resources(wacom); } -#ifdef CONFIG_PM static int wacom_resume(struct hid_device *hdev) { struct wacom *wacom = hid_get_drvdata(hdev); @@ -2941,7 +2940,6 @@ static int wacom_reset_resume(struct hid_device *hdev) { return wacom_resume(hdev); } -#endif /* CONFIG_PM */ static struct hid_driver wacom_driver = { .name = "wacom", @@ -2949,10 +2947,8 @@ static struct hid_driver wacom_driver = { .probe = wacom_probe, .remove = wacom_remove, .report = wacom_wac_report, -#ifdef CONFIG_PM - .resume = wacom_resume, - .reset_resume = wacom_reset_resume, -#endif + .resume = pm_ptr(wacom_resume), + .reset_resume = pm_ptr(wacom_reset_resume), .raw_event = wacom_raw_event, }; module_hid_driver(wacom_driver); From 515cb45d0973805e0f3c1eeb93dd4fdddd8c3a59 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:16:09 +0200 Subject: [PATCH 153/295] HID: sony: Use pm_ptr instead of #ifdef CONFIG_PM JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit ff7ed83beab92d304f326a98c575c9fff7503f4c Author: Bastien Nocera Date: Tue Jan 13 10:24:59 2026 +0100 HID: sony: Use pm_ptr instead of #ifdef CONFIG_PM This increases build coverage and allows to drop an #ifdef. Signed-off-by: Bastien Nocera Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-sony.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index d0270ffec4f4..d74ee8e756e9 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -2286,7 +2286,6 @@ static void sony_remove(struct hid_device *hdev) hid_hw_stop(hdev); } -#ifdef CONFIG_PM static int sony_suspend(struct hid_device *hdev, pm_message_t message) { @@ -2321,8 +2320,6 @@ static int sony_resume(struct hid_device *hdev) return 0; } -#endif - static const struct hid_device_id sony_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER), .driver_data = SIXAXIS_CONTROLLER_USB }, @@ -2405,12 +2402,9 @@ static struct hid_driver sony_driver = { .remove = sony_remove, .report_fixup = sony_report_fixup, .raw_event = sony_raw_event, - -#ifdef CONFIG_PM - .suspend = sony_suspend, - .resume = sony_resume, - .reset_resume = sony_resume, -#endif + .suspend = pm_ptr(sony_suspend), + .resume = pm_ptr(sony_resume), + .reset_resume = pm_ptr(sony_resume), }; static int __init sony_init(void) From 62aa06d7397294d071dacaae820cc2f9d4a5525e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:16:11 +0200 Subject: [PATCH 154/295] HID: logitech-dj: Differentiate "invalid device index" error JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 4c6a83fc2f3061108828fbd2c59bdd89d9dd54e7 Author: Bastien Nocera Date: Sun Jan 25 13:12:01 2026 +0100 HID: logitech-dj: Differentiate "invalid device index" error We had 2 codepaths throwing the "invalid device index" error, but one of them was about the index not matching the receiver, so change the error to "invalid receiver index". Signed-off-by: Bastien Nocera Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-dj.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 3d8821d5b2d2..c41fded63f4b 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -1664,7 +1664,7 @@ static int logi_dj_dj_event(struct hid_device *hdev, * so ignore those reports too. */ if (dj_report->device_index != DJ_RECEIVER_INDEX) - hid_err(hdev, "%s: invalid device index:%d\n", + hid_err(hdev, "%s: invalid receiver index:%d\n", __func__, dj_report->device_index); return false; } From 7c03f661554df0076281c242ad6f8bae68306a38 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:16:13 +0200 Subject: [PATCH 155/295] HID: logitech-hidpp: Add support for Logitech K980 JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit af4fe07a9d963a72438ade96cf090e84b3399d0c Author: Bastien Nocera Date: Sun Jan 25 13:12:02 2026 +0100 HID: logitech-hidpp: Add support for Logitech K980 Add support for the solar-charging Logitech K980 keyboard, over Bluetooth. Bolt traffic doesn't get routed through logitech-dj, so this code isn't triggered when Bolt is used. Signed-off-by: Bastien Nocera Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index d0a38eff9cfa..02d83c3bd73d 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -4666,6 +4666,8 @@ static const struct hid_device_id hidpp_devices[] = { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb037) }, { /* MX Anywhere 3SB mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb038) }, + { /* Slim Solar+ K980 Keyboard over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb391) }, {} }; From 2605179997f1560db517259fd162826e7a8c5853 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:16:14 +0200 Subject: [PATCH 156/295] HID: intel-ish-hid: fix NULL-ptr-deref in ishtp_bus_remove_all_clients JIRA: https://issues.redhat.com/browse/RHEL-170872 JIRA: https://redhat.atlassian.net/browse/RHEL-183865 Upstream Status: since v7.0 CVE: CVE-2026-45877 commit 56f7db581ee73af53cd512e00a6261a025bf1d58 Author: Ryan Lin Date: Fri Jan 30 13:34:56 2026 +0800 HID: intel-ish-hid: fix NULL-ptr-deref in ishtp_bus_remove_all_clients During a warm reset flow, the cl->device pointer may be NULL if the reset occurs while clients are still being enumerated. Accessing cl->device->reference_count without a NULL check leads to a kernel panic. This issue was identified during multi-unit warm reboot stress clycles. Add a defensive NULL check for cl->device to ensure stability under such intensive testing conditions. KASAN: null-ptr-deref in range [0000000000000000-0000000000000007] Workqueue: ish_fw_update_wq fw_reset_work_fn Call Trace: ishtp_bus_remove_all_clients+0xbe/0x130 [intel_ishtp] ishtp_reset_handler+0x85/0x1a0 [intel_ishtp] fw_reset_work_fn+0x8a/0xc0 [intel_ish_ipc] Fixes: 3703f53b99e4a ("HID: intel_ish-hid: ISH Transport layer") Signed-off-by: Ryan Lin Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-ish-hid/ishtp/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c index c3915f3a060e..b890fbf97a75 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.c +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -730,7 +730,7 @@ void ishtp_bus_remove_all_clients(struct ishtp_device *ishtp_dev, spin_lock_irqsave(&ishtp_dev->cl_list_lock, flags); list_for_each_entry(cl, &ishtp_dev->cl_list, link) { cl->state = ISHTP_CL_DISCONNECTED; - if (warm_reset && cl->device->reference_count) + if (warm_reset && cl->device && cl->device->reference_count) continue; /* From 97ac6dc71bca01f24c7e1e2bb7e5891eb739491b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:16:16 +0200 Subject: [PATCH 157/295] HID: Intel-thc-hid: Intel-thc: Fix wrong register fields updating JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit e4aa247d94a04574297a8bc9fabbede0dcba1ab6 Author: Even Xu Date: Mon Feb 2 11:01:44 2026 +0800 HID: Intel-thc-hid: Intel-thc: Fix wrong register fields updating Clear the target bit fields in register before setting new values. This ensures proper field updates by removing any existing bits that might interfere with the new configuration. Fixes: 22da60f0304b ("HID: Intel-thc-hid: Intel-thc: Introduce interrupt delay control") Fixes: 45e92a093099 ("HID: Intel-thc-hid: Intel-thc: Introduce max input size control") Signed-off-by: Even Xu Tested-by: Rui Zhang Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c index 7e220a4c5ded..d8e195189e4b 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c @@ -1597,6 +1597,7 @@ int thc_i2c_set_rx_max_size(struct thc_device *dev, u32 max_rx_size) if (ret) return ret; + val = val & ~THC_M_PRT_SPI_ICRRD_OPCODE_I2C_MAX_SIZE; val |= FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_I2C_MAX_SIZE, max_rx_size); ret = regmap_write(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, val); @@ -1667,6 +1668,7 @@ int thc_i2c_set_rx_int_delay(struct thc_device *dev, u32 delay_us) return ret; /* THC hardware counts at 10us unit */ + val = val & ~THC_M_PRT_SPI_ICRRD_OPCODE_I2C_INTERVAL; val |= FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_I2C_INTERVAL, DIV_ROUND_UP(delay_us, 10)); ret = regmap_write(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, val); From 8296796fb280dd1fb32e8e88b801ce2e6e581939 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:16:18 +0200 Subject: [PATCH 158/295] HID: sony: add dongle device IDs for CRKD Gibson SG JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit ae40ace015be442837934c5028a8eba7fe3cd98f Author: Rosalie Wanders Date: Fri Jan 30 07:57:51 2026 +0100 HID: sony: add dongle device IDs for CRKD Gibson SG This patch adds the dongle device IDs for the CRKD Gibson SG Signed-off-by: Rosalie Wanders Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 4 +++- drivers/hid/hid-sony.c | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index f105714098fd..3e299a30dcde 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -366,7 +366,9 @@ #define USB_VENDOR_ID_CRKD 0x3651 #define USB_DEVICE_ID_CRKD_PS4_GIBSON_SG 0x1500 -#define USB_DEVICE_ID_CRKD_PS5_GIBSON_SG 0x1600 +#define USB_DEVICE_ID_CRKD_PS4_GIBSON_SG_DONGLE 0x5500 +#define USB_DEVICE_ID_CRKD_PS5_GIBSON_SG 0x1600 +#define USB_DEVICE_ID_CRKD_PS5_GIBSON_SG_DONGLE 0x5600 #define USB_VENDOR_ID_CVTOUCH 0x1ff7 #define USB_DEVICE_ID_CVTOUCH_SCREEN 0x0013 diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index d74ee8e756e9..a89af14e4acc 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -2380,6 +2380,8 @@ static const struct hid_device_id sony_devices[] = { .driver_data = RB4_GUITAR_PS4_USB }, { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG), .driver_data = RB4_GUITAR_PS4_USB }, + { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG_DONGLE), + .driver_data = RB4_GUITAR_PS4_USB }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_JAGUAR), .driver_data = RB4_GUITAR_PS4_BT }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_PS4_STRATOCASTER), @@ -2389,6 +2391,8 @@ static const struct hid_device_id sony_devices[] = { .driver_data = RB4_GUITAR_PS5 }, { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS5_GIBSON_SG), .driver_data = RB4_GUITAR_PS5 }, + { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS5_GIBSON_SG_DONGLE), + .driver_data = RB4_GUITAR_PS5 }, { } }; MODULE_DEVICE_TABLE(hid, sony_devices); From 3677871ce96bc5d372fd0ca8a15ea5da971fdf67 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:35 +0200 Subject: [PATCH 159/295] HID: Document memory allocation properties of report_fixup() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 6b3e458806e34f1142592f786d3eb0ebac209cc2 Author: Günther Noack Date: Thu Feb 19 16:43:35 2026 +0100 HID: Document memory allocation properties of report_fixup() The memory pointer returned by the report_fixup() hook does not get freed by the caller. Instead, report_fixup() must return (in return value and *rsize) a memory buffer with at least the same lifetime as the input buffer (defined by rdesc and original *rsize). This is usually achieved using one of the following techniques: * Returning a pointer and size to a sub-portion of the input buffer * Returning a pointer to a static buffer * Allocating a buffer with a devm_*() function, which will automatically get freed when the device is removed. Signed-off-by: Günther Noack Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- include/linux/hid.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/hid.h b/include/linux/hid.h index dce862cafbbd..2990b9f94cb5 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -836,6 +836,12 @@ struct hid_usage_id { * raw_event and event should return negative on error, any other value will * pass the event on to .event() typically return 0 for success. * + * report_fixup must return a report descriptor pointer whose lifetime is at + * least that of the input rdesc. This is usually done by mutating the input + * rdesc and returning it or a sub-portion of it. In case a new buffer is + * allocated and returned, the implementation of report_fixup is responsible for + * freeing it later. + * * input_mapping shall return a negative value to completely ignore this usage * (e.g. doubled or invalid usage), zero to continue with parsing of this * usage by generic code (no special handling needed) or positive to skip From d04cd9719f0b1c8af04d91a1b7e546beb5fd37b9 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:36 +0200 Subject: [PATCH 160/295] HID: apple: avoid memory leak in apple_report_fixup() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-31520 commit 239c15116d80f67d32f00acc34575f1a6b699613 Author: Günther Noack Date: Thu Feb 19 16:43:36 2026 +0100 HID: apple: avoid memory leak in apple_report_fixup() The apple_report_fixup() function was returning a newly kmemdup()-allocated buffer, but never freeing it. The caller of report_fixup() does not take ownership of the returned pointer, but it *is* permitted to return a sub-portion of the input rdesc, whose lifetime is managed by the caller. Assisted-by: Gemini-CLI:Google Gemini 3 Signed-off-by: Günther Noack Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-apple.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 233e367cce1d..894adc23367b 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -686,9 +686,7 @@ static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc, hid_info(hdev, "fixing up Magic Keyboard battery report descriptor\n"); *rsize = *rsize - 1; - rdesc = kmemdup(rdesc + 1, *rsize, GFP_KERNEL); - if (!rdesc) - return NULL; + rdesc = rdesc + 1; rdesc[0] = 0x05; rdesc[1] = 0x01; From 7b82aa3ce5ccd0ef3c58a5277748a6b623dec483 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:37 +0200 Subject: [PATCH 161/295] HID: magicmouse: avoid memory leak in magicmouse_report_fixup() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-31522 commit 91e8c6e601bdc1ccdf886479b6513c01c7e51c2c Author: Günther Noack Date: Thu Feb 19 16:43:37 2026 +0100 HID: magicmouse: avoid memory leak in magicmouse_report_fixup() The magicmouse_report_fixup() function was returning a newly kmemdup()-allocated buffer, but never freeing it. The caller of report_fixup() does not take ownership of the returned pointer, but it *is* permitted to return a sub-portion of the input rdesc, whose lifetime is managed by the caller. Assisted-by: Gemini-CLI:Google Gemini 3 Signed-off-by: Günther Noack Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-magicmouse.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 91f621ceb924..17908d52c027 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -994,9 +994,7 @@ static const __u8 *magicmouse_report_fixup(struct hid_device *hdev, __u8 *rdesc, hid_info(hdev, "fixing up magicmouse battery report descriptor\n"); *rsize = *rsize - 1; - rdesc = kmemdup(rdesc + 1, *rsize, GFP_KERNEL); - if (!rdesc) - return NULL; + rdesc = rdesc + 1; rdesc[0] = 0x05; rdesc[1] = 0x01; From 562579920d0d0087a1a7a775aab4f1f24545829e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 09:39:09 +0200 Subject: [PATCH 162/295] HID: asus: avoid memory leak in asus_report_fixup() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-31524 commit 2bad24c17742fc88973d6aea526ce1353f5334a3 Author: Günther Noack Date: Thu Feb 19 16:43:38 2026 +0100 HID: asus: avoid memory leak in asus_report_fixup() The asus_report_fixup() function was returning a newly allocated kmemdup()-allocated buffer, but never freeing it. Switch to devm_kzalloc() to ensure the memory is managed and freed automatically when the device is removed. The caller of report_fixup() does not take ownership of the returned pointer, but it is permitted to return a pointer whose lifetime is at least that of the input buffer. Also fix a harmless out-of-bounds read by copying only the original descriptor size. Assisted-by: Gemini-CLI:Google Gemini 3 Signed-off-by: Günther Noack Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-asus.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 3a590c878b02..7b6dad25728d 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -1324,14 +1324,21 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc, */ if (*rsize == rsize_orig && rdesc[offs] == 0x09 && rdesc[offs + 1] == 0x76) { - *rsize = rsize_orig + 1; - rdesc = kmemdup(rdesc, *rsize, GFP_KERNEL); - if (!rdesc) - return NULL; + __u8 *new_rdesc; + + new_rdesc = devm_kzalloc(&hdev->dev, rsize_orig + 1, + GFP_KERNEL); + if (!new_rdesc) + return rdesc; hid_info(hdev, "Fixing up %s keyb report descriptor\n", drvdata->quirks & QUIRK_T100CHI ? "T100CHI" : "T90CHI"); + + memcpy(new_rdesc, rdesc, rsize_orig); + *rsize = rsize_orig + 1; + rdesc = new_rdesc; + memmove(rdesc + offs + 4, rdesc + offs + 2, 12); rdesc[offs] = 0x19; rdesc[offs + 1] = 0x00; From 987d75a5ae2c9dccf55e16f1cb96cd144fbb7dea Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:38 +0200 Subject: [PATCH 163/295] HID: Add HID_CLAIMED_INPUT guards in raw_event callbacks missing them JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-23382 commit ecfa6f34492c493a9a1dc2900f3edeb01c79946b Author: Greg Kroah-Hartman Date: Thu Feb 19 15:33:54 2026 +0100 HID: Add HID_CLAIMED_INPUT guards in raw_event callbacks missing them In commit 2ff5baa9b527 ("HID: appleir: Fix potential NULL dereference at raw event handle"), we handle the fact that raw event callbacks can happen even for a HID device that has not been "claimed" causing a crash if a broken device were attempted to be connected to the system. Fix up the remaining in-tree HID drivers that forgot to add this same check to resolve the same issue. Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: Bastien Nocera Cc: linux-input@vger.kernel.org Cc: stable Assisted-by: gkh_clanker_2000 Signed-off-by: Greg Kroah-Hartman Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-cmedia.c | 2 +- drivers/hid/hid-creative-sb0540.c | 2 +- drivers/hid/hid-zydacron.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-cmedia.c b/drivers/hid/hid-cmedia.c index 528d7f361215..8bf5649b0c79 100644 --- a/drivers/hid/hid-cmedia.c +++ b/drivers/hid/hid-cmedia.c @@ -99,7 +99,7 @@ static int cmhid_raw_event(struct hid_device *hid, struct hid_report *report, { struct cmhid *cm = hid_get_drvdata(hid); - if (len != CM6533_JD_RAWEV_LEN) + if (len != CM6533_JD_RAWEV_LEN || !(hid->claimed & HID_CLAIMED_INPUT)) goto out; if (memcmp(data+CM6533_JD_SFX_OFFSET, ji_sfx, sizeof(ji_sfx))) goto out; diff --git a/drivers/hid/hid-creative-sb0540.c b/drivers/hid/hid-creative-sb0540.c index b4c8e7a5d3e0..dfd6add353d1 100644 --- a/drivers/hid/hid-creative-sb0540.c +++ b/drivers/hid/hid-creative-sb0540.c @@ -153,7 +153,7 @@ static int creative_sb0540_raw_event(struct hid_device *hid, u64 code, main_code; int key; - if (len != 6) + if (len != 6 || !(hid->claimed & HID_CLAIMED_INPUT)) return 0; /* From daemons/hw_hiddev.c sb0540_rec() in lirc */ diff --git a/drivers/hid/hid-zydacron.c b/drivers/hid/hid-zydacron.c index 3bdb26f45592..1aae80f848f5 100644 --- a/drivers/hid/hid-zydacron.c +++ b/drivers/hid/hid-zydacron.c @@ -114,7 +114,7 @@ static int zc_raw_event(struct hid_device *hdev, struct hid_report *report, unsigned key; unsigned short index; - if (report->id == data[0]) { + if (report->id == data[0] && (hdev->claimed & HID_CLAIMED_INPUT)) { /* break keys */ for (index = 0; index < 4; index++) { From 6021059b203587ae4715c10637ce84e42e7bc8b8 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 11:31:50 +0200 Subject: [PATCH 164/295] treewide: Replace kmalloc with kmalloc_obj for non-scalar types JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 Partial backport: only drivers/hid/ All following omitted-fix are not in drivers/hid/ and can thus safely be ignored: Omitted-fix: fd1d6b9d13f35dccbacbae25ed53593cd9086f84 Omitted-fix: 96a7b71c4438d3b72d6c95e3efdc9e8e8aee6b78 Omitted-fix: 795469820c638b4449f3bb90ee5e98ebccfbc480 Omitted-fix: 5548dd7fa84510f7bbce67c35cc3b388c86aeddf Omitted-fix: 405ca72dc589dd746e5ee5378bb9d9ee7f844010 Omitted-fix: 4c0134639694fcdc4ab041d7c53d6188a3e18040 Omitted-fix: 4c6d43db2a4d2cef3921e885cf34798f790d34ea commit 69050f8d6d075dc01af7a5f2f550a8067510366f Author: Kees Cook Date: Fri Feb 20 23:49:23 2026 -0800 treewide: Replace kmalloc with kmalloc_obj for non-scalar types This is the result of running the Coccinelle script from scripts/coccinelle/api/kmalloc_objs.cocci. The script is designed to avoid scalar types (which need careful case-by-case checking), and instead replace kmalloc-family calls that allocate struct or union object instances: Single allocations: kmalloc(sizeof(TYPE), ...) are replaced with: kmalloc_obj(TYPE, ...) Array allocations: kmalloc_array(COUNT, sizeof(TYPE), ...) are replaced with: kmalloc_objs(TYPE, COUNT, ...) Flex array allocations: kmalloc(struct_size(PTR, FAM, COUNT), ...) are replaced with: kmalloc_flex(*PTR, FAM, COUNT, ...) (where TYPE may also be *VAR) The resulting allocations no longer return "void *", instead returning "TYPE *". Signed-off-by: Kees Cook Signed-off-by: Benjamin Tissoires --- drivers/hid/amd-sfh-hid/amd_sfh_client.c | 2 +- drivers/hid/amd-sfh-hid/amd_sfh_hid.c | 2 +- drivers/hid/bpf/hid_bpf_dispatch.c | 2 +- drivers/hid/hid-apple.c | 2 +- drivers/hid/hid-axff.c | 2 +- drivers/hid/hid-betopff.c | 2 +- drivers/hid/hid-cmedia.c | 2 +- drivers/hid/hid-core.c | 13 +++++++------ drivers/hid/hid-corsair.c | 4 ++-- drivers/hid/hid-cougar.c | 2 +- drivers/hid/hid-debug.c | 2 +- drivers/hid/hid-dr.c | 2 +- drivers/hid/hid-elo.c | 2 +- drivers/hid/hid-emsff.c | 2 +- drivers/hid/hid-gaff.c | 2 +- drivers/hid/hid-google-hammer.c | 4 ++-- drivers/hid/hid-haptic.c | 4 ++-- drivers/hid/hid-holtekff.c | 2 +- drivers/hid/hid-hyperv.c | 2 +- drivers/hid/hid-input.c | 4 ++-- drivers/hid/hid-lg.c | 2 +- drivers/hid/hid-lg2ff.c | 2 +- drivers/hid/hid-lg4ff.c | 2 +- drivers/hid/hid-logitech-dj.c | 8 ++++---- drivers/hid/hid-logitech-hidpp.c | 8 ++++---- drivers/hid/hid-megaworld.c | 2 +- drivers/hid/hid-mf.c | 2 +- drivers/hid/hid-ntrig.c | 2 +- drivers/hid/hid-picolcd_core.c | 4 ++-- drivers/hid/hid-pl.c | 2 +- drivers/hid/hid-playstation.c | 2 +- drivers/hid/hid-prodikeys.c | 2 +- drivers/hid/hid-quirks.c | 4 ++-- drivers/hid/hid-roccat-arvo.c | 2 +- drivers/hid/hid-roccat-isku.c | 2 +- drivers/hid/hid-roccat-kone.c | 2 +- drivers/hid/hid-roccat-koneplus.c | 2 +- drivers/hid/hid-roccat-konepure.c | 2 +- drivers/hid/hid-roccat-kovaplus.c | 2 +- drivers/hid/hid-roccat-lua.c | 2 +- drivers/hid/hid-roccat-pyra.c | 2 +- drivers/hid/hid-roccat-ryos.c | 2 +- drivers/hid/hid-roccat-savu.c | 2 +- drivers/hid/hid-roccat.c | 4 ++-- drivers/hid/hid-sensor-custom.c | 2 +- drivers/hid/hid-sensor-hub.c | 2 +- drivers/hid/hid-sjoy.c | 2 +- drivers/hid/hid-thrustmaster.c | 4 ++-- drivers/hid/hid-tmff.c | 2 +- drivers/hid/hid-uclogic-params.c | 4 ++-- drivers/hid/hid-wiimote-core.c | 2 +- drivers/hid/hid-wiimote-debug.c | 2 +- drivers/hid/hid-zpff.c | 2 +- drivers/hid/hidraw.c | 4 ++-- drivers/hid/intel-ish-hid/ishtp-hid.c | 2 +- drivers/hid/intel-ish-hid/ishtp/bus.c | 2 +- drivers/hid/intel-ish-hid/ishtp/client-buffers.c | 4 ++-- drivers/hid/intel-ish-hid/ishtp/client.c | 2 +- drivers/hid/intel-ish-hid/ishtp/hbm.c | 4 ++-- drivers/hid/uhid.c | 14 +++++++------- drivers/hid/usbhid/hid-core.c | 4 ++-- drivers/hid/usbhid/hid-pidff.c | 2 +- drivers/hid/usbhid/hiddev.c | 4 ++-- drivers/hid/usbhid/usbkbd.c | 4 ++-- drivers/hid/usbhid/usbmouse.c | 2 +- drivers/hid/wacom_sys.c | 2 +- 66 files changed, 99 insertions(+), 98 deletions(-) diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_client.c b/drivers/hid/amd-sfh-hid/amd_sfh_client.c index 7017bfa59093..8b479441bf90 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_client.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_client.c @@ -47,7 +47,7 @@ int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type) guard(mutex)(&mp2->lock); for (i = 0; i < cli_data->num_hid_devices; i++) { if (cli_data->hid_sensor_hubs[i] == hid) { - struct request_list *new = kzalloc(sizeof(*new), GFP_KERNEL); + struct request_list *new = kzalloc_obj(*new, GFP_KERNEL); if (!new) return -ENOMEM; diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c index 81f3024b7b1b..59bc9a2e0cd7 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c @@ -135,7 +135,7 @@ int amdtp_hid_probe(u32 cur_hid_dev, struct amdtp_cl_data *cli_data) if (IS_ERR(hid)) return PTR_ERR(hid); - hid_data = kzalloc(sizeof(*hid_data), GFP_KERNEL); + hid_data = kzalloc_obj(*hid_data, GFP_KERNEL); if (!hid_data) { rc = -ENOMEM; goto err_hid_data; diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index 892aca026ffa..c99d81789eb3 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -320,7 +320,7 @@ hid_bpf_allocate_context(unsigned int hid_id) if (IS_ERR(hdev)) return NULL; - ctx_kern = kzalloc(sizeof(*ctx_kern), GFP_KERNEL); + ctx_kern = kzalloc_obj(*ctx_kern, GFP_KERNEL); if (!ctx_kern) { hid_put_device(hdev); return NULL; diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 894adc23367b..cf48186ff1ff 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -792,7 +792,7 @@ static int apple_backlight_set(struct hid_device *hdev, u16 value, u16 rate) int ret = 0; struct apple_backlight_set_report *rep; - rep = kmalloc(sizeof(*rep), GFP_KERNEL); + rep = kmalloc_obj(*rep, GFP_KERNEL); if (rep == NULL) return -ENOMEM; diff --git a/drivers/hid/hid-axff.c b/drivers/hid/hid-axff.c index fbe4e16ab029..c7dcdffff9e6 100644 --- a/drivers/hid/hid-axff.c +++ b/drivers/hid/hid-axff.c @@ -96,7 +96,7 @@ static int axff_init(struct hid_device *hid) return -ENODEV; } - axff = kzalloc(sizeof(struct axff_device), GFP_KERNEL); + axff = kzalloc_obj(struct axff_device, GFP_KERNEL); if (!axff) return -ENOMEM; diff --git a/drivers/hid/hid-betopff.c b/drivers/hid/hid-betopff.c index a6d5f030d023..d739b3746138 100644 --- a/drivers/hid/hid-betopff.c +++ b/drivers/hid/hid-betopff.c @@ -100,7 +100,7 @@ static int betopff_init(struct hid_device *hid) } } - betopff = kzalloc(sizeof(*betopff), GFP_KERNEL); + betopff = kzalloc_obj(*betopff, GFP_KERNEL); if (!betopff) return -ENOMEM; diff --git a/drivers/hid/hid-cmedia.c b/drivers/hid/hid-cmedia.c index 8bf5649b0c79..b35292c0335e 100644 --- a/drivers/hid/hid-cmedia.c +++ b/drivers/hid/hid-cmedia.c @@ -145,7 +145,7 @@ static int cmhid_probe(struct hid_device *hid, const struct hid_device_id *id) int ret; struct cmhid *cm; - cm = kzalloc(sizeof(struct cmhid), GFP_KERNEL); + cm = kzalloc_obj(struct cmhid, GFP_KERNEL); if (!cm) { ret = -ENOMEM; goto allocfail; diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index d8fe9e6941b4..88569d454ee3 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -93,7 +93,7 @@ struct hid_report *hid_register_report(struct hid_device *device, if (report_enum->report_id_hash[id]) return report_enum->report_id_hash[id]; - report = kzalloc(sizeof(struct hid_report), GFP_KERNEL); + report = kzalloc_obj(struct hid_report, GFP_KERNEL); if (!report) return NULL; @@ -1319,8 +1319,9 @@ int hid_open_report(struct hid_device *device) end = start + size; - device->collection = kcalloc(HID_DEFAULT_NUM_COLLECTIONS, - sizeof(struct hid_collection), GFP_KERNEL); + device->collection = kzalloc_objs(struct hid_collection, + HID_DEFAULT_NUM_COLLECTIONS, + GFP_KERNEL); if (!device->collection) { ret = -ENOMEM; goto err; @@ -1796,7 +1797,7 @@ static void hid_report_process_ordering(struct hid_device *hid, } /* allocate the memory to process the fields */ - entries = kcalloc(count, sizeof(*entries), GFP_KERNEL); + entries = kzalloc_objs(*entries, count, GFP_KERNEL); if (!entries) return; @@ -2610,7 +2611,7 @@ static ssize_t new_id_store(struct device_driver *drv, const char *buf, if (ret < 3) return -EINVAL; - dynid = kzalloc(sizeof(*dynid), GFP_KERNEL); + dynid = kzalloc_obj(*dynid, GFP_KERNEL); if (!dynid) return -ENOMEM; @@ -2972,7 +2973,7 @@ struct hid_device *hid_allocate_device(void) struct hid_device *hdev; int ret = -ENOMEM; - hdev = kzalloc(sizeof(*hdev), GFP_KERNEL); + hdev = kzalloc_obj(*hdev, GFP_KERNEL); if (hdev == NULL) return ERR_PTR(ret); diff --git a/drivers/hid/hid-corsair.c b/drivers/hid/hid-corsair.c index 62b99f5c3cf8..750cac0a2a69 100644 --- a/drivers/hid/hid-corsair.c +++ b/drivers/hid/hid-corsair.c @@ -427,7 +427,7 @@ static int k90_init_backlight(struct hid_device *dev) size_t name_sz; char *name; - drvdata->backlight = kzalloc(sizeof(struct k90_led), GFP_KERNEL); + drvdata->backlight = kzalloc_obj(struct k90_led, GFP_KERNEL); if (!drvdata->backlight) { ret = -ENOMEM; goto fail_backlight_alloc; @@ -471,7 +471,7 @@ static int k90_init_macro_functions(struct hid_device *dev) size_t name_sz; char *name; - k90 = kzalloc(sizeof(struct k90_drvdata), GFP_KERNEL); + k90 = kzalloc_obj(struct k90_drvdata, GFP_KERNEL); if (!k90) { ret = -ENOMEM; goto fail_drvdata; diff --git a/drivers/hid/hid-cougar.c b/drivers/hid/hid-cougar.c index 5596dd940322..0b66a7c61ac6 100644 --- a/drivers/hid/hid-cougar.c +++ b/drivers/hid/hid-cougar.c @@ -166,7 +166,7 @@ static int cougar_bind_shared_data(struct hid_device *hdev, shared = cougar_get_shared_data(hdev); if (!shared) { - shared = kzalloc(sizeof(*shared), GFP_KERNEL); + shared = kzalloc_obj(*shared, GFP_KERNEL); if (!shared) { error = -ENOMEM; goto out; diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index 337d2dc81b4c..5430282b3089 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -3680,7 +3680,7 @@ static int hid_debug_events_open(struct inode *inode, struct file *file) struct hid_debug_list *list; unsigned long flags; - if (!(list = kzalloc(sizeof(struct hid_debug_list), GFP_KERNEL))) { + if (!(list = kzalloc_obj(struct hid_debug_list, GFP_KERNEL))) { err = -ENOMEM; goto out; } diff --git a/drivers/hid/hid-dr.c b/drivers/hid/hid-dr.c index 84e1e90a266b..9923b222383a 100644 --- a/drivers/hid/hid-dr.c +++ b/drivers/hid/hid-dr.c @@ -104,7 +104,7 @@ static int drff_init(struct hid_device *hid) return -ENODEV; } - drff = kzalloc(sizeof(struct drff_device), GFP_KERNEL); + drff = kzalloc_obj(struct drff_device, GFP_KERNEL); if (!drff) return -ENOMEM; diff --git a/drivers/hid/hid-elo.c b/drivers/hid/hid-elo.c index cf17bdd14d9c..465f04ed1a55 100644 --- a/drivers/hid/hid-elo.c +++ b/drivers/hid/hid-elo.c @@ -232,7 +232,7 @@ static int elo_probe(struct hid_device *hdev, const struct hid_device_id *id) if (!hid_is_usb(hdev)) return -EINVAL; - priv = kzalloc(sizeof(*priv), GFP_KERNEL); + priv = kzalloc_obj(*priv, GFP_KERNEL); if (!priv) return -ENOMEM; diff --git a/drivers/hid/hid-emsff.c b/drivers/hid/hid-emsff.c index 60bfb6a924d7..0dbe598f2e48 100644 --- a/drivers/hid/hid-emsff.c +++ b/drivers/hid/hid-emsff.c @@ -76,7 +76,7 @@ static int emsff_init(struct hid_device *hid) return -ENODEV; } - emsff = kzalloc(sizeof(struct emsff_device), GFP_KERNEL); + emsff = kzalloc_obj(struct emsff_device, GFP_KERNEL); if (!emsff) return -ENOMEM; diff --git a/drivers/hid/hid-gaff.c b/drivers/hid/hid-gaff.c index c6db8b6cc8ee..a9d305a193d2 100644 --- a/drivers/hid/hid-gaff.c +++ b/drivers/hid/hid-gaff.c @@ -96,7 +96,7 @@ static int gaff_init(struct hid_device *hid) return -ENODEV; } - gaff = kzalloc(sizeof(struct gaff_device), GFP_KERNEL); + gaff = kzalloc_obj(struct gaff_device, GFP_KERNEL); if (!gaff) return -ENOMEM; diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index fdb839c16987..c05ec4b5fd0c 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -59,8 +59,8 @@ static int cbas_ec_query_base(struct cros_ec_device *ec_dev, bool get_state, struct cros_ec_command *msg; int ret; - msg = kzalloc(struct_size(msg, data, max(sizeof(u32), sizeof(*params))), - GFP_KERNEL); + msg = kzalloc_flex(*msg, data, max(sizeof(u32), sizeof(*params)), + GFP_KERNEL); if (!msg) return -ENOMEM; diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c index aa090684c1f2..03b85df28aa1 100644 --- a/drivers/hid/hid-haptic.c +++ b/drivers/hid/hid-haptic.c @@ -474,8 +474,8 @@ int hid_haptic_init(struct hid_device *hdev, ret = -ENOMEM; goto duration_map; } - haptic->effect = kcalloc(FF_MAX_EFFECTS, - sizeof(struct hid_haptic_effect), GFP_KERNEL); + haptic->effect = kzalloc_objs(struct hid_haptic_effect, FF_MAX_EFFECTS, + GFP_KERNEL); if (!haptic->effect) { ret = -ENOMEM; goto output_queue; diff --git a/drivers/hid/hid-holtekff.c b/drivers/hid/hid-holtekff.c index 8619b80c834c..56980af08683 100644 --- a/drivers/hid/hid-holtekff.c +++ b/drivers/hid/hid-holtekff.c @@ -149,7 +149,7 @@ static int holtekff_init(struct hid_device *hid) return -ENODEV; } - holtekff = kzalloc(sizeof(*holtekff), GFP_KERNEL); + holtekff = kzalloc_obj(*holtekff, GFP_KERNEL); if (!holtekff) return -ENOMEM; diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c index 9eafff0b6ea4..f092547d08d2 100644 --- a/drivers/hid/hid-hyperv.c +++ b/drivers/hid/hid-hyperv.c @@ -149,7 +149,7 @@ static struct mousevsc_dev *mousevsc_alloc_device(struct hv_device *device) { struct mousevsc_dev *input_dev; - input_dev = kzalloc(sizeof(struct mousevsc_dev), GFP_KERNEL); + input_dev = kzalloc_obj(struct mousevsc_dev, GFP_KERNEL); if (!input_dev) return NULL; diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 2633fcd8f910..6137cc0aeeaf 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -531,7 +531,7 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, if (quirks & HID_BATTERY_QUIRK_IGNORE) return 0; - psy_desc = kzalloc(sizeof(*psy_desc), GFP_KERNEL); + psy_desc = kzalloc_obj(*psy_desc, GFP_KERNEL); if (!psy_desc) return -ENOMEM; @@ -2023,7 +2023,7 @@ static void report_features(struct hid_device *hid) static struct hid_input *hidinput_allocate(struct hid_device *hid, unsigned int application) { - struct hid_input *hidinput = kzalloc(sizeof(*hidinput), GFP_KERNEL); + struct hid_input *hidinput = kzalloc_obj(*hidinput, GFP_KERNEL); struct input_dev *input_dev = input_allocate_device(); const char *suffix = NULL; size_t suffix_len, name_len; diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index 9a2cfa018bd3..7cdf1e895b69 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -768,7 +768,7 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id) return -ENODEV; } - drv_data = kzalloc(sizeof(struct lg_drv_data), GFP_KERNEL); + drv_data = kzalloc_obj(struct lg_drv_data, GFP_KERNEL); if (!drv_data) { hid_err(hdev, "Insufficient memory, cannot allocate driver data\n"); return -ENOMEM; diff --git a/drivers/hid/hid-lg2ff.c b/drivers/hid/hid-lg2ff.c index 73d07e35f12a..e4a034bd4095 100644 --- a/drivers/hid/hid-lg2ff.c +++ b/drivers/hid/hid-lg2ff.c @@ -66,7 +66,7 @@ int lg2ff_init(struct hid_device *hid) if (!report) return -ENODEV; - lg2ff = kmalloc(sizeof(struct lg2ff_device), GFP_KERNEL); + lg2ff = kmalloc_obj(struct lg2ff_device, GFP_KERNEL); if (!lg2ff) return -ENOMEM; diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index 32b711723f2a..4979f7779de8 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -1288,7 +1288,7 @@ int lg4ff_init(struct hid_device *hid) hid_err(hid, "Cannot add device, private driver data not allocated\n"); return -1; } - entry = kzalloc(sizeof(*entry), GFP_KERNEL); + entry = kzalloc_obj(*entry, GFP_KERNEL); if (!entry) return -ENOMEM; spin_lock_init(&entry->report_lock); diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index c41fded63f4b..2fb6a67bede2 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -733,7 +733,7 @@ static struct dj_receiver_dev *dj_get_receiver_dev(struct hid_device *hdev, djrcv_dev = dj_find_receiver_dev(hdev, type); if (!djrcv_dev) { - djrcv_dev = kzalloc(sizeof(*djrcv_dev), GFP_KERNEL); + djrcv_dev = kzalloc_obj(*djrcv_dev, GFP_KERNEL); if (!djrcv_dev) goto out; @@ -851,7 +851,7 @@ static void logi_dj_recv_add_djhid_device(struct dj_receiver_dev *djrcv_dev, snprintf(tmpstr, sizeof(tmpstr), ":%d", device_index); strlcat(dj_hiddev->phys, tmpstr, sizeof(dj_hiddev->phys)); - dj_dev = kzalloc(sizeof(struct dj_device), GFP_KERNEL); + dj_dev = kzalloc_obj(struct dj_device, GFP_KERNEL); if (!dj_dev) { hid_err(djrcv_hdev, "%s: failed allocating dj_dev\n", __func__); @@ -1332,7 +1332,7 @@ static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev) goto out; } - dj_report = kzalloc(sizeof(struct dj_report), GFP_KERNEL); + dj_report = kzalloc_obj(struct dj_report, GFP_KERNEL); if (!dj_report) return -ENOMEM; dj_report->report_id = REPORT_ID_DJ_SHORT; @@ -1356,7 +1356,7 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, u8 *buf; int retval = 0; - dj_report = kzalloc(sizeof(struct dj_report), GFP_KERNEL); + dj_report = kzalloc_obj(struct dj_report, GFP_KERNEL); if (!dj_report) return -ENOMEM; diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 02d83c3bd73d..4f3289b8a7c2 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -390,7 +390,7 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, return -EINVAL; } - message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL); + message = kzalloc_obj(struct hidpp_report, GFP_KERNEL); if (!message) return -ENOMEM; @@ -443,7 +443,7 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, if (param_count > max_count) return -EINVAL; - message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL); + message = kzalloc_obj(struct hidpp_report, GFP_KERNEL); if (!message) return -ENOMEM; message->report_id = report_id; @@ -2527,7 +2527,7 @@ out: static int hidpp_ff_queue_work(struct hidpp_ff_private_data *data, int effect_id, u8 command, u8 *params, u8 size) { - struct hidpp_ff_work_data *wd = kzalloc(sizeof(*wd), GFP_KERNEL); + struct hidpp_ff_work_data *wd = kzalloc_obj(*wd, GFP_KERNEL); int s; if (!wd) @@ -2853,7 +2853,7 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, data = kmemdup(data, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - data->effect_ids = kcalloc(num_slots, sizeof(int), GFP_KERNEL); + data->effect_ids = kzalloc_objs(int, num_slots, GFP_KERNEL); if (!data->effect_ids) { kfree(data); return -ENOMEM; diff --git a/drivers/hid/hid-megaworld.c b/drivers/hid/hid-megaworld.c index 0476d7d16e7f..62f3e976f6b3 100644 --- a/drivers/hid/hid-megaworld.c +++ b/drivers/hid/hid-megaworld.c @@ -57,7 +57,7 @@ static int mwctrl_init(struct hid_device *hid) return -ENODEV; } - mwctrl = kzalloc(sizeof(struct mwctrl_device), GFP_KERNEL); + mwctrl = kzalloc_obj(struct mwctrl_device, GFP_KERNEL); if (!mwctrl) return -ENOMEM; diff --git a/drivers/hid/hid-mf.c b/drivers/hid/hid-mf.c index 49a4052a1496..9a34645e92b6 100644 --- a/drivers/hid/hid-mf.c +++ b/drivers/hid/hid-mf.c @@ -88,7 +88,7 @@ static int mf_init(struct hid_device *hid) input_ptr = input_ptr->next; input = list_entry(input_ptr, struct hid_input, list); - mf = kzalloc(sizeof(struct mf_device), GFP_KERNEL); + mf = kzalloc_obj(struct mf_device, GFP_KERNEL); if (!mf) return -ENOMEM; diff --git a/drivers/hid/hid-ntrig.c b/drivers/hid/hid-ntrig.c index a7f10c45f62b..f48dc2f34f79 100644 --- a/drivers/hid/hid-ntrig.c +++ b/drivers/hid/hid-ntrig.c @@ -900,7 +900,7 @@ static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id) hdev->quirks |= HID_QUIRK_MULTI_INPUT | HID_QUIRK_NO_INIT_REPORTS; - nd = kmalloc(sizeof(struct ntrig_data), GFP_KERNEL); + nd = kmalloc_obj(struct ntrig_data, GFP_KERNEL); if (!nd) { hid_err(hdev, "cannot allocate N-Trig data\n"); return -ENOMEM; diff --git a/drivers/hid/hid-picolcd_core.c b/drivers/hid/hid-picolcd_core.c index 6a88e6bc70f3..99cae54dc769 100644 --- a/drivers/hid/hid-picolcd_core.c +++ b/drivers/hid/hid-picolcd_core.c @@ -78,7 +78,7 @@ struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev, return NULL; if (data->status & PICOLCD_FAILED) return NULL; - work = kzalloc(sizeof(*work), GFP_KERNEL); + work = kzalloc_obj(*work, GFP_KERNEL); if (!work) return NULL; @@ -528,7 +528,7 @@ static int picolcd_probe(struct hid_device *hdev, * Let's allocate the picolcd data structure, set some reasonable * defaults, and associate it with the device */ - data = kzalloc(sizeof(struct picolcd_data), GFP_KERNEL); + data = kzalloc_obj(struct picolcd_data, GFP_KERNEL); if (data == NULL) { hid_err(hdev, "can't allocate space for Minibox PicoLCD device data\n"); return -ENOMEM; diff --git a/drivers/hid/hid-pl.c b/drivers/hid/hid-pl.c index dc11d5322fc0..967f93d9b46b 100644 --- a/drivers/hid/hid-pl.c +++ b/drivers/hid/hid-pl.c @@ -140,7 +140,7 @@ static int plff_init(struct hid_device *hid) return -ENODEV; } - plff = kzalloc(sizeof(struct plff_device), GFP_KERNEL); + plff = kzalloc_obj(struct plff_device, GFP_KERNEL); if (!plff) return -ENOMEM; diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 2ec6d4445e84..2458c6b61358 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -1658,7 +1658,7 @@ static int dualsense_reset_leds(struct dualsense *ds) struct dualsense_output_report report; struct dualsense_output_report_bt *buf; - buf = kzalloc(sizeof(*buf), GFP_KERNEL); + buf = kzalloc_obj(*buf, GFP_KERNEL); if (!buf) return -ENOMEM; diff --git a/drivers/hid/hid-prodikeys.c b/drivers/hid/hid-prodikeys.c index 6e413df38358..e69b3cb682bc 100644 --- a/drivers/hid/hid-prodikeys.c +++ b/drivers/hid/hid-prodikeys.c @@ -797,7 +797,7 @@ static int pk_probe(struct hid_device *hdev, const struct hid_device_id *id) intf = to_usb_interface(hdev->dev.parent); ifnum = intf->cur_altsetting->desc.bInterfaceNumber; - pm = kzalloc(sizeof(*pm), GFP_KERNEL); + pm = kzalloc_obj(*pm, GFP_KERNEL); if (pm == NULL) { hid_err(hdev, "can't alloc descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 3217e436c052..b6a9cfb71ec9 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -1164,11 +1164,11 @@ static int hid_modify_dquirk(const struct hid_device_id *id, int list_edited = 0; int ret = 0; - hdev = kzalloc(sizeof(*hdev), GFP_KERNEL); + hdev = kzalloc_obj(*hdev, GFP_KERNEL); if (!hdev) return -ENOMEM; - q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL); + q_new = kmalloc_obj(struct quirks_list_struct, GFP_KERNEL); if (!q_new) { ret = -ENOMEM; goto out; diff --git a/drivers/hid/hid-roccat-arvo.c b/drivers/hid/hid-roccat-arvo.c index 3048297569c5..edfa448a12d7 100644 --- a/drivers/hid/hid-roccat-arvo.c +++ b/drivers/hid/hid-roccat-arvo.c @@ -299,7 +299,7 @@ static int arvo_init_specials(struct hid_device *hdev) return 0; } - arvo = kzalloc(sizeof(*arvo), GFP_KERNEL); + arvo = kzalloc_obj(*arvo, GFP_KERNEL); if (!arvo) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-isku.c b/drivers/hid/hid-roccat-isku.c index 9fddc42f241b..0d72195a8816 100644 --- a/drivers/hid/hid-roccat-isku.c +++ b/drivers/hid/hid-roccat-isku.c @@ -279,7 +279,7 @@ static int isku_init_specials(struct hid_device *hdev) return 0; } - isku = kzalloc(sizeof(*isku), GFP_KERNEL); + isku = kzalloc_obj(*isku, GFP_KERNEL); if (!isku) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index af8ef42aca02..d1d319339803 100644 --- a/drivers/hid/hid-roccat-kone.c +++ b/drivers/hid/hid-roccat-kone.c @@ -704,7 +704,7 @@ static int kone_init_specials(struct hid_device *hdev) if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { - kone = kzalloc(sizeof(*kone), GFP_KERNEL); + kone = kzalloc_obj(*kone, GFP_KERNEL); if (!kone) return -ENOMEM; hid_set_drvdata(hdev, kone); diff --git a/drivers/hid/hid-roccat-koneplus.c b/drivers/hid/hid-roccat-koneplus.c index c3f01f7b7e48..e859618763c7 100644 --- a/drivers/hid/hid-roccat-koneplus.c +++ b/drivers/hid/hid-roccat-koneplus.c @@ -384,7 +384,7 @@ static int koneplus_init_specials(struct hid_device *hdev) if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { - koneplus = kzalloc(sizeof(*koneplus), GFP_KERNEL); + koneplus = kzalloc_obj(*koneplus, GFP_KERNEL); if (!koneplus) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-konepure.c b/drivers/hid/hid-roccat-konepure.c index 7fb705789d4e..34e4cb53b779 100644 --- a/drivers/hid/hid-roccat-konepure.c +++ b/drivers/hid/hid-roccat-konepure.c @@ -88,7 +88,7 @@ static int konepure_init_specials(struct hid_device *hdev) return 0; } - konepure = kzalloc(sizeof(*konepure), GFP_KERNEL); + konepure = kzalloc_obj(*konepure, GFP_KERNEL); if (!konepure) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-kovaplus.c b/drivers/hid/hid-roccat-kovaplus.c index 7d625ed53c9f..922c01dcab2a 100644 --- a/drivers/hid/hid-roccat-kovaplus.c +++ b/drivers/hid/hid-roccat-kovaplus.c @@ -453,7 +453,7 @@ static int kovaplus_init_specials(struct hid_device *hdev) if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { - kovaplus = kzalloc(sizeof(*kovaplus), GFP_KERNEL); + kovaplus = kzalloc_obj(*kovaplus, GFP_KERNEL); if (!kovaplus) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-lua.c b/drivers/hid/hid-roccat-lua.c index 45e30549c236..a7ed873f8761 100644 --- a/drivers/hid/hid-roccat-lua.c +++ b/drivers/hid/hid-roccat-lua.c @@ -119,7 +119,7 @@ static int lua_init_specials(struct hid_device *hdev) struct lua_device *lua; int retval; - lua = kzalloc(sizeof(*lua), GFP_KERNEL); + lua = kzalloc_obj(*lua, GFP_KERNEL); if (!lua) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c index dbb905585369..91c5c62f4ce8 100644 --- a/drivers/hid/hid-roccat-pyra.c +++ b/drivers/hid/hid-roccat-pyra.c @@ -403,7 +403,7 @@ static int pyra_init_specials(struct hid_device *hdev) if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { - pyra = kzalloc(sizeof(*pyra), GFP_KERNEL); + pyra = kzalloc_obj(*pyra, GFP_KERNEL); if (!pyra) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-ryos.c b/drivers/hid/hid-roccat-ryos.c index 902dac1e714e..f3ea2b5ac840 100644 --- a/drivers/hid/hid-roccat-ryos.c +++ b/drivers/hid/hid-roccat-ryos.c @@ -96,7 +96,7 @@ static int ryos_init_specials(struct hid_device *hdev) return 0; } - ryos = kzalloc(sizeof(*ryos), GFP_KERNEL); + ryos = kzalloc_obj(*ryos, GFP_KERNEL); if (!ryos) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-savu.c b/drivers/hid/hid-roccat-savu.c index 7399b8ffb5c7..262450374f8a 100644 --- a/drivers/hid/hid-roccat-savu.c +++ b/drivers/hid/hid-roccat-savu.c @@ -68,7 +68,7 @@ static int savu_init_specials(struct hid_device *hdev) return 0; } - savu = kzalloc(sizeof(*savu), GFP_KERNEL); + savu = kzalloc_obj(*savu, GFP_KERNEL); if (!savu) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c index c7f7562e22e5..5fe16ec1849a 100644 --- a/drivers/hid/hid-roccat.c +++ b/drivers/hid/hid-roccat.c @@ -152,7 +152,7 @@ static int roccat_open(struct inode *inode, struct file *file) struct roccat_device *device; int error = 0; - reader = kzalloc(sizeof(struct roccat_reader), GFP_KERNEL); + reader = kzalloc_obj(struct roccat_reader, GFP_KERNEL); if (!reader) return -ENOMEM; @@ -301,7 +301,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report struct roccat_device *device; int temp; - device = kzalloc(sizeof(struct roccat_device), GFP_KERNEL); + device = kzalloc_obj(struct roccat_device, GFP_KERNEL); if (!device) return -ENOMEM; diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c index 617ae240396d..7632288181f0 100644 --- a/drivers/hid/hid-sensor-custom.c +++ b/drivers/hid/hid-sensor-custom.c @@ -912,7 +912,7 @@ hid_sensor_custom_get_known(struct hid_sensor_hub_device *hsdev, hid_sensor_custom_known_table; struct hid_sensor_custom_properties *prop; - prop = kmalloc(sizeof(struct hid_sensor_custom_properties), GFP_KERNEL); + prop = kmalloc_obj(struct hid_sensor_custom_properties, GFP_KERNEL); if (!prop) return -ENOMEM; diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 0edec902be41..90666ff629de 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -139,7 +139,7 @@ int sensor_hub_register_callback(struct hid_sensor_hub_device *hsdev, spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags); return -EINVAL; } - callback = kzalloc(sizeof(*callback), GFP_ATOMIC); + callback = kzalloc_obj(*callback, GFP_ATOMIC); if (!callback) { spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags); return -ENOMEM; diff --git a/drivers/hid/hid-sjoy.c b/drivers/hid/hid-sjoy.c index d3a777f52a3f..e567c264aed3 100644 --- a/drivers/hid/hid-sjoy.c +++ b/drivers/hid/hid-sjoy.c @@ -83,7 +83,7 @@ static int sjoyff_init(struct hid_device *hid) return -ENODEV; } - sjoyff = kzalloc(sizeof(struct sjoyff_device), GFP_KERNEL); + sjoyff = kzalloc_obj(struct sjoyff_device, GFP_KERNEL); if (!sjoyff) return -ENOMEM; diff --git a/drivers/hid/hid-thrustmaster.c b/drivers/hid/hid-thrustmaster.c index 0bf70664c35e..15847eef4ac0 100644 --- a/drivers/hid/hid-thrustmaster.c +++ b/drivers/hid/hid-thrustmaster.c @@ -308,7 +308,7 @@ static int thrustmaster_probe(struct hid_device *hdev, const struct hid_device_i } // Now we allocate the tm_wheel - tm_wheel = kzalloc(sizeof(struct tm_wheel), GFP_KERNEL); + tm_wheel = kzalloc_obj(struct tm_wheel, GFP_KERNEL); if (!tm_wheel) { ret = -ENOMEM; goto error1; @@ -328,7 +328,7 @@ static int thrustmaster_probe(struct hid_device *hdev, const struct hid_device_i goto error3; } - tm_wheel->response = kzalloc(sizeof(struct tm_wheel_response), GFP_KERNEL); + tm_wheel->response = kzalloc_obj(struct tm_wheel_response, GFP_KERNEL); if (!tm_wheel->response) { ret = -ENOMEM; goto error4; diff --git a/drivers/hid/hid-tmff.c b/drivers/hid/hid-tmff.c index fcd859aa3a8c..ddb2629ed9e1 100644 --- a/drivers/hid/hid-tmff.c +++ b/drivers/hid/hid-tmff.c @@ -132,7 +132,7 @@ static int tmff_init(struct hid_device *hid, const signed short *ff_bits) hidinput = list_entry(hid->inputs.next, struct hid_input, list); input_dev = hidinput->input; - tmff = kzalloc(sizeof(struct tmff_device), GFP_KERNEL); + tmff = kzalloc_obj(struct tmff_device, GFP_KERNEL); if (!tmff) return -ENOMEM; diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index e28176d9d9c9..21b1d865bd0d 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -1358,13 +1358,13 @@ static int uclogic_params_ugee_v2_init_event_hooks(struct hid_device *hdev, if (!uclogic_params_ugee_v2_has_battery(hdev)) return 0; - p->event_hooks = kzalloc(sizeof(*p->event_hooks), GFP_KERNEL); + p->event_hooks = kzalloc_obj(*p->event_hooks, GFP_KERNEL); if (!p->event_hooks) return -ENOMEM; INIT_LIST_HEAD(&p->event_hooks->list); - event_hook = kzalloc(sizeof(*event_hook), GFP_KERNEL); + event_hook = kzalloc_obj(*event_hook, GFP_KERNEL); if (!event_hook) return -ENOMEM; diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c index 5b5fc460a4c5..c24ccfc7c05a 100644 --- a/drivers/hid/hid-wiimote-core.c +++ b/drivers/hid/hid-wiimote-core.c @@ -1737,7 +1737,7 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev) { struct wiimote_data *wdata; - wdata = kzalloc(sizeof(*wdata), GFP_KERNEL); + wdata = kzalloc_obj(*wdata, GFP_KERNEL); if (!wdata) return NULL; diff --git a/drivers/hid/hid-wiimote-debug.c b/drivers/hid/hid-wiimote-debug.c index 00f9be55f148..07d7137acfd4 100644 --- a/drivers/hid/hid-wiimote-debug.c +++ b/drivers/hid/hid-wiimote-debug.c @@ -174,7 +174,7 @@ int wiidebug_init(struct wiimote_data *wdata) struct wiimote_debug *dbg; unsigned long flags; - dbg = kzalloc(sizeof(*dbg), GFP_KERNEL); + dbg = kzalloc_obj(*dbg, GFP_KERNEL); if (!dbg) return -ENOMEM; diff --git a/drivers/hid/hid-zpff.c b/drivers/hid/hid-zpff.c index aacf7f137b18..b9d94b3fe8b2 100644 --- a/drivers/hid/hid-zpff.c +++ b/drivers/hid/hid-zpff.c @@ -71,7 +71,7 @@ static int zpff_init(struct hid_device *hid) return -ENODEV; } - zpff = kzalloc(sizeof(struct zpff_device), GFP_KERNEL); + zpff = kzalloc_obj(struct zpff_device, GFP_KERNEL); if (!zpff) return -ENOMEM; diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index bbd6f23bce78..c3cd8521b695 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -281,7 +281,7 @@ static int hidraw_open(struct inode *inode, struct file *file) unsigned long flags; int err = 0; - if (!(list = kzalloc(sizeof(struct hidraw_list), GFP_KERNEL))) { + if (!(list = kzalloc_obj(struct hidraw_list, GFP_KERNEL))) { err = -ENOMEM; goto out; } @@ -603,7 +603,7 @@ int hidraw_connect(struct hid_device *hid) /* we accept any HID device, all applications */ - dev = kzalloc(sizeof(struct hidraw), GFP_KERNEL); + dev = kzalloc_obj(struct hidraw, GFP_KERNEL); if (!dev) return -ENOMEM; diff --git a/drivers/hid/intel-ish-hid/ishtp-hid.c b/drivers/hid/intel-ish-hid/ishtp-hid.c index be2c62fc8251..48f06c93a07b 100644 --- a/drivers/hid/intel-ish-hid/ishtp-hid.c +++ b/drivers/hid/intel-ish-hid/ishtp-hid.c @@ -214,7 +214,7 @@ int ishtp_hid_probe(unsigned int cur_hid_dev, if (IS_ERR(hid)) return PTR_ERR(hid); - hid_data = kzalloc(sizeof(*hid_data), GFP_KERNEL); + hid_data = kzalloc_obj(*hid_data, GFP_KERNEL); if (!hid_data) { rv = -ENOMEM; goto err_hid_data; diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c index b890fbf97a75..ff60be49f2b8 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.c +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -435,7 +435,7 @@ static struct ishtp_cl_device *ishtp_bus_add_device(struct ishtp_device *dev, } spin_unlock_irqrestore(&dev->device_list_lock, flags); - device = kzalloc(sizeof(struct ishtp_cl_device), GFP_KERNEL); + device = kzalloc_obj(struct ishtp_cl_device, GFP_KERNEL); if (!device) return NULL; diff --git a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c index 97f4026b1627..717276f8460a 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c +++ b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c @@ -66,7 +66,7 @@ int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl) for (j = 0; j < cl->tx_ring_size; ++j) { struct ishtp_cl_tx_ring *tx_buf; - tx_buf = kzalloc(sizeof(struct ishtp_cl_tx_ring), GFP_KERNEL); + tx_buf = kzalloc_obj(struct ishtp_cl_tx_ring, GFP_KERNEL); if (!tx_buf) goto out; @@ -183,7 +183,7 @@ struct ishtp_cl_rb *ishtp_io_rb_init(struct ishtp_cl *cl) { struct ishtp_cl_rb *rb; - rb = kzalloc(sizeof(struct ishtp_cl_rb), GFP_KERNEL); + rb = kzalloc_obj(struct ishtp_cl_rb, GFP_KERNEL); if (!rb) return NULL; diff --git a/drivers/hid/intel-ish-hid/ishtp/client.c b/drivers/hid/intel-ish-hid/ishtp/client.c index 40f510b1c072..f22b2f85e70f 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client.c +++ b/drivers/hid/intel-ish-hid/ishtp/client.c @@ -105,7 +105,7 @@ struct ishtp_cl *ishtp_cl_allocate(struct ishtp_cl_device *cl_device) { struct ishtp_cl *cl; - cl = kmalloc(sizeof(struct ishtp_cl), GFP_KERNEL); + cl = kmalloc_obj(struct ishtp_cl, GFP_KERNEL); if (!cl) return NULL; diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.c b/drivers/hid/intel-ish-hid/ishtp/hbm.c index 97c4fcd9e3c6..9834dbd04726 100644 --- a/drivers/hid/intel-ish-hid/ishtp/hbm.c +++ b/drivers/hid/intel-ish-hid/ishtp/hbm.c @@ -34,8 +34,8 @@ static void ishtp_hbm_fw_cl_allocate(struct ishtp_device *dev) return; /* allocate storage for fw clients representation */ - clients = kcalloc(dev->fw_clients_num, sizeof(struct ishtp_fw_client), - GFP_KERNEL); + clients = kzalloc_objs(struct ishtp_fw_client, dev->fw_clients_num, + GFP_KERNEL); if (!clients) { dev->dev_state = ISHTP_DEV_RESETTING; ish_hw_reset(dev); diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c index 21a70420151e..a4f57143642c 100644 --- a/drivers/hid/uhid.c +++ b/drivers/hid/uhid.c @@ -110,7 +110,7 @@ static int uhid_queue_event(struct uhid_device *uhid, __u32 event) unsigned long flags; struct uhid_event *ev; - ev = kzalloc(sizeof(*ev), GFP_KERNEL); + ev = kzalloc_obj(*ev, GFP_KERNEL); if (!ev) return -ENOMEM; @@ -129,7 +129,7 @@ static int uhid_hid_start(struct hid_device *hid) struct uhid_event *ev; unsigned long flags; - ev = kzalloc(sizeof(*ev), GFP_KERNEL); + ev = kzalloc_obj(*ev, GFP_KERNEL); if (!ev) return -ENOMEM; @@ -240,7 +240,7 @@ static int uhid_hid_get_report(struct hid_device *hid, unsigned char rnum, if (!READ_ONCE(uhid->running)) return -EIO; - ev = kzalloc(sizeof(*ev), GFP_KERNEL); + ev = kzalloc_obj(*ev, GFP_KERNEL); if (!ev) return -ENOMEM; @@ -282,7 +282,7 @@ static int uhid_hid_set_report(struct hid_device *hid, unsigned char rnum, if (!READ_ONCE(uhid->running) || count > UHID_DATA_MAX) return -EIO; - ev = kzalloc(sizeof(*ev), GFP_KERNEL); + ev = kzalloc_obj(*ev, GFP_KERNEL); if (!ev) return -ENOMEM; @@ -365,7 +365,7 @@ static int uhid_hid_output_raw(struct hid_device *hid, __u8 *buf, size_t count, if (count < 1 || count > UHID_DATA_MAX) return -EINVAL; - ev = kzalloc(sizeof(*ev), GFP_KERNEL); + ev = kzalloc_obj(*ev, GFP_KERNEL); if (!ev) return -ENOMEM; @@ -433,7 +433,7 @@ static int uhid_event_from_user(const char __user *buffer, size_t len, */ struct uhid_create_req_compat *compat; - compat = kzalloc(sizeof(*compat), GFP_KERNEL); + compat = kzalloc_obj(*compat, GFP_KERNEL); if (!compat) return -ENOMEM; @@ -636,7 +636,7 @@ static int uhid_char_open(struct inode *inode, struct file *file) { struct uhid_device *uhid; - uhid = kzalloc(sizeof(*uhid), GFP_KERNEL); + uhid = kzalloc_obj(*uhid, GFP_KERNEL); if (!uhid) return -ENOMEM; diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 758eb21430cd..922022a9c253 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -858,7 +858,7 @@ static int hid_alloc_buffers(struct usb_device *dev, struct hid_device *hid) &usbhid->inbuf_dma); usbhid->outbuf = usb_alloc_coherent(dev, usbhid->bufsize, GFP_KERNEL, &usbhid->outbuf_dma); - usbhid->cr = kmalloc(sizeof(*usbhid->cr), GFP_KERNEL); + usbhid->cr = kmalloc_obj(*usbhid->cr, GFP_KERNEL); usbhid->ctrlbuf = usb_alloc_coherent(dev, usbhid->bufsize, GFP_KERNEL, &usbhid->ctrlbuf_dma); if (!usbhid->inbuf || !usbhid->outbuf || !usbhid->cr || @@ -1430,7 +1430,7 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id * if (usb_string(dev, dev->descriptor.iSerialNumber, hid->uniq, 64) <= 0) hid->uniq[0] = 0; - usbhid = kzalloc(sizeof(*usbhid), GFP_KERNEL); + usbhid = kzalloc_obj(*usbhid, GFP_KERNEL); if (usbhid == NULL) { ret = -ENOMEM; goto err; diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index a4e700b40ba9..6719c976a3c3 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -1528,7 +1528,7 @@ int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks) return -ENODEV; } - pidff = kzalloc(sizeof(*pidff), GFP_KERNEL); + pidff = kzalloc_obj(*pidff, GFP_KERNEL); if (!pidff) return -ENOMEM; diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 59cf3ddfdf78..3a115ea49cd2 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c @@ -434,7 +434,7 @@ static noinline int hiddev_ioctl_usage(struct hiddev *hiddev, unsigned int cmd, struct hid_field *field; int i; - uref_multi = kmalloc(sizeof(struct hiddev_usage_ref_multi), GFP_KERNEL); + uref_multi = kmalloc_obj(struct hiddev_usage_ref_multi, GFP_KERNEL); if (!uref_multi) return -ENOMEM; uref = &uref_multi->uref; @@ -890,7 +890,7 @@ int hiddev_connect(struct hid_device *hid, unsigned int force) return -EINVAL; } - if (!(hiddev = kzalloc(sizeof(struct hiddev), GFP_KERNEL))) + if (!(hiddev = kzalloc_obj(struct hiddev, GFP_KERNEL))) return -ENOMEM; init_waitqueue_head(&hiddev->wait); diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c index af6bc76dbf64..d32a860c6979 100644 --- a/drivers/hid/usbhid/usbkbd.c +++ b/drivers/hid/usbhid/usbkbd.c @@ -241,7 +241,7 @@ static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd) return -1; if (!(kbd->new = usb_alloc_coherent(dev, 8, GFP_KERNEL, &kbd->new_dma))) return -1; - if (!(kbd->cr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL))) + if (!(kbd->cr = kmalloc_obj(struct usb_ctrlrequest, GFP_KERNEL))) return -1; if (!(kbd->leds = usb_alloc_coherent(dev, 1, GFP_KERNEL, &kbd->leds_dma))) return -1; @@ -281,7 +281,7 @@ static int usb_kbd_probe(struct usb_interface *iface, pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); maxp = usb_maxpacket(dev, pipe); - kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL); + kbd = kzalloc_obj(struct usb_kbd, GFP_KERNEL); input_dev = input_allocate_device(); if (!kbd || !input_dev) goto fail1; diff --git a/drivers/hid/usbhid/usbmouse.c b/drivers/hid/usbhid/usbmouse.c index 3fd93c2e4f4a..da0d30de1d26 100644 --- a/drivers/hid/usbhid/usbmouse.c +++ b/drivers/hid/usbhid/usbmouse.c @@ -125,7 +125,7 @@ static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_i pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); maxp = usb_maxpacket(dev, pipe); - mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL); + mouse = kzalloc_obj(struct usb_mouse, GFP_KERNEL); input_dev = input_allocate_device(); if (!mouse || !input_dev) goto fail1; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index afc900560706..e2e9f8afec11 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -892,7 +892,7 @@ static int wacom_add_shared_data(struct hid_device *hdev) data = wacom_get_hdev_data(hdev); if (!data) { - data = kzalloc(sizeof(struct wacom_hdev_data), GFP_KERNEL); + data = kzalloc_obj(struct wacom_hdev_data, GFP_KERNEL); if (!data) { mutex_unlock(&wacom_udev_list_lock); return -ENOMEM; From 62d3d67183dd9d369ffea7ba3472878b00daff76 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:39 +0200 Subject: [PATCH 165/295] HID: pidff: Fix condition effect bit clearing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-23349 commit 97d5c8f5c09a604c4873c8348f58de3cea69a7df Author: Tomasz Pakuła Date: Wed Feb 4 22:44:55 2026 +0100 HID: pidff: Fix condition effect bit clearing As reported by MPDarkGuy on discord, NULL pointer dereferences were happening because not all the conditional effects bits were cleared. Properly clear all conditional effect bits from ffbit Fixes: 7f3d7bc0df4b ("HID: pidff: Better quirk assigment when searching for fields") Cc: stable@vger.kernel.org # 6.18.x Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 6719c976a3c3..f3b0bb6d39df 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -1452,10 +1452,13 @@ static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev) hid_warn(pidff->hid, "unknown ramp effect layout\n"); if (PIDFF_FIND_FIELDS(set_condition, PID_SET_CONDITION, 1)) { - if (test_and_clear_bit(FF_SPRING, dev->ffbit) || - test_and_clear_bit(FF_DAMPER, dev->ffbit) || - test_and_clear_bit(FF_FRICTION, dev->ffbit) || - test_and_clear_bit(FF_INERTIA, dev->ffbit)) + bool test = false; + + test |= test_and_clear_bit(FF_SPRING, dev->ffbit); + test |= test_and_clear_bit(FF_DAMPER, dev->ffbit); + test |= test_and_clear_bit(FF_FRICTION, dev->ffbit); + test |= test_and_clear_bit(FF_INERTIA, dev->ffbit); + if (test) hid_warn(pidff->hid, "unknown condition effect layout\n"); } From d492fecf0e9f64e516c0025860e13d26b3dd1e19 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 11:41:30 +0200 Subject: [PATCH 166/295] Convert 'alloc_obj' family to use the new default GFP_KERNEL argument JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 Partial commit: only drivers/hid/ commit bf4afc53b77aeaa48b5409da5c8da6bb4eff7f43 Author: Linus Torvalds Date: Sat Feb 21 16:37:42 2026 -0800 Convert 'alloc_obj' family to use the new default GFP_KERNEL argument This was done entirely with mindless brute force, using git grep -l '\ Signed-off-by: Benjamin Tissoires --- drivers/hid/amd-sfh-hid/amd_sfh_client.c | 2 +- drivers/hid/amd-sfh-hid/amd_sfh_hid.c | 2 +- drivers/hid/bpf/hid_bpf_dispatch.c | 2 +- drivers/hid/hid-apple.c | 2 +- drivers/hid/hid-axff.c | 2 +- drivers/hid/hid-betopff.c | 2 +- drivers/hid/hid-cmedia.c | 2 +- drivers/hid/hid-core.c | 8 ++++---- drivers/hid/hid-corsair.c | 4 ++-- drivers/hid/hid-cougar.c | 2 +- drivers/hid/hid-debug.c | 2 +- drivers/hid/hid-dr.c | 2 +- drivers/hid/hid-elo.c | 2 +- drivers/hid/hid-emsff.c | 2 +- drivers/hid/hid-gaff.c | 2 +- drivers/hid/hid-holtekff.c | 2 +- drivers/hid/hid-hyperv.c | 2 +- drivers/hid/hid-input.c | 4 ++-- drivers/hid/hid-lg.c | 2 +- drivers/hid/hid-lg2ff.c | 2 +- drivers/hid/hid-lg4ff.c | 2 +- drivers/hid/hid-logitech-dj.c | 8 ++++---- drivers/hid/hid-logitech-hidpp.c | 8 ++++---- drivers/hid/hid-megaworld.c | 2 +- drivers/hid/hid-mf.c | 2 +- drivers/hid/hid-ntrig.c | 2 +- drivers/hid/hid-picolcd_core.c | 4 ++-- drivers/hid/hid-pl.c | 2 +- drivers/hid/hid-playstation.c | 2 +- drivers/hid/hid-prodikeys.c | 2 +- drivers/hid/hid-quirks.c | 4 ++-- drivers/hid/hid-roccat-arvo.c | 2 +- drivers/hid/hid-roccat-isku.c | 2 +- drivers/hid/hid-roccat-kone.c | 2 +- drivers/hid/hid-roccat-koneplus.c | 2 +- drivers/hid/hid-roccat-konepure.c | 2 +- drivers/hid/hid-roccat-kovaplus.c | 2 +- drivers/hid/hid-roccat-lua.c | 2 +- drivers/hid/hid-roccat-pyra.c | 2 +- drivers/hid/hid-roccat-ryos.c | 2 +- drivers/hid/hid-roccat-savu.c | 2 +- drivers/hid/hid-roccat.c | 4 ++-- drivers/hid/hid-sensor-custom.c | 2 +- drivers/hid/hid-sjoy.c | 2 +- drivers/hid/hid-thrustmaster.c | 4 ++-- drivers/hid/hid-tmff.c | 2 +- drivers/hid/hid-uclogic-params.c | 4 ++-- drivers/hid/hid-wiimote-core.c | 2 +- drivers/hid/hid-wiimote-debug.c | 2 +- drivers/hid/hid-zpff.c | 2 +- drivers/hid/hidraw.c | 4 ++-- drivers/hid/intel-ish-hid/ishtp-hid.c | 2 +- drivers/hid/intel-ish-hid/ishtp/bus.c | 2 +- drivers/hid/intel-ish-hid/ishtp/client-buffers.c | 4 ++-- drivers/hid/intel-ish-hid/ishtp/client.c | 2 +- drivers/hid/uhid.c | 14 +++++++------- drivers/hid/usbhid/hid-core.c | 4 ++-- drivers/hid/usbhid/hid-pidff.c | 2 +- drivers/hid/usbhid/hiddev.c | 4 ++-- drivers/hid/usbhid/usbkbd.c | 4 ++-- drivers/hid/usbhid/usbmouse.c | 2 +- drivers/hid/wacom_sys.c | 2 +- 62 files changed, 89 insertions(+), 89 deletions(-) diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_client.c b/drivers/hid/amd-sfh-hid/amd_sfh_client.c index 8b479441bf90..96ae792beeb6 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_client.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_client.c @@ -47,7 +47,7 @@ int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type) guard(mutex)(&mp2->lock); for (i = 0; i < cli_data->num_hid_devices; i++) { if (cli_data->hid_sensor_hubs[i] == hid) { - struct request_list *new = kzalloc_obj(*new, GFP_KERNEL); + struct request_list *new = kzalloc_obj(*new); if (!new) return -ENOMEM; diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c index 59bc9a2e0cd7..b04f675d49b0 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c @@ -135,7 +135,7 @@ int amdtp_hid_probe(u32 cur_hid_dev, struct amdtp_cl_data *cli_data) if (IS_ERR(hid)) return PTR_ERR(hid); - hid_data = kzalloc_obj(*hid_data, GFP_KERNEL); + hid_data = kzalloc_obj(*hid_data); if (!hid_data) { rc = -ENOMEM; goto err_hid_data; diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index c99d81789eb3..f3d15994ca1e 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -320,7 +320,7 @@ hid_bpf_allocate_context(unsigned int hid_id) if (IS_ERR(hdev)) return NULL; - ctx_kern = kzalloc_obj(*ctx_kern, GFP_KERNEL); + ctx_kern = kzalloc_obj(*ctx_kern); if (!ctx_kern) { hid_put_device(hdev); return NULL; diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index cf48186ff1ff..87189c8396f4 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -792,7 +792,7 @@ static int apple_backlight_set(struct hid_device *hdev, u16 value, u16 rate) int ret = 0; struct apple_backlight_set_report *rep; - rep = kmalloc_obj(*rep, GFP_KERNEL); + rep = kmalloc_obj(*rep); if (rep == NULL) return -ENOMEM; diff --git a/drivers/hid/hid-axff.c b/drivers/hid/hid-axff.c index c7dcdffff9e6..3c5c2bf02425 100644 --- a/drivers/hid/hid-axff.c +++ b/drivers/hid/hid-axff.c @@ -96,7 +96,7 @@ static int axff_init(struct hid_device *hid) return -ENODEV; } - axff = kzalloc_obj(struct axff_device, GFP_KERNEL); + axff = kzalloc_obj(struct axff_device); if (!axff) return -ENOMEM; diff --git a/drivers/hid/hid-betopff.c b/drivers/hid/hid-betopff.c index d739b3746138..8a7fe895926c 100644 --- a/drivers/hid/hid-betopff.c +++ b/drivers/hid/hid-betopff.c @@ -100,7 +100,7 @@ static int betopff_init(struct hid_device *hid) } } - betopff = kzalloc_obj(*betopff, GFP_KERNEL); + betopff = kzalloc_obj(*betopff); if (!betopff) return -ENOMEM; diff --git a/drivers/hid/hid-cmedia.c b/drivers/hid/hid-cmedia.c index b35292c0335e..7b3dd4197875 100644 --- a/drivers/hid/hid-cmedia.c +++ b/drivers/hid/hid-cmedia.c @@ -145,7 +145,7 @@ static int cmhid_probe(struct hid_device *hid, const struct hid_device_id *id) int ret; struct cmhid *cm; - cm = kzalloc_obj(struct cmhid, GFP_KERNEL); + cm = kzalloc_obj(struct cmhid); if (!cm) { ret = -ENOMEM; goto allocfail; diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 88569d454ee3..ebabccbafdfd 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -93,7 +93,7 @@ struct hid_report *hid_register_report(struct hid_device *device, if (report_enum->report_id_hash[id]) return report_enum->report_id_hash[id]; - report = kzalloc_obj(struct hid_report, GFP_KERNEL); + report = kzalloc_obj(struct hid_report); if (!report) return NULL; @@ -1797,7 +1797,7 @@ static void hid_report_process_ordering(struct hid_device *hid, } /* allocate the memory to process the fields */ - entries = kzalloc_objs(*entries, count, GFP_KERNEL); + entries = kzalloc_objs(*entries, count); if (!entries) return; @@ -2611,7 +2611,7 @@ static ssize_t new_id_store(struct device_driver *drv, const char *buf, if (ret < 3) return -EINVAL; - dynid = kzalloc_obj(*dynid, GFP_KERNEL); + dynid = kzalloc_obj(*dynid); if (!dynid) return -ENOMEM; @@ -2973,7 +2973,7 @@ struct hid_device *hid_allocate_device(void) struct hid_device *hdev; int ret = -ENOMEM; - hdev = kzalloc_obj(*hdev, GFP_KERNEL); + hdev = kzalloc_obj(*hdev); if (hdev == NULL) return ERR_PTR(ret); diff --git a/drivers/hid/hid-corsair.c b/drivers/hid/hid-corsair.c index 750cac0a2a69..21cd8b12a757 100644 --- a/drivers/hid/hid-corsair.c +++ b/drivers/hid/hid-corsair.c @@ -427,7 +427,7 @@ static int k90_init_backlight(struct hid_device *dev) size_t name_sz; char *name; - drvdata->backlight = kzalloc_obj(struct k90_led, GFP_KERNEL); + drvdata->backlight = kzalloc_obj(struct k90_led); if (!drvdata->backlight) { ret = -ENOMEM; goto fail_backlight_alloc; @@ -471,7 +471,7 @@ static int k90_init_macro_functions(struct hid_device *dev) size_t name_sz; char *name; - k90 = kzalloc_obj(struct k90_drvdata, GFP_KERNEL); + k90 = kzalloc_obj(struct k90_drvdata); if (!k90) { ret = -ENOMEM; goto fail_drvdata; diff --git a/drivers/hid/hid-cougar.c b/drivers/hid/hid-cougar.c index 0b66a7c61ac6..ad027c45f162 100644 --- a/drivers/hid/hid-cougar.c +++ b/drivers/hid/hid-cougar.c @@ -166,7 +166,7 @@ static int cougar_bind_shared_data(struct hid_device *hdev, shared = cougar_get_shared_data(hdev); if (!shared) { - shared = kzalloc_obj(*shared, GFP_KERNEL); + shared = kzalloc_obj(*shared); if (!shared) { error = -ENOMEM; goto out; diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index 5430282b3089..7dda0464758a 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -3680,7 +3680,7 @@ static int hid_debug_events_open(struct inode *inode, struct file *file) struct hid_debug_list *list; unsigned long flags; - if (!(list = kzalloc_obj(struct hid_debug_list, GFP_KERNEL))) { + if (!(list = kzalloc_obj(struct hid_debug_list))) { err = -ENOMEM; goto out; } diff --git a/drivers/hid/hid-dr.c b/drivers/hid/hid-dr.c index 9923b222383a..8a8f68a7feb0 100644 --- a/drivers/hid/hid-dr.c +++ b/drivers/hid/hid-dr.c @@ -104,7 +104,7 @@ static int drff_init(struct hid_device *hid) return -ENODEV; } - drff = kzalloc_obj(struct drff_device, GFP_KERNEL); + drff = kzalloc_obj(struct drff_device); if (!drff) return -ENOMEM; diff --git a/drivers/hid/hid-elo.c b/drivers/hid/hid-elo.c index 465f04ed1a55..b8f5f3eb53a4 100644 --- a/drivers/hid/hid-elo.c +++ b/drivers/hid/hid-elo.c @@ -232,7 +232,7 @@ static int elo_probe(struct hid_device *hdev, const struct hid_device_id *id) if (!hid_is_usb(hdev)) return -EINVAL; - priv = kzalloc_obj(*priv, GFP_KERNEL); + priv = kzalloc_obj(*priv); if (!priv) return -ENOMEM; diff --git a/drivers/hid/hid-emsff.c b/drivers/hid/hid-emsff.c index 0dbe598f2e48..1b4ad18f6051 100644 --- a/drivers/hid/hid-emsff.c +++ b/drivers/hid/hid-emsff.c @@ -76,7 +76,7 @@ static int emsff_init(struct hid_device *hid) return -ENODEV; } - emsff = kzalloc_obj(struct emsff_device, GFP_KERNEL); + emsff = kzalloc_obj(struct emsff_device); if (!emsff) return -ENOMEM; diff --git a/drivers/hid/hid-gaff.c b/drivers/hid/hid-gaff.c index a9d305a193d2..8b99686b63df 100644 --- a/drivers/hid/hid-gaff.c +++ b/drivers/hid/hid-gaff.c @@ -96,7 +96,7 @@ static int gaff_init(struct hid_device *hid) return -ENODEV; } - gaff = kzalloc_obj(struct gaff_device, GFP_KERNEL); + gaff = kzalloc_obj(struct gaff_device); if (!gaff) return -ENOMEM; diff --git a/drivers/hid/hid-holtekff.c b/drivers/hid/hid-holtekff.c index 56980af08683..32d08f7a660d 100644 --- a/drivers/hid/hid-holtekff.c +++ b/drivers/hid/hid-holtekff.c @@ -149,7 +149,7 @@ static int holtekff_init(struct hid_device *hid) return -ENODEV; } - holtekff = kzalloc_obj(*holtekff, GFP_KERNEL); + holtekff = kzalloc_obj(*holtekff); if (!holtekff) return -ENOMEM; diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c index f092547d08d2..7d2b0063df15 100644 --- a/drivers/hid/hid-hyperv.c +++ b/drivers/hid/hid-hyperv.c @@ -149,7 +149,7 @@ static struct mousevsc_dev *mousevsc_alloc_device(struct hv_device *device) { struct mousevsc_dev *input_dev; - input_dev = kzalloc_obj(struct mousevsc_dev, GFP_KERNEL); + input_dev = kzalloc_obj(struct mousevsc_dev); if (!input_dev) return NULL; diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 6137cc0aeeaf..d5308adb2894 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -531,7 +531,7 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, if (quirks & HID_BATTERY_QUIRK_IGNORE) return 0; - psy_desc = kzalloc_obj(*psy_desc, GFP_KERNEL); + psy_desc = kzalloc_obj(*psy_desc); if (!psy_desc) return -ENOMEM; @@ -2023,7 +2023,7 @@ static void report_features(struct hid_device *hid) static struct hid_input *hidinput_allocate(struct hid_device *hid, unsigned int application) { - struct hid_input *hidinput = kzalloc_obj(*hidinput, GFP_KERNEL); + struct hid_input *hidinput = kzalloc_obj(*hidinput); struct input_dev *input_dev = input_allocate_device(); const char *suffix = NULL; size_t suffix_len, name_len; diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index 7cdf1e895b69..197c2f3b077f 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -768,7 +768,7 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id) return -ENODEV; } - drv_data = kzalloc_obj(struct lg_drv_data, GFP_KERNEL); + drv_data = kzalloc_obj(struct lg_drv_data); if (!drv_data) { hid_err(hdev, "Insufficient memory, cannot allocate driver data\n"); return -ENOMEM; diff --git a/drivers/hid/hid-lg2ff.c b/drivers/hid/hid-lg2ff.c index e4a034bd4095..c3e85b079d80 100644 --- a/drivers/hid/hid-lg2ff.c +++ b/drivers/hid/hid-lg2ff.c @@ -66,7 +66,7 @@ int lg2ff_init(struct hid_device *hid) if (!report) return -ENODEV; - lg2ff = kmalloc_obj(struct lg2ff_device, GFP_KERNEL); + lg2ff = kmalloc_obj(struct lg2ff_device); if (!lg2ff) return -ENOMEM; diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index 4979f7779de8..e901fdb7d033 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -1288,7 +1288,7 @@ int lg4ff_init(struct hid_device *hid) hid_err(hid, "Cannot add device, private driver data not allocated\n"); return -1; } - entry = kzalloc_obj(*entry, GFP_KERNEL); + entry = kzalloc_obj(*entry); if (!entry) return -ENOMEM; spin_lock_init(&entry->report_lock); diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 2fb6a67bede2..d9b6c72fc254 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -733,7 +733,7 @@ static struct dj_receiver_dev *dj_get_receiver_dev(struct hid_device *hdev, djrcv_dev = dj_find_receiver_dev(hdev, type); if (!djrcv_dev) { - djrcv_dev = kzalloc_obj(*djrcv_dev, GFP_KERNEL); + djrcv_dev = kzalloc_obj(*djrcv_dev); if (!djrcv_dev) goto out; @@ -851,7 +851,7 @@ static void logi_dj_recv_add_djhid_device(struct dj_receiver_dev *djrcv_dev, snprintf(tmpstr, sizeof(tmpstr), ":%d", device_index); strlcat(dj_hiddev->phys, tmpstr, sizeof(dj_hiddev->phys)); - dj_dev = kzalloc_obj(struct dj_device, GFP_KERNEL); + dj_dev = kzalloc_obj(struct dj_device); if (!dj_dev) { hid_err(djrcv_hdev, "%s: failed allocating dj_dev\n", __func__); @@ -1332,7 +1332,7 @@ static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev) goto out; } - dj_report = kzalloc_obj(struct dj_report, GFP_KERNEL); + dj_report = kzalloc_obj(struct dj_report); if (!dj_report) return -ENOMEM; dj_report->report_id = REPORT_ID_DJ_SHORT; @@ -1356,7 +1356,7 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, u8 *buf; int retval = 0; - dj_report = kzalloc_obj(struct dj_report, GFP_KERNEL); + dj_report = kzalloc_obj(struct dj_report); if (!dj_report) return -ENOMEM; diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 4f3289b8a7c2..d40932809ce1 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -390,7 +390,7 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, return -EINVAL; } - message = kzalloc_obj(struct hidpp_report, GFP_KERNEL); + message = kzalloc_obj(struct hidpp_report); if (!message) return -ENOMEM; @@ -443,7 +443,7 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, if (param_count > max_count) return -EINVAL; - message = kzalloc_obj(struct hidpp_report, GFP_KERNEL); + message = kzalloc_obj(struct hidpp_report); if (!message) return -ENOMEM; message->report_id = report_id; @@ -2527,7 +2527,7 @@ out: static int hidpp_ff_queue_work(struct hidpp_ff_private_data *data, int effect_id, u8 command, u8 *params, u8 size) { - struct hidpp_ff_work_data *wd = kzalloc_obj(*wd, GFP_KERNEL); + struct hidpp_ff_work_data *wd = kzalloc_obj(*wd); int s; if (!wd) @@ -2853,7 +2853,7 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, data = kmemdup(data, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - data->effect_ids = kzalloc_objs(int, num_slots, GFP_KERNEL); + data->effect_ids = kzalloc_objs(int, num_slots); if (!data->effect_ids) { kfree(data); return -ENOMEM; diff --git a/drivers/hid/hid-megaworld.c b/drivers/hid/hid-megaworld.c index 62f3e976f6b3..81acdbc3a00f 100644 --- a/drivers/hid/hid-megaworld.c +++ b/drivers/hid/hid-megaworld.c @@ -57,7 +57,7 @@ static int mwctrl_init(struct hid_device *hid) return -ENODEV; } - mwctrl = kzalloc_obj(struct mwctrl_device, GFP_KERNEL); + mwctrl = kzalloc_obj(struct mwctrl_device); if (!mwctrl) return -ENOMEM; diff --git a/drivers/hid/hid-mf.c b/drivers/hid/hid-mf.c index 9a34645e92b6..6ff54a1ec697 100644 --- a/drivers/hid/hid-mf.c +++ b/drivers/hid/hid-mf.c @@ -88,7 +88,7 @@ static int mf_init(struct hid_device *hid) input_ptr = input_ptr->next; input = list_entry(input_ptr, struct hid_input, list); - mf = kzalloc_obj(struct mf_device, GFP_KERNEL); + mf = kzalloc_obj(struct mf_device); if (!mf) return -ENOMEM; diff --git a/drivers/hid/hid-ntrig.c b/drivers/hid/hid-ntrig.c index f48dc2f34f79..2b31756b549e 100644 --- a/drivers/hid/hid-ntrig.c +++ b/drivers/hid/hid-ntrig.c @@ -900,7 +900,7 @@ static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id) hdev->quirks |= HID_QUIRK_MULTI_INPUT | HID_QUIRK_NO_INIT_REPORTS; - nd = kmalloc_obj(struct ntrig_data, GFP_KERNEL); + nd = kmalloc_obj(struct ntrig_data); if (!nd) { hid_err(hdev, "cannot allocate N-Trig data\n"); return -ENOMEM; diff --git a/drivers/hid/hid-picolcd_core.c b/drivers/hid/hid-picolcd_core.c index 99cae54dc769..2cc01e1bc1a8 100644 --- a/drivers/hid/hid-picolcd_core.c +++ b/drivers/hid/hid-picolcd_core.c @@ -78,7 +78,7 @@ struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev, return NULL; if (data->status & PICOLCD_FAILED) return NULL; - work = kzalloc_obj(*work, GFP_KERNEL); + work = kzalloc_obj(*work); if (!work) return NULL; @@ -528,7 +528,7 @@ static int picolcd_probe(struct hid_device *hdev, * Let's allocate the picolcd data structure, set some reasonable * defaults, and associate it with the device */ - data = kzalloc_obj(struct picolcd_data, GFP_KERNEL); + data = kzalloc_obj(struct picolcd_data); if (data == NULL) { hid_err(hdev, "can't allocate space for Minibox PicoLCD device data\n"); return -ENOMEM; diff --git a/drivers/hid/hid-pl.c b/drivers/hid/hid-pl.c index 967f93d9b46b..c6c2961dd574 100644 --- a/drivers/hid/hid-pl.c +++ b/drivers/hid/hid-pl.c @@ -140,7 +140,7 @@ static int plff_init(struct hid_device *hid) return -ENODEV; } - plff = kzalloc_obj(struct plff_device, GFP_KERNEL); + plff = kzalloc_obj(struct plff_device); if (!plff) return -ENOMEM; diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 2458c6b61358..3c0db8f93c82 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -1658,7 +1658,7 @@ static int dualsense_reset_leds(struct dualsense *ds) struct dualsense_output_report report; struct dualsense_output_report_bt *buf; - buf = kzalloc_obj(*buf, GFP_KERNEL); + buf = kzalloc_obj(*buf); if (!buf) return -ENOMEM; diff --git a/drivers/hid/hid-prodikeys.c b/drivers/hid/hid-prodikeys.c index e69b3cb682bc..fba01e4fcab1 100644 --- a/drivers/hid/hid-prodikeys.c +++ b/drivers/hid/hid-prodikeys.c @@ -797,7 +797,7 @@ static int pk_probe(struct hid_device *hdev, const struct hid_device_id *id) intf = to_usb_interface(hdev->dev.parent); ifnum = intf->cur_altsetting->desc.bInterfaceNumber; - pm = kzalloc_obj(*pm, GFP_KERNEL); + pm = kzalloc_obj(*pm); if (pm == NULL) { hid_err(hdev, "can't alloc descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index b6a9cfb71ec9..edc4339adb50 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -1164,11 +1164,11 @@ static int hid_modify_dquirk(const struct hid_device_id *id, int list_edited = 0; int ret = 0; - hdev = kzalloc_obj(*hdev, GFP_KERNEL); + hdev = kzalloc_obj(*hdev); if (!hdev) return -ENOMEM; - q_new = kmalloc_obj(struct quirks_list_struct, GFP_KERNEL); + q_new = kmalloc_obj(struct quirks_list_struct); if (!q_new) { ret = -ENOMEM; goto out; diff --git a/drivers/hid/hid-roccat-arvo.c b/drivers/hid/hid-roccat-arvo.c index edfa448a12d7..f963be0ccc18 100644 --- a/drivers/hid/hid-roccat-arvo.c +++ b/drivers/hid/hid-roccat-arvo.c @@ -299,7 +299,7 @@ static int arvo_init_specials(struct hid_device *hdev) return 0; } - arvo = kzalloc_obj(*arvo, GFP_KERNEL); + arvo = kzalloc_obj(*arvo); if (!arvo) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-isku.c b/drivers/hid/hid-roccat-isku.c index 0d72195a8816..d9095a106395 100644 --- a/drivers/hid/hid-roccat-isku.c +++ b/drivers/hid/hid-roccat-isku.c @@ -279,7 +279,7 @@ static int isku_init_specials(struct hid_device *hdev) return 0; } - isku = kzalloc_obj(*isku, GFP_KERNEL); + isku = kzalloc_obj(*isku); if (!isku) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index d1d319339803..3de5682f95cf 100644 --- a/drivers/hid/hid-roccat-kone.c +++ b/drivers/hid/hid-roccat-kone.c @@ -704,7 +704,7 @@ static int kone_init_specials(struct hid_device *hdev) if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { - kone = kzalloc_obj(*kone, GFP_KERNEL); + kone = kzalloc_obj(*kone); if (!kone) return -ENOMEM; hid_set_drvdata(hdev, kone); diff --git a/drivers/hid/hid-roccat-koneplus.c b/drivers/hid/hid-roccat-koneplus.c index e859618763c7..a8311c157e8f 100644 --- a/drivers/hid/hid-roccat-koneplus.c +++ b/drivers/hid/hid-roccat-koneplus.c @@ -384,7 +384,7 @@ static int koneplus_init_specials(struct hid_device *hdev) if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { - koneplus = kzalloc_obj(*koneplus, GFP_KERNEL); + koneplus = kzalloc_obj(*koneplus); if (!koneplus) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-konepure.c b/drivers/hid/hid-roccat-konepure.c index 34e4cb53b779..eaac5f7f5fee 100644 --- a/drivers/hid/hid-roccat-konepure.c +++ b/drivers/hid/hid-roccat-konepure.c @@ -88,7 +88,7 @@ static int konepure_init_specials(struct hid_device *hdev) return 0; } - konepure = kzalloc_obj(*konepure, GFP_KERNEL); + konepure = kzalloc_obj(*konepure); if (!konepure) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-kovaplus.c b/drivers/hid/hid-roccat-kovaplus.c index 922c01dcab2a..d3f4ce8d1090 100644 --- a/drivers/hid/hid-roccat-kovaplus.c +++ b/drivers/hid/hid-roccat-kovaplus.c @@ -453,7 +453,7 @@ static int kovaplus_init_specials(struct hid_device *hdev) if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { - kovaplus = kzalloc_obj(*kovaplus, GFP_KERNEL); + kovaplus = kzalloc_obj(*kovaplus); if (!kovaplus) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-lua.c b/drivers/hid/hid-roccat-lua.c index a7ed873f8761..8e35e1a0660b 100644 --- a/drivers/hid/hid-roccat-lua.c +++ b/drivers/hid/hid-roccat-lua.c @@ -119,7 +119,7 @@ static int lua_init_specials(struct hid_device *hdev) struct lua_device *lua; int retval; - lua = kzalloc_obj(*lua, GFP_KERNEL); + lua = kzalloc_obj(*lua); if (!lua) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c index 91c5c62f4ce8..b69f88d70a7d 100644 --- a/drivers/hid/hid-roccat-pyra.c +++ b/drivers/hid/hid-roccat-pyra.c @@ -403,7 +403,7 @@ static int pyra_init_specials(struct hid_device *hdev) if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { - pyra = kzalloc_obj(*pyra, GFP_KERNEL); + pyra = kzalloc_obj(*pyra); if (!pyra) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-ryos.c b/drivers/hid/hid-roccat-ryos.c index f3ea2b5ac840..28b2c717b662 100644 --- a/drivers/hid/hid-roccat-ryos.c +++ b/drivers/hid/hid-roccat-ryos.c @@ -96,7 +96,7 @@ static int ryos_init_specials(struct hid_device *hdev) return 0; } - ryos = kzalloc_obj(*ryos, GFP_KERNEL); + ryos = kzalloc_obj(*ryos); if (!ryos) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-savu.c b/drivers/hid/hid-roccat-savu.c index 262450374f8a..1f202bdfd7db 100644 --- a/drivers/hid/hid-roccat-savu.c +++ b/drivers/hid/hid-roccat-savu.c @@ -68,7 +68,7 @@ static int savu_init_specials(struct hid_device *hdev) return 0; } - savu = kzalloc_obj(*savu, GFP_KERNEL); + savu = kzalloc_obj(*savu); if (!savu) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c index 5fe16ec1849a..fd0ea52f7cba 100644 --- a/drivers/hid/hid-roccat.c +++ b/drivers/hid/hid-roccat.c @@ -152,7 +152,7 @@ static int roccat_open(struct inode *inode, struct file *file) struct roccat_device *device; int error = 0; - reader = kzalloc_obj(struct roccat_reader, GFP_KERNEL); + reader = kzalloc_obj(struct roccat_reader); if (!reader) return -ENOMEM; @@ -301,7 +301,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report struct roccat_device *device; int temp; - device = kzalloc_obj(struct roccat_device, GFP_KERNEL); + device = kzalloc_obj(struct roccat_device); if (!device) return -ENOMEM; diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c index 7632288181f0..ad96ffe4bcc3 100644 --- a/drivers/hid/hid-sensor-custom.c +++ b/drivers/hid/hid-sensor-custom.c @@ -912,7 +912,7 @@ hid_sensor_custom_get_known(struct hid_sensor_hub_device *hsdev, hid_sensor_custom_known_table; struct hid_sensor_custom_properties *prop; - prop = kmalloc_obj(struct hid_sensor_custom_properties, GFP_KERNEL); + prop = kmalloc_obj(struct hid_sensor_custom_properties); if (!prop) return -ENOMEM; diff --git a/drivers/hid/hid-sjoy.c b/drivers/hid/hid-sjoy.c index e567c264aed3..bab93d71b760 100644 --- a/drivers/hid/hid-sjoy.c +++ b/drivers/hid/hid-sjoy.c @@ -83,7 +83,7 @@ static int sjoyff_init(struct hid_device *hid) return -ENODEV; } - sjoyff = kzalloc_obj(struct sjoyff_device, GFP_KERNEL); + sjoyff = kzalloc_obj(struct sjoyff_device); if (!sjoyff) return -ENOMEM; diff --git a/drivers/hid/hid-thrustmaster.c b/drivers/hid/hid-thrustmaster.c index 15847eef4ac0..48a79c97b7d0 100644 --- a/drivers/hid/hid-thrustmaster.c +++ b/drivers/hid/hid-thrustmaster.c @@ -308,7 +308,7 @@ static int thrustmaster_probe(struct hid_device *hdev, const struct hid_device_i } // Now we allocate the tm_wheel - tm_wheel = kzalloc_obj(struct tm_wheel, GFP_KERNEL); + tm_wheel = kzalloc_obj(struct tm_wheel); if (!tm_wheel) { ret = -ENOMEM; goto error1; @@ -328,7 +328,7 @@ static int thrustmaster_probe(struct hid_device *hdev, const struct hid_device_i goto error3; } - tm_wheel->response = kzalloc_obj(struct tm_wheel_response, GFP_KERNEL); + tm_wheel->response = kzalloc_obj(struct tm_wheel_response); if (!tm_wheel->response) { ret = -ENOMEM; goto error4; diff --git a/drivers/hid/hid-tmff.c b/drivers/hid/hid-tmff.c index ddb2629ed9e1..423f395d01ac 100644 --- a/drivers/hid/hid-tmff.c +++ b/drivers/hid/hid-tmff.c @@ -132,7 +132,7 @@ static int tmff_init(struct hid_device *hid, const signed short *ff_bits) hidinput = list_entry(hid->inputs.next, struct hid_input, list); input_dev = hidinput->input; - tmff = kzalloc_obj(struct tmff_device, GFP_KERNEL); + tmff = kzalloc_obj(struct tmff_device); if (!tmff) return -ENOMEM; diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 21b1d865bd0d..ed16f50f21ed 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -1358,13 +1358,13 @@ static int uclogic_params_ugee_v2_init_event_hooks(struct hid_device *hdev, if (!uclogic_params_ugee_v2_has_battery(hdev)) return 0; - p->event_hooks = kzalloc_obj(*p->event_hooks, GFP_KERNEL); + p->event_hooks = kzalloc_obj(*p->event_hooks); if (!p->event_hooks) return -ENOMEM; INIT_LIST_HEAD(&p->event_hooks->list); - event_hook = kzalloc_obj(*event_hook, GFP_KERNEL); + event_hook = kzalloc_obj(*event_hook); if (!event_hook) return -ENOMEM; diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c index c24ccfc7c05a..63c4fa8fbb9b 100644 --- a/drivers/hid/hid-wiimote-core.c +++ b/drivers/hid/hid-wiimote-core.c @@ -1737,7 +1737,7 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev) { struct wiimote_data *wdata; - wdata = kzalloc_obj(*wdata, GFP_KERNEL); + wdata = kzalloc_obj(*wdata); if (!wdata) return NULL; diff --git a/drivers/hid/hid-wiimote-debug.c b/drivers/hid/hid-wiimote-debug.c index 07d7137acfd4..5f74917781f2 100644 --- a/drivers/hid/hid-wiimote-debug.c +++ b/drivers/hid/hid-wiimote-debug.c @@ -174,7 +174,7 @@ int wiidebug_init(struct wiimote_data *wdata) struct wiimote_debug *dbg; unsigned long flags; - dbg = kzalloc_obj(*dbg, GFP_KERNEL); + dbg = kzalloc_obj(*dbg); if (!dbg) return -ENOMEM; diff --git a/drivers/hid/hid-zpff.c b/drivers/hid/hid-zpff.c index b9d94b3fe8b2..d8e023c8aa84 100644 --- a/drivers/hid/hid-zpff.c +++ b/drivers/hid/hid-zpff.c @@ -71,7 +71,7 @@ static int zpff_init(struct hid_device *hid) return -ENODEV; } - zpff = kzalloc_obj(struct zpff_device, GFP_KERNEL); + zpff = kzalloc_obj(struct zpff_device); if (!zpff) return -ENOMEM; diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index c3cd8521b695..9129fabed181 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -281,7 +281,7 @@ static int hidraw_open(struct inode *inode, struct file *file) unsigned long flags; int err = 0; - if (!(list = kzalloc_obj(struct hidraw_list, GFP_KERNEL))) { + if (!(list = kzalloc_obj(struct hidraw_list))) { err = -ENOMEM; goto out; } @@ -603,7 +603,7 @@ int hidraw_connect(struct hid_device *hid) /* we accept any HID device, all applications */ - dev = kzalloc_obj(struct hidraw, GFP_KERNEL); + dev = kzalloc_obj(struct hidraw); if (!dev) return -ENOMEM; diff --git a/drivers/hid/intel-ish-hid/ishtp-hid.c b/drivers/hid/intel-ish-hid/ishtp-hid.c index 48f06c93a07b..6e255b1bc721 100644 --- a/drivers/hid/intel-ish-hid/ishtp-hid.c +++ b/drivers/hid/intel-ish-hid/ishtp-hid.c @@ -214,7 +214,7 @@ int ishtp_hid_probe(unsigned int cur_hid_dev, if (IS_ERR(hid)) return PTR_ERR(hid); - hid_data = kzalloc_obj(*hid_data, GFP_KERNEL); + hid_data = kzalloc_obj(*hid_data); if (!hid_data) { rv = -ENOMEM; goto err_hid_data; diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c index ff60be49f2b8..44db90ad5495 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.c +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -435,7 +435,7 @@ static struct ishtp_cl_device *ishtp_bus_add_device(struct ishtp_device *dev, } spin_unlock_irqrestore(&dev->device_list_lock, flags); - device = kzalloc_obj(struct ishtp_cl_device, GFP_KERNEL); + device = kzalloc_obj(struct ishtp_cl_device); if (!device) return NULL; diff --git a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c index 717276f8460a..45ddc9a047bc 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c +++ b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c @@ -66,7 +66,7 @@ int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl) for (j = 0; j < cl->tx_ring_size; ++j) { struct ishtp_cl_tx_ring *tx_buf; - tx_buf = kzalloc_obj(struct ishtp_cl_tx_ring, GFP_KERNEL); + tx_buf = kzalloc_obj(struct ishtp_cl_tx_ring); if (!tx_buf) goto out; @@ -183,7 +183,7 @@ struct ishtp_cl_rb *ishtp_io_rb_init(struct ishtp_cl *cl) { struct ishtp_cl_rb *rb; - rb = kzalloc_obj(struct ishtp_cl_rb, GFP_KERNEL); + rb = kzalloc_obj(struct ishtp_cl_rb); if (!rb) return NULL; diff --git a/drivers/hid/intel-ish-hid/ishtp/client.c b/drivers/hid/intel-ish-hid/ishtp/client.c index f22b2f85e70f..4824f3302927 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client.c +++ b/drivers/hid/intel-ish-hid/ishtp/client.c @@ -105,7 +105,7 @@ struct ishtp_cl *ishtp_cl_allocate(struct ishtp_cl_device *cl_device) { struct ishtp_cl *cl; - cl = kmalloc_obj(struct ishtp_cl, GFP_KERNEL); + cl = kmalloc_obj(struct ishtp_cl); if (!cl) return NULL; diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c index a4f57143642c..524b53a3c87b 100644 --- a/drivers/hid/uhid.c +++ b/drivers/hid/uhid.c @@ -110,7 +110,7 @@ static int uhid_queue_event(struct uhid_device *uhid, __u32 event) unsigned long flags; struct uhid_event *ev; - ev = kzalloc_obj(*ev, GFP_KERNEL); + ev = kzalloc_obj(*ev); if (!ev) return -ENOMEM; @@ -129,7 +129,7 @@ static int uhid_hid_start(struct hid_device *hid) struct uhid_event *ev; unsigned long flags; - ev = kzalloc_obj(*ev, GFP_KERNEL); + ev = kzalloc_obj(*ev); if (!ev) return -ENOMEM; @@ -240,7 +240,7 @@ static int uhid_hid_get_report(struct hid_device *hid, unsigned char rnum, if (!READ_ONCE(uhid->running)) return -EIO; - ev = kzalloc_obj(*ev, GFP_KERNEL); + ev = kzalloc_obj(*ev); if (!ev) return -ENOMEM; @@ -282,7 +282,7 @@ static int uhid_hid_set_report(struct hid_device *hid, unsigned char rnum, if (!READ_ONCE(uhid->running) || count > UHID_DATA_MAX) return -EIO; - ev = kzalloc_obj(*ev, GFP_KERNEL); + ev = kzalloc_obj(*ev); if (!ev) return -ENOMEM; @@ -365,7 +365,7 @@ static int uhid_hid_output_raw(struct hid_device *hid, __u8 *buf, size_t count, if (count < 1 || count > UHID_DATA_MAX) return -EINVAL; - ev = kzalloc_obj(*ev, GFP_KERNEL); + ev = kzalloc_obj(*ev); if (!ev) return -ENOMEM; @@ -433,7 +433,7 @@ static int uhid_event_from_user(const char __user *buffer, size_t len, */ struct uhid_create_req_compat *compat; - compat = kzalloc_obj(*compat, GFP_KERNEL); + compat = kzalloc_obj(*compat); if (!compat) return -ENOMEM; @@ -636,7 +636,7 @@ static int uhid_char_open(struct inode *inode, struct file *file) { struct uhid_device *uhid; - uhid = kzalloc_obj(*uhid, GFP_KERNEL); + uhid = kzalloc_obj(*uhid); if (!uhid) return -ENOMEM; diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 922022a9c253..ddd5d77fb5a5 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -858,7 +858,7 @@ static int hid_alloc_buffers(struct usb_device *dev, struct hid_device *hid) &usbhid->inbuf_dma); usbhid->outbuf = usb_alloc_coherent(dev, usbhid->bufsize, GFP_KERNEL, &usbhid->outbuf_dma); - usbhid->cr = kmalloc_obj(*usbhid->cr, GFP_KERNEL); + usbhid->cr = kmalloc_obj(*usbhid->cr); usbhid->ctrlbuf = usb_alloc_coherent(dev, usbhid->bufsize, GFP_KERNEL, &usbhid->ctrlbuf_dma); if (!usbhid->inbuf || !usbhid->outbuf || !usbhid->cr || @@ -1430,7 +1430,7 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id * if (usb_string(dev, dev->descriptor.iSerialNumber, hid->uniq, 64) <= 0) hid->uniq[0] = 0; - usbhid = kzalloc_obj(*usbhid, GFP_KERNEL); + usbhid = kzalloc_obj(*usbhid); if (usbhid == NULL) { ret = -ENOMEM; goto err; diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index f3b0bb6d39df..fbf3dbc92e66 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -1531,7 +1531,7 @@ int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks) return -ENODEV; } - pidff = kzalloc_obj(*pidff, GFP_KERNEL); + pidff = kzalloc_obj(*pidff); if (!pidff) return -ENOMEM; diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 3a115ea49cd2..6378801b22c6 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c @@ -434,7 +434,7 @@ static noinline int hiddev_ioctl_usage(struct hiddev *hiddev, unsigned int cmd, struct hid_field *field; int i; - uref_multi = kmalloc_obj(struct hiddev_usage_ref_multi, GFP_KERNEL); + uref_multi = kmalloc_obj(struct hiddev_usage_ref_multi); if (!uref_multi) return -ENOMEM; uref = &uref_multi->uref; @@ -890,7 +890,7 @@ int hiddev_connect(struct hid_device *hid, unsigned int force) return -EINVAL; } - if (!(hiddev = kzalloc_obj(struct hiddev, GFP_KERNEL))) + if (!(hiddev = kzalloc_obj(struct hiddev))) return -ENOMEM; init_waitqueue_head(&hiddev->wait); diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c index d32a860c6979..6b33e6ad0846 100644 --- a/drivers/hid/usbhid/usbkbd.c +++ b/drivers/hid/usbhid/usbkbd.c @@ -241,7 +241,7 @@ static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd) return -1; if (!(kbd->new = usb_alloc_coherent(dev, 8, GFP_KERNEL, &kbd->new_dma))) return -1; - if (!(kbd->cr = kmalloc_obj(struct usb_ctrlrequest, GFP_KERNEL))) + if (!(kbd->cr = kmalloc_obj(struct usb_ctrlrequest))) return -1; if (!(kbd->leds = usb_alloc_coherent(dev, 1, GFP_KERNEL, &kbd->leds_dma))) return -1; @@ -281,7 +281,7 @@ static int usb_kbd_probe(struct usb_interface *iface, pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); maxp = usb_maxpacket(dev, pipe); - kbd = kzalloc_obj(struct usb_kbd, GFP_KERNEL); + kbd = kzalloc_obj(struct usb_kbd); input_dev = input_allocate_device(); if (!kbd || !input_dev) goto fail1; diff --git a/drivers/hid/usbhid/usbmouse.c b/drivers/hid/usbhid/usbmouse.c index da0d30de1d26..7cc4f9558e5f 100644 --- a/drivers/hid/usbhid/usbmouse.c +++ b/drivers/hid/usbhid/usbmouse.c @@ -125,7 +125,7 @@ static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_i pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); maxp = usb_maxpacket(dev, pipe); - mouse = kzalloc_obj(struct usb_mouse, GFP_KERNEL); + mouse = kzalloc_obj(struct usb_mouse); input_dev = input_allocate_device(); if (!mouse || !input_dev) goto fail1; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index e2e9f8afec11..0d1c6d90fe21 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -892,7 +892,7 @@ static int wacom_add_shared_data(struct hid_device *hdev) data = wacom_get_hdev_data(hdev); if (!data) { - data = kzalloc_obj(struct wacom_hdev_data, GFP_KERNEL); + data = kzalloc_obj(struct wacom_hdev_data); if (!data) { mutex_unlock(&wacom_udev_list_lock); return -ENOMEM; From 82206562b5094ca2057c902bf9692ade765f8452 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 11:46:12 +0200 Subject: [PATCH 167/295] Convert more 'alloc_obj' cases to default GFP_KERNEL arguments JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 Partial commit: only the driver/hid/ part commit 32a92f8c89326985e05dce8b22d3f0aa07a3e1bd Author: Linus Torvalds Date: Sat Feb 21 20:03:00 2026 -0800 Convert more 'alloc_obj' cases to default GFP_KERNEL arguments This converts some of the visually simpler cases that have been split over multiple lines. I only did the ones that are easy to verify the resulting diff by having just that final GFP_KERNEL argument on the next line. Somebody should probably do a proper coccinelle script for this, but for me the trivial script actually resulted in an assertion failure in the middle of the script. I probably had made it a bit _too_ trivial. So after fighting that far a while I decided to just do some of the syntactically simpler cases with variations of the previous 'sed' scripts. The more syntactically complex multi-line cases would mostly really want whitespace cleanup anyway. Signed-off-by: Linus Torvalds Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-google-hammer.c | 3 +-- drivers/hid/hid-haptic.c | 3 +-- drivers/hid/intel-ish-hid/ishtp/hbm.c | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index c05ec4b5fd0c..9eee26e558e9 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -59,8 +59,7 @@ static int cbas_ec_query_base(struct cros_ec_device *ec_dev, bool get_state, struct cros_ec_command *msg; int ret; - msg = kzalloc_flex(*msg, data, max(sizeof(u32), sizeof(*params)), - GFP_KERNEL); + msg = kzalloc_flex(*msg, data, max(sizeof(u32), sizeof(*params))); if (!msg) return -ENOMEM; diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c index 03b85df28aa1..b591437200b8 100644 --- a/drivers/hid/hid-haptic.c +++ b/drivers/hid/hid-haptic.c @@ -474,8 +474,7 @@ int hid_haptic_init(struct hid_device *hdev, ret = -ENOMEM; goto duration_map; } - haptic->effect = kzalloc_objs(struct hid_haptic_effect, FF_MAX_EFFECTS, - GFP_KERNEL); + haptic->effect = kzalloc_objs(struct hid_haptic_effect, FF_MAX_EFFECTS); if (!haptic->effect) { ret = -ENOMEM; goto output_queue; diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.c b/drivers/hid/intel-ish-hid/ishtp/hbm.c index 9834dbd04726..d4296dbf7eb0 100644 --- a/drivers/hid/intel-ish-hid/ishtp/hbm.c +++ b/drivers/hid/intel-ish-hid/ishtp/hbm.c @@ -34,8 +34,7 @@ static void ishtp_hbm_fw_cl_allocate(struct ishtp_device *dev) return; /* allocate storage for fw clients representation */ - clients = kzalloc_objs(struct ishtp_fw_client, dev->fw_clients_num, - GFP_KERNEL); + clients = kzalloc_objs(struct ishtp_fw_client, dev->fw_clients_num); if (!clients) { dev->dev_state = ISHTP_DEV_RESETTING; ish_hw_reset(dev); From d9bcf4afe560e86a50c88e7175c851e117eb0bf1 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 11:48:00 +0200 Subject: [PATCH 168/295] Convert remaining multi-line kmalloc_obj/flex GFP_KERNEL uses JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 Partial commit: only drivers/hid/ commit 189f164e573e18d9f8876dbd3ad8fcbe11f93037 Author: Kees Cook Date: Sat Feb 21 23:46:04 2026 -0800 Convert remaining multi-line kmalloc_obj/flex GFP_KERNEL uses Conversion performed via this Coccinelle script: // SPDX-License-Identifier: GPL-2.0-only // Options: --include-headers-for-types --all-includes --include-headers --keep-comments virtual patch @gfp depends on patch && !(file in "tools") && !(file in "samples")@ identifier ALLOC = {kmalloc_obj,kmalloc_objs,kmalloc_flex, kzalloc_obj,kzalloc_objs,kzalloc_flex, kvmalloc_obj,kvmalloc_objs,kvmalloc_flex, kvzalloc_obj,kvzalloc_objs,kvzalloc_flex}; @@ ALLOC(... - , GFP_KERNEL ) $ make coccicheck MODE=patch COCCI=gfp.cocci Build and boot tested x86_64 with Fedora 42's GCC and Clang: Linux version 6.19.0+ (user@host) (gcc (GCC) 15.2.1 20260123 (Red Hat 15.2.1-7), GNU ld version 2.44-12.fc42) #1 SMP PREEMPT_DYNAMIC 1970-01-01 Linux version 6.19.0+ (user@host) (clang version 20.1.8 (Fedora 20.1.8-4.fc42), LLD 20.1.8) #1 SMP PREEMPT_DYNAMIC 1970-01-01 Signed-off-by: Kees Cook Signed-off-by: Linus Torvalds Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index ebabccbafdfd..455d443ffbb0 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1320,8 +1320,7 @@ int hid_open_report(struct hid_device *device) end = start + size; device->collection = kzalloc_objs(struct hid_collection, - HID_DEFAULT_NUM_COLLECTIONS, - GFP_KERNEL); + HID_DEFAULT_NUM_COLLECTIONS); if (!device->collection) { ret = -ENOMEM; goto err; From 96995ddaca46cbddacb75be9e4ce3cda8bc96560 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:40 +0200 Subject: [PATCH 169/295] HID: magicmouse: fix battery reporting for Apple Magic Trackpad 2 JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 5f3518d77419255f8b12bb23c8ec22acbeb6bc5b Author: Julius Lehmann Date: Sat Feb 14 20:34:21 2026 +0100 HID: magicmouse: fix battery reporting for Apple Magic Trackpad 2 Battery reporting does not work for the Apple Magic Trackpad 2 if it is connected via USB. The current hid descriptor fixup code checks for a hid descriptor length of exactly 83 bytes. If the hid descriptor is larger, which is the case for newer apple mice, the fixup is not applied. This fix checks for hid descriptor sizes greater/equal 83 bytes which applies the fixup for newer devices as well. Signed-off-by: Julius Lehmann Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-magicmouse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 17908d52c027..9eadf3252d0d 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -990,7 +990,7 @@ static const __u8 *magicmouse_report_fixup(struct hid_device *hdev, __u8 *rdesc, */ if ((is_usb_magicmouse2(hdev->vendor, hdev->product) || is_usb_magictrackpad2(hdev->vendor, hdev->product)) && - *rsize == 83 && rdesc[46] == 0x84 && rdesc[58] == 0x85) { + *rsize >= 83 && rdesc[46] == 0x84 && rdesc[58] == 0x85) { hid_info(hdev, "fixing up magicmouse battery report descriptor\n"); *rsize = *rsize - 1; From e7f52e9308cdd5a78ff75c8832552e82ac6fe8c2 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:42 +0200 Subject: [PATCH 170/295] HID: multitouch: new class MT_CLS_EGALAX_P80H84 JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit a2e70a89fa58133521b2deae4427d35776bda935 Author: Ian Ray Date: Tue Feb 17 13:51:51 2026 +0200 HID: multitouch: new class MT_CLS_EGALAX_P80H84 Fixes: f9e82295eec1 ("HID: multitouch: add eGalaxTouch P80H84 support") Signed-off-by: Ian Ray Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-multitouch.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 7daa8f6d8187..8052b35bfd7d 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -233,6 +233,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_SMART_TECH 0x0113 #define MT_CLS_APPLE_TOUCHBAR 0x0114 #define MT_CLS_YOGABOOK9I 0x0115 +#define MT_CLS_EGALAX_P80H84 0x0116 #define MT_CLS_SIS 0x0457 #define MT_DEFAULT_MAXCONTACT 10 @@ -438,6 +439,11 @@ static const struct mt_class mt_classes[] = { MT_QUIRK_YOGABOOK9I, .export_all_inputs = true }, + { .name = MT_CLS_EGALAX_P80H84, + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_CONTACT_CNT_ACCURATE, + }, { } }; @@ -2215,8 +2221,9 @@ static const struct hid_device_id mt_devices[] = { { .driver_data = MT_CLS_EGALAX_SERIAL, MT_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C000) }, - { .driver_data = MT_CLS_EGALAX, - MT_USB_DEVICE(USB_VENDOR_ID_DWAV, + { .driver_data = MT_CLS_EGALAX_P80H84, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH_WIN_8, + USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C002) }, /* Elan devices */ From d643dba361f45b84d6db00bc8828984c070b0425 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:43 +0200 Subject: [PATCH 171/295] selftests: hid: tests: test_wacom_generic: add tests for display devices and opaque devices JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit b4f4fd947a2a6df7cf1f530dd1028d9f3849eff5 Author: Alex Tran Date: Sat Jan 31 15:57:43 2026 -0800 selftests: hid: tests: test_wacom_generic: add tests for display devices and opaque devices Verify Wacom devices set INPUT_PROP_DIRECT on display devices and INPUT_PROP_POINTER on opaque devices. Verify INPUT_PROP_POINTER is not set on display devices and INPUT_PROP_DIRECT is not set on opaque devices. Moved test_prop_pointer into TestOpaqueTablet. Created a DirectTabletTest mixin class for test_prop_direct that can be inherited by display tablet test classes.Used DirectTabletTest for TestDTH2452Tablet case. Signed-off-by: Alex Tran Tested-by: Erin Skomra Reviewed-by: Erin Skomra Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../selftests/hid/tests/test_wacom_generic.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/tools/testing/selftests/hid/tests/test_wacom_generic.py b/tools/testing/selftests/hid/tests/test_wacom_generic.py index 2d6d04f0ff80..3903f479b15b 100644 --- a/tools/testing/selftests/hid/tests/test_wacom_generic.py +++ b/tools/testing/selftests/hid/tests/test_wacom_generic.py @@ -598,18 +598,6 @@ class BaseTest: if unit_set: assert required[usage].contains(field) - def test_prop_direct(self): - """ - Todo: Verify that INPUT_PROP_DIRECT is set on display devices. - """ - pass - - def test_prop_pointer(self): - """ - Todo: Verify that INPUT_PROP_POINTER is set on opaque devices. - """ - pass - class PenTabletTest(BaseTest.TestTablet): def assertName(self, uhdev): @@ -677,6 +665,15 @@ class TestOpaqueTablet(PenTabletTest): uhdev.event(130, 240, pressure=0), [], auto_syn=False, strict=True ) + def test_prop_pointer(self): + """ + Verify that INPUT_PROP_POINTER is set and INPUT_PROP_DIRECT + is not set on opaque devices. + """ + evdev = self.uhdev.get_evdev() + assert libevdev.INPUT_PROP_POINTER in evdev.properties + assert libevdev.INPUT_PROP_DIRECT not in evdev.properties + class TestOpaqueCTLTablet(TestOpaqueTablet): def create_device(self): @@ -862,7 +859,18 @@ class TestPTHX60_Pen(TestOpaqueCTLTablet): ) -class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest): +class DirectTabletTest(): + def test_prop_direct(self): + """ + Verify that INPUT_PROP_DIRECT is set and INPUT_PROP_POINTER + is not set on display devices. + """ + evdev = self.uhdev.get_evdev() + assert libevdev.INPUT_PROP_DIRECT in evdev.properties + assert libevdev.INPUT_PROP_POINTER not in evdev.properties + + +class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest, DirectTabletTest): ContactIds = namedtuple("ContactIds", "contact_id, tracking_id, slot_num") def create_device(self): From 1b6cd4443eb320ff87904132a1873022f0df70d7 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:44 +0200 Subject: [PATCH 172/295] HID: intel-ish-hid: ipc: Add Nova Lake-H/S PCI device IDs JIRA: https://issues.redhat.com/browse/RHEL-170872 JIRA: https://redhat.atlassian.net/browse/RHEL-183865 Upstream Status: since v7.0 commit 22f8bcec5aeb05104b3eaa950cb5a345e95f0aa8 Author: Zhang Lixu Date: Tue Feb 3 08:55:07 2026 +0800 HID: intel-ish-hid: ipc: Add Nova Lake-H/S PCI device IDs Add device IDs of Nova Lake-H and Nova Lake-S into ishtp support list. Signed-off-by: Zhang Lixu Reviewed-by: Andy Shevchenko Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-ish-hid/ipc/hw-ish.h | 2 ++ drivers/hid/intel-ish-hid/ipc/pci-ish.c | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish.h b/drivers/hid/intel-ish-hid/ipc/hw-ish.h index fa5d68c36313..27389971b96c 100644 --- a/drivers/hid/intel-ish-hid/ipc/hw-ish.h +++ b/drivers/hid/intel-ish-hid/ipc/hw-ish.h @@ -39,6 +39,8 @@ #define PCI_DEVICE_ID_INTEL_ISH_PTL_H 0xE345 #define PCI_DEVICE_ID_INTEL_ISH_PTL_P 0xE445 #define PCI_DEVICE_ID_INTEL_ISH_WCL 0x4D45 +#define PCI_DEVICE_ID_INTEL_ISH_NVL_H 0xD354 +#define PCI_DEVICE_ID_INTEL_ISH_NVL_S 0x6E78 #define REVISION_ID_CHT_A0 0x6 #define REVISION_ID_CHT_Ax_SI 0x0 diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c index 1612e8cb23f0..ed3405c05e73 100644 --- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -28,11 +28,15 @@ enum ishtp_driver_data_index { ISHTP_DRIVER_DATA_LNL_M, ISHTP_DRIVER_DATA_PTL, ISHTP_DRIVER_DATA_WCL, + ISHTP_DRIVER_DATA_NVL_H, + ISHTP_DRIVER_DATA_NVL_S, }; #define ISH_FW_GEN_LNL_M "lnlm" #define ISH_FW_GEN_PTL "ptl" #define ISH_FW_GEN_WCL "wcl" +#define ISH_FW_GEN_NVL_H "nvlh" +#define ISH_FW_GEN_NVL_S "nvls" #define ISH_FIRMWARE_PATH(gen) "intel/ish/ish_" gen ".bin" #define ISH_FIRMWARE_PATH_ALL "intel/ish/ish_*.bin" @@ -47,6 +51,12 @@ static struct ishtp_driver_data ishtp_driver_data[] = { [ISHTP_DRIVER_DATA_WCL] = { .fw_generation = ISH_FW_GEN_WCL, }, + [ISHTP_DRIVER_DATA_NVL_H] = { + .fw_generation = ISH_FW_GEN_NVL_H, + }, + [ISHTP_DRIVER_DATA_NVL_S] = { + .fw_generation = ISH_FW_GEN_NVL_S, + }, }; static const struct pci_device_id ish_pci_tbl[] = { @@ -76,6 +86,8 @@ static const struct pci_device_id ish_pci_tbl[] = { {PCI_DEVICE_DATA(INTEL, ISH_PTL_H, ISHTP_DRIVER_DATA_PTL)}, {PCI_DEVICE_DATA(INTEL, ISH_PTL_P, ISHTP_DRIVER_DATA_PTL)}, {PCI_DEVICE_DATA(INTEL, ISH_WCL, ISHTP_DRIVER_DATA_WCL)}, + {PCI_DEVICE_DATA(INTEL, ISH_NVL_H, ISHTP_DRIVER_DATA_NVL_H)}, + {PCI_DEVICE_DATA(INTEL, ISH_NVL_S, ISHTP_DRIVER_DATA_NVL_S)}, {} }; MODULE_DEVICE_TABLE(pci, ish_pci_tbl); From b786f2cf293af08544957de762a4fb0920ba6ebd Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:46 +0200 Subject: [PATCH 173/295] HID: apple: Add EPOMAKER TH87 to the non-apple keyboards list JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 7c698de0dc5daa1e1a5fd1f0c6aa1b6bb2f5d867 Author: Takashi Iwai Date: Tue Feb 24 10:00:02 2026 +0100 HID: apple: Add EPOMAKER TH87 to the non-apple keyboards list EPOMAKER TH87 has the very same ID as Apple Aluminum keyboard (05ac:024f) although it doesn't work as expected in compatible way. Put three entries to the non-apple keyboards list to exclude this device: one for BT ("TH87"), one for USB ("HFD Epomaker TH87") and one for dongle ("2.4G Wireless Receiver"). Link: https://bugzilla.suse.com/show_bug.cgi?id=1258455 Signed-off-by: Takashi Iwai Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-apple.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 87189c8396f4..fc5897a6bb53 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -365,6 +365,9 @@ static const struct apple_non_apple_keyboard non_apple_keyboards[] = { { "A3R" }, { "hfd.cn" }, { "WKB603" }, + { "TH87" }, /* EPOMAKER TH87 BT mode */ + { "HFD Epomaker TH87" }, /* EPOMAKER TH87 USB mode */ + { "2.4G Wireless Receiver" }, /* EPOMAKER TH87 dongle */ }; static bool apple_is_non_apple_keyboard(struct hid_device *hdev) From b077da654122eee5a4e40b0ee7c9f93f16b82e63 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:48 +0200 Subject: [PATCH 174/295] HID: multitouch: Keep latency normal on deactivate for reactivation gesture JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit ec3070f01fa30f2c5547d645dbb76174304bf0e4 Author: Werner Sembach Date: Thu Jan 8 17:09:54 2026 +0100 HID: multitouch: Keep latency normal on deactivate for reactivation gesture Uniwill devices have a built in gesture in the touchpad to de- and reactivate it by double taping the upper left corner. This gesture stops working when latency is set to high, so this patch keeps the latency on normal. Cc: stable@vger.kernel.org Signed-off-by: Werner Sembach [jkosina@suse.com: change bit from 24 to 25] [jkosina@suse.com: update shortlog] Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-multitouch.c | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 8052b35bfd7d..b8a748bbf0fd 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -77,6 +77,7 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_ORIENTATION_INVERT BIT(22) #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) #define MT_QUIRK_YOGABOOK9I BIT(24) +#define MT_QUIRK_KEEP_LATENCY_ON_CLOSE BIT(25) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 @@ -214,6 +215,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_WIN_8_DISABLE_WAKEUP 0x0016 #define MT_CLS_WIN_8_NO_STICKY_FINGERS 0x0017 #define MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU 0x0018 +#define MT_CLS_WIN_8_KEEP_LATENCY_ON_CLOSE 0x0019 /* vendor specific classes */ #define MT_CLS_3M 0x0101 @@ -335,6 +337,15 @@ static const struct mt_class mt_classes[] = { MT_QUIRK_CONTACT_CNT_ACCURATE | MT_QUIRK_WIN8_PTP_BUTTONS, .export_all_inputs = true }, + { .name = MT_CLS_WIN_8_KEEP_LATENCY_ON_CLOSE, + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_STICKY_FINGERS | + MT_QUIRK_WIN8_PTP_BUTTONS | + MT_QUIRK_KEEP_LATENCY_ON_CLOSE, + .export_all_inputs = true }, /* * vendor specific classes @@ -855,7 +866,8 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi, if ((cls->name == MT_CLS_WIN_8 || cls->name == MT_CLS_WIN_8_FORCE_MULTI_INPUT || cls->name == MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU || - cls->name == MT_CLS_WIN_8_DISABLE_WAKEUP) && + cls->name == MT_CLS_WIN_8_DISABLE_WAKEUP || + cls->name == MT_CLS_WIN_8_KEEP_LATENCY_ON_CLOSE) && (field->application == HID_DG_TOUCHPAD || field->application == HID_DG_TOUCHSCREEN)) app->quirks |= MT_QUIRK_CONFIDENCE; @@ -1768,7 +1780,8 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) int ret; if (td->is_haptic_touchpad && (td->mtclass.name == MT_CLS_WIN_8 || - td->mtclass.name == MT_CLS_WIN_8_FORCE_MULTI_INPUT)) { + td->mtclass.name == MT_CLS_WIN_8_FORCE_MULTI_INPUT || + td->mtclass.name == MT_CLS_WIN_8_KEEP_LATENCY_ON_CLOSE)) { if (hid_haptic_input_configured(hdev, td->haptic, hi) == 0) td->is_haptic_touchpad = false; } else { @@ -2081,7 +2094,12 @@ static void mt_on_hid_hw_open(struct hid_device *hdev) static void mt_on_hid_hw_close(struct hid_device *hdev) { - mt_set_modes(hdev, HID_LATENCY_HIGH, TOUCHPAD_REPORT_NONE); + struct mt_device *td = hid_get_drvdata(hdev); + + if (td->mtclass.quirks & MT_QUIRK_KEEP_LATENCY_ON_CLOSE) + mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_NONE); + else + mt_set_modes(hdev, HID_LATENCY_HIGH, TOUCHPAD_REPORT_NONE); } /* @@ -2468,6 +2486,14 @@ static const struct hid_device_id mt_devices[] = { MT_USB_DEVICE(USB_VENDOR_ID_UNITEC, USB_DEVICE_ID_UNITEC_USB_TOUCH_0A19) }, + /* Uniwill touchpads */ + { .driver_data = MT_CLS_WIN_8_KEEP_LATENCY_ON_CLOSE, + HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, + USB_VENDOR_ID_PIXART, 0x0255) }, + { .driver_data = MT_CLS_WIN_8_KEEP_LATENCY_ON_CLOSE, + HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, + USB_VENDOR_ID_PIXART, 0x0274) }, + /* VTL panels */ { .driver_data = MT_CLS_VTL, MT_USB_DEVICE(USB_VENDOR_ID_VTL, From ce1738cd2918b49b7cd0aa8981617fb64ea8c0f7 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:52 +0200 Subject: [PATCH 175/295] HID: mcp2221: cancel last I2C command on read error JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit e31b556c0ba21f20c298aa61181b96541140b7b9 Author: Romain Sioen Date: Fri Feb 6 17:32:58 2026 +0100 HID: mcp2221: cancel last I2C command on read error When an I2C SMBus read operation fails, the MCP2221 internal state machine may not reset correctly, causing subsequent transactions to fail. By adding a short delay and explicitly cancelling the last command, we ensure the device is ready for the next operation. Fix an issue where i2cdetect was not able to detect all devices correctly on the bus. Signed-off-by: Romain Sioen Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-mcp2221.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c index 157c363ea9ba..17110eefc3b3 100644 --- a/drivers/hid/hid-mcp2221.c +++ b/drivers/hid/hid-mcp2221.c @@ -353,6 +353,8 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp, usleep_range(90, 100); retries++; } else { + usleep_range(980, 1000); + mcp_cancel_last_cmd(mcp); return ret; } } else { From 54554a74406a324d4e919a8ad60fd5926a41752c Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:56 +0200 Subject: [PATCH 176/295] HID: input: Drop Asus UX550* touchscreen ignore battery quirks JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 487b23afaf4b258a70d3e4a8febf66f09850e75f Author: Hans de Goede Date: Sat Feb 28 15:52:57 2026 +0100 HID: input: Drop Asus UX550* touchscreen ignore battery quirks Drop the Asus UX550* touchscreen ignore battery quirks, there is a blanket HID_BATTERY_QUIRK_IGNORE for all USB_VENDOR_ID_ELAN USB touchscreens now, so these are just a duplicate of those. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 2 -- drivers/hid/hid-input.c | 4 ---- 2 files changed, 6 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 3e299a30dcde..f2d1c6869541 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -453,8 +453,6 @@ #define USB_DEVICE_ID_TOSHIBA_CLICK_L9W 0x0401 #define USB_DEVICE_ID_HP_X2 0x074d #define USB_DEVICE_ID_HP_X2_10_COVER 0x0755 -#define USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN 0x2544 -#define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN 0x2706 #define I2C_DEVICE_ID_CHROMEBOOK_TROGDOR_POMPOM 0x2F81 #define USB_VENDOR_ID_ELECOM 0x056e diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index d5308adb2894..eb64b66d28a6 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -386,10 +386,6 @@ static const struct hid_device_id hid_battery_quirks[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DINOVO_EDGE_KBD), HID_BATTERY_QUIRK_IGNORE }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN), - HID_BATTERY_QUIRK_IGNORE }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN), - HID_BATTERY_QUIRK_IGNORE }, { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L), HID_BATTERY_QUIRK_AVOID_QUERY }, { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW), From 5d51f454065f52997307936a04bc798673ae761b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:57 +0200 Subject: [PATCH 177/295] HID: input: Add HID_BATTERY_QUIRK_DYNAMIC for Elan touchscreens JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 227312b4a65c373d5d8b4683b7fc36203fedc516 Author: Hans de Goede Date: Sat Feb 28 15:52:58 2026 +0100 HID: input: Add HID_BATTERY_QUIRK_DYNAMIC for Elan touchscreens Elan touchscreens have a HID-battery device for the stylus which is always there even if there is no stylus. This is causing upower to report an empty battery for the stylus and some desktop-environments will show a notification about this, which is quite annoying. Because of this the HID-battery is being ignored on all Elan I2c and USB touchscreens, but this causes there to be no battery reporting for the stylus at all. This adds a new HID_BATTERY_QUIRK_DYNAMIC and uses these for the Elan touchscreens. This new quirks causes the present value of the battery to start at 0, which will make userspace ignore it and only sets present to 1 after receiving a battery input report which only happens when the stylus gets in range. Reported-by: ggrundik@gmail.com Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221118 Signed-off-by: Hans de Goede Reviewed-by: Sebastian Reichel Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-input.c | 14 +++++++++++--- include/linux/hid.h | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index eb64b66d28a6..9475b7e9da43 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -354,6 +354,7 @@ static enum power_supply_property hidinput_battery_props[] = { #define HID_BATTERY_QUIRK_FEATURE (1 << 1) /* ask for feature report */ #define HID_BATTERY_QUIRK_IGNORE (1 << 2) /* completely ignore the battery */ #define HID_BATTERY_QUIRK_AVOID_QUERY (1 << 3) /* do not query the battery */ +#define HID_BATTERY_QUIRK_DYNAMIC (1 << 4) /* report present only after life signs */ static const struct hid_device_id hid_battery_quirks[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, @@ -398,8 +399,8 @@ static const struct hid_device_id hid_battery_quirks[] = { * Elan HID touchscreens seem to all report a non present battery, * set HID_BATTERY_QUIRK_IGNORE for all Elan I2C and USB HID devices. */ - { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_IGNORE }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_IGNORE }, + { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_DYNAMIC }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_DYNAMIC }, {} }; @@ -456,11 +457,14 @@ static int hidinput_get_battery_property(struct power_supply *psy, int ret = 0; switch (prop) { - case POWER_SUPPLY_PROP_PRESENT: case POWER_SUPPLY_PROP_ONLINE: val->intval = 1; break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = dev->battery_present; + break; + case POWER_SUPPLY_PROP_CAPACITY: if (dev->battery_status != HID_BATTERY_REPORTED && !dev->battery_avoid_query) { @@ -573,6 +577,8 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, if (quirks & HID_BATTERY_QUIRK_AVOID_QUERY) dev->battery_avoid_query = true; + dev->battery_present = (quirks & HID_BATTERY_QUIRK_DYNAMIC) ? false : true; + dev->battery = power_supply_register(&dev->dev, psy_desc, &psy_cfg); if (IS_ERR(dev->battery)) { error = PTR_ERR(dev->battery); @@ -628,6 +634,7 @@ static void hidinput_update_battery(struct hid_device *dev, unsigned int usage, return; if (hidinput_update_battery_charge_status(dev, usage, value)) { + dev->battery_present = true; power_supply_changed(dev->battery); return; } @@ -643,6 +650,7 @@ static void hidinput_update_battery(struct hid_device *dev, unsigned int usage, if (dev->battery_status != HID_BATTERY_REPORTED || capacity != dev->battery_capacity || ktime_after(ktime_get_coarse(), dev->battery_ratelimit_time)) { + dev->battery_present = true; dev->battery_capacity = capacity; dev->battery_status = HID_BATTERY_REPORTED; dev->battery_ratelimit_time = diff --git a/include/linux/hid.h b/include/linux/hid.h index 2990b9f94cb5..31324609af4d 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -682,6 +682,7 @@ struct hid_device { __s32 battery_charge_status; enum hid_battery_status battery_status; bool battery_avoid_query; + bool battery_present; ktime_t battery_ratelimit_time; #endif From f3aa2fcb15f64578adc4fce7691b800b0e9cba5f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:17:59 +0200 Subject: [PATCH 178/295] HID: logitech-hidpp: Enable MX Master 4 over bluetooth JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 70031e70ca15ede6a39db4d978e53a6cc720d454 Author: Adrian Freund Date: Wed Mar 11 20:19:33 2026 +0100 HID: logitech-hidpp: Enable MX Master 4 over bluetooth The Logitech MX Master 4 can be connected over bluetooth or through a Logitech Bolt receiver. This change adds support for non-standard HID features, such as high resolution scrolling when the mouse is connected over bluetooth. Because no Logitech Bolt receiver driver exists yet those features won't be available when the mouse is connected through the receiver. Signed-off-by: Adrian Freund Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index d40932809ce1..cf07660bd85b 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -4668,6 +4668,8 @@ static const struct hid_device_id hidpp_devices[] = { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb038) }, { /* Slim Solar+ K980 Keyboard over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb391) }, + { /* MX Master 4 mouse over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb042) }, {} }; From 17c4ff179e1e30aa57be0f963a5e2aab4292a4a4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:18:01 +0200 Subject: [PATCH 179/295] HID: appletb-kbd: add .resume method in PM JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 1965445e13c09b79932ca8154977b4408cb9610c Author: Aditya Garg Date: Tue Feb 17 02:54:46 2026 +0530 HID: appletb-kbd: add .resume method in PM Upon resuming from suspend, the Touch Bar driver was missing a resume method in order to restore the original mode the Touch Bar was on before suspending. It is the same as the reset_resume method. [jkosina@suse.com: rebased on top of the pm_ptr() conversion] Cc: stable@vger.kernel.org Signed-off-by: Aditya Garg Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-appletb-kbd.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-appletb-kbd.c b/drivers/hid/hid-appletb-kbd.c index a1db3b3d0667..0fdc0968b9ef 100644 --- a/drivers/hid/hid-appletb-kbd.c +++ b/drivers/hid/hid-appletb-kbd.c @@ -476,7 +476,7 @@ static int appletb_kbd_suspend(struct hid_device *hdev, pm_message_t msg) return 0; } -static int appletb_kbd_reset_resume(struct hid_device *hdev) +static int appletb_kbd_resume(struct hid_device *hdev) { struct appletb_kbd *kbd = hid_get_drvdata(hdev); @@ -500,7 +500,8 @@ static struct hid_driver appletb_kbd_hid_driver = { .event = appletb_kbd_hid_event, .input_configured = appletb_kbd_input_configured, .suspend = pm_ptr(appletb_kbd_suspend), - .reset_resume = pm_ptr(appletb_kbd_reset_resume), + .resume = pm_ptr(appletb_kbd_resume), + .reset_resume = pm_ptr(appletb_kbd_resume), .driver.dev_groups = appletb_kbd_groups, }; module_hid_driver(appletb_kbd_hid_driver); From 2181e4f93be6646a360ad37445e0aeb6d3d879cb Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:18:03 +0200 Subject: [PATCH 180/295] HID: intel-thc-hid: Set HID_PHYS with PCI BDF JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 4bc7bc457922742d38915458e630195e761c1efd Author: Daniel Schaefer Date: Fri Mar 13 21:39:25 2026 +0800 HID: intel-thc-hid: Set HID_PHYS with PCI BDF Currently HID_PHYS is empty, which means userspace tools (e.g. fwupd) that depend on it for distinguishing the devices, are unable to do so. Other drivers like i2c-hid, usbhid, surface-hid, all populate it. With this change it's set to, for example: HID_PHYS=0000:00:10.0 Each function has just a single HID device, as far as I can tell, so there is no need to add a suffix. Tested with fwupd 2.1.1, can avoid https://github.com/fwupd/fwupd/pull/9995 Cc: Even Xu Cc: Xinpeng Sun Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: Sakari Ailus Signed-off-by: Daniel Schaefer Reviewed-by: Even Xu Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c | 1 + drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c index f9fcb398673b..8075992e8732 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c @@ -127,6 +127,7 @@ int quicki2c_hid_probe(struct quicki2c_device *qcdev) hid->product = le16_to_cpu(qcdev->dev_desc.product_id); snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "quicki2c-hid", hid->vendor, hid->product); + strscpy(hid->phys, dev_name(qcdev->dev), sizeof(hid->phys)); ret = hid_add_device(hid); if (ret) { diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c index 82c72bfa2795..91d5807b4a83 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c @@ -118,6 +118,7 @@ int quickspi_hid_probe(struct quickspi_device *qsdev) hid->product = le16_to_cpu(qsdev->dev_desc.product_id); snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "quickspi-hid", hid->vendor, hid->product); + strscpy(hid->phys, dev_name(qsdev->dev), sizeof(hid->phys)); ret = hid_add_device(hid); if (ret) { From 81d4a409a607e560b60800a255c71f257a716b47 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:18:05 +0200 Subject: [PATCH 181/295] HID: core: Mitigate potential OOB by removing bogus memset() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-43048 commit 0a3fe972a7cb1404f693d6f1711f32bc1d244b1c Author: Lee Jones Date: Mon Mar 9 14:59:29 2026 +0000 HID: core: Mitigate potential OOB by removing bogus memset() The memset() in hid_report_raw_event() has the good intention of clearing out bogus data by zeroing the area from the end of the incoming data string to the assumed end of the buffer. However, as we have previously seen, doing so can easily result in OOB reads and writes in the subsequent thread of execution. The current suggestion from one of the HID maintainers is to remove the memset() and simply return if the incoming event buffer size is not large enough to fill the associated report. Suggested-by Benjamin Tissoires Signed-off-by: Lee Jones [bentiss: changed the return value] Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 455d443ffbb0..b1036a794a57 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2057,9 +2057,10 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 * rsize = max_buffer_size; if (csize < rsize) { - dbg_hid("report %d is too short, (%d < %d)\n", report->id, - csize, rsize); - memset(cdata + csize, 0, rsize - csize); + hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %d)\n", + report->id, rsize, csize); + ret = -EINVAL; + goto out; } if ((hid->claimed & HID_CLAIMED_HIDDEV) && hid->hiddev_report_event) From 9345473c2d1aae231f34beaa2c6240ce72a3ae19 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:18:06 +0200 Subject: [PATCH 182/295] selftests/hid: fix compilation when bpf_wq and hid_device are not exported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 5d4c6c132ea9a967d48890dd03e6a786c060e968 Author: Benjamin Tissoires Date: Fri Mar 13 08:40:24 2026 +0100 selftests/hid: fix compilation when bpf_wq and hid_device are not exported This can happen in situations when CONFIG_HID_SUPPORT is set to no, or some complex situations where struct bpf_wq is not exported. So do the usual dance of hiding them before including vmlinux.h, and then redefining them and make use of CO-RE to have the correct offsets. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202603111558.KLCIxsZB-lkp@intel.com/ Fixes: fe8d561db3e8 ("selftests/hid: add wq test for hid_bpf_input_report()") Cc: stable@vger.kernel.org Acked-by: Jiri Kosina Reviewed-by: Thomas Weißschuh Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/progs/hid_bpf_helpers.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tools/testing/selftests/hid/progs/hid_bpf_helpers.h b/tools/testing/selftests/hid/progs/hid_bpf_helpers.h index 531228b849da..125a975f32f9 100644 --- a/tools/testing/selftests/hid/progs/hid_bpf_helpers.h +++ b/tools/testing/selftests/hid/progs/hid_bpf_helpers.h @@ -6,8 +6,10 @@ #define __HID_BPF_HELPERS_H /* "undefine" structs and enums in vmlinux.h, because we "override" them below */ +#define bpf_wq bpf_wq___not_used #define hid_bpf_ctx hid_bpf_ctx___not_used #define hid_bpf_ops hid_bpf_ops___not_used +#define hid_device hid_device___not_used #define hid_report_type hid_report_type___not_used #define hid_class_request hid_class_request___not_used #define hid_bpf_attach_flags hid_bpf_attach_flags___not_used @@ -27,8 +29,10 @@ #include "vmlinux.h" +#undef bpf_wq #undef hid_bpf_ctx #undef hid_bpf_ops +#undef hid_device #undef hid_report_type #undef hid_class_request #undef hid_bpf_attach_flags @@ -55,6 +59,14 @@ enum hid_report_type { HID_REPORT_TYPES, }; +struct hid_device { + unsigned int id; +} __attribute__((preserve_access_index)); + +struct bpf_wq { + __u64 __opaque[2]; +}; + struct hid_bpf_ctx { struct hid_device *hid; __u32 allocated_size; From 089313211adcc832c103d1586f31a58daba51ccf Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:18:08 +0200 Subject: [PATCH 183/295] HID: bpf: prevent buffer overflow in hid_hw_request JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-31401 commit 2b658c1c442ec1cd9eec5ead98d68662c40fe645 Author: Benjamin Tissoires Date: Fri Mar 13 08:40:25 2026 +0100 HID: bpf: prevent buffer overflow in hid_hw_request right now the returned value is considered to be always valid. However, when playing with HID-BPF, the return value can be arbitrary big, because it's the return value of dispatch_hid_bpf_raw_requests(), which calls the struct_ops and we have no guarantees that the value makes sense. Fixes: 8bd0488b5ea5 ("HID: bpf: add HID-BPF hooks for hid_hw_raw_requests") Cc: stable@vger.kernel.org Acked-by: Jiri Kosina Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/hid_bpf_dispatch.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index f3d15994ca1e..50c7b45c59e3 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -444,6 +444,8 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz, (u64)(long)ctx, true); /* prevent infinite recursions */ + if (ret > size) + ret = size; if (ret > 0) memcpy(buf, dma_data, ret); From d834920fd79fce1d633d656c86d73a29e2e52d19 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:18:10 +0200 Subject: [PATCH 184/295] HID: logitech-hidpp: Prevent use-after-free on force feedback initialisation failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-43049 commit f7a4c78bfeb320299c1b641500fe7761eadbd101 Author: Lee Jones Date: Fri Feb 27 10:09:38 2026 +0000 HID: logitech-hidpp: Prevent use-after-free on force feedback initialisation failure Presently, if the force feedback initialisation fails when probing the Logitech G920 Driving Force Racing Wheel for Xbox One, an error number will be returned and propagated before the userspace infrastructure (sysfs and /dev/input) has been torn down. If userspace ignores the errors and continues to use its references to these dangling entities, a UAF will promptly follow. We have 2 options; continue to return the error, but ensure that all of the infrastructure is torn down accordingly or continue to treat this condition as a warning by emitting the message but returning success. It is thought that the original author's intention was to emit the warning but keep the device functional, less the force feedback feature, so let's go with that. Signed-off-by: Lee Jones Reviewed-by: Günther Noack Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index cf07660bd85b..d1dea7297712 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -4487,10 +4487,12 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) if (!ret) ret = hidpp_ff_init(hidpp, &data); - if (ret) + if (ret) { hid_warn(hidpp->hid_dev, "Unable to initialize force feedback support, errno %d\n", ret); + ret = 0; + } } /* From edc3b2021e7046c8931936488a4e0be3db55ccbc Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:18:12 +0200 Subject: [PATCH 185/295] HID: multitouch: Check to ensure report responses match the request JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-43047 commit e716edafedad4952fe3a4a273d2e039a84e8681a Author: Lee Jones Date: Fri Feb 27 16:30:25 2026 +0000 HID: multitouch: Check to ensure report responses match the request It is possible for a malicious (or clumsy) device to respond to a specific report's feature request using a completely different report ID. This can cause confusion in the HID core resulting in nasty side-effects such as OOB writes. Add a check to ensure that the report ID in the response, matches the one that was requested. If it doesn't, omit reporting the raw event and return early. Signed-off-by: Lee Jones Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-multitouch.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index b8a748bbf0fd..e82a3c4e5b44 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -526,12 +526,19 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) dev_warn(&hdev->dev, "failed to fetch feature %d\n", report->id); } else { + /* The report ID in the request and the response should match */ + if (report->id != buf[0]) { + hid_err(hdev, "Returned feature report did not match the request\n"); + goto free; + } + ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, buf, size, 0); if (ret) dev_warn(&hdev->dev, "failed to report feature\n"); } +free: kfree(buf); } From 070ed317c3c25e3cc30b5069d919ad526683a585 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:18:13 +0200 Subject: [PATCH 186/295] HID: Intel-thc-hid: Intel-quicki2c: Add NVL Device IDs JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit d56c9cca2e7f8f0ad91f37abd4e6357c2318b0db Author: Even Xu Date: Tue Mar 17 13:56:28 2026 +0800 HID: Intel-thc-hid: Intel-quicki2c: Add NVL Device IDs Add Nova Lake THC QuickI2C device IDs to support list. Signed-off-by: Even Xu Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c | 7 +++++++ drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c index f178017352ba..46d3e9a01999 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c @@ -26,6 +26,11 @@ static struct quicki2c_ddata ptl_ddata = { .max_interrupt_delay = MAX_RX_INTERRUPT_DELAY, }; +static struct quicki2c_ddata nvl_ddata = { + .max_detect_size = MAX_RX_DETECT_SIZE_NVL, + .max_interrupt_delay = MAX_RX_INTERRUPT_DELAY, +}; + /* THC QuickI2C ACPI method to get device properties */ /* HIDI2C device method */ static guid_t i2c_hid_guid = @@ -1032,6 +1037,8 @@ static const struct pci_device_id quicki2c_pci_tbl[] = { { PCI_DEVICE_DATA(INTEL, THC_PTL_U_DEVICE_ID_I2C_PORT2, &ptl_ddata) }, { PCI_DEVICE_DATA(INTEL, THC_WCL_DEVICE_ID_I2C_PORT1, &ptl_ddata) }, { PCI_DEVICE_DATA(INTEL, THC_WCL_DEVICE_ID_I2C_PORT2, &ptl_ddata) }, + { PCI_DEVICE_DATA(INTEL, THC_NVL_H_DEVICE_ID_I2C_PORT1, &nvl_ddata) }, + { PCI_DEVICE_DATA(INTEL, THC_NVL_H_DEVICE_ID_I2C_PORT2, &nvl_ddata) }, { } }; MODULE_DEVICE_TABLE(pci, quicki2c_pci_tbl); diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h index 33a1e3db1cb2..61dbdece59a1 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h @@ -15,6 +15,8 @@ #define PCI_DEVICE_ID_INTEL_THC_PTL_U_DEVICE_ID_I2C_PORT2 0xE44A #define PCI_DEVICE_ID_INTEL_THC_WCL_DEVICE_ID_I2C_PORT1 0x4D48 #define PCI_DEVICE_ID_INTEL_THC_WCL_DEVICE_ID_I2C_PORT2 0x4D4A +#define PCI_DEVICE_ID_INTEL_THC_NVL_H_DEVICE_ID_I2C_PORT1 0xD348 +#define PCI_DEVICE_ID_INTEL_THC_NVL_H_DEVICE_ID_I2C_PORT2 0xD34A /* Packet size value, the unit is 16 bytes */ #define MAX_PACKET_SIZE_VALUE_LNL 256 @@ -40,6 +42,8 @@ /* PTL Max packet size detection capability is 255 Bytes */ #define MAX_RX_DETECT_SIZE_PTL 255 +/* NVL Max packet size detection capability is 64K Bytes */ +#define MAX_RX_DETECT_SIZE_NVL 65535 /* Max interrupt delay capability is 2.56ms */ #define MAX_RX_INTERRUPT_DELAY 256 From ce4d1a6da1048bb8b8e63781a58d8c2b4307a956 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:18:15 +0200 Subject: [PATCH 187/295] HID: Intel-thc-hid: Intel-quickspi: Add NVL Device IDs JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 48e91af0cbe942d50ef6257d850accdca1d01378 Author: Even Xu Date: Tue Mar 17 13:56:29 2026 +0800 HID: Intel-thc-hid: Intel-quickspi: Add NVL Device IDs Add Nova Lake THC QuickSPI device IDs to support list. Signed-off-by: Even Xu Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c | 6 ++++++ drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c index ad6bd59963b2..b6a69995692c 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c @@ -37,6 +37,10 @@ struct quickspi_driver_data arl = { .max_packet_size_value = MAX_PACKET_SIZE_VALUE_MTL, }; +struct quickspi_driver_data nvl = { + .max_packet_size_value = MAX_PACKET_SIZE_VALUE_LNL, +}; + /* THC QuickSPI ACPI method to get device properties */ /* HIDSPI Method: {6e2ac436-0fcf-41af-a265-b32a220dcfab} */ static guid_t hidspi_guid = @@ -982,6 +986,8 @@ static const struct pci_device_id quickspi_pci_tbl[] = { {PCI_DEVICE_DATA(INTEL, THC_WCL_DEVICE_ID_SPI_PORT2, &ptl), }, {PCI_DEVICE_DATA(INTEL, THC_ARL_DEVICE_ID_SPI_PORT1, &arl), }, {PCI_DEVICE_DATA(INTEL, THC_ARL_DEVICE_ID_SPI_PORT2, &arl), }, + {PCI_DEVICE_DATA(INTEL, THC_NVL_H_DEVICE_ID_SPI_PORT1, &nvl), }, + {PCI_DEVICE_DATA(INTEL, THC_NVL_H_DEVICE_ID_SPI_PORT2, &nvl), }, {} }; MODULE_DEVICE_TABLE(pci, quickspi_pci_tbl); diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h index c30e1a42eb09..bf5e18f5a5f4 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h @@ -23,6 +23,8 @@ #define PCI_DEVICE_ID_INTEL_THC_WCL_DEVICE_ID_SPI_PORT2 0x4D4B #define PCI_DEVICE_ID_INTEL_THC_ARL_DEVICE_ID_SPI_PORT1 0x7749 #define PCI_DEVICE_ID_INTEL_THC_ARL_DEVICE_ID_SPI_PORT2 0x774B +#define PCI_DEVICE_ID_INTEL_THC_NVL_H_DEVICE_ID_SPI_PORT1 0xD349 +#define PCI_DEVICE_ID_INTEL_THC_NVL_H_DEVICE_ID_SPI_PORT2 0xD34B /* HIDSPI special ACPI parameters DSM methods */ #define ACPI_QUICKSPI_REVISION_NUM 2 From 7e30290acdbd0f978ba40407d75fa629d7944461 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:18:17 +0200 Subject: [PATCH 188/295] HID: roccat: fix use-after-free in roccat_report_event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 CVE: CVE-2026-43111 commit d802d848308b35220f21a8025352f0c0aba15c12 Author: Benoît Sevens Date: Mon Mar 23 16:11:07 2026 +0000 HID: roccat: fix use-after-free in roccat_report_event roccat_report_event() iterates over the device->readers list without holding the readers_lock. This allows a concurrent roccat_release() to remove and free a reader while it's still being accessed, leading to a use-after-free. Protect the readers list traversal with the readers_lock mutex. Signed-off-by: Benoît Sevens Reviewed-by: Silvan Jegen Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-roccat.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c index fd0ea52f7cba..d6fff53d4ee7 100644 --- a/drivers/hid/hid-roccat.c +++ b/drivers/hid/hid-roccat.c @@ -257,6 +257,7 @@ int roccat_report_event(int minor, u8 const *data) if (!new_value) return -ENOMEM; + mutex_lock(&device->readers_lock); mutex_lock(&device->cbuf_lock); report = &device->cbuf[device->cbuf_end]; @@ -279,6 +280,7 @@ int roccat_report_event(int minor, u8 const *data) } mutex_unlock(&device->cbuf_lock); + mutex_unlock(&device->readers_lock); wake_up_interruptible(&device->wait); return 0; From a4f52781fdcf1c9c499878f430db22fd46ba2bdb Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:18:19 +0200 Subject: [PATCH 189/295] HID: quirks: add HID_QUIRK_ALWAYS_POLL for 8BitDo Pro 3 JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 532743944324a873bbaf8620fcabcd0e69e30c36 Author: leo vriska Date: Wed Mar 4 13:36:59 2026 -0500 HID: quirks: add HID_QUIRK_ALWAYS_POLL for 8BitDo Pro 3 According to a mailing list report [1], this controller's predecessor has the same issue. However, it uses the xpad driver instead of HID, so this quirk wouldn't apply. [1]: https://lore.kernel.org/linux-input/unufo3$det$1@ciao.gmane.io/ Signed-off-by: leo vriska Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 3 +++ drivers/hid/hid-quirks.c | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index f2d1c6869541..881f666ab513 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -22,6 +22,9 @@ #define USB_DEVICE_ID_3M2256 0x0502 #define USB_DEVICE_ID_3M3266 0x0506 +#define USB_VENDOR_ID_8BITDO 0x2dc8 +#define USB_DEVICE_ID_8BITDO_PRO_3 0x6009 + #define USB_VENDOR_ID_A4TECH 0x09da #define USB_DEVICE_ID_A4TECH_WCP32PU 0x0006 #define USB_DEVICE_ID_A4TECH_X5_005D 0x000a diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index edc4339adb50..02f7db5c1056 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -25,6 +25,7 @@ */ static const struct hid_device_id hid_quirks[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_8BITDO, USB_DEVICE_ID_8BITDO_PRO_3), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD), HID_QUIRK_BADPAD }, { HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR), HID_QUIRK_BADPAD }, { HID_USB_DEVICE(USB_VENDOR_ID_ADATA_XPG, USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE), HID_QUIRK_ALWAYS_POLL }, From 823b0d8ff79c01150f5997547854b79eb9ac4d83 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:18:21 +0200 Subject: [PATCH 190/295] HID: amd_sfh: don't log error when device discovery fails with -EOPNOTSUPP JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 743677a8cb30b09f16a7f167f497c2c927891b5a Author: Maximilian Pezzullo Date: Wed Mar 4 09:25:22 2026 +0100 HID: amd_sfh: don't log error when device discovery fails with -EOPNOTSUPP When sensor discovery fails on systems without AMD SFH sensors, the code already emits a warning via dev_warn() in amd_sfh_hid_client_init(). The subsequent dev_err() in sfh_init_work() for the same -EOPNOTSUPP return value is redundant and causes unnecessary alarm. Suppress the dev_err() for -EOPNOTSUPP to avoid confusing users who have no AMD SFH sensors. Fixes: 2105e8e00da4 ("HID: amd_sfh: Improve boot time when SFH is available") Reported-by: Casey Croy Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221099 Signed-off-by: Maximilian Pezzullo Acked-by: Basavaraj Natikar Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/amd-sfh-hid/amd_sfh_pcie.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c index 1d9f955573aa..4b81cebdc335 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c @@ -413,7 +413,8 @@ static void sfh_init_work(struct work_struct *work) rc = amd_sfh_hid_client_init(mp2); if (rc) { amd_sfh_clear_intr(mp2); - dev_err(&pdev->dev, "amd_sfh_hid_client_init failed err %d\n", rc); + if (rc != -EOPNOTSUPP) + dev_err(&pdev->dev, "amd_sfh_hid_client_init failed err %d\n", rc); return; } From 32f382794c73e5586a2ca1f5a91afdb41fe9354d Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 17:18:23 +0200 Subject: [PATCH 191/295] HID: Kysona: Add support for VXE Dragonfly R1 Pro JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 1a2d30aa73140e008e62dc5898e4c2c3b59b35e4 Author: Lode Willems Date: Tue Mar 24 21:43:06 2026 +0100 HID: Kysona: Add support for VXE Dragonfly R1 Pro Apparently this same protocol is used by more mice from different brands. This patch adds support for the VXE Dragonfly R1 Pro. Tested-by: Dominykas Svetikas Signed-off-by: Lode Willems Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 4 ++++ drivers/hid/hid-kysona.c | 2 ++ 2 files changed, 6 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 881f666ab513..474539a61c97 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1472,6 +1472,10 @@ #define USB_VENDOR_ID_VTL 0x0306 #define USB_DEVICE_ID_VTL_MULTITOUCH_FF3F 0xff3f +#define USB_VENDOR_ID_VXE 0x3554 +#define USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_DONGLE 0xf58a +#define USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_WIRED 0xf58c + #define USB_VENDOR_ID_WACOM 0x056a #define USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH 0x81 #define USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH 0x00BD diff --git a/drivers/hid/hid-kysona.c b/drivers/hid/hid-kysona.c index 09bfe30d02cb..ccbd8380064e 100644 --- a/drivers/hid/hid-kysona.c +++ b/drivers/hid/hid-kysona.c @@ -272,6 +272,8 @@ static void kysona_remove(struct hid_device *hdev) static const struct hid_device_id kysona_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_DONGLE) }, { HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_VXE, USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_DONGLE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_VXE, USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_WIRED) }, { } }; MODULE_DEVICE_TABLE(hid, kysona_devices); From 7da72fb99f8766b653a0921bc0cab3fa202cd1d8 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 18:26:57 +0200 Subject: [PATCH 192/295] [redhat] New configs in drivers/hid JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: RHEL only, https://gitlab.com/cki-project/kernel-ark commit 596c35370dd00d8c84d167938209af8746c83f47 Author: Fedora Kernel Team Date: Thu Feb 12 03:01:00 2026 +0000 [redhat] New configs in drivers/hid Hi, As part of the ongoing rebase effort, the following configuration options need to be reviewed. As a reminder, the ARK configuration flow involves moving unreviewed configuration options from the pending directory to the ark directory. In the diff below, options are removed from the pending directory and added to the ark hierarchy. The final options that need to be ACKed are the files that are being added to the ark hierarchy. If the value for a file that is added should be changed, please reply with a better option. ~~~ Symbol: HID_RAPOO [=n] Type : tristate Defined at drivers/hid/Kconfig:1019 Prompt: Rapoo non-fully HID-compliant devices Depends on: HID_SUPPORT [=y] && HID [=y] Location: -> Device Drivers -> HID bus support (HID_SUPPORT [=y]) -> HID bus core support (HID [=y]) -> Special HID drivers -> Rapoo non-fully HID-compliant devices (HID_RAPOO [=n]) ~~~ Commit: b3b1c68fb726 (HID: rapoo: Add support for side buttons on RAPOO 0x2015 mouse) --- Signed-off-by: Fedora Kernel Team Signed-off-by: Benjamin Tissoires --- redhat/configs/rhel/generic/CONFIG_HID_RAPOO | 1 + 1 file changed, 1 insertion(+) create mode 100644 redhat/configs/rhel/generic/CONFIG_HID_RAPOO diff --git a/redhat/configs/rhel/generic/CONFIG_HID_RAPOO b/redhat/configs/rhel/generic/CONFIG_HID_RAPOO new file mode 100644 index 000000000000..b8a157347409 --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_HID_RAPOO @@ -0,0 +1 @@ +# CONFIG_HID_RAPOO is not set From 3d1baed947dc20e0ad75b695c1db5d7f1d93cdee Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 8 Jun 2026 18:34:49 +0200 Subject: [PATCH 193/295] [redhat] enable HID haptic JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: RHEL only, https://gitlab.com/cki-project/kernel-ark Was introduced through the following commits: commit 513f37186ade ("fedora: updates for 6.18") commit 6d35694eb62a ("Flip HID_HAPTIC to inline for Fedora due to symbol errors") commit 0e230db5697d ("Consolidate configs into common for 6.18") Signed-off-by: Benjamin Tissoires --- redhat/configs/common/generic/CONFIG_HID_HAPTIC | 1 + 1 file changed, 1 insertion(+) create mode 100644 redhat/configs/common/generic/CONFIG_HID_HAPTIC diff --git a/redhat/configs/common/generic/CONFIG_HID_HAPTIC b/redhat/configs/common/generic/CONFIG_HID_HAPTIC new file mode 100644 index 000000000000..dbb05f03a27d --- /dev/null +++ b/redhat/configs/common/generic/CONFIG_HID_HAPTIC @@ -0,0 +1 @@ +CONFIG_HID_HAPTIC=y From ed133ae54c5c6257c3d221f7a1b9dfd27725ab02 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 9 Jun 2026 09:08:46 +0200 Subject: [PATCH 194/295] Input: rename INPUT_PROP_HAPTIC_TOUCHPAD to INPUT_PROP_PRESSUREPAD JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.18 Partial: ae8966b7b5bd already backported without the hid-haptic.c changes, so bring back the rest of it commit ae8966b7b5bd69b86209cc34bcca1ba9f18b68e6 Author: Peter Hutterer Date: Thu Nov 6 21:45:34 2025 +1000 Input: rename INPUT_PROP_HAPTIC_TOUCHPAD to INPUT_PROP_PRESSUREPAD And expand it to encompass all pressure pads. Definition: "pressure pad" as used here as includes all touchpads that use physical pressure to convert to click, without physical hinges. Also called haptic touchpads in general parlance, Synaptics calls them ForcePads. Most (all?) pressure pads are currently advertised as INPUT_PROP_BUTTONPAD. The suggestion to identify them as pressure pads by defining the resolution on ABS_MT_PRESSURE has been in the docs since commit 20ccc8dd38a3 ("Documentation: input: define ABS_PRESSURE/ABS_MT_PRESSURE resolution as grams") but few devices provide this information. In userspace it's thus impossible to determine whether a device is a true pressure pad (pressure equals pressure) or a normal clickpad with (pressure equals finger size). Commit 7075ae4ac9db ("Input: add INPUT_PROP_HAPTIC_TOUCHPAD") introduces INPUT_PROP_HAPTIC_TOUCHPAD but restricted it to those touchpads that have support for userspace-controlled effects. Let's expand and rename that definition to include all pressure pad touchpads since those that do support FF effects can be identified by the presence of the FF_HAPTIC bit. This means: - clickpad: INPUT_PROP_BUTTONPAD - pressurepad: INPUT_PROP_BUTTONPAD + INPUT_PROP_PRESSUREPAD - pressurepad with configurable haptics: INPUT_PROP_BUTTONPAD + INPUT_PROP_PRESSUREPAD + FF_HAPTIC Signed-off-by: Peter Hutterer Acked-by: Benjamin Tissoires Link: https://patch.msgid.link/20251106114534.GA405512@tassie Signed-off-by: Dmitry Torokhov Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-haptic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c index b591437200b8..deadab28cdbe 100644 --- a/drivers/hid/hid-haptic.c +++ b/drivers/hid/hid-haptic.c @@ -86,7 +86,7 @@ int hid_haptic_input_configured(struct hid_device *hdev, if (hi->application == HID_DG_TOUCHPAD) { if (haptic->auto_trigger_report && haptic->manual_trigger_report) { - __set_bit(INPUT_PROP_HAPTIC_TOUCHPAD, hi->input->propbit); + __set_bit(INPUT_PROP_PRESSUREPAD, hi->input->propbit); return 1; } return 0; From 419aa3271f765417ff59e28b73b70a26f70cef0f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:08:22 +0200 Subject: [PATCH 195/295] HID: pidff: Refactor field quirks detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 446e303982a616f5cd48beabd236f0e6a17ec0ab Author: Tomasz Pakuła Date: Tue Feb 3 18:45:27 2026 +0100 HID: pidff: Refactor field quirks detection In preparation for additional quirks Co-developed-by: Oleg Makarenko Signed-off-by: Oleg Makarenko Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index fbf3dbc92e66..9495ab9a1e5c 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #define PID_EFFECTS_MAX 64 @@ -1053,6 +1054,11 @@ static int pidff_find_field_with_usage(int *usage_index, return -1; } +#define PIDFF_MISSING_FIELD(name, quirks) \ + ({ pr_debug("%s field not found, but that's OK\n", __stringify(name)); \ + pr_debug("Setting MISSING_%s quirk\n", __stringify(name)); \ + *quirks |= HID_PIDFF_QUIRK_MISSING_ ## name; }) + /* * Find fields from a report and fill a pidff_usage */ @@ -1060,9 +1066,6 @@ static int pidff_find_fields(struct pidff_usage *usage, const u8 *table, struct hid_report *report, int count, int strict, u32 *quirks) { - const u8 block_offset = pidff_set_condition[PID_PARAM_BLOCK_OFFSET]; - const u8 delay = pidff_set_effect[PID_START_DELAY]; - if (!report) { pr_debug("%s, null report\n", __func__); return -1; @@ -1080,17 +1083,14 @@ static int pidff_find_fields(struct pidff_usage *usage, const u8 *table, continue; } - if (table[i] == delay) { - pr_debug("Delay field not found, but that's OK\n"); - pr_debug("Setting MISSING_DELAY quirk\n"); - *quirks |= HID_PIDFF_QUIRK_MISSING_DELAY; + /* Field quirks auto-detection */ + if (table[i] == pidff_set_effect[PID_START_DELAY]) + PIDFF_MISSING_FIELD(DELAY, quirks); - } else if (table[i] == block_offset) { - pr_debug("PBO field not found, but that's OK\n"); - pr_debug("Setting MISSING_PBO quirk\n"); - *quirks |= HID_PIDFF_QUIRK_MISSING_PBO; + else if (table[i] == pidff_set_condition[PID_PARAM_BLOCK_OFFSET]) + PIDFF_MISSING_FIELD(PBO, quirks); - } else if (strict) { + else if (strict) { pr_debug("failed to locate %d\n", i); return -1; } From 46f524264b8d749f496d52103ae647b76df97402 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:08:22 +0200 Subject: [PATCH 196/295] HID: pidff: Add MISSING_NEG_COEFFICIENT quirk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit cf3bf7ad4e7722735d085b5fd5ab90a01be831d4 Author: Tomasz Pakuła Date: Tue Feb 3 18:45:28 2026 +0100 HID: pidff: Add MISSING_NEG_COEFFICIENT quirk Windows/Directinput allows devices with missing negative coefficient for conditional effects. Negative coefficient is ignored in such cases. Donot fail set_condition usage search if negative coefficient is missing. Fixes conditional effect playback on Asetek wheelbases. https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ee416601(v=vs.85) Co-developed-by: Oleg Makarenko Signed-off-by: Oleg Makarenko Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 11 +++++++++-- drivers/hid/usbhid/hid-pidff.h | 3 +++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 9495ab9a1e5c..56e1a115f648 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -619,8 +619,12 @@ static void pidff_set_condition_report(struct pidff_device *pidff, effect->u.condition[i].center); pidff_set_signed(&pidff->set_condition[PID_POS_COEFFICIENT], effect->u.condition[i].right_coeff); - pidff_set_signed(&pidff->set_condition[PID_NEG_COEFFICIENT], - effect->u.condition[i].left_coeff); + + /* Omit Negative Coefficient if missing */ + if (!(pidff->quirks & HID_PIDFF_QUIRK_MISSING_NEG_COEFFICIENT)) + pidff_set_signed(&pidff->set_condition[PID_NEG_COEFFICIENT], + effect->u.condition[i].left_coeff); + pidff_set(&pidff->set_condition[PID_POS_SATURATION], effect->u.condition[i].right_saturation); pidff_set(&pidff->set_condition[PID_NEG_SATURATION], @@ -1090,6 +1094,9 @@ static int pidff_find_fields(struct pidff_usage *usage, const u8 *table, else if (table[i] == pidff_set_condition[PID_PARAM_BLOCK_OFFSET]) PIDFF_MISSING_FIELD(PBO, quirks); + else if (table[i] == pidff_set_condition[PID_NEG_COEFFICIENT]) + PIDFF_MISSING_FIELD(NEG_COEFFICIENT, quirks); + else if (strict) { pr_debug("failed to locate %d\n", i); return -1; diff --git a/drivers/hid/usbhid/hid-pidff.h b/drivers/hid/usbhid/hid-pidff.h index f321f675e131..5bf54e981543 100644 --- a/drivers/hid/usbhid/hid-pidff.h +++ b/drivers/hid/usbhid/hid-pidff.h @@ -21,6 +21,9 @@ /* Force all periodic effects to be uploaded as SINE */ #define HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY BIT(4) +/* Allow devices with missing negative coefficient in the set condition usage */ +#define HID_PIDFF_QUIRK_MISSING_NEG_COEFFICIENT BIT(5) + #ifdef CONFIG_HID_PID int hid_pidff_init(struct hid_device *hid); int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks); From 79ab7c63cb7a3b3caad2a65d639961c8d08548d1 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:08:23 +0200 Subject: [PATCH 197/295] HID: pidff: Add MISSING_NEG_SATURATION quirk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit d9cef0989ace79582dac90a2915c100144460e9f Author: Tomasz Pakuła Date: Tue Feb 3 18:45:29 2026 +0100 HID: pidff: Add MISSING_NEG_SATURATION quirk This is the same case as the previous MISSING_NEG_COEFFICIENT quirk Co-developed-by: Oleg Makarenko Signed-off-by: Oleg Makarenko Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 14 ++++++++++---- drivers/hid/usbhid/hid-pidff.h | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 56e1a115f648..911d63bf2103 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -619,16 +619,19 @@ static void pidff_set_condition_report(struct pidff_device *pidff, effect->u.condition[i].center); pidff_set_signed(&pidff->set_condition[PID_POS_COEFFICIENT], effect->u.condition[i].right_coeff); + pidff_set(&pidff->set_condition[PID_POS_SATURATION], + effect->u.condition[i].right_saturation); /* Omit Negative Coefficient if missing */ if (!(pidff->quirks & HID_PIDFF_QUIRK_MISSING_NEG_COEFFICIENT)) pidff_set_signed(&pidff->set_condition[PID_NEG_COEFFICIENT], effect->u.condition[i].left_coeff); - pidff_set(&pidff->set_condition[PID_POS_SATURATION], - effect->u.condition[i].right_saturation); - pidff_set(&pidff->set_condition[PID_NEG_SATURATION], - effect->u.condition[i].left_saturation); + /* Omit Negative Saturation if missing */ + if (!(pidff->quirks & HID_PIDFF_QUIRK_MISSING_NEG_SATURATION)) + pidff_set_signed(&pidff->set_condition[PID_NEG_SATURATION], + effect->u.condition[i].left_saturation); + pidff_set(&pidff->set_condition[PID_DEAD_BAND], effect->u.condition[i].deadband); hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONDITION], @@ -1097,6 +1100,9 @@ static int pidff_find_fields(struct pidff_usage *usage, const u8 *table, else if (table[i] == pidff_set_condition[PID_NEG_COEFFICIENT]) PIDFF_MISSING_FIELD(NEG_COEFFICIENT, quirks); + else if (table[i] == pidff_set_condition[PID_NEG_SATURATION]) + PIDFF_MISSING_FIELD(NEG_SATURATION, quirks); + else if (strict) { pr_debug("failed to locate %d\n", i); return -1; diff --git a/drivers/hid/usbhid/hid-pidff.h b/drivers/hid/usbhid/hid-pidff.h index 5bf54e981543..8d879067718f 100644 --- a/drivers/hid/usbhid/hid-pidff.h +++ b/drivers/hid/usbhid/hid-pidff.h @@ -24,6 +24,9 @@ /* Allow devices with missing negative coefficient in the set condition usage */ #define HID_PIDFF_QUIRK_MISSING_NEG_COEFFICIENT BIT(5) +/* Allow devices with missing negative saturation in the set condition usage */ +#define HID_PIDFF_QUIRK_MISSING_NEG_SATURATION BIT(6) + #ifdef CONFIG_HID_PID int hid_pidff_init(struct hid_device *hid); int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks); From e55440f1e4e5c9e11b21bf40a86c80d089253d66 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:08:24 +0200 Subject: [PATCH 198/295] HID: pidff: Add MISSING_DEADBAND quirk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 7daaa0fc44f47882e7b2efff7b95c5f72bb08c26 Author: Tomasz Pakuła Date: Tue Feb 3 18:45:30 2026 +0100 HID: pidff: Add MISSING_DEADBAND quirk Some devices (mainly Asetek) do not have deadband field in set conditional usage. Do not fail set conditional usage search if it's missing. Allows conditional effect playback on Asetek wheelbases. Deadband is practically never used in simracing anyway. Align property name in the whole driver to use 'deadband' without space. Co-developed-by: Oleg Makarenko Signed-off-by: Oleg Makarenko Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 12 +++++++++--- drivers/hid/usbhid/hid-pidff.h | 3 +++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 911d63bf2103..aee8a4443305 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -82,7 +82,7 @@ static const u8 pidff_set_envelope[] = { 0x22, 0x5b, 0x5c, 0x5d, 0x5e }; #define PID_NEG_COEFFICIENT 4 #define PID_POS_SATURATION 5 #define PID_NEG_SATURATION 6 -#define PID_DEAD_BAND 7 +#define PID_DEADBAND 7 static const u8 pidff_set_condition[] = { 0x22, 0x23, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65 }; @@ -632,8 +632,11 @@ static void pidff_set_condition_report(struct pidff_device *pidff, pidff_set_signed(&pidff->set_condition[PID_NEG_SATURATION], effect->u.condition[i].left_saturation); - pidff_set(&pidff->set_condition[PID_DEAD_BAND], - effect->u.condition[i].deadband); + /* Omit Deadband field if missing */ + if (!(pidff->quirks & HID_PIDFF_QUIRK_MISSING_DEADBAND)) + pidff_set(&pidff->set_condition[PID_DEADBAND], + effect->u.condition[i].deadband); + hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONDITION], HID_REQ_SET_REPORT); } @@ -1103,6 +1106,9 @@ static int pidff_find_fields(struct pidff_usage *usage, const u8 *table, else if (table[i] == pidff_set_condition[PID_NEG_SATURATION]) PIDFF_MISSING_FIELD(NEG_SATURATION, quirks); + else if (table[i] == pidff_set_condition[PID_DEADBAND]) + PIDFF_MISSING_FIELD(DEADBAND, quirks); + else if (strict) { pr_debug("failed to locate %d\n", i); return -1; diff --git a/drivers/hid/usbhid/hid-pidff.h b/drivers/hid/usbhid/hid-pidff.h index 8d879067718f..c413aa732842 100644 --- a/drivers/hid/usbhid/hid-pidff.h +++ b/drivers/hid/usbhid/hid-pidff.h @@ -27,6 +27,9 @@ /* Allow devices with missing negative saturation in the set condition usage */ #define HID_PIDFF_QUIRK_MISSING_NEG_SATURATION BIT(6) +/* Allow devices with missing deadband in the set condition usage */ +#define HID_PIDFF_QUIRK_MISSING_DEADBAND BIT(7) + #ifdef CONFIG_HID_PID int hid_pidff_init(struct hid_device *hid); int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks); From 96f218ff6e333cfbf71f5abca000f4eebd3f9b71 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:08:25 +0200 Subject: [PATCH 199/295] HID: i2c-hid: elan: Add parade-tc3408 timing JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 4410a3f14c305de493036b7d982f24b84e4c8e03 Author: Langyan Ye Date: Thu Jan 8 14:35:24 2026 +0800 HID: i2c-hid: elan: Add parade-tc3408 timing Parade-tc3408 requires reset to pull down time greater than 10ms, so the configuration post_power_delay_ms is 10, and the chipset initial time is required to be greater than 300ms, so the post_gpio_reset_on_delay_ms is set to 300. Signed-off-by: Langyan Ye Reviewed-by: Douglas Anderson Acked-by: Jiri Kosina Link: https://patch.msgid.link/20260108063524.742464-3-yelangyan@huaqin.corp-partner.google.com Signed-off-by: Dmitry Torokhov Signed-off-by: Benjamin Tissoires --- drivers/hid/i2c-hid/i2c-hid-of-elan.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/hid/i2c-hid/i2c-hid-of-elan.c b/drivers/hid/i2c-hid/i2c-hid-of-elan.c index b81fcc6ff49e..919e32c47e12 100644 --- a/drivers/hid/i2c-hid/i2c-hid-of-elan.c +++ b/drivers/hid/i2c-hid/i2c-hid-of-elan.c @@ -195,12 +195,20 @@ static const struct elan_i2c_hid_chip_data ilitek_ili2901_chip_data = { .main_supply_name = "vcc33", }; +static const struct elan_i2c_hid_chip_data parade_tc3408_chip_data = { + .post_power_delay_ms = 10, + .post_gpio_reset_on_delay_ms = 300, + .hid_descriptor_address = 0x0001, + .main_supply_name = "vcc33", +}; + static const struct of_device_id elan_i2c_hid_of_match[] = { { .compatible = "elan,ekth6915", .data = &elan_ekth6915_chip_data }, { .compatible = "elan,ekth6a12nay", .data = &elan_ekth6a12nay_chip_data }, { .compatible = "focaltech,ft8112", .data = &focaltech_ft8112_chip_data }, { .compatible = "ilitek,ili9882t", .data = &ilitek_ili9882t_chip_data }, { .compatible = "ilitek,ili2901", .data = &ilitek_ili2901_chip_data }, + { .compatible = "parade,tc3408", .data = ¶de_tc3408_chip_data }, { } }; MODULE_DEVICE_TABLE(of, elan_i2c_hid_of_match); From 2c0f30ffcc65f26644e39d424c3492c9428880cf Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:08:26 +0200 Subject: [PATCH 200/295] HID: mcp2221: use mcp_i2c_smbus_read for block reads JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit ab05515757fcb77edbbe7c68fbc7c0c930c0f668 Author: Romain Sioen Date: Fri Feb 6 17:31:53 2026 +0100 HID: mcp2221: use mcp_i2c_smbus_read for block reads Refactor mcp_smbus_xfer() to use the mcp_i2c_smbus_read() helper function for I2C_SMBUS_BLOCK_DATA and I2C_SMBUS_I2C_BLOCK_DATA read operations. This replaces the manual setup of the receive buffer and explicit calls to mcp_send_data_req_status(), making the code cleaner and more consistent with other read paths in the driver. Signed-off-by: Romain Sioen Signed-off-by: Marius Cristea Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-mcp2221.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c index 17110eefc3b3..8452440e5f8f 100644 --- a/drivers/hid/hid-mcp2221.c +++ b/drivers/hid/hid-mcp2221.c @@ -536,10 +536,10 @@ static int mcp_smbus_xfer(struct i2c_adapter *adapter, u16 addr, if (ret) goto exit; - mcp->rxbuf_idx = 0; - mcp->rxbuf = data->block; - mcp->txbuf[0] = MCP2221_I2C_GET_DATA; - ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1); + ret = mcp_i2c_smbus_read(mcp, NULL, + MCP2221_I2C_RD_RPT_START, + addr, data->block[0] + 1, + data->block); if (ret) goto exit; } else { @@ -555,14 +555,14 @@ static int mcp_smbus_xfer(struct i2c_adapter *adapter, u16 addr, case I2C_SMBUS_I2C_BLOCK_DATA: if (read_write == I2C_SMBUS_READ) { ret = mcp_smbus_write(mcp, addr, command, NULL, - 0, MCP2221_I2C_WR_NO_STOP, 1); + 0, MCP2221_I2C_WR_NO_STOP, 0); if (ret) goto exit; - mcp->rxbuf_idx = 0; - mcp->rxbuf = data->block; - mcp->txbuf[0] = MCP2221_I2C_GET_DATA; - ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1); + ret = mcp_i2c_smbus_read(mcp, NULL, + MCP2221_I2C_RD_RPT_START, + addr, data->block[0], + &data->block[1]); if (ret) goto exit; } else { From 45d984d764b85aaa604aefe54184d46345a16f1f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:09:27 +0200 Subject: [PATCH 201/295] HID: hid-lenovo-go: Add Lenovo Legion Go Series HID Driver JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 Conflict: MAINTAINERS is missing the other LENOVO entries (platform-x86 tree) commit d69ccfcbc9551988190895bc125a8bf709aa5931 Author: Derek J. Clark Date: Tue Mar 10 07:29:20 2026 +0000 HID: hid-lenovo-go: Add Lenovo Legion Go Series HID Driver Adds initial framework for a new HID driver, hid-lenovo-go, along with attributes that report the firmware and hardware version for each component of the HID device, of which there are 4 parts: The MCU, the transmission dongle, the left "handle" controller half, and the right "handle" controller half. Each of these devices are provided an attribute group to contain its device specific attributes. Additionally, the touchpad device attributes are logically separated from the other components in another attribute group. This driver primarily provides access to the configurable settings of the Lenovo Legion Go and Lenovo Legion Go 2 controllers running the latest firmware. As previously noted, the Legion Go controllers recently had a firmware update[1] which switched from the original "SepentiaUSB" protocol to a brand new protocol for the Go 2, primarily to ensure backwards and forwards compatibility between the Go and Go 2 devices. As part of that update the PIDs for the controllers were changed, so there is no risk of this driver attaching to controller firmware that it doesn't support. -- v6: - Make attributes static. - Use NULL instead of 0 in mcu_propery_out when there is no data. v5: - Make version attributes static, retrieve them using delayed work during probe. - Fix endianness of version strings and print as hex. v3: - Add hid-lenovo.c and Mark Pearson to LENOVO HID DRIVERS entry in MAINTAINERS Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- MAINTAINERS | 8 + drivers/hid/Kconfig | 12 + drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 3 + drivers/hid/hid-lenovo-go.c | 914 ++++++++++++++++++++++++++++++++++++ 5 files changed, 938 insertions(+) create mode 100644 drivers/hid/hid-lenovo-go.c diff --git a/MAINTAINERS b/MAINTAINERS index 84f4b3901e02..32d90085d478 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13056,6 +13056,14 @@ S: Maintained W: http://legousb.sourceforge.net/ F: drivers/usb/misc/legousbtower.c +LENOVO HID drivers +M: Derek J. Clark +M: Mark Pearson +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-lenovo-go.c +F: drivers/hid/hid-lenovo.c + LETSKETCH HID TABLET DRIVER M: Hans de Goede L: linux-input@vger.kernel.org diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 8d2cc97fb0f4..b37a854ae491 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -623,6 +623,18 @@ config HID_LENOVO - ThinkPad Compact Bluetooth Keyboard with TrackPoint (supports Fn keys) - ThinkPad Compact USB Keyboard with TrackPoint (supports Fn keys) +config HID_LENOVO_GO + tristate "HID Driver for Lenovo Legion Go Series Controllers" + depends on USB_HID + select LEDS_CLASS + select LEDS_CLASS_MULTICOLOR + help + Support for Lenovo Legion Go devices with detachable controllers. + + Say Y here to include configuration interface support for the Lenovo Legion Go + and Legion Go 2 Handheld Console Controllers. Say M here to compile this + driver as a module. The module will be called hid-lenovo-go. + config HID_LETSKETCH tristate "Letsketch WP9620N tablets" depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index e01838239ae6..79fbe4e3e2f4 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_HID_KYE) += hid-kye.o obj-$(CONFIG_HID_KYSONA) += hid-kysona.o obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o obj-$(CONFIG_HID_LENOVO) += hid-lenovo.o +obj-$(CONFIG_HID_LENOVO_GO) += hid-lenovo-go.o obj-$(CONFIG_HID_LETSKETCH) += hid-letsketch.o obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o obj-$(CONFIG_HID_LOGITECH) += hid-lg-g15.o diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 474539a61c97..4bf3284bd4de 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -859,7 +859,10 @@ #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_602E 0x602e #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6093 0x6093 #define USB_DEVICE_ID_LENOVO_LEGION_GO_DUAL_DINPUT 0x6184 +#define USB_DEVICE_ID_LENOVO_LEGION_GO2_XINPUT 0x61eb +#define USB_DEVICE_ID_LENOVO_LEGION_GO2_DINPUT 0x61ec #define USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT 0x61ed +#define USB_DEVICE_ID_LENOVO_LEGION_GO2_FPS 0x61ee #define USB_VENDOR_ID_LETSKETCH 0x6161 #define USB_DEVICE_ID_WP9620N 0x4d15 diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c new file mode 100644 index 000000000000..a13ddbe28c7d --- /dev/null +++ b/drivers/hid/hid-lenovo-go.c @@ -0,0 +1,914 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Lenovo Legion Go series gamepads. + * + * Copyright (c) 2026 Derek J. Clark + * Copyright (c) 2026 Valve Corporation + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" + +#define GO_GP_INTF_IN 0x83 +#define GO_OUTPUT_REPORT_ID 0x05 +#define GO_GP_RESET_SUCCESS 0x01 +#define GO_PACKET_SIZE 64 + +static struct hid_go_cfg { + struct delayed_work go_cfg_setup; + struct completion send_cmd_complete; + struct hid_device *hdev; + struct mutex cfg_mutex; /*ensure single synchronous output report*/ + u32 gp_left_version_firmware; + u8 gp_left_version_gen; + u32 gp_left_version_hardware; + u32 gp_left_version_product; + u32 gp_left_version_protocol; + u32 gp_right_version_firmware; + u8 gp_right_version_gen; + u32 gp_right_version_hardware; + u32 gp_right_version_product; + u32 gp_right_version_protocol; + u32 mcu_version_firmware; + u8 mcu_version_gen; + u32 mcu_version_hardware; + u32 mcu_version_product; + u32 mcu_version_protocol; + u32 tx_dongle_version_firmware; + u8 tx_dongle_version_gen; + u32 tx_dongle_version_hardware; + u32 tx_dongle_version_product; + u32 tx_dongle_version_protocol; +} drvdata; + +struct go_cfg_attr { + u8 index; +}; + +struct command_report { + u8 report_id; + u8 id; + u8 cmd; + u8 sub_cmd; + u8 device_type; + u8 data[59]; +} __packed; + +enum command_id { + MCU_CONFIG_DATA = 0x00, + OS_MODE_DATA = 0x06, + GAMEPAD_DATA = 0x3c, +}; + +enum mcu_command_index { + GET_VERSION_DATA = 0x02, + GET_FEATURE_STATUS, + SET_FEATURE_STATUS, + GET_MOTOR_CFG, + SET_MOTOR_CFG, + GET_DPI_CFG, + SET_DPI_CFG, + SET_TRIGGER_CFG = 0x0a, + SET_JOYSTICK_CFG = 0x0c, + SET_GYRO_CFG = 0x0e, + GET_RGB_CFG, + SET_RGB_CFG, + GET_DEVICE_STATUS = 0xa0, + +}; + +enum dev_type { + UNSPECIFIED, + USB_MCU, + TX_DONGLE, + LEFT_CONTROLLER, + RIGHT_CONTROLLER, +}; + +enum version_data_index { + PRODUCT_VERSION = 0x02, + PROTOCOL_VERSION, + FIRMWARE_VERSION, + HARDWARE_VERSION, + HARDWARE_GENERATION, +}; + +static int hid_go_version_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->sub_cmd) { + case PRODUCT_VERSION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_product = + get_unaligned_be32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_product = + get_unaligned_be32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_product = + get_unaligned_be32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_product = + get_unaligned_be32(cmd_rep->data); + return 0; + default: + return -EINVAL; + } + case PROTOCOL_VERSION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_protocol = + get_unaligned_be32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_protocol = + get_unaligned_be32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_protocol = + get_unaligned_be32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_protocol = + get_unaligned_be32(cmd_rep->data); + return 0; + default: + return -EINVAL; + } + case FIRMWARE_VERSION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_firmware = + get_unaligned_be32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_firmware = + get_unaligned_be32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_firmware = + get_unaligned_be32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_firmware = + get_unaligned_be32(cmd_rep->data); + return 0; + default: + return -EINVAL; + } + case HARDWARE_VERSION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_hardware = + get_unaligned_be32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_hardware = + get_unaligned_be32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_hardware = + get_unaligned_be32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_hardware = + get_unaligned_be32(cmd_rep->data); + return 0; + default: + return -EINVAL; + } + case HARDWARE_GENERATION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_gen = cmd_rep->data[0]; + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_gen = cmd_rep->data[0]; + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_gen = cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_gen = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int get_endpoint_address(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_host_endpoint *ep; + + if (!intf) + return -ENODEV; + + ep = intf->cur_altsetting->endpoint; + if (!ep) + return -ENODEV; + + return ep->desc.bEndpointAddress; +} + +static int hid_go_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct command_report *cmd_rep; + int ep, ret; + + if (size != GO_PACKET_SIZE) + goto passthrough; + + ep = get_endpoint_address(hdev); + if (ep != GO_GP_INTF_IN) + goto passthrough; + + cmd_rep = (struct command_report *)data; + + switch (cmd_rep->id) { + case MCU_CONFIG_DATA: + switch (cmd_rep->cmd) { + case GET_VERSION_DATA: + ret = hid_go_version_event(cmd_rep); + break; + default: + ret = -EINVAL; + break; + }; + break; + default: + goto passthrough; + }; + dev_dbg(&hdev->dev, "Rx data as raw input report: [%*ph]\n", + GO_PACKET_SIZE, data); + + complete(&drvdata.send_cmd_complete); + return ret; + +passthrough: + /* Forward other HID reports so they generate events */ + hid_input_report(hdev, HID_INPUT_REPORT, data, size, 1); + return 0; +} + +static int mcu_property_out(struct hid_device *hdev, u8 id, u8 command, + u8 index, enum dev_type device, u8 *data, size_t len) +{ + unsigned char *dmabuf __free(kfree) = NULL; + u8 header[] = { GO_OUTPUT_REPORT_ID, id, command, index, device }; + size_t header_size = ARRAY_SIZE(header); + int timeout = 50; + int ret; + + if (header_size + len > GO_PACKET_SIZE) + return -EINVAL; + + guard(mutex)(&drvdata.cfg_mutex); + /* We can't use a devm_alloc reusable buffer without side effects during suspend */ + dmabuf = kzalloc(GO_PACKET_SIZE, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + memcpy(dmabuf, header, header_size); + memcpy(dmabuf + header_size, data, len); + + dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n", + GO_PACKET_SIZE, dmabuf); + + ret = hid_hw_output_report(hdev, dmabuf, GO_PACKET_SIZE); + if (ret < 0) + return ret; + + ret = ret == GO_PACKET_SIZE ? 0 : -EINVAL; + if (ret) + return ret; + + ret = wait_for_completion_interruptible_timeout(&drvdata.send_cmd_complete, + msecs_to_jiffies(timeout)); + + if (ret == 0) /* timeout occurred */ + ret = -EBUSY; + + reinit_completion(&drvdata.send_cmd_complete); + return 0; +} + +static ssize_t version_show(struct device *dev, struct device_attribute *attr, + char *buf, enum version_data_index index, + enum dev_type device_type) +{ + ssize_t count = 0; + + switch (index) { + case PRODUCT_VERSION: + switch (device_type) { + case USB_MCU: + count = sysfs_emit(buf, "%x\n", + drvdata.mcu_version_product); + break; + case TX_DONGLE: + count = sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_product); + break; + case LEFT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_product); + break; + case RIGHT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_right_version_product); + break; + default: + return -EINVAL; + } + break; + case PROTOCOL_VERSION: + switch (device_type) { + case USB_MCU: + count = sysfs_emit(buf, "%x\n", + drvdata.mcu_version_protocol); + break; + case TX_DONGLE: + count = sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_protocol); + break; + case LEFT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_protocol); + break; + case RIGHT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_right_version_protocol); + break; + default: + return -EINVAL; + } + break; + case FIRMWARE_VERSION: + switch (device_type) { + case USB_MCU: + count = sysfs_emit(buf, "%x\n", + drvdata.mcu_version_firmware); + break; + case TX_DONGLE: + count = sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_firmware); + break; + case LEFT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_firmware); + break; + case RIGHT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_right_version_firmware); + break; + default: + return -EINVAL; + } + break; + case HARDWARE_VERSION: + switch (device_type) { + case USB_MCU: + count = sysfs_emit(buf, "%x\n", + drvdata.mcu_version_hardware); + break; + case TX_DONGLE: + count = sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_hardware); + break; + case LEFT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_hardware); + break; + case RIGHT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_right_version_hardware); + break; + default: + return -EINVAL; + } + break; + case HARDWARE_GENERATION: + switch (device_type) { + case USB_MCU: + count = sysfs_emit(buf, "%x\n", + drvdata.mcu_version_gen); + break; + case TX_DONGLE: + count = sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_gen); + break; + case LEFT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_gen); + break; + case RIGHT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_right_version_gen); + break; + default: + return -EINVAL; + } + break; + } + + return count; +} + +#define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _group) \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return _group##_store(dev, attr, buf, count, _name.index, \ + _dtype); \ + } \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_show(dev, attr, buf, _name.index, _dtype); \ + } \ + static ssize_t _name##_##_rtype##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return _group##_options(dev, attr, buf, _name.index); \ + } \ + static DEVICE_ATTR_RW_NAMED(_name, _attrname) + +#define LEGO_DEVICE_ATTR_WO(_name, _attrname, _dtype, _group) \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return _group##_store(dev, attr, buf, count, _name.index, \ + _dtype); \ + } \ + static DEVICE_ATTR_WO_NAMED(_name, _attrname) + +#define LEGO_DEVICE_ATTR_RO(_name, _attrname, _dtype, _group) \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_show(dev, attr, buf, _name.index, _dtype); \ + } \ + static DEVICE_ATTR_RO_NAMED(_name, _attrname) + +/* Gamepad - MCU */ +static struct go_cfg_attr version_product_mcu = { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_mcu, "product_version", USB_MCU, version); + +static struct go_cfg_attr version_protocol_mcu = { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_mcu, "protocol_version", USB_MCU, version); + +static struct go_cfg_attr version_firmware_mcu = { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_mcu, "firmware_version", USB_MCU, version); + +static struct go_cfg_attr version_hardware_mcu = { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_mcu, "hardware_version", USB_MCU, version); + +static struct go_cfg_attr version_gen_mcu = { HARDWARE_GENERATION }; +LEGO_DEVICE_ATTR_RO(version_gen_mcu, "hardware_generation", USB_MCU, version); + +static struct attribute *mcu_attrs[] = { + &dev_attr_version_firmware_mcu.attr, + &dev_attr_version_gen_mcu.attr, + &dev_attr_version_hardware_mcu.attr, + &dev_attr_version_product_mcu.attr, + &dev_attr_version_protocol_mcu.attr, + NULL, +}; + +static const struct attribute_group mcu_attr_group = { + .attrs = mcu_attrs, +}; + +/* Gamepad - TX Dongle */ +static struct go_cfg_attr version_product_tx_dongle = { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_tx_dongle, "product_version", TX_DONGLE, version); + +static struct go_cfg_attr version_protocol_tx_dongle = { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_tx_dongle, "protocol_version", TX_DONGLE, version); + +static struct go_cfg_attr version_firmware_tx_dongle = { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_tx_dongle, "firmware_version", TX_DONGLE, version); + +static struct go_cfg_attr version_hardware_tx_dongle = { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_tx_dongle, "hardware_version", TX_DONGLE, version); + +static struct go_cfg_attr version_gen_tx_dongle = { HARDWARE_GENERATION }; +LEGO_DEVICE_ATTR_RO(version_gen_tx_dongle, "hardware_generation", TX_DONGLE, version); + +static struct attribute *tx_dongle_attrs[] = { + &dev_attr_version_hardware_tx_dongle.attr, + &dev_attr_version_firmware_tx_dongle.attr, + &dev_attr_version_gen_tx_dongle.attr, + &dev_attr_version_product_tx_dongle.attr, + &dev_attr_version_protocol_tx_dongle.attr, + NULL, +}; + +static const struct attribute_group tx_dongle_attr_group = { + .name = "tx_dongle", + .attrs = tx_dongle_attrs, +}; + +/* Gamepad - Left */ +static struct go_cfg_attr version_product_left = { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_left, "product_version", LEFT_CONTROLLER, version); + +static struct go_cfg_attr version_protocol_left = { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_left, "protocol_version", LEFT_CONTROLLER, version); + +static struct go_cfg_attr version_firmware_left = { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_left, "firmware_version", LEFT_CONTROLLER, version); + +static struct go_cfg_attr version_hardware_left = { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_left, "hardware_version", LEFT_CONTROLLER, version); + +static struct go_cfg_attr version_gen_left = { HARDWARE_GENERATION }; +LEGO_DEVICE_ATTR_RO(version_gen_left, "hardware_generation", LEFT_CONTROLLER, version); + +static struct attribute *left_gamepad_attrs[] = { + &dev_attr_version_hardware_left.attr, + &dev_attr_version_firmware_left.attr, + &dev_attr_version_gen_left.attr, + &dev_attr_version_product_left.attr, + &dev_attr_version_protocol_left.attr, + NULL, +}; + +static const struct attribute_group left_gamepad_attr_group = { + .name = "left_handle", + .attrs = left_gamepad_attrs, +}; + +/* Gamepad - Right */ +static struct go_cfg_attr version_product_right = { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_right, "product_version", RIGHT_CONTROLLER, version); + +static struct go_cfg_attr version_protocol_right = { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_right, "protocol_version", RIGHT_CONTROLLER, version); + +static struct go_cfg_attr version_firmware_right = { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_right, "firmware_version", RIGHT_CONTROLLER, version); + +static struct go_cfg_attr version_hardware_right = { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_right, "hardware_version", RIGHT_CONTROLLER, version); + +static struct go_cfg_attr version_gen_right = { HARDWARE_GENERATION }; +LEGO_DEVICE_ATTR_RO(version_gen_right, "hardware_generation", RIGHT_CONTROLLER, version); + +static struct attribute *right_gamepad_attrs[] = { + &dev_attr_version_hardware_right.attr, + &dev_attr_version_firmware_right.attr, + &dev_attr_version_gen_right.attr, + &dev_attr_version_product_right.attr, + &dev_attr_version_protocol_right.attr, + NULL, +}; + +static const struct attribute_group right_gamepad_attr_group = { + .name = "right_handle", + .attrs = right_gamepad_attrs, +}; + +/* Touchpad */ +static struct attribute *touchpad_attrs[] = { + NULL, +}; + +static const struct attribute_group touchpad_attr_group = { + .name = "touchpad", + .attrs = touchpad_attrs, +}; + +static const struct attribute_group *top_level_attr_groups[] = { + &mcu_attr_group, &tx_dongle_attr_group, + &left_gamepad_attr_group, &right_gamepad_attr_group, + &touchpad_attr_group, NULL, +}; + +static void cfg_setup(struct work_struct *work) +{ + int ret; + + /* MCU Version Attrs */ + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PRODUCT_VERSION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Product Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PROTOCOL_VERSION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Protocol Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + FIRMWARE_VERSION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Firmware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_VERSION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Hardware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_GENERATION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Hardware Generation: %i\n", ret); + return; + } + + /* TX Dongle Version Attrs */ + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PRODUCT_VERSION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Product Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PROTOCOL_VERSION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Protocol Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + FIRMWARE_VERSION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Firmware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_VERSION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Hardware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_GENERATION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Hardware Generation: %i\n", ret); + return; + } + + /* Left Handle Version Attrs */ + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PRODUCT_VERSION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Product Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PROTOCOL_VERSION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Protocol Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + FIRMWARE_VERSION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Firmware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_VERSION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Hardware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_GENERATION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Hardware Generation: %i\n", ret); + return; + } + + /* Right Handle Version Attrs */ + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PRODUCT_VERSION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Product Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PROTOCOL_VERSION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Protocol Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + FIRMWARE_VERSION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Firmware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_VERSION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Hardware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_GENERATION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Hardware Generation: %i\n", ret); + return; + } +} + +static int hid_go_cfg_probe(struct hid_device *hdev, + const struct hid_device_id *_id) +{ + unsigned char *buf; + int ret; + + buf = devm_kzalloc(&hdev->dev, GO_PACKET_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + hid_set_drvdata(hdev, &drvdata); + drvdata.hdev = hdev; + mutex_init(&drvdata.cfg_mutex); + + ret = sysfs_create_groups(&hdev->dev.kobj, top_level_attr_groups); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create gamepad configuration attributes\n"); + return ret; + } + + init_completion(&drvdata.send_cmd_complete); + + /* Executing calls prior to returning from probe will lock the MCU. Schedule + * initial data call after probe has completed and MCU can accept calls. + */ + INIT_DELAYED_WORK(&drvdata.go_cfg_setup, &cfg_setup); + ret = schedule_delayed_work(&drvdata.go_cfg_setup, msecs_to_jiffies(2)); + if (!ret) { + dev_err(&hdev->dev, + "Failed to schedule startup delayed work\n"); + return -ENODEV; + } + return 0; +} + +static void hid_go_cfg_remove(struct hid_device *hdev) +{ + guard(mutex)(&drvdata.cfg_mutex); + sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups); + hid_hw_close(hdev); + hid_hw_stop(hdev); + hid_set_drvdata(hdev, NULL); +} + +static int hid_go_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret, ep; + + hdev->quirks |= HID_QUIRK_INPUT_PER_APP | HID_QUIRK_MULTI_INPUT; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "Parse failed\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "Failed to start HID device\n"); + return ret; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "Failed to open HID device\n"); + hid_hw_stop(hdev); + return ret; + } + + ep = get_endpoint_address(hdev); + if (ep != GO_GP_INTF_IN) { + dev_dbg(&hdev->dev, "Started interface %x as generic HID device\n", ep); + return 0; + } + + ret = hid_go_cfg_probe(hdev, id); + if (ret) + dev_err_probe(&hdev->dev, ret, "Failed to start configuration interface\n"); + + dev_dbg(&hdev->dev, "Started Legion Go HID Device: %x\n", ep); + + return ret; +} + +static void hid_go_remove(struct hid_device *hdev) +{ + int ep = get_endpoint_address(hdev); + + if (ep <= 0) + return; + + switch (ep) { + case GO_GP_INTF_IN: + hid_go_cfg_remove(hdev); + break; + default: + hid_hw_close(hdev); + hid_hw_stop(hdev); + break; + } +} + +static const struct hid_device_id hid_go_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_XINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_DINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_FPS) }, + {} +}; +MODULE_DEVICE_TABLE(hid, hid_go_devices); + +static struct hid_driver hid_lenovo_go = { + .name = "hid-lenovo-go", + .id_table = hid_go_devices, + .probe = hid_go_probe, + .remove = hid_go_remove, + .raw_event = hid_go_raw_event, +}; +module_hid_driver(hid_lenovo_go); + +MODULE_AUTHOR("Derek J. Clark"); +MODULE_DESCRIPTION("HID Driver for Lenovo Legion Go Series Gamepads."); +MODULE_LICENSE("GPL"); From 580828cc3527d5e3ab3b242fbf1ed3a5ef2e9a1a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:09:27 +0200 Subject: [PATCH 202/295] HID: hid-lenovo-go: Add Feature Status Attributes JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 82cd9bc866e1823cabd3af962e467e51724506eb Author: Derek J. Clark Date: Tue Mar 10 07:29:21 2026 +0000 HID: hid-lenovo-go: Add Feature Status Attributes Adds various feature status indicators and toggles to hid-lenovo-go, including the FPS mode switch setting, touchpad enable toggle, handle automatic sleep timer, etc. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go.c | 396 +++++++++++++++++++++++++++++++++++- 1 file changed, 395 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index a13ddbe28c7d..d7d47db8362c 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -39,21 +39,31 @@ static struct hid_go_cfg { struct completion send_cmd_complete; struct hid_device *hdev; struct mutex cfg_mutex; /*ensure single synchronous output report*/ + u8 fps_mode; + u8 gp_left_auto_sleep_time; u32 gp_left_version_firmware; u8 gp_left_version_gen; u32 gp_left_version_hardware; u32 gp_left_version_product; u32 gp_left_version_protocol; + u8 gp_mode; + u8 gp_right_auto_sleep_time; u32 gp_right_version_firmware; u8 gp_right_version_gen; u32 gp_right_version_hardware; u32 gp_right_version_product; u32 gp_right_version_protocol; + u8 imu_left_bypass_en; + u8 imu_left_sensor_en; + u8 imu_right_bypass_en; + u8 imu_right_sensor_en; u32 mcu_version_firmware; u8 mcu_version_gen; u32 mcu_version_hardware; u32 mcu_version_product; u32 mcu_version_protocol; + u8 rgb_en; + u8 tp_en; u32 tx_dongle_version_firmware; u8 tx_dongle_version_gen; u32 tx_dongle_version_hardware; @@ -105,6 +115,18 @@ enum dev_type { RIGHT_CONTROLLER, }; +enum enabled_status_index { + FEATURE_UNKNOWN, + FEATURE_ENABLED, + FEATURE_DISABLED, +}; + +static const char *const enabled_status_text[] = { + [FEATURE_UNKNOWN] = "unknown", + [FEATURE_ENABLED] = "true", + [FEATURE_DISABLED] = "false", +}; + enum version_data_index { PRODUCT_VERSION = 0x02, PROTOCOL_VERSION, @@ -113,6 +135,41 @@ enum version_data_index { HARDWARE_GENERATION, }; +enum feature_status_index { + FEATURE_RESET_GAMEPAD = 0x02, + FEATURE_IMU_BYPASS, + FEATURE_IMU_ENABLE = 0x05, + FEATURE_TOUCHPAD_ENABLE = 0x07, + FEATURE_LIGHT_ENABLE, + FEATURE_AUTO_SLEEP_TIME, + FEATURE_FPS_SWITCH_STATUS = 0x0b, + FEATURE_GAMEPAD_MODE = 0x0e, +}; + +enum fps_switch_status_index { + FPS_STATUS_UNKNOWN, + GAMEPAD, + FPS, +}; + +static const char *const fps_switch_text[] = { + [FPS_STATUS_UNKNOWN] = "unknown", + [GAMEPAD] = "gamepad", + [FPS] = "fps", +}; + +enum gamepad_mode_index { + GAMEPAD_MODE_UNKNOWN, + XINPUT, + DINPUT, +}; + +static const char *const gamepad_mode_text[] = { + [GAMEPAD_MODE_UNKNOWN] = "unknown", + [XINPUT] = "xinput", + [DINPUT] = "dinput", +}; + static int hid_go_version_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { @@ -222,6 +279,71 @@ static int hid_go_version_event(struct command_report *cmd_rep) } } +static int hid_go_feature_status_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->sub_cmd) { + case FEATURE_RESET_GAMEPAD: + return 0; + case FEATURE_IMU_ENABLE: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.imu_left_sensor_en = cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.imu_right_sensor_en = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + }; + case FEATURE_IMU_BYPASS: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.imu_left_bypass_en = cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.imu_right_bypass_en = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + }; + break; + case FEATURE_LIGHT_ENABLE: + drvdata.rgb_en = cmd_rep->data[0]; + return 0; + case FEATURE_AUTO_SLEEP_TIME: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.gp_left_auto_sleep_time = cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_auto_sleep_time = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + }; + break; + case FEATURE_TOUCHPAD_ENABLE: + drvdata.tp_en = cmd_rep->data[0]; + return 0; + case FEATURE_GAMEPAD_MODE: + drvdata.gp_mode = cmd_rep->data[0]; + return 0; + case FEATURE_FPS_SWITCH_STATUS: + drvdata.fps_mode = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + } +} + +static int hid_go_set_event_return(struct command_report *cmd_rep) +{ + if (cmd_rep->data[0] != 0) + return -EIO; + + return 0; +} + static int get_endpoint_address(struct hid_device *hdev) { struct usb_interface *intf = to_usb_interface(hdev->dev.parent); @@ -258,6 +380,12 @@ static int hid_go_raw_event(struct hid_device *hdev, struct hid_report *report, case GET_VERSION_DATA: ret = hid_go_version_event(cmd_rep); break; + case GET_FEATURE_STATUS: + ret = hid_go_feature_status_event(cmd_rep); + break; + case SET_FEATURE_STATUS: + ret = hid_go_set_event_return(cmd_rep); + break; default: ret = -EINVAL; break; @@ -442,6 +570,195 @@ static ssize_t version_show(struct device *dev, struct device_attribute *attr, return count; } +static ssize_t feature_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + enum feature_status_index index, + enum dev_type device_type) +{ + size_t size = 1; + u8 val = 0; + int ret; + + switch (index) { + case FEATURE_IMU_ENABLE: + case FEATURE_IMU_BYPASS: + case FEATURE_LIGHT_ENABLE: + case FEATURE_TOUCHPAD_ENABLE: + ret = sysfs_match_string(enabled_status_text, buf); + val = ret; + break; + case FEATURE_AUTO_SLEEP_TIME: + ret = kstrtou8(buf, 10, &val); + break; + case FEATURE_RESET_GAMEPAD: + ret = kstrtou8(buf, 10, &val); + if (val != GO_GP_RESET_SUCCESS) + return -EINVAL; + break; + case FEATURE_FPS_SWITCH_STATUS: + ret = sysfs_match_string(fps_switch_text, buf); + val = ret; + break; + case FEATURE_GAMEPAD_MODE: + ret = sysfs_match_string(gamepad_mode_text, buf); + val = ret; + break; + default: + return -EINVAL; + }; + + if (ret < 0) + return ret; + + if (!val) + size = 0; + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, + SET_FEATURE_STATUS, index, device_type, &val, + size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t feature_status_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum feature_status_index index, + enum dev_type device_type) +{ + ssize_t count = 0; + int ret; + u8 i; + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, + GET_FEATURE_STATUS, index, device_type, NULL, 0); + if (ret) + return ret; + + switch (index) { + case FEATURE_IMU_ENABLE: + switch (device_type) { + case LEFT_CONTROLLER: + i = drvdata.imu_left_sensor_en; + break; + case RIGHT_CONTROLLER: + i = drvdata.imu_right_sensor_en; + break; + default: + return -EINVAL; + } + if (i >= ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case FEATURE_IMU_BYPASS: + switch (device_type) { + case LEFT_CONTROLLER: + i = drvdata.imu_left_bypass_en; + break; + case RIGHT_CONTROLLER: + i = drvdata.imu_right_bypass_en; + break; + default: + return -EINVAL; + } + if (i >= ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case FEATURE_LIGHT_ENABLE: + i = drvdata.rgb_en; + if (i >= ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case FEATURE_TOUCHPAD_ENABLE: + i = drvdata.tp_en; + if (i >= ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case FEATURE_AUTO_SLEEP_TIME: + switch (device_type) { + case LEFT_CONTROLLER: + i = drvdata.gp_left_auto_sleep_time; + break; + case RIGHT_CONTROLLER: + i = drvdata.gp_right_auto_sleep_time; + break; + default: + return -EINVAL; + }; + count = sysfs_emit(buf, "%u\n", i); + break; + case FEATURE_FPS_SWITCH_STATUS: + i = drvdata.fps_mode; + if (i >= ARRAY_SIZE(fps_switch_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", fps_switch_text[i]); + break; + case FEATURE_GAMEPAD_MODE: + i = drvdata.gp_mode; + if (i >= ARRAY_SIZE(gamepad_mode_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", gamepad_mode_text[i]); + break; + default: + return -EINVAL; + }; + + return count; +} + +static ssize_t feature_status_options(struct device *dev, + struct device_attribute *attr, char *buf, + enum feature_status_index index) +{ + ssize_t count = 0; + unsigned int i; + + switch (index) { + case FEATURE_IMU_ENABLE: + case FEATURE_IMU_BYPASS: + case FEATURE_LIGHT_ENABLE: + case FEATURE_TOUCHPAD_ENABLE: + for (i = 1; i < ARRAY_SIZE(enabled_status_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + enabled_status_text[i]); + } + break; + case FEATURE_AUTO_SLEEP_TIME: + return sysfs_emit(buf, "0-255\n"); + case FEATURE_FPS_SWITCH_STATUS: + for (i = 1; i < ARRAY_SIZE(fps_switch_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + fps_switch_text[i]); + } + break; + case FEATURE_GAMEPAD_MODE: + for (i = 1; i < ARRAY_SIZE(gamepad_mode_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + gamepad_mode_text[i]); + } + break; + default: + return -EINVAL; + }; + + if (count) + buf[count - 1] = '\n'; + + return count; +} + #define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _group) \ static ssize_t _name##_store(struct device *dev, \ struct device_attribute *attr, \ @@ -496,7 +813,22 @@ LEGO_DEVICE_ATTR_RO(version_hardware_mcu, "hardware_version", USB_MCU, version); static struct go_cfg_attr version_gen_mcu = { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_mcu, "hardware_generation", USB_MCU, version); +static struct go_cfg_attr fps_switch_status = { FEATURE_FPS_SWITCH_STATUS }; +LEGO_DEVICE_ATTR_RO(fps_switch_status, "fps_switch_status", UNSPECIFIED, + feature_status); + +static struct go_cfg_attr gamepad_mode = { FEATURE_GAMEPAD_MODE }; +LEGO_DEVICE_ATTR_RW(gamepad_mode, "mode", UNSPECIFIED, index, feature_status); +static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mode_index"); + +static struct go_cfg_attr reset_mcu = { FEATURE_RESET_GAMEPAD }; +LEGO_DEVICE_ATTR_WO(reset_mcu, "reset_mcu", USB_MCU, feature_status); + static struct attribute *mcu_attrs[] = { + &dev_attr_fps_switch_status.attr, + &dev_attr_gamepad_mode.attr, + &dev_attr_gamepad_mode_index.attr, + &dev_attr_reset_mcu.attr, &dev_attr_version_firmware_mcu.attr, &dev_attr_version_gen_mcu.attr, &dev_attr_version_hardware_mcu.attr, @@ -525,7 +857,11 @@ LEGO_DEVICE_ATTR_RO(version_hardware_tx_dongle, "hardware_version", TX_DONGLE, v static struct go_cfg_attr version_gen_tx_dongle = { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_tx_dongle, "hardware_generation", TX_DONGLE, version); +static struct go_cfg_attr reset_tx_dongle = { FEATURE_RESET_GAMEPAD }; +LEGO_DEVICE_ATTR_RO(reset_tx_dongle, "reset", TX_DONGLE, feature_status); + static struct attribute *tx_dongle_attrs[] = { + &dev_attr_reset_tx_dongle.attr, &dev_attr_version_hardware_tx_dongle.attr, &dev_attr_version_firmware_tx_dongle.attr, &dev_attr_version_gen_tx_dongle.attr, @@ -555,7 +891,33 @@ LEGO_DEVICE_ATTR_RO(version_hardware_left, "hardware_version", LEFT_CONTROLLER, static struct go_cfg_attr version_gen_left = { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_left, "hardware_generation", LEFT_CONTROLLER, version); +static struct go_cfg_attr auto_sleep_time_left = { FEATURE_AUTO_SLEEP_TIME }; +LEGO_DEVICE_ATTR_RW(auto_sleep_time_left, "auto_sleep_time", LEFT_CONTROLLER, + range, feature_status); +static DEVICE_ATTR_RO_NAMED(auto_sleep_time_left_range, + "auto_sleep_time_range"); + +static struct go_cfg_attr imu_bypass_left = { FEATURE_IMU_BYPASS }; +LEGO_DEVICE_ATTR_RW(imu_bypass_left, "imu_bypass_enabled", LEFT_CONTROLLER, + index, feature_status); +static DEVICE_ATTR_RO_NAMED(imu_bypass_left_index, "imu_bypass_enabled_index"); + +static struct go_cfg_attr imu_enabled_left = { FEATURE_IMU_ENABLE }; +LEGO_DEVICE_ATTR_RW(imu_enabled_left, "imu_enabled", LEFT_CONTROLLER, index, + feature_status); +static DEVICE_ATTR_RO_NAMED(imu_enabled_left_index, "imu_enabled_index"); + +static struct go_cfg_attr reset_left = { FEATURE_RESET_GAMEPAD }; +LEGO_DEVICE_ATTR_WO(reset_left, "reset", LEFT_CONTROLLER, feature_status); + static struct attribute *left_gamepad_attrs[] = { + &dev_attr_auto_sleep_time_left.attr, + &dev_attr_auto_sleep_time_left_range.attr, + &dev_attr_imu_bypass_left.attr, + &dev_attr_imu_bypass_left_index.attr, + &dev_attr_imu_enabled_left.attr, + &dev_attr_imu_enabled_left_index.attr, + &dev_attr_reset_left.attr, &dev_attr_version_hardware_left.attr, &dev_attr_version_firmware_left.attr, &dev_attr_version_gen_left.attr, @@ -585,7 +947,33 @@ LEGO_DEVICE_ATTR_RO(version_hardware_right, "hardware_version", RIGHT_CONTROLLER static struct go_cfg_attr version_gen_right = { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_right, "hardware_generation", RIGHT_CONTROLLER, version); +static struct go_cfg_attr auto_sleep_time_right = { FEATURE_AUTO_SLEEP_TIME }; +LEGO_DEVICE_ATTR_RW(auto_sleep_time_right, "auto_sleep_time", RIGHT_CONTROLLER, + range, feature_status); +static DEVICE_ATTR_RO_NAMED(auto_sleep_time_right_range, + "auto_sleep_time_range"); + +static struct go_cfg_attr imu_bypass_right = { FEATURE_IMU_BYPASS }; +LEGO_DEVICE_ATTR_RW(imu_bypass_right, "imu_bypass_enabled", RIGHT_CONTROLLER, + index, feature_status); +static DEVICE_ATTR_RO_NAMED(imu_bypass_right_index, "imu_bypass_enabled_index"); + +static struct go_cfg_attr imu_enabled_right = { FEATURE_IMU_BYPASS }; +LEGO_DEVICE_ATTR_RW(imu_enabled_right, "imu_enabled", RIGHT_CONTROLLER, index, + feature_status); +static DEVICE_ATTR_RO_NAMED(imu_enabled_right_index, "imu_enabled_index"); + +static struct go_cfg_attr reset_right = { FEATURE_RESET_GAMEPAD }; +LEGO_DEVICE_ATTR_WO(reset_right, "reset", LEFT_CONTROLLER, feature_status); + static struct attribute *right_gamepad_attrs[] = { + &dev_attr_auto_sleep_time_right.attr, + &dev_attr_auto_sleep_time_right_range.attr, + &dev_attr_imu_bypass_right.attr, + &dev_attr_imu_bypass_right_index.attr, + &dev_attr_imu_enabled_right.attr, + &dev_attr_imu_enabled_right_index.attr, + &dev_attr_reset_right.attr, &dev_attr_version_hardware_right.attr, &dev_attr_version_firmware_right.attr, &dev_attr_version_gen_right.attr, @@ -600,8 +988,14 @@ static const struct attribute_group right_gamepad_attr_group = { }; /* Touchpad */ +static struct go_cfg_attr touchpad_enabled = { FEATURE_TOUCHPAD_ENABLE }; +LEGO_DEVICE_ATTR_RW(touchpad_enabled, "enabled", UNSPECIFIED, index, + feature_status); +static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); + static struct attribute *touchpad_attrs[] = { - NULL, + &dev_attr_touchpad_enabled.attr, + &dev_attr_touchpad_enabled_index.attr, }; static const struct attribute_group touchpad_attr_group = { From eacb4e93b5fcddba3f7f673bc32ff5bb0cfc55a9 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:09:28 +0200 Subject: [PATCH 203/295] HID: hid-lenovo-go: Add Rumble and Haptic Settings JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 96b20c1fe9031be04a36d58af70e4c297a928a3a Author: Derek J. Clark Date: Tue Mar 10 07:29:22 2026 +0000 HID: hid-lenovo-go: Add Rumble and Haptic Settings Adds attributes that control the handles rumble mode and intensity, as well as touchpad haptic feedback settings. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go.c | 312 ++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index d7d47db8362c..f2a54865cfbb 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -41,6 +41,8 @@ static struct hid_go_cfg { struct mutex cfg_mutex; /*ensure single synchronous output report*/ u8 fps_mode; u8 gp_left_auto_sleep_time; + u8 gp_left_notify_en; + u8 gp_left_rumble_mode; u32 gp_left_version_firmware; u8 gp_left_version_gen; u32 gp_left_version_hardware; @@ -48,11 +50,14 @@ static struct hid_go_cfg { u32 gp_left_version_protocol; u8 gp_mode; u8 gp_right_auto_sleep_time; + u8 gp_right_notify_en; + u8 gp_right_rumble_mode; u32 gp_right_version_firmware; u8 gp_right_version_gen; u32 gp_right_version_hardware; u32 gp_right_version_product; u32 gp_right_version_protocol; + u8 gp_rumble_intensity; u8 imu_left_bypass_en; u8 imu_left_sensor_en; u8 imu_right_bypass_en; @@ -64,6 +69,8 @@ static struct hid_go_cfg { u32 mcu_version_protocol; u8 rgb_en; u8 tp_en; + u8 tp_vibration_en; + u8 tp_vibration_intensity; u32 tx_dongle_version_firmware; u8 tx_dongle_version_gen; u32 tx_dongle_version_hardware; @@ -170,6 +177,49 @@ static const char *const gamepad_mode_text[] = { [DINPUT] = "dinput", }; +enum motor_cfg_index { + MOTOR_CFG_ALL = 0x01, + MOTOR_INTENSITY, + VIBRATION_NOTIFY_ENABLE, + RUMBLE_MODE, + TP_VIBRATION_ENABLE, + TP_VIBRATION_INTENSITY, +}; + +enum intensity_index { + INTENSITY_UNKNOWN, + INTENSITY_OFF, + INTENSITY_LOW, + INTENSITY_MEDIUM, + INTENSITY_HIGH, +}; + +static const char *const intensity_text[] = { + [INTENSITY_UNKNOWN] = "unknown", + [INTENSITY_OFF] = "off", + [INTENSITY_LOW] = "low", + [INTENSITY_MEDIUM] = "medium", + [INTENSITY_HIGH] = "high", +}; + +enum rumble_mode_index { + RUMBLE_MODE_UNKNOWN, + RUMBLE_MODE_FPS, + RUMBLE_MODE_RACE, + RUMBLE_MODE_AVERAGE, + RUMBLE_MODE_SPG, + RUMBLE_MODE_RPG, +}; + +static const char *const rumble_mode_text[] = { + [RUMBLE_MODE_UNKNOWN] = "unknown", + [RUMBLE_MODE_FPS] = "fps", + [RUMBLE_MODE_RACE] = "racing", + [RUMBLE_MODE_AVERAGE] = "standard", + [RUMBLE_MODE_SPG] = "spg", + [RUMBLE_MODE_RPG] = "rpg", +}; + static int hid_go_version_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { @@ -336,6 +386,47 @@ static int hid_go_feature_status_event(struct command_report *cmd_rep) } } +static int hid_go_motor_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->sub_cmd) { + case MOTOR_CFG_ALL: + return -EINVAL; + case MOTOR_INTENSITY: + drvdata.gp_rumble_intensity = cmd_rep->data[0]; + return 0; + case VIBRATION_NOTIFY_ENABLE: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.gp_left_notify_en = cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_notify_en = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + }; + break; + case RUMBLE_MODE: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.gp_left_rumble_mode = cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_rumble_mode = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + }; + case TP_VIBRATION_ENABLE: + drvdata.tp_vibration_en = cmd_rep->data[0]; + return 0; + case TP_VIBRATION_INTENSITY: + drvdata.tp_vibration_intensity = cmd_rep->data[0]; + return 0; + } + return -EINVAL; +} + static int hid_go_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] != 0) @@ -383,7 +474,11 @@ static int hid_go_raw_event(struct hid_device *hdev, struct hid_report *report, case GET_FEATURE_STATUS: ret = hid_go_feature_status_event(cmd_rep); break; + case GET_MOTOR_CFG: + ret = hid_go_motor_event(cmd_rep); + break; case SET_FEATURE_STATUS: + case SET_MOTOR_CFG: ret = hid_go_set_event_return(cmd_rep); break; default: @@ -759,6 +854,168 @@ static ssize_t feature_status_options(struct device *dev, return count; } +static ssize_t motor_config_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + enum motor_cfg_index index, + enum dev_type device_type) +{ + size_t size = 1; + u8 val = 0; + int ret; + + switch (index) { + case MOTOR_CFG_ALL: + return -EINVAL; + case MOTOR_INTENSITY: + ret = sysfs_match_string(intensity_text, buf); + val = ret; + break; + case VIBRATION_NOTIFY_ENABLE: + ret = sysfs_match_string(enabled_status_text, buf); + val = ret; + break; + case RUMBLE_MODE: + ret = sysfs_match_string(rumble_mode_text, buf); + val = ret; + break; + case TP_VIBRATION_ENABLE: + ret = sysfs_match_string(enabled_status_text, buf); + val = ret; + break; + case TP_VIBRATION_INTENSITY: + ret = sysfs_match_string(intensity_text, buf); + val = ret; + break; + }; + + if (ret < 0) + return ret; + + if (!val) + size = 0; + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, SET_MOTOR_CFG, + index, device_type, &val, size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t motor_config_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum motor_cfg_index index, + enum dev_type device_type) +{ + ssize_t count = 0; + int ret; + u8 i; + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_MOTOR_CFG, + index, device_type, NULL, 0); + if (ret) + return ret; + + switch (index) { + case MOTOR_CFG_ALL: + return -EINVAL; + case MOTOR_INTENSITY: + i = drvdata.gp_rumble_intensity; + if (i >= ARRAY_SIZE(intensity_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", intensity_text[i]); + break; + case VIBRATION_NOTIFY_ENABLE: + switch (device_type) { + case LEFT_CONTROLLER: + i = drvdata.gp_left_notify_en; + break; + case RIGHT_CONTROLLER: + i = drvdata.gp_right_notify_en; + break; + default: + return -EINVAL; + }; + if (i >= ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case RUMBLE_MODE: + switch (device_type) { + case LEFT_CONTROLLER: + i = drvdata.gp_left_rumble_mode; + break; + case RIGHT_CONTROLLER: + i = drvdata.gp_right_rumble_mode; + break; + default: + return -EINVAL; + }; + if (i >= ARRAY_SIZE(rumble_mode_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", rumble_mode_text[i]); + break; + case TP_VIBRATION_ENABLE: + i = drvdata.tp_vibration_en; + if (i >= ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case TP_VIBRATION_INTENSITY: + i = drvdata.tp_vibration_intensity; + if (i >= ARRAY_SIZE(intensity_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", intensity_text[i]); + break; + }; + + return count; +} + +static ssize_t motor_config_options(struct device *dev, + struct device_attribute *attr, char *buf, + enum motor_cfg_index index) +{ + ssize_t count = 0; + unsigned int i; + + switch (index) { + case MOTOR_CFG_ALL: + break; + case RUMBLE_MODE: + for (i = 1; i < ARRAY_SIZE(rumble_mode_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + rumble_mode_text[i]); + } + break; + case MOTOR_INTENSITY: + case TP_VIBRATION_INTENSITY: + for (i = 1; i < ARRAY_SIZE(intensity_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + intensity_text[i]); + } + break; + case VIBRATION_NOTIFY_ENABLE: + case TP_VIBRATION_ENABLE: + for (i = 1; i < ARRAY_SIZE(enabled_status_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + enabled_status_text[i]); + } + break; + }; + + if (count) + buf[count - 1] = '\n'; + + return count; +} + #define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _group) \ static ssize_t _name##_store(struct device *dev, \ struct device_attribute *attr, \ @@ -824,10 +1081,18 @@ static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mode_index"); static struct go_cfg_attr reset_mcu = { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_WO(reset_mcu, "reset_mcu", USB_MCU, feature_status); +static struct go_cfg_attr gamepad_rumble_intensity = { MOTOR_INTENSITY }; +LEGO_DEVICE_ATTR_RW(gamepad_rumble_intensity, "rumble_intensity", UNSPECIFIED, + index, motor_config); +static DEVICE_ATTR_RO_NAMED(gamepad_rumble_intensity_index, + "rumble_intensity_index"); + static struct attribute *mcu_attrs[] = { &dev_attr_fps_switch_status.attr, &dev_attr_gamepad_mode.attr, &dev_attr_gamepad_mode_index.attr, + &dev_attr_gamepad_rumble_intensity.attr, + &dev_attr_gamepad_rumble_intensity_index.attr, &dev_attr_reset_mcu.attr, &dev_attr_version_firmware_mcu.attr, &dev_attr_version_gen_mcu.attr, @@ -910,6 +1175,17 @@ static DEVICE_ATTR_RO_NAMED(imu_enabled_left_index, "imu_enabled_index"); static struct go_cfg_attr reset_left = { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_WO(reset_left, "reset", LEFT_CONTROLLER, feature_status); +static struct go_cfg_attr rumble_mode_left = { RUMBLE_MODE }; +LEGO_DEVICE_ATTR_RW(rumble_mode_left, "rumble_mode", LEFT_CONTROLLER, index, + motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_mode_left_index, "rumble_mode_index"); + +static struct go_cfg_attr rumble_notification_left = { VIBRATION_NOTIFY_ENABLE }; +LEGO_DEVICE_ATTR_RW(rumble_notification_left, "rumble_notification", + LEFT_CONTROLLER, index, motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_notification_left_index, + "rumble_notification_index"); + static struct attribute *left_gamepad_attrs[] = { &dev_attr_auto_sleep_time_left.attr, &dev_attr_auto_sleep_time_left_range.attr, @@ -918,6 +1194,10 @@ static struct attribute *left_gamepad_attrs[] = { &dev_attr_imu_enabled_left.attr, &dev_attr_imu_enabled_left_index.attr, &dev_attr_reset_left.attr, + &dev_attr_rumble_mode_left.attr, + &dev_attr_rumble_mode_left_index.attr, + &dev_attr_rumble_notification_left.attr, + &dev_attr_rumble_notification_left_index.attr, &dev_attr_version_hardware_left.attr, &dev_attr_version_firmware_left.attr, &dev_attr_version_gen_left.attr, @@ -966,6 +1246,17 @@ static DEVICE_ATTR_RO_NAMED(imu_enabled_right_index, "imu_enabled_index"); static struct go_cfg_attr reset_right = { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_WO(reset_right, "reset", LEFT_CONTROLLER, feature_status); +static struct go_cfg_attr rumble_mode_right = { RUMBLE_MODE }; +LEGO_DEVICE_ATTR_RW(rumble_mode_right, "rumble_mode", RIGHT_CONTROLLER, index, + motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_mode_right_index, "rumble_mode_index"); + +static struct go_cfg_attr rumble_notification_right = { VIBRATION_NOTIFY_ENABLE }; +LEGO_DEVICE_ATTR_RW(rumble_notification_right, "rumble_notification", + RIGHT_CONTROLLER, index, motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_notification_right_index, + "rumble_notification_index"); + static struct attribute *right_gamepad_attrs[] = { &dev_attr_auto_sleep_time_right.attr, &dev_attr_auto_sleep_time_right_range.attr, @@ -974,6 +1265,10 @@ static struct attribute *right_gamepad_attrs[] = { &dev_attr_imu_enabled_right.attr, &dev_attr_imu_enabled_right_index.attr, &dev_attr_reset_right.attr, + &dev_attr_rumble_mode_right.attr, + &dev_attr_rumble_mode_right_index.attr, + &dev_attr_rumble_notification_right.attr, + &dev_attr_rumble_notification_right_index.attr, &dev_attr_version_hardware_right.attr, &dev_attr_version_firmware_right.attr, &dev_attr_version_gen_right.attr, @@ -993,9 +1288,26 @@ LEGO_DEVICE_ATTR_RW(touchpad_enabled, "enabled", UNSPECIFIED, index, feature_status); static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); +static struct go_cfg_attr touchpad_vibration_enabled = { TP_VIBRATION_ENABLE }; +LEGO_DEVICE_ATTR_RW(touchpad_vibration_enabled, "vibration_enabled", UNSPECIFIED, + index, motor_config); +static DEVICE_ATTR_RO_NAMED(touchpad_vibration_enabled_index, + "vibration_enabled_index"); + +static struct go_cfg_attr touchpad_vibration_intensity = { TP_VIBRATION_INTENSITY }; +LEGO_DEVICE_ATTR_RW(touchpad_vibration_intensity, "vibration_intensity", + UNSPECIFIED, index, motor_config); +static DEVICE_ATTR_RO_NAMED(touchpad_vibration_intensity_index, + "vibration_intensity_index"); + static struct attribute *touchpad_attrs[] = { &dev_attr_touchpad_enabled.attr, &dev_attr_touchpad_enabled_index.attr, + &dev_attr_touchpad_vibration_enabled.attr, + &dev_attr_touchpad_vibration_enabled_index.attr, + &dev_attr_touchpad_vibration_intensity.attr, + &dev_attr_touchpad_vibration_intensity_index.attr, + NULL, }; static const struct attribute_group touchpad_attr_group = { From da741b8caa677c151311699189ccfcebb050040e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:09:29 +0200 Subject: [PATCH 204/295] HID: hid-lenovo-go: Add FPS Mode DPI settings JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit f0bedee60607597682d3125f3c0530d76568857b Author: Derek J. Clark Date: Tue Mar 10 07:29:23 2026 +0000 HID: hid-lenovo-go: Add FPS Mode DPI settings Adds attribute that enables selection of the DPI of the optical sensor when the right handle toggle is set to FPS mode. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go.c | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index f2a54865cfbb..24f9444c93cd 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -67,6 +67,7 @@ static struct hid_go_cfg { u32 mcu_version_hardware; u32 mcu_version_product; u32 mcu_version_protocol; + u32 mouse_dpi; u8 rgb_en; u8 tp_en; u8 tp_vibration_en; @@ -220,6 +221,8 @@ static const char *const rumble_mode_text[] = { [RUMBLE_MODE_RPG] = "rpg", }; +#define FPS_MODE_DPI 0x02 + static int hid_go_version_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { @@ -427,6 +430,16 @@ static int hid_go_motor_event(struct command_report *cmd_rep) return -EINVAL; } +static int hid_go_fps_dpi_event(struct command_report *cmd_rep) +{ + if (cmd_rep->sub_cmd != FPS_MODE_DPI) + return -EINVAL; + + drvdata.mouse_dpi = get_unaligned_le32(cmd_rep->data); + + return 0; +} + static int hid_go_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] != 0) @@ -477,8 +490,12 @@ static int hid_go_raw_event(struct hid_device *hdev, struct hid_report *report, case GET_MOTOR_CFG: ret = hid_go_motor_event(cmd_rep); break; + case GET_DPI_CFG: + ret = hid_go_fps_dpi_event(cmd_rep); + break; case SET_FEATURE_STATUS: case SET_MOTOR_CFG: + case SET_DPI_CFG: ret = hid_go_set_event_return(cmd_rep); break; default: @@ -1016,6 +1033,52 @@ static ssize_t motor_config_options(struct device *dev, return count; } +static ssize_t fps_mode_dpi_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) + +{ + size_t size = 4; + u32 value; + u8 val[4]; + int ret; + + ret = kstrtou32(buf, 10, &value); + if (ret) + return ret; + + if (value != 500 && value != 800 && value != 1200 && value != 1800) + return -EINVAL; + + put_unaligned_le32(value, val); + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, SET_DPI_CFG, + FPS_MODE_DPI, UNSPECIFIED, val, size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t fps_mode_dpi_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_DPI_CFG, + FPS_MODE_DPI, UNSPECIFIED, NULL, 0); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%u\n", drvdata.mouse_dpi); +} + +static ssize_t fps_mode_dpi_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "500 800 1200 1800\n"); +} + #define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _group) \ static ssize_t _name##_store(struct device *dev, \ struct device_attribute *attr, \ @@ -1087,7 +1150,12 @@ LEGO_DEVICE_ATTR_RW(gamepad_rumble_intensity, "rumble_intensity", UNSPECIFIED, static DEVICE_ATTR_RO_NAMED(gamepad_rumble_intensity_index, "rumble_intensity_index"); +static DEVICE_ATTR_RW(fps_mode_dpi); +static DEVICE_ATTR_RO(fps_mode_dpi_index); + static struct attribute *mcu_attrs[] = { + &dev_attr_fps_mode_dpi.attr, + &dev_attr_fps_mode_dpi_index.attr, &dev_attr_fps_switch_status.attr, &dev_attr_gamepad_mode.attr, &dev_attr_gamepad_mode_index.attr, From 7e1c53660f8a5e68cf07cd8b21f7ebebacda330f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:09:30 +0200 Subject: [PATCH 205/295] HID: hid-lenovo-go: Add RGB LED control interface JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 325262fa1d2e919e417fdb207bdb8d2baafcb047 Author: Derek J. Clark Date: Tue Mar 10 07:29:24 2026 +0000 HID: hid-lenovo-go: Add RGB LED control interface Adds an LED multicolor class device and attribute group for controlling the RGB of the Left and right handles. In addition to the standard led_cdev attributes, additional attributes that allow for the control of the effect (monocolor, breathe, rainbow, and chroma), speed of the effect change, an enable toggle, and profile. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go.c | 429 ++++++++++++++++++++++++++++++++++++ 1 file changed, 429 insertions(+) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index 24f9444c93cd..ee7d4bf23a17 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ static struct hid_go_cfg { struct delayed_work go_cfg_setup; struct completion send_cmd_complete; + struct led_classdev *led_cdev; struct hid_device *hdev; struct mutex cfg_mutex; /*ensure single synchronous output report*/ u8 fps_mode; @@ -68,7 +70,11 @@ static struct hid_go_cfg { u32 mcu_version_product; u32 mcu_version_protocol; u32 mouse_dpi; + u8 rgb_effect; u8 rgb_en; + u8 rgb_mode; + u8 rgb_profile; + u8 rgb_speed; u8 tp_en; u8 tp_vibration_en; u8 tp_vibration_intensity; @@ -223,6 +229,41 @@ static const char *const rumble_mode_text[] = { #define FPS_MODE_DPI 0x02 +enum rgb_config_index { + LIGHT_CFG_ALL = 0x01, + LIGHT_MODE_SEL, + LIGHT_PROFILE_SEL, + USR_LIGHT_PROFILE_1, + USR_LIGHT_PROFILE_2, + USR_LIGHT_PROFILE_3, +}; + +enum rgb_mode_index { + RGB_MODE_UNKNOWN, + RGB_MODE_DYNAMIC, + RGB_MODE_CUSTOM, +}; + +static const char *const rgb_mode_text[] = { + [RGB_MODE_UNKNOWN] = "unknown", + [RGB_MODE_DYNAMIC] = "dynamic", + [RGB_MODE_CUSTOM] = "custom", +}; + +enum rgb_effect_index { + RGB_EFFECT_MONO, + RGB_EFFECT_BREATHE, + RGB_EFFECT_CHROMA, + RGB_EFFECT_RAINBOW, +}; + +static const char *const rgb_effect_text[] = { + [RGB_EFFECT_MONO] = "monocolor", + [RGB_EFFECT_BREATHE] = "breathe", + [RGB_EFFECT_CHROMA] = "chroma", + [RGB_EFFECT_RAINBOW] = "rainbow", +}; + static int hid_go_version_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { @@ -440,6 +481,33 @@ static int hid_go_fps_dpi_event(struct command_report *cmd_rep) return 0; } +static int hid_go_light_event(struct command_report *cmd_rep) +{ + struct led_classdev_mc *mc_cdev; + + switch (cmd_rep->sub_cmd) { + case LIGHT_MODE_SEL: + drvdata.rgb_mode = cmd_rep->data[0]; + return 0; + case LIGHT_PROFILE_SEL: + drvdata.rgb_profile = cmd_rep->data[0]; + return 0; + case USR_LIGHT_PROFILE_1: + case USR_LIGHT_PROFILE_2: + case USR_LIGHT_PROFILE_3: + mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + drvdata.rgb_effect = cmd_rep->data[0]; + mc_cdev->subled_info[0].intensity = cmd_rep->data[1]; + mc_cdev->subled_info[1].intensity = cmd_rep->data[2]; + mc_cdev->subled_info[2].intensity = cmd_rep->data[3]; + drvdata.led_cdev->brightness = cmd_rep->data[4]; + drvdata.rgb_speed = 100 - cmd_rep->data[5]; + return 0; + default: + return -EINVAL; + } +} + static int hid_go_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] != 0) @@ -493,9 +561,13 @@ static int hid_go_raw_event(struct hid_device *hdev, struct hid_report *report, case GET_DPI_CFG: ret = hid_go_fps_dpi_event(cmd_rep); break; + case GET_RGB_CFG: + ret = hid_go_light_event(cmd_rep); + break; case SET_FEATURE_STATUS: case SET_MOTOR_CFG: case SET_DPI_CFG: + case SET_RGB_CFG: ret = hid_go_set_event_return(cmd_rep); break; default: @@ -565,6 +637,12 @@ static ssize_t version_show(struct device *dev, struct device_attribute *attr, enum dev_type device_type) { ssize_t count = 0; + int ret; + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + index, device_type, NULL, 0); + if (ret) + return ret; switch (index) { case PRODUCT_VERSION: @@ -1079,6 +1157,277 @@ static ssize_t fps_mode_dpi_index_show(struct device *dev, return sysfs_emit(buf, "500 800 1200 1800\n"); } +static int rgb_cfg_call(struct hid_device *hdev, enum mcu_command_index cmd, + enum rgb_config_index index, u8 *val, size_t size) +{ + if (cmd != SET_RGB_CFG && cmd != GET_RGB_CFG) + return -EINVAL; + + if (index < LIGHT_CFG_ALL || index > USR_LIGHT_PROFILE_3) + return -EINVAL; + + return mcu_property_out(hdev, MCU_CONFIG_DATA, cmd, index, UNSPECIFIED, + val, size); +} + +static int rgb_attr_show(void) +{ + enum rgb_config_index index; + + index = drvdata.rgb_profile + 3; + + return rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, index, NULL, 0); +}; + +static ssize_t rgb_effect_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + u8 effect; + int ret; + + ret = sysfs_match_string(rgb_effect_text, buf); + if (ret < 0) + return ret; + + effect = ret; + index = drvdata.rgb_profile + 3; + u8 rgb_profile[6] = { effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + drvdata.led_cdev->brightness, + drvdata.rgb_speed }; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + if (ret) + return ret; + + drvdata.rgb_effect = effect; + return count; +}; + +static ssize_t rgb_effect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret = rgb_attr_show(); + if (ret) + return ret; + + if (drvdata.rgb_effect >= ARRAY_SIZE(rgb_effect_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", rgb_effect_text[drvdata.rgb_effect]); +} + +static ssize_t rgb_effect_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rgb_effect_text); i++) + count += sysfs_emit_at(buf, count, "%s ", rgb_effect_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t rgb_speed_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + int val = 0; + int ret; + + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; + + if (val < 0 || val > 100) + return -EINVAL; + + /* This is a delay setting, invert logic for consistency with other drivers */ + val = 100 - val; + + index = drvdata.rgb_profile + 3; + u8 rgb_profile[6] = { drvdata.rgb_effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + drvdata.led_cdev->brightness, + val }; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + if (ret) + return ret; + + drvdata.rgb_speed = val; + + return count; +}; + +static ssize_t rgb_speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret, val; + + ret = rgb_attr_show(); + if (ret) + return ret; + + if (drvdata.rgb_speed > 100) + return -EINVAL; + + val = drvdata.rgb_speed; + + return sysfs_emit(buf, "%hhu\n", val); +} + +static ssize_t rgb_speed_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0-100\n"); +} + +static ssize_t rgb_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret = sysfs_match_string(rgb_mode_text, buf); + if (ret <= 0) + return ret; + + val = ret; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_MODE_SEL, &val, 1); + if (ret) + return ret; + + drvdata.rgb_mode = val; + + return count; +}; + +static ssize_t rgb_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + + ret = rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_MODE_SEL, NULL, 0); + if (ret) + return ret; + + if (drvdata.rgb_mode >= ARRAY_SIZE(rgb_mode_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", rgb_mode_text[drvdata.rgb_mode]); +}; + +static ssize_t rgb_mode_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(rgb_mode_text); i++) + count += sysfs_emit_at(buf, count, "%s ", rgb_mode_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t rgb_profile_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + size_t size = 1; + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret < 0) + return ret; + + if (val < 1 || val > 3) + return -EINVAL; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_PROFILE_SEL, &val, size); + if (ret) + return ret; + + drvdata.rgb_profile = val; + + return count; +}; + +static ssize_t rgb_profile_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret = rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_PROFILE_SEL, NULL, 0); + if (ret) + return ret; + + if (drvdata.rgb_profile < 1 || drvdata.rgb_profile > 3) + return -EINVAL; + + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_profile); +}; + +static ssize_t rgb_profile_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "1-3\n"); +} + +static void hid_go_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + int ret; + + if (brightness > led_cdev->max_brightness) { + dev_err(led_cdev->dev, "Invalid argument\n"); + return; + } + + index = drvdata.rgb_profile + 3; + u8 rgb_profile[6] = { drvdata.rgb_effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + brightness, + drvdata.rgb_speed }; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + switch (ret) { + case 0: + led_cdev->brightness = brightness; + break; + case -ENODEV: /* during switch to IAP -ENODEV is expected */ + case -ENOSYS: /* during rmmod -ENOSYS is expected */ + dev_dbg(led_cdev->dev, "Failed to write RGB profile: %i\n", ret); + break; + default: + dev_err(led_cdev->dev, "Failed to write RGB profile: %i\n", ret); + }; +} + #define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _group) \ static ssize_t _name##_store(struct device *dev, \ struct device_attribute *attr, \ @@ -1389,6 +1738,71 @@ static const struct attribute_group *top_level_attr_groups[] = { &touchpad_attr_group, NULL, }; +/* RGB */ +static struct go_cfg_attr rgb_enabled = { FEATURE_LIGHT_ENABLE }; + +LEGO_DEVICE_ATTR_RW(rgb_enabled, "enabled", UNSPECIFIED, index, feature_status); +static DEVICE_ATTR_RO_NAMED(rgb_effect_index, "effect_index"); +static DEVICE_ATTR_RO_NAMED(rgb_enabled_index, "enabled_index"); +static DEVICE_ATTR_RO_NAMED(rgb_mode_index, "mode_index"); +static DEVICE_ATTR_RO_NAMED(rgb_profile_range, "profile_range"); +static DEVICE_ATTR_RO_NAMED(rgb_speed_range, "speed_range"); +static DEVICE_ATTR_RW_NAMED(rgb_effect, "effect"); +static DEVICE_ATTR_RW_NAMED(rgb_mode, "mode"); +static DEVICE_ATTR_RW_NAMED(rgb_profile, "profile"); +static DEVICE_ATTR_RW_NAMED(rgb_speed, "speed"); + +static struct attribute *go_rgb_attrs[] = { + &dev_attr_rgb_effect.attr, + &dev_attr_rgb_effect_index.attr, + &dev_attr_rgb_enabled.attr, + &dev_attr_rgb_enabled_index.attr, + &dev_attr_rgb_mode.attr, + &dev_attr_rgb_mode_index.attr, + &dev_attr_rgb_profile.attr, + &dev_attr_rgb_profile_range.attr, + &dev_attr_rgb_speed.attr, + &dev_attr_rgb_speed_range.attr, + NULL, +}; + +static struct attribute_group rgb_attr_group = { + .attrs = go_rgb_attrs, +}; + +static struct mc_subled go_rgb_subled_info[] = { + { + .color_index = LED_COLOR_ID_RED, + .brightness = 0x50, + .intensity = 0x24, + .channel = 0x1, + }, + { + .color_index = LED_COLOR_ID_GREEN, + .brightness = 0x50, + .intensity = 0x22, + .channel = 0x2, + }, + { + .color_index = LED_COLOR_ID_BLUE, + .brightness = 0x50, + .intensity = 0x99, + .channel = 0x3, + }, +}; + +static struct led_classdev_mc go_cdev_rgb = { + .led_cdev = { + .name = "go:rgb:joystick_rings", + .color = LED_COLOR_ID_RGB, + .brightness = 0x50, + .max_brightness = 0x64, + .brightness_set = hid_go_brightness_set, + }, + .num_colors = ARRAY_SIZE(go_rgb_subled_info), + .subled_info = go_rgb_subled_info, +}; + static void cfg_setup(struct work_struct *work) { int ret; @@ -1579,6 +1993,21 @@ static int hid_go_cfg_probe(struct hid_device *hdev, return ret; } + ret = devm_led_classdev_multicolor_register(&hdev->dev, &go_cdev_rgb); + if (ret) { + dev_err_probe(&hdev->dev, ret, "Failed to create RGB device\n"); + return ret; + } + + ret = devm_device_add_group(go_cdev_rgb.led_cdev.dev, &rgb_attr_group); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create RGB configuration attributes\n"); + return ret; + } + + drvdata.led_cdev = &go_cdev_rgb.led_cdev; + init_completion(&drvdata.send_cmd_complete); /* Executing calls prior to returning from probe will lock the MCU. Schedule From 3d9d3c289243ec1e1af629d2f32518d8d73042d7 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:09:30 +0200 Subject: [PATCH 206/295] HID: hid-lenovo-go: Add Calibration Settings JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 995887a10da1f6e23a52fe656b30ba6cf1a56316 Author: Derek J. Clark Date: Tue Mar 10 07:29:25 2026 +0000 HID: hid-lenovo-go: Add Calibration Settings Adds calibration enable and last calibration status indicators for the triggers, joysticks, and handle gyros. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go.c | 284 +++++++++++++++++++++++++++++++++++- 1 file changed, 283 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index ee7d4bf23a17..082d1b85d679 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -43,8 +43,11 @@ static struct hid_go_cfg { struct mutex cfg_mutex; /*ensure single synchronous output report*/ u8 fps_mode; u8 gp_left_auto_sleep_time; + u8 gp_left_gyro_cal_status; + u8 gp_left_joy_cal_status; u8 gp_left_notify_en; u8 gp_left_rumble_mode; + u8 gp_left_trigg_cal_status; u32 gp_left_version_firmware; u8 gp_left_version_gen; u32 gp_left_version_hardware; @@ -52,8 +55,11 @@ static struct hid_go_cfg { u32 gp_left_version_protocol; u8 gp_mode; u8 gp_right_auto_sleep_time; + u8 gp_right_gyro_cal_status; + u8 gp_right_joy_cal_status; u8 gp_right_notify_en; u8 gp_right_rumble_mode; + u8 gp_right_trigg_cal_status; u32 gp_right_version_firmware; u8 gp_right_version_gen; u32 gp_right_version_hardware; @@ -227,7 +233,41 @@ static const char *const rumble_mode_text[] = { [RUMBLE_MODE_RPG] = "rpg", }; -#define FPS_MODE_DPI 0x02 +#define FPS_MODE_DPI 0x02 +#define TRIGGER_CALIBRATE 0x04 +#define JOYSTICK_CALIBRATE 0x04 +#define GYRO_CALIBRATE 0x06 + +enum cal_device_type { + CALDEV_GYROSCOPE = 0x01, + CALDEV_JOYSTICK, + CALDEV_TRIGGER, + CALDEV_JOY_TRIGGER, +}; + +enum cal_enable { + CAL_UNKNOWN, + CAL_START, + CAL_STOP, +}; + +static const char *const cal_enabled_text[] = { + [CAL_UNKNOWN] = "unknown", + [CAL_START] = "start", + [CAL_STOP] = "stop", +}; + +enum cal_status_index { + CAL_STAT_UNKNOWN, + CAL_STAT_SUCCESS, + CAL_STAT_FAILURE, +}; + +static const char *const cal_status_text[] = { + [CAL_STAT_UNKNOWN] = "unknown", + [CAL_STAT_SUCCESS] = "success", + [CAL_STAT_FAILURE] = "failure", +}; enum rgb_config_index { LIGHT_CFG_ALL = 0x01, @@ -264,6 +304,13 @@ static const char *const rgb_effect_text[] = { [RGB_EFFECT_RAINBOW] = "rainbow", }; +enum device_status_index { + GET_CAL_STATUS = 0x02, + GET_UPGRADE_STATUS, + GET_MACRO_REC_STATUS, + GET_HOTKEY_TRIGG_STATUS, +}; + static int hid_go_version_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { @@ -508,6 +555,44 @@ static int hid_go_light_event(struct command_report *cmd_rep) } } +static int hid_go_device_status_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + switch (cmd_rep->data[0]) { + case CALDEV_GYROSCOPE: + drvdata.gp_left_gyro_cal_status = cmd_rep->data[1]; + return 0; + case CALDEV_JOYSTICK: + drvdata.gp_left_joy_cal_status = cmd_rep->data[1]; + return 0; + case CALDEV_TRIGGER: + drvdata.gp_left_trigg_cal_status = cmd_rep->data[1]; + return 0; + default: + return -EINVAL; + } + break; + case RIGHT_CONTROLLER: + switch (cmd_rep->data[0]) { + case CALDEV_GYROSCOPE: + drvdata.gp_right_gyro_cal_status = cmd_rep->data[1]; + return 0; + case CALDEV_JOYSTICK: + drvdata.gp_right_joy_cal_status = cmd_rep->data[1]; + return 0; + case CALDEV_TRIGGER: + drvdata.gp_right_trigg_cal_status = cmd_rep->data[1]; + return 0; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } +} + static int hid_go_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] != 0) @@ -564,10 +649,16 @@ static int hid_go_raw_event(struct hid_device *hdev, struct hid_report *report, case GET_RGB_CFG: ret = hid_go_light_event(cmd_rep); break; + case GET_DEVICE_STATUS: + ret = hid_go_device_status_event(cmd_rep); + break; case SET_FEATURE_STATUS: case SET_MOTOR_CFG: case SET_DPI_CFG: case SET_RGB_CFG: + case SET_TRIGGER_CFG: + case SET_JOYSTICK_CFG: + case SET_GYRO_CFG: ret = hid_go_set_event_return(cmd_rep); break; default: @@ -1157,6 +1248,101 @@ static ssize_t fps_mode_dpi_index_show(struct device *dev, return sysfs_emit(buf, "500 800 1200 1800\n"); } +static ssize_t device_status_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum device_status_index index, + enum dev_type device_type, + enum cal_device_type cal_type) +{ + u8 i; + + switch (index) { + case GET_CAL_STATUS: + switch (device_type) { + case LEFT_CONTROLLER: + switch (cal_type) { + case CALDEV_GYROSCOPE: + i = drvdata.gp_left_gyro_cal_status; + break; + case CALDEV_JOYSTICK: + i = drvdata.gp_left_joy_cal_status; + break; + case CALDEV_TRIGGER: + i = drvdata.gp_left_trigg_cal_status; + break; + default: + return -EINVAL; + } + break; + case RIGHT_CONTROLLER: + switch (cal_type) { + case CALDEV_GYROSCOPE: + i = drvdata.gp_right_gyro_cal_status; + break; + case CALDEV_JOYSTICK: + i = drvdata.gp_right_joy_cal_status; + break; + case CALDEV_TRIGGER: + i = drvdata.gp_right_trigg_cal_status; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + }; + + if (i >= ARRAY_SIZE(cal_status_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", cal_status_text[i]); +} + +static ssize_t calibrate_config_store(struct device *dev, + struct device_attribute *attr, + const char *buf, u8 cmd, u8 sub_cmd, + size_t count, enum dev_type device_type) +{ + size_t size = 1; + u8 val = 0; + int ret; + + ret = sysfs_match_string(cal_enabled_text, buf); + if (ret < 0) + return ret; + + val = ret; + if (!val) + size = 0; + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, cmd, sub_cmd, + device_type, &val, size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t calibrate_config_options(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(cal_enabled_text); i++) + count += sysfs_emit_at(buf, count, "%s ", cal_enabled_text[i]); + + buf[count - 1] = '\n'; + + return count; +} + static int rgb_cfg_call(struct hid_device *hdev, enum mcu_command_index cmd, enum rgb_config_index index, u8 *val, size_t size) { @@ -1466,6 +1652,30 @@ static void hid_go_brightness_set(struct led_classdev *led_cdev, } \ static DEVICE_ATTR_RO_NAMED(_name, _attrname) +#define LEGO_CAL_DEVICE_ATTR(_name, _attrname, _scmd, _dtype, _rtype) \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return calibrate_config_store(dev, attr, buf, _name.index, \ + _scmd, count, _dtype); \ + } \ + static ssize_t _name##_##_rtype##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return calibrate_config_options(dev, attr, buf); \ + } \ + static DEVICE_ATTR_WO_NAMED(_name, _attrname) + +#define LEGO_DEVICE_STATUS_ATTR(_name, _attrname, _scmd, _dtype) \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return device_status_show(dev, attr, buf, _name.index, _scmd, \ + _dtype); \ + } \ + static DEVICE_ATTR_RO_NAMED(_name, _attrname) + /* Gamepad - MCU */ static struct go_cfg_attr version_product_mcu = { PRODUCT_VERSION }; LEGO_DEVICE_ATTR_RO(version_product_mcu, "product_version", USB_MCU, version); @@ -1603,9 +1813,45 @@ LEGO_DEVICE_ATTR_RW(rumble_notification_left, "rumble_notification", static DEVICE_ATTR_RO_NAMED(rumble_notification_left_index, "rumble_notification_index"); +static struct go_cfg_attr cal_trigg_left = { TRIGGER_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_trigg_left, "calibrate_trigger", SET_TRIGGER_CFG, + LEFT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_trigg_left_index, "calibrate_trigger_index"); + +static struct go_cfg_attr cal_joy_left = { JOYSTICK_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_joy_left, "calibrate_joystick", SET_JOYSTICK_CFG, + LEFT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_joy_left_index, "calibrate_joystick_index"); + +static struct go_cfg_attr cal_gyro_left = { GYRO_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_gyro_left, "calibrate_gyro", SET_GYRO_CFG, + LEFT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_gyro_left_index, "calibrate_gyro_index"); + +static struct go_cfg_attr cal_trigg_left_status = { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_trigg_left_status, "calibrate_trigger_status", + LEFT_CONTROLLER, CALDEV_TRIGGER); + +static struct go_cfg_attr cal_joy_left_status = { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_joy_left_status, "calibrate_joystick_status", + LEFT_CONTROLLER, CALDEV_JOYSTICK); + +static struct go_cfg_attr cal_gyro_left_status = { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_gyro_left_status, "calibrate_gyro_status", + LEFT_CONTROLLER, CALDEV_GYROSCOPE); + static struct attribute *left_gamepad_attrs[] = { &dev_attr_auto_sleep_time_left.attr, &dev_attr_auto_sleep_time_left_range.attr, + &dev_attr_cal_gyro_left.attr, + &dev_attr_cal_gyro_left_index.attr, + &dev_attr_cal_gyro_left_status.attr, + &dev_attr_cal_joy_left.attr, + &dev_attr_cal_joy_left_index.attr, + &dev_attr_cal_joy_left_status.attr, + &dev_attr_cal_trigg_left.attr, + &dev_attr_cal_trigg_left_index.attr, + &dev_attr_cal_trigg_left_status.attr, &dev_attr_imu_bypass_left.attr, &dev_attr_imu_bypass_left_index.attr, &dev_attr_imu_enabled_left.attr, @@ -1674,9 +1920,45 @@ LEGO_DEVICE_ATTR_RW(rumble_notification_right, "rumble_notification", static DEVICE_ATTR_RO_NAMED(rumble_notification_right_index, "rumble_notification_index"); +static struct go_cfg_attr cal_trigg_right = { TRIGGER_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_trigg_right, "calibrate_trigger", SET_TRIGGER_CFG, + RIGHT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_trigg_right_index, "calibrate_trigger_index"); + +static struct go_cfg_attr cal_joy_right = { JOYSTICK_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_joy_right, "calibrate_joystick", SET_JOYSTICK_CFG, + RIGHT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_joy_right_index, "calibrate_joystick_index"); + +static struct go_cfg_attr cal_gyro_right = { GYRO_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_gyro_right, "calibrate_gyro", SET_GYRO_CFG, + RIGHT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_gyro_right_index, "calibrate_gyro_index"); + +static struct go_cfg_attr cal_trigg_right_status = { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_trigg_right_status, "calibrate_trigger_status", + RIGHT_CONTROLLER, CALDEV_TRIGGER); + +static struct go_cfg_attr cal_joy_right_status = { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_joy_right_status, "calibrate_joystick_status", + RIGHT_CONTROLLER, CALDEV_JOYSTICK); + +static struct go_cfg_attr cal_gyro_right_status = { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_gyro_right_status, "calibrate_gyro_status", + RIGHT_CONTROLLER, CALDEV_GYROSCOPE); + static struct attribute *right_gamepad_attrs[] = { &dev_attr_auto_sleep_time_right.attr, &dev_attr_auto_sleep_time_right_range.attr, + &dev_attr_cal_gyro_right.attr, + &dev_attr_cal_gyro_right_index.attr, + &dev_attr_cal_gyro_right_status.attr, + &dev_attr_cal_joy_right.attr, + &dev_attr_cal_joy_right_index.attr, + &dev_attr_cal_joy_right_status.attr, + &dev_attr_cal_trigg_right.attr, + &dev_attr_cal_trigg_right_index.attr, + &dev_attr_cal_trigg_right_status.attr, &dev_attr_imu_bypass_right.attr, &dev_attr_imu_bypass_right_index.attr, &dev_attr_imu_enabled_right.attr, From 863e47793fddb26bf39462e89155716c5c95a9fb Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:09:31 +0200 Subject: [PATCH 207/295] HID: hid-lenovo-go: Add OS Mode Toggle JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 51e4270c9d0454b8c384a656ee9e37f0f54b58ac Author: Derek J. Clark Date: Tue Mar 10 07:29:26 2026 +0000 HID: hid-lenovo-go: Add OS Mode Toggle Adds OS Mode toggle, who's primary function is to change the built-in functional chords to use the right handle legion button instead of the left handle legion button as the mode shift key. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go.c | 101 ++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index 082d1b85d679..54861f2e04fc 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -76,6 +76,7 @@ static struct hid_go_cfg { u32 mcu_version_product; u32 mcu_version_protocol; u32 mouse_dpi; + u8 os_mode; u8 rgb_effect; u8 rgb_en; u8 rgb_mode; @@ -166,6 +167,8 @@ enum feature_status_index { FEATURE_GAMEPAD_MODE = 0x0e, }; +#define FEATURE_OS_MODE 0x69 + enum fps_switch_status_index { FPS_STATUS_UNKNOWN, GAMEPAD, @@ -311,6 +314,23 @@ enum device_status_index { GET_HOTKEY_TRIGG_STATUS, }; +enum os_mode_cfg_index { + SET_OS_MODE = 0x09, + GET_OS_MODE, +}; + +enum os_mode_type_index { + OS_UNKNOWN, + WINDOWS, + LINUX, +}; + +static const char *const os_mode_text[] = { + [OS_UNKNOWN] = "unknown", + [WINDOWS] = "windows", + [LINUX] = "linux", +}; + static int hid_go_version_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { @@ -593,6 +613,21 @@ static int hid_go_device_status_event(struct command_report *cmd_rep) } } +static int hid_go_os_mode_cfg_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->sub_cmd) { + case SET_OS_MODE: + if (cmd_rep->data[0] != 1) + return -EIO; + return 0; + case GET_OS_MODE: + drvdata.os_mode = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + }; +} + static int hid_go_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] != 0) @@ -666,6 +701,9 @@ static int hid_go_raw_event(struct hid_device *hdev, struct hid_report *report, break; }; break; + case OS_MODE_DATA: + ret = hid_go_os_mode_cfg_event(cmd_rep); + break; default: goto passthrough; }; @@ -1343,6 +1381,64 @@ static ssize_t calibrate_config_options(struct device *dev, return count; } +static ssize_t os_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + size_t size = 1; + int ret; + u8 val; + + ret = sysfs_match_string(os_mode_text, buf); + if (ret <= 0) + return ret; + + val = ret; + ret = mcu_property_out(drvdata.hdev, OS_MODE_DATA, FEATURE_OS_MODE, + SET_OS_MODE, USB_MCU, &val, size); + if (ret < 0) + return ret; + + drvdata.os_mode = val; + + return count; +} + +static ssize_t os_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + ssize_t count = 0; + int ret; + u8 i; + + ret = mcu_property_out(drvdata.hdev, OS_MODE_DATA, FEATURE_OS_MODE, + GET_OS_MODE, USB_MCU, NULL, 0); + if (ret) + return ret; + + i = drvdata.os_mode; + if (i >= ARRAY_SIZE(os_mode_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", os_mode_text[i]); + + return count; +} + +static ssize_t os_mode_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(os_mode_text); i++) + count += sysfs_emit_at(buf, count, "%s ", os_mode_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} + static int rgb_cfg_call(struct hid_device *hdev, enum mcu_command_index cmd, enum rgb_config_index index, u8 *val, size_t size) { @@ -1712,6 +1808,9 @@ static DEVICE_ATTR_RO_NAMED(gamepad_rumble_intensity_index, static DEVICE_ATTR_RW(fps_mode_dpi); static DEVICE_ATTR_RO(fps_mode_dpi_index); +static DEVICE_ATTR_RW(os_mode); +static DEVICE_ATTR_RO(os_mode_index); + static struct attribute *mcu_attrs[] = { &dev_attr_fps_mode_dpi.attr, &dev_attr_fps_mode_dpi_index.attr, @@ -1720,6 +1819,8 @@ static struct attribute *mcu_attrs[] = { &dev_attr_gamepad_mode_index.attr, &dev_attr_gamepad_rumble_intensity.attr, &dev_attr_gamepad_rumble_intensity_index.attr, + &dev_attr_os_mode.attr, + &dev_attr_os_mode_index.attr, &dev_attr_reset_mcu.attr, &dev_attr_version_firmware_mcu.attr, &dev_attr_version_gen_mcu.attr, From 6d94e125a0319d0ae7a8e87537c6c42ad3fe7e01 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:09:32 +0200 Subject: [PATCH 208/295] HID: Include firmware version in the uevent JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 6ca9029c823b7853e980585e757343e0e84227cd Author: Mario Limonciello Date: Tue Mar 10 07:29:27 2026 +0000 HID: Include firmware version in the uevent Userspace software fwupd probes some HID devices when the daemon starts up to determine the current firmware version in order to be able to offer updated firmware if the manufacturer has made it available. In order to do this fwupd will detach the existing kernel driver if one is present, send a HID command and then reattach the kernel driver. This can be problematic if the user is using the HID device at the time that fwupd probes the hardware and can cause a few frames of input to be dropped. In some cases HID drivers already have a command to look up the firmware version, and so if that is exported to userspace fwupd can discover it and avoid needing to detach the kernel driver until it's time to update the device. Introduce a new member in the struct hid_device for the version and export a new uevent variable HID_FIRMWARE_VERSION that will display the version that HID drivers obtained. Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Cc: Richard Hughes Signed-off-by: Mario Limonciello Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 5 +++++ include/linux/hid.h | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index b1036a794a57..24efe3b9d805 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2888,6 +2888,11 @@ static int hid_uevent(const struct device *dev, struct kobj_uevent_env *env) if (add_uevent_var(env, "MODALIAS=hid:b%04Xg%04Xv%08Xp%08X", hdev->bus, hdev->group, hdev->vendor, hdev->product)) return -ENOMEM; + if (hdev->firmware_version) { + if (add_uevent_var(env, "HID_FIRMWARE_VERSION=0x%04llX", + hdev->firmware_version)) + return -ENOMEM; + } return 0; } diff --git a/include/linux/hid.h b/include/linux/hid.h index 31324609af4d..44357295b7a1 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -699,6 +699,7 @@ struct hid_device { char name[128]; /* Device name */ char phys[64]; /* Device physical location */ char uniq[64]; /* Device unique identifier (serial #) */ + u64 firmware_version; /* Firmware version */ void *driver_data; From 6b0b03cd60846a58529b41dd1d536e3b299ab06c Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:01 +0200 Subject: [PATCH 209/295] HID: hid-lenovo-go-s: Add Lenovo Legion Go S Series HID Driver JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit a23f3497bf208c59adac1bc8fd1d8eae240b3001 Author: Derek J. Clark Date: Tue Mar 10 07:29:28 2026 +0000 HID: hid-lenovo-go-s: Add Lenovo Legion Go S Series HID Driver Adds initial framework for a new HID driver, hid-lenovo-go-s, along with a uevent to report the firmware version for the MCU. This driver primarily provides access to the configurable settings of the Lenovo Legion Go S controller. It will attach if the controller is in xinput or dinput mode. Non-configuration raw reports are forwarded to ensure the other endpoints continue to function as normal. Reviewed-by: Mark Pearson Co-developed-by: Mario Limonciello Signed-off-by: Mario Limonciello Co-developed-by: Ethan Tidmore Signed-off-by: Ethan Tidmore Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- MAINTAINERS | 1 + drivers/hid/Kconfig | 12 ++ drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 4 + drivers/hid/hid-lenovo-go-s.c | 278 ++++++++++++++++++++++++++++++++++ 5 files changed, 296 insertions(+) create mode 100644 drivers/hid/hid-lenovo-go-s.c diff --git a/MAINTAINERS b/MAINTAINERS index 32d90085d478..52cdfafe37c2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13061,6 +13061,7 @@ M: Derek J. Clark M: Mark Pearson L: linux-input@vger.kernel.org S: Maintained +F: drivers/hid/hid-lenovo-go-s.c F: drivers/hid/hid-lenovo-go.c F: drivers/hid/hid-lenovo.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index b37a854ae491..f3b5332ae82a 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -635,6 +635,18 @@ config HID_LENOVO_GO and Legion Go 2 Handheld Console Controllers. Say M here to compile this driver as a module. The module will be called hid-lenovo-go. +config HID_LENOVO_GO_S + tristate "HID Driver for Lenovo Legion Go S Controller" + depends on USB_HID + select LEDS_CLASS + select LEDS_CLASS_MULTICOLOR + help + Support for Lenovo Legion Go S Handheld Console Controller. + + Say Y here to include configuration interface support for the Lenovo Legion Go + S. Say M here to compile this driver as a module. The module will be called + hid-lenovo-go-s. + config HID_LETSKETCH tristate "Letsketch WP9620N tablets" depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 79fbe4e3e2f4..07dfdb6a49c5 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -77,6 +77,7 @@ obj-$(CONFIG_HID_KYSONA) += hid-kysona.o obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o obj-$(CONFIG_HID_LENOVO) += hid-lenovo.o obj-$(CONFIG_HID_LENOVO_GO) += hid-lenovo-go.o +obj-$(CONFIG_HID_LENOVO_GO_S) += hid-lenovo-go-s.o obj-$(CONFIG_HID_LETSKETCH) += hid-letsketch.o obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o obj-$(CONFIG_HID_LOGITECH) += hid-lg-g15.o diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 4bf3284bd4de..f2eaaaffb8c8 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -740,6 +740,10 @@ #define USB_DEVICE_ID_ITE8595 0x8595 #define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50 +#define USB_VENDOR_ID_QHE 0x1a86 +#define USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT 0xe310 +#define USB_DEVICE_ID_LENOVO_LEGION_GO_S_DINPUT 0xe311 + #define USB_VENDOR_ID_JABRA 0x0b0e #define USB_DEVICE_ID_JABRA_SPEAK_410 0x0412 #define USB_DEVICE_ID_JABRA_SPEAK_510 0x0420 diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c new file mode 100644 index 000000000000..c9f57dfa145a --- /dev/null +++ b/drivers/hid/hid-lenovo-go-s.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Lenovo Legion Go S devices. + * + * Copyright (c) 2026 Derek J. Clark + * Copyright (c) 2026 Valve Corporation + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" + +#define GO_S_CFG_INTF_IN 0x84 +#define GO_S_PACKET_SIZE 64 + +static struct hid_gos_cfg { + struct delayed_work gos_cfg_setup; + struct completion send_cmd_complete; + struct hid_device *hdev; + struct mutex cfg_mutex; /*ensure single synchronous output report*/ +} drvdata; + +struct command_report { + u8 cmd; + u8 sub_cmd; + u8 data[63]; +} __packed; + +struct version_report { + u8 cmd; + u32 version; + u8 reserved[59]; +} __packed; + +enum mcu_command_index { + GET_VERSION = 0x01, + GET_MCU_ID, + GET_GAMEPAD_CFG, + SET_GAMEPAD_CFG, + GET_TP_PARAM, + SET_TP_PARAM, + GET_RGB_CFG = 0x0f, + SET_RGB_CFG, + GET_PL_TEST = 0xdf, +}; + +#define FEATURE_NONE 0x00 + +static int hid_gos_version_event(u8 *data) +{ + struct version_report *ver_rep = (struct version_report *)data; + + drvdata.hdev->firmware_version = get_unaligned_le32(&ver_rep->version); + return 0; +} + +static int get_endpoint_address(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_host_endpoint *ep; + + if (intf) { + ep = intf->cur_altsetting->endpoint; + if (ep) + return ep->desc.bEndpointAddress; + } + + return -ENODEV; +} + +static int hid_gos_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct command_report *cmd_rep; + int ep, ret; + + ep = get_endpoint_address(hdev); + if (ep != GO_S_CFG_INTF_IN) + return 0; + + if (size != GO_S_PACKET_SIZE) + return -EINVAL; + + cmd_rep = (struct command_report *)data; + + switch (cmd_rep->cmd) { + case GET_VERSION: + ret = hid_gos_version_event(data); + break; + default: + ret = -EINVAL; + break; + } + dev_dbg(&hdev->dev, "Rx data as raw input report: [%*ph]\n", + GO_S_PACKET_SIZE, data); + + complete(&drvdata.send_cmd_complete); + return ret; +} + +static int mcu_property_out(struct hid_device *hdev, u8 command, u8 index, + u8 *data, size_t len) +{ + unsigned char *dmabuf __free(kfree) = NULL; + u8 header[] = { command, index }; + size_t header_size = ARRAY_SIZE(header); + int timeout, ret; + + if (header_size + len > GO_S_PACKET_SIZE) + return -EINVAL; + + guard(mutex)(&drvdata.cfg_mutex); + /* We can't use a devm_alloc reusable buffer without side effects during suspend */ + dmabuf = kzalloc(GO_S_PACKET_SIZE, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + memcpy(dmabuf, header, header_size); + memcpy(dmabuf + header_size, data, len); + + dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n", + GO_S_PACKET_SIZE, dmabuf); + + ret = hid_hw_output_report(hdev, dmabuf, GO_S_PACKET_SIZE); + if (ret < 0) + return ret; + + ret = ret == GO_S_PACKET_SIZE ? 0 : -EINVAL; + if (ret) + return ret; + + /* PL_TEST commands can take longer because they go out to another device */ + timeout = (command == GET_PL_TEST) ? 200 : 5; + ret = wait_for_completion_interruptible_timeout(&drvdata.send_cmd_complete, + msecs_to_jiffies(timeout)); + + if (ret == 0) /* timeout occurred */ + ret = -EBUSY; + + reinit_completion(&drvdata.send_cmd_complete); + return 0; +} + +static void cfg_setup(struct work_struct *work) +{ + int ret; + + ret = mcu_property_out(drvdata.hdev, GET_VERSION, FEATURE_NONE, NULL, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, "Failed to retrieve MCU Version: %i\n", ret); + return; + } +} + +static int hid_gos_cfg_probe(struct hid_device *hdev, + const struct hid_device_id *_id) +{ + int ret; + + hid_set_drvdata(hdev, &drvdata); + drvdata.hdev = hdev; + mutex_init(&drvdata.cfg_mutex); + + init_completion(&drvdata.send_cmd_complete); + + /* Executing calls prior to returning from probe will lock the MCU. Schedule + * initial data call after probe has completed and MCU can accept calls. + */ + INIT_DELAYED_WORK(&drvdata.gos_cfg_setup, &cfg_setup); + ret = schedule_delayed_work(&drvdata.gos_cfg_setup, msecs_to_jiffies(2)); + if (!ret) { + dev_err(&hdev->dev, "Failed to schedule startup delayed work\n"); + return -ENODEV; + } + + return 0; +} + +static void hid_gos_cfg_remove(struct hid_device *hdev) +{ + guard(mutex)(&drvdata.cfg_mutex); + cancel_delayed_work_sync(&drvdata.gos_cfg_setup); + hid_hw_close(hdev); + hid_hw_stop(hdev); + hid_set_drvdata(hdev, NULL); +} + +static int hid_gos_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret, ep; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "Parse failed\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) { + hid_err(hdev, "Failed to start HID device\n"); + return ret; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "Failed to open HID device\n"); + hid_hw_stop(hdev); + return ret; + } + + ep = get_endpoint_address(hdev); + if (ep != GO_S_CFG_INTF_IN) { + dev_dbg(&hdev->dev, "Started interface %x as generic HID device.\n", ep); + return 0; + } + + ret = hid_gos_cfg_probe(hdev, id); + if (ret) + dev_err_probe(&hdev->dev, ret, "Failed to start configuration interface"); + + dev_dbg(&hdev->dev, "Started interface %x as Go S configuration interface\n", ep); + return ret; +} + +static void hid_gos_remove(struct hid_device *hdev) +{ + int ep = get_endpoint_address(hdev); + + switch (ep) { + case GO_S_CFG_INTF_IN: + hid_gos_cfg_remove(hdev); + break; + default: + hid_hw_close(hdev); + hid_hw_stop(hdev); + + break; + } +} + +static const struct hid_device_id hid_gos_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_QHE, + USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_QHE, + USB_DEVICE_ID_LENOVO_LEGION_GO_S_DINPUT) }, + {} +}; + +MODULE_DEVICE_TABLE(hid, hid_gos_devices); +static struct hid_driver hid_lenovo_go_s = { + .name = "hid-lenovo-go-s", + .id_table = hid_gos_devices, + .probe = hid_gos_probe, + .remove = hid_gos_remove, + .raw_event = hid_gos_raw_event, +}; +module_hid_driver(hid_lenovo_go_s); + +MODULE_AUTHOR("Derek J. Clark"); +MODULE_DESCRIPTION("HID Driver for Lenovo Legion Go S Series gamepad."); +MODULE_LICENSE("GPL"); From 3cc1b71c031d3fae634cc4260d6dc12136714ac2 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:01 +0200 Subject: [PATCH 210/295] HID: hid-lenovo-go-s: Add MCU ID Attribute JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 5153f1aa6cd529874f4d82c9133dc5f06b10d262 Author: Derek J. Clark Date: Tue Mar 10 07:29:29 2026 +0000 HID: hid-lenovo-go-s: Add MCU ID Attribute Adds command to probe for the MCU ID of the Lenovo Legion Go S Controller and assign it to a device attribute. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go-s.c | 56 +++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index c9f57dfa145a..8ee75f724b5b 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -34,8 +35,13 @@ static struct hid_gos_cfg { struct completion send_cmd_complete; struct hid_device *hdev; struct mutex cfg_mutex; /*ensure single synchronous output report*/ + u8 mcu_id[12]; } drvdata; +struct gos_cfg_attr { + u8 index; +}; + struct command_report { u8 cmd; u8 sub_cmd; @@ -70,6 +76,14 @@ static int hid_gos_version_event(u8 *data) return 0; } +static int hid_gos_mcu_id_event(struct command_report *cmd_rep) +{ + drvdata.mcu_id[0] = cmd_rep->sub_cmd; + memcpy(&drvdata.mcu_id[1], cmd_rep->data, 11); + + return 0; +} + static int get_endpoint_address(struct hid_device *hdev) { struct usb_interface *intf = to_usb_interface(hdev->dev.parent); @@ -103,6 +117,9 @@ static int hid_gos_raw_event(struct hid_device *hdev, struct hid_report *report, case GET_VERSION: ret = hid_gos_version_event(data); break; + case GET_MCU_ID: + ret = hid_gos_mcu_id_event(cmd_rep); + break; default: ret = -EINVAL; break; @@ -157,10 +174,41 @@ static int mcu_property_out(struct hid_device *hdev, u8 command, u8 index, return 0; } +static ssize_t mcu_id_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%*phN\n", 12, &drvdata.mcu_id); +} + +/* MCU */ +static DEVICE_ATTR_RO(mcu_id); + +static struct attribute *legos_mcu_attrs[] = { + &dev_attr_mcu_id.attr, + NULL, +}; + +static const struct attribute_group mcu_attr_group = { + .attrs = legos_mcu_attrs, +}; + +static const struct attribute_group *top_level_attr_groups[] = { + &mcu_attr_group, + NULL, +}; + static void cfg_setup(struct work_struct *work) { int ret; + /* MCU */ + ret = mcu_property_out(drvdata.hdev, GET_MCU_ID, FEATURE_NONE, NULL, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, "Failed to retrieve MCU ID: %i\n", + ret); + return; + } + ret = mcu_property_out(drvdata.hdev, GET_VERSION, FEATURE_NONE, NULL, 0); if (ret) { dev_err(&drvdata.hdev->dev, "Failed to retrieve MCU Version: %i\n", ret); @@ -177,6 +225,13 @@ static int hid_gos_cfg_probe(struct hid_device *hdev, drvdata.hdev = hdev; mutex_init(&drvdata.cfg_mutex); + ret = sysfs_create_groups(&hdev->dev.kobj, top_level_attr_groups); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create gamepad configuration attributes\n"); + return ret; + } + init_completion(&drvdata.send_cmd_complete); /* Executing calls prior to returning from probe will lock the MCU. Schedule @@ -196,6 +251,7 @@ static void hid_gos_cfg_remove(struct hid_device *hdev) { guard(mutex)(&drvdata.cfg_mutex); cancel_delayed_work_sync(&drvdata.gos_cfg_setup); + sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups); hid_hw_close(hdev); hid_hw_stop(hdev); hid_set_drvdata(hdev, NULL); From 9ad6793d9d77fcacb5be48e1a6e11714ba068ce5 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:02 +0200 Subject: [PATCH 211/295] HID: hid-lenovo-go-s: Add Feature Status Attributes JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit c78d07c37c65361f34a51efd99aab2ea59722559 Author: Derek J. Clark Date: Tue Mar 10 07:29:30 2026 +0000 HID: hid-lenovo-go-s: Add Feature Status Attributes Adds features status attributes for the gamepad, MCU, touchpad/mouse, and IMU devices. Reviewed-by: Mark Pearson Co-developed-by: Ethan Tidmore Signed-off-by: Ethan Tidmore Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go-s.c | 484 +++++++++++++++++++++++++++++++++- 1 file changed, 483 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 8ee75f724b5b..97c572cfe66c 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -35,7 +36,17 @@ static struct hid_gos_cfg { struct completion send_cmd_complete; struct hid_device *hdev; struct mutex cfg_mutex; /*ensure single synchronous output report*/ + u8 gp_auto_sleep_time; + u8 gp_dpad_mode; + u8 gp_mode; + u8 gp_poll_rate; + u8 imu_bypass_en; + u8 imu_sensor_en; u8 mcu_id[12]; + u8 mouse_step; + u8 os_mode; + u8 rgb_en; + u8 tp_en; } drvdata; struct gos_cfg_attr { @@ -66,7 +77,73 @@ enum mcu_command_index { GET_PL_TEST = 0xdf, }; -#define FEATURE_NONE 0x00 +enum feature_enabled_index { + FEATURE_DISABLED, + FEATURE_ENABLED, +}; + +static const char *const feature_enabled_text[] = { + [FEATURE_DISABLED] = "false", + [FEATURE_ENABLED] = "true", +}; + +enum feature_status_index { + FEATURE_NONE = 0x00, + FEATURE_GAMEPAD_MODE = 0x01, + FEATURE_AUTO_SLEEP_TIME = 0x04, + FEATURE_IMU_BYPASS, + FEATURE_RGB_ENABLE, + FEATURE_IMU_ENABLE, + FEATURE_TOUCHPAD_ENABLE, + FEATURE_OS_MODE = 0x0A, + FEATURE_POLL_RATE = 0x10, + FEATURE_DPAD_MODE, + FEATURE_MOUSE_WHEEL_STEP, +}; + +enum gamepad_mode_index { + XINPUT, + DINPUT, +}; + +static const char *const gamepad_mode_text[] = { + [XINPUT] = "xinput", + [DINPUT] = "dinput", +}; + +enum os_type_index { + WINDOWS, + LINUX, +}; + +static const char *const os_type_text[] = { + [WINDOWS] = "windows", + [LINUX] = "linux", +}; + +enum poll_rate_index { + HZ125, + HZ250, + HZ500, + HZ1000, +}; + +static const char *const poll_rate_text[] = { + [HZ125] = "125", + [HZ250] = "250", + [HZ500] = "500", + [HZ1000] = "1000", +}; + +enum dpad_mode_index { + DIR8, + DIR4, +}; + +static const char *const dpad_mode_text[] = { + [DIR8] = "8-way", + [DIR4] = "4-way", +}; static int hid_gos_version_event(u8 *data) { @@ -84,6 +161,57 @@ static int hid_gos_mcu_id_event(struct command_report *cmd_rep) return 0; } +static int hid_gos_gamepad_cfg_event(struct command_report *cmd_rep) +{ + int ret = 0; + + switch (cmd_rep->sub_cmd) { + case FEATURE_GAMEPAD_MODE: + drvdata.gp_mode = cmd_rep->data[0]; + break; + case FEATURE_AUTO_SLEEP_TIME: + drvdata.gp_auto_sleep_time = cmd_rep->data[0]; + break; + case FEATURE_IMU_BYPASS: + drvdata.imu_bypass_en = cmd_rep->data[0]; + break; + case FEATURE_RGB_ENABLE: + drvdata.rgb_en = cmd_rep->data[0]; + break; + case FEATURE_IMU_ENABLE: + drvdata.imu_sensor_en = cmd_rep->data[0]; + break; + case FEATURE_TOUCHPAD_ENABLE: + drvdata.tp_en = cmd_rep->data[0]; + break; + case FEATURE_OS_MODE: + drvdata.os_mode = cmd_rep->data[0]; + break; + case FEATURE_POLL_RATE: + drvdata.gp_poll_rate = cmd_rep->data[0]; + break; + case FEATURE_DPAD_MODE: + drvdata.gp_dpad_mode = cmd_rep->data[0]; + break; + case FEATURE_MOUSE_WHEEL_STEP: + drvdata.mouse_step = cmd_rep->data[0]; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int hid_gos_set_event_return(struct command_report *cmd_rep) +{ + if (cmd_rep->data[0] != 0) + return -EIO; + + return 0; +} + static int get_endpoint_address(struct hid_device *hdev) { struct usb_interface *intf = to_usb_interface(hdev->dev.parent); @@ -120,6 +248,12 @@ static int hid_gos_raw_event(struct hid_device *hdev, struct hid_report *report, case GET_MCU_ID: ret = hid_gos_mcu_id_event(cmd_rep); break; + case GET_GAMEPAD_CFG: + ret = hid_gos_gamepad_cfg_event(cmd_rep); + break; + case SET_GAMEPAD_CFG: + ret = hid_gos_set_event_return(cmd_rep); + break; default: ret = -EINVAL; break; @@ -174,17 +308,329 @@ static int mcu_property_out(struct hid_device *hdev, u8 command, u8 index, return 0; } +static ssize_t gamepad_property_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + enum feature_status_index index) +{ + size_t size = 1; + u8 val = 0; + int ret; + + switch (index) { + case FEATURE_GAMEPAD_MODE: + ret = sysfs_match_string(gamepad_mode_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_AUTO_SLEEP_TIME: + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + break; + case FEATURE_IMU_ENABLE: + ret = sysfs_match_string(feature_enabled_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_IMU_BYPASS: + ret = sysfs_match_string(feature_enabled_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_RGB_ENABLE: + ret = sysfs_match_string(feature_enabled_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_TOUCHPAD_ENABLE: + ret = sysfs_match_string(feature_enabled_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_OS_MODE: + ret = sysfs_match_string(os_type_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_POLL_RATE: + ret = sysfs_match_string(poll_rate_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_DPAD_MODE: + ret = sysfs_match_string(dpad_mode_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_MOUSE_WHEEL_STEP: + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + if (val < 1 || val > 127) + return -EINVAL; + break; + default: + return -EINVAL; + } + + if (!val) + size = 0; + + ret = mcu_property_out(drvdata.hdev, SET_GAMEPAD_CFG, index, &val, + size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t gamepad_property_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum feature_status_index index) +{ + ssize_t count = 0; + u8 i; + + count = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, index, NULL, 0); + if (count < 0) + return count; + + switch (index) { + case FEATURE_GAMEPAD_MODE: + i = drvdata.gp_mode; + if (i >= ARRAY_SIZE(gamepad_mode_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", gamepad_mode_text[i]); + break; + case FEATURE_AUTO_SLEEP_TIME: + count = sysfs_emit(buf, "%u\n", drvdata.gp_auto_sleep_time); + break; + case FEATURE_IMU_ENABLE: + i = drvdata.imu_sensor_en; + if (i >= ARRAY_SIZE(feature_enabled_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]); + break; + case FEATURE_IMU_BYPASS: + i = drvdata.imu_bypass_en; + if (i >= ARRAY_SIZE(feature_enabled_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]); + break; + case FEATURE_RGB_ENABLE: + i = drvdata.rgb_en; + if (i >= ARRAY_SIZE(feature_enabled_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]); + break; + case FEATURE_TOUCHPAD_ENABLE: + i = drvdata.tp_en; + if (i >= ARRAY_SIZE(feature_enabled_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]); + break; + case FEATURE_OS_MODE: + i = drvdata.os_mode; + if (i >= ARRAY_SIZE(os_type_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", os_type_text[i]); + break; + case FEATURE_POLL_RATE: + i = drvdata.gp_poll_rate; + if (i >= ARRAY_SIZE(poll_rate_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", poll_rate_text[i]); + break; + case FEATURE_DPAD_MODE: + i = drvdata.gp_dpad_mode; + if (i >= ARRAY_SIZE(dpad_mode_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", dpad_mode_text[i]); + break; + case FEATURE_MOUSE_WHEEL_STEP: + i = drvdata.mouse_step; + if (i < 1 || i > 127) + return -EINVAL; + count = sysfs_emit(buf, "%u\n", i); + break; + default: + return -EINVAL; + } + + return count; +} + +static ssize_t gamepad_property_options(struct device *dev, + struct device_attribute *attr, + char *buf, + enum feature_status_index index) +{ + size_t count = 0; + unsigned int i; + + switch (index) { + case FEATURE_GAMEPAD_MODE: + for (i = 0; i < ARRAY_SIZE(gamepad_mode_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + gamepad_mode_text[i]); + } + break; + case FEATURE_AUTO_SLEEP_TIME: + return sysfs_emit(buf, "0-255\n"); + case FEATURE_IMU_ENABLE: + for (i = 0; i < ARRAY_SIZE(feature_enabled_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + feature_enabled_text[i]); + } + break; + case FEATURE_IMU_BYPASS: + case FEATURE_RGB_ENABLE: + case FEATURE_TOUCHPAD_ENABLE: + for (i = 0; i < ARRAY_SIZE(feature_enabled_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + feature_enabled_text[i]); + } + break; + case FEATURE_OS_MODE: + for (i = 0; i < ARRAY_SIZE(os_type_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + os_type_text[i]); + } + break; + case FEATURE_POLL_RATE: + for (i = 0; i < ARRAY_SIZE(poll_rate_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + poll_rate_text[i]); + } + break; + case FEATURE_DPAD_MODE: + for (i = 0; i < ARRAY_SIZE(dpad_mode_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + dpad_mode_text[i]); + } + break; + case FEATURE_MOUSE_WHEEL_STEP: + return sysfs_emit(buf, "1-127\n"); + default: + return count; + } + + if (count) + buf[count - 1] = '\n'; + + return count; +} + static ssize_t mcu_id_show(struct device *dev, struct device_attribute *attr, char *buf) { return sysfs_emit(buf, "%*phN\n", 12, &drvdata.mcu_id); } +#define LEGOS_DEVICE_ATTR_RW(_name, _attrname, _rtype, _group) \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return _group##_property_store(dev, attr, buf, count, \ + _name.index); \ + } \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_property_show(dev, attr, buf, _name.index); \ + } \ + static ssize_t _name##_##_rtype##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return _group##_property_options(dev, attr, buf, _name.index); \ + } \ + static DEVICE_ATTR_RW_NAMED(_name, _attrname) + +#define LEGOS_DEVICE_ATTR_RO(_name, _attrname, _group) \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_property_show(dev, attr, buf, _name.index); \ + } \ + static DEVICE_ATTR_RO_NAMED(_name, _attrname) + +/* Gamepad */ +static struct gos_cfg_attr auto_sleep_time = { FEATURE_AUTO_SLEEP_TIME }; +LEGOS_DEVICE_ATTR_RW(auto_sleep_time, "auto_sleep_time", range, gamepad); +static DEVICE_ATTR_RO(auto_sleep_time_range); + +static struct gos_cfg_attr dpad_mode = { FEATURE_DPAD_MODE }; +LEGOS_DEVICE_ATTR_RW(dpad_mode, "dpad_mode", index, gamepad); +static DEVICE_ATTR_RO(dpad_mode_index); + +static struct gos_cfg_attr gamepad_mode = { FEATURE_GAMEPAD_MODE }; +LEGOS_DEVICE_ATTR_RW(gamepad_mode, "mode", index, gamepad); +static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mode_index"); + +static struct gos_cfg_attr gamepad_poll_rate = { FEATURE_POLL_RATE }; +LEGOS_DEVICE_ATTR_RW(gamepad_poll_rate, "poll_rate", index, gamepad); +static DEVICE_ATTR_RO_NAMED(gamepad_poll_rate_index, "poll_rate_index"); + +static struct attribute *legos_gamepad_attrs[] = { + &dev_attr_auto_sleep_time.attr, + &dev_attr_auto_sleep_time_range.attr, + &dev_attr_dpad_mode.attr, + &dev_attr_dpad_mode_index.attr, + &dev_attr_gamepad_mode.attr, + &dev_attr_gamepad_mode_index.attr, + &dev_attr_gamepad_poll_rate.attr, + &dev_attr_gamepad_poll_rate_index.attr, + NULL, +}; + +static const struct attribute_group gamepad_attr_group = { + .name = "gamepad", + .attrs = legos_gamepad_attrs, +}; + +/* IMU */ +static struct gos_cfg_attr imu_bypass_enabled = { FEATURE_IMU_BYPASS }; +LEGOS_DEVICE_ATTR_RW(imu_bypass_enabled, "bypass_enabled", index, gamepad); +static DEVICE_ATTR_RO_NAMED(imu_bypass_enabled_index, "bypass_enabled_index"); + +static struct gos_cfg_attr imu_sensor_enabled = { FEATURE_IMU_ENABLE }; +LEGOS_DEVICE_ATTR_RW(imu_sensor_enabled, "sensor_enabled", index, gamepad); +static DEVICE_ATTR_RO_NAMED(imu_sensor_enabled_index, "sensor_enabled_index"); + +static struct attribute *legos_imu_attrs[] = { + &dev_attr_imu_bypass_enabled.attr, + &dev_attr_imu_bypass_enabled_index.attr, + &dev_attr_imu_sensor_enabled.attr, + &dev_attr_imu_sensor_enabled_index.attr, + NULL, +}; + +static const struct attribute_group imu_attr_group = { + .name = "imu", + .attrs = legos_imu_attrs, +}; + /* MCU */ static DEVICE_ATTR_RO(mcu_id); +static struct gos_cfg_attr os_mode = { FEATURE_OS_MODE }; +LEGOS_DEVICE_ATTR_RW(os_mode, "os_mode", index, gamepad); +static DEVICE_ATTR_RO(os_mode_index); + static struct attribute *legos_mcu_attrs[] = { &dev_attr_mcu_id.attr, + &dev_attr_os_mode.attr, + &dev_attr_os_mode_index.attr, NULL, }; @@ -192,8 +638,44 @@ static const struct attribute_group mcu_attr_group = { .attrs = legos_mcu_attrs, }; +/* Mouse */ +static struct gos_cfg_attr mouse_wheel_step = { FEATURE_MOUSE_WHEEL_STEP }; +LEGOS_DEVICE_ATTR_RW(mouse_wheel_step, "step", range, gamepad); +static DEVICE_ATTR_RO_NAMED(mouse_wheel_step_range, "step_range"); + +static struct attribute *legos_mouse_attrs[] = { + &dev_attr_mouse_wheel_step.attr, + &dev_attr_mouse_wheel_step_range.attr, + NULL, +}; + +static const struct attribute_group mouse_attr_group = { + .name = "mouse", + .attrs = legos_mouse_attrs, +}; + +/* Touchpad */ +static struct gos_cfg_attr touchpad_enabled = { FEATURE_TOUCHPAD_ENABLE }; +LEGOS_DEVICE_ATTR_RW(touchpad_enabled, "enabled", index, gamepad); +static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); + +static struct attribute *legos_touchpad_attrs[] = { + &dev_attr_touchpad_enabled.attr, + &dev_attr_touchpad_enabled_index.attr, + NULL, +}; + +static const struct attribute_group touchpad_attr_group = { + .name = "touchpad", + .attrs = legos_touchpad_attrs, +}; + static const struct attribute_group *top_level_attr_groups[] = { + &gamepad_attr_group, + &imu_attr_group, &mcu_attr_group, + &mouse_attr_group, + &touchpad_attr_group, NULL, }; From e24d3dc3eeb3a5f4a632d8e759eae2b1351fa286 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:03 +0200 Subject: [PATCH 212/295] HID: hid-lenovo-go-s: Add Touchpad Mode Attributes JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit e10f5499384d33d4a818f4309b46b974ac1a155e Author: Derek J. Clark Date: Tue Mar 10 07:29:31 2026 +0000 HID: hid-lenovo-go-s: Add Touchpad Mode Attributes Adds attributes for managing the touchpad operating modes. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go-s.c | 142 ++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 97c572cfe66c..5899cabe950f 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -47,6 +47,8 @@ static struct hid_gos_cfg { u8 os_mode; u8 rgb_en; u8 tp_en; + u8 tp_linux_mode; + u8 tp_windows_mode; } drvdata; struct gos_cfg_attr { @@ -145,6 +147,22 @@ static const char *const dpad_mode_text[] = { [DIR4] = "4-way", }; +enum touchpad_mode_index { + TP_REL, + TP_ABS, +}; + +static const char *const touchpad_mode_text[] = { + [TP_REL] = "relative", + [TP_ABS] = "absolute", +}; + +enum touchpad_config_index { + CFG_WINDOWS_MODE = 0x03, + CFG_LINUX_MODE, + +}; + static int hid_gos_version_event(u8 *data) { struct version_report *ver_rep = (struct version_report *)data; @@ -204,6 +222,25 @@ static int hid_gos_gamepad_cfg_event(struct command_report *cmd_rep) return ret; } +static int hid_gos_touchpad_event(struct command_report *cmd_rep) +{ + int ret = 0; + + switch (cmd_rep->sub_cmd) { + case CFG_LINUX_MODE: + drvdata.tp_linux_mode = cmd_rep->data[0]; + break; + case CFG_WINDOWS_MODE: + drvdata.tp_windows_mode = cmd_rep->data[0]; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + static int hid_gos_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] != 0) @@ -251,7 +288,11 @@ static int hid_gos_raw_event(struct hid_device *hdev, struct hid_report *report, case GET_GAMEPAD_CFG: ret = hid_gos_gamepad_cfg_event(cmd_rep); break; + case GET_TP_PARAM: + ret = hid_gos_touchpad_event(cmd_rep); + break; case SET_GAMEPAD_CFG: + case SET_TP_PARAM: ret = hid_gos_set_event_return(cmd_rep); break; default: @@ -530,6 +571,95 @@ static ssize_t gamepad_property_options(struct device *dev, return count; } +static ssize_t touchpad_property_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + enum touchpad_config_index index) +{ + size_t size = 1; + u8 val = 0; + int ret; + + switch (index) { + case CFG_WINDOWS_MODE: + ret = sysfs_match_string(touchpad_mode_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case CFG_LINUX_MODE: + ret = sysfs_match_string(touchpad_mode_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + default: + return -EINVAL; + } + if (!val) + size = 0; + + ret = mcu_property_out(drvdata.hdev, SET_TP_PARAM, index, &val, size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t touchpad_property_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum touchpad_config_index index) +{ + int ret = 0; + u8 i; + + ret = mcu_property_out(drvdata.hdev, GET_TP_PARAM, index, NULL, 0); + if (ret < 0) + return ret; + + switch (index) { + case CFG_WINDOWS_MODE: + i = drvdata.tp_windows_mode; + break; + case CFG_LINUX_MODE: + i = drvdata.tp_linux_mode; + break; + default: + return -EINVAL; + } + + if (i >= ARRAY_SIZE(touchpad_mode_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", touchpad_mode_text[i]); +} + +static ssize_t touchpad_property_options(struct device *dev, + struct device_attribute *attr, + char *buf, + enum touchpad_config_index index) +{ + size_t count = 0; + unsigned int i; + + switch (index) { + case CFG_WINDOWS_MODE: + case CFG_LINUX_MODE: + for (i = 0; i < ARRAY_SIZE(touchpad_mode_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + touchpad_mode_text[i]); + } + break; + default: + return count; + } + + if (count) + buf[count - 1] = '\n'; + + return count; +} + static ssize_t mcu_id_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -659,9 +789,21 @@ static struct gos_cfg_attr touchpad_enabled = { FEATURE_TOUCHPAD_ENABLE }; LEGOS_DEVICE_ATTR_RW(touchpad_enabled, "enabled", index, gamepad); static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); +static struct gos_cfg_attr touchpad_linux_mode = { CFG_LINUX_MODE }; +LEGOS_DEVICE_ATTR_RW(touchpad_linux_mode, "linux_mode", index, touchpad); +static DEVICE_ATTR_RO_NAMED(touchpad_linux_mode_index, "linux_mode_index"); + +static struct gos_cfg_attr touchpad_windows_mode = { CFG_WINDOWS_MODE }; +LEGOS_DEVICE_ATTR_RW(touchpad_windows_mode, "windows_mode", index, touchpad); +static DEVICE_ATTR_RO_NAMED(touchpad_windows_mode_index, "windows_mode_index"); + static struct attribute *legos_touchpad_attrs[] = { &dev_attr_touchpad_enabled.attr, &dev_attr_touchpad_enabled_index.attr, + &dev_attr_touchpad_linux_mode.attr, + &dev_attr_touchpad_linux_mode_index.attr, + &dev_attr_touchpad_windows_mode.attr, + &dev_attr_touchpad_windows_mode_index.attr, NULL, }; From b4277965475b475486d909845b0b89097bd1f9b8 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:04 +0200 Subject: [PATCH 213/295] HID: hid-lenovo-go-s: Add RGB LED control interface JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit be6d7dbb368ba840d7f7624d90e93d0acb2a1e94 Author: Derek J. Clark Date: Tue Mar 10 07:29:32 2026 +0000 HID: hid-lenovo-go-s: Add RGB LED control interface Adds an LED multicolor class device and attribute group for controlling the RGB of the Left and right joystick rings. In addition to the standard led_cdev attributes, additional attributes that allow for the control of the effect (monocolor, breathe, rainbow, and chroma), speed of the effect change, an enable toggle, and profile. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go-s.c | 422 ++++++++++++++++++++++++++++++++++ 1 file changed, 422 insertions(+) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 5899cabe950f..0123c4b03cf4 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,7 @@ static struct hid_gos_cfg { struct delayed_work gos_cfg_setup; struct completion send_cmd_complete; + struct led_classdev *led_cdev; struct hid_device *hdev; struct mutex cfg_mutex; /*ensure single synchronous output report*/ u8 gp_auto_sleep_time; @@ -45,7 +47,11 @@ static struct hid_gos_cfg { u8 mcu_id[12]; u8 mouse_step; u8 os_mode; + u8 rgb_effect; u8 rgb_en; + u8 rgb_mode; + u8 rgb_profile; + u8 rgb_speed; u8 tp_en; u8 tp_linux_mode; u8 tp_windows_mode; @@ -163,6 +169,38 @@ enum touchpad_config_index { }; +enum rgb_mode_index { + RGB_MODE_DYNAMIC, + RGB_MODE_CUSTOM, +}; + +static const char *const rgb_mode_text[] = { + [RGB_MODE_DYNAMIC] = "dynamic", + [RGB_MODE_CUSTOM] = "custom", +}; + +enum rgb_effect_index { + RGB_EFFECT_MONO, + RGB_EFFECT_BREATHE, + RGB_EFFECT_CHROMA, + RGB_EFFECT_RAINBOW, +}; + +static const char *const rgb_effect_text[] = { + [RGB_EFFECT_MONO] = "monocolor", + [RGB_EFFECT_BREATHE] = "breathe", + [RGB_EFFECT_CHROMA] = "chroma", + [RGB_EFFECT_RAINBOW] = "rainbow", +}; + +enum rgb_config_index { + LIGHT_MODE_SEL = 0x01, + LIGHT_PROFILE_SEL, + USR_LIGHT_PROFILE_1, + USR_LIGHT_PROFILE_2, + USR_LIGHT_PROFILE_3, +}; + static int hid_gos_version_event(u8 *data) { struct version_report *ver_rep = (struct version_report *)data; @@ -241,6 +279,39 @@ static int hid_gos_touchpad_event(struct command_report *cmd_rep) return ret; } +static int hid_gos_light_event(struct command_report *cmd_rep) +{ + struct led_classdev_mc *mc_cdev; + int ret = 0; + + switch (cmd_rep->sub_cmd) { + case LIGHT_MODE_SEL: + drvdata.rgb_mode = cmd_rep->data[0]; + ret = 0; + break; + case LIGHT_PROFILE_SEL: + drvdata.rgb_profile = cmd_rep->data[0]; + ret = 0; + break; + case USR_LIGHT_PROFILE_1: + case USR_LIGHT_PROFILE_2: + case USR_LIGHT_PROFILE_3: + mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + drvdata.rgb_effect = cmd_rep->data[0]; + mc_cdev->subled_info[0].intensity = cmd_rep->data[1]; + mc_cdev->subled_info[1].intensity = cmd_rep->data[2]; + mc_cdev->subled_info[2].intensity = cmd_rep->data[3]; + drvdata.led_cdev->brightness = cmd_rep->data[4]; + drvdata.rgb_speed = cmd_rep->data[5]; + ret = 0; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + static int hid_gos_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] != 0) @@ -291,7 +362,11 @@ static int hid_gos_raw_event(struct hid_device *hdev, struct hid_report *report, case GET_TP_PARAM: ret = hid_gos_touchpad_event(cmd_rep); break; + case GET_RGB_CFG: + ret = hid_gos_light_event(cmd_rep); + break; case SET_GAMEPAD_CFG: + case SET_RGB_CFG: case SET_TP_PARAM: ret = hid_gos_set_event_return(cmd_rep); break; @@ -666,6 +741,274 @@ static ssize_t mcu_id_show(struct device *dev, struct device_attribute *attr, return sysfs_emit(buf, "%*phN\n", 12, &drvdata.mcu_id); } +static int rgb_cfg_call(struct hid_device *hdev, enum mcu_command_index cmd, + enum rgb_config_index index, u8 *val, size_t size) +{ + if (cmd != SET_RGB_CFG && cmd != GET_RGB_CFG) + return -EINVAL; + + if (index < LIGHT_MODE_SEL || index > USR_LIGHT_PROFILE_3) + return -EINVAL; + + return mcu_property_out(hdev, cmd, index, val, size); +} + +static int rgb_attr_show(void) +{ + enum rgb_config_index index; + + index = drvdata.rgb_profile + 2; + + return rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, index, NULL, 0); +}; + +static ssize_t rgb_effect_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + u8 effect; + int ret; + + ret = sysfs_match_string(rgb_effect_text, buf); + if (ret < 0) + return ret; + + effect = ret; + index = drvdata.rgb_profile + 2; + u8 rgb_profile[6] = { effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + drvdata.led_cdev->brightness, + drvdata.rgb_speed }; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + if (ret) + return ret; + + drvdata.rgb_effect = effect; + return count; +}; + +static ssize_t rgb_effect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret = rgb_attr_show(); + if (ret) + return ret; + + if (drvdata.rgb_effect >= ARRAY_SIZE(rgb_effect_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", rgb_effect_text[drvdata.rgb_effect]); +} + +static ssize_t rgb_effect_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rgb_effect_text); i++) + count += sysfs_emit_at(buf, count, "%s ", rgb_effect_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t rgb_speed_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + int val = 0; + int ret; + + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; + + if (val < 0 || val > 100) + return -EINVAL; + + index = drvdata.rgb_profile + 2; + u8 rgb_profile[6] = { drvdata.rgb_effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + drvdata.led_cdev->brightness, + val }; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + if (ret) + return ret; + + drvdata.rgb_speed = val; + + return count; +}; + +static ssize_t rgb_speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + + ret = rgb_attr_show(); + if (ret) + return ret; + + if (drvdata.rgb_speed > 100) + return -EINVAL; + + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed); +} + +static ssize_t rgb_speed_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0-100\n"); +} + +static ssize_t rgb_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret = sysfs_match_string(rgb_mode_text, buf); + if (ret <= 0) + return ret; + + val = ret; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_MODE_SEL, &val, + 1); + if (ret) + return ret; + + drvdata.rgb_mode = val; + + return count; +}; + +static ssize_t rgb_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + + ret = rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_MODE_SEL, NULL, 0); + if (ret) + return ret; + + if (drvdata.rgb_mode >= ARRAY_SIZE(rgb_mode_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", rgb_mode_text[drvdata.rgb_mode]); +}; + +static ssize_t rgb_mode_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(rgb_mode_text); i++) + count += sysfs_emit_at(buf, count, "%s ", rgb_mode_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t rgb_profile_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + size_t size = 1; + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret < 0) + return ret; + + if (val < 1 || val > 3) + return -EINVAL; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_PROFILE_SEL, &val, size); + if (ret) + return ret; + + drvdata.rgb_profile = val; + + return count; +}; + +static ssize_t rgb_profile_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret = rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_PROFILE_SEL, NULL, 0); + if (ret) + return ret; + + if (drvdata.rgb_profile < 1 || drvdata.rgb_profile > 3) + return -EINVAL; + + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_profile); +}; + +static ssize_t rgb_profile_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "1-3\n"); +} + +static void hid_gos_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + int ret; + + if (brightness > led_cdev->max_brightness) { + dev_err(led_cdev->dev, "Invalid argument\n"); + return; + } + + index = drvdata.rgb_profile + 2; + u8 rgb_profile[6] = { drvdata.rgb_effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + brightness, + drvdata.rgb_speed }; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + switch (ret) { + case 0: + led_cdev->brightness = brightness; + break; + case -ENODEV: /* during switch to IAP -ENODEV is expected */ + case -ENOSYS: /* during rmmod -ENOSYS is expected */ + dev_dbg(led_cdev->dev, "Failed to write RGB profile: %i\n", + ret); + break; + default: + dev_err(led_cdev->dev, "Failed to write RGB profile: %i\n", + ret); + }; +} + #define LEGOS_DEVICE_ATTR_RW(_name, _attrname, _rtype, _group) \ static ssize_t _name##_store(struct device *dev, \ struct device_attribute *attr, \ @@ -821,6 +1164,70 @@ static const struct attribute_group *top_level_attr_groups[] = { NULL, }; +/* RGB */ +static struct gos_cfg_attr rgb_enabled = { FEATURE_RGB_ENABLE }; +LEGOS_DEVICE_ATTR_RW(rgb_enabled, "enabled", index, gamepad); +static DEVICE_ATTR_RO_NAMED(rgb_enabled_index, "enabled_index"); + +static DEVICE_ATTR_RW_NAMED(rgb_effect, "effect"); +static DEVICE_ATTR_RO_NAMED(rgb_effect_index, "effect_index"); +static DEVICE_ATTR_RW_NAMED(rgb_mode, "mode"); +static DEVICE_ATTR_RO_NAMED(rgb_mode_index, "mode_index"); +static DEVICE_ATTR_RW_NAMED(rgb_profile, "profile"); +static DEVICE_ATTR_RO_NAMED(rgb_profile_range, "profile_range"); +static DEVICE_ATTR_RW_NAMED(rgb_speed, "speed"); +static DEVICE_ATTR_RO_NAMED(rgb_speed_range, "speed_range"); + +static struct attribute *gos_rgb_attrs[] = { + &dev_attr_rgb_enabled.attr, + &dev_attr_rgb_enabled_index.attr, + &dev_attr_rgb_effect.attr, + &dev_attr_rgb_effect_index.attr, + &dev_attr_rgb_mode.attr, + &dev_attr_rgb_mode_index.attr, + &dev_attr_rgb_profile.attr, + &dev_attr_rgb_profile_range.attr, + &dev_attr_rgb_speed.attr, + &dev_attr_rgb_speed_range.attr, + NULL, +}; + +static struct attribute_group rgb_attr_group = { + .attrs = gos_rgb_attrs, +}; + +static struct mc_subled gos_rgb_subled_info[] = { + { + .color_index = LED_COLOR_ID_RED, + .brightness = 0x50, + .intensity = 0x24, + .channel = 0x1, + }, + { + .color_index = LED_COLOR_ID_GREEN, + .brightness = 0x50, + .intensity = 0x22, + .channel = 0x2, + }, + { + .color_index = LED_COLOR_ID_BLUE, + .brightness = 0x50, + .intensity = 0x99, + .channel = 0x3, + }, +}; + +static struct led_classdev_mc gos_cdev_rgb = { + .led_cdev = { + .name = "go_s:rgb:joystick_rings", + .brightness = 0x50, + .max_brightness = 0x64, + .brightness_set = hid_gos_brightness_set, + }, + .num_colors = ARRAY_SIZE(gos_rgb_subled_info), + .subled_info = gos_rgb_subled_info, +}; + static void cfg_setup(struct work_struct *work) { int ret; @@ -856,6 +1263,21 @@ static int hid_gos_cfg_probe(struct hid_device *hdev, return ret; } + ret = devm_led_classdev_multicolor_register(&hdev->dev, &gos_cdev_rgb); + if (ret) { + dev_err_probe(&hdev->dev, ret, "Failed to create RGB device\n"); + return ret; + } + + ret = devm_device_add_group(gos_cdev_rgb.led_cdev.dev, &rgb_attr_group); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create RGB configuratiion attributes\n"); + return ret; + } + + drvdata.led_cdev = &gos_cdev_rgb.led_cdev; + init_completion(&drvdata.send_cmd_complete); /* Executing calls prior to returning from probe will lock the MCU. Schedule From 804c59477604b3e337c39909829b6217dc79cf97 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:04 +0200 Subject: [PATCH 214/295] HID: hid-lenovo-go-s: Add IMU and Touchpad RO Attributes JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 34a05773e81505a35cacc0548797c64777f91f9c Author: Derek J. Clark Date: Tue Mar 10 07:29:33 2026 +0000 HID: hid-lenovo-go-s: Add IMU and Touchpad RO Attributes Adds attributes for reporting the touchpad manufacturer, version, and IMU manufacturer. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go-s.c | 124 ++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 0123c4b03cf4..431fffde4695 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -43,6 +43,7 @@ static struct hid_gos_cfg { u8 gp_mode; u8 gp_poll_rate; u8 imu_bypass_en; + u8 imu_manufacturer; u8 imu_sensor_en; u8 mcu_id[12]; u8 mouse_step; @@ -55,6 +56,8 @@ static struct hid_gos_cfg { u8 tp_en; u8 tp_linux_mode; u8 tp_windows_mode; + u8 tp_version; + u8 tp_manufacturer; } drvdata; struct gos_cfg_attr { @@ -201,6 +204,36 @@ enum rgb_config_index { USR_LIGHT_PROFILE_3, }; +enum test_command_index { + TEST_TP_MFR = 0x02, + TEST_IMU_MFR, + TEST_TP_VER, +}; + +enum tp_mfr_index { + TP_NONE, + TP_BETTERLIFE, + TP_SIPO, +}; + +static const char *const touchpad_manufacturer_text[] = { + [TP_NONE] = "none", + [TP_BETTERLIFE] = "BetterLife", + [TP_SIPO] = "SIPO", +}; + +enum imu_mfr_index { + IMU_NONE, + IMU_BOSCH, + IMU_ST, +}; + +static const char *const imu_manufacturer_text[] = { + [IMU_NONE] = "none", + [IMU_BOSCH] = "Bosch", + [IMU_ST] = "ST", +}; + static int hid_gos_version_event(u8 *data) { struct version_report *ver_rep = (struct version_report *)data; @@ -279,6 +312,30 @@ static int hid_gos_touchpad_event(struct command_report *cmd_rep) return ret; } +static int hid_gos_pl_test_event(struct command_report *cmd_rep) +{ + int ret = 0; + + switch (cmd_rep->sub_cmd) { + case TEST_TP_MFR: + drvdata.tp_manufacturer = cmd_rep->data[0]; + ret = 0; + break; + case TEST_IMU_MFR: + drvdata.imu_manufacturer = cmd_rep->data[0]; + ret = 0; + break; + case TEST_TP_VER: + drvdata.tp_version = cmd_rep->data[0]; + ret = 0; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + static int hid_gos_light_event(struct command_report *cmd_rep) { struct led_classdev_mc *mc_cdev; @@ -362,6 +419,9 @@ static int hid_gos_raw_event(struct hid_device *hdev, struct hid_report *report, case GET_TP_PARAM: ret = hid_gos_touchpad_event(cmd_rep); break; + case GET_PL_TEST: + ret = hid_gos_pl_test_event(cmd_rep); + break; case GET_RGB_CFG: ret = hid_gos_light_event(cmd_rep); break; @@ -735,6 +795,37 @@ static ssize_t touchpad_property_options(struct device *dev, return count; } +static ssize_t test_property_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum test_command_index index) +{ + size_t count = 0; + u8 i; + + switch (index) { + case TEST_TP_MFR: + i = drvdata.tp_manufacturer; + if (i >= ARRAY_SIZE(touchpad_manufacturer_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", touchpad_manufacturer_text[i]); + break; + case TEST_IMU_MFR: + i = drvdata.imu_manufacturer; + if (i >= ARRAY_SIZE(imu_manufacturer_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", imu_manufacturer_text[i]); + break; + case TEST_TP_VER: + count = sysfs_emit(buf, "%u\n", drvdata.tp_version); + break; + default: + count = -EINVAL; + break; + } + + return count; +} + static ssize_t mcu_id_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1076,6 +1167,9 @@ static struct gos_cfg_attr imu_bypass_enabled = { FEATURE_IMU_BYPASS }; LEGOS_DEVICE_ATTR_RW(imu_bypass_enabled, "bypass_enabled", index, gamepad); static DEVICE_ATTR_RO_NAMED(imu_bypass_enabled_index, "bypass_enabled_index"); +static struct gos_cfg_attr imu_manufacturer = { TEST_IMU_MFR }; +LEGOS_DEVICE_ATTR_RO(imu_manufacturer, "manufacturer", test); + static struct gos_cfg_attr imu_sensor_enabled = { FEATURE_IMU_ENABLE }; LEGOS_DEVICE_ATTR_RW(imu_sensor_enabled, "sensor_enabled", index, gamepad); static DEVICE_ATTR_RO_NAMED(imu_sensor_enabled_index, "sensor_enabled_index"); @@ -1083,6 +1177,7 @@ static DEVICE_ATTR_RO_NAMED(imu_sensor_enabled_index, "sensor_enabled_index"); static struct attribute *legos_imu_attrs[] = { &dev_attr_imu_bypass_enabled.attr, &dev_attr_imu_bypass_enabled_index.attr, + &dev_attr_imu_manufacturer.attr, &dev_attr_imu_sensor_enabled.attr, &dev_attr_imu_sensor_enabled_index.attr, NULL, @@ -1136,6 +1231,12 @@ static struct gos_cfg_attr touchpad_linux_mode = { CFG_LINUX_MODE }; LEGOS_DEVICE_ATTR_RW(touchpad_linux_mode, "linux_mode", index, touchpad); static DEVICE_ATTR_RO_NAMED(touchpad_linux_mode_index, "linux_mode_index"); +static struct gos_cfg_attr touchpad_manufacturer = { TEST_TP_MFR }; +LEGOS_DEVICE_ATTR_RO(touchpad_manufacturer, "manufacturer", test); + +static struct gos_cfg_attr touchpad_version = { TEST_TP_VER }; +LEGOS_DEVICE_ATTR_RO(touchpad_version, "version", test); + static struct gos_cfg_attr touchpad_windows_mode = { CFG_WINDOWS_MODE }; LEGOS_DEVICE_ATTR_RW(touchpad_windows_mode, "windows_mode", index, touchpad); static DEVICE_ATTR_RO_NAMED(touchpad_windows_mode_index, "windows_mode_index"); @@ -1145,6 +1246,8 @@ static struct attribute *legos_touchpad_attrs[] = { &dev_attr_touchpad_enabled_index.attr, &dev_attr_touchpad_linux_mode.attr, &dev_attr_touchpad_linux_mode_index.attr, + &dev_attr_touchpad_manufacturer.attr, + &dev_attr_touchpad_version.attr, &dev_attr_touchpad_windows_mode.attr, &dev_attr_touchpad_windows_mode_index.attr, NULL, @@ -1245,6 +1348,27 @@ static void cfg_setup(struct work_struct *work) dev_err(&drvdata.hdev->dev, "Failed to retrieve MCU Version: %i\n", ret); return; } + + ret = mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_TP_MFR, NULL, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve Touchpad Manufacturer: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_TP_VER, NULL, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve Touchpad Firmware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_IMU_MFR, NULL, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve IMU Manufacturer: %i\n", ret); + return; + } } static int hid_gos_cfg_probe(struct hid_device *hdev, From 8fbe4ed1bbc1356c21d5fc408f83de495105080c Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:05 +0200 Subject: [PATCH 215/295] HID: hid-lenovo-go-s: Remove unneeded semicolon JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit c04744049febb39a21185c356507eda112e97385 Author: Chen Ni Date: Tue Mar 10 07:29:35 2026 +0000 HID: hid-lenovo-go-s: Remove unneeded semicolon Remove unnecessary semicolons reported by Coccinelle/coccicheck and the semantic patch at scripts/coccinelle/misc/semicolon.cocci. Signed-off-by: Chen Ni Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go-s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 431fffde4695..8ffa25b20f9c 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -1097,7 +1097,7 @@ static void hid_gos_brightness_set(struct led_classdev *led_cdev, default: dev_err(led_cdev->dev, "Failed to write RGB profile: %i\n", ret); - }; + } } #define LEGOS_DEVICE_ATTR_RW(_name, _attrname, _rtype, _group) \ From 7972fa54a2039a2635acf4f490dc18f4af9206ef Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:06 +0200 Subject: [PATCH 216/295] HID: hid-lenovo-go: Remove unneeded semicolon JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 54549af86f2d6fd6e547d208dadfdf904101f7ae Author: Chen Ni Date: Tue Mar 10 07:29:36 2026 +0000 HID: hid-lenovo-go: Remove unneeded semicolon Remove unnecessary semicolons after switch statements and function bodies. Most issues were reported by Coccinelle/coccicheck using the semantic patch at scripts/coccinelle/misc/semicolon.cocci. Additional instances found during manual code review were also fixed. Signed-off-by: Chen Ni Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go.c | 52 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index 54861f2e04fc..d4d26c783356 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -455,7 +455,7 @@ static int hid_go_feature_status_event(struct command_report *cmd_rep) return 0; default: return -EINVAL; - }; + } case FEATURE_IMU_BYPASS: switch (cmd_rep->device_type) { case LEFT_CONTROLLER: @@ -466,7 +466,7 @@ static int hid_go_feature_status_event(struct command_report *cmd_rep) return 0; default: return -EINVAL; - }; + } break; case FEATURE_LIGHT_ENABLE: drvdata.rgb_en = cmd_rep->data[0]; @@ -481,7 +481,7 @@ static int hid_go_feature_status_event(struct command_report *cmd_rep) return 0; default: return -EINVAL; - }; + } break; case FEATURE_TOUCHPAD_ENABLE: drvdata.tp_en = cmd_rep->data[0]; @@ -515,7 +515,7 @@ static int hid_go_motor_event(struct command_report *cmd_rep) return 0; default: return -EINVAL; - }; + } break; case RUMBLE_MODE: switch (cmd_rep->device_type) { @@ -527,7 +527,7 @@ static int hid_go_motor_event(struct command_report *cmd_rep) return 0; default: return -EINVAL; - }; + } case TP_VIBRATION_ENABLE: drvdata.tp_vibration_en = cmd_rep->data[0]; return 0; @@ -625,7 +625,7 @@ static int hid_go_os_mode_cfg_event(struct command_report *cmd_rep) return 0; default: return -EINVAL; - }; + } } static int hid_go_set_event_return(struct command_report *cmd_rep) @@ -699,14 +699,14 @@ static int hid_go_raw_event(struct hid_device *hdev, struct hid_report *report, default: ret = -EINVAL; break; - }; + } break; case OS_MODE_DATA: ret = hid_go_os_mode_cfg_event(cmd_rep); break; default: goto passthrough; - }; + } dev_dbg(&hdev->dev, "Rx data as raw input report: [%*ph]\n", GO_PACKET_SIZE, data); @@ -925,7 +925,7 @@ static ssize_t feature_status_store(struct device *dev, break; default: return -EINVAL; - }; + } if (ret < 0) return ret; @@ -1013,7 +1013,7 @@ static ssize_t feature_status_show(struct device *dev, break; default: return -EINVAL; - }; + } count = sysfs_emit(buf, "%u\n", i); break; case FEATURE_FPS_SWITCH_STATUS: @@ -1032,7 +1032,7 @@ static ssize_t feature_status_show(struct device *dev, break; default: return -EINVAL; - }; + } return count; } @@ -1070,7 +1070,7 @@ static ssize_t feature_status_options(struct device *dev, break; default: return -EINVAL; - }; + } if (count) buf[count - 1] = '\n'; @@ -1111,7 +1111,7 @@ static ssize_t motor_config_store(struct device *dev, ret = sysfs_match_string(intensity_text, buf); val = ret; break; - }; + } if (ret < 0) return ret; @@ -1161,7 +1161,7 @@ static ssize_t motor_config_show(struct device *dev, break; default: return -EINVAL; - }; + } if (i >= ARRAY_SIZE(enabled_status_text)) return -EINVAL; @@ -1177,7 +1177,7 @@ static ssize_t motor_config_show(struct device *dev, break; default: return -EINVAL; - }; + } if (i >= ARRAY_SIZE(rumble_mode_text)) return -EINVAL; @@ -1197,7 +1197,7 @@ static ssize_t motor_config_show(struct device *dev, count = sysfs_emit(buf, "%s\n", intensity_text[i]); break; - }; + } return count; } @@ -1232,7 +1232,7 @@ static ssize_t motor_config_options(struct device *dev, enabled_status_text[i]); } break; - }; + } if (count) buf[count - 1] = '\n'; @@ -1333,7 +1333,7 @@ static ssize_t device_status_show(struct device *dev, break; default: return -EINVAL; - }; + } if (i >= ARRAY_SIZE(cal_status_text)) return -EINVAL; @@ -1459,7 +1459,7 @@ static int rgb_attr_show(void) index = drvdata.rgb_profile + 3; return rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, index, NULL, 0); -}; +} static ssize_t rgb_effect_store(struct device *dev, struct device_attribute *attr, const char *buf, @@ -1489,7 +1489,7 @@ static ssize_t rgb_effect_store(struct device *dev, drvdata.rgb_effect = effect; return count; -}; +} static ssize_t rgb_effect_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -1555,7 +1555,7 @@ static ssize_t rgb_speed_store(struct device *dev, drvdata.rgb_speed = val; return count; -}; +} static ssize_t rgb_speed_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -1599,7 +1599,7 @@ static ssize_t rgb_mode_store(struct device *dev, struct device_attribute *attr, drvdata.rgb_mode = val; return count; -}; +} static ssize_t rgb_mode_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -1614,7 +1614,7 @@ static ssize_t rgb_mode_show(struct device *dev, struct device_attribute *attr, return -EINVAL; return sysfs_emit(buf, "%s\n", rgb_mode_text[drvdata.rgb_mode]); -}; +} static ssize_t rgb_mode_index_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -1653,7 +1653,7 @@ static ssize_t rgb_profile_store(struct device *dev, drvdata.rgb_profile = val; return count; -}; +} static ssize_t rgb_profile_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -1668,7 +1668,7 @@ static ssize_t rgb_profile_show(struct device *dev, return -EINVAL; return sysfs_emit(buf, "%hhu\n", drvdata.rgb_profile); -}; +} static ssize_t rgb_profile_range_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -1707,7 +1707,7 @@ static void hid_go_brightness_set(struct led_classdev *led_cdev, break; default: dev_err(led_cdev->dev, "Failed to write RGB profile: %i\n", ret); - }; + } } #define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _group) \ From 2962983a79e3d07cbecd94320f1ae340a41cc4b2 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:07 +0200 Subject: [PATCH 217/295] HID: hid-lenovo-go-s: Fix spelling mistake "configuratiion" -> "configuration" JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit dd800099c26d9cb16d688c23aa469dc677b0b735 Author: Colin Ian King Date: Tue Mar 10 07:29:37 2026 +0000 HID: hid-lenovo-go-s: Fix spelling mistake "configuratiion" -> "configuration" There is a spelling mistake in a dev_err_probe message. Fix it. Signed-off-by: Colin Ian King Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go-s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 8ffa25b20f9c..01c7bdd4fbe0 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -1396,7 +1396,7 @@ static int hid_gos_cfg_probe(struct hid_device *hdev, ret = devm_device_add_group(gos_cdev_rgb.led_cdev.dev, &rgb_attr_group); if (ret) { dev_err_probe(&hdev->dev, ret, - "Failed to create RGB configuratiion attributes\n"); + "Failed to create RGB configuration attributes\n"); return ret; } From 01f0038cfbbb8ad09c700186d9883e1ae5ce1db8 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:07 +0200 Subject: [PATCH 218/295] HID: mcp2221: Add module parameter to enfoce GPIO mode JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 200e6c6e94fa786e36e4fe33eebf16b822181b99 Author: Linus Walleij Date: Wed Feb 18 14:44:38 2026 +0100 HID: mcp2221: Add module parameter to enfoce GPIO mode Add a module parameter to MCP2221 to enfor GPIO mode on the general purpose pins GP0 thru GP3. If I plug a device of this type into my machine, it will use the GP0, GP1, GP2 and GP3 pins for IIO voltage readings by default if CONFIG_IIO is set. However there may be cases where IIO is available but we want to use the GP0 thru GP3 lines for GPIO anyway. Example use: insmode hid-mcp2221.ko gpio_mode_enforce=1 Result in dmesg: mcp2221 0003:04D8:00DD.0005: GPIO 0 not in gpio mode mcp2221 0003:04D8:00DD.0005: GPIO 1 not in gpio mode mcp2221 0003:04D8:00DD.0005: GPIO 2 not in gpio mode mcp2221 0003:04D8:00DD.0005: GPIO 3 not in gpio mode mcp2221 0003:04D8:00DD.0005: Set GPIO mode for gpio pin 0! mcp2221 0003:04D8:00DD.0005: Set GPIO mode for gpio pin 1! mcp2221 0003:04D8:00DD.0005: Set GPIO mode for gpio pin 2! mcp2221 0003:04D8:00DD.0005: Set GPIO mode for gpio pin 3! After this the gpiolib tools such as gpioset can be used to alter the GPIO line values successfully. Signed-off-by: Linus Walleij Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-mcp2221.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c index 8452440e5f8f..b09b61263eb7 100644 --- a/drivers/hid/hid-mcp2221.c +++ b/drivers/hid/hid-mcp2221.c @@ -19,8 +19,15 @@ #include #include #include +#include #include "hid-ids.h" +static bool gpio_mode_enforce; + +module_param(gpio_mode_enforce, bool, 0644); +MODULE_PARM_DESC(gpio_mode_enforce, + "Enfore GPIO mode for GP0 thru GP3 (default: false, will be used for IIO)"); + /* Commands codes in a raw output report */ enum { MCP2221_I2C_WR_DATA = 0x90, @@ -650,7 +657,7 @@ static int mcp2221_check_gpio_pinfunc(struct mcp2221 *mcp) int needgpiofix = 0; int ret; - if (IS_ENABLED(CONFIG_IIO)) + if (IS_ENABLED(CONFIG_IIO) && !gpio_mode_enforce) return 0; ret = mcp_gpio_read_sram(mcp); @@ -1045,7 +1052,8 @@ static void mcp2221_remove(struct hid_device *hdev) #if IS_REACHABLE(CONFIG_IIO) struct mcp2221 *mcp = hid_get_drvdata(hdev); - cancel_delayed_work_sync(&mcp->init_work); + if (!gpio_mode_enforce) + cancel_delayed_work_sync(&mcp->init_work); #endif } @@ -1319,8 +1327,10 @@ static int mcp2221_probe(struct hid_device *hdev, #endif #if IS_REACHABLE(CONFIG_IIO) - INIT_DELAYED_WORK(&mcp->init_work, mcp_init_work); - schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100)); + if (!gpio_mode_enforce) { + INIT_DELAYED_WORK(&mcp->init_work, mcp_init_work); + schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100)); + } #endif return 0; From 52d4c8a367e2fe1dc765178f3ee9f813a04bf77b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:08 +0200 Subject: [PATCH 219/295] HID: pl: eliminate private debug macro JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 9bd35baa37226a76888516edfbe290709d3ffac2 Author: Oliver Neukum Date: Tue Mar 3 09:43:38 2026 +0100 HID: pl: eliminate private debug macro Use proper dynamic debugging. Signed-off-by: Oliver Neukum Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-pl.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/hid/hid-pl.c b/drivers/hid/hid-pl.c index c6c2961dd574..8bba29ef6c7a 100644 --- a/drivers/hid/hid-pl.c +++ b/drivers/hid/hid-pl.c @@ -24,10 +24,6 @@ */ -/* #define DEBUG */ - -#define debug(format, arg...) pr_debug("hid-plff: " format "\n" , ## arg) - #include #include #include @@ -53,14 +49,14 @@ static int hid_plff_play(struct input_dev *dev, void *data, left = effect->u.rumble.strong_magnitude; right = effect->u.rumble.weak_magnitude; - debug("called with 0x%04x 0x%04x", left, right); + hid_dbg(dev, "called with 0x%04x 0x%04x", left, right); left = left * plff->maxval / 0xffff; right = right * plff->maxval / 0xffff; *plff->strong = left; *plff->weak = right; - debug("running with 0x%02x 0x%02x", left, right); + hid_dbg(dev, "running with 0x%02x 0x%02x", left, right); hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT); return 0; @@ -119,7 +115,7 @@ static int plff_init(struct hid_device *hid) report->field[0]->value[1] = 0x00; strong = &report->field[0]->value[2]; weak = &report->field[0]->value[3]; - debug("detected single-field device"); + hid_dbg(hid, "detected single-field device"); } else if (report->field[0]->maxusage == 1 && report->field[0]->usage[0].hid == (HID_UP_LED | 0x43) && @@ -134,7 +130,7 @@ static int plff_init(struct hid_device *hid) weak = &report->field[3]->value[0]; if (hid->vendor == USB_VENDOR_ID_JESS2) maxval = 0xff; - debug("detected 4-field device"); + hid_dbg(hid, "detected 4-field device"); } else { hid_err(hid, "not enough fields or values\n"); return -ENODEV; From 239e4588613dbd3da65bd7df695bcc4946fcd8d0 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:24 +0200 Subject: [PATCH 220/295] HID: mcp2221: Fix spelling mistake "Enfore" -> "Enforce" JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit ac977b900907f621e117184e71766f297a2f702b Author: Colin Ian King Date: Sat Mar 14 17:02:25 2026 +0000 HID: mcp2221: Fix spelling mistake "Enfore" -> "Enforce" There is a spelling mistake in a module description. Fix it. Signed-off-by: Colin Ian King Reviewed-by: Linus Walleij Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-mcp2221.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c index b09b61263eb7..1443be66e0da 100644 --- a/drivers/hid/hid-mcp2221.c +++ b/drivers/hid/hid-mcp2221.c @@ -26,7 +26,7 @@ static bool gpio_mode_enforce; module_param(gpio_mode_enforce, bool, 0644); MODULE_PARM_DESC(gpio_mode_enforce, - "Enfore GPIO mode for GP0 thru GP3 (default: false, will be used for IIO)"); + "Enforce GPIO mode for GP0 thru GP3 (default: false, will be used for IIO)"); /* Commands codes in a raw output report */ enum { From 8fe9b613342d72589e6d9142d7b81aca01c48354 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:25 +0200 Subject: [PATCH 221/295] HID: input: use __free(kfree) to clean up temporary buffers JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 142068281f5138a9888d488e6911e8d9f7923c15 Author: Dmitry Torokhov Date: Sat Feb 28 21:05:11 2026 -0800 HID: input: use __free(kfree) to clean up temporary buffers The __free() cleanup automatically releases given resource when leaving the scope, so use it to make the code less cluttered with error handling. Signed-off-by: Dmitry Torokhov Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-input.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 9475b7e9da43..395138372853 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -429,23 +429,18 @@ static int hidinput_scale_battery_capacity(struct hid_device *dev, static int hidinput_query_battery_capacity(struct hid_device *dev) { - u8 *buf; int ret; - buf = kmalloc(4, GFP_KERNEL); + u8 *buf __free(kfree) = kmalloc(4, GFP_KERNEL); if (!buf) return -ENOMEM; ret = hid_hw_raw_request(dev, dev->battery_report_id, buf, 4, dev->battery_report_type, HID_REQ_GET_REPORT); - if (ret < 2) { - kfree(buf); + if (ret < 2) return -ENODATA; - } - ret = hidinput_scale_battery_capacity(dev, buf[1]); - kfree(buf); - return ret; + return hidinput_scale_battery_capacity(dev, buf[1]); } static int hidinput_get_battery_property(struct power_supply *psy, @@ -1836,7 +1831,6 @@ static void hidinput_led_worker(struct work_struct *work) struct hid_report *report; int ret; u32 len; - __u8 *buf; field = hidinput_get_led_field(hid); if (!field) @@ -1863,7 +1857,7 @@ static void hidinput_led_worker(struct work_struct *work) /* fall back to generic raw-output-report */ len = hid_report_len(report); - buf = hid_alloc_report_buf(report, GFP_KERNEL); + u8 *buf __free(kfree) = hid_alloc_report_buf(report, GFP_KERNEL); if (!buf) return; @@ -1873,7 +1867,6 @@ static void hidinput_led_worker(struct work_struct *work) if (ret == -ENOSYS) hid_hw_raw_request(hid, report->id, buf, len, HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); - kfree(buf); } static int hidinput_input_event(struct input_dev *dev, unsigned int type, From 5ec4d2d9cb2f138dc90cb99c555799e82512175a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:35 +0200 Subject: [PATCH 222/295] HID: input: Convert battery code to devm_* JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 5a9df498581a2e12fd960ddeb1da41dd771d9000 Author: Lucas Zampieri Date: Sat Mar 14 01:05:28 2026 +0000 HID: input: Convert battery code to devm_* Convert the HID battery code to use devm_* managed resource APIs for the power_supply_desc allocation, battery name string, and power supply registration. The error path uses devm_kfree() to clean up allocated memory if devm_power_supply_register() fails, preventing memory waste on repeated setup attempts. The hidinput_cleanup_battery() function is removed as devm handles cleanup automatically. Signed-off-by: Lucas Zampieri Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-input.c | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 395138372853..7e0f971efe5d 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -526,13 +526,13 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, if (quirks & HID_BATTERY_QUIRK_IGNORE) return 0; - psy_desc = kzalloc_obj(*psy_desc); + psy_desc = devm_kzalloc(&dev->dev, sizeof(*psy_desc), GFP_KERNEL); if (!psy_desc) return -ENOMEM; - psy_desc->name = kasprintf(GFP_KERNEL, "hid-%s-battery", - strlen(dev->uniq) ? - dev->uniq : dev_name(&dev->dev)); + psy_desc->name = devm_kasprintf(&dev->dev, GFP_KERNEL, "hid-%s-battery", + strlen(dev->uniq) ? + dev->uniq : dev_name(&dev->dev)); if (!psy_desc->name) { error = -ENOMEM; goto err_free_mem; @@ -574,7 +574,7 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, dev->battery_present = (quirks & HID_BATTERY_QUIRK_DYNAMIC) ? false : true; - dev->battery = power_supply_register(&dev->dev, psy_desc, &psy_cfg); + dev->battery = devm_power_supply_register(&dev->dev, psy_desc, &psy_cfg); if (IS_ERR(dev->battery)) { error = PTR_ERR(dev->battery); hid_warn(dev, "can't register power supply: %d\n", error); @@ -585,27 +585,13 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, return 0; err_free_name: - kfree(psy_desc->name); + devm_kfree(&dev->dev, psy_desc->name); err_free_mem: - kfree(psy_desc); + devm_kfree(&dev->dev, psy_desc); dev->battery = NULL; return error; } -static void hidinput_cleanup_battery(struct hid_device *dev) -{ - const struct power_supply_desc *psy_desc; - - if (!dev->battery) - return; - - psy_desc = dev->battery->desc; - power_supply_unregister(dev->battery); - kfree(psy_desc->name); - kfree(psy_desc); - dev->battery = NULL; -} - static bool hidinput_update_battery_charge_status(struct hid_device *dev, unsigned int usage, int value) { @@ -660,10 +646,6 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, return 0; } -static void hidinput_cleanup_battery(struct hid_device *dev) -{ -} - static void hidinput_update_battery(struct hid_device *dev, unsigned int usage, int value) { @@ -2393,8 +2375,6 @@ void hidinput_disconnect(struct hid_device *hid) { struct hid_input *hidinput, *next; - hidinput_cleanup_battery(hid); - list_for_each_entry_safe(hidinput, next, &hid->inputs, list) { list_del(&hidinput->list); if (hidinput->registered) From b1ac94fb4b6f9a2248846a88c27bb9509ac8832f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:53 +0200 Subject: [PATCH 223/295] HID: input: Introduce struct hid_battery and refactor battery code JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 7a3ac62473f2bd213557e41aaab7a8f144037dfd Author: Lucas Zampieri Date: Sat Mar 14 01:05:29 2026 +0000 HID: input: Introduce struct hid_battery and refactor battery code Introduce struct hid_battery to encapsulate individual battery state, preparing for future multi-battery support. The new structure contains all battery-related fields previously stored directly in hid_device (capacity, min, max, report_type, report_id, charge_status, etc.). The hid_device->battery pointer type changes from struct power_supply* to struct hid_battery*, and all battery functions are refactored accordingly. A hid_get_battery() helper is added for external drivers, with hid-apple.c and hid-magicmouse.c updated to use the new API. The hid-input-test.c KUnit tests are also updated for the new structure. No functional changes for single-battery devices. Signed-off-by: Lucas Zampieri Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-apple.c | 10 +-- drivers/hid/hid-input-test.c | 39 ++++++----- drivers/hid/hid-input.c | 131 +++++++++++++++++++---------------- drivers/hid/hid-magicmouse.c | 10 +-- include/linux/hid.h | 52 ++++++++++---- 5 files changed, 146 insertions(+), 96 deletions(-) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index fc5897a6bb53..8b8b05c8a441 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -623,17 +623,19 @@ static int apple_fetch_battery(struct hid_device *hdev) struct apple_sc *asc = hid_get_drvdata(hdev); struct hid_report_enum *report_enum; struct hid_report *report; + struct hid_battery *bat; - if (!(asc->quirks & APPLE_RDESC_BATTERY) || !hdev->battery) + bat = hid_get_battery(hdev); + if (!(asc->quirks & APPLE_RDESC_BATTERY) || !bat) return -1; - report_enum = &hdev->report_enum[hdev->battery_report_type]; - report = report_enum->report_id_hash[hdev->battery_report_id]; + report_enum = &hdev->report_enum[bat->report_type]; + report = report_enum->report_id_hash[bat->report_id]; if (!report || report->maxfield < 1) return -1; - if (hdev->battery_capacity == hdev->battery_max) + if (bat->capacity == bat->max) return -1; hid_hw_request(hdev, report, HID_REQ_GET_REPORT); diff --git a/drivers/hid/hid-input-test.c b/drivers/hid/hid-input-test.c index 6f5c71660d82..c92008dafddf 100644 --- a/drivers/hid/hid-input-test.c +++ b/drivers/hid/hid-input-test.c @@ -9,54 +9,59 @@ static void hid_test_input_update_battery_charge_status(struct kunit *test) { - struct hid_device *dev; + struct hid_battery *bat; bool handled; - dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + bat = kunit_kzalloc(test, sizeof(*bat), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bat); - handled = hidinput_update_battery_charge_status(dev, HID_DG_HEIGHT, 0); + handled = hidinput_update_battery_charge_status(bat, HID_DG_HEIGHT, 0); KUNIT_EXPECT_FALSE(test, handled); - KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_UNKNOWN); + KUNIT_EXPECT_EQ(test, bat->charge_status, POWER_SUPPLY_STATUS_UNKNOWN); - handled = hidinput_update_battery_charge_status(dev, HID_BAT_CHARGING, 0); + handled = hidinput_update_battery_charge_status(bat, HID_BAT_CHARGING, 0); KUNIT_EXPECT_TRUE(test, handled); - KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_DISCHARGING); + KUNIT_EXPECT_EQ(test, bat->charge_status, POWER_SUPPLY_STATUS_DISCHARGING); - handled = hidinput_update_battery_charge_status(dev, HID_BAT_CHARGING, 1); + handled = hidinput_update_battery_charge_status(bat, HID_BAT_CHARGING, 1); KUNIT_EXPECT_TRUE(test, handled); - KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_CHARGING); + KUNIT_EXPECT_EQ(test, bat->charge_status, POWER_SUPPLY_STATUS_CHARGING); } static void hid_test_input_get_battery_property(struct kunit *test) { struct power_supply *psy; + struct hid_battery *bat; struct hid_device *dev; union power_supply_propval val; int ret; dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); - dev->battery_avoid_query = true; + + bat = kunit_kzalloc(test, sizeof(*bat), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bat); + bat->dev = dev; + bat->avoid_query = true; psy = kunit_kzalloc(test, sizeof(*psy), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, psy); - psy->drv_data = dev; + psy->drv_data = bat; - dev->battery_status = HID_BATTERY_UNKNOWN; - dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING; + bat->status = HID_BATTERY_UNKNOWN; + bat->charge_status = POWER_SUPPLY_STATUS_CHARGING; ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_UNKNOWN); - dev->battery_status = HID_BATTERY_REPORTED; - dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING; + bat->status = HID_BATTERY_REPORTED; + bat->charge_status = POWER_SUPPLY_STATUS_CHARGING; ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_CHARGING); - dev->battery_status = HID_BATTERY_REPORTED; - dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + bat->status = HID_BATTERY_REPORTED; + bat->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_DISCHARGING); diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 7e0f971efe5d..b5d34658b68d 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -416,18 +416,18 @@ static unsigned find_battery_quirk(struct hid_device *hdev) return quirks; } -static int hidinput_scale_battery_capacity(struct hid_device *dev, +static int hidinput_scale_battery_capacity(struct hid_battery *bat, int value) { - if (dev->battery_min < dev->battery_max && - value >= dev->battery_min && value <= dev->battery_max) - value = ((value - dev->battery_min) * 100) / - (dev->battery_max - dev->battery_min); + if (bat->min < bat->max && + value >= bat->min && value <= bat->max) + value = ((value - bat->min) * 100) / + (bat->max - bat->min); return value; } -static int hidinput_query_battery_capacity(struct hid_device *dev) +static int hidinput_query_battery_capacity(struct hid_battery *bat) { int ret; @@ -435,19 +435,20 @@ static int hidinput_query_battery_capacity(struct hid_device *dev) if (!buf) return -ENOMEM; - ret = hid_hw_raw_request(dev, dev->battery_report_id, buf, 4, - dev->battery_report_type, HID_REQ_GET_REPORT); + ret = hid_hw_raw_request(bat->dev, bat->report_id, buf, 4, + bat->report_type, HID_REQ_GET_REPORT); if (ret < 2) return -ENODATA; - return hidinput_scale_battery_capacity(dev, buf[1]); + return hidinput_scale_battery_capacity(bat, buf[1]); } static int hidinput_get_battery_property(struct power_supply *psy, enum power_supply_property prop, union power_supply_propval *val) { - struct hid_device *dev = power_supply_get_drvdata(psy); + struct hid_battery *bat = power_supply_get_drvdata(psy); + struct hid_device *dev = bat->dev; int value; int ret = 0; @@ -457,17 +458,17 @@ static int hidinput_get_battery_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_PRESENT: - val->intval = dev->battery_present; + val->intval = bat->present; break; case POWER_SUPPLY_PROP_CAPACITY: - if (dev->battery_status != HID_BATTERY_REPORTED && - !dev->battery_avoid_query) { - value = hidinput_query_battery_capacity(dev); + if (bat->status != HID_BATTERY_REPORTED && + !bat->avoid_query) { + value = hidinput_query_battery_capacity(bat); if (value < 0) return value; } else { - value = dev->battery_capacity; + value = bat->capacity; } val->intval = value; @@ -478,20 +479,20 @@ static int hidinput_get_battery_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_STATUS: - if (dev->battery_status != HID_BATTERY_REPORTED && - !dev->battery_avoid_query) { - value = hidinput_query_battery_capacity(dev); + if (bat->status != HID_BATTERY_REPORTED && + !bat->avoid_query) { + value = hidinput_query_battery_capacity(bat); if (value < 0) return value; - dev->battery_capacity = value; - dev->battery_status = HID_BATTERY_QUERIED; + bat->capacity = value; + bat->status = HID_BATTERY_QUERIED; } - if (dev->battery_status == HID_BATTERY_UNKNOWN) + if (bat->status == HID_BATTERY_UNKNOWN) val->intval = POWER_SUPPLY_STATUS_UNKNOWN; else - val->intval = dev->battery_charge_status; + val->intval = bat->charge_status; break; case POWER_SUPPLY_PROP_SCOPE: @@ -509,8 +510,9 @@ static int hidinput_get_battery_property(struct power_supply *psy, static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, struct hid_field *field, bool is_percentage) { + struct hid_battery *bat; struct power_supply_desc *psy_desc; - struct power_supply_config psy_cfg = { .drv_data = dev, }; + struct power_supply_config psy_cfg = { 0 }; unsigned quirks; s32 min, max; int error; @@ -526,16 +528,22 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, if (quirks & HID_BATTERY_QUIRK_IGNORE) return 0; - psy_desc = devm_kzalloc(&dev->dev, sizeof(*psy_desc), GFP_KERNEL); - if (!psy_desc) + bat = devm_kzalloc(&dev->dev, sizeof(*bat), GFP_KERNEL); + if (!bat) return -ENOMEM; + psy_desc = devm_kzalloc(&dev->dev, sizeof(*psy_desc), GFP_KERNEL); + if (!psy_desc) { + error = -ENOMEM; + goto err_free_bat; + } + psy_desc->name = devm_kasprintf(&dev->dev, GFP_KERNEL, "hid-%s-battery", strlen(dev->uniq) ? dev->uniq : dev_name(&dev->dev)); if (!psy_desc->name) { error = -ENOMEM; - goto err_free_mem; + goto err_free_desc; } psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; @@ -555,51 +563,57 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, if (quirks & HID_BATTERY_QUIRK_FEATURE) report_type = HID_FEATURE_REPORT; - dev->battery_min = min; - dev->battery_max = max; - dev->battery_report_type = report_type; - dev->battery_report_id = field->report->id; - dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + bat->dev = dev; + bat->min = min; + bat->max = max; + bat->report_type = report_type; + bat->report_id = field->report->id; + bat->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + bat->status = HID_BATTERY_UNKNOWN; /* * Stylus is normally not connected to the device and thus we * can't query the device and get meaningful battery strength. * We have to wait for the device to report it on its own. */ - dev->battery_avoid_query = report_type == HID_INPUT_REPORT && - field->physical == HID_DG_STYLUS; + bat->avoid_query = report_type == HID_INPUT_REPORT && + field->physical == HID_DG_STYLUS; if (quirks & HID_BATTERY_QUIRK_AVOID_QUERY) - dev->battery_avoid_query = true; + bat->avoid_query = true; - dev->battery_present = (quirks & HID_BATTERY_QUIRK_DYNAMIC) ? false : true; + bat->present = (quirks & HID_BATTERY_QUIRK_DYNAMIC) ? false : true; - dev->battery = devm_power_supply_register(&dev->dev, psy_desc, &psy_cfg); - if (IS_ERR(dev->battery)) { - error = PTR_ERR(dev->battery); + psy_cfg.drv_data = bat; + bat->ps = devm_power_supply_register(&dev->dev, psy_desc, &psy_cfg); + if (IS_ERR(bat->ps)) { + error = PTR_ERR(bat->ps); hid_warn(dev, "can't register power supply: %d\n", error); goto err_free_name; } - power_supply_powers(dev->battery, &dev->dev); + power_supply_powers(bat->ps, &dev->dev); + dev->battery = bat; return 0; err_free_name: devm_kfree(&dev->dev, psy_desc->name); -err_free_mem: +err_free_desc: devm_kfree(&dev->dev, psy_desc); +err_free_bat: + devm_kfree(&dev->dev, bat); dev->battery = NULL; return error; } -static bool hidinput_update_battery_charge_status(struct hid_device *dev, +static bool hidinput_update_battery_charge_status(struct hid_battery *bat, unsigned int usage, int value) { switch (usage) { case HID_BAT_CHARGING: - dev->battery_charge_status = value ? - POWER_SUPPLY_STATUS_CHARGING : - POWER_SUPPLY_STATUS_DISCHARGING; + bat->charge_status = value ? + POWER_SUPPLY_STATUS_CHARGING : + POWER_SUPPLY_STATUS_DISCHARGING; return true; } @@ -609,34 +623,35 @@ static bool hidinput_update_battery_charge_status(struct hid_device *dev, static void hidinput_update_battery(struct hid_device *dev, unsigned int usage, int value) { + struct hid_battery *bat = dev->battery; int capacity; - if (!dev->battery) + if (!bat) return; - if (hidinput_update_battery_charge_status(dev, usage, value)) { - dev->battery_present = true; - power_supply_changed(dev->battery); + if (hidinput_update_battery_charge_status(bat, usage, value)) { + bat->present = true; + power_supply_changed(bat->ps); return; } if ((usage & HID_USAGE_PAGE) == HID_UP_DIGITIZER && value == 0) return; - if (value < dev->battery_min || value > dev->battery_max) + if (value < bat->min || value > bat->max) return; - capacity = hidinput_scale_battery_capacity(dev, value); + capacity = hidinput_scale_battery_capacity(bat, value); - if (dev->battery_status != HID_BATTERY_REPORTED || - capacity != dev->battery_capacity || - ktime_after(ktime_get_coarse(), dev->battery_ratelimit_time)) { - dev->battery_present = true; - dev->battery_capacity = capacity; - dev->battery_status = HID_BATTERY_REPORTED; - dev->battery_ratelimit_time = + if (bat->status != HID_BATTERY_REPORTED || + capacity != bat->capacity || + ktime_after(ktime_get_coarse(), bat->ratelimit_time)) { + bat->present = true; + bat->capacity = capacity; + bat->status = HID_BATTERY_REPORTED; + bat->ratelimit_time = ktime_add_ms(ktime_get_coarse(), 30 * 1000); - power_supply_changed(dev->battery); + power_supply_changed(bat->ps); } } #else /* !CONFIG_HID_BATTERY_STRENGTH */ diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 9eadf3252d0d..e70bd3dc07ab 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -817,19 +817,21 @@ static int magicmouse_fetch_battery(struct hid_device *hdev) #ifdef CONFIG_HID_BATTERY_STRENGTH struct hid_report_enum *report_enum; struct hid_report *report; + struct hid_battery *bat; - if (!hdev->battery || + bat = hid_get_battery(hdev); + if (!bat || (!is_usb_magicmouse2(hdev->vendor, hdev->product) && !is_usb_magictrackpad2(hdev->vendor, hdev->product))) return -1; - report_enum = &hdev->report_enum[hdev->battery_report_type]; - report = report_enum->report_id_hash[hdev->battery_report_id]; + report_enum = &hdev->report_enum[bat->report_type]; + report = report_enum->report_id_hash[bat->report_id]; if (!report || report->maxfield < 1) return -1; - if (hdev->battery_capacity == hdev->battery_max) + if (bat->capacity == bat->max) return -1; hid_hw_request(hdev, report, HID_REQ_GET_REPORT); diff --git a/include/linux/hid.h b/include/linux/hid.h index 44357295b7a1..71beff0031e7 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -634,6 +634,36 @@ enum hid_battery_status { HID_BATTERY_REPORTED, /* Device sent unsolicited battery strength report */ }; +/** + * struct hid_battery - represents a single battery power supply + * @dev: pointer to the parent hid_device + * @ps: the power supply instance + * @min: minimum battery value from HID descriptor + * @max: maximum battery value from HID descriptor + * @report_type: HID report type (input/feature) + * @report_id: HID report ID for this battery + * @charge_status: current charging status + * @status: battery reporting status + * @capacity: current battery capacity (0-100) + * @avoid_query: if true, avoid querying battery (e.g., for stylus) + * @present: if true, battery is present (may be dynamic) + * @ratelimit_time: rate limiting for battery reports + */ +struct hid_battery { + struct hid_device *dev; + struct power_supply *ps; + __s32 min; + __s32 max; + __s32 report_type; + __s32 report_id; + __s32 charge_status; + enum hid_battery_status status; + __s32 capacity; + bool avoid_query; + bool present; + ktime_t ratelimit_time; +}; + struct hid_driver; struct hid_ll_driver; @@ -670,20 +700,9 @@ struct hid_device { #ifdef CONFIG_HID_BATTERY_STRENGTH /* * Power supply information for HID devices which report - * battery strength. power_supply was successfully registered if - * battery is non-NULL. + * battery strength. battery is non-NULL if successfully registered. */ - struct power_supply *battery; - __s32 battery_capacity; - __s32 battery_min; - __s32 battery_max; - __s32 battery_report_type; - __s32 battery_report_id; - __s32 battery_charge_status; - enum hid_battery_status battery_status; - bool battery_avoid_query; - bool battery_present; - ktime_t battery_ratelimit_time; + struct hid_battery *battery; #endif unsigned long status; /* see STAT flags above */ @@ -745,6 +764,13 @@ static inline void hid_set_drvdata(struct hid_device *hdev, void *data) dev_set_drvdata(&hdev->dev, data); } +#ifdef CONFIG_HID_BATTERY_STRENGTH +static inline struct hid_battery *hid_get_battery(struct hid_device *hdev) +{ + return hdev->battery; +} +#endif + #define HID_GLOBAL_STACK_SIZE 4 #define HID_COLLECTION_STACK_SIZE 4 From a3dada43c0e714ca1af6cb0df7d0c09abde80248 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:54 +0200 Subject: [PATCH 224/295] HID: input: Add support for multiple batteries per device JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 4a58ae85c3f9b142ffba023d0f976978ade57d1b Author: Lucas Zampieri Date: Sat Mar 14 01:05:30 2026 +0000 HID: input: Add support for multiple batteries per device Add support for HID devices that report multiple batteries, each identified by its report ID. The hid_device->battery pointer is replaced with a batteries list. Batteries are named using the pattern hid-{uniq}-battery-{report_id}. The hid_get_battery() helper returns the first battery in the list for backwards compatibility with single-battery drivers. Signed-off-by: Lucas Zampieri Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 4 ++++ drivers/hid/hid-input.c | 44 ++++++++++++++++++++++++++++------------- include/linux/hid.h | 11 ++++++++--- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 24efe3b9d805..11c001f6f7c5 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2996,6 +2996,10 @@ struct hid_device *hid_allocate_device(void) mutex_init(&hdev->ll_open_lock); kref_init(&hdev->ref); +#ifdef CONFIG_HID_BATTERY_STRENGTH + INIT_LIST_HEAD(&hdev->batteries); +#endif + ret = hid_bpf_device_init(hdev); if (ret) goto out_err; diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index b5d34658b68d..8fff185fe0e6 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -507,6 +507,18 @@ static int hidinput_get_battery_property(struct power_supply *psy, return ret; } +static struct hid_battery *hidinput_find_battery(struct hid_device *dev, + int report_id) +{ + struct hid_battery *bat; + + list_for_each_entry(bat, &dev->batteries, list) { + if (bat->report_id == report_id) + return bat; + } + return NULL; +} + static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, struct hid_field *field, bool is_percentage) { @@ -517,13 +529,15 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, s32 min, max; int error; - if (dev->battery) - return 0; /* already initialized? */ + /* Check if battery for this report ID already exists */ + if (hidinput_find_battery(dev, field->report->id)) + return 0; quirks = find_battery_quirk(dev); - hid_dbg(dev, "device %x:%x:%x %d quirks %d\n", - dev->bus, dev->vendor, dev->product, dev->version, quirks); + hid_dbg(dev, "device %x:%x:%x %d quirks %d report_id %d\n", + dev->bus, dev->vendor, dev->product, dev->version, quirks, + field->report->id); if (quirks & HID_BATTERY_QUIRK_IGNORE) return 0; @@ -538,9 +552,11 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, goto err_free_bat; } - psy_desc->name = devm_kasprintf(&dev->dev, GFP_KERNEL, "hid-%s-battery", + psy_desc->name = devm_kasprintf(&dev->dev, GFP_KERNEL, + "hid-%s-battery-%d", strlen(dev->uniq) ? - dev->uniq : dev_name(&dev->dev)); + dev->uniq : dev_name(&dev->dev), + field->report->id); if (!psy_desc->name) { error = -ENOMEM; goto err_free_desc; @@ -593,7 +609,7 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, } power_supply_powers(bat->ps, &dev->dev); - dev->battery = bat; + list_add_tail(&bat->list, &dev->batteries); return 0; err_free_name: @@ -602,7 +618,6 @@ err_free_desc: devm_kfree(&dev->dev, psy_desc); err_free_bat: devm_kfree(&dev->dev, bat); - dev->battery = NULL; return error; } @@ -620,12 +635,13 @@ static bool hidinput_update_battery_charge_status(struct hid_battery *bat, return false; } -static void hidinput_update_battery(struct hid_device *dev, unsigned int usage, - int value) +static void hidinput_update_battery(struct hid_device *dev, int report_id, + unsigned int usage, int value) { - struct hid_battery *bat = dev->battery; + struct hid_battery *bat; int capacity; + bat = hidinput_find_battery(dev, report_id); if (!bat) return; @@ -661,8 +677,8 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, return 0; } -static void hidinput_update_battery(struct hid_device *dev, unsigned int usage, - int value) +static void hidinput_update_battery(struct hid_device *dev, int report_id, + unsigned int usage, int value) { } #endif /* CONFIG_HID_BATTERY_STRENGTH */ @@ -1546,7 +1562,7 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct return; if (usage->type == EV_PWR) { - hidinput_update_battery(hid, usage->hid, value); + hidinput_update_battery(hid, report->id, usage->hid, value); return; } diff --git a/include/linux/hid.h b/include/linux/hid.h index 71beff0031e7..442a80d79e89 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -648,6 +648,7 @@ enum hid_battery_status { * @avoid_query: if true, avoid querying battery (e.g., for stylus) * @present: if true, battery is present (may be dynamic) * @ratelimit_time: rate limiting for battery reports + * @list: list node for linking into hid_device's battery list */ struct hid_battery { struct hid_device *dev; @@ -662,6 +663,7 @@ struct hid_battery { bool avoid_query; bool present; ktime_t ratelimit_time; + struct list_head list; }; struct hid_driver; @@ -700,9 +702,10 @@ struct hid_device { #ifdef CONFIG_HID_BATTERY_STRENGTH /* * Power supply information for HID devices which report - * battery strength. battery is non-NULL if successfully registered. + * battery strength. Each battery is tracked separately in the + * batteries list. */ - struct hid_battery *battery; + struct list_head batteries; #endif unsigned long status; /* see STAT flags above */ @@ -767,7 +770,9 @@ static inline void hid_set_drvdata(struct hid_device *hdev, void *data) #ifdef CONFIG_HID_BATTERY_STRENGTH static inline struct hid_battery *hid_get_battery(struct hid_device *hdev) { - return hdev->battery; + if (list_empty(&hdev->batteries)) + return NULL; + return list_first_entry(&hdev->batteries, struct hid_battery, list); } #endif From 009b44b00ac6e64eee56414b25226d96917ba68b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:55 +0200 Subject: [PATCH 225/295] HID: hid-lenovo-go: fix LEDS dependencies JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 19acad732afbfb23bd0d4d5830d7553889e739f8 Author: Arnd Bergmann Date: Tue Mar 24 20:20:28 2026 +0100 HID: hid-lenovo-go: fix LEDS dependencies The newly added hid-lenovo-go and hid-lenovo-go-s drivers attempt to 'select LEDS_CLASS', which has a dependency on NEW_LEDS, causing a build failure when NEW_LEDS itself it disabled: WARNING: unmet direct dependencies detected for LEDS_CLASS Depends on [n]: NEW_LEDS [=n] Selected by [m]: - HID_LENOVO_GO [=m] && HID_SUPPORT [=y] && HID [=m] && USB_HID [=m] - HID_LENOVO_GO_S [=m] && HID_SUPPORT [=y] && HID [=m] && USB_HID [=m] WARNING: unmet direct dependencies detected for LEDS_CLASS_MULTICOLOR Depends on [n]: NEW_LEDS [=n] && LEDS_CLASS [=m] Selected by [m]: - HID_LENOVO_GO [=m] && HID_SUPPORT [=y] && HID [=m] && USB_HID [=m] - HID_LENOVO_GO_S [=m] && HID_SUPPORT [=y] && HID [=m] && USB_HID [=m] ERROR: modpost: "led_set_brightness_nopm" [drivers/leds/led-class.ko] undefined! ERROR: modpost: "led_set_brightness" [drivers/leds/led-class.ko] undefined! ERROR: modpost: "led_get_color_name" [drivers/leds/led-class-multicolor.ko] undefined! ERROR: modpost: "led_set_brightness" [drivers/leds/led-class-multicolor.ko] undefined! Device drivers generally should not select other subsystems like this, as that tends to cause dependency problems including loops in the dependency graph. Change these two and the older hid-lenovo driver to behave the same way as all other HID drivers and use 'depends on LEDS_CLASS' or 'depends on LEDS_CLASS_MULTICOLOR' instead, which itself has NEW_LEDS and LEDS_CLASS as dependencies. Fixes: a23f3497bf20 ("HID: hid-lenovo-go-s: Add Lenovo Legion Go S Series HID Driver") Fixes: d69ccfcbc955 ("HID: hid-lenovo-go: Add Lenovo Legion Go Series HID Driver") Signed-off-by: Arnd Bergmann Reviewed-by: Mark Pearson Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/Kconfig | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index f3b5332ae82a..af0daead223a 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -610,8 +610,7 @@ config HID_LED config HID_LENOVO tristate "Lenovo / Thinkpad devices" - select NEW_LEDS - select LEDS_CLASS + depends on LEDS_CLASS help Support for IBM/Lenovo devices that are not fully compliant with HID standard. @@ -626,8 +625,7 @@ config HID_LENOVO config HID_LENOVO_GO tristate "HID Driver for Lenovo Legion Go Series Controllers" depends on USB_HID - select LEDS_CLASS - select LEDS_CLASS_MULTICOLOR + depends on LEDS_CLASS_MULTICOLOR help Support for Lenovo Legion Go devices with detachable controllers. @@ -638,8 +636,7 @@ config HID_LENOVO_GO config HID_LENOVO_GO_S tristate "HID Driver for Lenovo Legion Go S Controller" depends on USB_HID - select LEDS_CLASS - select LEDS_CLASS_MULTICOLOR + depends on LEDS_CLASS_MULTICOLOR help Support for Lenovo Legion Go S Handheld Console Controller. From 5c6144ab82803a99f0e21dafc0527cd67021a5e1 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:11:56 +0200 Subject: [PATCH 226/295] HID: core: factor out hid_parse_collections() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit d7db259bd6df56f9540ef92535a5c709b375c4d5 Author: Dmitry Torokhov Date: Wed Mar 25 23:25:37 2026 -0700 HID: core: factor out hid_parse_collections() In preparation to making use of __free(...) cleanup facilities to clean up temporary buffers, factor out code parsing collections into a separate function to make the code simpler. Signed-off-by: Dmitry Torokhov Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 169 ++++++++++++++++++++++------------------- 1 file changed, 89 insertions(+), 80 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 11c001f6f7c5..02702e0b6f7d 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1244,6 +1244,88 @@ void hid_setup_resolution_multiplier(struct hid_device *hid) } EXPORT_SYMBOL_GPL(hid_setup_resolution_multiplier); +static int hid_parse_collections(struct hid_device *device) +{ + struct hid_parser *parser; + struct hid_item item; + const u8 *start = device->rdesc; + const u8 *end = start + device->rsize; + const u8 *next; + int ret; + static typeof(hid_parser_main) (* const dispatch_type[]) = { + hid_parser_main, + hid_parser_global, + hid_parser_local, + hid_parser_reserved + }; + + parser = vzalloc(sizeof(*parser)); + if (!parser) + return -ENOMEM; + + parser->device = device; + + device->collection = kzalloc_objs(*device->collection, + HID_DEFAULT_NUM_COLLECTIONS); + if (!device->collection) { + ret = -ENOMEM; + goto out; + } + device->collection_size = HID_DEFAULT_NUM_COLLECTIONS; + for (unsigned int i = 0; i < HID_DEFAULT_NUM_COLLECTIONS; i++) + device->collection[i].parent_idx = -1; + + ret = -EINVAL; + while ((next = fetch_item(start, end, &item)) != NULL) { + start = next; + + if (item.format != HID_ITEM_FORMAT_SHORT) { + hid_err(device, "unexpected long global item\n"); + goto out; + } + + if (dispatch_type[item.type](parser, &item)) { + hid_err(device, "item %u %u %u %u parsing failed\n", + item.format, + (unsigned int)item.size, + (unsigned int)item.type, + (unsigned int)item.tag); + goto out; + } + } + + if (start != end) { + hid_err(device, "item fetching failed at offset %u/%u\n", + device->rsize - (unsigned int)(end - start), + device->rsize); + goto out; + } + + if (parser->collection_stack_ptr) { + hid_err(device, "unbalanced collection at end of report description\n"); + goto out; + } + + if (parser->local.delimiter_depth) { + hid_err(device, "unbalanced delimiter at end of report description\n"); + goto out; + } + + /* + * fetch initial values in case the device's + * default multiplier isn't the recommended 1 + */ + hid_setup_resolution_multiplier(device); + + device->status |= HID_STAT_PARSED; + ret = 0; + +out: + kfree(parser->collection_stack); + vfree(parser); + return ret; +} + /** * hid_open_report - open a driver-specific device report * @@ -1258,21 +1340,9 @@ EXPORT_SYMBOL_GPL(hid_setup_resolution_multiplier); */ int hid_open_report(struct hid_device *device) { - struct hid_parser *parser; - struct hid_item item; unsigned int size; - const __u8 *start; - const __u8 *end; - const __u8 *next; - int ret; - int i; - static int (*dispatch_type[])(struct hid_parser *parser, - struct hid_item *item) = { - hid_parser_main, - hid_parser_global, - hid_parser_local, - hid_parser_reserved - }; + const u8 *start; + int error; if (WARN_ON(device->status & HID_STAT_PARSED)) return -EBUSY; @@ -1309,74 +1379,13 @@ int hid_open_report(struct hid_device *device) device->rdesc = start; device->rsize = size; - parser = vzalloc(sizeof(struct hid_parser)); - if (!parser) { - ret = -ENOMEM; - goto alloc_err; + error = hid_parse_collections(device); + if (error) { + hid_close_report(device); + return error; } - parser->device = device; - - end = start + size; - - device->collection = kzalloc_objs(struct hid_collection, - HID_DEFAULT_NUM_COLLECTIONS); - if (!device->collection) { - ret = -ENOMEM; - goto err; - } - device->collection_size = HID_DEFAULT_NUM_COLLECTIONS; - for (i = 0; i < HID_DEFAULT_NUM_COLLECTIONS; i++) - device->collection[i].parent_idx = -1; - - ret = -EINVAL; - while ((next = fetch_item(start, end, &item)) != NULL) { - start = next; - - if (item.format != HID_ITEM_FORMAT_SHORT) { - hid_err(device, "unexpected long global item\n"); - goto err; - } - - if (dispatch_type[item.type](parser, &item)) { - hid_err(device, "item %u %u %u %u parsing failed\n", - item.format, (unsigned)item.size, - (unsigned)item.type, (unsigned)item.tag); - goto err; - } - - if (start == end) { - if (parser->collection_stack_ptr) { - hid_err(device, "unbalanced collection at end of report description\n"); - goto err; - } - if (parser->local.delimiter_depth) { - hid_err(device, "unbalanced delimiter at end of report description\n"); - goto err; - } - - /* - * fetch initial values in case the device's - * default multiplier isn't the recommended 1 - */ - hid_setup_resolution_multiplier(device); - - kfree(parser->collection_stack); - vfree(parser); - device->status |= HID_STAT_PARSED; - - return 0; - } - } - - hid_err(device, "item fetching failed at offset %u/%u\n", - size - (unsigned int)(end - start), size); -err: - kfree(parser->collection_stack); -alloc_err: - vfree(parser); - hid_close_report(device); - return ret; + return 0; } EXPORT_SYMBOL_GPL(hid_open_report); From 4d4087feec8330e3c767ca2f757cc3da508dbab1 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:05 +0200 Subject: [PATCH 227/295] HID: core: use __free(kfree) and __free(kvfree) to clean up temporary buffers JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 8c58be287604da91a4da6b0b1af866c1c193576c Author: Dmitry Torokhov Date: Wed Mar 25 23:25:38 2026 -0700 HID: core: use __free(kfree) and __free(kvfree) to clean up temporary buffers This simplifies error handling and protects against memory leaks. Signed-off-by: Dmitry Torokhov Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 02702e0b6f7d..5f3769395246 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -924,7 +924,6 @@ static int hid_scan_main(struct hid_parser *parser, struct hid_item *item) */ static int hid_scan_report(struct hid_device *hid) { - struct hid_parser *parser; struct hid_item item; const __u8 *start = hid->dev_rdesc; const __u8 *end = start + hid->dev_rsize; @@ -936,7 +935,7 @@ static int hid_scan_report(struct hid_device *hid) hid_parser_reserved }; - parser = vzalloc(sizeof(struct hid_parser)); + struct hid_parser *parser __free(kvfree) = vzalloc(sizeof(*parser)); if (!parser) return -ENOMEM; @@ -987,7 +986,6 @@ static int hid_scan_report(struct hid_device *hid) } kfree(parser->collection_stack); - vfree(parser); return 0; } @@ -1246,7 +1244,6 @@ EXPORT_SYMBOL_GPL(hid_setup_resolution_multiplier); static int hid_parse_collections(struct hid_device *device) { - struct hid_parser *parser; struct hid_item item; const u8 *start = device->rdesc; const u8 *end = start + device->rsize; @@ -1259,7 +1256,7 @@ static int hid_parse_collections(struct hid_device *device) hid_parser_reserved }; - parser = vzalloc(sizeof(*parser)); + struct hid_parser *parser __free(kvfree) = vzalloc(sizeof(*parser)); if (!parser) return -ENOMEM; @@ -1267,10 +1264,9 @@ static int hid_parse_collections(struct hid_device *device) device->collection = kzalloc_objs(*device->collection, HID_DEFAULT_NUM_COLLECTIONS); - if (!device->collection) { - ret = -ENOMEM; - goto out; - } + if (!device->collection) + return -ENOMEM; + device->collection_size = HID_DEFAULT_NUM_COLLECTIONS; for (unsigned int i = 0; i < HID_DEFAULT_NUM_COLLECTIONS; i++) device->collection[i].parent_idx = -1; @@ -1322,7 +1318,6 @@ static int hid_parse_collections(struct hid_device *device) out: kfree(parser->collection_stack); - vfree(parser); return ret; } @@ -1358,9 +1353,9 @@ int hid_open_report(struct hid_device *device) * on a copy of our report descriptor so it can * change it. */ - __u8 *buf = kmemdup(start, size, GFP_KERNEL); + u8 *buf __free(kfree) = kmemdup(start, size, GFP_KERNEL); - if (buf == NULL) + if (!buf) return -ENOMEM; start = device->driver->report_fixup(device, buf, &size); @@ -1371,8 +1366,7 @@ int hid_open_report(struct hid_device *device) * needs to be cleaned up or not at the end. */ start = kmemdup(start, size, GFP_KERNEL); - kfree(buf); - if (start == NULL) + if (!start) return -ENOMEM; } @@ -1998,11 +1992,11 @@ static struct hid_report *hid_get_report(struct hid_report_enum *report_enum, int __hid_request(struct hid_device *hid, struct hid_report *report, enum hid_class_request reqtype) { - char *buf, *data_buf; + u8 *data_buf; int ret; u32 len; - buf = hid_alloc_report_buf(report, GFP_KERNEL); + u8 *buf __free(kfree) = hid_alloc_report_buf(report, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -2021,17 +2015,13 @@ int __hid_request(struct hid_device *hid, struct hid_report *report, ret = hid_hw_raw_request(hid, report->id, buf, len, report->type, reqtype); if (ret < 0) { dbg_hid("unable to complete request: %d\n", ret); - goto out; + return ret; } if (reqtype == HID_REQ_GET_REPORT) hid_input_report(hid, report->type, buf, ret, 0); - ret = 0; - -out: - kfree(buf); - return ret; + return 0; } EXPORT_SYMBOL_GPL(__hid_request); From e93a1736425251c02486ecde7fc76229c2629d64 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:06 +0200 Subject: [PATCH 228/295] HID: intel-thc-hid: Intel-quickspi: Improve power management for touch devices JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 4ba38ee380555a5aeb4291d5111faaf7852d63bd Author: Even Xu Date: Wed Mar 18 11:25:47 2026 +0800 HID: intel-thc-hid: Intel-quickspi: Improve power management for touch devices Enhance power management with two key improvements: 1. Hibernate support: Send POWER_OFF command when entering hibernate mode. 2. Conditional sleep commands: Only send POWER_SLEEP/POWER_ON commands during system suspend/resume when the touch device is not configured as a wake source, preserving Wake-on-Touch (WoT) functionality. This ensures proper power states while maintaining expected wake behavior. Signed-off-by: Even Xu Tested-by: Rui Zhang Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../intel-thc-hid/intel-quickspi/pci-quickspi.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c index b6a69995692c..f669235f1883 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c @@ -753,9 +753,11 @@ static int quickspi_suspend(struct device *device) if (!qsdev) return -ENODEV; - ret = quickspi_set_power(qsdev, HIDSPI_SLEEP); - if (ret) - return ret; + if (!device_may_wakeup(qsdev->dev)) { + ret = quickspi_set_power(qsdev, HIDSPI_SLEEP); + if (ret) + return ret; + } ret = thc_interrupt_quiesce(qsdev->thc_hw, true); if (ret) @@ -794,9 +796,8 @@ static int quickspi_resume(struct device *device) if (ret) return ret; - ret = quickspi_set_power(qsdev, HIDSPI_ON); - if (ret) - return ret; + if (!device_may_wakeup(qsdev->dev)) + return quickspi_set_power(qsdev, HIDSPI_ON); return 0; } @@ -855,6 +856,9 @@ static int quickspi_poweroff(struct device *device) if (!qsdev) return -ENODEV; + /* Ignore the return value as platform will be poweroff soon */ + quickspi_set_power(qsdev, HIDSPI_OFF); + ret = thc_interrupt_quiesce(qsdev->thc_hw, true); if (ret) return ret; From 98469f9beb4f51de7c07258ec9faac37f8f40f7c Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:07 +0200 Subject: [PATCH 229/295] HID: intel-thc-hid: Intel-thc: Add more frequency support for SPI JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 88919bedabb8d34b6b76c00238310b7deca33a2d Author: Even Xu Date: Wed Mar 18 11:22:04 2026 +0800 HID: intel-thc-hid: Intel-thc: Add more frequency support for SPI The Nova Lake platform enhances THC with half divider capability for clock division, allowing more granular frequency control for the THC SPI port. Supported frequencies include 50MHz (125MHz/2.5), 35MHz (125MHz/3.5), and 10MHz (125MHz/8/1.5). Signed-off-by: Even Xu Tested-by: Rui Zhang Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../intel-thc-hid/intel-thc/intel-thc-dev.c | 47 +++++++++++++++++++ .../intel-thc-hid/intel-thc/intel-thc-hw.h | 4 ++ 2 files changed, 51 insertions(+) diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c index d8e195189e4b..9a8449428170 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c @@ -1112,12 +1112,15 @@ int thc_port_select(struct thc_device *dev, enum thc_port_type port_type) EXPORT_SYMBOL_NS_GPL(thc_port_select, "INTEL_THC"); #define THC_SPI_FREQUENCY_7M 7812500 +#define THC_SPI_FREQUENCY_10M 10416700 #define THC_SPI_FREQUENCY_15M 15625000 #define THC_SPI_FREQUENCY_17M 17857100 #define THC_SPI_FREQUENCY_20M 20833000 #define THC_SPI_FREQUENCY_25M 25000000 #define THC_SPI_FREQUENCY_31M 31250000 +#define THC_SPI_FREQUENCY_35M 35714200 #define THC_SPI_FREQUENCY_41M 41666700 +#define THC_SPI_FREQUENCY_50M 50000000 #define THC_SPI_LOW_FREQUENCY THC_SPI_FREQUENCY_17M @@ -1125,21 +1128,27 @@ static u8 thc_get_spi_freq_div_val(struct thc_device *dev, u32 spi_freq_val) { static const int frequency[] = { THC_SPI_FREQUENCY_7M, + THC_SPI_FREQUENCY_10M, THC_SPI_FREQUENCY_15M, THC_SPI_FREQUENCY_17M, THC_SPI_FREQUENCY_20M, THC_SPI_FREQUENCY_25M, THC_SPI_FREQUENCY_31M, + THC_SPI_FREQUENCY_35M, THC_SPI_FREQUENCY_41M, + THC_SPI_FREQUENCY_50M, }; static const u8 frequency_div[] = { THC_SPI_FRQ_DIV_2, THC_SPI_FRQ_DIV_1, + THC_SPI_FRQ_DIV_1, THC_SPI_FRQ_DIV_7, THC_SPI_FRQ_DIV_6, THC_SPI_FRQ_DIV_5, THC_SPI_FRQ_DIV_4, THC_SPI_FRQ_DIV_3, + THC_SPI_FRQ_DIV_3, + THC_SPI_FRQ_DIV_2, }; int size = ARRAY_SIZE(frequency); u32 closest_freq; @@ -1190,6 +1199,25 @@ int thc_spi_read_config(struct thc_device *dev, u32 spi_freq_val, if (spi_freq_val < THC_SPI_LOW_FREQUENCY) is_low_freq = true; + /* 10M, 35M and 50M CLK need 1.5, 3.5 and 2.5 half divider */ + if ((freq_div == THC_SPI_FRQ_DIV_2 && spi_freq_val >= THC_SPI_FREQUENCY_50M) || + (freq_div == THC_SPI_FRQ_DIV_3 && spi_freq_val < THC_SPI_FREQUENCY_41M) || + (freq_div == THC_SPI_FRQ_DIV_1 && spi_freq_val < THC_SPI_FREQUENCY_15M)) { + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCRF_HALF_DIV_EN, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCRF_HALF_DIV_EN); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPARE_REG_OFFSET, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE); + } else { + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCRF_HALF_DIV_EN, 0); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPARE_REG_OFFSET, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE, 0); + } + cfg = FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TCRF, freq_div) | FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TRMODE, io_mode) | (is_low_freq ? THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN : 0) | @@ -1243,6 +1271,25 @@ int thc_spi_write_config(struct thc_device *dev, u32 spi_freq_val, if (spi_freq_val < THC_SPI_LOW_FREQUENCY) is_low_freq = true; + /* 10M, 35M and 50M CLK need 1.5, 3.5 and 2.5 half divider */ + if ((freq_div == THC_SPI_FRQ_DIV_2 && spi_freq_val >= THC_SPI_FREQUENCY_50M) || + (freq_div == THC_SPI_FRQ_DIV_3 && spi_freq_val < THC_SPI_FREQUENCY_41M) || + (freq_div == THC_SPI_FRQ_DIV_1 && spi_freq_val < THC_SPI_FREQUENCY_15M)) { + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCWF_HALF_DIV_EN, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCWF_HALF_DIV_EN); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPARE_REG_OFFSET, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE); + } else { + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCWF_HALF_DIV_EN, 0); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPARE_REG_OFFSET, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE, 0); + } + cfg = FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TCWF, freq_div) | FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TWMODE, io_mode) | (is_low_freq ? THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN : 0) | diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h index 413730f8e3f7..c6d026686b7a 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h @@ -643,6 +643,10 @@ #define THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_VAL GENMASK(3, 0) #define THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_EN BIT(25) +#define THC_M_PRT_SPI_DUTYC_CFG_SPI_TCRF_HALF_DIV_EN BIT(30) +#define THC_M_PRT_SPI_DUTYC_CFG_SPI_TCWF_HALF_DIV_EN BIT(31) + +#define THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE BIT(2) /* CS Assertion delay default value */ #define THC_CSA_CK_DELAY_VAL_DEFAULT 4 From b287554ea87c665355401d68978a07601de918f7 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:15 +0200 Subject: [PATCH 230/295] Input: add keycodes for contextual AI usages (HUTRR119) JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.0 commit 45065a5095c7773fb98c35d60c20c3b513540597 Author: Akshai Murari Date: Fri Mar 27 06:54:45 2026 +0000 Input: add keycodes for contextual AI usages (HUTRR119) HUTRR119 introduces new usages for keys intended to invoke AI agents based on the current context. These are useful with the increasing number of operating systems with integrated Large Language Models Add new key definitions for KEY_ACTION_ON_SELECTION, KEY_CONTEXTUAL_INSERT and KEY_CONTEXTUAL_QUERY Signed-off-by: Akshai Murari Acked-by: Dmitry Torokhov Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-debug.c | 6 ++++++ drivers/hid/hid-input.c | 3 +++ include/uapi/linux/input-event-codes.h | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index 7dda0464758a..6fb76c0c389d 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -990,6 +990,9 @@ static const struct hid_usage_entry hid_usage_table[] = { { 0x0c, 0x01c9, "ALContactSync" }, { 0x0c, 0x01ca, "ALNavigation" }, { 0x0c, 0x01cb, "ALContextawareDesktopAssistant" }, + { 0x0c, 0x01cc, "ALActionOnSelection" }, + { 0x0c, 0x01cd, "ALContextualInsertion" }, + { 0x0c, 0x01ce, "ALContextualQuery" }, { 0x0c, 0x0200, "GenericGUIApplicationControls" }, { 0x0c, 0x0201, "ACNew" }, { 0x0c, 0x0202, "ACOpen" }, @@ -3375,6 +3378,9 @@ static const char *keys[KEY_MAX + 1] = { [KEY_BRIGHTNESS_MIN] = "BrightnessMin", [KEY_BRIGHTNESS_MAX] = "BrightnessMax", [KEY_BRIGHTNESS_AUTO] = "BrightnessAuto", + [KEY_ACTION_ON_SELECTION] = "ActionOnSelection", + [KEY_CONTEXTUAL_INSERT] = "ContextualInsert", + [KEY_CONTEXTUAL_QUERY] = "ContextualQuery", [KEY_KBDINPUTASSIST_PREV] = "KbdInputAssistPrev", [KEY_KBDINPUTASSIST_NEXT] = "KbdInputAssistNext", [KEY_KBDINPUTASSIST_PREVGROUP] = "KbdInputAssistPrevGroup", diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 8fff185fe0e6..d73cfa2e73d3 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -1235,6 +1235,9 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel case 0x1bc: map_key_clear(KEY_MESSENGER); break; case 0x1bd: map_key_clear(KEY_INFO); break; case 0x1cb: map_key_clear(KEY_ASSISTANT); break; + case 0x1cc: map_key_clear(KEY_ACTION_ON_SELECTION); break; + case 0x1cd: map_key_clear(KEY_CONTEXTUAL_INSERT); break; + case 0x1ce: map_key_clear(KEY_CONTEXTUAL_QUERY); break; case 0x201: map_key_clear(KEY_NEW); break; case 0x202: map_key_clear(KEY_OPEN); break; case 0x203: map_key_clear(KEY_CLOSE); break; diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index 4bdb6a165987..3528168f7c6d 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -643,6 +643,10 @@ #define KEY_EPRIVACY_SCREEN_ON 0x252 #define KEY_EPRIVACY_SCREEN_OFF 0x253 +#define KEY_ACTION_ON_SELECTION 0x254 /* AL Action on Selection (HUTRR119) */ +#define KEY_CONTEXTUAL_INSERT 0x255 /* AL Contextual Insertion (HUTRR119) */ +#define KEY_CONTEXTUAL_QUERY 0x256 /* AL Contextual Query (HUTRR119) */ + #define KEY_KBDINPUTASSIST_PREV 0x260 #define KEY_KBDINPUTASSIST_NEXT 0x261 #define KEY_KBDINPUTASSIST_PREVGROUP 0x262 From c265286ecbfaf6108fbe4805b55331f79640aaaf Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:48 +0200 Subject: [PATCH 231/295] HID: bpf: fix some signed vs unsigned compiler warnings JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit e43d74d66b5efad97e62f34cd1ff250add969586 Author: Peter Hutterer Date: Fri Apr 3 18:12:19 2026 +0200 HID: bpf: fix some signed vs unsigned compiler warnings On udev-hid-bpf, we are now getting warnings here, shut them off. Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/227 Signed-off-by: Peter Hutterer Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c | 3 ++- drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c | 2 +- drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c | 2 +- drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c b/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c index ec360d71130f..c562c2d684fe 100644 --- a/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c +++ b/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c @@ -462,7 +462,8 @@ int BPF_PROG(k20_fix_events, struct hid_bpf_ctx *hctx) __u32 buttons; __u8 wheel; } __attribute__((packed)) *pad_report; - int i, b; + int i; + size_t b; __u8 modifiers = data[1]; __u32 buttons = 0; diff --git a/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c b/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c index 82f1950445dd..253b96458c58 100644 --- a/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c +++ b/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c @@ -34,7 +34,7 @@ int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx) if (data[3] != 0x06) return 0; - for (int idx = 0; idx < ARRAY_SIZE(offsets); idx++) { + for (size_t idx = 0; idx < ARRAY_SIZE(offsets); idx++) { u8 offset = offsets[idx]; /* if Input (Cnst,Var,Abs) , make it Input (Data,Var,Abs) */ diff --git a/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c b/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c index 2da680bc4e11..ed60a660cc1a 100644 --- a/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c +++ b/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c @@ -148,7 +148,7 @@ int probe(struct hid_bpf_probe_args *ctx) { struct hid_bpf_ctx *hid_ctx; __u16 pid; - int i; + size_t i; /* get a struct hid_device to access the actual pid of the device */ hid_ctx = hid_bpf_allocate_context(ctx->hid); diff --git a/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c b/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c index 46d5c459d0c9..ac07216f5b67 100644 --- a/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c +++ b/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c @@ -173,7 +173,7 @@ int BPF_PROG(hid_device_event_xppen_deco_mini_4, struct hid_bpf_ctx *hctx) { __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 8 /* size */); __u8 button_mask = 0; - int d, b; + size_t d, b; if (!data) return 0; /* EPERM check */ From b5ec3a452cbe92412e3587425e34880c4c477cb6 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:49 +0200 Subject: [PATCH 232/295] HID: bpf: hid_bpf_helpers: provide a cleanup functions JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit ed1d6d21c961df2241d0434963c6885036172718 Author: Benjamin Tissoires Date: Fri Apr 3 18:12:20 2026 +0200 HID: bpf: hid_bpf_helpers: provide a cleanup functions Combination of 2 udev-hid-bpf commits: bpf: hid_bpf_helpers: provide a cleanup function for hid_bpf_release_context bpf: helpers: add guard(bpf_spin) macro Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/221 Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/progs/hid_bpf_async.h | 36 +++++------ drivers/hid/bpf/progs/hid_bpf_helpers.h | 80 +++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 18 deletions(-) diff --git a/drivers/hid/bpf/progs/hid_bpf_async.h b/drivers/hid/bpf/progs/hid_bpf_async.h index 9ab585434239..877bb7e81f03 100644 --- a/drivers/hid/bpf/progs/hid_bpf_async.h +++ b/drivers/hid/bpf/progs/hid_bpf_async.h @@ -116,15 +116,14 @@ static int hid_bpf_async_find_empty_key(void) if (!elem) return -ENOMEM; /* should never happen */ - bpf_spin_lock(&elem->lock); + { + guard(bpf_spin)(&elem->lock); - if (elem->state == HID_BPF_ASYNC_STATE_UNSET) { - elem->state = HID_BPF_ASYNC_STATE_INITIALIZING; - bpf_spin_unlock(&elem->lock); - return i; + if (elem->state == HID_BPF_ASYNC_STATE_UNSET) { + elem->state = HID_BPF_ASYNC_STATE_INITIALIZING; + return i; + } } - - bpf_spin_unlock(&elem->lock); } return -EINVAL; @@ -175,18 +174,19 @@ static int hid_bpf_async_delayed_call(struct hid_bpf_ctx *hctx, u64 milliseconds if (!elem) return -EINVAL; - bpf_spin_lock(&elem->lock); - /* The wq must be: - * - HID_BPF_ASYNC_STATE_INITIALIZED -> it's been initialized and ready to be called - * - HID_BPF_ASYNC_STATE_RUNNING -> possible re-entry from the wq itself - */ - if (elem->state != HID_BPF_ASYNC_STATE_INITIALIZED && - elem->state != HID_BPF_ASYNC_STATE_RUNNING) { - bpf_spin_unlock(&elem->lock); - return -EINVAL; + { + guard(bpf_spin)(&elem->lock); + + /* The wq must be: + * - HID_BPF_ASYNC_STATE_INITIALIZED -> it's been initialized and ready to be called + * - HID_BPF_ASYNC_STATE_RUNNING -> possible re-entry from the wq itself + */ + if (elem->state != HID_BPF_ASYNC_STATE_INITIALIZED && + elem->state != HID_BPF_ASYNC_STATE_RUNNING) + return -EINVAL; + + elem->state = HID_BPF_ASYNC_STATE_STARTING; } - elem->state = HID_BPF_ASYNC_STATE_STARTING; - bpf_spin_unlock(&elem->lock); elem->hid = hctx->hid->id; diff --git a/drivers/hid/bpf/progs/hid_bpf_helpers.h b/drivers/hid/bpf/progs/hid_bpf_helpers.h index bf19785a6b06..829024dc2004 100644 --- a/drivers/hid/bpf/progs/hid_bpf_helpers.h +++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h @@ -42,6 +42,86 @@ extern int bpf_wq_set_callback_impl(struct bpf_wq *wq, #define HID_MAX_DESCRIPTOR_SIZE 4096 #define HID_IGNORE_EVENT -1 +/** + * Use: _cleanup_(somefunction) struct foo *bar; + */ +#define _cleanup_(_x) __attribute__((cleanup(_x))) + +/** + * Use: _release_(foo) *bar; + * + * This requires foo_releasep() to be present, use DEFINE_RELEASE_CLEANUP_FUNC. + */ +#define _release_(_type) struct _type __attribute__((cleanup(_type##_releasep))) + +/** + * Define a cleanup function for the struct type foo with a matching + * foo_release(). Use: + * DEFINE_RELEASE_CLEANUP_FUNC(foo) + * _unref_(foo) struct foo *bar; + */ +#define DEFINE_RELEASE_CLEANUP_FUNC(_type) \ + static inline void _type##_releasep(struct _type **_p) { \ + if (*_p) \ + _type##_release(*_p); \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +/* for being able to have a cleanup function */ +#define hid_bpf_ctx_release hid_bpf_release_context +DEFINE_RELEASE_CLEANUP_FUNC(hid_bpf_ctx); + +/* + * Kernel-style guard macros adapted for BPF + * Based on include/linux/cleanup.h from the Linux kernel + * + * These provide automatic lock/unlock using __attribute__((cleanup)) + * similar to how _release_() works for contexts. + */ + +/** + * DEFINE_GUARD(name, type, lock, unlock): + * Define a guard for automatic lock/unlock using the same pattern as _release_() + * @name: identifier for the guard (e.g., bpf_spin) + * @type: lock variable type (e.g., struct bpf_spin_lock) + * @lock: lock function name (e.g., bpf_spin_lock) + * @unlock: unlock function name (e.g., bpf_spin_unlock) + * + * guard(name): + * Declare and lock in one statement - lock held until end of scope + * + * Example: + * DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock) + * + * void foo(struct bpf_spin_lock *lock) { + * guard(bpf_spin)(lock); + * // lock held until end of scope + * } + */ + +/* Guard helper struct - stores lock pointer for cleanup */ +#define DEFINE_GUARD(_name, _type, _lock, _unlock) \ +struct _name##_guard { \ + _type *lock; \ +}; \ +static inline void _name##_guard_cleanup(struct _name##_guard *g) { \ + if (g && g->lock) \ + _unlock(g->lock); \ +} \ +static inline struct _name##_guard _name##_guard_init(_type *l) { \ + if (l) \ + _lock(l); \ + return (struct _name##_guard){.lock = l}; \ +} \ +struct __useless_struct_to_allow_trailing_semicolon__ + +#define guard(_name) \ + struct _name##_guard COMBINE(guard, __LINE__) __attribute__((cleanup(_name##_guard_cleanup))) = \ + _name##_guard_init + +/* Define BPF spinlock guard */ +DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock); + /* extracted from */ #define BUS_ANY 0x00 #define BUS_PCI 0x01 From 9f39c645c3eab6c815891e9d7890f2792dce44cb Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:49 +0200 Subject: [PATCH 233/295] HID: bpf: add helper macros for LE/BE conversion JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 462240acc5c84a1462fd66f0e0e1ecbbb3613ddb Author: Peter Hutterer Date: Fri Apr 3 18:12:21 2026 +0200 HID: bpf: add helper macros for LE/BE conversion BPF has bpf_htons and friends but those only work with data in Big Endian format. HID is little endian so we need our own macros. Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/221 Signed-off-by: Peter Hutterer Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/progs/hid_bpf_helpers.h | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/drivers/hid/bpf/progs/hid_bpf_helpers.h b/drivers/hid/bpf/progs/hid_bpf_helpers.h index 829024dc2004..51cd4e210850 100644 --- a/drivers/hid/bpf/progs/hid_bpf_helpers.h +++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h @@ -7,6 +7,7 @@ #include "vmlinux.h" #include +#include #include extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx, @@ -265,4 +266,70 @@ DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock); _EXPAND(_ARG, __VA_ARGS__) \ } _device_ids SEC(".hid_bpf_config") + +/* Equivalency macros for bpf_htons and friends which are + * Big Endian only - HID needs little endian so these are the + * corresponding macros for that. See bpf/bpf_endian.h + */ +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define __hid_bpf_le16_to_cpu(x) (x) +# define __hid_bpf_le32_to_cpu(x) (x) +# define __hid_bpf_le64_to_cpu(x) (x) +# define __hid_bpf_cpu_to_le16(x) (x) +# define __hid_bpf_cpu_to_le32(x) (x) +# define __hid_bpf_cpu_to_le64(x) (x) +# define __hid_bpf_constant_le16_to_cpu(x) (x) +# define __hid_bpf_constant_le32_to_cpu(x) (x) +# define __hid_bpf_constant_le64_to_cpu(x) (x) +# define __hid_bpf_constant_cpu_to_le16(x) (x) +# define __hid_bpf_constant_cpu_to_le32(x) (x) +# define __hid_bpf_constant_cpu_to_le64(x) (x) +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define __hid_bpf_le16_to_cpu(x) __builtin_bswap16(x) +# define __hid_bpf_le32_to_cpu(x) __builtin_bswap32(x) +# define __hid_bpf_le64_to_cpu(x) __builtin_bswap64(x) +# define __hid_bpf_cpu_to_le16(x) __builtin_bswap16(x) +# define __hid_bpf_cpu_to_le32(x) __builtin_bswap32(x) +# define __hid_bpf_cpu_to_le64(x) __builtin_bswap64(x) +# define __hid_bpf_constant_le16_to_cpu(x) __bpf_swab16(x) +# define __hid_bpf_constant_le32_to_cpu(x) __bpf_swab32(x) +# define __hid_bpf_constant_le64_to_cpu(x) __bpf_swab64(x) +# define __hid_bpf_constant_cpu_to_le16(x) __bpf_swab16(x) +# define __hid_bpf_constant_cpu_to_le32(x) __bpf_swab32(x) +# define __hid_bpf_constant_cpu_to_le64(x) __bpf_swab64(x) +#else +# error "Invalid __BYTE_ORDER__" +#endif + +#define hid_bpf_le16_to_cpu(x) \ + (__builtin_constant_p(x) ? \ + __hid_bpf_constant_le16_to_cpu(x) : __hid_bpf_le16_to_cpu(x)) + +#define hid_bpf_le32_to_cpu(x) \ + (__builtin_constant_p(x) ? \ + __hid_bpf_constant_le32_to_cpu(x) : __hid_bpf_le32_to_cpu(x)) + +#define hid_bpf_le64_to_cpu(x) \ + (__builtin_constant_p(x) ? \ + __hid_bpf_constant_le64_to_cpu(x) : __hid_bpf_le64_to_cpu(x)) + +#define hid_bpf_cpu_to_le16(x) \ + (__builtin_constant_p(x) ? \ + __hid_bpf_constant_cpu_to_le16(x) : __hid_bpf_cpu_to_le16(x)) + +#define hid_bpf_cpu_to_le32(x) \ + (__builtin_constant_p(x) ? \ + __hid_bpf_constant_cpu_to_le32(x) : __hid_bpf_cpu_to_le32(x)) + +#define hid_bpf_cpu_to_le64(x) \ + (__builtin_constant_p(x) ? \ + __hid_bpf_constant_cpu_to_le64(x) : __hid_bpf_cpu_to_le64(x)) + +#define hid_bpf_be16_to_cpu(x) bpf_ntohs(x) +#define hid_bpf_be32_to_cpu(x) bpf_ntohl(x) +#define hid_bpf_be64_to_cpu(x) bpf_be64_to_cpu(x) +#define hid_bpf_cpu_to_be16(x) bpf_htons(x) +#define hid_bpf_cpu_to_be32(x) bpf_htonl(x) +#define hid_bpf_cpu_to_be64(x) bpf_cpu_to_be64(x) + #endif /* __HID_BPF_HELPERS_H */ From 6dd6a35f33020a1be10cfcadd5597d7ce21c249d Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:50 +0200 Subject: [PATCH 234/295] HID: bpf: handle injected report descriptor in HID-BPF JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit fc778f21a18e79cfb1ad17b7af0478e21ab41daf Author: Benjamin Tissoires Date: Fri Apr 3 18:12:22 2026 +0200 HID: bpf: handle injected report descriptor in HID-BPF udev-hid-bpf is now capable of injecting the parsed report descriptor in the program. Provide the macros required for it. Sync up from udev-hid-bpf commits: bpf: inject the parsed report descriptor in HID_REPORT_DESCRIPTOR hid_bpf_helpers: provide iterator macros for walking the HID report descriptor hid_bpf_helpers: Add extract_bits function bpf: add hid_usages.h bpf: move the report descriptor structs into their own header Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/221 Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/228 Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/progs/hid_bpf_helpers.h | 136 + .../bpf/progs/hid_report_descriptor_helpers.h | 80 + drivers/hid/bpf/progs/hid_usages.h | 2810 +++++++++++++++++ 3 files changed, 3026 insertions(+) create mode 100644 drivers/hid/bpf/progs/hid_report_descriptor_helpers.h create mode 100644 drivers/hid/bpf/progs/hid_usages.h diff --git a/drivers/hid/bpf/progs/hid_bpf_helpers.h b/drivers/hid/bpf/progs/hid_bpf_helpers.h index 51cd4e210850..298c2bb69b64 100644 --- a/drivers/hid/bpf/progs/hid_bpf_helpers.h +++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h @@ -9,6 +9,16 @@ #include #include #include +#include "hid_report_descriptor_helpers.h" + +/* Compiler attributes */ +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +#ifndef __maybe_unused +#define __maybe_unused __attribute__((__unused__)) +#endif extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, @@ -332,4 +342,130 @@ DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock); #define hid_bpf_cpu_to_be32(x) bpf_htonl(x) #define hid_bpf_cpu_to_be64(x) bpf_cpu_to_be64(x) +static inline __maybe_unused __u16 field_start_byte(struct hid_rdesc_field *field) +{ + return field->bits_start / 8; +} + +static inline __maybe_unused __u16 field_end_byte(struct hid_rdesc_field *field) +{ + if (!field->bits_end) + return 0; + + return (__u16)(field->bits_end - 1) / 8; +} + +static __maybe_unused __u32 extract_bits(__u8 *buffer, const size_t size, struct hid_rdesc_field *field) +{ + __s32 nbits = field->bits_end - field->bits_start; + __u32 start = field_start_byte(field); + __u32 end = field_end_byte(field); + __u8 base_shift = field->bits_start % 8; + + if (nbits <= 0 || nbits > 32 || start >= size || end >= size) + return 0; + + /* Fast path for byte-aligned standard-sized reads */ + if (base_shift == 0) { + /* 8-bit aligned read */ + if (nbits == 8 && start < size) + return buffer[start]; + + /* 16-bit aligned read - use separate variables for verifier */ + if (nbits == 16) { + __u32 off0 = start; + __u32 off1 = start + 1; + + if (off0 < size && off1 < size) { + return buffer[off0] | + ((__u32)buffer[off1] << 8); + } + } + + /* 32-bit aligned read - use separate variables for verifier */ + if (nbits == 32) { + __u32 off0 = start; + __u32 off1 = start + 1; + __u32 off2 = start + 2; + __u32 off3 = start + 3; + + if (off0 < size && off1 < size && + off2 < size && off3 < size) { + return buffer[off0] | + ((__u32)buffer[off1] << 8) | + ((__u32)buffer[off2] << 16) | + ((__u32)buffer[off3] << 24); + } + } + } + + /* General case: bit manipulation for unaligned or non-standard sizes */ + int mask = 0xffffffff >> (32 - nbits); + __u64 value = 0; + __u32 i; + + bpf_for (i, start, end + 1) { + value |= (__u64)buffer[i] << ((i - start) * 8); + } + + return (value >> base_shift) & mask; +} + +#define EXTRACT_BITS(buffer, field) extract_bits(buffer, sizeof(buffer), field) + +/* Base macro for iterating over HID arrays with bounds checking. + * Follows the bpf_for pattern from libbpf. + */ +#define __hid_bpf_for_each_array(array, num_elements, max_elements, var) \ + for ( \ + /* initialize and define destructor */ \ + struct bpf_iter_num ___it __attribute__((aligned(8), \ + cleanup(bpf_iter_num_destroy))), \ + /* ___p pointer is necessary to call bpf_iter_num_new() *once* */ \ + *___p __attribute__((unused)) = ( \ + /* always initialize iterator; if bounds fail, iterate 0 times */ \ + bpf_iter_num_new(&___it, 0, \ + (num_elements) > (max_elements) ? \ + 0 : (num_elements)), \ + /* workaround for Clang bug */ \ + (void)bpf_iter_num_destroy, (void *)0); \ + ({ \ + /* iteration step */ \ + int *___t = bpf_iter_num_next(&___it); \ + int ___i; \ + /* termination and bounds check, assign var */ \ + (___t && (___i = *___t, ___i >= 0 && ___i < (num_elements)) && \ + ((num_elements) <= (max_elements)) && \ + (var = &(array)[___i], 1)); \ + }); \ + ) + +/* Iterate over input reports in a descriptor */ +#define hid_bpf_for_each_input_report(descriptor, report_var) \ + __hid_bpf_for_each_array((descriptor)->input_reports, \ + (descriptor)->num_input_reports, \ + HID_MAX_REPORTS, report_var) + +/* Iterate over feature reports in a descriptor */ +#define hid_bpf_for_each_feature_report(descriptor, report_var) \ + __hid_bpf_for_each_array((descriptor)->feature_reports, \ + (descriptor)->num_feature_reports, \ + HID_MAX_REPORTS, report_var) + +/* Iterate over output reports in a descriptor */ +#define hid_bpf_for_each_output_report(descriptor, report_var) \ + __hid_bpf_for_each_array((descriptor)->output_reports, \ + (descriptor)->num_output_reports, \ + HID_MAX_REPORTS, report_var) + +/* Iterate over fields in a report */ +#define hid_bpf_for_each_field(report, field_var) \ + __hid_bpf_for_each_array((report)->fields, (report)->num_fields, \ + HID_MAX_FIELDS, field_var) + +/* Iterate over collections in a field */ +#define hid_bpf_for_each_collection(field, collection_var) \ + __hid_bpf_for_each_array((field)->collections, (field)->num_collections, \ + HID_MAX_COLLECTIONS, collection_var) + #endif /* __HID_BPF_HELPERS_H */ diff --git a/drivers/hid/bpf/progs/hid_report_descriptor_helpers.h b/drivers/hid/bpf/progs/hid_report_descriptor_helpers.h new file mode 100644 index 000000000000..2aed5c0a6ad4 --- /dev/null +++ b/drivers/hid/bpf/progs/hid_report_descriptor_helpers.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2022 Benjamin Tissoires + */ + +#ifndef __HID_REPORT_DESCRIPTOR_HELPERS_H +#define __HID_REPORT_DESCRIPTOR_HELPERS_H + +#include "vmlinux.h" + +/* Compiler attributes */ +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +#ifndef __maybe_unused +#define __maybe_unused __attribute__((__unused__)) +#endif + +/* Report Descriptor Structures */ +#define HID_MAX_COLLECTIONS 32 +#define HID_MAX_FIELDS 64 +#define HID_MAX_REPORTS 16 + +enum hid_rdesc_field_type { + HID_FIELD_VARIABLE = 0, + HID_FIELD_ARRAY = 1, + HID_FIELD_CONSTANT = 2, +}; + +struct hid_rdesc_collection { + __u16 usage_page; + __u16 usage_id; + __u8 collection_type; +} __packed; + +struct hid_rdesc_field { + __u8 field_type; /* enum hid_rdesc_field_type */ + __u8 num_collections; + __u16 bits_start; + __u16 bits_end; + __u16 usage_page; + union { + __u16 usage_id; /* For Variable fields */ + struct __packed { /* For Array fields */ + __u16 usage_minimum; + __u16 usage_maximum; + }; + }; + __s32 logical_minimum; + __s32 logical_maximum; + struct { + __u8 is_relative:1; /* Data is relative to previous value */ + __u8 wraps:1; /* Value wraps around (e.g., rotary encoder) */ + __u8 is_nonlinear:1; /* Non-linear relationship between logical/physical */ + __u8 has_no_preferred_state:1; /* No rest position (e.g., free-floating joystick) */ + __u8 has_null_state:1; /* Can report null/no-data values */ + __u8 is_volatile:1; /* Volatile (Output/Feature) - NOT POPULATED, always 0 */ + __u8 is_buffered_bytes:1; /* Fixed-size byte stream vs bitfield */ + __u8 reserved:1; /* Reserved for future use */ + } flags; + struct hid_rdesc_collection collections[HID_MAX_COLLECTIONS]; +} __packed; + +struct hid_rdesc_report { + __u8 report_id; /* 0 means no report ID */ + __u16 size_in_bits; + __u8 num_fields; + struct hid_rdesc_field fields[HID_MAX_FIELDS]; +} __packed; + +struct hid_rdesc_descriptor { + __u8 num_input_reports; + __u8 num_output_reports; + __u8 num_feature_reports; + struct hid_rdesc_report input_reports[HID_MAX_REPORTS]; + struct hid_rdesc_report output_reports[HID_MAX_REPORTS]; + struct hid_rdesc_report feature_reports[HID_MAX_REPORTS]; +} __packed; + +#endif /* __HID_REPORT_DESCRIPTOR_HELPERS_H */ diff --git a/drivers/hid/bpf/progs/hid_usages.h b/drivers/hid/bpf/progs/hid_usages.h new file mode 100644 index 000000000000..5469b25c9f79 --- /dev/null +++ b/drivers/hid/bpf/progs/hid_usages.h @@ -0,0 +1,2810 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2025 Red Hat, Inc + */ + +// THIS FILE IS GENERATED, DO NOT EDIT + +#pragma once + +/* ----- Generated Usage Pages and Usages ------ */ +#define HidUsagePage_GenericDesktop 0x01 +#define HidUsagePage_SimulationControls 0x02 +#define HidUsagePage_VRControls 0x03 +#define HidUsagePage_SportControls 0x04 +#define HidUsagePage_GameControls 0x05 +#define HidUsagePage_GenericDeviceControls 0x06 +#define HidUsagePage_KeyboardKeypad 0x07 +#define HidUsagePage_LED 0x08 +#define HidUsagePage_Button 0x09 +#define HidUsagePage_Ordinal 0x0a +#define HidUsagePage_TelephonyDevice 0x0b +#define HidUsagePage_Consumer 0x0c +#define HidUsagePage_Digitizers 0x0d +#define HidUsagePage_Haptics 0x0e +#define HidUsagePage_PhysicalInputDevice 0x0f +#define HidUsagePage_Unicode 0x10 +#define HidUsagePage_SoC 0x11 +#define HidUsagePage_EyeandHeadTrackers 0x12 +#define HidUsagePage_AuxiliaryDisplay 0x14 +#define HidUsagePage_Sensors 0x20 +#define HidUsagePage_MedicalInstrument 0x40 +#define HidUsagePage_BrailleDisplay 0x41 +#define HidUsagePage_LightingAndIllumination 0x59 +#define HidUsagePage_Monitor 0x80 +#define HidUsagePage_MonitorEnumerated 0x81 +#define HidUsagePage_VESAVirtualControls 0x82 +#define HidUsagePage_Power 0x84 +#define HidUsagePage_BatterySystem 0x85 +#define HidUsagePage_BarcodeScanner 0x8c +#define HidUsagePage_Scales 0x8d +#define HidUsagePage_MagneticStripeReader 0x8e +#define HidUsagePage_CameraControl 0x90 +#define HidUsagePage_Arcade 0x91 +#define HidUsagePage_FIDOAlliance 0xf1d0 + +#define HidUsage_GD_Pointer 0x01 +#define HidUsage_GD_Mouse 0x02 +#define HidUsage_GD_Joystick 0x04 +#define HidUsage_GD_Gamepad 0x05 +#define HidUsage_GD_Keyboard 0x06 +#define HidUsage_GD_Keypad 0x07 +#define HidUsage_GD_MultiaxisController 0x08 +#define HidUsage_GD_TabletPCSystemControls 0x09 +#define HidUsage_GD_WaterCoolingDevice 0x0a +#define HidUsage_GD_ComputerChassisDevice 0x0b +#define HidUsage_GD_WirelessRadioControls 0x0c +#define HidUsage_GD_PortableDeviceControl 0x0d +#define HidUsage_GD_SystemMultiAxisController 0x0e +#define HidUsage_GD_SpatialController 0x0f +#define HidUsage_GD_AssistiveControl 0x10 +#define HidUsage_GD_DeviceDock 0x11 +#define HidUsage_GD_DockableDevice 0x12 +#define HidUsage_GD_CallStateManagementControl 0x13 +#define HidUsage_GD_X 0x30 +#define HidUsage_GD_Y 0x31 +#define HidUsage_GD_Z 0x32 +#define HidUsage_GD_Rx 0x33 +#define HidUsage_GD_Ry 0x34 +#define HidUsage_GD_Rz 0x35 +#define HidUsage_GD_Slider 0x36 +#define HidUsage_GD_Dial 0x37 +#define HidUsage_GD_Wheel 0x38 +#define HidUsage_GD_HatSwitch 0x39 +#define HidUsage_GD_CountedBuffer 0x3a +#define HidUsage_GD_ByteCount 0x3b +#define HidUsage_GD_MotionWakeup 0x3c +#define HidUsage_GD_Start 0x3d +#define HidUsage_GD_Select 0x3e +#define HidUsage_GD_Vx 0x40 +#define HidUsage_GD_Vy 0x41 +#define HidUsage_GD_Vz 0x42 +#define HidUsage_GD_Vbrx 0x43 +#define HidUsage_GD_Vbry 0x44 +#define HidUsage_GD_Vbrz 0x45 +#define HidUsage_GD_Vno 0x46 +#define HidUsage_GD_FeatureNotification 0x47 +#define HidUsage_GD_ResolutionMultiplier 0x48 +#define HidUsage_GD_Qx 0x49 +#define HidUsage_GD_Qy 0x4a +#define HidUsage_GD_Qz 0x4b +#define HidUsage_GD_Qw 0x4c +#define HidUsage_GD_SystemControl 0x80 +#define HidUsage_GD_SystemPowerDown 0x81 +#define HidUsage_GD_SystemSleep 0x82 +#define HidUsage_GD_SystemWakeUp 0x83 +#define HidUsage_GD_SystemContextMenu 0x84 +#define HidUsage_GD_SystemMainMenu 0x85 +#define HidUsage_GD_SystemAppMenu 0x86 +#define HidUsage_GD_SystemMenuHelp 0x87 +#define HidUsage_GD_SystemMenuExit 0x88 +#define HidUsage_GD_SystemMenuSelect 0x89 +#define HidUsage_GD_SystemMenuRight 0x8a +#define HidUsage_GD_SystemMenuLeft 0x8b +#define HidUsage_GD_SystemMenuUp 0x8c +#define HidUsage_GD_SystemMenuDown 0x8d +#define HidUsage_GD_SystemColdRestart 0x8e +#define HidUsage_GD_SystemWarmRestart 0x8f +#define HidUsage_GD_DpadUp 0x90 +#define HidUsage_GD_DpadDown 0x91 +#define HidUsage_GD_DpadRight 0x92 +#define HidUsage_GD_DpadLeft 0x93 +#define HidUsage_GD_IndexTrigger 0x94 +#define HidUsage_GD_PalmTrigger 0x95 +#define HidUsage_GD_Thumbstick 0x96 +#define HidUsage_GD_SystemFunctionShift 0x97 +#define HidUsage_GD_SystemFunctionShiftLock 0x98 +#define HidUsage_GD_SystemFunctionShiftLockIndicator 0x99 +#define HidUsage_GD_SystemDismissNotification 0x9a +#define HidUsage_GD_SystemDoNotDisturb 0x9b +#define HidUsage_GD_SystemDock 0xa0 +#define HidUsage_GD_SystemUndock 0xa1 +#define HidUsage_GD_SystemSetup 0xa2 +#define HidUsage_GD_SystemBreak 0xa3 +#define HidUsage_GD_SystemDebuggerBreak 0xa4 +#define HidUsage_GD_ApplicationBreak 0xa5 +#define HidUsage_GD_ApplicationDebuggerBreak 0xa6 +#define HidUsage_GD_SystemSpeakerMute 0xa7 +#define HidUsage_GD_SystemHibernate 0xa8 +#define HidUsage_GD_SystemMicrophoneMute 0xa9 +#define HidUsage_GD_SystemAccessibilityBinding 0xaa +#define HidUsage_GD_SystemDisplayInvert 0xb0 +#define HidUsage_GD_SystemDisplayInternal 0xb1 +#define HidUsage_GD_SystemDisplayExternal 0xb2 +#define HidUsage_GD_SystemDisplayBoth 0xb3 +#define HidUsage_GD_SystemDisplayDual 0xb4 +#define HidUsage_GD_SystemDisplayToggleIntExtMode 0xb5 +#define HidUsage_GD_SystemDisplaySwapPrimarySecondary 0xb6 +#define HidUsage_GD_SystemDisplayToggleLCDAutoscale 0xb7 +#define HidUsage_GD_SensorZone 0xc0 +#define HidUsage_GD_RPM 0xc1 +#define HidUsage_GD_CoolantLevel 0xc2 +#define HidUsage_GD_CoolantCriticalLevel 0xc3 +#define HidUsage_GD_CoolantPump 0xc4 +#define HidUsage_GD_ChassisEnclosure 0xc5 +#define HidUsage_GD_WirelessRadioButton 0xc6 +#define HidUsage_GD_WirelessRadioLED 0xc7 +#define HidUsage_GD_WirelessRadioSliderSwitch 0xc8 +#define HidUsage_GD_SystemDisplayRotationLockButton 0xc9 +#define HidUsage_GD_SystemDisplayRotationLockSliderSwitch 0xca +#define HidUsage_GD_ControlEnable 0xcb +#define HidUsage_GD_DockableDeviceUniqueID 0xd0 +#define HidUsage_GD_DockableDeviceVendorID 0xd1 +#define HidUsage_GD_DockableDevicePrimaryUsagePage 0xd2 +#define HidUsage_GD_DockableDevicePrimaryUsageID 0xd3 +#define HidUsage_GD_DockableDeviceDockingState 0xd4 +#define HidUsage_GD_DockableDeviceDisplayOcclusion 0xd5 +#define HidUsage_GD_DockableDeviceObjectType 0xd6 +#define HidUsage_GD_CallActiveLED 0xe0 +#define HidUsage_GD_CallMuteToggle 0xe1 +#define HidUsage_GD_CallMuteLED 0xe2 +#define HidUsage_SC_FlightSimulationDevice 0x01 +#define HidUsage_SC_AutomobileSimulationDevice 0x02 +#define HidUsage_SC_TankSimulationDevice 0x03 +#define HidUsage_SC_SpaceshipSimulationDevice 0x04 +#define HidUsage_SC_SubmarineSimulationDevice 0x05 +#define HidUsage_SC_SailingSimulationDevice 0x06 +#define HidUsage_SC_MotorcycleSimulationDevice 0x07 +#define HidUsage_SC_SportsSimulationDevice 0x08 +#define HidUsage_SC_AirplaneSimulationDevice 0x09 +#define HidUsage_SC_HelicopterSimulationDevice 0x0a +#define HidUsage_SC_MagicCarpetSimulationDevice 0x0b +#define HidUsage_SC_BicycleSimulationDevice 0x0c +#define HidUsage_SC_FlightControlStick 0x20 +#define HidUsage_SC_FlightStick 0x21 +#define HidUsage_SC_CyclicControl 0x22 +#define HidUsage_SC_CyclicTrim 0x23 +#define HidUsage_SC_FlightYoke 0x24 +#define HidUsage_SC_TrackControl 0x25 +#define HidUsage_SC_Aileron 0xb0 +#define HidUsage_SC_AileronTrim 0xb1 +#define HidUsage_SC_AntiTorqueControl 0xb2 +#define HidUsage_SC_AutopilotEnable 0xb3 +#define HidUsage_SC_ChaffRelease 0xb4 +#define HidUsage_SC_CollectiveControl 0xb5 +#define HidUsage_SC_DiveBrake 0xb6 +#define HidUsage_SC_ElectronicCountermeasures 0xb7 +#define HidUsage_SC_Elevator 0xb8 +#define HidUsage_SC_ElevatorTrim 0xb9 +#define HidUsage_SC_Rudder 0xba +#define HidUsage_SC_Throttle 0xbb +#define HidUsage_SC_FlightCommunications 0xbc +#define HidUsage_SC_FlareRelease 0xbd +#define HidUsage_SC_LandingGear 0xbe +#define HidUsage_SC_ToeBrake 0xbf +#define HidUsage_SC_Trigger 0xc0 +#define HidUsage_SC_WeaponsArm 0xc1 +#define HidUsage_SC_WeaponsSelect 0xc2 +#define HidUsage_SC_WingFlaps 0xc3 +#define HidUsage_SC_Accelerator 0xc4 +#define HidUsage_SC_Brake 0xc5 +#define HidUsage_SC_Clutch 0xc6 +#define HidUsage_SC_Shifter 0xc7 +#define HidUsage_SC_Steering 0xc8 +#define HidUsage_SC_TurretDirection 0xc9 +#define HidUsage_SC_BarrelElevation 0xca +#define HidUsage_SC_DivePlane 0xcb +#define HidUsage_SC_Ballast 0xcc +#define HidUsage_SC_BicycleCrank 0xcd +#define HidUsage_SC_HandleBars 0xce +#define HidUsage_SC_FrontBrake 0xcf +#define HidUsage_SC_RearBrake 0xd0 +#define HidUsage_VRC_Belt 0x01 +#define HidUsage_VRC_BodySuit 0x02 +#define HidUsage_VRC_Flexor 0x03 +#define HidUsage_VRC_Glove 0x04 +#define HidUsage_VRC_HeadTracker 0x05 +#define HidUsage_VRC_HeadMountedDisplay 0x06 +#define HidUsage_VRC_HandTracker 0x07 +#define HidUsage_VRC_Oculometer 0x08 +#define HidUsage_VRC_Vest 0x09 +#define HidUsage_VRC_AnimatronicDevice 0x0a +#define HidUsage_VRC_StereoEnable 0x20 +#define HidUsage_VRC_DisplayEnable 0x21 +#define HidUsage_SC_BaseballBat 0x01 +#define HidUsage_SC_GolfClub 0x02 +#define HidUsage_SC_RowingMachine 0x03 +#define HidUsage_SC_Treadmill 0x04 +#define HidUsage_SC_Oar 0x30 +#define HidUsage_SC_Slope 0x31 +#define HidUsage_SC_Rate 0x32 +#define HidUsage_SC_StickSpeed 0x33 +#define HidUsage_SC_StickFaceAngle 0x34 +#define HidUsage_SC_StickHeelToe 0x35 +#define HidUsage_SC_StickFollowThrough 0x36 +#define HidUsage_SC_StickTempo 0x37 +#define HidUsage_SC_StickType 0x38 +#define HidUsage_SC_StickHeight 0x39 +#define HidUsage_SC_Putter 0x50 +#define HidUsage_SC_OneIron 0x51 +#define HidUsage_SC_TwoIron 0x52 +#define HidUsage_SC_ThreeIron 0x53 +#define HidUsage_SC_FourIron 0x54 +#define HidUsage_SC_FiveIron 0x55 +#define HidUsage_SC_SixIron 0x56 +#define HidUsage_SC_SevenIron 0x57 +#define HidUsage_SC_EightIron 0x58 +#define HidUsage_SC_NineIron 0x59 +#define HidUsage_SC_One0Iron 0x5a +#define HidUsage_SC_One1Iron 0x5b +#define HidUsage_SC_SandWedge 0x5c +#define HidUsage_SC_LoftWedge 0x5d +#define HidUsage_SC_PowerWedge 0x5e +#define HidUsage_SC_OneWood 0x5f +#define HidUsage_SC_ThreeWood 0x60 +#define HidUsage_SC_FiveWood 0x61 +#define HidUsage_SC_SevenWood 0x62 +#define HidUsage_SC_NineWood 0x63 +#define HidUsage_GC_ThreeDGameController 0x01 +#define HidUsage_GC_PinballDevice 0x02 +#define HidUsage_GC_GunDevice 0x03 +#define HidUsage_GC_PointofView 0x20 +#define HidUsage_GC_TurnRightLeft 0x21 +#define HidUsage_GC_PitchForwardBackward 0x22 +#define HidUsage_GC_RollRightLeft 0x23 +#define HidUsage_GC_MoveRightLeft 0x24 +#define HidUsage_GC_MoveForwardBackward 0x25 +#define HidUsage_GC_MoveUpDown 0x26 +#define HidUsage_GC_LeanRightLeft 0x27 +#define HidUsage_GC_LeanForwardBackward 0x28 +#define HidUsage_GC_HeightofPOV 0x29 +#define HidUsage_GC_Flipper 0x2a +#define HidUsage_GC_SecondaryFlipper 0x2b +#define HidUsage_GC_Bump 0x2c +#define HidUsage_GC_NewGame 0x2d +#define HidUsage_GC_ShootBall 0x2e +#define HidUsage_GC_Player 0x2f +#define HidUsage_GC_GunBolt 0x30 +#define HidUsage_GC_GunClip 0x31 +#define HidUsage_GC_GunSelector 0x32 +#define HidUsage_GC_GunSingleShot 0x33 +#define HidUsage_GC_GunBurst 0x34 +#define HidUsage_GC_GunAutomatic 0x35 +#define HidUsage_GC_GunSafety 0x36 +#define HidUsage_GC_GamepadFireJump 0x37 +#define HidUsage_GC_GamepadTrigger 0x39 +#define HidUsage_GC_FormfittingGamepad 0x3a +#define HidUsage_GDC_BackgroundNonuserControls 0x01 +#define HidUsage_GDC_BatteryStrength 0x20 +#define HidUsage_GDC_WirelessChannel 0x21 +#define HidUsage_GDC_WirelessID 0x22 +#define HidUsage_GDC_DiscoverWirelessControl 0x23 +#define HidUsage_GDC_SecurityCodeCharacterEntered 0x24 +#define HidUsage_GDC_SecurityCodeCharacterErased 0x25 +#define HidUsage_GDC_SecurityCodeCleared 0x26 +#define HidUsage_GDC_SequenceID 0x27 +#define HidUsage_GDC_SequenceIDReset 0x28 +#define HidUsage_GDC_RFSignalStrength 0x29 +#define HidUsage_GDC_SoftwareVersion 0x2a +#define HidUsage_GDC_ProtocolVersion 0x2b +#define HidUsage_GDC_HardwareVersion 0x2c +#define HidUsage_GDC_Major 0x2d +#define HidUsage_GDC_Minor 0x2e +#define HidUsage_GDC_Revision 0x2f +#define HidUsage_GDC_Handedness 0x30 +#define HidUsage_GDC_EitherHand 0x31 +#define HidUsage_GDC_LeftHand 0x32 +#define HidUsage_GDC_RightHand 0x33 +#define HidUsage_GDC_BothHands 0x34 +#define HidUsage_GDC_GripPoseOffset 0x40 +#define HidUsage_GDC_PointerPoseOffset 0x41 +#define HidUsage_KK_ErrorRollOver 0x01 +#define HidUsage_KK_POSTFail 0x02 +#define HidUsage_KK_ErrorUndefined 0x03 +#define HidUsage_KK_KeyboardA 0x04 +#define HidUsage_KK_KeyboardB 0x05 +#define HidUsage_KK_KeyboardC 0x06 +#define HidUsage_KK_KeyboardD 0x07 +#define HidUsage_KK_KeyboardE 0x08 +#define HidUsage_KK_KeyboardF 0x09 +#define HidUsage_KK_KeyboardG 0x0a +#define HidUsage_KK_KeyboardH 0x0b +#define HidUsage_KK_KeyboardI 0x0c +#define HidUsage_KK_KeyboardJ 0x0d +#define HidUsage_KK_KeyboardK 0x0e +#define HidUsage_KK_KeyboardL 0x0f +#define HidUsage_KK_KeyboardM 0x10 +#define HidUsage_KK_KeyboardN 0x11 +#define HidUsage_KK_KeyboardO 0x12 +#define HidUsage_KK_KeyboardP 0x13 +#define HidUsage_KK_KeyboardQ 0x14 +#define HidUsage_KK_KeyboardR 0x15 +#define HidUsage_KK_KeyboardS 0x16 +#define HidUsage_KK_KeyboardT 0x17 +#define HidUsage_KK_KeyboardU 0x18 +#define HidUsage_KK_KeyboardV 0x19 +#define HidUsage_KK_KeyboardW 0x1a +#define HidUsage_KK_KeyboardX 0x1b +#define HidUsage_KK_KeyboardY 0x1c +#define HidUsage_KK_KeyboardZ 0x1d +#define HidUsage_KK_Keyboard1andBang 0x1e +#define HidUsage_KK_Keyboard2andAt 0x1f +#define HidUsage_KK_Keyboard3andHash 0x20 +#define HidUsage_KK_Keyboard4andDollar 0x21 +#define HidUsage_KK_Keyboard5andPercent 0x22 +#define HidUsage_KK_Keyboard6andCaret 0x23 +#define HidUsage_KK_Keyboard7andAmpersand 0x24 +#define HidUsage_KK_Keyboard8andStar 0x25 +#define HidUsage_KK_Keyboard9andLeftBracket 0x26 +#define HidUsage_KK_Keyboard0andRightBracket 0x27 +#define HidUsage_KK_KeyboardReturnEnter 0x28 +#define HidUsage_KK_KeyboardEscape 0x29 +#define HidUsage_KK_KeyboardDelete 0x2a +#define HidUsage_KK_KeyboardTab 0x2b +#define HidUsage_KK_KeyboardSpacebar 0x2c +#define HidUsage_KK_KeyboardDashandUnderscore 0x2d +#define HidUsage_KK_KeyboardEqualsandPlus 0x2e +#define HidUsage_KK_KeyboardLeftBrace 0x2f +#define HidUsage_KK_KeyboardRightBrace 0x30 +#define HidUsage_KK_KeyboardBackslashandPipe 0x31 +#define HidUsage_KK_KeyboardNonUSHashandTilde 0x32 +#define HidUsage_KK_KeyboardSemiColonandColon 0x33 +#define HidUsage_KK_KeyboardLeftAposandDouble 0x34 +#define HidUsage_KK_KeyboardGraveAccentandTilde 0x35 +#define HidUsage_KK_KeyboardCommaandLessThan 0x36 +#define HidUsage_KK_KeyboardPeriodandGreaterThan 0x37 +#define HidUsage_KK_KeyboardForwardSlashandQuestionMark 0x38 +#define HidUsage_KK_KeyboardCapsLock 0x39 +#define HidUsage_KK_KeyboardF1 0x3a +#define HidUsage_KK_KeyboardF2 0x3b +#define HidUsage_KK_KeyboardF3 0x3c +#define HidUsage_KK_KeyboardF4 0x3d +#define HidUsage_KK_KeyboardF5 0x3e +#define HidUsage_KK_KeyboardF6 0x3f +#define HidUsage_KK_KeyboardF7 0x40 +#define HidUsage_KK_KeyboardF8 0x41 +#define HidUsage_KK_KeyboardF9 0x42 +#define HidUsage_KK_KeyboardF10 0x43 +#define HidUsage_KK_KeyboardF11 0x44 +#define HidUsage_KK_KeyboardF12 0x45 +#define HidUsage_KK_KeyboardPrintScreen 0x46 +#define HidUsage_KK_KeyboardScrollLock 0x47 +#define HidUsage_KK_KeyboardPause 0x48 +#define HidUsage_KK_KeyboardInsert 0x49 +#define HidUsage_KK_KeyboardHome 0x4a +#define HidUsage_KK_KeyboardPageUp 0x4b +#define HidUsage_KK_KeyboardDeleteForward 0x4c +#define HidUsage_KK_KeyboardEnd 0x4d +#define HidUsage_KK_KeyboardPageDown 0x4e +#define HidUsage_KK_KeyboardRightArrow 0x4f +#define HidUsage_KK_KeyboardLeftArrow 0x50 +#define HidUsage_KK_KeyboardDownArrow 0x51 +#define HidUsage_KK_KeyboardUpArrow 0x52 +#define HidUsage_KK_KeypadNumLockandClear 0x53 +#define HidUsage_KK_KeypadForwardSlash 0x54 +#define HidUsage_KK_KeypadStar 0x55 +#define HidUsage_KK_KeypadDash 0x56 +#define HidUsage_KK_KeypadPlus 0x57 +#define HidUsage_KK_KeypadENTER 0x58 +#define HidUsage_KK_Keypad1andEnd 0x59 +#define HidUsage_KK_Keypad2andDownArrow 0x5a +#define HidUsage_KK_Keypad3andPageDn 0x5b +#define HidUsage_KK_Keypad4andLeftArrow 0x5c +#define HidUsage_KK_Keypad5 0x5d +#define HidUsage_KK_Keypad6andRightArrow 0x5e +#define HidUsage_KK_Keypad7andHome 0x5f +#define HidUsage_KK_Keypad8andUpArrow 0x60 +#define HidUsage_KK_Keypad9andPageUp 0x61 +#define HidUsage_KK_Keypad0andInsert 0x62 +#define HidUsage_KK_KeypadPeriodandDelete 0x63 +#define HidUsage_KK_KeyboardNonUSBackslashandPipe 0x64 +#define HidUsage_KK_KeyboardApplication 0x65 +#define HidUsage_KK_KeyboardPower 0x66 +#define HidUsage_KK_KeypadEquals 0x67 +#define HidUsage_KK_KeyboardF13 0x68 +#define HidUsage_KK_KeyboardF14 0x69 +#define HidUsage_KK_KeyboardF15 0x6a +#define HidUsage_KK_KeyboardF16 0x6b +#define HidUsage_KK_KeyboardF17 0x6c +#define HidUsage_KK_KeyboardF18 0x6d +#define HidUsage_KK_KeyboardF19 0x6e +#define HidUsage_KK_KeyboardF20 0x6f +#define HidUsage_KK_KeyboardF21 0x70 +#define HidUsage_KK_KeyboardF22 0x71 +#define HidUsage_KK_KeyboardF23 0x72 +#define HidUsage_KK_KeyboardF24 0x73 +#define HidUsage_KK_KeyboardExecute 0x74 +#define HidUsage_KK_KeyboardHelp 0x75 +#define HidUsage_KK_KeyboardMenu 0x76 +#define HidUsage_KK_KeyboardSelect 0x77 +#define HidUsage_KK_KeyboardStop 0x78 +#define HidUsage_KK_KeyboardAgain 0x79 +#define HidUsage_KK_KeyboardUndo 0x7a +#define HidUsage_KK_KeyboardCut 0x7b +#define HidUsage_KK_KeyboardCopy 0x7c +#define HidUsage_KK_KeyboardPaste 0x7d +#define HidUsage_KK_KeyboardFind 0x7e +#define HidUsage_KK_KeyboardMute 0x7f +#define HidUsage_KK_KeyboardVolumeUp 0x80 +#define HidUsage_KK_KeyboardVolumeDown 0x81 +#define HidUsage_KK_KeyboardLockingCapsLock 0x82 +#define HidUsage_KK_KeyboardLockingNumLock 0x83 +#define HidUsage_KK_KeyboardLockingScrollLock 0x84 +#define HidUsage_KK_KeypadComma 0x85 +#define HidUsage_KK_KeypadEqualSign 0x86 +#define HidUsage_KK_KeyboardInternational1 0x87 +#define HidUsage_KK_KeyboardInternational2 0x88 +#define HidUsage_KK_KeyboardInternational3 0x89 +#define HidUsage_KK_KeyboardInternational4 0x8a +#define HidUsage_KK_KeyboardInternational5 0x8b +#define HidUsage_KK_KeyboardInternational6 0x8c +#define HidUsage_KK_KeyboardInternational7 0x8d +#define HidUsage_KK_KeyboardInternational8 0x8e +#define HidUsage_KK_KeyboardInternational9 0x8f +#define HidUsage_KK_KeyboardLANG1 0x90 +#define HidUsage_KK_KeyboardLANG2 0x91 +#define HidUsage_KK_KeyboardLANG3 0x92 +#define HidUsage_KK_KeyboardLANG4 0x93 +#define HidUsage_KK_KeyboardLANG5 0x94 +#define HidUsage_KK_KeyboardLANG6 0x95 +#define HidUsage_KK_KeyboardLANG7 0x96 +#define HidUsage_KK_KeyboardLANG8 0x97 +#define HidUsage_KK_KeyboardLANG9 0x98 +#define HidUsage_KK_KeyboardAlternateErase 0x99 +#define HidUsage_KK_KeyboardSysReqAttention 0x9a +#define HidUsage_KK_KeyboardCancel 0x9b +#define HidUsage_KK_KeyboardClear 0x9c +#define HidUsage_KK_KeyboardPrior 0x9d +#define HidUsage_KK_KeyboardReturn 0x9e +#define HidUsage_KK_KeyboardSeparator 0x9f +#define HidUsage_KK_KeyboardOut 0xa0 +#define HidUsage_KK_KeyboardOper 0xa1 +#define HidUsage_KK_KeyboardClearAgain 0xa2 +#define HidUsage_KK_KeyboardCrSelProps 0xa3 +#define HidUsage_KK_KeyboardExSel 0xa4 +#define HidUsage_KK_KeypadDouble0 0xb0 +#define HidUsage_KK_KeypadTriple0 0xb1 +#define HidUsage_KK_ThousandsSeparator 0xb2 +#define HidUsage_KK_DecimalSeparator 0xb3 +#define HidUsage_KK_CurrencyUnit 0xb4 +#define HidUsage_KK_CurrencySubunit 0xb5 +#define HidUsage_KK_KeypadLeftBracket 0xb6 +#define HidUsage_KK_KeypadRightBracket 0xb7 +#define HidUsage_KK_KeypadLeftBrace 0xb8 +#define HidUsage_KK_KeypadRightBrace 0xb9 +#define HidUsage_KK_KeypadTab 0xba +#define HidUsage_KK_KeypadBackspace 0xbb +#define HidUsage_KK_KeypadA 0xbc +#define HidUsage_KK_KeypadB 0xbd +#define HidUsage_KK_KeypadC 0xbe +#define HidUsage_KK_KeypadD 0xbf +#define HidUsage_KK_KeypadE 0xc0 +#define HidUsage_KK_KeypadF 0xc1 +#define HidUsage_KK_KeypadXOR 0xc2 +#define HidUsage_KK_KeypadCaret 0xc3 +#define HidUsage_KK_KeypadPercentage 0xc4 +#define HidUsage_KK_KeypadLess 0xc5 +#define HidUsage_KK_KeypadGreater 0xc6 +#define HidUsage_KK_KeypadAmpersand 0xc7 +#define HidUsage_KK_KeypadDoubleAmpersand 0xc8 +#define HidUsage_KK_KeypadBar 0xc9 +#define HidUsage_KK_KeypadDoubleBar 0xca +#define HidUsage_KK_KeypadColon 0xcb +#define HidUsage_KK_KeypadHash 0xcc +#define HidUsage_KK_KeypadSpace 0xcd +#define HidUsage_KK_KeypadAt 0xce +#define HidUsage_KK_KeypadBang 0xcf +#define HidUsage_KK_KeypadMemoryStore 0xd0 +#define HidUsage_KK_KeypadMemoryRecall 0xd1 +#define HidUsage_KK_KeypadMemoryClear 0xd2 +#define HidUsage_KK_KeypadMemoryAdd 0xd3 +#define HidUsage_KK_KeypadMemorySubtract 0xd4 +#define HidUsage_KK_KeypadMemoryMultiply 0xd5 +#define HidUsage_KK_KeypadMemoryDivide 0xd6 +#define HidUsage_KK_KeypadPlusMinus 0xd7 +#define HidUsage_KK_KeypadClear 0xd8 +#define HidUsage_KK_KeypadClearEntry 0xd9 +#define HidUsage_KK_KeypadBinary 0xda +#define HidUsage_KK_KeypadOctal 0xdb +#define HidUsage_KK_KeypadDecimal 0xdc +#define HidUsage_KK_KeypadHexadecimal 0xdd +#define HidUsage_KK_KeyboardLeftControl 0xe0 +#define HidUsage_KK_KeyboardLeftShift 0xe1 +#define HidUsage_KK_KeyboardLeftAlt 0xe2 +#define HidUsage_KK_KeyboardLeftGUI 0xe3 +#define HidUsage_KK_KeyboardRightControl 0xe4 +#define HidUsage_KK_KeyboardRightShift 0xe5 +#define HidUsage_KK_KeyboardRightAlt 0xe6 +#define HidUsage_KK_KeyboardRightGUI 0xe7 +#define HidUsage_LED_NumLock 0x01 +#define HidUsage_LED_CapsLock 0x02 +#define HidUsage_LED_ScrollLock 0x03 +#define HidUsage_LED_Compose 0x04 +#define HidUsage_LED_Kana 0x05 +#define HidUsage_LED_Power 0x06 +#define HidUsage_LED_Shift 0x07 +#define HidUsage_LED_DoNotDisturb 0x08 +#define HidUsage_LED_Mute 0x09 +#define HidUsage_LED_ToneEnable 0x0a +#define HidUsage_LED_HighCutFilter 0x0b +#define HidUsage_LED_LowCutFilter 0x0c +#define HidUsage_LED_EqualizerEnable 0x0d +#define HidUsage_LED_SoundFieldOn 0x0e +#define HidUsage_LED_SurroundOn 0x0f +#define HidUsage_LED_Repeat 0x10 +#define HidUsage_LED_Stereo 0x11 +#define HidUsage_LED_SamplingRateDetect 0x12 +#define HidUsage_LED_Spinning 0x13 +#define HidUsage_LED_CAV 0x14 +#define HidUsage_LED_CLV 0x15 +#define HidUsage_LED_RecordingFormatDetect 0x16 +#define HidUsage_LED_OffHook 0x17 +#define HidUsage_LED_Ring 0x18 +#define HidUsage_LED_MessageWaiting 0x19 +#define HidUsage_LED_DataMode 0x1a +#define HidUsage_LED_BatteryOperation 0x1b +#define HidUsage_LED_BatteryOK 0x1c +#define HidUsage_LED_BatteryLow 0x1d +#define HidUsage_LED_Speaker 0x1e +#define HidUsage_LED_Headset 0x1f +#define HidUsage_LED_Hold 0x20 +#define HidUsage_LED_Microphone 0x21 +#define HidUsage_LED_Coverage 0x22 +#define HidUsage_LED_NightMode 0x23 +#define HidUsage_LED_SendCalls 0x24 +#define HidUsage_LED_CallPickup 0x25 +#define HidUsage_LED_Conference 0x26 +#define HidUsage_LED_Standby 0x27 +#define HidUsage_LED_CameraOn 0x28 +#define HidUsage_LED_CameraOff 0x29 +#define HidUsage_LED_OnLine 0x2a +#define HidUsage_LED_OffLine 0x2b +#define HidUsage_LED_Busy 0x2c +#define HidUsage_LED_Ready 0x2d +#define HidUsage_LED_PaperOut 0x2e +#define HidUsage_LED_PaperJam 0x2f +#define HidUsage_LED_Remote 0x30 +#define HidUsage_LED_Forward 0x31 +#define HidUsage_LED_Reverse 0x32 +#define HidUsage_LED_Stop 0x33 +#define HidUsage_LED_Rewind 0x34 +#define HidUsage_LED_FastForward 0x35 +#define HidUsage_LED_Play 0x36 +#define HidUsage_LED_Pause 0x37 +#define HidUsage_LED_Record 0x38 +#define HidUsage_LED_Error 0x39 +#define HidUsage_LED_UsageSelectedIndicator 0x3a +#define HidUsage_LED_UsageInUseIndicator 0x3b +#define HidUsage_LED_UsageMultiModeIndicator 0x3c +#define HidUsage_LED_IndicatorOn 0x3d +#define HidUsage_LED_IndicatorFlash 0x3e +#define HidUsage_LED_IndicatorSlowBlink 0x3f +#define HidUsage_LED_IndicatorFastBlink 0x40 +#define HidUsage_LED_IndicatorOff 0x41 +#define HidUsage_LED_FlashOnTime 0x42 +#define HidUsage_LED_SlowBlinkOnTime 0x43 +#define HidUsage_LED_SlowBlinkOffTime 0x44 +#define HidUsage_LED_FastBlinkOnTime 0x45 +#define HidUsage_LED_FastBlinkOffTime 0x46 +#define HidUsage_LED_UsageIndicatorColor 0x47 +#define HidUsage_LED_IndicatorRed 0x48 +#define HidUsage_LED_IndicatorGreen 0x49 +#define HidUsage_LED_IndicatorAmber 0x4a +#define HidUsage_LED_GenericIndicator 0x4b +#define HidUsage_LED_SystemSuspend 0x4c +#define HidUsage_LED_ExternalPowerConnected 0x4d +#define HidUsage_LED_IndicatorBlue 0x4e +#define HidUsage_LED_IndicatorOrange 0x4f +#define HidUsage_LED_GoodStatus 0x50 +#define HidUsage_LED_WarningStatus 0x51 +#define HidUsage_LED_RGBLED 0x52 +#define HidUsage_LED_RedLEDChannel 0x53 +#define HidUsage_LED_BlueLEDChannel 0x54 +#define HidUsage_LED_GreenLEDChannel 0x55 +#define HidUsage_LED_LEDIntensity 0x56 +#define HidUsage_LED_SystemMicrophoneMute 0x57 +#define HidUsage_LED_PlayerIndicator 0x60 +#define HidUsage_LED_Player1 0x61 +#define HidUsage_LED_Player2 0x62 +#define HidUsage_LED_Player3 0x63 +#define HidUsage_LED_Player4 0x64 +#define HidUsage_LED_Player5 0x65 +#define HidUsage_LED_Player6 0x66 +#define HidUsage_LED_Player7 0x67 +#define HidUsage_LED_Player8 0x68 +#define HidUsage_TD_Phone 0x01 +#define HidUsage_TD_AnsweringMachine 0x02 +#define HidUsage_TD_MessageControls 0x03 +#define HidUsage_TD_Handset 0x04 +#define HidUsage_TD_Headset 0x05 +#define HidUsage_TD_TelephonyKeyPad 0x06 +#define HidUsage_TD_ProgrammableButton 0x07 +#define HidUsage_TD_HookSwitch 0x20 +#define HidUsage_TD_Flash 0x21 +#define HidUsage_TD_Feature 0x22 +#define HidUsage_TD_Hold 0x23 +#define HidUsage_TD_Redial 0x24 +#define HidUsage_TD_Transfer 0x25 +#define HidUsage_TD_Drop 0x26 +#define HidUsage_TD_Park 0x27 +#define HidUsage_TD_ForwardCalls 0x28 +#define HidUsage_TD_AlternateFunction 0x29 +#define HidUsage_TD_Line 0x2a +#define HidUsage_TD_SpeakerPhone 0x2b +#define HidUsage_TD_Conference 0x2c +#define HidUsage_TD_RingEnable 0x2d +#define HidUsage_TD_RingSelect 0x2e +#define HidUsage_TD_PhoneMute 0x2f +#define HidUsage_TD_CallerID 0x30 +#define HidUsage_TD_Send 0x31 +#define HidUsage_TD_SpeedDial 0x50 +#define HidUsage_TD_StoreNumber 0x51 +#define HidUsage_TD_RecallNumber 0x52 +#define HidUsage_TD_PhoneDirectory 0x53 +#define HidUsage_TD_VoiceMail 0x70 +#define HidUsage_TD_ScreenCalls 0x71 +#define HidUsage_TD_DoNotDisturb 0x72 +#define HidUsage_TD_Message 0x73 +#define HidUsage_TD_AnswerOnOff 0x74 +#define HidUsage_TD_InsideDialTone 0x90 +#define HidUsage_TD_OutsideDialTone 0x91 +#define HidUsage_TD_InsideRingTone 0x92 +#define HidUsage_TD_OutsideRingTone 0x93 +#define HidUsage_TD_PriorityRingTone 0x94 +#define HidUsage_TD_InsideRingback 0x95 +#define HidUsage_TD_PriorityRingback 0x96 +#define HidUsage_TD_LineBusyTone 0x97 +#define HidUsage_TD_ReorderTone 0x98 +#define HidUsage_TD_CallWaitingTone 0x99 +#define HidUsage_TD_ConfirmationTone1 0x9a +#define HidUsage_TD_ConfirmationTone2 0x9b +#define HidUsage_TD_TonesOff 0x9c +#define HidUsage_TD_OutsideRingback 0x9d +#define HidUsage_TD_Ringer 0x9e +#define HidUsage_TD_PhoneKey0 0xb0 +#define HidUsage_TD_PhoneKey1 0xb1 +#define HidUsage_TD_PhoneKey2 0xb2 +#define HidUsage_TD_PhoneKey3 0xb3 +#define HidUsage_TD_PhoneKey4 0xb4 +#define HidUsage_TD_PhoneKey5 0xb5 +#define HidUsage_TD_PhoneKey6 0xb6 +#define HidUsage_TD_PhoneKey7 0xb7 +#define HidUsage_TD_PhoneKey8 0xb8 +#define HidUsage_TD_PhoneKey9 0xb9 +#define HidUsage_TD_PhoneKeyStar 0xba +#define HidUsage_TD_PhoneKeyPound 0xbb +#define HidUsage_TD_PhoneKeyA 0xbc +#define HidUsage_TD_PhoneKeyB 0xbd +#define HidUsage_TD_PhoneKeyC 0xbe +#define HidUsage_TD_PhoneKeyD 0xbf +#define HidUsage_TD_PhoneCallHistoryKey 0xc0 +#define HidUsage_TD_PhoneCallerIDKey 0xc1 +#define HidUsage_TD_PhoneSettingsKey 0xc2 +#define HidUsage_TD_HostControl 0xf0 +#define HidUsage_TD_HostAvailable 0xf1 +#define HidUsage_TD_HostCallActive 0xf2 +#define HidUsage_TD_ActivateHandsetAudio 0xf3 +#define HidUsage_TD_RingType 0xf4 +#define HidUsage_TD_RedialablePhoneNumber 0xf5 +#define HidUsage_TD_StopRingTone 0xf8 +#define HidUsage_TD_PSTNRingTone 0xf9 +#define HidUsage_TD_HostRingTone 0xfa +#define HidUsage_TD_AlertSoundError 0xfb +#define HidUsage_TD_AlertSoundConfirm 0xfc +#define HidUsage_TD_AlertSoundNotification 0xfd +#define HidUsage_TD_SilentRing 0xfe +#define HidUsage_TD_EmailMessageWaiting 0x108 +#define HidUsage_TD_VoicemailMessageWaiting 0x109 +#define HidUsage_TD_HostHold 0x10a +#define HidUsage_TD_IncomingCallHistoryCount 0x110 +#define HidUsage_TD_OutgoingCallHistoryCount 0x111 +#define HidUsage_TD_IncomingCallHistory 0x112 +#define HidUsage_TD_OutgoingCallHistory 0x113 +#define HidUsage_TD_PhoneLocale 0x114 +#define HidUsage_TD_PhoneTimeSecond 0x140 +#define HidUsage_TD_PhoneTimeMinute 0x141 +#define HidUsage_TD_PhoneTimeHour 0x142 +#define HidUsage_TD_PhoneDateDay 0x143 +#define HidUsage_TD_PhoneDateMonth 0x144 +#define HidUsage_TD_PhoneDateYear 0x145 +#define HidUsage_TD_HandsetNickname 0x146 +#define HidUsage_TD_AddressBookID 0x147 +#define HidUsage_TD_CallDuration 0x14a +#define HidUsage_TD_DualModePhone 0x14b +#define HidUsage_Con_ConsumerControl 0x01 +#define HidUsage_Con_NumericKeyPad 0x02 +#define HidUsage_Con_ProgrammableButtons 0x03 +#define HidUsage_Con_Microphone 0x04 +#define HidUsage_Con_Headphone 0x05 +#define HidUsage_Con_GraphicEqualizer 0x06 +#define HidUsage_Con_Plus10 0x20 +#define HidUsage_Con_Plus100 0x21 +#define HidUsage_Con_AMPM 0x22 +#define HidUsage_Con_Power 0x30 +#define HidUsage_Con_Reset 0x31 +#define HidUsage_Con_Sleep 0x32 +#define HidUsage_Con_SleepAfter 0x33 +#define HidUsage_Con_SleepMode 0x34 +#define HidUsage_Con_Illumination 0x35 +#define HidUsage_Con_FunctionButtons 0x36 +#define HidUsage_Con_Menu 0x40 +#define HidUsage_Con_MenuPick 0x41 +#define HidUsage_Con_MenuUp 0x42 +#define HidUsage_Con_MenuDown 0x43 +#define HidUsage_Con_MenuLeft 0x44 +#define HidUsage_Con_MenuRight 0x45 +#define HidUsage_Con_MenuEscape 0x46 +#define HidUsage_Con_MenuValueIncrease 0x47 +#define HidUsage_Con_MenuValueDecrease 0x48 +#define HidUsage_Con_DataOnScreen 0x60 +#define HidUsage_Con_ClosedCaption 0x61 +#define HidUsage_Con_ClosedCaptionSelect 0x62 +#define HidUsage_Con_VCRTV 0x63 +#define HidUsage_Con_BroadcastMode 0x64 +#define HidUsage_Con_Snapshot 0x65 +#define HidUsage_Con_Still 0x66 +#define HidUsage_Con_PictureinPictureToggle 0x67 +#define HidUsage_Con_PictureinPictureSwap 0x68 +#define HidUsage_Con_RedMenuButton 0x69 +#define HidUsage_Con_GreenMenuButton 0x6a +#define HidUsage_Con_BlueMenuButton 0x6b +#define HidUsage_Con_YellowMenuButton 0x6c +#define HidUsage_Con_Aspect 0x6d +#define HidUsage_Con_ThreeDModeSelect 0x6e +#define HidUsage_Con_DisplayBrightnessIncrement 0x6f +#define HidUsage_Con_DisplayBrightnessDecrement 0x70 +#define HidUsage_Con_DisplayBrightness 0x71 +#define HidUsage_Con_DisplayBacklightToggle 0x72 +#define HidUsage_Con_DisplaySetBrightnesstoMinimum 0x73 +#define HidUsage_Con_DisplaySetBrightnesstoMaximum 0x74 +#define HidUsage_Con_DisplaySetAutoBrightness 0x75 +#define HidUsage_Con_CameraAccessEnabled 0x76 +#define HidUsage_Con_CameraAccessDisabled 0x77 +#define HidUsage_Con_CameraAccessToggle 0x78 +#define HidUsage_Con_KeyboardBrightnessIncrement 0x79 +#define HidUsage_Con_KeyboardBrightnessDecrement 0x7a +#define HidUsage_Con_KeyboardBacklightSetLevel 0x7b +#define HidUsage_Con_KeyboardBacklightOOC 0x7c +#define HidUsage_Con_KeyboardBacklightSetMinimum 0x7d +#define HidUsage_Con_KeyboardBacklightSetMaximum 0x7e +#define HidUsage_Con_KeyboardBacklightAuto 0x7f +#define HidUsage_Con_Selection 0x80 +#define HidUsage_Con_AssignSelection 0x81 +#define HidUsage_Con_ModeStep 0x82 +#define HidUsage_Con_RecallLast 0x83 +#define HidUsage_Con_EnterChannel 0x84 +#define HidUsage_Con_OrderMovie 0x85 +#define HidUsage_Con_Channel 0x86 +#define HidUsage_Con_MediaSelection 0x87 +#define HidUsage_Con_MediaSelectComputer 0x88 +#define HidUsage_Con_MediaSelectTV 0x89 +#define HidUsage_Con_MediaSelectWWW 0x8a +#define HidUsage_Con_MediaSelectDVD 0x8b +#define HidUsage_Con_MediaSelectTelephone 0x8c +#define HidUsage_Con_MediaSelectProgramGuide 0x8d +#define HidUsage_Con_MediaSelectVideoPhone 0x8e +#define HidUsage_Con_MediaSelectGames 0x8f +#define HidUsage_Con_MediaSelectMessages 0x90 +#define HidUsage_Con_MediaSelectCD 0x91 +#define HidUsage_Con_MediaSelectVCR 0x92 +#define HidUsage_Con_MediaSelectTuner 0x93 +#define HidUsage_Con_Quit 0x94 +#define HidUsage_Con_Help 0x95 +#define HidUsage_Con_MediaSelectTape 0x96 +#define HidUsage_Con_MediaSelectCable 0x97 +#define HidUsage_Con_MediaSelectSatellite 0x98 +#define HidUsage_Con_MediaSelectSecurity 0x99 +#define HidUsage_Con_MediaSelectHome 0x9a +#define HidUsage_Con_MediaSelectCall 0x9b +#define HidUsage_Con_ChannelIncrement 0x9c +#define HidUsage_Con_ChannelDecrement 0x9d +#define HidUsage_Con_MediaSelectSAP 0x9e +#define HidUsage_Con_VCRPlus 0xa0 +#define HidUsage_Con_Once 0xa1 +#define HidUsage_Con_Daily 0xa2 +#define HidUsage_Con_Weekly 0xa3 +#define HidUsage_Con_Monthly 0xa4 +#define HidUsage_Con_Play 0xb0 +#define HidUsage_Con_Pause 0xb1 +#define HidUsage_Con_Record 0xb2 +#define HidUsage_Con_FastForward 0xb3 +#define HidUsage_Con_Rewind 0xb4 +#define HidUsage_Con_ScanNextTrack 0xb5 +#define HidUsage_Con_ScanPreviousTrack 0xb6 +#define HidUsage_Con_Stop 0xb7 +#define HidUsage_Con_Eject 0xb8 +#define HidUsage_Con_RandomPlay 0xb9 +#define HidUsage_Con_SelectDisc 0xba +#define HidUsage_Con_EnterDisc 0xbb +#define HidUsage_Con_Repeat 0xbc +#define HidUsage_Con_Tracking 0xbd +#define HidUsage_Con_TrackNormal 0xbe +#define HidUsage_Con_SlowTracking 0xbf +#define HidUsage_Con_FrameForward 0xc0 +#define HidUsage_Con_FrameBack 0xc1 +#define HidUsage_Con_Mark 0xc2 +#define HidUsage_Con_ClearMark 0xc3 +#define HidUsage_Con_RepeatFromMark 0xc4 +#define HidUsage_Con_ReturnToMark 0xc5 +#define HidUsage_Con_SearchMarkForward 0xc6 +#define HidUsage_Con_SearchMarkBackwards 0xc7 +#define HidUsage_Con_CounterReset 0xc8 +#define HidUsage_Con_ShowCounter 0xc9 +#define HidUsage_Con_TrackingIncrement 0xca +#define HidUsage_Con_TrackingDecrement 0xcb +#define HidUsage_Con_StopEject 0xcc +#define HidUsage_Con_PlayPause 0xcd +#define HidUsage_Con_PlaySkip 0xce +#define HidUsage_Con_VoiceCommand 0xcf +#define HidUsage_Con_InvokeCaptureInterface 0xd0 +#define HidUsage_Con_StartorStopGameRecording 0xd1 +#define HidUsage_Con_HistoricalGameCapture 0xd2 +#define HidUsage_Con_CaptureGameScreenshot 0xd3 +#define HidUsage_Con_ShoworHideRecordingIndicator 0xd4 +#define HidUsage_Con_StartorStopMicrophoneCapture 0xd5 +#define HidUsage_Con_StartorStopCameraCapture 0xd6 +#define HidUsage_Con_StartorStopGameBroadcast 0xd7 +#define HidUsage_Con_StartorStopVoiceDictationSession 0xd8 +#define HidUsage_Con_InvokeDismissEmojiPicker 0xd9 +#define HidUsage_Con_Volume 0xe0 +#define HidUsage_Con_Balance 0xe1 +#define HidUsage_Con_Mute 0xe2 +#define HidUsage_Con_Bass 0xe3 +#define HidUsage_Con_Treble 0xe4 +#define HidUsage_Con_BassBoost 0xe5 +#define HidUsage_Con_SurroundMode 0xe6 +#define HidUsage_Con_Loudness 0xe7 +#define HidUsage_Con_MPX 0xe8 +#define HidUsage_Con_VolumeIncrement 0xe9 +#define HidUsage_Con_VolumeDecrement 0xea +#define HidUsage_Con_SpeedSelect 0xf0 +#define HidUsage_Con_PlaybackSpeed 0xf1 +#define HidUsage_Con_StandardPlay 0xf2 +#define HidUsage_Con_LongPlay 0xf3 +#define HidUsage_Con_ExtendedPlay 0xf4 +#define HidUsage_Con_Slow 0xf5 +#define HidUsage_Con_FanEnable 0x100 +#define HidUsage_Con_FanSpeed 0x101 +#define HidUsage_Con_LightEnable 0x102 +#define HidUsage_Con_LightIlluminationLevel 0x103 +#define HidUsage_Con_ClimateControlEnable 0x104 +#define HidUsage_Con_RoomTemperature 0x105 +#define HidUsage_Con_SecurityEnable 0x106 +#define HidUsage_Con_FireAlarm 0x107 +#define HidUsage_Con_PoliceAlarm 0x108 +#define HidUsage_Con_Proximity 0x109 +#define HidUsage_Con_Motion 0x10a +#define HidUsage_Con_DuressAlarm 0x10b +#define HidUsage_Con_HoldupAlarm 0x10c +#define HidUsage_Con_MedicalAlarm 0x10d +#define HidUsage_Con_BalanceRight 0x150 +#define HidUsage_Con_BalanceLeft 0x151 +#define HidUsage_Con_BassIncrement 0x152 +#define HidUsage_Con_BassDecrement 0x153 +#define HidUsage_Con_TrebleIncrement 0x154 +#define HidUsage_Con_TrebleDecrement 0x155 +#define HidUsage_Con_SpeakerSystem 0x160 +#define HidUsage_Con_ChannelLeft 0x161 +#define HidUsage_Con_ChannelRight 0x162 +#define HidUsage_Con_ChannelCenter 0x163 +#define HidUsage_Con_ChannelFront 0x164 +#define HidUsage_Con_ChannelCenterFront 0x165 +#define HidUsage_Con_ChannelSide 0x166 +#define HidUsage_Con_ChannelSurround 0x167 +#define HidUsage_Con_ChannelLowFrequencyEnhancement 0x168 +#define HidUsage_Con_ChannelTop 0x169 +#define HidUsage_Con_ChannelUnknown 0x16a +#define HidUsage_Con_Subchannel 0x170 +#define HidUsage_Con_SubchannelIncrement 0x171 +#define HidUsage_Con_SubchannelDecrement 0x172 +#define HidUsage_Con_AlternateAudioIncrement 0x173 +#define HidUsage_Con_AlternateAudioDecrement 0x174 +#define HidUsage_Con_ApplicationLaunchButtons 0x180 +#define HidUsage_Con_ALLaunchButtonConfigurationTool 0x181 +#define HidUsage_Con_ALProgrammableButtonConfiguration 0x182 +#define HidUsage_Con_ALConsumerControlConfiguration 0x183 +#define HidUsage_Con_ALWordProcessor 0x184 +#define HidUsage_Con_ALTextEditor 0x185 +#define HidUsage_Con_ALSpreadsheet 0x186 +#define HidUsage_Con_ALGraphicsEditor 0x187 +#define HidUsage_Con_ALPresentationApp 0x188 +#define HidUsage_Con_ALDatabaseApp 0x189 +#define HidUsage_Con_ALEmailReader 0x18a +#define HidUsage_Con_ALNewsreader 0x18b +#define HidUsage_Con_ALVoicemail 0x18c +#define HidUsage_Con_ALContactsAddressBook 0x18d +#define HidUsage_Con_ALCalendarSchedule 0x18e +#define HidUsage_Con_ALTaskProjectManager 0x18f +#define HidUsage_Con_ALLogJournalTimecard 0x190 +#define HidUsage_Con_ALCheckbookFinance 0x191 +#define HidUsage_Con_ALCalculator 0x192 +#define HidUsage_Con_ALAVCapturePlayback 0x193 +#define HidUsage_Con_ALLocalMachineBrowser 0x194 +#define HidUsage_Con_ALLANWANBrowser 0x195 +#define HidUsage_Con_ALInternetBrowser 0x196 +#define HidUsage_Con_ALRemoteNetworkingISPConnect 0x197 +#define HidUsage_Con_ALNetworkConference 0x198 +#define HidUsage_Con_ALNetworkChat 0x199 +#define HidUsage_Con_ALTelephonyDialer 0x19a +#define HidUsage_Con_ALLogon 0x19b +#define HidUsage_Con_ALLogoff 0x19c +#define HidUsage_Con_ALLogonLogoff 0x19d +#define HidUsage_Con_ALTerminalLockScreensaver 0x19e +#define HidUsage_Con_ALControlPanel 0x19f +#define HidUsage_Con_ALCommandLineProcessorRun 0x1a0 +#define HidUsage_Con_ALProcessTaskManager 0x1a1 +#define HidUsage_Con_ALSelectTaskApplication 0x1a2 +#define HidUsage_Con_ALNextTaskApplication 0x1a3 +#define HidUsage_Con_ALPreviousTaskApplication 0x1a4 +#define HidUsage_Con_ALPreemptiveHaltTaskApplication 0x1a5 +#define HidUsage_Con_ALIntegratedHelpCenter 0x1a6 +#define HidUsage_Con_ALDocuments 0x1a7 +#define HidUsage_Con_ALThesaurus 0x1a8 +#define HidUsage_Con_ALDictionary 0x1a9 +#define HidUsage_Con_ALDesktop 0x1aa +#define HidUsage_Con_ALSpellCheck 0x1ab +#define HidUsage_Con_ALGrammarCheck 0x1ac +#define HidUsage_Con_ALWirelessStatus 0x1ad +#define HidUsage_Con_ALKeyboardLayout 0x1ae +#define HidUsage_Con_ALVirusProtection 0x1af +#define HidUsage_Con_ALEncryption 0x1b0 +#define HidUsage_Con_ALScreenSaver 0x1b1 +#define HidUsage_Con_ALAlarms 0x1b2 +#define HidUsage_Con_ALClock 0x1b3 +#define HidUsage_Con_ALFileBrowser 0x1b4 +#define HidUsage_Con_ALPowerStatus 0x1b5 +#define HidUsage_Con_ALImageBrowser 0x1b6 +#define HidUsage_Con_ALAudioBrowser 0x1b7 +#define HidUsage_Con_ALMovieBrowser 0x1b8 +#define HidUsage_Con_ALDigitalRightsManager 0x1b9 +#define HidUsage_Con_ALDigitalWallet 0x1ba +#define HidUsage_Con_ALInstantMessaging 0x1bc +#define HidUsage_Con_ALOEMFeaturesTipsTutorialBrowser 0x1bd +#define HidUsage_Con_ALOEMHelp 0x1be +#define HidUsage_Con_ALOnlineCommunity 0x1bf +#define HidUsage_Con_ALEntertainmentContentBrowser 0x1c0 +#define HidUsage_Con_ALOnlineShoppingBrowser 0x1c1 +#define HidUsage_Con_ALSmartCardInformationHelp 0x1c2 +#define HidUsage_Con_ALMarketMonitorFinanceBrowser 0x1c3 +#define HidUsage_Con_ALCustomizedCorporateNewsBrowser 0x1c4 +#define HidUsage_Con_ALOnlineActivityBrowser 0x1c5 +#define HidUsage_Con_ALResearchSearchBrowser 0x1c6 +#define HidUsage_Con_ALAudioPlayer 0x1c7 +#define HidUsage_Con_ALMessageStatus 0x1c8 +#define HidUsage_Con_ALContactSync 0x1c9 +#define HidUsage_Con_ALNavigation 0x1ca +#define HidUsage_Con_ALContextawareDesktopAssistant 0x1cb +#define HidUsage_Con_GenericGUIApplicationControls 0x200 +#define HidUsage_Con_ACNew 0x201 +#define HidUsage_Con_ACOpen 0x202 +#define HidUsage_Con_ACClose 0x203 +#define HidUsage_Con_ACExit 0x204 +#define HidUsage_Con_ACMaximize 0x205 +#define HidUsage_Con_ACMinimize 0x206 +#define HidUsage_Con_ACSave 0x207 +#define HidUsage_Con_ACPrint 0x208 +#define HidUsage_Con_ACProperties 0x209 +#define HidUsage_Con_ACUndo 0x21a +#define HidUsage_Con_ACCopy 0x21b +#define HidUsage_Con_ACCut 0x21c +#define HidUsage_Con_ACPaste 0x21d +#define HidUsage_Con_ACSelectAll 0x21e +#define HidUsage_Con_ACFind 0x21f +#define HidUsage_Con_ACFindandReplace 0x220 +#define HidUsage_Con_ACSearch 0x221 +#define HidUsage_Con_ACGoTo 0x222 +#define HidUsage_Con_ACHome 0x223 +#define HidUsage_Con_ACBack 0x224 +#define HidUsage_Con_ACForward 0x225 +#define HidUsage_Con_ACStop 0x226 +#define HidUsage_Con_ACRefresh 0x227 +#define HidUsage_Con_ACPreviousLink 0x228 +#define HidUsage_Con_ACNextLink 0x229 +#define HidUsage_Con_ACBookmarks 0x22a +#define HidUsage_Con_ACHistory 0x22b +#define HidUsage_Con_ACSubscriptions 0x22c +#define HidUsage_Con_ACZoomIn 0x22d +#define HidUsage_Con_ACZoomOut 0x22e +#define HidUsage_Con_ACZoom 0x22f +#define HidUsage_Con_ACFullScreenView 0x230 +#define HidUsage_Con_ACNormalView 0x231 +#define HidUsage_Con_ACViewToggle 0x232 +#define HidUsage_Con_ACScrollUp 0x233 +#define HidUsage_Con_ACScrollDown 0x234 +#define HidUsage_Con_ACScroll 0x235 +#define HidUsage_Con_ACPanLeft 0x236 +#define HidUsage_Con_ACPanRight 0x237 +#define HidUsage_Con_ACPan 0x238 +#define HidUsage_Con_ACNewWindow 0x239 +#define HidUsage_Con_ACTileHorizontally 0x23a +#define HidUsage_Con_ACTileVertically 0x23b +#define HidUsage_Con_ACFormat 0x23c +#define HidUsage_Con_ACEdit 0x23d +#define HidUsage_Con_ACBold 0x23e +#define HidUsage_Con_ACItalics 0x23f +#define HidUsage_Con_ACUnderline 0x240 +#define HidUsage_Con_ACStrikethrough 0x241 +#define HidUsage_Con_ACSubscript 0x242 +#define HidUsage_Con_ACSuperscript 0x243 +#define HidUsage_Con_ACAllCaps 0x244 +#define HidUsage_Con_ACRotate 0x245 +#define HidUsage_Con_ACResize 0x246 +#define HidUsage_Con_ACFlipHorizontal 0x247 +#define HidUsage_Con_ACFlipVertical 0x248 +#define HidUsage_Con_ACMirrorHorizontal 0x249 +#define HidUsage_Con_ACMirrorVertical 0x24a +#define HidUsage_Con_ACFontSelect 0x24b +#define HidUsage_Con_ACFontColor 0x24c +#define HidUsage_Con_ACFontSize 0x24d +#define HidUsage_Con_ACJustifyLeft 0x24e +#define HidUsage_Con_ACJustifyCenterH 0x24f +#define HidUsage_Con_ACJustifyRight 0x250 +#define HidUsage_Con_ACJustifyBlockH 0x251 +#define HidUsage_Con_ACJustifyTop 0x252 +#define HidUsage_Con_ACJustifyCenterV 0x253 +#define HidUsage_Con_ACJustifyBottom 0x254 +#define HidUsage_Con_ACJustifyBlockV 0x255 +#define HidUsage_Con_ACIndentDecrease 0x256 +#define HidUsage_Con_ACIndentIncrease 0x257 +#define HidUsage_Con_ACNumberedList 0x258 +#define HidUsage_Con_ACRestartNumbering 0x259 +#define HidUsage_Con_ACBulletedList 0x25a +#define HidUsage_Con_ACPromote 0x25b +#define HidUsage_Con_ACDemote 0x25c +#define HidUsage_Con_ACYes 0x25d +#define HidUsage_Con_ACNo 0x25e +#define HidUsage_Con_ACCancel 0x25f +#define HidUsage_Con_ACCatalog 0x260 +#define HidUsage_Con_ACBuyCheckout 0x261 +#define HidUsage_Con_ACAddtoCart 0x262 +#define HidUsage_Con_ACExpand 0x263 +#define HidUsage_Con_ACExpandAll 0x264 +#define HidUsage_Con_ACCollapse 0x265 +#define HidUsage_Con_ACCollapseAll 0x266 +#define HidUsage_Con_ACPrintPreview 0x267 +#define HidUsage_Con_ACPasteSpecial 0x268 +#define HidUsage_Con_ACInsertMode 0x269 +#define HidUsage_Con_ACDelete 0x26a +#define HidUsage_Con_ACLock 0x26b +#define HidUsage_Con_ACUnlock 0x26c +#define HidUsage_Con_ACProtect 0x26d +#define HidUsage_Con_ACUnprotect 0x26e +#define HidUsage_Con_ACAttachComment 0x26f +#define HidUsage_Con_ACDeleteComment 0x270 +#define HidUsage_Con_ACViewComment 0x271 +#define HidUsage_Con_ACSelectWord 0x272 +#define HidUsage_Con_ACSelectSentence 0x273 +#define HidUsage_Con_ACSelectParagraph 0x274 +#define HidUsage_Con_ACSelectColumn 0x275 +#define HidUsage_Con_ACSelectRow 0x276 +#define HidUsage_Con_ACSelectTable 0x277 +#define HidUsage_Con_ACSelectObject 0x278 +#define HidUsage_Con_ACRedoRepeat 0x279 +#define HidUsage_Con_ACSort 0x27a +#define HidUsage_Con_ACSortAscending 0x27b +#define HidUsage_Con_ACSortDescending 0x27c +#define HidUsage_Con_ACFilter 0x27d +#define HidUsage_Con_ACSetClock 0x27e +#define HidUsage_Con_ACViewClock 0x27f +#define HidUsage_Con_ACSelectTimeZone 0x280 +#define HidUsage_Con_ACEditTimeZones 0x281 +#define HidUsage_Con_ACSetAlarm 0x282 +#define HidUsage_Con_ACClearAlarm 0x283 +#define HidUsage_Con_ACSnoozeAlarm 0x284 +#define HidUsage_Con_ACResetAlarm 0x285 +#define HidUsage_Con_ACSynchronize 0x286 +#define HidUsage_Con_ACSendReceive 0x287 +#define HidUsage_Con_ACSendTo 0x288 +#define HidUsage_Con_ACReply 0x289 +#define HidUsage_Con_ACReplyAll 0x28a +#define HidUsage_Con_ACForwardMsg 0x28b +#define HidUsage_Con_ACSend 0x28c +#define HidUsage_Con_ACAttachFile 0x28d +#define HidUsage_Con_ACUpload 0x28e +#define HidUsage_Con_ACDownloadSaveTargetAs 0x28f +#define HidUsage_Con_ACSetBorders 0x290 +#define HidUsage_Con_ACInsertRow 0x291 +#define HidUsage_Con_ACInsertColumn 0x292 +#define HidUsage_Con_ACInsertFile 0x293 +#define HidUsage_Con_ACInsertPicture 0x294 +#define HidUsage_Con_ACInsertObject 0x295 +#define HidUsage_Con_ACInsertSymbol 0x296 +#define HidUsage_Con_ACSaveandClose 0x297 +#define HidUsage_Con_ACRename 0x298 +#define HidUsage_Con_ACMerge 0x299 +#define HidUsage_Con_ACSplit 0x29a +#define HidUsage_Con_ACDisributeHorizontally 0x29b +#define HidUsage_Con_ACDistributeVertically 0x29c +#define HidUsage_Con_ACNextKeyboardLayoutSelect 0x29d +#define HidUsage_Con_ACNavigationGuidance 0x29e +#define HidUsage_Con_ACDesktopShowAllWindows 0x29f +#define HidUsage_Con_ACSoftKeyLeft 0x2a0 +#define HidUsage_Con_ACSoftKeyRight 0x2a1 +#define HidUsage_Con_ACDesktopShowAllApplications 0x2a2 +#define HidUsage_Con_ACIdleKeepAlive 0x2b0 +#define HidUsage_Con_ExtendedKeyboardAttributesCollection 0x2c0 +#define HidUsage_Con_KeyboardFormFactor 0x2c1 +#define HidUsage_Con_KeyboardKeyType 0x2c2 +#define HidUsage_Con_KeyboardPhysicalLayout 0x2c3 +#define HidUsage_Con_VendorSpecificKeyboardPhysicalLayout 0x2c4 +#define HidUsage_Con_KeyboardIETFLanguageTagIndex 0x2c5 +#define HidUsage_Con_ImplementedKeyboardInputAssistControls 0x2c6 +#define HidUsage_Con_KeyboardInputAssistPrevious 0x2c7 +#define HidUsage_Con_KeyboardInputAssistNext 0x2c8 +#define HidUsage_Con_KeyboardInputAssistPreviousGroup 0x2c9 +#define HidUsage_Con_KeyboardInputAssistNextGroup 0x2ca +#define HidUsage_Con_KeyboardInputAssistAccept 0x2cb +#define HidUsage_Con_KeyboardInputAssistCancel 0x2cc +#define HidUsage_Con_PrivacyScreenToggle 0x2d0 +#define HidUsage_Con_PrivacyScreenLevelDecrement 0x2d1 +#define HidUsage_Con_PrivacyScreenLevelIncrement 0x2d2 +#define HidUsage_Con_PrivacyScreenLevelMinimum 0x2d3 +#define HidUsage_Con_PrivacyScreenLevelMaximum 0x2d4 +#define HidUsage_Con_ContactEdited 0x500 +#define HidUsage_Con_ContactAdded 0x501 +#define HidUsage_Con_ContactRecordActive 0x502 +#define HidUsage_Con_ContactIndex 0x503 +#define HidUsage_Con_ContactNickname 0x504 +#define HidUsage_Con_ContactFirstName 0x505 +#define HidUsage_Con_ContactLastName 0x506 +#define HidUsage_Con_ContactFullName 0x507 +#define HidUsage_Con_ContactPhoneNumberPersonal 0x508 +#define HidUsage_Con_ContactPhoneNumberBusiness 0x509 +#define HidUsage_Con_ContactPhoneNumberMobile 0x50a +#define HidUsage_Con_ContactPhoneNumberPager 0x50b +#define HidUsage_Con_ContactPhoneNumberFax 0x50c +#define HidUsage_Con_ContactPhoneNumberOther 0x50d +#define HidUsage_Con_ContactEmailPersonal 0x50e +#define HidUsage_Con_ContactEmailBusiness 0x50f +#define HidUsage_Con_ContactEmailOther 0x510 +#define HidUsage_Con_ContactEmailMain 0x511 +#define HidUsage_Con_ContactSpeedDialNumber 0x512 +#define HidUsage_Con_ContactStatusFlag 0x513 +#define HidUsage_Con_ContactMisc 0x514 +#define HidUsage_Dig_Digitizer 0x01 +#define HidUsage_Dig_Pen 0x02 +#define HidUsage_Dig_LightPen 0x03 +#define HidUsage_Dig_TouchScreen 0x04 +#define HidUsage_Dig_TouchPad 0x05 +#define HidUsage_Dig_Whiteboard 0x06 +#define HidUsage_Dig_CoordinateMeasuringMachine 0x07 +#define HidUsage_Dig_ThreeDDigitizer 0x08 +#define HidUsage_Dig_StereoPlotter 0x09 +#define HidUsage_Dig_ArticulatedArm 0x0a +#define HidUsage_Dig_Armature 0x0b +#define HidUsage_Dig_MultiplePointDigitizer 0x0c +#define HidUsage_Dig_FreeSpaceWand 0x0d +#define HidUsage_Dig_DeviceConfiguration 0x0e +#define HidUsage_Dig_CapacitiveHeatMapDigitizer 0x0f +#define HidUsage_Dig_Stylus 0x20 +#define HidUsage_Dig_Puck 0x21 +#define HidUsage_Dig_Finger 0x22 +#define HidUsage_Dig_Devicesettings 0x23 +#define HidUsage_Dig_CharacterGesture 0x24 +#define HidUsage_Dig_TipPressure 0x30 +#define HidUsage_Dig_BarrelPressure 0x31 +#define HidUsage_Dig_InRange 0x32 +#define HidUsage_Dig_Touch 0x33 +#define HidUsage_Dig_Untouch 0x34 +#define HidUsage_Dig_Tap 0x35 +#define HidUsage_Dig_Quality 0x36 +#define HidUsage_Dig_DataValid 0x37 +#define HidUsage_Dig_TransducerIndex 0x38 +#define HidUsage_Dig_TabletFunctionKeys 0x39 +#define HidUsage_Dig_ProgramChangeKeys 0x3a +#define HidUsage_Dig_BatteryStrength 0x3b +#define HidUsage_Dig_Invert 0x3c +#define HidUsage_Dig_XTilt 0x3d +#define HidUsage_Dig_YTilt 0x3e +#define HidUsage_Dig_Azimuth 0x3f +#define HidUsage_Dig_Altitude 0x40 +#define HidUsage_Dig_Twist 0x41 +#define HidUsage_Dig_TipSwitch 0x42 +#define HidUsage_Dig_SecondaryTipSwitch 0x43 +#define HidUsage_Dig_BarrelSwitch 0x44 +#define HidUsage_Dig_Eraser 0x45 +#define HidUsage_Dig_TabletPick 0x46 +#define HidUsage_Dig_TouchValid 0x47 +#define HidUsage_Dig_Width 0x48 +#define HidUsage_Dig_Height 0x49 +#define HidUsage_Dig_ContactIdentifier 0x51 +#define HidUsage_Dig_DeviceMode 0x52 +#define HidUsage_Dig_DeviceIdentifier 0x53 +#define HidUsage_Dig_ContactCount 0x54 +#define HidUsage_Dig_ContactCountMaximum 0x55 +#define HidUsage_Dig_ScanTime 0x56 +#define HidUsage_Dig_SurfaceSwitch 0x57 +#define HidUsage_Dig_ButtonSwitch 0x58 +#define HidUsage_Dig_PadType 0x59 +#define HidUsage_Dig_SecondaryBarrelSwitch 0x5a +#define HidUsage_Dig_TransducerSerialNumber 0x5b +#define HidUsage_Dig_PreferredColor 0x5c +#define HidUsage_Dig_PreferredColorisLocked 0x5d +#define HidUsage_Dig_PreferredLineWidth 0x5e +#define HidUsage_Dig_PreferredLineWidthisLocked 0x5f +#define HidUsage_Dig_LatencyMode 0x60 +#define HidUsage_Dig_GestureCharacterQuality 0x61 +#define HidUsage_Dig_CharacterGestureDataLength 0x62 +#define HidUsage_Dig_CharacterGestureData 0x63 +#define HidUsage_Dig_GestureCharacterEncoding 0x64 +#define HidUsage_Dig_UTF8CharacterGestureEncoding 0x65 +#define HidUsage_Dig_UTF16LittleEndianCharacterGestureEncoding 0x66 +#define HidUsage_Dig_UTF16BigEndianCharacterGestureEncoding 0x67 +#define HidUsage_Dig_UTF32LittleEndianCharacterGestureEncoding 0x68 +#define HidUsage_Dig_UTF32BigEndianCharacterGestureEncoding 0x69 +#define HidUsage_Dig_CapacitiveHeatMapProtocolVendorID 0x6a +#define HidUsage_Dig_CapacitiveHeatMapProtocolVersion 0x6b +#define HidUsage_Dig_CapacitiveHeatMapFrameData 0x6c +#define HidUsage_Dig_GestureCharacterEnable 0x6d +#define HidUsage_Dig_TransducerSerialNumberPart2 0x6e +#define HidUsage_Dig_NoPreferredColor 0x6f +#define HidUsage_Dig_PreferredLineStyle 0x70 +#define HidUsage_Dig_PreferredLineStyleisLocked 0x71 +#define HidUsage_Dig_Ink 0x72 +#define HidUsage_Dig_Pencil 0x73 +#define HidUsage_Dig_Highlighter 0x74 +#define HidUsage_Dig_ChiselMarker 0x75 +#define HidUsage_Dig_Brush 0x76 +#define HidUsage_Dig_NoPreference 0x77 +#define HidUsage_Dig_DigitizerDiagnostic 0x80 +#define HidUsage_Dig_DigitizerError 0x81 +#define HidUsage_Dig_ErrNormalStatus 0x82 +#define HidUsage_Dig_ErrTransducersExceeded 0x83 +#define HidUsage_Dig_ErrFullTransFeaturesUnavailable 0x84 +#define HidUsage_Dig_ErrChargeLow 0x85 +#define HidUsage_Dig_TransducerSoftwareInfo 0x90 +#define HidUsage_Dig_TransducerVendorId 0x91 +#define HidUsage_Dig_TransducerProductId 0x92 +#define HidUsage_Dig_DeviceSupportedProtocols 0x93 +#define HidUsage_Dig_TransducerSupportedProtocols 0x94 +#define HidUsage_Dig_NoProtocol 0x95 +#define HidUsage_Dig_WacomAESProtocol 0x96 +#define HidUsage_Dig_USIProtocol 0x97 +#define HidUsage_Dig_MicrosoftPenProtocol 0x98 +#define HidUsage_Dig_SupportedReportRates 0xa0 +#define HidUsage_Dig_ReportRate 0xa1 +#define HidUsage_Dig_TransducerConnected 0xa2 +#define HidUsage_Dig_SwitchDisabled 0xa3 +#define HidUsage_Dig_SwitchUnimplemented 0xa4 +#define HidUsage_Dig_TransducerSwitches 0xa5 +#define HidUsage_Dig_TransducerIndexSelector 0xa6 +#define HidUsage_Dig_ButtonPressThreshold 0xb0 +#define HidUsage_Hap_SimpleHapticController 0x01 +#define HidUsage_Hap_WaveformList 0x10 +#define HidUsage_Hap_DurationList 0x11 +#define HidUsage_Hap_AutoTrigger 0x20 +#define HidUsage_Hap_ManualTrigger 0x21 +#define HidUsage_Hap_AutoTriggerAssociatedControl 0x22 +#define HidUsage_Hap_Intensity 0x23 +#define HidUsage_Hap_RepeatCount 0x24 +#define HidUsage_Hap_RetriggerPeriod 0x25 +#define HidUsage_Hap_WaveformVendorPage 0x26 +#define HidUsage_Hap_WaveformVendorID 0x27 +#define HidUsage_Hap_WaveformCutoffTime 0x28 +#define HidUsage_Hap_WaveformNone 0x1001 +#define HidUsage_Hap_WaveformStop 0x1002 +#define HidUsage_Hap_WaveformClick 0x1003 +#define HidUsage_Hap_WaveformBuzzContinuous 0x1004 +#define HidUsage_Hap_WaveformRumbleContinuous 0x1005 +#define HidUsage_Hap_WaveformPress 0x1006 +#define HidUsage_Hap_WaveformRelease 0x1007 +#define HidUsage_Hap_WaveformHover 0x1008 +#define HidUsage_Hap_WaveformSuccess 0x1009 +#define HidUsage_Hap_WaveformError 0x100a +#define HidUsage_Hap_WaveformInkContinuous 0x100b +#define HidUsage_Hap_WaveformPencilContinuous 0x100c +#define HidUsage_Hap_WaveformMarkerContinuous 0x100d +#define HidUsage_Hap_WaveformChiselMarkerContinuous 0x100e +#define HidUsage_Hap_WaveformBrushContinuous 0x100f +#define HidUsage_Hap_WaveformEraserContinuous 0x1010 +#define HidUsage_Hap_WaveformSparkleContinuous 0x1011 +#define HidUsage_PID_PhysicalInputDevice 0x01 +#define HidUsage_PID_Normal 0x20 +#define HidUsage_PID_SetEffectReport 0x21 +#define HidUsage_PID_EffectParameterBlockIndex 0x22 +#define HidUsage_PID_ParameterBlockOffset 0x23 +#define HidUsage_PID_ROMFlag 0x24 +#define HidUsage_PID_EffectType 0x25 +#define HidUsage_PID_ETConstantForce 0x26 +#define HidUsage_PID_ETRamp 0x27 +#define HidUsage_PID_ETCustomForce 0x28 +#define HidUsage_PID_ETSquare 0x30 +#define HidUsage_PID_ETSine 0x31 +#define HidUsage_PID_ETTriangle 0x32 +#define HidUsage_PID_ETSawtoothUp 0x33 +#define HidUsage_PID_ETSawtoothDown 0x34 +#define HidUsage_PID_ETSpring 0x40 +#define HidUsage_PID_ETDamper 0x41 +#define HidUsage_PID_ETInertia 0x42 +#define HidUsage_PID_ETFriction 0x43 +#define HidUsage_PID_Duration 0x50 +#define HidUsage_PID_SamplePeriod 0x51 +#define HidUsage_PID_Gain 0x52 +#define HidUsage_PID_TriggerButton 0x53 +#define HidUsage_PID_TriggerRepeatInterval 0x54 +#define HidUsage_PID_AxesEnable 0x55 +#define HidUsage_PID_DirectionEnable 0x56 +#define HidUsage_PID_Direction 0x57 +#define HidUsage_PID_TypeSpecificBlockOffset 0x58 +#define HidUsage_PID_BlockType 0x59 +#define HidUsage_PID_SetEnvelopeReport 0x5a +#define HidUsage_PID_AttackLevel 0x5b +#define HidUsage_PID_AttackTime 0x5c +#define HidUsage_PID_FadeLevel 0x5d +#define HidUsage_PID_FadeTime 0x5e +#define HidUsage_PID_SetConditionReport 0x5f +#define HidUsage_PID_CenterPointOffset 0x60 +#define HidUsage_PID_PositiveCoefficient 0x61 +#define HidUsage_PID_NegativeCoefficient 0x62 +#define HidUsage_PID_PositiveSaturation 0x63 +#define HidUsage_PID_NegativeSaturation 0x64 +#define HidUsage_PID_DeadBand 0x65 +#define HidUsage_PID_DownloadForceSample 0x66 +#define HidUsage_PID_IsochCustomForceEnable 0x67 +#define HidUsage_PID_CustomForceDataReport 0x68 +#define HidUsage_PID_CustomForceData 0x69 +#define HidUsage_PID_CustomForceVendorDefinedData 0x6a +#define HidUsage_PID_SetCustomForceReport 0x6b +#define HidUsage_PID_CustomForceDataOffset 0x6c +#define HidUsage_PID_SampleCount 0x6d +#define HidUsage_PID_SetPeriodicReport 0x6e +#define HidUsage_PID_Offset 0x6f +#define HidUsage_PID_Magnitude 0x70 +#define HidUsage_PID_Phase 0x71 +#define HidUsage_PID_Period 0x72 +#define HidUsage_PID_SetConstantForceReport 0x73 +#define HidUsage_PID_SetRampForceReport 0x74 +#define HidUsage_PID_RampStart 0x75 +#define HidUsage_PID_RampEnd 0x76 +#define HidUsage_PID_EffectOperationReport 0x77 +#define HidUsage_PID_EffectOperation 0x78 +#define HidUsage_PID_OpEffectStart 0x79 +#define HidUsage_PID_OpEffectStartSolo 0x7a +#define HidUsage_PID_OpEffectStop 0x7b +#define HidUsage_PID_LoopCount 0x7c +#define HidUsage_PID_DeviceGainReport 0x7d +#define HidUsage_PID_DeviceGain 0x7e +#define HidUsage_PID_ParameterBlockPoolsReport 0x7f +#define HidUsage_PID_RAMPoolSize 0x80 +#define HidUsage_PID_ROMPoolSize 0x81 +#define HidUsage_PID_ROMEffectBlockCount 0x82 +#define HidUsage_PID_SimultaneousEffectsMax 0x83 +#define HidUsage_PID_PoolAlignment 0x84 +#define HidUsage_PID_ParameterBlockMoveReport 0x85 +#define HidUsage_PID_MoveSource 0x86 +#define HidUsage_PID_MoveDestination 0x87 +#define HidUsage_PID_MoveLength 0x88 +#define HidUsage_PID_EffectParameterBlockLoadReport 0x89 +#define HidUsage_PID_EffectParameterBlockLoadStatus 0x8b +#define HidUsage_PID_BlockLoadSuccess 0x8c +#define HidUsage_PID_BlockLoadFull 0x8d +#define HidUsage_PID_BlockLoadError 0x8e +#define HidUsage_PID_BlockHandle 0x8f +#define HidUsage_PID_EffectParameterBlockFreeReport 0x90 +#define HidUsage_PID_TypeSpecificBlockHandle 0x91 +#define HidUsage_PID_PIDStateReport 0x92 +#define HidUsage_PID_EffectPlaying 0x94 +#define HidUsage_PID_PIDDeviceControlReport 0x95 +#define HidUsage_PID_PIDDeviceControl 0x96 +#define HidUsage_PID_DCEnableActuators 0x97 +#define HidUsage_PID_DCDisableActuators 0x98 +#define HidUsage_PID_DCStopAllEffects 0x99 +#define HidUsage_PID_DCReset 0x9a +#define HidUsage_PID_DCPause 0x9b +#define HidUsage_PID_DCContinue 0x9c +#define HidUsage_PID_DevicePaused 0x9f +#define HidUsage_PID_ActuatorsEnabled 0xa0 +#define HidUsage_PID_SafetySwitch 0xa4 +#define HidUsage_PID_ActuatorOverrideSwitch 0xa5 +#define HidUsage_PID_ActuatorPower 0xa6 +#define HidUsage_PID_StartDelay 0xa7 +#define HidUsage_PID_ParameterBlockSize 0xa8 +#define HidUsage_PID_DeviceManagedPool 0xa9 +#define HidUsage_PID_SharedParameterBlocks 0xaa +#define HidUsage_PID_CreateNewEffectParameterBlockReport 0xab +#define HidUsage_PID_RAMPoolAvailable 0xac +#define HidUsage_SC_SocControl 0x01 +#define HidUsage_SC_FirmwareTransfer 0x02 +#define HidUsage_SC_FirmwareFileId 0x03 +#define HidUsage_SC_FileOffsetInBytes 0x04 +#define HidUsage_SC_FileTransferSizeMaxInBytes 0x05 +#define HidUsage_SC_FilePayload 0x06 +#define HidUsage_SC_FilePayloadSizeInBytes 0x07 +#define HidUsage_SC_FilePayloadContainsLastBytes 0x08 +#define HidUsage_SC_FileTransferStop 0x09 +#define HidUsage_SC_FileTransferTillEnd 0x0a +#define HidUsage_EHT_EyeTracker 0x01 +#define HidUsage_EHT_HeadTracker 0x02 +#define HidUsage_EHT_TrackingData 0x10 +#define HidUsage_EHT_Capabilities 0x11 +#define HidUsage_EHT_Configuration 0x12 +#define HidUsage_EHT_Status 0x13 +#define HidUsage_EHT_Control 0x14 +#define HidUsage_EHT_SensorTimestamp 0x20 +#define HidUsage_EHT_PositionX 0x21 +#define HidUsage_EHT_PositionY 0x22 +#define HidUsage_EHT_PositionZ 0x23 +#define HidUsage_EHT_GazePoint 0x24 +#define HidUsage_EHT_LeftEyePosition 0x25 +#define HidUsage_EHT_RightEyePosition 0x26 +#define HidUsage_EHT_HeadPosition 0x27 +#define HidUsage_EHT_HeadDirectionPoint 0x28 +#define HidUsage_EHT_RotationaboutXaxis 0x29 +#define HidUsage_EHT_RotationaboutYaxis 0x2a +#define HidUsage_EHT_RotationaboutZaxis 0x2b +#define HidUsage_EHT_TrackerQuality 0x100 +#define HidUsage_EHT_MinimumTrackingDistance 0x101 +#define HidUsage_EHT_OptimumTrackingDistance 0x102 +#define HidUsage_EHT_MaximumTrackingDistance 0x103 +#define HidUsage_EHT_MaximumScreenPlaneWidth 0x104 +#define HidUsage_EHT_MaximumScreenPlaneHeight 0x105 +#define HidUsage_EHT_DisplayManufacturerID 0x200 +#define HidUsage_EHT_DisplayProductID 0x201 +#define HidUsage_EHT_DisplaySerialNumber 0x202 +#define HidUsage_EHT_DisplayManufacturerDate 0x203 +#define HidUsage_EHT_CalibratedScreenWidth 0x204 +#define HidUsage_EHT_CalibratedScreenHeight 0x205 +#define HidUsage_EHT_SamplingFrequency 0x300 +#define HidUsage_EHT_ConfigurationStatus 0x301 +#define HidUsage_EHT_DeviceModeRequest 0x400 +#define HidUsage_AD_AlphanumericDisplay 0x01 +#define HidUsage_AD_AuxiliaryDisplay 0x02 +#define HidUsage_AD_DisplayAttributesReport 0x20 +#define HidUsage_AD_ASCIICharacterSet 0x21 +#define HidUsage_AD_DataReadBack 0x22 +#define HidUsage_AD_FontReadBack 0x23 +#define HidUsage_AD_DisplayControlReport 0x24 +#define HidUsage_AD_ClearDisplay 0x25 +#define HidUsage_AD_DisplayEnable 0x26 +#define HidUsage_AD_ScreenSaverDelay 0x27 +#define HidUsage_AD_ScreenSaverEnable 0x28 +#define HidUsage_AD_VerticalScroll 0x29 +#define HidUsage_AD_HorizontalScroll 0x2a +#define HidUsage_AD_CharacterReport 0x2b +#define HidUsage_AD_DisplayData 0x2c +#define HidUsage_AD_DisplayStatus 0x2d +#define HidUsage_AD_StatNotReady 0x2e +#define HidUsage_AD_StatReady 0x2f +#define HidUsage_AD_ErrNotaloadablecharacter 0x30 +#define HidUsage_AD_ErrFontdatacannotberead 0x31 +#define HidUsage_AD_CursorPositionReport 0x32 +#define HidUsage_AD_Row 0x33 +#define HidUsage_AD_Column 0x34 +#define HidUsage_AD_Rows 0x35 +#define HidUsage_AD_Columns 0x36 +#define HidUsage_AD_CursorPixelPositioning 0x37 +#define HidUsage_AD_CursorMode 0x38 +#define HidUsage_AD_CursorEnable 0x39 +#define HidUsage_AD_CursorBlink 0x3a +#define HidUsage_AD_FontReport 0x3b +#define HidUsage_AD_FontData 0x3c +#define HidUsage_AD_CharacterWidth 0x3d +#define HidUsage_AD_CharacterHeight 0x3e +#define HidUsage_AD_CharacterSpacingHorizontal 0x3f +#define HidUsage_AD_CharacterSpacingVertical 0x40 +#define HidUsage_AD_UnicodeCharacterSet 0x41 +#define HidUsage_AD_Font7Segment 0x42 +#define HidUsage_AD_SevenSegmentDirectMap 0x43 +#define HidUsage_AD_Font14Segment 0x44 +#define HidUsage_AD_One4SegmentDirectMap 0x45 +#define HidUsage_AD_DisplayBrightness 0x46 +#define HidUsage_AD_DisplayContrast 0x47 +#define HidUsage_AD_CharacterAttribute 0x48 +#define HidUsage_AD_AttributeReadback 0x49 +#define HidUsage_AD_AttributeData 0x4a +#define HidUsage_AD_CharAttrEnhance 0x4b +#define HidUsage_AD_CharAttrUnderline 0x4c +#define HidUsage_AD_CharAttrBlink 0x4d +#define HidUsage_AD_BitmapSizeX 0x80 +#define HidUsage_AD_BitmapSizeY 0x81 +#define HidUsage_AD_MaxBlitSize 0x82 +#define HidUsage_AD_BitDepthFormat 0x83 +#define HidUsage_AD_DisplayOrientation 0x84 +#define HidUsage_AD_PaletteReport 0x85 +#define HidUsage_AD_PaletteDataSize 0x86 +#define HidUsage_AD_PaletteDataOffset 0x87 +#define HidUsage_AD_PaletteData 0x88 +#define HidUsage_AD_BlitReport 0x8a +#define HidUsage_AD_BlitRectangleX1 0x8b +#define HidUsage_AD_BlitRectangleY1 0x8c +#define HidUsage_AD_BlitRectangleX2 0x8d +#define HidUsage_AD_BlitRectangleY2 0x8e +#define HidUsage_AD_BlitData 0x8f +#define HidUsage_AD_SoftButton 0x90 +#define HidUsage_AD_SoftButtonID 0x91 +#define HidUsage_AD_SoftButtonSide 0x92 +#define HidUsage_AD_SoftButtonOffset1 0x93 +#define HidUsage_AD_SoftButtonOffset2 0x94 +#define HidUsage_AD_SoftButtonReport 0x95 +#define HidUsage_AD_SoftKeys 0xc2 +#define HidUsage_AD_DisplayDataExtensions 0xcc +#define HidUsage_AD_CharacterMapping 0xcf +#define HidUsage_AD_UnicodeEquivalent 0xdd +#define HidUsage_AD_CharacterPageMapping 0xdf +#define HidUsage_AD_RequestReport 0xff +#define HidUsage_Sen_Sensor 0x01 +#define HidUsage_Sen_Biometric 0x10 +#define HidUsage_Sen_BiometricHumanPresence 0x11 +#define HidUsage_Sen_BiometricHumanProximity 0x12 +#define HidUsage_Sen_BiometricHumanTouch 0x13 +#define HidUsage_Sen_BiometricBloodPressure 0x14 +#define HidUsage_Sen_BiometricBodyTemperature 0x15 +#define HidUsage_Sen_BiometricHeartRate 0x16 +#define HidUsage_Sen_BiometricHeartRateVariability 0x17 +#define HidUsage_Sen_BiometricPeripheralOxygenSaturation 0x18 +#define HidUsage_Sen_BiometricRespiratoryRate 0x19 +#define HidUsage_Sen_Electrical 0x20 +#define HidUsage_Sen_ElectricalCapacitance 0x21 +#define HidUsage_Sen_ElectricalCurrent 0x22 +#define HidUsage_Sen_ElectricalPower 0x23 +#define HidUsage_Sen_ElectricalInductance 0x24 +#define HidUsage_Sen_ElectricalResistance 0x25 +#define HidUsage_Sen_ElectricalVoltage 0x26 +#define HidUsage_Sen_ElectricalPotentiometer 0x27 +#define HidUsage_Sen_ElectricalFrequency 0x28 +#define HidUsage_Sen_ElectricalPeriod 0x29 +#define HidUsage_Sen_Environmental 0x30 +#define HidUsage_Sen_EnvironmentalAtmosphericPressure 0x31 +#define HidUsage_Sen_EnvironmentalHumidity 0x32 +#define HidUsage_Sen_EnvironmentalTemperature 0x33 +#define HidUsage_Sen_EnvironmentalWindDirection 0x34 +#define HidUsage_Sen_EnvironmentalWindSpeed 0x35 +#define HidUsage_Sen_EnvironmentalAirQuality 0x36 +#define HidUsage_Sen_EnvironmentalHeatIndex 0x37 +#define HidUsage_Sen_EnvironmentalSurfaceTemperature 0x38 +#define HidUsage_Sen_EnvironmentalVolatileOrganicCompounds 0x39 +#define HidUsage_Sen_EnvironmentalObjectPresence 0x3a +#define HidUsage_Sen_EnvironmentalObjectProximity 0x3b +#define HidUsage_Sen_Light 0x40 +#define HidUsage_Sen_LightAmbientLight 0x41 +#define HidUsage_Sen_LightConsumerInfrared 0x42 +#define HidUsage_Sen_LightInfraredLight 0x43 +#define HidUsage_Sen_LightVisibleLight 0x44 +#define HidUsage_Sen_LightUltravioletLight 0x45 +#define HidUsage_Sen_Location 0x50 +#define HidUsage_Sen_LocationBroadcast 0x51 +#define HidUsage_Sen_LocationDeadReckoning 0x52 +#define HidUsage_Sen_LocationGPSGlobalPositioningSystem 0x53 +#define HidUsage_Sen_LocationLookup 0x54 +#define HidUsage_Sen_LocationOther 0x55 +#define HidUsage_Sen_LocationStatic 0x56 +#define HidUsage_Sen_LocationTriangulation 0x57 +#define HidUsage_Sen_Mechanical 0x60 +#define HidUsage_Sen_MechanicalBooleanSwitch 0x61 +#define HidUsage_Sen_MechanicalBooleanSwitchArray 0x62 +#define HidUsage_Sen_MechanicalMultivalueSwitch 0x63 +#define HidUsage_Sen_MechanicalForce 0x64 +#define HidUsage_Sen_MechanicalPressure 0x65 +#define HidUsage_Sen_MechanicalStrain 0x66 +#define HidUsage_Sen_MechanicalWeight 0x67 +#define HidUsage_Sen_MechanicalHapticVibrator 0x68 +#define HidUsage_Sen_MechanicalHallEffectSwitch 0x69 +#define HidUsage_Sen_Motion 0x70 +#define HidUsage_Sen_MotionAccelerometer1D 0x71 +#define HidUsage_Sen_MotionAccelerometer2D 0x72 +#define HidUsage_Sen_MotionAccelerometer3D 0x73 +#define HidUsage_Sen_MotionGyrometer1D 0x74 +#define HidUsage_Sen_MotionGyrometer2D 0x75 +#define HidUsage_Sen_MotionGyrometer3D 0x76 +#define HidUsage_Sen_MotionMotionDetector 0x77 +#define HidUsage_Sen_MotionSpeedometer 0x78 +#define HidUsage_Sen_MotionAccelerometer 0x79 +#define HidUsage_Sen_MotionGyrometer 0x7a +#define HidUsage_Sen_MotionGravityVector 0x7b +#define HidUsage_Sen_MotionLinearAccelerometer 0x7c +#define HidUsage_Sen_Orientation 0x80 +#define HidUsage_Sen_OrientationCompass1D 0x81 +#define HidUsage_Sen_OrientationCompass2D 0x82 +#define HidUsage_Sen_OrientationCompass3D 0x83 +#define HidUsage_Sen_OrientationInclinometer1D 0x84 +#define HidUsage_Sen_OrientationInclinometer2D 0x85 +#define HidUsage_Sen_OrientationInclinometer3D 0x86 +#define HidUsage_Sen_OrientationDistance1D 0x87 +#define HidUsage_Sen_OrientationDistance2D 0x88 +#define HidUsage_Sen_OrientationDistance3D 0x89 +#define HidUsage_Sen_OrientationDeviceOrientation 0x8a +#define HidUsage_Sen_OrientationCompass 0x8b +#define HidUsage_Sen_OrientationInclinometer 0x8c +#define HidUsage_Sen_OrientationDistance 0x8d +#define HidUsage_Sen_OrientationRelativeOrientation 0x8e +#define HidUsage_Sen_OrientationSimpleOrientation 0x8f +#define HidUsage_Sen_Scanner 0x90 +#define HidUsage_Sen_ScannerBarcode 0x91 +#define HidUsage_Sen_ScannerRFID 0x92 +#define HidUsage_Sen_ScannerNFC 0x93 +#define HidUsage_Sen_Time 0xa0 +#define HidUsage_Sen_TimeAlarmTimer 0xa1 +#define HidUsage_Sen_TimeRealTimeClock 0xa2 +#define HidUsage_Sen_PersonalActivity 0xb0 +#define HidUsage_Sen_PersonalActivityActivityDetection 0xb1 +#define HidUsage_Sen_PersonalActivityDevicePosition 0xb2 +#define HidUsage_Sen_PersonalActivityFloorTracker 0xb3 +#define HidUsage_Sen_PersonalActivityPedometer 0xb4 +#define HidUsage_Sen_PersonalActivityStepDetection 0xb5 +#define HidUsage_Sen_OrientationExtended 0xc0 +#define HidUsage_Sen_OrientationExtendedGeomagneticOrientation 0xc1 +#define HidUsage_Sen_OrientationExtendedMagnetometer 0xc2 +#define HidUsage_Sen_Gesture 0xd0 +#define HidUsage_Sen_GestureChassisFlipGesture 0xd1 +#define HidUsage_Sen_GestureHingeFoldGesture 0xd2 +#define HidUsage_Sen_Other 0xe0 +#define HidUsage_Sen_OtherCustom 0xe1 +#define HidUsage_Sen_OtherGeneric 0xe2 +#define HidUsage_Sen_OtherGenericEnumerator 0xe3 +#define HidUsage_Sen_OtherHingeAngle 0xe4 +#define HidUsage_Sen_VendorReserved1 0xf0 +#define HidUsage_Sen_VendorReserved2 0xf1 +#define HidUsage_Sen_VendorReserved3 0xf2 +#define HidUsage_Sen_VendorReserved4 0xf3 +#define HidUsage_Sen_VendorReserved5 0xf4 +#define HidUsage_Sen_VendorReserved6 0xf5 +#define HidUsage_Sen_VendorReserved7 0xf6 +#define HidUsage_Sen_VendorReserved8 0xf7 +#define HidUsage_Sen_VendorReserved9 0xf8 +#define HidUsage_Sen_VendorReserved10 0xf9 +#define HidUsage_Sen_VendorReserved11 0xfa +#define HidUsage_Sen_VendorReserved12 0xfb +#define HidUsage_Sen_VendorReserved13 0xfc +#define HidUsage_Sen_VendorReserved14 0xfd +#define HidUsage_Sen_VendorReserved15 0xfe +#define HidUsage_Sen_VendorReserved16 0xff +#define HidUsage_Sen_Event 0x200 +#define HidUsage_Sen_EventSensorState 0x201 +#define HidUsage_Sen_EventSensorEvent 0x202 +#define HidUsage_Sen_Property 0x300 +#define HidUsage_Sen_PropertyFriendlyName 0x301 +#define HidUsage_Sen_PropertyPersistentUniqueID 0x302 +#define HidUsage_Sen_PropertySensorStatus 0x303 +#define HidUsage_Sen_PropertyMinimumReportInterval 0x304 +#define HidUsage_Sen_PropertySensorManufacturer 0x305 +#define HidUsage_Sen_PropertySensorModel 0x306 +#define HidUsage_Sen_PropertySensorSerialNumber 0x307 +#define HidUsage_Sen_PropertySensorDescription 0x308 +#define HidUsage_Sen_PropertySensorConnectionType 0x309 +#define HidUsage_Sen_PropertySensorDevicePath 0x30a +#define HidUsage_Sen_PropertyHardwareRevision 0x30b +#define HidUsage_Sen_PropertyFirmwareVersion 0x30c +#define HidUsage_Sen_PropertyReleaseDate 0x30d +#define HidUsage_Sen_PropertyReportInterval 0x30e +#define HidUsage_Sen_PropertyChangeSensitivityAbsolute 0x30f +#define HidUsage_Sen_PropertyChangeSensitivityPercentofRange 0x310 +#define HidUsage_Sen_PropertyChangeSensitivityPercentRelative 0x311 +#define HidUsage_Sen_PropertyAccuracy 0x312 +#define HidUsage_Sen_PropertyResolution 0x313 +#define HidUsage_Sen_PropertyMaximum 0x314 +#define HidUsage_Sen_PropertyMinimum 0x315 +#define HidUsage_Sen_PropertyReportingState 0x316 +#define HidUsage_Sen_PropertySamplingRate 0x317 +#define HidUsage_Sen_PropertyResponseCurve 0x318 +#define HidUsage_Sen_PropertyPowerState 0x319 +#define HidUsage_Sen_PropertyMaximumFIFOEvents 0x31a +#define HidUsage_Sen_PropertyReportLatency 0x31b +#define HidUsage_Sen_PropertyFlushFIFOEvents 0x31c +#define HidUsage_Sen_PropertyMaximumPowerConsumption 0x31d +#define HidUsage_Sen_PropertyIsPrimary 0x31e +#define HidUsage_Sen_PropertyHumanPresenceDetectionType 0x31f +#define HidUsage_Sen_DataFieldLocation 0x400 +#define HidUsage_Sen_DataFieldAltitudeAntennaSeaLevel 0x402 +#define HidUsage_Sen_DataFieldDifferentialReferenceStationID 0x403 +#define HidUsage_Sen_DataFieldAltitudeEllipsoidError 0x404 +#define HidUsage_Sen_DataFieldAltitudeEllipsoid 0x405 +#define HidUsage_Sen_DataFieldAltitudeSeaLevelError 0x406 +#define HidUsage_Sen_DataFieldAltitudeSeaLevel 0x407 +#define HidUsage_Sen_DataFieldDifferentialGPSDataAge 0x408 +#define HidUsage_Sen_DataFieldErrorRadius 0x409 +#define HidUsage_Sen_DataFieldFixQuality 0x40a +#define HidUsage_Sen_DataFieldFixType 0x40b +#define HidUsage_Sen_DataFieldGeoidalSeparation 0x40c +#define HidUsage_Sen_DataFieldGPSOperationMode 0x40d +#define HidUsage_Sen_DataFieldGPSSelectionMode 0x40e +#define HidUsage_Sen_DataFieldGPSStatus 0x40f +#define HidUsage_Sen_DataFieldPositionDilutionofPrecision 0x410 +#define HidUsage_Sen_DataFieldHorizontalDilutionofPrecision 0x411 +#define HidUsage_Sen_DataFieldVerticalDilutionofPrecision 0x412 +#define HidUsage_Sen_DataFieldLatitude 0x413 +#define HidUsage_Sen_DataFieldLongitude 0x414 +#define HidUsage_Sen_DataFieldTrueHeading 0x415 +#define HidUsage_Sen_DataFieldMagneticHeading 0x416 +#define HidUsage_Sen_DataFieldMagneticVariation 0x417 +#define HidUsage_Sen_DataFieldSpeed 0x418 +#define HidUsage_Sen_DataFieldSatellitesinView 0x419 +#define HidUsage_Sen_DataFieldSatellitesinViewAzimuth 0x41a +#define HidUsage_Sen_DataFieldSatellitesinViewElevation 0x41b +#define HidUsage_Sen_DataFieldSatellitesinViewIDs 0x41c +#define HidUsage_Sen_DataFieldSatellitesinViewPRNs 0x41d +#define HidUsage_Sen_DataFieldSatellitesinViewSNRatios 0x41e +#define HidUsage_Sen_DataFieldSatellitesUsedCount 0x41f +#define HidUsage_Sen_DataFieldSatellitesUsedPRNs 0x420 +#define HidUsage_Sen_DataFieldNMEASentence 0x421 +#define HidUsage_Sen_DataFieldAddressLine1 0x422 +#define HidUsage_Sen_DataFieldAddressLine2 0x423 +#define HidUsage_Sen_DataFieldCity 0x424 +#define HidUsage_Sen_DataFieldStateorProvince 0x425 +#define HidUsage_Sen_DataFieldCountryorRegion 0x426 +#define HidUsage_Sen_DataFieldPostalCode 0x427 +#define HidUsage_Sen_PropertyLocation 0x42a +#define HidUsage_Sen_PropertyLocationDesiredAccuracy 0x42b +#define HidUsage_Sen_DataFieldEnvironmental 0x430 +#define HidUsage_Sen_DataFieldAtmosphericPressure 0x431 +#define HidUsage_Sen_DataFieldRelativeHumidity 0x433 +#define HidUsage_Sen_DataFieldTemperature 0x434 +#define HidUsage_Sen_DataFieldWindDirection 0x435 +#define HidUsage_Sen_DataFieldWindSpeed 0x436 +#define HidUsage_Sen_DataFieldAirQualityIndex 0x437 +#define HidUsage_Sen_DataFieldEquivalentCO2 0x438 +#define HidUsage_Sen_DataFieldVolatileOrganicCompoundConcentration 0x439 +#define HidUsage_Sen_DataFieldObjectPresence 0x43a +#define HidUsage_Sen_DataFieldObjectProximityRange 0x43b +#define HidUsage_Sen_DataFieldObjectProximityOutofRange 0x43c +#define HidUsage_Sen_PropertyEnvironmental 0x440 +#define HidUsage_Sen_PropertyReferencePressure 0x441 +#define HidUsage_Sen_DataFieldMotion 0x450 +#define HidUsage_Sen_DataFieldMotionState 0x451 +#define HidUsage_Sen_DataFieldAcceleration 0x452 +#define HidUsage_Sen_DataFieldAccelerationAxisX 0x453 +#define HidUsage_Sen_DataFieldAccelerationAxisY 0x454 +#define HidUsage_Sen_DataFieldAccelerationAxisZ 0x455 +#define HidUsage_Sen_DataFieldAngularVelocity 0x456 +#define HidUsage_Sen_DataFieldAngularVelocityaboutXAxis 0x457 +#define HidUsage_Sen_DataFieldAngularVelocityaboutYAxis 0x458 +#define HidUsage_Sen_DataFieldAngularVelocityaboutZAxis 0x459 +#define HidUsage_Sen_DataFieldAngularPosition 0x45a +#define HidUsage_Sen_DataFieldAngularPositionaboutXAxis 0x45b +#define HidUsage_Sen_DataFieldAngularPositionaboutYAxis 0x45c +#define HidUsage_Sen_DataFieldAngularPositionaboutZAxis 0x45d +#define HidUsage_Sen_DataFieldMotionSpeed 0x45e +#define HidUsage_Sen_DataFieldMotionIntensity 0x45f +#define HidUsage_Sen_DataFieldOrientation 0x470 +#define HidUsage_Sen_DataFieldHeading 0x471 +#define HidUsage_Sen_DataFieldHeadingXAxis 0x472 +#define HidUsage_Sen_DataFieldHeadingYAxis 0x473 +#define HidUsage_Sen_DataFieldHeadingZAxis 0x474 +#define HidUsage_Sen_DataFieldHeadingCompensatedMagneticNorth 0x475 +#define HidUsage_Sen_DataFieldHeadingCompensatedTrueNorth 0x476 +#define HidUsage_Sen_DataFieldHeadingMagneticNorth 0x477 +#define HidUsage_Sen_DataFieldHeadingTrueNorth 0x478 +#define HidUsage_Sen_DataFieldDistance 0x479 +#define HidUsage_Sen_DataFieldDistanceXAxis 0x47a +#define HidUsage_Sen_DataFieldDistanceYAxis 0x47b +#define HidUsage_Sen_DataFieldDistanceZAxis 0x47c +#define HidUsage_Sen_DataFieldDistanceOutofRange 0x47d +#define HidUsage_Sen_DataFieldTilt 0x47e +#define HidUsage_Sen_DataFieldTiltXAxis 0x47f +#define HidUsage_Sen_DataFieldTiltYAxis 0x480 +#define HidUsage_Sen_DataFieldTiltZAxis 0x481 +#define HidUsage_Sen_DataFieldRotationMatrix 0x482 +#define HidUsage_Sen_DataFieldQuaternion 0x483 +#define HidUsage_Sen_DataFieldMagneticFlux 0x484 +#define HidUsage_Sen_DataFieldMagneticFluxXAxis 0x485 +#define HidUsage_Sen_DataFieldMagneticFluxYAxis 0x486 +#define HidUsage_Sen_DataFieldMagneticFluxZAxis 0x487 +#define HidUsage_Sen_DataFieldMagnetometerAccuracy 0x488 +#define HidUsage_Sen_DataFieldSimpleOrientationDirection 0x489 +#define HidUsage_Sen_DataFieldMechanical 0x490 +#define HidUsage_Sen_DataFieldBooleanSwitchState 0x491 +#define HidUsage_Sen_DataFieldBooleanSwitchArrayStates 0x492 +#define HidUsage_Sen_DataFieldMultivalueSwitchValue 0x493 +#define HidUsage_Sen_DataFieldForce 0x494 +#define HidUsage_Sen_DataFieldAbsolutePressure 0x495 +#define HidUsage_Sen_DataFieldGaugePressure 0x496 +#define HidUsage_Sen_DataFieldStrain 0x497 +#define HidUsage_Sen_DataFieldWeight 0x498 +#define HidUsage_Sen_PropertyMechanical 0x4a0 +#define HidUsage_Sen_PropertyVibrationState 0x4a1 +#define HidUsage_Sen_PropertyForwardVibrationSpeed 0x4a2 +#define HidUsage_Sen_PropertyBackwardVibrationSpeed 0x4a3 +#define HidUsage_Sen_DataFieldBiometric 0x4b0 +#define HidUsage_Sen_DataFieldHumanPresence 0x4b1 +#define HidUsage_Sen_DataFieldHumanProximityRange 0x4b2 +#define HidUsage_Sen_DataFieldHumanProximityOutofRange 0x4b3 +#define HidUsage_Sen_DataFieldHumanTouchState 0x4b4 +#define HidUsage_Sen_DataFieldBloodPressure 0x4b5 +#define HidUsage_Sen_DataFieldBloodPressureDiastolic 0x4b6 +#define HidUsage_Sen_DataFieldBloodPressureSystolic 0x4b7 +#define HidUsage_Sen_DataFieldHeartRate 0x4b8 +#define HidUsage_Sen_DataFieldRestingHeartRate 0x4b9 +#define HidUsage_Sen_DataFieldHeartbeatInterval 0x4ba +#define HidUsage_Sen_DataFieldRespiratoryRate 0x4bb +#define HidUsage_Sen_DataFieldSpO2 0x4bc +#define HidUsage_Sen_DataFieldHumanAttentionDetected 0x4bd +#define HidUsage_Sen_DataFieldHumanHeadAzimuth 0x4be +#define HidUsage_Sen_DataFieldHumanHeadAltitude 0x4bf +#define HidUsage_Sen_DataFieldHumanHeadRoll 0x4c0 +#define HidUsage_Sen_DataFieldHumanHeadPitch 0x4c1 +#define HidUsage_Sen_DataFieldHumanHeadYaw 0x4c2 +#define HidUsage_Sen_DataFieldHumanCorrelationId 0x4c3 +#define HidUsage_Sen_DataFieldLight 0x4d0 +#define HidUsage_Sen_DataFieldIlluminance 0x4d1 +#define HidUsage_Sen_DataFieldColorTemperature 0x4d2 +#define HidUsage_Sen_DataFieldChromaticity 0x4d3 +#define HidUsage_Sen_DataFieldChromaticityX 0x4d4 +#define HidUsage_Sen_DataFieldChromaticityY 0x4d5 +#define HidUsage_Sen_DataFieldConsumerIRSentenceReceive 0x4d6 +#define HidUsage_Sen_DataFieldInfraredLight 0x4d7 +#define HidUsage_Sen_DataFieldRedLight 0x4d8 +#define HidUsage_Sen_DataFieldGreenLight 0x4d9 +#define HidUsage_Sen_DataFieldBlueLight 0x4da +#define HidUsage_Sen_DataFieldUltravioletALight 0x4db +#define HidUsage_Sen_DataFieldUltravioletBLight 0x4dc +#define HidUsage_Sen_DataFieldUltravioletIndex 0x4dd +#define HidUsage_Sen_DataFieldNearInfraredLight 0x4de +#define HidUsage_Sen_PropertyLight 0x4df +#define HidUsage_Sen_PropertyConsumerIRSentenceSend 0x4e0 +#define HidUsage_Sen_PropertyAutoBrightnessPreferred 0x4e2 +#define HidUsage_Sen_PropertyAutoColorPreferred 0x4e3 +#define HidUsage_Sen_DataFieldScanner 0x4f0 +#define HidUsage_Sen_DataFieldRFIDTag40Bit 0x4f1 +#define HidUsage_Sen_DataFieldNFCSentenceReceive 0x4f2 +#define HidUsage_Sen_PropertyScanner 0x4f8 +#define HidUsage_Sen_PropertyNFCSentenceSend 0x4f9 +#define HidUsage_Sen_DataFieldElectrical 0x500 +#define HidUsage_Sen_DataFieldCapacitance 0x501 +#define HidUsage_Sen_DataFieldCurrent 0x502 +#define HidUsage_Sen_DataFieldElectricalPower 0x503 +#define HidUsage_Sen_DataFieldInductance 0x504 +#define HidUsage_Sen_DataFieldResistance 0x505 +#define HidUsage_Sen_DataFieldVoltage 0x506 +#define HidUsage_Sen_DataFieldFrequency 0x507 +#define HidUsage_Sen_DataFieldPeriod 0x508 +#define HidUsage_Sen_DataFieldPercentofRange 0x509 +#define HidUsage_Sen_DataFieldTime 0x520 +#define HidUsage_Sen_DataFieldYear 0x521 +#define HidUsage_Sen_DataFieldMonth 0x522 +#define HidUsage_Sen_DataFieldDay 0x523 +#define HidUsage_Sen_DataFieldDayofWeek 0x524 +#define HidUsage_Sen_DataFieldHour 0x525 +#define HidUsage_Sen_DataFieldMinute 0x526 +#define HidUsage_Sen_DataFieldSecond 0x527 +#define HidUsage_Sen_DataFieldMillisecond 0x528 +#define HidUsage_Sen_DataFieldTimestamp 0x529 +#define HidUsage_Sen_DataFieldJulianDayofYear 0x52a +#define HidUsage_Sen_DataFieldTimeSinceSystemBoot 0x52b +#define HidUsage_Sen_PropertyTime 0x530 +#define HidUsage_Sen_PropertyTimeZoneOffsetfromUTC 0x531 +#define HidUsage_Sen_PropertyTimeZoneName 0x532 +#define HidUsage_Sen_PropertyDaylightSavingsTimeObserved 0x533 +#define HidUsage_Sen_PropertyTimeTrimAdjustment 0x534 +#define HidUsage_Sen_PropertyArmAlarm 0x535 +#define HidUsage_Sen_DataFieldCustom 0x540 +#define HidUsage_Sen_DataFieldCustomUsage 0x541 +#define HidUsage_Sen_DataFieldCustomBooleanArray 0x542 +#define HidUsage_Sen_DataFieldCustomValue 0x543 +#define HidUsage_Sen_DataFieldCustomValue1 0x544 +#define HidUsage_Sen_DataFieldCustomValue2 0x545 +#define HidUsage_Sen_DataFieldCustomValue3 0x546 +#define HidUsage_Sen_DataFieldCustomValue4 0x547 +#define HidUsage_Sen_DataFieldCustomValue5 0x548 +#define HidUsage_Sen_DataFieldCustomValue6 0x549 +#define HidUsage_Sen_DataFieldCustomValue7 0x54a +#define HidUsage_Sen_DataFieldCustomValue8 0x54b +#define HidUsage_Sen_DataFieldCustomValue9 0x54c +#define HidUsage_Sen_DataFieldCustomValue10 0x54d +#define HidUsage_Sen_DataFieldCustomValue11 0x54e +#define HidUsage_Sen_DataFieldCustomValue12 0x54f +#define HidUsage_Sen_DataFieldCustomValue13 0x550 +#define HidUsage_Sen_DataFieldCustomValue14 0x551 +#define HidUsage_Sen_DataFieldCustomValue15 0x552 +#define HidUsage_Sen_DataFieldCustomValue16 0x553 +#define HidUsage_Sen_DataFieldCustomValue17 0x554 +#define HidUsage_Sen_DataFieldCustomValue18 0x555 +#define HidUsage_Sen_DataFieldCustomValue19 0x556 +#define HidUsage_Sen_DataFieldCustomValue20 0x557 +#define HidUsage_Sen_DataFieldCustomValue21 0x558 +#define HidUsage_Sen_DataFieldCustomValue22 0x559 +#define HidUsage_Sen_DataFieldCustomValue23 0x55a +#define HidUsage_Sen_DataFieldCustomValue24 0x55b +#define HidUsage_Sen_DataFieldCustomValue25 0x55c +#define HidUsage_Sen_DataFieldCustomValue26 0x55d +#define HidUsage_Sen_DataFieldCustomValue27 0x55e +#define HidUsage_Sen_DataFieldCustomValue28 0x55f +#define HidUsage_Sen_DataFieldGeneric 0x560 +#define HidUsage_Sen_DataFieldGenericGUIDorPROPERTYKEY 0x561 +#define HidUsage_Sen_DataFieldGenericCategoryGUID 0x562 +#define HidUsage_Sen_DataFieldGenericTypeGUID 0x563 +#define HidUsage_Sen_DataFieldGenericEventPROPERTYKEY 0x564 +#define HidUsage_Sen_DataFieldGenericPropertyPROPERTYKEY 0x565 +#define HidUsage_Sen_DataFieldGenericDataFieldPROPERTYKEY 0x566 +#define HidUsage_Sen_DataFieldGenericEvent 0x567 +#define HidUsage_Sen_DataFieldGenericProperty 0x568 +#define HidUsage_Sen_DataFieldGenericDataField 0x569 +#define HidUsage_Sen_DataFieldEnumeratorTableRowIndex 0x56a +#define HidUsage_Sen_DataFieldEnumeratorTableRowCount 0x56b +#define HidUsage_Sen_DataFieldGenericGUIDorPROPERTYKEYkind 0x56c +#define HidUsage_Sen_DataFieldGenericGUID 0x56d +#define HidUsage_Sen_DataFieldGenericPROPERTYKEY 0x56e +#define HidUsage_Sen_DataFieldGenericTopLevelCollectionID 0x56f +#define HidUsage_Sen_DataFieldGenericReportID 0x570 +#define HidUsage_Sen_DataFieldGenericReportItemPositionIndex 0x571 +#define HidUsage_Sen_DataFieldGenericFirmwareVARTYPE 0x572 +#define HidUsage_Sen_DataFieldGenericUnitofMeasure 0x573 +#define HidUsage_Sen_DataFieldGenericUnitExponent 0x574 +#define HidUsage_Sen_DataFieldGenericReportSize 0x575 +#define HidUsage_Sen_DataFieldGenericReportCount 0x576 +#define HidUsage_Sen_PropertyGeneric 0x580 +#define HidUsage_Sen_PropertyEnumeratorTableRowIndex 0x581 +#define HidUsage_Sen_PropertyEnumeratorTableRowCount 0x582 +#define HidUsage_Sen_DataFieldPersonalActivity 0x590 +#define HidUsage_Sen_DataFieldActivityType 0x591 +#define HidUsage_Sen_DataFieldActivityState 0x592 +#define HidUsage_Sen_DataFieldDevicePosition 0x593 +#define HidUsage_Sen_DataFieldStepCount 0x594 +#define HidUsage_Sen_DataFieldStepCountReset 0x595 +#define HidUsage_Sen_DataFieldStepDuration 0x596 +#define HidUsage_Sen_DataFieldStepType 0x597 +#define HidUsage_Sen_PropertyMinimumActivityDetectionInterval 0x5a0 +#define HidUsage_Sen_PropertySupportedActivityTypes 0x5a1 +#define HidUsage_Sen_PropertySubscribedActivityTypes 0x5a2 +#define HidUsage_Sen_PropertySupportedStepTypes 0x5a3 +#define HidUsage_Sen_PropertySubscribedStepTypes 0x5a4 +#define HidUsage_Sen_PropertyFloorHeight 0x5a5 +#define HidUsage_Sen_DataFieldCustomTypeID 0x5b0 +#define HidUsage_Sen_PropertyCustom 0x5c0 +#define HidUsage_Sen_PropertyCustomValue1 0x5c1 +#define HidUsage_Sen_PropertyCustomValue2 0x5c2 +#define HidUsage_Sen_PropertyCustomValue3 0x5c3 +#define HidUsage_Sen_PropertyCustomValue4 0x5c4 +#define HidUsage_Sen_PropertyCustomValue5 0x5c5 +#define HidUsage_Sen_PropertyCustomValue6 0x5c6 +#define HidUsage_Sen_PropertyCustomValue7 0x5c7 +#define HidUsage_Sen_PropertyCustomValue8 0x5c8 +#define HidUsage_Sen_PropertyCustomValue9 0x5c9 +#define HidUsage_Sen_PropertyCustomValue10 0x5ca +#define HidUsage_Sen_PropertyCustomValue11 0x5cb +#define HidUsage_Sen_PropertyCustomValue12 0x5cc +#define HidUsage_Sen_PropertyCustomValue13 0x5cd +#define HidUsage_Sen_PropertyCustomValue14 0x5ce +#define HidUsage_Sen_PropertyCustomValue15 0x5cf +#define HidUsage_Sen_PropertyCustomValue16 0x5d0 +#define HidUsage_Sen_DataFieldHinge 0x5e0 +#define HidUsage_Sen_DataFieldHingeAngle 0x5e1 +#define HidUsage_Sen_DataFieldGestureSensor 0x5f0 +#define HidUsage_Sen_DataFieldGestureState 0x5f1 +#define HidUsage_Sen_DataFieldHingeFoldInitialAngle 0x5f2 +#define HidUsage_Sen_DataFieldHingeFoldFinalAngle 0x5f3 +#define HidUsage_Sen_DataFieldHingeFoldContributingPanel 0x5f4 +#define HidUsage_Sen_DataFieldHingeFoldType 0x5f5 +#define HidUsage_Sen_SensorStateUndefined 0x800 +#define HidUsage_Sen_SensorStateReady 0x801 +#define HidUsage_Sen_SensorStateNotAvailable 0x802 +#define HidUsage_Sen_SensorStateNoData 0x803 +#define HidUsage_Sen_SensorStateInitializing 0x804 +#define HidUsage_Sen_SensorStateAccessDenied 0x805 +#define HidUsage_Sen_SensorStateError 0x806 +#define HidUsage_Sen_SensorEventUnknown 0x810 +#define HidUsage_Sen_SensorEventStateChanged 0x811 +#define HidUsage_Sen_SensorEventPropertyChanged 0x812 +#define HidUsage_Sen_SensorEventDataUpdated 0x813 +#define HidUsage_Sen_SensorEventPollResponse 0x814 +#define HidUsage_Sen_SensorEventChangeSensitivity 0x815 +#define HidUsage_Sen_SensorEventRangeMaximumReached 0x816 +#define HidUsage_Sen_SensorEventRangeMinimumReached 0x817 +#define HidUsage_Sen_SensorEventHighThresholdCrossUpward 0x818 +#define HidUsage_Sen_SensorEventHighThresholdCrossDownward 0x819 +#define HidUsage_Sen_SensorEventLowThresholdCrossUpward 0x81a +#define HidUsage_Sen_SensorEventLowThresholdCrossDownward 0x81b +#define HidUsage_Sen_SensorEventZeroThresholdCrossUpward 0x81c +#define HidUsage_Sen_SensorEventZeroThresholdCrossDownward 0x81d +#define HidUsage_Sen_SensorEventPeriodExceeded 0x81e +#define HidUsage_Sen_SensorEventFrequencyExceeded 0x81f +#define HidUsage_Sen_SensorEventComplexTrigger 0x820 +#define HidUsage_Sen_ConnectionTypePCIntegrated 0x830 +#define HidUsage_Sen_ConnectionTypePCAttached 0x831 +#define HidUsage_Sen_ConnectionTypePCExternal 0x832 +#define HidUsage_Sen_ReportingStateReportNoEvents 0x840 +#define HidUsage_Sen_ReportingStateReportAllEvents 0x841 +#define HidUsage_Sen_ReportingStateReportThresholdEvents 0x842 +#define HidUsage_Sen_ReportingStateWakeOnNoEvents 0x843 +#define HidUsage_Sen_ReportingStateWakeOnAllEvents 0x844 +#define HidUsage_Sen_ReportingStateWakeOnThresholdEvents 0x845 +#define HidUsage_Sen_ReportingStateAnytime 0x846 +#define HidUsage_Sen_PowerStateUndefined 0x850 +#define HidUsage_Sen_PowerStateD0FullPower 0x851 +#define HidUsage_Sen_PowerStateD1LowPower 0x852 +#define HidUsage_Sen_PowerStateD2StandbyPowerwithWakeup 0x853 +#define HidUsage_Sen_PowerStateD3SleepwithWakeup 0x854 +#define HidUsage_Sen_PowerStateD4PowerOff 0x855 +#define HidUsage_Sen_AccuracyDefault 0x860 +#define HidUsage_Sen_AccuracyHigh 0x861 +#define HidUsage_Sen_AccuracyMedium 0x862 +#define HidUsage_Sen_AccuracyLow 0x863 +#define HidUsage_Sen_FixQualityNoFix 0x870 +#define HidUsage_Sen_FixQualityGPS 0x871 +#define HidUsage_Sen_FixQualityDGPS 0x872 +#define HidUsage_Sen_FixTypeNoFix 0x880 +#define HidUsage_Sen_FixTypeGPSSPSModeFixValid 0x881 +#define HidUsage_Sen_FixTypeDGPSSPSModeFixValid 0x882 +#define HidUsage_Sen_FixTypeGPSPPSModeFixValid 0x883 +#define HidUsage_Sen_FixTypeRealTimeKinematic 0x884 +#define HidUsage_Sen_FixTypeFloatRTK 0x885 +#define HidUsage_Sen_FixTypeEstimateddeadreckoned 0x886 +#define HidUsage_Sen_FixTypeManualInputMode 0x887 +#define HidUsage_Sen_FixTypeSimulatorMode 0x888 +#define HidUsage_Sen_GPSOperationModeManual 0x890 +#define HidUsage_Sen_GPSOperationModeAutomatic 0x891 +#define HidUsage_Sen_GPSSelectionModeAutonomous 0x8a0 +#define HidUsage_Sen_GPSSelectionModeDGPS 0x8a1 +#define HidUsage_Sen_GPSSelectionModeEstimateddeadreckoned 0x8a2 +#define HidUsage_Sen_GPSSelectionModeManualInput 0x8a3 +#define HidUsage_Sen_GPSSelectionModeSimulator 0x8a4 +#define HidUsage_Sen_GPSSelectionModeDataNotValid 0x8a5 +#define HidUsage_Sen_GPSStatusDataValid 0x8b0 +#define HidUsage_Sen_GPSStatusDataNotValid 0x8b1 +#define HidUsage_Sen_DayofWeekSunday 0x8c0 +#define HidUsage_Sen_DayofWeekMonday 0x8c1 +#define HidUsage_Sen_DayofWeekTuesday 0x8c2 +#define HidUsage_Sen_DayofWeekWednesday 0x8c3 +#define HidUsage_Sen_DayofWeekThursday 0x8c4 +#define HidUsage_Sen_DayofWeekFriday 0x8c5 +#define HidUsage_Sen_DayofWeekSaturday 0x8c6 +#define HidUsage_Sen_KindCategory 0x8d0 +#define HidUsage_Sen_KindType 0x8d1 +#define HidUsage_Sen_KindEvent 0x8d2 +#define HidUsage_Sen_KindProperty 0x8d3 +#define HidUsage_Sen_KindDataField 0x8d4 +#define HidUsage_Sen_MagnetometerAccuracyLow 0x8e0 +#define HidUsage_Sen_MagnetometerAccuracyMedium 0x8e1 +#define HidUsage_Sen_MagnetometerAccuracyHigh 0x8e2 +#define HidUsage_Sen_SimpleOrientationDirectionNotRotated 0x8f0 +#define HidUsage_Sen_SimpleOrientationDirectionRotated90DegreesCCW 0x8f1 +#define HidUsage_Sen_SimpleOrientationDirectionRotated180DegreesCCW 0x8f2 +#define HidUsage_Sen_SimpleOrientationDirectionRotated270DegreesCCW 0x8f3 +#define HidUsage_Sen_SimpleOrientationDirectionFaceUp 0x8f4 +#define HidUsage_Sen_SimpleOrientationDirectionFaceDown 0x8f5 +#define HidUsage_Sen_VT_NULL 0x900 +#define HidUsage_Sen_VT_BOOL 0x901 +#define HidUsage_Sen_VT_UI1 0x902 +#define HidUsage_Sen_VT_I1 0x903 +#define HidUsage_Sen_VT_UI2 0x904 +#define HidUsage_Sen_VT_I2 0x905 +#define HidUsage_Sen_VT_UI4 0x906 +#define HidUsage_Sen_VT_I4 0x907 +#define HidUsage_Sen_VT_UI8 0x908 +#define HidUsage_Sen_VT_I8 0x909 +#define HidUsage_Sen_VT_R4 0x90a +#define HidUsage_Sen_VT_R8 0x90b +#define HidUsage_Sen_VT_WSTR 0x90c +#define HidUsage_Sen_VT_STR 0x90d +#define HidUsage_Sen_VT_CLSID 0x90e +#define HidUsage_Sen_VT_VECTORVT_UI1 0x90f +#define HidUsage_Sen_VT_F16E0 0x910 +#define HidUsage_Sen_VT_F16E1 0x911 +#define HidUsage_Sen_VT_F16E2 0x912 +#define HidUsage_Sen_VT_F16E3 0x913 +#define HidUsage_Sen_VT_F16E4 0x914 +#define HidUsage_Sen_VT_F16E5 0x915 +#define HidUsage_Sen_VT_F16E6 0x916 +#define HidUsage_Sen_VT_F16E7 0x917 +#define HidUsage_Sen_VT_F16E8 0x918 +#define HidUsage_Sen_VT_F16E9 0x919 +#define HidUsage_Sen_VT_F16EA 0x91a +#define HidUsage_Sen_VT_F16EB 0x91b +#define HidUsage_Sen_VT_F16EC 0x91c +#define HidUsage_Sen_VT_F16ED 0x91d +#define HidUsage_Sen_VT_F16EE 0x91e +#define HidUsage_Sen_VT_F16EF 0x91f +#define HidUsage_Sen_VT_F32E0 0x920 +#define HidUsage_Sen_VT_F32E1 0x921 +#define HidUsage_Sen_VT_F32E2 0x922 +#define HidUsage_Sen_VT_F32E3 0x923 +#define HidUsage_Sen_VT_F32E4 0x924 +#define HidUsage_Sen_VT_F32E5 0x925 +#define HidUsage_Sen_VT_F32E6 0x926 +#define HidUsage_Sen_VT_F32E7 0x927 +#define HidUsage_Sen_VT_F32E8 0x928 +#define HidUsage_Sen_VT_F32E9 0x929 +#define HidUsage_Sen_VT_F32EA 0x92a +#define HidUsage_Sen_VT_F32EB 0x92b +#define HidUsage_Sen_VT_F32EC 0x92c +#define HidUsage_Sen_VT_F32ED 0x92d +#define HidUsage_Sen_VT_F32EE 0x92e +#define HidUsage_Sen_VT_F32EF 0x92f +#define HidUsage_Sen_ActivityTypeUnknown 0x930 +#define HidUsage_Sen_ActivityTypeStationary 0x931 +#define HidUsage_Sen_ActivityTypeFidgeting 0x932 +#define HidUsage_Sen_ActivityTypeWalking 0x933 +#define HidUsage_Sen_ActivityTypeRunning 0x934 +#define HidUsage_Sen_ActivityTypeInVehicle 0x935 +#define HidUsage_Sen_ActivityTypeBiking 0x936 +#define HidUsage_Sen_ActivityTypeIdle 0x937 +#define HidUsage_Sen_UnitNotSpecified 0x940 +#define HidUsage_Sen_UnitLux 0x941 +#define HidUsage_Sen_UnitDegreesKelvin 0x942 +#define HidUsage_Sen_UnitDegreesCelsius 0x943 +#define HidUsage_Sen_UnitPascal 0x944 +#define HidUsage_Sen_UnitNewton 0x945 +#define HidUsage_Sen_UnitMetersSecond 0x946 +#define HidUsage_Sen_UnitKilogram 0x947 +#define HidUsage_Sen_UnitMeter 0x948 +#define HidUsage_Sen_UnitMetersSecondSecond 0x949 +#define HidUsage_Sen_UnitFarad 0x94a +#define HidUsage_Sen_UnitAmpere 0x94b +#define HidUsage_Sen_UnitWatt 0x94c +#define HidUsage_Sen_UnitHenry 0x94d +#define HidUsage_Sen_UnitOhm 0x94e +#define HidUsage_Sen_UnitVolt 0x94f +#define HidUsage_Sen_UnitHertz 0x950 +#define HidUsage_Sen_UnitBar 0x951 +#define HidUsage_Sen_UnitDegreesAnticlockwise 0x952 +#define HidUsage_Sen_UnitDegreesClockwise 0x953 +#define HidUsage_Sen_UnitDegrees 0x954 +#define HidUsage_Sen_UnitDegreesSecond 0x955 +#define HidUsage_Sen_UnitDegreesSecondSecond 0x956 +#define HidUsage_Sen_UnitKnot 0x957 +#define HidUsage_Sen_UnitPercent 0x958 +#define HidUsage_Sen_UnitSecond 0x959 +#define HidUsage_Sen_UnitMillisecond 0x95a +#define HidUsage_Sen_UnitG 0x95b +#define HidUsage_Sen_UnitBytes 0x95c +#define HidUsage_Sen_UnitMilligauss 0x95d +#define HidUsage_Sen_UnitBits 0x95e +#define HidUsage_Sen_ActivityStateNoStateChange 0x960 +#define HidUsage_Sen_ActivityStateStartActivity 0x961 +#define HidUsage_Sen_ActivityStateEndActivity 0x962 +#define HidUsage_Sen_Exponent0 0x970 +#define HidUsage_Sen_Exponent1 0x971 +#define HidUsage_Sen_Exponent2 0x972 +#define HidUsage_Sen_Exponent3 0x973 +#define HidUsage_Sen_Exponent4 0x974 +#define HidUsage_Sen_Exponent5 0x975 +#define HidUsage_Sen_Exponent6 0x976 +#define HidUsage_Sen_Exponent7 0x977 +#define HidUsage_Sen_Exponent8 0x978 +#define HidUsage_Sen_Exponent9 0x979 +#define HidUsage_Sen_ExponentA 0x97a +#define HidUsage_Sen_ExponentB 0x97b +#define HidUsage_Sen_ExponentC 0x97c +#define HidUsage_Sen_ExponentD 0x97d +#define HidUsage_Sen_ExponentE 0x97e +#define HidUsage_Sen_ExponentF 0x97f +#define HidUsage_Sen_DevicePositionUnknown 0x980 +#define HidUsage_Sen_DevicePositionUnchanged 0x981 +#define HidUsage_Sen_DevicePositionOnDesk 0x982 +#define HidUsage_Sen_DevicePositionInHand 0x983 +#define HidUsage_Sen_DevicePositionMovinginBag 0x984 +#define HidUsage_Sen_DevicePositionStationaryinBag 0x985 +#define HidUsage_Sen_StepTypeUnknown 0x990 +#define HidUsage_Sen_StepTypeWalking 0x991 +#define HidUsage_Sen_StepTypeRunning 0x992 +#define HidUsage_Sen_GestureStateUnknown 0x9a0 +#define HidUsage_Sen_GestureStateStarted 0x9a1 +#define HidUsage_Sen_GestureStateCompleted 0x9a2 +#define HidUsage_Sen_GestureStateCancelled 0x9a3 +#define HidUsage_Sen_HingeFoldContributingPanelUnknown 0x9b0 +#define HidUsage_Sen_HingeFoldContributingPanelPanel1 0x9b1 +#define HidUsage_Sen_HingeFoldContributingPanelPanel2 0x9b2 +#define HidUsage_Sen_HingeFoldContributingPanelBoth 0x9b3 +#define HidUsage_Sen_HingeFoldTypeUnknown 0x9b4 +#define HidUsage_Sen_HingeFoldTypeIncreasing 0x9b5 +#define HidUsage_Sen_HingeFoldTypeDecreasing 0x9b6 +#define HidUsage_Sen_HumanPresenceDetectionTypeVendorDefinedNonBiometric 0x9c0 +#define HidUsage_Sen_HumanPresenceDetectionTypeVendorDefinedBiometric 0x9c1 +#define HidUsage_Sen_HumanPresenceDetectionTypeFacialBiometric 0x9c2 +#define HidUsage_Sen_HumanPresenceDetectionTypeAudioBiometric 0x9c3 +#define HidUsage_Sen_ModifierChangeSensitivityAbsolute 0x1000 +#define HidUsage_Sen_ModifierMaximum 0x2000 +#define HidUsage_Sen_ModifierMinimum 0x3000 +#define HidUsage_Sen_ModifierAccuracy 0x4000 +#define HidUsage_Sen_ModifierResolution 0x5000 +#define HidUsage_Sen_ModifierThresholdHigh 0x6000 +#define HidUsage_Sen_ModifierThresholdLow 0x7000 +#define HidUsage_Sen_ModifierCalibrationOffset 0x8000 +#define HidUsage_Sen_ModifierCalibrationMultiplier 0x9000 +#define HidUsage_Sen_ModifierReportInterval 0xa000 +#define HidUsage_Sen_ModifierFrequencyMax 0xb000 +#define HidUsage_Sen_ModifierPeriodMax 0xc000 +#define HidUsage_Sen_ModifierChangeSensitivityPercentofRange 0xd000 +#define HidUsage_Sen_ModifierChangeSensitivityPercentRelative 0xe000 +#define HidUsage_Sen_ModifierVendorReserved 0xf000 +#define HidUsage_MI_MedicalUltrasound 0x01 +#define HidUsage_MI_VCRAcquisition 0x20 +#define HidUsage_MI_FreezeThaw 0x21 +#define HidUsage_MI_ClipStore 0x22 +#define HidUsage_MI_Update 0x23 +#define HidUsage_MI_Next 0x24 +#define HidUsage_MI_Save 0x25 +#define HidUsage_MI_Print 0x26 +#define HidUsage_MI_MicrophoneEnable 0x27 +#define HidUsage_MI_Cine 0x40 +#define HidUsage_MI_TransmitPower 0x41 +#define HidUsage_MI_Volume 0x42 +#define HidUsage_MI_Focus 0x43 +#define HidUsage_MI_Depth 0x44 +#define HidUsage_MI_SoftStepPrimary 0x60 +#define HidUsage_MI_SoftStepSecondary 0x61 +#define HidUsage_MI_DepthGainCompensation 0x70 +#define HidUsage_MI_ZoomSelect 0x80 +#define HidUsage_MI_ZoomAdjust 0x81 +#define HidUsage_MI_SpectralDopplerModeSelect 0x82 +#define HidUsage_MI_SpectralDopplerAdjust 0x83 +#define HidUsage_MI_ColorDopplerModeSelect 0x84 +#define HidUsage_MI_ColorDopplerAdjust 0x85 +#define HidUsage_MI_MotionModeSelect 0x86 +#define HidUsage_MI_MotionModeAdjust 0x87 +#define HidUsage_MI_TwoDModeSelect 0x88 +#define HidUsage_MI_TwoDModeAdjust 0x89 +#define HidUsage_MI_SoftControlSelect 0xa0 +#define HidUsage_MI_SoftControlAdjust 0xa1 +#define HidUsage_BD_BrailleDisplay 0x01 +#define HidUsage_BD_BrailleRow 0x02 +#define HidUsage_BD_EightDotBrailleCell 0x03 +#define HidUsage_BD_SixDotBrailleCell 0x04 +#define HidUsage_BD_NumberofBrailleCells 0x05 +#define HidUsage_BD_ScreenReaderControl 0x06 +#define HidUsage_BD_ScreenReaderIdentifier 0x07 +#define HidUsage_BD_RouterSet1 0xfa +#define HidUsage_BD_RouterSet2 0xfb +#define HidUsage_BD_RouterSet3 0xfc +#define HidUsage_BD_RouterKey 0x100 +#define HidUsage_BD_RowRouterKey 0x101 +#define HidUsage_BD_BrailleButtons 0x200 +#define HidUsage_BD_BrailleKeyboardDot1 0x201 +#define HidUsage_BD_BrailleKeyboardDot2 0x202 +#define HidUsage_BD_BrailleKeyboardDot3 0x203 +#define HidUsage_BD_BrailleKeyboardDot4 0x204 +#define HidUsage_BD_BrailleKeyboardDot5 0x205 +#define HidUsage_BD_BrailleKeyboardDot6 0x206 +#define HidUsage_BD_BrailleKeyboardDot7 0x207 +#define HidUsage_BD_BrailleKeyboardDot8 0x208 +#define HidUsage_BD_BrailleKeyboardSpace 0x209 +#define HidUsage_BD_BrailleKeyboardLeftSpace 0x20a +#define HidUsage_BD_BrailleKeyboardRightSpace 0x20b +#define HidUsage_BD_BrailleFaceControls 0x20c +#define HidUsage_BD_BrailleLeftControls 0x20d +#define HidUsage_BD_BrailleRightControls 0x20e +#define HidUsage_BD_BrailleTopControls 0x20f +#define HidUsage_BD_BrailleJoystickCenter 0x210 +#define HidUsage_BD_BrailleJoystickUp 0x211 +#define HidUsage_BD_BrailleJoystickDown 0x212 +#define HidUsage_BD_BrailleJoystickLeft 0x213 +#define HidUsage_BD_BrailleJoystickRight 0x214 +#define HidUsage_BD_BrailleDPadCenter 0x215 +#define HidUsage_BD_BrailleDPadUp 0x216 +#define HidUsage_BD_BrailleDPadDown 0x217 +#define HidUsage_BD_BrailleDPadLeft 0x218 +#define HidUsage_BD_BrailleDPadRight 0x219 +#define HidUsage_BD_BraillePanLeft 0x21a +#define HidUsage_BD_BraillePanRight 0x21b +#define HidUsage_BD_BrailleRockerUp 0x21c +#define HidUsage_BD_BrailleRockerDown 0x21d +#define HidUsage_BD_BrailleRockerPress 0x21e +#define HidUsage_LAI_LampArray 0x01 +#define HidUsage_LAI_LampArrayAttributesReport 0x02 +#define HidUsage_LAI_LampCount 0x03 +#define HidUsage_LAI_BoundingBoxWidthInMicrometers 0x04 +#define HidUsage_LAI_BoundingBoxHeightInMicrometers 0x05 +#define HidUsage_LAI_BoundingBoxDepthInMicrometers 0x06 +#define HidUsage_LAI_LampArrayKind 0x07 +#define HidUsage_LAI_MinUpdateIntervalInMicroseconds 0x08 +#define HidUsage_LAI_LampAttributesRequestReport 0x20 +#define HidUsage_LAI_LampId 0x21 +#define HidUsage_LAI_LampAttributesResponseReport 0x22 +#define HidUsage_LAI_PositionXInMicrometers 0x23 +#define HidUsage_LAI_PositionYInMicrometers 0x24 +#define HidUsage_LAI_PositionZInMicrometers 0x25 +#define HidUsage_LAI_LampPurposes 0x26 +#define HidUsage_LAI_UpdateLatencyInMicroseconds 0x27 +#define HidUsage_LAI_RedLevelCount 0x28 +#define HidUsage_LAI_GreenLevelCount 0x29 +#define HidUsage_LAI_BlueLevelCount 0x2a +#define HidUsage_LAI_IntensityLevelCount 0x2b +#define HidUsage_LAI_IsProgrammable 0x2c +#define HidUsage_LAI_InputBinding 0x2d +#define HidUsage_LAI_LampMultiUpdateReport 0x50 +#define HidUsage_LAI_RedUpdateChannel 0x51 +#define HidUsage_LAI_GreenUpdateChannel 0x52 +#define HidUsage_LAI_BlueUpdateChannel 0x53 +#define HidUsage_LAI_IntensityUpdateChannel 0x54 +#define HidUsage_LAI_LampUpdateFlags 0x55 +#define HidUsage_LAI_LampRangeUpdateReport 0x60 +#define HidUsage_LAI_LampIdStart 0x61 +#define HidUsage_LAI_LampIdEnd 0x62 +#define HidUsage_LAI_LampArrayControlReport 0x70 +#define HidUsage_LAI_AutonomousMode 0x71 +#define HidUsage_Mon_MonitorControl 0x01 +#define HidUsage_Mon_EDIDInformation 0x02 +#define HidUsage_Mon_VDIFInformation 0x03 +#define HidUsage_Mon_VESAVersion 0x04 +#define HidUsage_VESAVC_Degauss 0x01 +#define HidUsage_VESAVC_Brightness 0x10 +#define HidUsage_VESAVC_Contrast 0x12 +#define HidUsage_VESAVC_RedVideoGain 0x16 +#define HidUsage_VESAVC_GreenVideoGain 0x18 +#define HidUsage_VESAVC_BlueVideoGain 0x1a +#define HidUsage_VESAVC_Focus 0x1c +#define HidUsage_VESAVC_HorizontalPosition 0x20 +#define HidUsage_VESAVC_HorizontalSize 0x22 +#define HidUsage_VESAVC_HorizontalPincushion 0x24 +#define HidUsage_VESAVC_HorizontalPincushionBalance 0x26 +#define HidUsage_VESAVC_HorizontalMisconvergence 0x28 +#define HidUsage_VESAVC_HorizontalLinearity 0x2a +#define HidUsage_VESAVC_HorizontalLinearityBalance 0x2c +#define HidUsage_VESAVC_VerticalPosition 0x30 +#define HidUsage_VESAVC_VerticalSize 0x32 +#define HidUsage_VESAVC_VerticalPincushion 0x34 +#define HidUsage_VESAVC_VerticalPincushionBalance 0x36 +#define HidUsage_VESAVC_VerticalMisconvergence 0x38 +#define HidUsage_VESAVC_VerticalLinearity 0x3a +#define HidUsage_VESAVC_VerticalLinearityBalance 0x3c +#define HidUsage_VESAVC_ParallelogramDistortionKeyBalance 0x40 +#define HidUsage_VESAVC_TrapezoidalDistortionKey 0x42 +#define HidUsage_VESAVC_TiltRotation 0x44 +#define HidUsage_VESAVC_TopCornerDistortionControl 0x46 +#define HidUsage_VESAVC_TopCornerDistortionBalance 0x48 +#define HidUsage_VESAVC_BottomCornerDistortionControl 0x4a +#define HidUsage_VESAVC_BottomCornerDistortionBalance 0x4c +#define HidUsage_VESAVC_HorizontalMoiré 0x56 +#define HidUsage_VESAVC_VerticalMoiré 0x58 +#define HidUsage_VESAVC_InputLevelSelect 0x5e +#define HidUsage_VESAVC_InputSourceSelect 0x60 +#define HidUsage_VESAVC_RedVideoBlackLevel 0x6c +#define HidUsage_VESAVC_GreenVideoBlackLevel 0x6e +#define HidUsage_VESAVC_BlueVideoBlackLevel 0x70 +#define HidUsage_VESAVC_AutoSizeCenter 0xa2 +#define HidUsage_VESAVC_PolarityHorizontalSynchronization 0xa4 +#define HidUsage_VESAVC_PolarityVerticalSynchronization 0xa6 +#define HidUsage_VESAVC_SynchronizationType 0xa8 +#define HidUsage_VESAVC_ScreenOrientation 0xaa +#define HidUsage_VESAVC_HorizontalFrequency 0xac +#define HidUsage_VESAVC_VerticalFrequency 0xae +#define HidUsage_VESAVC_Settings 0xb0 +#define HidUsage_VESAVC_OnScreenDisplay 0xca +#define HidUsage_VESAVC_StereoMode 0xd4 +#define HidUsage_Pow_iName 0x01 +#define HidUsage_Pow_PresentStatus 0x02 +#define HidUsage_Pow_ChangedStatus 0x03 +#define HidUsage_Pow_UPS 0x04 +#define HidUsage_Pow_PowerSupply 0x05 +#define HidUsage_Pow_BatterySystem 0x10 +#define HidUsage_Pow_BatterySystemId 0x11 +#define HidUsage_Pow_Battery 0x12 +#define HidUsage_Pow_BatteryId 0x13 +#define HidUsage_Pow_Charger 0x14 +#define HidUsage_Pow_ChargerId 0x15 +#define HidUsage_Pow_PowerConverter 0x16 +#define HidUsage_Pow_PowerConverterId 0x17 +#define HidUsage_Pow_OutletSystem 0x18 +#define HidUsage_Pow_OutletSystemId 0x19 +#define HidUsage_Pow_Input 0x1a +#define HidUsage_Pow_InputId 0x1b +#define HidUsage_Pow_Output 0x1c +#define HidUsage_Pow_OutputId 0x1d +#define HidUsage_Pow_Flow 0x1e +#define HidUsage_Pow_FlowId 0x1f +#define HidUsage_Pow_Outlet 0x20 +#define HidUsage_Pow_OutletId 0x21 +#define HidUsage_Pow_Gang 0x22 +#define HidUsage_Pow_GangId 0x23 +#define HidUsage_Pow_PowerSummary 0x24 +#define HidUsage_Pow_PowerSummaryId 0x25 +#define HidUsage_Pow_Voltage 0x30 +#define HidUsage_Pow_Current 0x31 +#define HidUsage_Pow_Frequency 0x32 +#define HidUsage_Pow_ApparentPower 0x33 +#define HidUsage_Pow_ActivePower 0x34 +#define HidUsage_Pow_PercentLoad 0x35 +#define HidUsage_Pow_Temperature 0x36 +#define HidUsage_Pow_Humidity 0x37 +#define HidUsage_Pow_BadCount 0x38 +#define HidUsage_Pow_ConfigVoltage 0x40 +#define HidUsage_Pow_ConfigCurrent 0x41 +#define HidUsage_Pow_ConfigFrequency 0x42 +#define HidUsage_Pow_ConfigApparentPower 0x43 +#define HidUsage_Pow_ConfigActivePower 0x44 +#define HidUsage_Pow_ConfigPercentLoad 0x45 +#define HidUsage_Pow_ConfigTemperature 0x46 +#define HidUsage_Pow_ConfigHumidity 0x47 +#define HidUsage_Pow_SwitchOnControl 0x50 +#define HidUsage_Pow_SwitchOffControl 0x51 +#define HidUsage_Pow_ToggleControl 0x52 +#define HidUsage_Pow_LowVoltageTransfer 0x53 +#define HidUsage_Pow_HighVoltageTransfer 0x54 +#define HidUsage_Pow_DelayBeforeReboot 0x55 +#define HidUsage_Pow_DelayBeforeStartup 0x56 +#define HidUsage_Pow_DelayBeforeShutdown 0x57 +#define HidUsage_Pow_Test 0x58 +#define HidUsage_Pow_ModuleReset 0x59 +#define HidUsage_Pow_AudibleAlarmControl 0x5a +#define HidUsage_Pow_Present 0x60 +#define HidUsage_Pow_Good 0x61 +#define HidUsage_Pow_InternalFailure 0x62 +#define HidUsage_Pow_VoltagOutOfRange 0x63 +#define HidUsage_Pow_FrequencyOutOfRange 0x64 +#define HidUsage_Pow_Overload 0x65 +#define HidUsage_Pow_OverCharged 0x66 +#define HidUsage_Pow_OverTemperature 0x67 +#define HidUsage_Pow_ShutdownRequested 0x68 +#define HidUsage_Pow_ShutdownImminent 0x69 +#define HidUsage_Pow_SwitchOnOff 0x6b +#define HidUsage_Pow_Switchable 0x6c +#define HidUsage_Pow_Used 0x6d +#define HidUsage_Pow_Boost 0x6e +#define HidUsage_Pow_Buck 0x6f +#define HidUsage_Pow_Initialized 0x70 +#define HidUsage_Pow_Tested 0x71 +#define HidUsage_Pow_AwaitingPower 0x72 +#define HidUsage_Pow_CommunicationLost 0x73 +#define HidUsage_Pow_iManufacturer 0xfd +#define HidUsage_Pow_iProduct 0xfe +#define HidUsage_Pow_iSerialNumber 0xff +#define HidUsage_BS_SmartBatteryBatteryMode 0x01 +#define HidUsage_BS_SmartBatteryBatteryStatus 0x02 +#define HidUsage_BS_SmartBatteryAlarmWarning 0x03 +#define HidUsage_BS_SmartBatteryChargerMode 0x04 +#define HidUsage_BS_SmartBatteryChargerStatus 0x05 +#define HidUsage_BS_SmartBatteryChargerSpecInfo 0x06 +#define HidUsage_BS_SmartBatterySelectorState 0x07 +#define HidUsage_BS_SmartBatterySelectorPresets 0x08 +#define HidUsage_BS_SmartBatterySelectorInfo 0x09 +#define HidUsage_BS_OptionalMfgFunction1 0x10 +#define HidUsage_BS_OptionalMfgFunction2 0x11 +#define HidUsage_BS_OptionalMfgFunction3 0x12 +#define HidUsage_BS_OptionalMfgFunction4 0x13 +#define HidUsage_BS_OptionalMfgFunction5 0x14 +#define HidUsage_BS_ConnectionToSMBus 0x15 +#define HidUsage_BS_OutputConnection 0x16 +#define HidUsage_BS_ChargerConnection 0x17 +#define HidUsage_BS_BatteryInsertion 0x18 +#define HidUsage_BS_UseNext 0x19 +#define HidUsage_BS_OKToUse 0x1a +#define HidUsage_BS_BatterySupported 0x1b +#define HidUsage_BS_SelectorRevision 0x1c +#define HidUsage_BS_ChargingIndicator 0x1d +#define HidUsage_BS_ManufacturerAccess 0x28 +#define HidUsage_BS_RemainingCapacityLimit 0x29 +#define HidUsage_BS_RemainingTimeLimit 0x2a +#define HidUsage_BS_AtRate 0x2b +#define HidUsage_BS_CapacityMode 0x2c +#define HidUsage_BS_BroadcastToCharger 0x2d +#define HidUsage_BS_PrimaryBattery 0x2e +#define HidUsage_BS_ChargeController 0x2f +#define HidUsage_BS_TerminateCharge 0x40 +#define HidUsage_BS_TerminateDischarge 0x41 +#define HidUsage_BS_BelowRemainingCapacityLimit 0x42 +#define HidUsage_BS_RemainingTimeLimitExpired 0x43 +#define HidUsage_BS_Charging 0x44 +#define HidUsage_BS_Discharging 0x45 +#define HidUsage_BS_FullyCharged 0x46 +#define HidUsage_BS_FullyDischarged 0x47 +#define HidUsage_BS_ConditioningFlag 0x48 +#define HidUsage_BS_AtRateOK 0x49 +#define HidUsage_BS_SmartBatteryErrorCode 0x4a +#define HidUsage_BS_NeedReplacement 0x4b +#define HidUsage_BS_AtRateTimeToFull 0x60 +#define HidUsage_BS_AtRateTimeToEmpty 0x61 +#define HidUsage_BS_AverageCurrent 0x62 +#define HidUsage_BS_MaxError 0x63 +#define HidUsage_BS_RelativeStateOfCharge 0x64 +#define HidUsage_BS_AbsoluteStateOfCharge 0x65 +#define HidUsage_BS_RemainingCapacity 0x66 +#define HidUsage_BS_FullChargeCapacity 0x67 +#define HidUsage_BS_RunTimeToEmpty 0x68 +#define HidUsage_BS_AverageTimeToEmpty 0x69 +#define HidUsage_BS_AverageTimeToFull 0x6a +#define HidUsage_BS_CycleCount 0x6b +#define HidUsage_BS_BatteryPackModelLevel 0x80 +#define HidUsage_BS_InternalChargeController 0x81 +#define HidUsage_BS_PrimaryBatterySupport 0x82 +#define HidUsage_BS_DesignCapacity 0x83 +#define HidUsage_BS_SpecificationInfo 0x84 +#define HidUsage_BS_ManufactureDate 0x85 +#define HidUsage_BS_SerialNumber 0x86 +#define HidUsage_BS_iManufacturerName 0x87 +#define HidUsage_BS_iDeviceName 0x88 +#define HidUsage_BS_iDeviceChemistry 0x89 +#define HidUsage_BS_ManufacturerData 0x8a +#define HidUsage_BS_Rechargeable 0x8b +#define HidUsage_BS_WarningCapacityLimit 0x8c +#define HidUsage_BS_CapacityGranularity1 0x8d +#define HidUsage_BS_CapacityGranularity2 0x8e +#define HidUsage_BS_iOEMInformation 0x8f +#define HidUsage_BS_InhibitCharge 0xc0 +#define HidUsage_BS_EnablePolling 0xc1 +#define HidUsage_BS_ResetToZero 0xc2 +#define HidUsage_BS_ACPresent 0xd0 +#define HidUsage_BS_BatteryPresent 0xd1 +#define HidUsage_BS_PowerFail 0xd2 +#define HidUsage_BS_AlarmInhibited 0xd3 +#define HidUsage_BS_ThermistorUnderRange 0xd4 +#define HidUsage_BS_ThermistorHot 0xd5 +#define HidUsage_BS_ThermistorCold 0xd6 +#define HidUsage_BS_ThermistorOverRange 0xd7 +#define HidUsage_BS_VoltageOutOfRange 0xd8 +#define HidUsage_BS_CurrentOutOfRange 0xd9 +#define HidUsage_BS_CurrentNotRegulated 0xda +#define HidUsage_BS_VoltageNotRegulated 0xdb +#define HidUsage_BS_MasterMode 0xdc +#define HidUsage_BS_ChargerSelectorSupport 0xf0 +#define HidUsage_BS_ChargerSpec 0xf1 +#define HidUsage_BS_Level2 0xf2 +#define HidUsage_BS_Level3 0xf3 +#define HidUsage_BS_BarcodeBadgeReader 0x01 +#define HidUsage_BS_BarcodeScanner 0x02 +#define HidUsage_BS_DumbBarCodeScanner 0x03 +#define HidUsage_BS_CordlessScannerBase 0x04 +#define HidUsage_BS_BarCodeScannerCradle 0x05 +#define HidUsage_BS_AttributeReport 0x10 +#define HidUsage_BS_SettingsReport 0x11 +#define HidUsage_BS_ScannedDataReport 0x12 +#define HidUsage_BS_RawScannedDataReport 0x13 +#define HidUsage_BS_TriggerReport 0x14 +#define HidUsage_BS_StatusReport 0x15 +#define HidUsage_BS_UPCEANControlReport 0x16 +#define HidUsage_BS_EAN23LabelControlReport 0x17 +#define HidUsage_BS_Code39ControlReport 0x18 +#define HidUsage_BS_Interleaved2of5ControlReport 0x19 +#define HidUsage_BS_Standard2of5ControlReport 0x1a +#define HidUsage_BS_MSIPlesseyControlReport 0x1b +#define HidUsage_BS_CodabarControlReport 0x1c +#define HidUsage_BS_Code128ControlReport 0x1d +#define HidUsage_BS_Misc1DControlReport 0x1e +#define HidUsage_BS_TwoDControlReport 0x1f +#define HidUsage_BS_AimingPointerMode 0x30 +#define HidUsage_BS_BarCodePresentSensor 0x31 +#define HidUsage_BS_Class1ALaser 0x32 +#define HidUsage_BS_Class2Laser 0x33 +#define HidUsage_BS_HeaterPresent 0x34 +#define HidUsage_BS_ContactScanner 0x35 +#define HidUsage_BS_ElectronicArticleSurveillanceNotification 0x36 +#define HidUsage_BS_ConstantElectronicArticleSurveillance 0x37 +#define HidUsage_BS_ErrorIndication 0x38 +#define HidUsage_BS_FixedBeeper 0x39 +#define HidUsage_BS_GoodDecodeIndication 0x3a +#define HidUsage_BS_HandsFreeScanning 0x3b +#define HidUsage_BS_IntrinsicallySafe 0x3c +#define HidUsage_BS_KlasseEinsLaser 0x3d +#define HidUsage_BS_LongRangeScanner 0x3e +#define HidUsage_BS_MirrorSpeedControl 0x3f +#define HidUsage_BS_NotOnFileIndication 0x40 +#define HidUsage_BS_ProgrammableBeeper 0x41 +#define HidUsage_BS_Triggerless 0x42 +#define HidUsage_BS_Wand 0x43 +#define HidUsage_BS_WaterResistant 0x44 +#define HidUsage_BS_MultiRangeScanner 0x45 +#define HidUsage_BS_ProximitySensor 0x46 +#define HidUsage_BS_FragmentDecoding 0x4d +#define HidUsage_BS_ScannerReadConfidence 0x4e +#define HidUsage_BS_DataPrefix 0x4f +#define HidUsage_BS_PrefixAIMI 0x50 +#define HidUsage_BS_PrefixNone 0x51 +#define HidUsage_BS_PrefixProprietary 0x52 +#define HidUsage_BS_ActiveTime 0x55 +#define HidUsage_BS_AimingLaserPattern 0x56 +#define HidUsage_BS_BarCodePresent 0x57 +#define HidUsage_BS_BeeperState 0x58 +#define HidUsage_BS_LaserOnTime 0x59 +#define HidUsage_BS_LaserState 0x5a +#define HidUsage_BS_LockoutTime 0x5b +#define HidUsage_BS_MotorState 0x5c +#define HidUsage_BS_MotorTimeout 0x5d +#define HidUsage_BS_PowerOnResetScanner 0x5e +#define HidUsage_BS_PreventReadofBarcodes 0x5f +#define HidUsage_BS_InitiateBarcodeRead 0x60 +#define HidUsage_BS_TriggerState 0x61 +#define HidUsage_BS_TriggerMode 0x62 +#define HidUsage_BS_TriggerModeBlinkingLaserOn 0x63 +#define HidUsage_BS_TriggerModeContinuousLaserOn 0x64 +#define HidUsage_BS_TriggerModeLaseronwhilePulled 0x65 +#define HidUsage_BS_TriggerModeLaserstaysonafterrelease 0x66 +#define HidUsage_BS_CommitParameterstoNVM 0x6d +#define HidUsage_BS_ParameterScanning 0x6e +#define HidUsage_BS_ParametersChanged 0x6f +#define HidUsage_BS_Setparameterdefaultvalues 0x70 +#define HidUsage_BS_ScannerInCradle 0x75 +#define HidUsage_BS_ScannerInRange 0x76 +#define HidUsage_BS_AimDuration 0x7a +#define HidUsage_BS_GoodReadLampDuration 0x7b +#define HidUsage_BS_GoodReadLampIntensity 0x7c +#define HidUsage_BS_GoodReadLED 0x7d +#define HidUsage_BS_GoodReadToneFrequency 0x7e +#define HidUsage_BS_GoodReadToneLength 0x7f +#define HidUsage_BS_GoodReadToneVolume 0x80 +#define HidUsage_BS_NoReadMessage 0x82 +#define HidUsage_BS_NotonFileVolume 0x83 +#define HidUsage_BS_PowerupBeep 0x84 +#define HidUsage_BS_SoundErrorBeep 0x85 +#define HidUsage_BS_SoundGoodReadBeep 0x86 +#define HidUsage_BS_SoundNotOnFileBeep 0x87 +#define HidUsage_BS_GoodReadWhentoWrite 0x88 +#define HidUsage_BS_GRWTIAfterDecode 0x89 +#define HidUsage_BS_GRWTIBeepLampaftertransmit 0x8a +#define HidUsage_BS_GRWTINoBeepLampuseatall 0x8b +#define HidUsage_BS_BooklandEAN 0x91 +#define HidUsage_BS_ConvertEAN8to13Type 0x92 +#define HidUsage_BS_ConvertUPCAtoEAN13 0x93 +#define HidUsage_BS_ConvertUPCEtoA 0x94 +#define HidUsage_BS_EAN13 0x95 +#define HidUsage_BS_EAN8 0x96 +#define HidUsage_BS_EAN99128Mandatory 0x97 +#define HidUsage_BS_EAN99P5128Optional 0x98 +#define HidUsage_BS_EnableEANTwoLabel 0x99 +#define HidUsage_BS_UPCEAN 0x9a +#define HidUsage_BS_UPCEANCouponCode 0x9b +#define HidUsage_BS_UPCEANPeriodicals 0x9c +#define HidUsage_BS_UPCA 0x9d +#define HidUsage_BS_UPCAwith128Mandatory 0x9e +#define HidUsage_BS_UPCAwith128Optional 0x9f +#define HidUsage_BS_UPCAwithP5Optional 0xa0 +#define HidUsage_BS_UPCE 0xa1 +#define HidUsage_BS_UPCE1 0xa2 +#define HidUsage_BS_Periodical 0xa9 +#define HidUsage_BS_PeriodicalAutoDiscriminatePlus2 0xaa +#define HidUsage_BS_PeriodicalOnlyDecodewithPlus2 0xab +#define HidUsage_BS_PeriodicalIgnorePlus2 0xac +#define HidUsage_BS_PeriodicalAutoDiscriminatePlus5 0xad +#define HidUsage_BS_PeriodicalOnlyDecodewithPlus5 0xae +#define HidUsage_BS_PeriodicalIgnorePlus5 0xaf +#define HidUsage_BS_Check 0xb0 +#define HidUsage_BS_CheckDisablePrice 0xb1 +#define HidUsage_BS_CheckEnable4digitPrice 0xb2 +#define HidUsage_BS_CheckEnable5digitPrice 0xb3 +#define HidUsage_BS_CheckEnableEuropean4digitPrice 0xb4 +#define HidUsage_BS_CheckEnableEuropean5digitPrice 0xb5 +#define HidUsage_BS_EANTwoLabel 0xb7 +#define HidUsage_BS_EANThreeLabel 0xb8 +#define HidUsage_BS_EAN8FlagDigit1 0xb9 +#define HidUsage_BS_EAN8FlagDigit2 0xba +#define HidUsage_BS_EAN8FlagDigit3 0xbb +#define HidUsage_BS_EAN13FlagDigit1 0xbc +#define HidUsage_BS_EAN13FlagDigit2 0xbd +#define HidUsage_BS_EAN13FlagDigit3 0xbe +#define HidUsage_BS_AddEAN23LabelDefinition 0xbf +#define HidUsage_BS_ClearallEAN23LabelDefinitions 0xc0 +#define HidUsage_BS_Codabar 0xc3 +#define HidUsage_BS_Code128 0xc4 +#define HidUsage_BS_Code39 0xc7 +#define HidUsage_BS_Code93 0xc8 +#define HidUsage_BS_FullASCIIConversion 0xc9 +#define HidUsage_BS_Interleaved2of5 0xca +#define HidUsage_BS_ItalianPharmacyCode 0xcb +#define HidUsage_BS_MSIPlessey 0xcc +#define HidUsage_BS_Standard2of5IATA 0xcd +#define HidUsage_BS_Standard2of5 0xce +#define HidUsage_BS_TransmitStartStop 0xd3 +#define HidUsage_BS_TriOptic 0xd4 +#define HidUsage_BS_UCCEAN128 0xd5 +#define HidUsage_BS_CheckDigit 0xd6 +#define HidUsage_BS_CheckDigitDisable 0xd7 +#define HidUsage_BS_CheckDigitEnableInterleaved2of5OPCC 0xd8 +#define HidUsage_BS_CheckDigitEnableInterleaved2of5USS 0xd9 +#define HidUsage_BS_CheckDigitEnableStandard2of5OPCC 0xda +#define HidUsage_BS_CheckDigitEnableStandard2of5USS 0xdb +#define HidUsage_BS_CheckDigitEnableOneMSIPlessey 0xdc +#define HidUsage_BS_CheckDigitEnableTwoMSIPlessey 0xdd +#define HidUsage_BS_CheckDigitCodabarEnable 0xde +#define HidUsage_BS_CheckDigitCode39Enable 0xdf +#define HidUsage_BS_TransmitCheckDigit 0xf0 +#define HidUsage_BS_DisableCheckDigitTransmit 0xf1 +#define HidUsage_BS_EnableCheckDigitTransmit 0xf2 +#define HidUsage_BS_SymbologyIdentifier1 0xfb +#define HidUsage_BS_SymbologyIdentifier2 0xfc +#define HidUsage_BS_SymbologyIdentifier3 0xfd +#define HidUsage_BS_DecodedData 0xfe +#define HidUsage_BS_DecodeDataContinued 0xff +#define HidUsage_BS_BarSpaceData 0x100 +#define HidUsage_BS_ScannerDataAccuracy 0x101 +#define HidUsage_BS_RawDataPolarity 0x102 +#define HidUsage_BS_PolarityInvertedBarCode 0x103 +#define HidUsage_BS_PolarityNormalBarCode 0x104 +#define HidUsage_BS_MinimumLengthtoDecode 0x106 +#define HidUsage_BS_MaximumLengthtoDecode 0x107 +#define HidUsage_BS_DiscreteLengthtoDecode1 0x108 +#define HidUsage_BS_DiscreteLengthtoDecode2 0x109 +#define HidUsage_BS_DataLengthMethod 0x10a +#define HidUsage_BS_DLMethodReadany 0x10b +#define HidUsage_BS_DLMethodCheckinRange 0x10c +#define HidUsage_BS_DLMethodCheckforDiscrete 0x10d +#define HidUsage_BS_AztecCode 0x110 +#define HidUsage_BS_BC412 0x111 +#define HidUsage_BS_ChannelCode 0x112 +#define HidUsage_BS_Code16 0x113 +#define HidUsage_BS_Code32 0x114 +#define HidUsage_BS_Code49 0x115 +#define HidUsage_BS_CodeOne 0x116 +#define HidUsage_BS_Colorcode 0x117 +#define HidUsage_BS_DataMatrix 0x118 +#define HidUsage_BS_MaxiCode 0x119 +#define HidUsage_BS_MicroPDF 0x11a +#define HidUsage_BS_PDF417 0x11b +#define HidUsage_BS_PosiCode 0x11c +#define HidUsage_BS_QRCode 0x11d +#define HidUsage_BS_SuperCode 0x11e +#define HidUsage_BS_UltraCode 0x11f +#define HidUsage_BS_USD5SlugCode 0x120 +#define HidUsage_BS_VeriCode 0x121 +#define HidUsage_Sca_Scales 0x01 +#define HidUsage_Sca_ScaleDevice 0x20 +#define HidUsage_Sca_ScaleClass 0x21 +#define HidUsage_Sca_ScaleClassIMetric 0x22 +#define HidUsage_Sca_ScaleClassIIMetric 0x23 +#define HidUsage_Sca_ScaleClassIIIMetric 0x24 +#define HidUsage_Sca_ScaleClassIIILMetric 0x25 +#define HidUsage_Sca_ScaleClassIVMetric 0x26 +#define HidUsage_Sca_ScaleClassIIIEnglish 0x27 +#define HidUsage_Sca_ScaleClassIIILEnglish 0x28 +#define HidUsage_Sca_ScaleClassIVEnglish 0x29 +#define HidUsage_Sca_ScaleClassGeneric 0x2a +#define HidUsage_Sca_ScaleAttributeReport 0x30 +#define HidUsage_Sca_ScaleControlReport 0x31 +#define HidUsage_Sca_ScaleDataReport 0x32 +#define HidUsage_Sca_ScaleStatusReport 0x33 +#define HidUsage_Sca_ScaleWeightLimitReport 0x34 +#define HidUsage_Sca_ScaleStatisticsReport 0x35 +#define HidUsage_Sca_DataWeight 0x40 +#define HidUsage_Sca_DataScaling 0x41 +#define HidUsage_Sca_WeightUnit 0x50 +#define HidUsage_Sca_WeightUnitMilligram 0x51 +#define HidUsage_Sca_WeightUnitGram 0x52 +#define HidUsage_Sca_WeightUnitKilogram 0x53 +#define HidUsage_Sca_WeightUnitCarats 0x54 +#define HidUsage_Sca_WeightUnitTaels 0x55 +#define HidUsage_Sca_WeightUnitGrains 0x56 +#define HidUsage_Sca_WeightUnitPennyweights 0x57 +#define HidUsage_Sca_WeightUnitMetricTon 0x58 +#define HidUsage_Sca_WeightUnitAvoirTon 0x59 +#define HidUsage_Sca_WeightUnitTroyOunce 0x5a +#define HidUsage_Sca_WeightUnitOunce 0x5b +#define HidUsage_Sca_WeightUnitPound 0x5c +#define HidUsage_Sca_CalibrationCount 0x60 +#define HidUsage_Sca_ReZeroCount 0x61 +#define HidUsage_Sca_ScaleStatus 0x70 +#define HidUsage_Sca_ScaleStatusFault 0x71 +#define HidUsage_Sca_ScaleStatusStableatCenterofZero 0x72 +#define HidUsage_Sca_ScaleStatusInMotion 0x73 +#define HidUsage_Sca_ScaleStatusWeightStable 0x74 +#define HidUsage_Sca_ScaleStatusUnderZero 0x75 +#define HidUsage_Sca_ScaleStatusOverWeightLimit 0x76 +#define HidUsage_Sca_ScaleStatusRequiresCalibration 0x77 +#define HidUsage_Sca_ScaleStatusRequiresRezeroing 0x78 +#define HidUsage_Sca_ZeroScale 0x80 +#define HidUsage_Sca_EnforcedZeroReturn 0x81 +#define HidUsage_MSR_MSRDeviceReadOnly 0x01 +#define HidUsage_MSR_Track1Length 0x11 +#define HidUsage_MSR_Track2Length 0x12 +#define HidUsage_MSR_Track3Length 0x13 +#define HidUsage_MSR_TrackJISLength 0x14 +#define HidUsage_MSR_TrackData 0x20 +#define HidUsage_MSR_Track1Data 0x21 +#define HidUsage_MSR_Track2Data 0x22 +#define HidUsage_MSR_Track3Data 0x23 +#define HidUsage_MSR_TrackJISData 0x24 +#define HidUsage_CC_CameraAutofocus 0x20 +#define HidUsage_CC_CameraShutter 0x21 +#define HidUsage_Arc_GeneralPurposeIOCard 0x01 +#define HidUsage_Arc_CoinDoor 0x02 +#define HidUsage_Arc_WatchdogTimer 0x03 +#define HidUsage_Arc_GeneralPurposeAnalogInputState 0x30 +#define HidUsage_Arc_GeneralPurposeDigitalInputState 0x31 +#define HidUsage_Arc_GeneralPurposeOpticalInputState 0x32 +#define HidUsage_Arc_GeneralPurposeDigitalOutputState 0x33 +#define HidUsage_Arc_NumberofCoinDoors 0x34 +#define HidUsage_Arc_CoinDrawerDropCount 0x35 +#define HidUsage_Arc_CoinDrawerStart 0x36 +#define HidUsage_Arc_CoinDrawerService 0x37 +#define HidUsage_Arc_CoinDrawerTilt 0x38 +#define HidUsage_Arc_CoinDoorTest 0x39 +#define HidUsage_Arc_CoinDoorLockout 0x40 +#define HidUsage_Arc_WatchdogTimeout 0x41 +#define HidUsage_Arc_WatchdogAction 0x42 +#define HidUsage_Arc_WatchdogReboot 0x43 +#define HidUsage_Arc_WatchdogRestart 0x44 +#define HidUsage_Arc_AlarmInput 0x45 +#define HidUsage_Arc_CoinDoorCounter 0x46 +#define HidUsage_Arc_IODirectionMapping 0x47 +#define HidUsage_Arc_SetIODirectionMapping 0x48 +#define HidUsage_Arc_ExtendedOpticalInputState 0x49 +#define HidUsage_Arc_PinPadInputState 0x4a +#define HidUsage_Arc_PinPadStatus 0x4b +#define HidUsage_Arc_PinPadOutput 0x4c +#define HidUsage_Arc_PinPadCommand 0x4d +#define HidUsage_FIDOA_U2FAuthenticatorDevice 0x01 +#define HidUsage_FIDOA_InputReportData 0x20 +#define HidUsage_FIDOA_OutputReportData 0x21 From 3b13d2803c5ac144cd9af9dabee935b69a480fd6 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:51 +0200 Subject: [PATCH 235/295] hid: bpf: hid_bpf_helpers: add helper for having read/write udev properties JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit bb8be68d1280eff1abb697136b54576af218b266 Author: Benjamin Tissoires Date: Fri Apr 3 18:12:23 2026 +0200 hid: bpf: hid_bpf_helpers: add helper for having read/write udev properties We want udev-hid-bpf to be able to set udev properties by printing them out after the BPF object has been loaded. This allows to make a query to the device, and set a udev prop based on the answer. Because the way udev works, the properties are cleared on bind/unbind, and we need a way to store them. After several attempts to keep the property alive without re-running the udev-hid-bpf tool to communicate with the device, it came out that HID-BPF maps are pinned in the bpffs and we can then query them. So the following would export a UDEV property in the bpffs: EXPORT_UDEV_PROP(HID_FOO, 32); SEC("syscall") int probe(struct hid_bpf_probe_args *ctx) { const char *foo = "foo"; UDEV_PROP_SPRINTF(HID_FOO, "%s", foo); return 0; } Then, we can debug it with a simple cat: sudo cat /sys/fs/bpf/hid/.../UDEV_PROP_HID_FOO 0: {['f','o','o',],} This way, the property is always accessible without talking to the device Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/220 Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/progs/hid_bpf_helpers.h | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/drivers/hid/bpf/progs/hid_bpf_helpers.h b/drivers/hid/bpf/progs/hid_bpf_helpers.h index 298c2bb69b64..db5fcd77ed33 100644 --- a/drivers/hid/bpf/progs/hid_bpf_helpers.h +++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h @@ -342,6 +342,44 @@ DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock); #define hid_bpf_cpu_to_be32(x) bpf_htonl(x) #define hid_bpf_cpu_to_be64(x) bpf_cpu_to_be64(x) +/* + * The following macros are helpers for exporting udev properties: + * + * EXPORT_UDEV_PROP(name, len) generates: + * - a map with a single element UDEV_PROP_##name, of size len + * - a const global declaration of that len: SIZEOF_##name + * + * udev_prop_ptr(name) retrieves the data pointer behind the map. + * + * UDEV_PROP_SPRINTF(name, fmt, ...) writes data into the udev property. + * + * Can be used as such: + * EXPORT_UDEV_PROP(HID_FOO, 32); + * + * SEC("syscall") + * int probe(struct hid_bpf_probe_args *ctx) + * { + * const char *foo = "foo"; + * UDEV_PROP_SPRINTF(HID_FOO, "%s", foo); + * + * return 0; + * } + */ +#define EXPORT_UDEV_PROP(name, len) \ + const __u32 SIZEOF_##name = len; \ + struct COMBINE(udev_prop, __LINE__) { \ + __uint(type, BPF_MAP_TYPE_ARRAY); \ + __uint(max_entries, 1); \ + __type(key, __u32); \ + __type(value, __u8[len]); \ + } UDEV_PROP_##name SEC(".maps"); + +#define udev_prop_ptr(name) \ + bpf_map_lookup_elem(&UDEV_PROP_##name, &(__u32){0}) + +#define UDEV_PROP_SPRINTF(name, fmt, ...) \ + BPF_SNPRINTF(udev_prop_ptr(name), SIZEOF_##name, fmt, ##__VA_ARGS__) + static inline __maybe_unused __u16 field_start_byte(struct hid_rdesc_field *field) { return field->bits_start / 8; From 76197e992d264132e156587ff6e6583b0d9d8112 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:52 +0200 Subject: [PATCH 236/295] HID: bpf: add a BPF to get the touchpad type JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit af79443be6c6f1732c2912aa0d50f5bb71c70cf9 Author: Benjamin Tissoires Date: Fri Apr 3 18:12:24 2026 +0200 HID: bpf: add a BPF to get the touchpad type Currently the kernel is scheduled to do this call by itself, but it requires a kernel v6.18 at least to have the INPUT_PROP set. For older kernels, we can try to query the property from a HID-BPF probe, and set a udev property based on that. This way we can provide the information to old kernels without modifying them. Signed-off-by: Benjamin Tissoires Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/220 Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/progs/Generic__touchpad.bpf.c | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 drivers/hid/bpf/progs/Generic__touchpad.bpf.c diff --git a/drivers/hid/bpf/progs/Generic__touchpad.bpf.c b/drivers/hid/bpf/progs/Generic__touchpad.bpf.c new file mode 100644 index 000000000000..b9f2cac91724 --- /dev/null +++ b/drivers/hid/bpf/progs/Generic__touchpad.bpf.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2025 Benjamin Tissoires + */ + +#include "vmlinux.h" +#include "hid_bpf.h" +#include "hid_bpf_helpers.h" +#include "hid_report_helpers.h" +#include "hid_usages.h" +#include + +HID_BPF_CONFIG( + HID_DEVICE(BUS_ANY, HID_GROUP_MULTITOUCH_WIN_8, HID_VID_ANY, HID_PID_ANY), +); + +EXPORT_UDEV_PROP(HID_DIGITIZER_PAD_TYPE, 32); + +__u8 hw_req_buf[1024]; + +/* to be filled by udev-hid-bpf */ +struct hid_rdesc_descriptor HID_REPORT_DESCRIPTOR; + +SEC("syscall") +int probe(struct hid_bpf_probe_args *ctx) +{ + struct hid_rdesc_report *pad_type_feature = NULL; + struct hid_rdesc_field *pad_type = NULL; + struct hid_rdesc_report *feature; + struct hid_bpf_ctx *hid_ctx; + char *pad_type_str = ""; + int ret; + + hid_bpf_for_each_feature_report(&HID_REPORT_DESCRIPTOR, feature) { + struct hid_rdesc_field *field; + + hid_bpf_for_each_field(feature, field) { + if (field->usage_page == HidUsagePage_Digitizers && + field->usage_id == HidUsage_Dig_PadType) { + pad_type = field; + pad_type_feature = feature; + break; + } + } + if (pad_type) + break; + } + + if (!pad_type || !pad_type_feature) { + ctx->retval = -EINVAL; + return 0; + } + + hid_ctx = hid_bpf_allocate_context(ctx->hid); + + if (!hid_ctx) + return -1; /* EPERM check */ + + hw_req_buf[0] = pad_type_feature->report_id; + + ret = hid_bpf_hw_request(hid_ctx, hw_req_buf, sizeof(hw_req_buf), + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + hid_bpf_release_context(hid_ctx); + + if (ret < 0) { + ctx->retval = ret; + return 0; + } + + ctx->retval = 0; + + switch (EXTRACT_BITS(hw_req_buf, pad_type)) { + case 0: + pad_type_str = "Clickpad"; + break; + case 1: + pad_type_str = "Pressurepad"; + break; + case 2: + pad_type_str = "Discrete"; + break; + default: + pad_type_str = "Unknown"; + } + + UDEV_PROP_SPRINTF(HID_DIGITIZER_PAD_TYPE, "%s", pad_type_str); + + return 0; +} + +char _license[] SEC("license") = "GPL"; From 251483fc6cc3b6ee91929d47df37884719b329a7 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:52 +0200 Subject: [PATCH 237/295] HID: bpf: Add support for the Huion KeyDial K20 over bluetooth JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit cc3993d3484672635d14a9e5b17ec53920a34407 Author: Benjamin Tissoires Date: Fri Apr 3 18:12:25 2026 +0200 HID: bpf: Add support for the Huion KeyDial K20 over bluetooth When connected over bluetooth this device is just different enough that forcing it into the same source file as the USB connection doesn't gain us much benefit. So let's duplicate this. Code and tests originally produced by Claude code. Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/work_items/69 Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/201 Signed-off-by: Peter Hutterer Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../progs/Huion__KeydialK20-Bluetooth.bpf.c | 492 ++++++++++++++++++ 1 file changed, 492 insertions(+) create mode 100644 drivers/hid/bpf/progs/Huion__KeydialK20-Bluetooth.bpf.c diff --git a/drivers/hid/bpf/progs/Huion__KeydialK20-Bluetooth.bpf.c b/drivers/hid/bpf/progs/Huion__KeydialK20-Bluetooth.bpf.c new file mode 100644 index 000000000000..d0769e990039 --- /dev/null +++ b/drivers/hid/bpf/progs/Huion__KeydialK20-Bluetooth.bpf.c @@ -0,0 +1,492 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2024 Red Hat, Inc + */ + +#include "vmlinux.h" +#include "hid_bpf.h" +#include "hid_bpf_helpers.h" +#include "hid_report_helpers.h" +#include + +#define VID_HUION 0x256C +#define PID_KEYDIAL_K20_BLUETOOTH 0x8251 + +HID_BPF_CONFIG( + HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_HUION, PID_KEYDIAL_K20_BLUETOOTH), +); + +/* This is the same device as in 0010-Huion__KeydialK20 but connected via Bluetooth. + * It does not need (to support?) switching to a vendor mode so we just modify the + * existing mode. + * + * By default it exports two hidraw nodes, only the second one sends events. + * + * This is the first hidraw node which we disable: + * + * # Keydial mini-050 + * # Report descriptor length: 114 bytes + * # Bytes // Field Name Offset + * # ---------------------------------------------------------------------------------- + * # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 0 + * # 🭬 0x09, 0x0e, // Usage (System Multi-Axis Controller) 2 + * # 0xa1, 0x01, // Collection (Application) 4 + * # ┅ 0x85, 0x03, // Report ID (3) 6 + * # 🮥 0x05, 0x0d, // Usage Page (Digitizers) 8 + * # 0x75, 0x08, // Report Size (8) 10 + * # 0x95, 0x01, // Report Count (1) 12 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 14 + * # 🭬 0x09, 0x21, // Usage (Puck) 16 + * # 0xa1, 0x02, // Collection (Logical) 18 + * # 0x15, 0x00, // Logical Minimum (0) 20 + * # 0x25, 0x01, // Logical Maximum (1) 22 + * # 0x75, 0x01, // Report Size (1) 24 + * # 0x95, 0x01, // Report Count (1) 26 + * # 0xa1, 0x00, // Collection (Physical) 28 + * # 🮥 0x05, 0x09, // Usage Page (Button) 30 + * # 🭬 0x09, 0x01, // Usage (Button 1) 32 + * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 34 + * # 🮥 0x05, 0x0d, // Usage Page (Digitizers) 36 + * # 🭬 0x09, 0x33, // Usage (Touch) 38 + * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 40 + * # 0x95, 0x06, // Report Count (6) 42 + * # ┇ 0x81, 0x03, // Input (Cnst,Var,Abs) 44 + * # 0xa1, 0x02, // Collection (Logical) 46 + * # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 48 + * # 🭬 0x09, 0x37, // Usage (Dial) 50 + * # 0x16, 0x00, 0x80, // Logical Minimum (32768) 52 + * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 55 + * # 0x75, 0x10, // Report Size (16) 58 + * # 0x95, 0x01, // Report Count (1) 60 + * # ┇ 0x81, 0x06, // Input (Data,Var,Rel) 62 + * # 0x35, 0x00, // Physical Minimum (0) 64 + * # 0x46, 0x10, 0x0e, // Physical Maximum (3600) 66 + * # 0x15, 0x00, // Logical Minimum (0) 69 + * # 0x26, 0x10, 0x0e, // Logical Maximum (3600) 71 + * # 🭬 0x09, 0x48, // Usage (Resolution Multiplier) 74 + * # ║ 0xb1, 0x02, // Feature (Data,Var,Abs) 76 + * # 0x45, 0x00, // Physical Maximum (0) 78 + * # 0xc0, // End Collection 80 + * # 0x75, 0x08, // Report Size (8) 81 + * # 0x95, 0x01, // Report Count (1) 83 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 85 + * # 0x75, 0x08, // Report Size (8) 87 + * # 0x95, 0x01, // Report Count (1) 89 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 91 + * # 0x75, 0x08, // Report Size (8) 93 + * # 0x95, 0x01, // Report Count (1) 95 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 97 + * # 0x75, 0x08, // Report Size (8) 99 + * # 0x95, 0x01, // Report Count (1) 101 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 103 + * # 0x75, 0x08, // Report Size (8) 105 + * # 0x95, 0x01, // Report Count (1) 107 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 109 + * # 0xc0, // End Collection 111 + * # 0xc0, // End Collection 112 + * # 0xc0, // End Collection 113 + * R: 114 05 01 09 0e a1 01 85 03 05 0d 75 08 95 01 81 01 09 21 a1 02 15 00 25 01 75 01 95 01 a1 00 05 09 09 01 81 02 05 0d 09 33 81 02 95 06 81 03 a1 02 05 01 09 37 16 00 80 26 ff 7f 75 10 95 01 81 06 35 00 46 10 0e 15 00 26 10 0e 09 48 b1 02 45 00 c0 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 c0 c0 c0 + * N: Keydial mini-050 + * I: 5 256c 8251 + * + * The second hidraw node is what sends events: + * + * # Keydial mini-050 + * # Report descriptor length: 160 bytes + * # Bytes // Field Name Offset + * # ---------------------------------------------------------------------------------- + * # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 0 + * # 🭬 0x09, 0x06, // Usage (Keyboard) 2 + * # 0xa1, 0x01, // Collection (Application) 4 + * # ┅ 0x85, 0x01, // Report ID (1) 6 + * # 🮥 0x05, 0x07, // Usage Page (Keyboard/Keypad) 8 + * # 🭬 0x19, 0xe0, // Usage Minimum (224) 10 + * # 🭬 0x29, 0xe7, // Usage Maximum (231) 12 + * # 0x15, 0x00, // Logical Minimum (0) 14 + * # 0x25, 0x01, // Logical Maximum (1) 16 + * # 0x75, 0x01, // Report Size (1) 18 + * # 0x95, 0x08, // Report Count (8) 20 + * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 22 + * # 0x95, 0x01, // Report Count (1) 24 + * # 0x75, 0x08, // Report Size (8) 26 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 28 + * # 0x95, 0x05, // Report Count (5) 30 + * # 0x75, 0x01, // Report Size (1) 32 + * # 🮥 0x05, 0x08, // Usage Page (LED) 34 + * # 🭬 0x19, 0x01, // Usage Minimum (1) 36 + * # 🭬 0x29, 0x05, // Usage Maximum (5) 38 + * # ┊ 0x91, 0x02, // Output (Data,Var,Abs) 40 + * # 0x95, 0x01, // Report Count (1) 42 + * # 0x75, 0x03, // Report Size (3) 44 + * # ┊ 0x91, 0x01, // Output (Cnst,Arr,Abs) 46 + * # 0x95, 0x06, // Report Count (6) 48 + * # 0x75, 0x08, // Report Size (8) 50 + * # 0x15, 0x00, // Logical Minimum (0) 52 + * # 0x25, 0xf1, // Logical Maximum (241) 54 + * # 🮥 0x05, 0x07, // Usage Page (Keyboard/Keypad) 56 + * # 🭬 0x19, 0x00, // Usage Minimum (0) 58 + * # 🭬 0x29, 0xf1, // Usage Maximum (241) 60 + * # ┇ 0x81, 0x00, // Input (Data,Arr,Abs) 62 + * # 0xc0, // End Collection 64 + * # 🮥 0x05, 0x0c, // Usage Page (Consumer) 65 + * # 🭬 0x09, 0x01, // Usage (Consumer Control) 67 + * # 0xa1, 0x01, // Collection (Application) 69 + * # ┅ 0x85, 0x02, // Report ID (2) 71 + * # 🮥 0x05, 0x0c, // Usage Page (Consumer) 73 + * # 🭬 0x19, 0x00, // Usage Minimum (0) 75 + * # 🭬 0x2a, 0x80, 0x03, // Usage Maximum (896) 77 + * # 0x15, 0x00, // Logical Minimum (0) 80 + * # 0x26, 0x80, 0x03, // Logical Maximum (896) 82 + * # 0x75, 0x10, // Report Size (16) 85 + * # 0x95, 0x01, // Report Count (1) 87 + * # ┇ 0x81, 0x00, // Input (Data,Arr,Abs) 89 + * # 0xc0, // End Collection 91 + * # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 92 + * # 🭬 0x09, 0x02, // Usage (Mouse) 94 + * # 0xa1, 0x01, // Collection (Application) 96 + * # 🭬 0x09, 0x01, // Usage (Pointer) 98 + * # ┅ 0x85, 0x05, // Report ID (5) 100 + * # 0xa1, 0x00, // Collection (Physical) 102 + * # 🮥 0x05, 0x09, // Usage Page (Button) 104 + * # 🭬 0x19, 0x01, // Usage Minimum (1) 106 + * # 🭬 0x29, 0x05, // Usage Maximum (5) 108 + * # 0x15, 0x00, // Logical Minimum (0) 110 + * # 0x25, 0x01, // Logical Maximum (1) 112 + * # 0x95, 0x05, // Report Count (5) 114 + * # 0x75, 0x01, // Report Size (1) 116 + * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 118 + * # 0x95, 0x01, // Report Count (1) 120 + * # 0x75, 0x03, // Report Size (3) 122 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 124 + * # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 126 + * # 🭬 0x09, 0x30, // Usage (X) 128 + * # 🭬 0x09, 0x31, // Usage (Y) 130 + * # 0x16, 0x01, 0x80, // Logical Minimum (32769) 132 + * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 135 + * # 0x75, 0x10, // Report Size (16) 138 + * # 0x95, 0x02, // Report Count (2) 140 + * # ┇ 0x81, 0x06, // Input (Data,Var,Rel) 142 + * # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 144 + * # 🭬 0x09, 0x38, // Usage (Wheel) 146 + * # 0x15, 0x81, // Logical Minimum (129) 148 + * # 0x25, 0x7f, // Logical Maximum (127) 150 + * # 0x95, 0x01, // Report Count (1) 152 + * # 0x75, 0x08, // Report Size (8) 154 + * # ┇ 0x81, 0x06, // Input (Data,Var,Rel) 156 + * # 0xc0, // End Collection 158 + * # 0xc0, // End Collection 159 + * R: 160 05 01 09 06 a1 01 85 01 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 95 01 75 08 81 01 95 05 75 01 05 08 19 01 29 05 91 02 95 01 75 03 91 01 95 06 75 08 15 00 25 f1 05 07 19 00 29 f1 81 00 c0 05 0c 09 01 a1 01 85 02 05 0c 19 00 2a 80 03 15 00 26 80 03 75 10 95 01 81 00 c0 05 01 09 02 a1 01 09 01 85 05 a1 00 05 09 19 01 29 05 15 00 25 01 95 05 75 01 81 02 95 01 75 03 81 01 05 01 09 30 09 31 16 01 80 26 ff 7f 75 10 95 02 81 06 05 01 09 38 15 81 25 7f 95 01 75 08 81 06 c0 c0 + * N: Keydial mini-050 + * I: 5 256c 8251 + * # Report descriptor: + * # ------- Input Report ------- + * # ░ Report ID: 1 + * # ░ | Report size: 72 bits + * # ░ Bit: 8 Usage: 0007/00e0: Keyboard/Keypad / Keyboard LeftControl Logical Range: 0..=1 + * # ░ Bit: 9 Usage: 0007/00e1: Keyboard/Keypad / Keyboard LeftShift Logical Range: 0..=1 + * # ░ Bit: 10 Usage: 0007/00e2: Keyboard/Keypad / Keyboard LeftAlt Logical Range: 0..=1 + * # ░ Bit: 11 Usage: 0007/00e3: Keyboard/Keypad / Keyboard Left GUI Logical Range: 0..=1 + * # ░ Bit: 12 Usage: 0007/00e4: Keyboard/Keypad / Keyboard RightControl Logical Range: 0..=1 + * # ░ Bit: 13 Usage: 0007/00e5: Keyboard/Keypad / Keyboard RightShift Logical Range: 0..=1 + * # ░ Bit: 14 Usage: 0007/00e6: Keyboard/Keypad / Keyboard RightAlt Logical Range: 0..=1 + * # ░ Bit: 15 Usage: 0007/00e7: Keyboard/Keypad / Keyboard Right GUI Logical Range: 0..=1 + * # ░ Bits: 16..=23 ######### Padding + * # ░ Bits: 24..=71 Usages: Logical Range: 0..=241 + * # ░ 0007/0000: + * # ░ 0007/0001: Keyboard/Keypad / ErrorRollOver + * # ░ 0007/0002: Keyboard/Keypad / POSTFail + * # ░ 0007/0003: Keyboard/Keypad / ErrorUndefined + * # ░ 0007/0004: Keyboard/Keypad / Keyboard A + * # ░ ... use --full to see all usages + * # ------- Input Report ------- + * # ▒ Report ID: 2 + * # ▒ | Report size: 24 bits + * # ▒ Bits: 8..=23 Usages: Logical Range: 0..=896 + * # ▒ 000c/0000: + * # ▒ 000c/0001: Consumer / Consumer Control + * # ▒ 000c/0002: Consumer / Numeric Key Pad + * # ▒ 000c/0003: Consumer / Programmable Buttons + * # ▒ 000c/0004: Consumer / Microphone + * # ▒ ... use --full to see all usages + * # ------- Input Report ------- + * # ▞ Report ID: 5 + * # ▞ | Report size: 56 bits + * # ▞ Bit: 8 Usage: 0009/0001: Button / Button 1 Logical Range: 0..=1 + * # ▞ Bit: 9 Usage: 0009/0002: Button / Button 2 Logical Range: 0..=1 + * # ▞ Bit: 10 Usage: 0009/0003: Button / Button 3 Logical Range: 0..=1 + * # ▞ Bit: 11 Usage: 0009/0004: Button / Button 4 Logical Range: 0..=1 + * # ▞ Bit: 12 Usage: 0009/0005: Button / Button 5 Logical Range: 0..=1 + * # ▞ Bits: 13..=15 ######### Padding + * # ▞ Bits: 16..=31 Usage: 0001/0030: Generic Desktop / X Logical Range: 32769..=32767 + * # ▞ Bits: 32..=47 Usage: 0001/0031: Generic Desktop / Y Logical Range: 32769..=32767 + * # ▞ Bits: 48..=55 Usage: 0001/0038: Generic Desktop / Wheel Logical Range: 129..=127 + * # ------- Output Report ------- + * # ░ Report ID: 1 + * # ░ | Report size: 16 bits + * # ░ Bit: 8 Usage: 0008/0001: LED / Num Lock Logical Range: 0..=1 + * # ░ Bit: 9 Usage: 0008/0002: LED / Caps Lock Logical Range: 0..=1 + * # ░ Bit: 10 Usage: 0008/0003: LED / Scroll Lock Logical Range: 0..=1 + * # ░ Bit: 11 Usage: 0008/0004: LED / Compose Logical Range: 0..=1 + * # ░ Bit: 12 Usage: 0008/0005: LED / Kana Logical Range: 0..=1 + * # ░ Bits: 13..=15 ######### Padding + * ############################################################################## + * # Event nodes: + * # - /dev/input/event12: "Keydial mini-050 Keyboard" + * # - /dev/input/event14: "Keydial mini-050 Mouse" + * ############################################################################## + * # Recorded events below in format: + * # E: . [bytes ...] + * # + * + * - Report ID 1 sends keyboard shortcuts when pressing the buttons, e.g. + * + * # ░ Report ID: 1 / + * # ░ Keyboard LeftControl: 0 |Keyboard LeftShift: 0 |Keyboard LeftAlt: 0 |Keyboard Left GUI: 0 |Keyboard RightControl: 0 |Keyboard RightShift: 0 |Keyboard RightAlt: 0 |Keyboard Right GUI: 0 |<8 bits padding> |0007/0000: 0| Keyboard K: 14| 0007/0000: 0| 0007/0000: 0| 0007/0000: 0| 0007/0000: 0 + * E: 000000.000292 9 01 00 00 00 0e 00 00 00 00 + * + * - Report ID 2 sends the button inside the wheel/dial thing + * # ▒ Report ID: 2 / + * # ▒ Play/Pause: 205 + * E: 000134.347845 3 02 cd 00 + * # ▒ Report ID: 2 / + * # ▒ 000c/0000: 0 + * E: 000134.444965 3 02 00 00 + * + * - Report ID 5 sends the wheel relative events (always a double-event with the second as zero) + * # ▞ Report ID: 5 / + * # ▞ Button 1: 0 |Button 2: 0 |Button 3: 0 |Button 4: 0 |Button 5: 0 |<3 bits padding> |X: 0 |Y: 0 |Wheel: 255 + * E: 000064.859915 7 05 00 00 00 00 00 ff + * # ▞ Report ID: 5 / + * # ▞ Button 1: 0 |Button 2: 0 |Button 3: 0 |Button 4: 0 |Button 5: 0 |<3 bits padding> |X: 0 |Y: 0 |Wheel: 0 + * E: 000064.882009 7 05 00 00 00 00 00 00 + */ + +#define BT_PAD_REPORT_DESCRIPTOR_LENGTH 160 +#define BT_PUCK_REPORT_DESCRIPTOR_LENGTH 114 // This one doesn't send events +#define BT_PAD_KBD_REPORT_ID 1 +#define BT_PAD_CC_REPORT_ID 2 +#define BT_PAD_MOUSE_REPORT_ID 5 +#define BT_PAD_KBD_REPORT_LENGTH 9 +#define BT_PAD_CC_REPORT_LENGTH 3 +#define BT_PAD_MOUSE_REPORT_LENGTH 7 +#define OUR_REPORT_ID 11 /* "randomly" picked report ID for our reports */ + +__u32 last_button_state = 0; + +static const __u8 disabled_rdesc_puck[] = { + FixedSizeVendorReport(BT_PUCK_REPORT_DESCRIPTOR_LENGTH) +}; + +static const __u8 fixed_rdesc_pad[] = { + UsagePage_GenericDesktop + Usage_GD_Keypad + CollectionApplication( + // Byte 0 + ReportId(OUR_REPORT_ID) + UsagePage_Digitizers + Usage_Dig_TabletFunctionKeys + CollectionPhysical( + // Byte 1 is a button so we look like a tablet + Usage_Dig_BarrelSwitch // BTN_STYLUS, needed so we get to be a tablet pad + ReportCount(1) + ReportSize(1) + Input(Var|Abs) + ReportCount(7) // Padding + Input(Const) + // Bytes 2/3 - x/y just exist so we get to be a tablet pad + UsagePage_GenericDesktop + Usage_GD_X + Usage_GD_Y + LogicalMinimum_i8(0x0) + LogicalMaximum_i8(0x1) + ReportCount(2) + ReportSize(8) + Input(Var|Abs) + // Bytes 4-7 are the button state for 19 buttons + pad out to u32 + // We send the first 10 buttons as buttons 1-10 which is BTN_0 -> BTN_9 + UsagePage_Button + UsageMinimum_i8(1) + UsageMaximum_i8(10) + LogicalMinimum_i8(0x0) + LogicalMaximum_i8(0x1) + ReportCount(10) + ReportSize(1) + Input(Var|Abs) + // We send the other 9 buttons as buttons 0x31 and above -> BTN_A - BTN_TL2 + UsageMinimum_i8(0x31) + UsageMaximum_i8(0x3a) + ReportCount(9) + ReportSize(1) + Input(Var|Abs) + ReportCount(13) + ReportSize(1) + Input(Const) // padding + // Byte 8 is the wheel + UsagePage_GenericDesktop + Usage_GD_Wheel + LogicalMinimum_i8(-1) + LogicalMaximum_i8(1) + ReportCount(1) + ReportSize(8) + Input(Var|Rel) + ) + // Make sure we match our original report length + FixedSizeVendorReport(BT_PAD_KBD_REPORT_LENGTH) + ) +}; + +SEC(HID_BPF_RDESC_FIXUP) +int BPF_PROG(k20_bt_fix_rdesc, struct hid_bpf_ctx *hctx) +{ + __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */); + __s32 rdesc_size = hctx->size; + + if (!data) + return 0; /* EPERM check */ + + if (rdesc_size == BT_PAD_REPORT_DESCRIPTOR_LENGTH) { + __builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad)); + return sizeof(fixed_rdesc_pad); + } + if (rdesc_size == BT_PUCK_REPORT_DESCRIPTOR_LENGTH) { + // This hidraw node doesn't send anything and can be ignored + __builtin_memcpy(data, disabled_rdesc_puck, sizeof(disabled_rdesc_puck)); + return sizeof(disabled_rdesc_puck); + } + + return 0; +} + +SEC(HID_BPF_DEVICE_EVENT) +int BPF_PROG(k20_bt_fix_events, struct hid_bpf_ctx *hctx) +{ + __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 12 /* size */); + struct pad_report { + __u8 report_id; + __u8 btn_stylus:1; + __u8 pad:7; + __u8 x; + __u8 y; + __u32 buttons; + __u8 wheel; + } __packed * pad_report = (struct pad_report *)data; + + if (!data) + return 0; /* EPERM check */ + + /* Report ID 1 - Keyboard events (button presses) */ + if (data[0] == BT_PAD_KBD_REPORT_ID) { + const __u8 button_mapping[] = { + 0x0e, /* Button 1: K */ + 0x0a, /* Button 2: G */ + 0x0f, /* Button 3: L */ + 0x4c, /* Button 4: Delete */ + 0x0c, /* Button 5: I */ + 0x07, /* Button 6: D */ + 0x05, /* Button 7: B */ + 0x08, /* Button 8: E */ + 0x16, /* Button 9: S */ + 0x1d, /* Button 10: Z */ + 0x06, /* Button 11: C */ + 0x19, /* Button 12: V */ + 0xff, /* Button 13: LeftControl */ + 0xff, /* Button 14: LeftAlt */ + 0xff, /* Button 15: LeftShift */ + 0x28, /* Button 16: Return Enter */ + 0x2c, /* Button 17: Spacebar */ + 0x11, /* Button 18: N */ + }; + + __u8 modifiers = data[1]; + __u32 buttons = 0; + + if (modifiers & 0x01) { /* Control */ + buttons |= BIT(12); + } + if (modifiers & 0x02) { /* Shift */ + buttons |= BIT(14); + } + if (modifiers & 0x04) { /* Alt */ + buttons |= BIT(13); + } + + for (int i = 4; i < BT_PAD_KBD_REPORT_LENGTH; i++) { + if (!data[i]) + break; + + for (size_t b = 0; b < ARRAY_SIZE(button_mapping); b++) { + if (data[i] != 0xff && data[i] == button_mapping[b]) { + buttons |= BIT(b); + break; + } + } + } + + last_button_state = buttons; + + pad_report->report_id = OUR_REPORT_ID; + pad_report->btn_stylus = 0; + pad_report->x = 0; + pad_report->y = 0; + pad_report->buttons = buttons; + pad_report->wheel = 0; + + return sizeof(struct pad_report); + } + + /* Report ID 2 - Consumer control events (the button inside the wheel) */ + if (data[0] == BT_PAD_CC_REPORT_ID) { + const __u8 PlayPause = 0xcd; + + if (data[1] == PlayPause) + last_button_state |= BIT(18); + else + last_button_state &= ~BIT(18); + + pad_report->report_id = OUR_REPORT_ID; + pad_report->btn_stylus = 0; + pad_report->x = 0; + pad_report->y = 0; + pad_report->buttons = last_button_state; + pad_report->wheel = 0; + + return sizeof(struct pad_report); + } + + /* Report ID 5 - Mouse events (wheel rotation) */ + if (data[0] == BT_PAD_MOUSE_REPORT_ID) { + __u8 wheel_delta = data[6]; + + pad_report->report_id = OUR_REPORT_ID; + pad_report->btn_stylus = 0; + pad_report->x = 0; + pad_report->y = 0; + pad_report->buttons = last_button_state; + pad_report->wheel = wheel_delta; + + return sizeof(struct pad_report); + } + + return 0; +} + +HID_BPF_OPS(keydial_k20_bluetooth) = { + .hid_device_event = (void *)k20_bt_fix_events, + .hid_rdesc_fixup = (void *)k20_bt_fix_rdesc, +}; + +SEC("syscall") +int probe(struct hid_bpf_probe_args *ctx) +{ + switch (ctx->rdesc_size) { + case BT_PAD_REPORT_DESCRIPTOR_LENGTH: + case BT_PUCK_REPORT_DESCRIPTOR_LENGTH: + ctx->retval = 0; + break; + default: + ctx->retval = -EINVAL; + } + + return 0; +} + +char _license[] SEC("license") = "GPL"; From 4ddbc6906dfede8558d743fbf422df1538008d36 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:53 +0200 Subject: [PATCH 238/295] bpf: Add fix for Trust Philips SPK6327 (145f:024b) modifier keys JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 30fb45cc2e4aa1b215e0b4f5aeb757128811a3ff Author: muhammed Rishal Date: Fri Apr 3 18:12:26 2026 +0200 bpf: Add fix for Trust Philips SPK6327 (145f:024b) modifier keys The Trust Philips SPK6327 keyboard (USB ID 145f:024b) has a broken HID descriptor on interface 1. Byte 101 is 0x00 (Input Array) but should be 0x02 (Input Variable), causing LCtrl, LAlt, Super, RAlt, RCtrl and RShift to all report as LShift on Linux. This BPF fix patches byte 101 at runtime fixing all affected modifier keys. Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/234 Signed-off-by: muhammed Rishal Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- .../bpf/progs/Trust__Philips-SPK6327.bpf.c | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 drivers/hid/bpf/progs/Trust__Philips-SPK6327.bpf.c diff --git a/drivers/hid/bpf/progs/Trust__Philips-SPK6327.bpf.c b/drivers/hid/bpf/progs/Trust__Philips-SPK6327.bpf.c new file mode 100644 index 000000000000..bc7ff27eac9f --- /dev/null +++ b/drivers/hid/bpf/progs/Trust__Philips-SPK6327.bpf.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Fix for Trust Philips SPK6327 (145f:024b) + * Modifier keys report as Array (0x00) instead of Variable (0x02) + * causing LCtrl, LAlt, Super etc. to all act as LShift + */ +#include "vmlinux.h" +#include "hid_bpf.h" +#include "hid_bpf_helpers.h" +#include + +#define VID_TRUST 0x145F +#define PID_SPK6327 0x024B + +HID_BPF_CONFIG( + HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_TRUST, PID_SPK6327) +); + +SEC(HID_BPF_RDESC_FIXUP) +int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx) +{ + __u8 *data = hid_bpf_get_data(hctx, 0, 4096); + + if (!data) + return 0; + + /* Fix modifier keys: Input Array (0x00) -> Input Variable (0x02) */ + if (data[101] == 0x00) + data[101] = 0x02; + + return 0; +} + +HID_BPF_OPS(trust_spk6327) = { + .hid_rdesc_fixup = (void *)hid_fix_rdesc, +}; + +SEC("syscall") +int probe(struct hid_bpf_probe_args *ctx) +{ + /* Only apply to interface 1 (169 bytes) not interface 0 (62 bytes) */ + if (ctx->rdesc_size == 169) + ctx->retval = 0; + else + ctx->retval = -EINVAL; + + return 0; +} + +char _license[] SEC("license") = "GPL"; From 4153fa295c3730a6f08c9194bc60926394fb1485 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:54 +0200 Subject: [PATCH 239/295] HID: sony: add support for more instruments JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 7245b0017d1218042507e2f94129e1873ca29d6d Author: Rosalie Wanders Date: Tue Apr 7 21:46:36 2026 +0200 HID: sony: add support for more instruments This patch adds support for the following instruments: * Rock Band 1/2/3 Wii/PS3 instruments * Rock Band 3 PS3 Pro instruments * DJ Hero Turntable Wii and PS3 instruments are the same besides the vendor and product ID. This patch also fixes the mappings for the existing Guitar Hero instruments. Co-developed-by: Sanjay Govind Signed-off-by: Sanjay Govind Co-developed-by: Brenton Simpson Signed-off-by: Brenton Simpson Signed-off-by: Rosalie Wanders Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 26 +++- drivers/hid/hid-sony.c | 282 ++++++++++++++++++++++++++++++++++------- 2 files changed, 257 insertions(+), 51 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index f2eaaaffb8c8..75e080090df0 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -665,6 +665,18 @@ #define USB_DEVICE_ID_UGCI_FLYING 0x0020 #define USB_DEVICE_ID_UGCI_FIGHTING 0x0030 +#define USB_VENDOR_ID_HARMONIX 0x1bad +#define USB_DEVICE_ID_HARMONIX_WII_RB1_GUITAR 0x0004 +#define USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR 0x3010 +#define USB_DEVICE_ID_HARMONIX_WII_RB1_DRUMS 0x0005 +#define USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS 0x3110 +#define USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_DRUMS_MODE 0x3138 +#define USB_DEVICE_ID_HARMONIX_WII_RB3_MUSTANG_GUITAR 0x3430 +#define USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_MUSTANG_MODE 0x3438 +#define USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_SQUIER_MODE 0x3538 +#define USB_DEVICE_ID_HARMONIX_WII_RB3_KEYBOARD 0x3330 +#define USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_KEYBOARD_MODE 0x3338 + #define USB_VENDOR_ID_HP 0x03f0 #define USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A 0x464a #define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a @@ -1306,8 +1318,18 @@ #define USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER 0x1000 #define USB_VENDOR_ID_SONY_RHYTHM 0x12ba -#define USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE_DONGLE 0x074b -#define USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE 0x0100 +#define USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE 0x074b +#define USB_DEVICE_ID_SONY_PS3_GH_GUITAR 0x0100 +#define USB_DEVICE_ID_SONY_PS3_GH_DRUMS 0x0120 +#define USB_DEVICE_ID_SONY_PS3_DJH_TURNTABLE 0x0140 +#define USB_DEVICE_ID_SONY_PS3_RB_GUITAR 0x0200 +#define USB_DEVICE_ID_SONY_PS3_RB_DRUMS 0x0210 +#define USB_DEVICE_ID_SONY_PS3_RB3_MPA_DRUMS_MODE 0x0218 +#define USB_DEVICE_ID_SONY_PS3_RB3_MUSTANG_GUITAR 0x2430 +#define USB_DEVICE_ID_SONY_PS3_RB3_MPA_MUSTANG_MODE 0x2438 +#define USB_DEVICE_ID_SONY_PS3_RB3_MPA_SQUIER_MODE 0x2538 +#define USB_DEVICE_ID_SONY_PS3_RB3_KEYBOARD 0x2330 +#define USB_DEVICE_ID_SONY_PS3_RB3_MPA_KEYBOARD_MODE 0x2338 #define USB_VENDOR_ID_SINO_LITE 0x1345 #define USB_DEVICE_ID_SINO_LITE_CONTROLLER 0x3008 diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index a89af14e4acc..a14e730318ce 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * HID driver for Sony / PS2 / PS3 / PS4 BD devices. + * HID driver for Sony / PS2 / PS3 / PS4 / PS5 BD devices. * * Copyright (c) 1999 Andreas Gal * Copyright (c) 2000-2005 Vojtech Pavlik @@ -12,9 +12,10 @@ * Copyright (c) 2014-2016 Frank Praznik * Copyright (c) 2018 Todd Kelner * Copyright (c) 2020-2021 Pascal Giard - * Copyright (c) 2020 Sanjay Govind + * Copyright (c) 2020-2026 Sanjay Govind * Copyright (c) 2021 Daniel Nguyen * Copyright (c) 2026 Rosalie Wanders + * Copyright (c) 2026 Brenton Simpson */ /* @@ -59,12 +60,15 @@ #define NSG_MR5U_REMOTE_BT BIT(11) #define NSG_MR7U_REMOTE_BT BIT(12) #define SHANWAN_GAMEPAD BIT(13) -#define GH_GUITAR_CONTROLLER BIT(14) -#define GHL_GUITAR_PS3WIIU BIT(15) -#define GHL_GUITAR_PS4 BIT(16) -#define RB4_GUITAR_PS4_USB BIT(17) -#define RB4_GUITAR_PS4_BT BIT(18) -#define RB4_GUITAR_PS5 BIT(19) +#define INSTRUMENT BIT(14) +#define GH_GUITAR_TILT BIT(15) +#define GHL_GUITAR_PS3WIIU BIT(16) +#define GHL_GUITAR_PS4 BIT(17) +#define RB4_GUITAR_PS4_USB BIT(18) +#define RB4_GUITAR_PS4_BT BIT(19) +#define RB4_GUITAR_PS5 BIT(20) +#define RB3_PS3_PRO_INSTRUMENT BIT(21) +#define PS3_DJH_TURNTABLE BIT(22) #define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT) #define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT) @@ -87,6 +91,10 @@ #define GHL_GUITAR_POKE_INTERVAL 8 /* In seconds */ #define GUITAR_TILT_USAGE 44 +#define TURNTABLE_EFFECTS_KNOB_USAGE 44 +#define TURNTABLE_PLATTER_BUTTONS_USAGE 45 +#define TURNTABLE_CROSS_FADER_USAGE 46 + /* Magic data taken from GHLtarUtility: * https://github.com/ghlre/GHLtarUtility/blob/master/PS3Guitar.cs * Note: The Wii U and PS3 dongles happen to share the same! @@ -102,6 +110,13 @@ static const char ghl_ps4_magic_data[] = { 0x30, 0x02, 0x08, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00 }; +/* Rock Band 3 PS3 Pro Instruments require sending a report + * once an instrument is connected to its dongle. + * We need to retry sending these reports, + * but to avoid doing this too often we delay the retries + */ +#define RB3_PRO_INSTRUMENT_POKE_RETRY_INTERVAL 8 /* In seconds */ + /* PS/3 Motion controller */ static const u8 motion_rdesc[] = { 0x05, 0x01, /* Usage Page (Desktop), */ @@ -427,20 +442,25 @@ static const unsigned int rb4_absmap[] = { [0x31] = ABS_Y, }; -static const unsigned int rb4_keymap[] = { - [0x1] = BTN_WEST, /* Square */ - [0x2] = BTN_SOUTH, /* Cross */ - [0x3] = BTN_EAST, /* Circle */ - [0x4] = BTN_NORTH, /* Triangle */ - [0x5] = BTN_TL, /* L1 */ - [0x6] = BTN_TR, /* R1 */ - [0x7] = BTN_TL2, /* L2 */ - [0x8] = BTN_TR2, /* R2 */ - [0x9] = BTN_SELECT, /* Share */ - [0xa] = BTN_START, /* Options */ - [0xb] = BTN_THUMBL, /* L3 */ - [0xc] = BTN_THUMBR, /* R3 */ - [0xd] = BTN_MODE, /* PS */ +static const unsigned int ps3_turntable_absmap[] = { + [0x32] = ABS_X, + [0x35] = ABS_Y, +}; + +static const unsigned int instrument_keymap[] = { + [0x1] = BTN_WEST, + [0x2] = BTN_SOUTH, + [0x3] = BTN_EAST, + [0x4] = BTN_NORTH, + [0x5] = BTN_TL, + [0x6] = BTN_TR, + [0x7] = BTN_TL2, + [0x8] = BTN_TR2, + [0x9] = BTN_SELECT, + [0xa] = BTN_START, + [0xb] = BTN_THUMBL, + [0xc] = BTN_THUMBR, + [0xd] = BTN_MODE, }; static enum power_supply_property sony_battery_props[] = { @@ -490,6 +510,7 @@ struct motion_output_report_02 { #define SIXAXIS_REPORT_0xF2_SIZE 17 #define SIXAXIS_REPORT_0xF5_SIZE 8 #define MOTION_REPORT_0x02_SIZE 49 +#define PRO_INSTRUMENT_0x00_SIZE 8 #define SENSOR_SUFFIX " Motion Sensors" #define TOUCHPAD_SUFFIX " Touchpad" @@ -539,6 +560,9 @@ struct sony_sc { /* GH Live */ struct urb *ghl_urb; struct timer_list ghl_poke_timer; + + /* Rock Band 3 Pro Instruments */ + unsigned long rb3_pro_poke_jiffies; }; static void sony_set_leds(struct sony_sc *sc); @@ -610,6 +634,88 @@ static int ghl_init_urb(struct sony_sc *sc, struct usb_device *usbdev, return 0; } + + +/* + * Sending HID_REQ_SET_REPORT enables the full report. Without this + * Rock Band 3 Pro instruments only report navigation events + */ +static int rb3_pro_instrument_enable_full_report(struct sony_sc *sc) +{ + struct hid_device *hdev = sc->hdev; + static const u8 report[] = { 0x00, 0xE9, 0x00, 0x89, 0x1B, + 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x89, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xE9, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00 }; + u8 *buf; + int ret; + + buf = kmemdup(report, sizeof(report), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(report), + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + + kfree(buf); + + return ret; +} + +static int djh_turntable_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR) { + unsigned int abs = usage->hid & HID_USAGE; + + if (abs == TURNTABLE_CROSS_FADER_USAGE) { + hid_map_usage_clear(hi, usage, bit, max, EV_ABS, ABS_RX); + return 1; + } else if (abs == TURNTABLE_EFFECTS_KNOB_USAGE) { + hid_map_usage_clear(hi, usage, bit, max, EV_ABS, ABS_RY); + return 1; + } else if (abs == TURNTABLE_PLATTER_BUTTONS_USAGE) { + hid_map_usage_clear(hi, usage, bit, max, EV_ABS, ABS_RZ); + return 1; + } + } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) { + unsigned int abs = usage->hid & HID_USAGE; + + if (abs >= ARRAY_SIZE(ps3_turntable_absmap)) + return -1; + + abs = ps3_turntable_absmap[abs]; + + hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs); + return 1; + } + return 0; +} + +static int instrument_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) { + unsigned int key = usage->hid & HID_USAGE; + + if (key >= ARRAY_SIZE(instrument_keymap)) + return 0; + + key = instrument_keymap[key]; + hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key); + return 1; + } + + return 0; +} + static int gh_guitar_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) @@ -629,16 +735,7 @@ static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { - if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) { - unsigned int key = usage->hid & HID_USAGE; - - if (key >= ARRAY_SIZE(rb4_keymap)) - return 0; - - key = rb4_keymap[key]; - hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key); - return 1; - } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) { + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) { unsigned int abs = usage->hid & HID_USAGE; /* Let the HID parser deal with the HAT. */ @@ -1052,6 +1149,18 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, return 1; } + /* Rock Band 3 PS3 Pro instruments set rd[24] to 0xE0 when they're + * sending full reports, and 0x02 when only sending navigation. + */ + if ((sc->quirks & RB3_PS3_PRO_INSTRUMENT) && rd[24] == 0x02) { + /* Only attempt to enable report every 8 seconds */ + if (time_after(jiffies, sc->rb3_pro_poke_jiffies)) { + sc->rb3_pro_poke_jiffies = jiffies + + (RB3_PRO_INSTRUMENT_POKE_RETRY_INTERVAL * HZ); + rb3_pro_instrument_enable_full_report(sc); + } + } + if (sc->defer_initialization) { sc->defer_initialization = 0; sony_schedule_work(sc, SONY_WORKER_STATE); @@ -1065,6 +1174,7 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi, unsigned long **bit, int *max) { struct sony_sc *sc = hid_get_drvdata(hdev); + int ret; if (sc->quirks & BUZZ_CONTROLLER) { unsigned int key = usage->hid & HID_USAGE; @@ -1098,9 +1208,19 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi, if (sc->quirks & SIXAXIS_CONTROLLER) return sixaxis_mapping(hdev, hi, field, usage, bit, max); - if (sc->quirks & GH_GUITAR_CONTROLLER) + /* INSTRUMENT quirk is used as a base mapping for instruments */ + if (sc->quirks & INSTRUMENT) { + ret = instrument_mapping(hdev, hi, field, usage, bit, max); + if (ret != 0) + return ret; + } + + if (sc->quirks & GH_GUITAR_TILT) return gh_guitar_mapping(hdev, hi, field, usage, bit, max); + if (sc->quirks & PS3_DJH_TURNTABLE) + return djh_turntable_mapping(hdev, hi, field, usage, bit, max); + if (sc->quirks & (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT)) return rb4_guitar_mapping(hdev, hi, field, usage, bit, max); @@ -2060,6 +2180,19 @@ static int sony_input_configured(struct hid_device *hdev, } sony_init_output_report(sc, sixaxis_send_output_report); + } else if (sc->quirks & RB3_PS3_PRO_INSTRUMENT) { + /* + * Rock Band 3 PS3 Pro Instruments also do not handle HID Output + * Reports on the interrupt EP like they should, so we need to force + * HID output reports to use HID_REQ_SET_REPORT on the Control EP. + * + * There is also another issue about HID Output Reports via USB, + * these instruments do not want the report_id as part of the data + * packet, so we have to discard buf[0] when sending the actual + * control message, even for numbered reports. + */ + hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP; + hdev->quirks |= HID_QUIRK_SKIP_OUTPUT_REPORT_ID; } else if (sc->quirks & SIXAXIS_CONTROLLER_USB) { /* * The Sony Sixaxis does not handle HID Output Reports on the @@ -2227,6 +2360,10 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) goto err; } + if (sc->quirks & RB3_PS3_PRO_INSTRUMENT) { + sc->rb3_pro_poke_jiffies = 0; + } + if (sc->quirks & (GHL_GUITAR_PS3WIIU | GHL_GUITAR_PS4)) { if (!hid_is_usb(hdev)) { ret = -EINVAL; @@ -2364,35 +2501,82 @@ static const struct hid_device_id sony_devices[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_NSG_MR7U_REMOTE), .driver_data = NSG_MR7U_REMOTE_BT }, /* Guitar Hero Live PS3 and Wii U guitar dongles */ - { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE_DONGLE), - .driver_data = GHL_GUITAR_PS3WIIU | GH_GUITAR_CONTROLLER }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE), + .driver_data = GHL_GUITAR_PS3WIIU | GH_GUITAR_TILT | INSTRUMENT }, /* Guitar Hero PC Guitar Dongle */ { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_GUITAR_DONGLE), - .driver_data = GH_GUITAR_CONTROLLER }, - /* Guitar Hero PS3 World Tour Guitar Dongle */ - { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE), - .driver_data = GH_GUITAR_CONTROLLER }, + .driver_data = GH_GUITAR_TILT | INSTRUMENT }, + /* Guitar Hero PS3 Guitar Dongle */ + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_GUITAR), + .driver_data = GH_GUITAR_TILT | INSTRUMENT }, + /* Guitar Hero PS3 Drum Dongle */ + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_DRUMS), + .driver_data = INSTRUMENT }, + /* DJ Hero PS3 Guitar Dongle */ + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_DJH_TURNTABLE), + .driver_data = PS3_DJH_TURNTABLE | INSTRUMENT }, /* Guitar Hero Live PS4 guitar dongles */ { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE), - .driver_data = GHL_GUITAR_PS4 | GH_GUITAR_CONTROLLER }, + .driver_data = GHL_GUITAR_PS4 | GH_GUITAR_TILT | INSTRUMENT }, + /* Rock Band 1 Wii instruments */ + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB1_GUITAR), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB1_DRUMS), + .driver_data = INSTRUMENT }, + /* Rock Band 2 Wii instruments */ + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS), + .driver_data = INSTRUMENT }, + /* Rock Band 3 Wii instruments */ + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_DRUMS_MODE), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MUSTANG_GUITAR), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_MUSTANG_MODE), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_SQUIER_MODE), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_KEYBOARD), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_KEYBOARD_MODE), + .driver_data = INSTRUMENT }, + /* Rock Band 3 PS3 instruments */ + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB_GUITAR), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB_DRUMS), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_DRUMS_MODE), + .driver_data = INSTRUMENT }, + /* Rock Band 3 PS3 Pro instruments */ + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MUSTANG_GUITAR), + .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_MUSTANG_MODE), + .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_SQUIER_MODE), + .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_KEYBOARD), + .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_KEYBOARD_MODE), + .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT }, /* Rock Band 4 PS4 guitars */ { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER), - .driver_data = RB4_GUITAR_PS4_USB }, + .driver_data = RB4_GUITAR_PS4_USB | INSTRUMENT }, { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG), - .driver_data = RB4_GUITAR_PS4_USB }, + .driver_data = RB4_GUITAR_PS4_USB | INSTRUMENT }, { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG_DONGLE), - .driver_data = RB4_GUITAR_PS4_USB }, + .driver_data = RB4_GUITAR_PS4_USB | INSTRUMENT }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_JAGUAR), - .driver_data = RB4_GUITAR_PS4_BT }, + .driver_data = RB4_GUITAR_PS4_BT | INSTRUMENT }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_PS4_STRATOCASTER), - .driver_data = RB4_GUITAR_PS4_BT }, + .driver_data = RB4_GUITAR_PS4_BT | INSTRUMENT }, /* Rock Band 4 PS5 guitars */ { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS5_RIFFMASTER), - .driver_data = RB4_GUITAR_PS5 }, + .driver_data = RB4_GUITAR_PS5 | INSTRUMENT }, { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS5_GIBSON_SG), - .driver_data = RB4_GUITAR_PS5 }, + .driver_data = RB4_GUITAR_PS5 | INSTRUMENT }, { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS5_GIBSON_SG_DONGLE), - .driver_data = RB4_GUITAR_PS5 }, + .driver_data = RB4_GUITAR_PS5 | INSTRUMENT }, { } }; MODULE_DEVICE_TABLE(hid, sony_devices); @@ -2428,5 +2612,5 @@ static void __exit sony_exit(void) module_init(sony_init); module_exit(sony_exit); -MODULE_DESCRIPTION("HID driver for Sony / PS2 / PS3 / PS4 BD devices"); +MODULE_DESCRIPTION("HID driver for Sony / PS2 / PS3 / PS4 / PS5 BD devices"); MODULE_LICENSE("GPL"); From 348ec81bd0a1e6f85b6c002fd08c5c24955a2143 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:55 +0200 Subject: [PATCH 240/295] HID: quirks: update hid-sony supported devices JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 2531731e29455a02c6084658155eb3e88388600b Author: Rosalie Wanders Date: Tue Apr 7 21:53:28 2026 +0200 HID: quirks: update hid-sony supported devices hid-sony has been updated with new device support, update the hid_have_special_driver list accordingly. Signed-off-by: Rosalie Wanders Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-quirks.c | 46 ++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 02f7db5c1056..d38f9ac4662d 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -691,22 +691,54 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_JOY_BOX_5_PRO) }, #endif #if IS_ENABLED(CONFIG_HID_SONY) + { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG_DONGLE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS5_GIBSON_SG) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS5_GIBSON_SG_DONGLE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB1_DRUMS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB1_GUITAR) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_KEYBOARD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_DRUMS_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_KEYBOARD_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_MUSTANG_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_SQUIER_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MUSTANG_GUITAR) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_PS3) }, - { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_PS3_BDREMOTE) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_PS4_STRATOCASTER) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_JAGUAR) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS5_RIFFMASTER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_GUITAR_DONGLE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SINO_LITE, USB_DEVICE_ID_SINO_LITE_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_NSG_MR5U_REMOTE) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_NSG_MR7U_REMOTE) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_PS3_BDREMOTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_BUZZ_CONTROLLER) }, - { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER) }, - { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_MOTION_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_MOTION_CONTROLLER) }, - { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_MOTION_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_BDREMOTE) }, - { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, - { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE) }, - { HID_USB_DEVICE(USB_VENDOR_ID_SINO_LITE, USB_DEVICE_ID_SINO_LITE_CONTROLLER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_DJH_TURNTABLE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_DRUMS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_GUITAR) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_KEYBOARD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_DRUMS_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_KEYBOARD_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_MUSTANG_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_SQUIER_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MUSTANG_GUITAR) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB_DRUMS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB_GUITAR) }, #endif #if IS_ENABLED(CONFIG_HID_SPEEDLINK) { HID_USB_DEVICE(USB_VENDOR_ID_X_TENSIONS, USB_DEVICE_ID_SPEEDLINK_VAD_CEZANNE) }, From bc5bc51c03fdb4c985e7662efec5e19736d5a474 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:55 +0200 Subject: [PATCH 241/295] HID: sony: fix style issues JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 4b9c410e7d6dacef1a27baddb176fece2c6a6a30 Author: Rosalie Wanders Date: Tue Apr 7 21:49:20 2026 +0200 HID: sony: fix style issues This commit fixes inconsistent quirk names and also fixes all the checkpatch.pl issues alongside inconsistent code, it also adds static asserts to assert struct sizes at compile time. Signed-off-by: Rosalie Wanders Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-sony.c | 77 +++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index a14e730318ce..e37e19c017af 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -67,8 +67,8 @@ #define RB4_GUITAR_PS4_USB BIT(18) #define RB4_GUITAR_PS4_BT BIT(19) #define RB4_GUITAR_PS5 BIT(20) -#define RB3_PS3_PRO_INSTRUMENT BIT(21) -#define PS3_DJH_TURNTABLE BIT(22) +#define RB3_PRO_INSTRUMENT BIT(21) +#define DJH_TURNTABLE BIT(22) #define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT) #define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT) @@ -110,13 +110,6 @@ static const char ghl_ps4_magic_data[] = { 0x30, 0x02, 0x08, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00 }; -/* Rock Band 3 PS3 Pro Instruments require sending a report - * once an instrument is connected to its dongle. - * We need to retry sending these reports, - * but to avoid doing this too often we delay the retries - */ -#define RB3_PRO_INSTRUMENT_POKE_RETRY_INTERVAL 8 /* In seconds */ - /* PS/3 Motion controller */ static const u8 motion_rdesc[] = { 0x05, 0x01, /* Usage Page (Desktop), */ @@ -477,6 +470,7 @@ struct sixaxis_led { u8 duty_off; /* % of duty_length the led is off (0xff means 100%) */ u8 duty_on; /* % of duty_length the led is on (0xff mean 100%) */ } __packed; +static_assert(sizeof(struct sixaxis_led) == 5); struct sixaxis_rumble { u8 padding; @@ -485,6 +479,7 @@ struct sixaxis_rumble { u8 left_duration; /* Left motor duration (0xff means forever) */ u8 left_motor_force; /* left (large) motor, supports force values from 0 to 255 */ } __packed; +static_assert(sizeof(struct sixaxis_rumble) == 5); struct sixaxis_output_report { u8 report_id; @@ -494,11 +489,13 @@ struct sixaxis_output_report { struct sixaxis_led led[4]; /* LEDx at (4 - x) */ struct sixaxis_led _reserved; /* LED5, not actually soldered */ } __packed; +static_assert(sizeof(struct sixaxis_output_report) == 36); union sixaxis_output_report_01 { struct sixaxis_output_report data; u8 buf[36]; }; +static_assert(sizeof(union sixaxis_output_report_01) == 36); struct motion_output_report_02 { u8 type, zero; @@ -506,6 +503,7 @@ struct motion_output_report_02 { u8 zero2; u8 rumble; }; +static_assert(sizeof(struct motion_output_report_02) == 7); #define SIXAXIS_REPORT_0xF2_SIZE 17 #define SIXAXIS_REPORT_0xF5_SIZE 8 @@ -536,7 +534,7 @@ struct sony_sc { struct led_classdev *leds[MAX_LEDS]; unsigned long quirks; struct work_struct state_worker; - void (*send_output_report)(struct sony_sc *); + void (*send_output_report)(struct sony_sc *sc); struct power_supply *battery; struct power_supply_desc battery_desc; int device_id; @@ -613,11 +611,11 @@ static int ghl_init_urb(struct sony_sc *sc, struct usb_device *usbdev, pipe = usb_sndctrlpipe(usbdev, 0); cr = devm_kzalloc(&sc->hdev->dev, sizeof(*cr), GFP_ATOMIC); - if (cr == NULL) + if (!cr) return -ENOMEM; databuf = devm_kzalloc(&sc->hdev->dev, poke_size, GFP_ATOMIC); - if (databuf == NULL) + if (!databuf) return -ENOMEM; cr->bRequestType = @@ -952,6 +950,7 @@ static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size) static const u8 sixaxis_battery_capacity[] = { 0, 1, 25, 50, 75, 100 }; unsigned long flags; int offset; + u8 index; u8 battery_capacity; int battery_status; @@ -967,7 +966,7 @@ static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size) battery_capacity = 100; battery_status = (rd[offset] & 0x01) ? POWER_SUPPLY_STATUS_FULL : POWER_SUPPLY_STATUS_CHARGING; } else { - u8 index = rd[offset] <= 5 ? rd[offset] : 5; + index = rd[offset] <= 5 ? rd[offset] : 5; battery_capacity = sixaxis_battery_capacity[index]; battery_status = POWER_SUPPLY_STATUS_DISCHARGING; } @@ -1005,7 +1004,7 @@ static void nsg_mrxu_parse_report(struct sony_sc *sc, u8 *rd, int size) * the touch-related data starts at offset 2. * For the first byte, bit 0 is set when touchpad button is pressed. * Bit 2 is set when a touch is active and the drag (Fn) key is pressed. - * This drag key is mapped to BTN_LEFT. It is operational only when a + * This drag key is mapped to BTN_LEFT. It is operational only when a * touch point is active. * Bit 4 is set when only the first touch point is active. * Bit 6 is set when only the second touch point is active. @@ -1152,11 +1151,10 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, /* Rock Band 3 PS3 Pro instruments set rd[24] to 0xE0 when they're * sending full reports, and 0x02 when only sending navigation. */ - if ((sc->quirks & RB3_PS3_PRO_INSTRUMENT) && rd[24] == 0x02) { - /* Only attempt to enable report every 8 seconds */ + if ((sc->quirks & RB3_PRO_INSTRUMENT) && rd[24] == 0x02) { + /* Only attempt to enable full report every 8 seconds */ if (time_after(jiffies, sc->rb3_pro_poke_jiffies)) { - sc->rb3_pro_poke_jiffies = jiffies + - (RB3_PRO_INSTRUMENT_POKE_RETRY_INTERVAL * HZ); + sc->rb3_pro_poke_jiffies = jiffies + secs_to_jiffies(8); rb3_pro_instrument_enable_full_report(sc); } } @@ -1218,7 +1216,7 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi, if (sc->quirks & GH_GUITAR_TILT) return gh_guitar_mapping(hdev, hi, field, usage, bit, max); - if (sc->quirks & PS3_DJH_TURNTABLE) + if (sc->quirks & DJH_TURNTABLE) return djh_turntable_mapping(hdev, hi, field, usage, bit, max); if (sc->quirks & (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT)) @@ -1273,19 +1271,18 @@ static int sony_register_touchpad(struct sony_sc *sc, int touch_count, input_set_abs_params(sc->touchpad, ABS_MT_POSITION_Y, 0, h, 0, 0); if (touch_major > 0) { - input_set_abs_params(sc->touchpad, ABS_MT_TOUCH_MAJOR, + input_set_abs_params(sc->touchpad, ABS_MT_TOUCH_MAJOR, 0, touch_major, 0, 0); if (touch_minor > 0) - input_set_abs_params(sc->touchpad, ABS_MT_TOUCH_MINOR, + input_set_abs_params(sc->touchpad, ABS_MT_TOUCH_MINOR, 0, touch_minor, 0, 0); if (orientation > 0) - input_set_abs_params(sc->touchpad, ABS_MT_ORIENTATION, + input_set_abs_params(sc->touchpad, ABS_MT_ORIENTATION, 0, orientation, 0, 0); } - if (sc->quirks & NSG_MRXU_REMOTE) { + if (sc->quirks & NSG_MRXU_REMOTE) __set_bit(EV_REL, sc->touchpad->evbit); - } ret = input_mt_init_slots(sc->touchpad, touch_count, INPUT_MT_POINTER); if (ret < 0) @@ -1440,7 +1437,7 @@ static void sixaxis_set_leds_from_id(struct sony_sc *sc) int id = sc->device_id; - BUILD_BUG_ON(MAX_LEDS < ARRAY_SIZE(sixaxis_leds[0])); + BUILD_BUG_ON(ARRAY_SIZE(sixaxis_leds[0]) > MAX_LEDS); if (id < 0) return; @@ -1458,7 +1455,7 @@ static void buzz_set_leds(struct sony_sc *sc) struct hid_report, list); s32 *value = report->field[0]->value; - BUILD_BUG_ON(MAX_LEDS < 4); + BUILD_BUG_ON(4 > MAX_LEDS); value[0] = 0x00; value[1] = sc->led_state[0] ? 0xff : 0x00; @@ -1655,15 +1652,12 @@ static int sony_leds_init(struct sony_sc *sc) name_sz = strlen(dev_name(&hdev->dev)) + strlen(color_name_str[n]) + 2; led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev) + name_sz, GFP_KERNEL); - if (!led) { - hid_err(hdev, "Couldn't allocate memory for LED %d\n", n); + if (!led) return -ENOMEM; - } name = (void *)(&led[1]); if (use_color_names) - snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), - color_name_str[n]); + snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), color_name_str[n]); else snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), n + 1); led->name = name; @@ -2180,7 +2174,7 @@ static int sony_input_configured(struct hid_device *hdev, } sony_init_output_report(sc, sixaxis_send_output_report); - } else if (sc->quirks & RB3_PS3_PRO_INSTRUMENT) { + } else if (sc->quirks & RB3_PRO_INSTRUMENT) { /* * Rock Band 3 PS3 Pro Instruments also do not handle HID Output * Reports on the interrupt EP like they should, so we need to force @@ -2309,10 +2303,8 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) quirks |= SHANWAN_GAMEPAD; sc = devm_kzalloc(&hdev->dev, sizeof(*sc), GFP_KERNEL); - if (sc == NULL) { - hid_err(hdev, "can't alloc sony descriptor\n"); + if (!sc) return -ENOMEM; - } spin_lock_init(&sc->lock); @@ -2360,9 +2352,8 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) goto err; } - if (sc->quirks & RB3_PS3_PRO_INSTRUMENT) { + if (sc->quirks & RB3_PRO_INSTRUMENT) sc->rb3_pro_poke_jiffies = 0; - } if (sc->quirks & (GHL_GUITAR_PS3WIIU | GHL_GUITAR_PS4)) { if (!hid_is_usb(hdev)) { @@ -2514,7 +2505,7 @@ static const struct hid_device_id sony_devices[] = { .driver_data = INSTRUMENT }, /* DJ Hero PS3 Guitar Dongle */ { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_DJH_TURNTABLE), - .driver_data = PS3_DJH_TURNTABLE | INSTRUMENT }, + .driver_data = DJH_TURNTABLE | INSTRUMENT }, /* Guitar Hero Live PS4 guitar dongles */ { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE), .driver_data = GHL_GUITAR_PS4 | GH_GUITAR_TILT | INSTRUMENT }, @@ -2550,15 +2541,15 @@ static const struct hid_device_id sony_devices[] = { .driver_data = INSTRUMENT }, /* Rock Band 3 PS3 Pro instruments */ { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MUSTANG_GUITAR), - .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT }, + .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_MUSTANG_MODE), - .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT }, + .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_SQUIER_MODE), - .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT }, + .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_KEYBOARD), - .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT }, + .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_KEYBOARD_MODE), - .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT }, + .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT }, /* Rock Band 4 PS4 guitars */ { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER), .driver_data = RB4_GUITAR_PS4_USB | INSTRUMENT }, From 10bf55f13ce1b413050f9f1595d37f417de7189f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:56 +0200 Subject: [PATCH 242/295] HID: sony: add battery status support for Rock Band 4 PS5 guitars JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit e4bdeaef035135957d2a833c16117e6f6f16260e Author: Rosalie Wanders Date: Sat Mar 7 10:48:25 2026 +0100 HID: sony: add battery status support for Rock Band 4 PS5 guitars This commit adds battery status support for Rock Band 4 PS5 guitars. The data is reported in the same way as the dualsense in hid-playstation except it's located at byte 30. Signed-off-by: Rosalie Wanders Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-sony.c | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index e37e19c017af..83e82a0a3327 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -76,7 +76,8 @@ NAVIGATION_CONTROLLER_BT) #define SONY_LED_SUPPORT (SIXAXIS_CONTROLLER | BUZZ_CONTROLLER |\ MOTION_CONTROLLER | NAVIGATION_CONTROLLER) -#define SONY_BATTERY_SUPPORT (SIXAXIS_CONTROLLER | MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER) +#define SONY_BATTERY_SUPPORT (SIXAXIS_CONTROLLER | MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER |\ + RB4_GUITAR_PS5) #define SONY_FF_SUPPORT (SIXAXIS_CONTROLLER | MOTION_CONTROLLER) #define SONY_BT_DEVICE (SIXAXIS_CONTROLLER_BT | MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER_BT) #define NSG_MRXU_REMOTE (NSG_MR5U_REMOTE_BT | NSG_MR7U_REMOTE_BT) @@ -1087,6 +1088,12 @@ static void rb4_ps4_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size) static void rb4_ps5_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size) { + u8 charging_status; + u8 battery_data; + u8 battery_capacity; + u8 battery_status; + unsigned long flags; + /* * Rock Band 4 PS5 guitars have whammy and * tilt functionality, they're located at @@ -1099,6 +1106,37 @@ static void rb4_ps5_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size) input_report_abs(sc->input_dev, ABS_Z, rd[41]); input_report_abs(sc->input_dev, ABS_RZ, rd[42]); + /* + * Rock Band 4 PS5 guitars also report the + * battery status and level at byte 30. + */ + charging_status = (rd[30] >> 4) & 0x0F; + battery_data = rd[30] & 0x0F; + + switch (charging_status) { + case 0x0: + battery_capacity = min(battery_data * 10 + 5, 100); + battery_status = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case 0x1: + battery_capacity = min(battery_data * 10 + 5, 100); + battery_status = POWER_SUPPLY_STATUS_CHARGING; + break; + case 0x2: + battery_capacity = 100; + battery_status = POWER_SUPPLY_STATUS_FULL; + break; + default: + battery_capacity = 0; + battery_status = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + + spin_lock_irqsave(&sc->lock, flags); + sc->battery_capacity = battery_capacity; + sc->battery_status = battery_status; + spin_unlock_irqrestore(&sc->lock, flags); + input_sync(sc->input_dev); } From dea647f33e354822acdcb6ca1786c58e7c1a8c25 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:12:57 +0200 Subject: [PATCH 243/295] Docs: hid: intel-ish-hid: make long URL usable JIRA: https://issues.redhat.com/browse/RHEL-170872 JIRA: https://redhat.atlassian.net/browse/RHEL-183865 Upstream Status: since v7.1 commit c41d03d03fe95f9f81fcb519d1f934e48e168c29 Author: Randy Dunlap Date: Sat Mar 21 16:09:34 2026 -0700 Docs: hid: intel-ish-hid: make long URL usable The '\' line continuation character in this long URL doesn't help anything. There is no documentation tooling that handles the line continuation character to join the 2 lines to make a usable URL. Web browsers terminate the URL just before the '\' character so that the second line of the URL is lost. See: https://docs.kernel.org/hid/intel-ish-hid.html Join the 2 lines together so that the URL is usable. Signed-off-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260321230934.435020-1-rdunlap@infradead.org> Signed-off-by: Benjamin Tissoires --- Documentation/hid/intel-ish-hid.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/hid/intel-ish-hid.rst b/Documentation/hid/intel-ish-hid.rst index 068a5906b177..21500c1cc1fe 100644 --- a/Documentation/hid/intel-ish-hid.rst +++ b/Documentation/hid/intel-ish-hid.rst @@ -163,8 +163,8 @@ The transport layer is a bi-directional protocol, which defines: - A flow control mechanism to avoid buffer overflows This protocol resembles bus messages described in the following document: -http://www.intel.com/content/dam/www/public/us/en/documents/technical-\ -specifications/dcmi-hi-1-0-spec.pdf "Chapter 7: Bus Message Layer" +http://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/dcmi-hi-1-0-spec.pdf +"Chapter 7: Bus Message Layer". Connection and Flow Control Mechanism ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 621354b03e5d79c35ef72fa48d9ffbebe93af174 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:13:44 +0200 Subject: [PATCH 244/295] HID: logitech-hidpp: Check bounds when deleting force-feedback effects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit e606d8329be1e19b7eb3e0c6c72a73cbbb25ae3d Author: Günther Noack Date: Tue Mar 31 09:40:51 2026 +0200 HID: logitech-hidpp: Check bounds when deleting force-feedback effects Without this bounds check, this might otherwise overwrite index -1. Triggering this condition requires action both from the USB device and from userspace, which reduces the scenarios in which it can be exploited. Cc: Lee Jones Signed-off-by: Günther Noack Reviewed-by: Lee Jones Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index d1dea7297712..5f63f1d2303a 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -2502,12 +2502,15 @@ static void hidpp_ff_work_handler(struct work_struct *w) } break; case HIDPP_FF_DESTROY_EFFECT: - if (wd->effect_id >= 0) - /* regular effect destroyed */ - data->effect_ids[wd->params[0]-1] = -1; - else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER) - /* autocenter spring destroyed */ - data->slot_autocenter = 0; + slot = wd->params[0]; + if (slot > 0 && slot <= data->num_effects) { + if (wd->effect_id >= 0) + /* regular effect destroyed */ + data->effect_ids[slot-1] = -1; + else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER) + /* autocenter spring destroyed */ + data->slot_autocenter = 0; + } break; case HIDPP_FF_SET_GLOBAL_GAINS: data->gain = (wd->params[0] << 8) + wd->params[1]; From 8d15afef0eff0539a86006c253add42b94a19242 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:13:45 +0200 Subject: [PATCH 245/295] HID: sony: update module description JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit de80aa182d6ca1eb7ad41fa46d9ec7bb685850db Author: Rosalie Wanders Date: Thu Apr 2 17:59:15 2026 +0200 HID: sony: update module description This commit updates the hid-sony module description to make it correct with the recent hid-sony changes alongside making it more consistent. Signed-off-by: Rosalie Wanders Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/Kconfig | 12 +++++++----- drivers/hid/hid-sony.c | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index af0daead223a..451cd49d47a5 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1121,13 +1121,15 @@ config HID_SONY help Support for - * Sony PS3 6-axis controllers + * Sixaxis controllers for PS3 * Buzz controllers - * Sony PS3 Blue-ray Disk Remote Control (Bluetooth) - * Logitech Harmony adapter for Sony Playstation 3 (Bluetooth) - * Guitar Hero Live PS3, Wii U and PS4 guitar dongles - * Guitar Hero PS3 and PC guitar dongles + * Blu-ray Disc Remote Control for PS3 + * Logitech Harmony adapter for PS3 + * Guitar Hero Live PS3, Wii U and PS4 guitars + * Guitar Hero PS3 and PC guitars + * Rock Band 1, 2 and 3 PS3 and Wii instruments * Rock Band 4 PS4 and PS5 guitars + * DJ Hero Turntable for PS3 config SONY_FF bool "Sony PS2/3/4 accessories force feedback support" diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 83e82a0a3327..b5e724676c1d 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * HID driver for Sony / PS2 / PS3 / PS4 / PS5 BD devices. + * HID driver for Sony / PS2 / PS3 BD / PS4 / PS5 devices. * * Copyright (c) 1999 Andreas Gal * Copyright (c) 2000-2005 Vojtech Pavlik @@ -2641,5 +2641,5 @@ static void __exit sony_exit(void) module_init(sony_init); module_exit(sony_exit); -MODULE_DESCRIPTION("HID driver for Sony / PS2 / PS3 / PS4 / PS5 BD devices"); +MODULE_DESCRIPTION("HID driver for Sony / PS2 / PS3 BD / PS4 / PS5 devices"); MODULE_LICENSE("GPL"); From a4892765cb2408859e44a36898531cecf341df43 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:13:46 +0200 Subject: [PATCH 246/295] HID: logitech-dj: Standardise hid_report_enum variable nomenclature JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit a940aee176437046598dfc786b719bd96db3c74c Author: Lee Jones Date: Tue Mar 24 14:36:43 2026 +0000 HID: logitech-dj: Standardise hid_report_enum variable nomenclature Since we will need to differentiate between the two report_enum types soon, let's unify the naming conventions now to save confusion and/or unnecessary/unrelated changes in upcoming commits. {input,output}_report_enum is used in other places to let's conform. Signed-off-by: Lee Jones Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-dj.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index d9b6c72fc254..ddeb271c2833 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -1858,7 +1858,7 @@ static int logi_dj_raw_event(struct hid_device *hdev, static int logi_dj_probe(struct hid_device *hdev, const struct hid_device_id *id) { - struct hid_report_enum *rep_enum; + struct hid_report_enum *input_report_enum; struct hid_report *rep; struct dj_receiver_dev *djrcv_dev; struct usb_interface *intf; @@ -1903,10 +1903,10 @@ static int logi_dj_probe(struct hid_device *hdev, } } - rep_enum = &hdev->report_enum[HID_INPUT_REPORT]; + input_report_enum = &hdev->report_enum[HID_INPUT_REPORT]; /* no input reports, bail out */ - if (list_empty(&rep_enum->report_list)) + if (list_empty(&input_report_enum->report_list)) return -ENODEV; /* @@ -1914,7 +1914,7 @@ static int logi_dj_probe(struct hid_device *hdev, * Note: we should theoretically check for HID++ and DJ * collections, but this will do. */ - list_for_each_entry(rep, &rep_enum->report_list, list) { + list_for_each_entry(rep, &input_report_enum->report_list, list) { if (rep->application == 0xff000001) has_hidpp = true; } @@ -1927,7 +1927,7 @@ static int logi_dj_probe(struct hid_device *hdev, return -ENODEV; /* get the current application attached to the node */ - rep = list_first_entry(&rep_enum->report_list, struct hid_report, list); + rep = list_first_entry(&input_report_enum->report_list, struct hid_report, list); djrcv_dev = dj_get_receiver_dev(hdev, id->driver_data, rep->application, has_hidpp); if (!djrcv_dev) { @@ -1935,7 +1935,7 @@ static int logi_dj_probe(struct hid_device *hdev, return -ENOMEM; } - if (!rep_enum->numbered) + if (!input_report_enum->numbered) djrcv_dev->unnumbered_application = rep->application; /* Starts the usb device and connects to upper interfaces hiddev and From 51c7bdfdf3c0c2156c48d94b761d4d3ed83ad504 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:16:11 +0200 Subject: [PATCH 247/295] HID: logitech-dj: Prevent REPORT_ID_DJ_SHORT related user initiated OOB write JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit b6a57912854e7ea36f3b270032661140cc4209cd Author: Lee Jones Date: Tue Mar 24 14:36:44 2026 +0000 HID: logitech-dj: Prevent REPORT_ID_DJ_SHORT related user initiated OOB write logi_dj_recv_send_report() assumes that all incoming REPORT_ID_DJ_SHORT reports are 14 Bytes (DJREPORT_SHORT_LENGTH - 1) long. It uses that assumption to load the associated field's 'value' array with 14 Bytes of data. However, if a malicious user only sends say 1 Byte of data, 'report_count' will be 1 and only 1 Byte of memory will be allocated to the 'value' Byte array. When we come to populate 'value[1-13]' we will experience an OOB write. Signed-off-by: Lee Jones Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-dj.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index ddeb271c2833..2a5d6c4e6f65 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -1859,6 +1859,7 @@ static int logi_dj_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct hid_report_enum *input_report_enum; + struct hid_report_enum *output_report_enum; struct hid_report *rep; struct dj_receiver_dev *djrcv_dev; struct usb_interface *intf; @@ -1903,6 +1904,15 @@ static int logi_dj_probe(struct hid_device *hdev, } } + output_report_enum = &hdev->report_enum[HID_OUTPUT_REPORT]; + rep = output_report_enum->report_id_hash[REPORT_ID_DJ_SHORT]; + + if (rep->maxfield < 1 || rep->field[0]->report_count != DJREPORT_SHORT_LENGTH - 1) { + hid_err(hdev, "Expected size of DJ short report is %d, but got %d", + DJREPORT_SHORT_LENGTH - 1, rep->field[0]->report_count); + return -EINVAL; + } + input_report_enum = &hdev->report_enum[HID_INPUT_REPORT]; /* no input reports, bail out */ From db2565339e4cf64608ca8e0851580f40e2683077 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:13:47 +0200 Subject: [PATCH 248/295] HID: alps: fix NULL pointer dereference in alps_raw_event() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 CVE: CVE-2026-31625 commit 1badfc4319224820d5d890f8eab6aa52e4e83339 Author: Greg Kroah-Hartman Date: Mon Apr 6 16:03:25 2026 +0200 HID: alps: fix NULL pointer dereference in alps_raw_event() Commit ecfa6f34492c ("HID: Add HID_CLAIMED_INPUT guards in raw_event callbacks missing them") attempted to fix up the HID drivers that had missed the previous fix that was done in 2ff5baa9b527 ("HID: appleir: Fix potential NULL dereference at raw event handle"), but the alps driver was missed. Fix this up by properly checking in the hid-alps driver that it had been claimed correctly before attempting to process the raw event. Fixes: 73196ebe134d ("HID: alps: add support for Alps T4 Touchpad device") Cc: stable Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: Masaki Ota Cc: linux-input@vger.kernel.org Assisted-by: gregkh_clanker_t1000 Signed-off-by: Greg Kroah-Hartman Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-alps.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index 21e55f3d0d1b..67179e3fe39b 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -437,6 +437,9 @@ static int alps_raw_event(struct hid_device *hdev, int ret = 0; struct alps_dev *hdata = hid_get_drvdata(hdev); + if (!(hdev->claimed & HID_CLAIMED_INPUT) || !hdata->input) + return 0; + switch (hdev->product) { case HID_PRODUCT_ID_T4_BTNLESS: ret = t4_raw_event(hdata, data, size); From 42a8d8dd60239fa660e742f3329f12f5f751302b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:13:47 +0200 Subject: [PATCH 249/295] HID: quirks: Set ALWAYS_POLL for LOGITECH_BOLT_RECEIVER MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit d4229fc0cb50c52b385538d072c5fc8827b287a9 Author: Nícolas F. R. A. Prado Date: Tue Apr 7 16:59:28 2026 -0400 HID: quirks: Set ALWAYS_POLL for LOGITECH_BOLT_RECEIVER The Logitech Bolt receiver once connected to a wireless device will generate data on interface 2. If this data isn't polled, when the USB port it is connected to gets suspended (and if that happens within 5 minutes of the last input from the wireless device), it will trigger a remote wakeup 3 seconds later, which will result in a spurious system wakeup if the port was suspended as part of system sleep. Set the ALWAYS_POLL quirk for this device to ensure interface 2 is always polled and this spurious wakeup never happens. With this change in place the system can be suspended with the receiver plugged in and the system can be woken up when an input is sent from the wireless device. Signed-off-by: Nícolas F. R. A. Prado Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-quirks.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index d38f9ac4662d..9e88c9d6c6dc 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -134,6 +134,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6019), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_602E), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6093), HID_QUIRK_ALWAYS_POLL }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C007), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C077), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_KEYBOARD_G710_PLUS), HID_QUIRK_NOGET }, From d5ba6a568270f6aaa6d614fe0af1a2c679350169 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:13:48 +0200 Subject: [PATCH 250/295] HID: apple: ensure the keyboard backlight is off if suspending MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 1f95a6cd5ad78ed27a31a20cbd1facff6f10b33d Author: Aditya Garg Date: Sat Apr 4 15:14:34 2026 +0530 HID: apple: ensure the keyboard backlight is off if suspending Some users reported that upon suspending their keyboard backlight remained on. Fix this by adding the missing LED_CORE_SUSPENDRESUME flag. Cc: stable@vger.kernel.org Fixes: 394ba612f941 ("HID: apple: Add support for magic keyboard backlight on T2 Macs") Fixes: 9018eacbe623 ("HID: apple: Add support for keyboard backlight on certain T2 Macs.") Reported-by: André Eikmeyer Tested-by: André Eikmeyer Signed-off-by: Aditya Garg Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-apple.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 8b8b05c8a441..bf7dd0fbf249 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -860,6 +860,7 @@ static int apple_backlight_init(struct hid_device *hdev) asc->backlight->cdev.name = "apple::kbd_backlight"; asc->backlight->cdev.max_brightness = rep->backlight_on_max; asc->backlight->cdev.brightness_set_blocking = apple_backlight_led_set; + asc->backlight->cdev.flags = LED_CORE_SUSPENDRESUME; ret = apple_backlight_set(hdev, 0, 0); if (ret < 0) { @@ -928,6 +929,7 @@ static int apple_magic_backlight_init(struct hid_device *hdev) backlight->cdev.name = ":white:" LED_FUNCTION_KBD_BACKLIGHT; backlight->cdev.max_brightness = backlight->brightness->field[0]->logical_maximum; backlight->cdev.brightness_set_blocking = apple_magic_backlight_led_set; + backlight->cdev.flags = LED_CORE_SUSPENDRESUME; apple_magic_backlight_set(backlight, 0, 0); From 49f941c726022d8bcf78fe079e1e07a02552dcf7 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:13:49 +0200 Subject: [PATCH 251/295] HID: usbhid: fix deadlock in hid_post_reset() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 CVE: CVE-2026-53037 commit 8df2c1b47ee3cd50fd454f75c7a7e2ae8a6adf72 Author: Oliver Neukum Date: Tue Mar 24 15:24:54 2026 +0100 HID: usbhid: fix deadlock in hid_post_reset() You can build a USB device that includes a HID component and a storage or UAS component. The components can be reset only together. That means that hid_pre_reset() and hid_post_reset() are in the block IO error handling. Hence no memory allocation used in them may do block IO because the IO can deadlock on the mutex held while resetting a device and calling the interface drivers. Use GFP_NOIO for all allocations in them. Fixes: dc3c78e434690 ("HID: usbhid: Check HID report descriptor contents after device reset") Signed-off-by: Oliver Neukum Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index ddd5d77fb5a5..fd3e1aedc5cb 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -1552,7 +1552,7 @@ static int hid_post_reset(struct usb_interface *intf) * configuration descriptors passed, we already know that * the size of the HID report descriptor has not changed. */ - rdesc = kmalloc(hid->dev_rsize, GFP_KERNEL); + rdesc = kmalloc(hid->dev_rsize, GFP_NOIO); if (!rdesc) return -ENOMEM; From aea2662b01467fd5e046c47ef06844977c3918a0 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:13:50 +0200 Subject: [PATCH 252/295] HID: drop 'default !EXPERT' from tristate symbols MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 3d39be2a76d1dfed9ab74305bb5bb107a7d7ce67 Author: Thomas Weißschuh Date: Sat Mar 21 14:15:45 2026 +0100 HID: drop 'default !EXPERT' from tristate symbols There is no reason to build random drivers for obscure hardware into the core kernel by default. The usages of 'default !EXPERT' for the HID_PICOLCD suboptions are kept, as these make some sense, although they probably should use 'default y'. Signed-off-by: Thomas Weißschuh Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/Kconfig | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 451cd49d47a5..2d3be0a6cde9 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -107,7 +107,6 @@ menu "Special HID drivers" config HID_A4TECH tristate "A4TECH mice" - default !EXPERT help Support for some A4TECH mice with two scroll wheels. @@ -140,7 +139,6 @@ config HID_APPLE tristate "Apple {i,Power,Mac}Books" depends on LEDS_CLASS depends on NEW_LEDS - default !EXPERT help Support for some Apple devices which less or more break HID specification. @@ -209,7 +207,6 @@ config HID_AUREAL config HID_BELKIN tristate "Belkin Flip KVM and Wireless keyboard" - default !EXPERT help Support for Belkin Flip KVM and Wireless keyboard. @@ -237,14 +234,12 @@ config HID_BIGBEN_FF config HID_CHERRY tristate "Cherry Cymotion keyboard" - default !EXPERT help Support for Cherry Cymotion keyboard. config HID_CHICONY tristate "Chicony devices" depends on USB_HID - default !EXPERT help Support for Chicony Tactical pad and special keys on Chicony keyboards. @@ -322,7 +317,6 @@ config HID_CREATIVE_SB0540 config HID_CYPRESS tristate "Cypress mouse and barcode readers" - default !EXPERT help Support for cypress mouse and barcode readers. @@ -388,7 +382,6 @@ config HID_EVISION config HID_EZKEY tristate "Ezkey BTC 8193 keyboard" - default !EXPERT help Support for Ezkey BTC 8193 keyboard. @@ -564,7 +557,6 @@ config HID_ICADE config HID_ITE tristate "ITE devices" - default !EXPERT help Support for ITE devices not fully compliant with HID standard. @@ -585,7 +577,6 @@ config HID_TWINHAN config HID_KENSINGTON tristate "Kensington Slimblade Trackball" - default !EXPERT help Support for Kensington Slimblade Trackball. @@ -663,7 +654,6 @@ config HID_LOGITECH depends on USB_HID depends on LEDS_CLASS depends on LEDS_CLASS_MULTICOLOR - default !EXPERT help Support for Logitech devices that are not fully compliant with HID standard. @@ -778,20 +768,17 @@ config HID_MEGAWORLD_FF config HID_REDRAGON tristate "Redragon keyboards" - default !EXPERT help Support for Redragon keyboards that need fix-ups to work properly. config HID_MICROSOFT tristate "Microsoft non-fully HID-compliant devices" - default !EXPERT select INPUT_FF_MEMLESS help Support for Microsoft devices that are not fully compliant with HID standard. config HID_MONTEREY tristate "Monterey Genius KB29E keyboard" - default !EXPERT help Support for Monterey Genius KB29E. From f9ac4fa3474c05b56f39ea4e03654d437230d81a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:13:50 +0200 Subject: [PATCH 253/295] HID: playstation: validate num_touch_reports in DualShock 4 reports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 82a4fc46330910b4c1d9b189561439d468e3ff11 Author: Benoît Sevens Date: Mon Mar 23 12:47:37 2026 +0000 HID: playstation: validate num_touch_reports in DualShock 4 reports The DualShock 4 HID driver fails to validate the num_touch_reports field received from the device in both USB and Bluetooth input reports. A malicious device could set this field to a value larger than the allocated size of the touch_reports array (3 for USB, 4 for Bluetooth), leading to an out-of-bounds read in dualshock4_parse_report(). This can result in kernel memory disclosure when processing malicious HID reports. Validate num_touch_reports against the array size for the respective connection types before processing the touch data. Signed-off-by: Benoît Sevens Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 3c0db8f93c82..c43caac20b61 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -2377,6 +2377,12 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * struct dualshock4_input_report_usb *usb = (struct dualshock4_input_report_usb *)data; + if (usb->num_touch_reports > ARRAY_SIZE(usb->touch_reports)) { + hid_err(hdev, "DualShock4 USB input report has invalid num_touch_reports=%d\n", + usb->num_touch_reports); + return -EINVAL; + } + ds4_report = &usb->common; num_touch_reports = usb->num_touch_reports; touch_reports = usb->touch_reports; @@ -2391,6 +2397,12 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * return -EILSEQ; } + if (bt->num_touch_reports > ARRAY_SIZE(bt->touch_reports)) { + hid_err(hdev, "DualShock4 BT input report has invalid num_touch_reports=%d\n", + bt->num_touch_reports); + return -EINVAL; + } + ds4_report = &bt->common; num_touch_reports = bt->num_touch_reports; touch_reports = bt->touch_reports; From 3a1a933278cc59092731ab0a737aaba22bc2abb0 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:13:51 +0200 Subject: [PATCH 254/295] HID: huawei: fix CD30 keyboard report descriptor issue JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit e93faaca84b73431ccef029b2c8e902e5be83006 Author: Miao Li Date: Wed Mar 18 17:12:49 2026 +0800 HID: huawei: fix CD30 keyboard report descriptor issue When the Huawei CD30 USB keyboard undergoes 500 reboot cycles, initialization may fail due to a report descriptor problem. The error log is as follows: [pid:175,cpu0,kworker/0:1,6]usb 1-1.2.2: new low-speed USB device number 6 using xhci-hcd [pid:175,cpu0,kworker/0:1,9]usb 1-1.2.2: New USB device found, idVendor=12d1, idProduct=109b, bcdDevice= 1.03 [pid:175,cpu0,kworker/0:1,0]usb 1-1.2.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0 [pid:175,cpu0,kworker/0:1,1]usb 1-1.2.2: Product: HUAWEI USB Wired Keyboard [pid:175,cpu0,kworker/0:1,2]usb 1-1.2.2: Manufacturer: HUAWEI [pid:175,cpu0,kworker/0:1,4]input: HUAWEI HUAWEI USB Wired Keyboard as /devices/platform/efc00000.hisi_usb/efc00000.dwc3/xhci-hcd.1.auto/usb1/1-1/1-1.2/1-1.2.2/1-1.2.2:1.0/0003:12D1:109B.0002/input/input6 [pid:175,cpu0,kworker/0:1,5]hid-generic 0003:12D1:109B.0002: input,hidraw1: USB HID v1.10 Keyboard [HUAWEI HUAWEI USB Wired Keyboard] on usb-xhci-hcd.1.auto-1.2.2/input0 [pid:175,cpu0,kworker/0:1,9]hid-generic 0003:12D1:109B.0003: collection stack underflow [pid:175,cpu0,kworker/0:1,0]hid-generic 0003:12D1:109B.0003: item 0 0 0 12 parsing failed [pid:175,cpu0,kworker/0:1,1]hid-generic: probe of 0003:12D1:109B.0003 failed with error -22 ... When encountering such a situation, fix it with the correct report descriptor. Signed-off-by: Miao Li Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/Kconfig | 7 ++++ drivers/hid/Makefile | 1 + drivers/hid/hid-huawei.c | 82 ++++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-ids.h | 3 ++ 4 files changed, 93 insertions(+) create mode 100644 drivers/hid/hid-huawei.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 2d3be0a6cde9..7ecf78e7f582 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1445,6 +1445,13 @@ config HID_KUNIT_TEST If in doubt, say "N". +config HID_HUAWEI + tristate "Huawei HID devices support" + depends on USB_HID + help + Support for huawei cd30 keyboard or other hid devices + that need fix-ups to work properly. + endmenu source "drivers/hid/bpf/Kconfig" diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 07dfdb6a49c5..0597fd6a4ffd 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -154,6 +154,7 @@ obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o obj-$(CONFIG_HID_ZYDACRON) += hid-zydacron.o obj-$(CONFIG_HID_VIEWSONIC) += hid-viewsonic.o obj-$(CONFIG_HID_VRC2) += hid-vrc2.o +obj-$(CONFIG_HID_HUAWEI) += hid-huawei.o wacom-objs := wacom_wac.o wacom_sys.o obj-$(CONFIG_HID_WACOM) += wacom.o diff --git a/drivers/hid/hid-huawei.c b/drivers/hid/hid-huawei.c new file mode 100644 index 000000000000..6a616bf21b38 --- /dev/null +++ b/drivers/hid/hid-huawei.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * HID driver for some huawei "special" devices + * + * Copyright (c) 2026 Miao Li + */ + +#include +#include +#include +#include + +#include "hid-ids.h" + +static const __u8 huawei_cd30_kbd_rdesc_fixed[] = { + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x80, /* Usage (System Control) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x85, 0x01, /* Report ID (1) */ + 0x19, 0x81, /* Usage Minimum (System Power Down) */ + 0x29, 0x83, /* Usage Maximum (System Wake Up) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x75, 0x01, /* Report Size (1 bit) */ + 0x95, 0x03, /* Report Count (3) */ + 0x81, 0x02, /* Input (Data,Var,Abs) */ + 0x95, 0x05, /* Report Count (5) */ + 0x81, 0x01, /* Input (Cnst,Ary,Abs) */ + 0xc0, /* End Collection */ + 0x05, 0x0c, /* Usage Page (Consumer) */ + 0x09, 0x01, /* Usage (Consumer Control) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x85, 0x02, /* Report ID (2) */ + 0x19, 0x00, /* Usage Minimum (0) */ + 0x2a, 0x3c, 0x02, /* Usage Maximum (0x023C) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0x3c, 0x02, /* Logical Maximum (0x023C) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x10, /* Report Size (16 bits) */ + 0x81, 0x00, /* Input (Data,Ary,Abs) */ + 0xc0 /* End Collection */ +}; + +static const __u8 *huawei_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + + switch (hdev->product) { + case USB_DEVICE_ID_HUAWEI_CD30KBD: + if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { + if (*rsize != sizeof(huawei_cd30_kbd_rdesc_fixed) || + memcmp(huawei_cd30_kbd_rdesc_fixed, rdesc, + sizeof(huawei_cd30_kbd_rdesc_fixed)) != 0) { + hid_info(hdev, "Replacing Huawei cd30 keyboard report descriptor.\n"); + *rsize = sizeof(huawei_cd30_kbd_rdesc_fixed); + return huawei_cd30_kbd_rdesc_fixed; + } + } + break; + } + + return rdesc; +} + +static const struct hid_device_id huawei_devices[] = { + /* HUAWEI cd30 keyboard */ + { HID_USB_DEVICE(USB_VENDOR_ID_HUAWEI, USB_DEVICE_ID_HUAWEI_CD30KBD)}, + { } +}; +MODULE_DEVICE_TABLE(hid, huawei_devices); + +static struct hid_driver huawei_driver = { + .name = "huawei", + .id_table = huawei_devices, + .report_fixup = huawei_report_fixup, +}; +module_hid_driver(huawei_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Miao Li "); +MODULE_DESCRIPTION("HID driver for some huawei \"special\" devices"); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 75e080090df0..6e9a6c16a4fb 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1614,4 +1614,7 @@ #define USB_VENDOR_ID_JIELI_SDK_DEFAULT 0x4c4a #define USB_DEVICE_ID_JIELI_SDK_4155 0x4155 +#define USB_VENDOR_ID_HUAWEI 0x12d1 +#define USB_DEVICE_ID_HUAWEI_CD30KBD 0x109b + #endif From 0eaac227ffb9848be5e725a29dfc824294aa6780 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:13:52 +0200 Subject: [PATCH 255/295] HID: usbhid: refactor endpoint lookup JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 2fcc5e0ee7989154c4a5d169bcc9565cf2d62147 Author: Johan Hovold Date: Mon Mar 30 11:50:34 2026 +0200 HID: usbhid: refactor endpoint lookup Use the common USB helper for looking up interrupt-in endpoints instead of open coding. Signed-off-by: Johan Hovold Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-core.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index fd3e1aedc5cb..fbbfc0f60829 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -1363,19 +1363,17 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id * { struct usb_host_interface *interface = intf->cur_altsetting; struct usb_device *dev = interface_to_usbdev(intf); + struct usb_endpoint_descriptor *ep; struct usbhid_device *usbhid; struct hid_device *hid; - unsigned int n, has_in = 0; size_t len; int ret; dbg_hid("HID probe called for ifnum %d\n", intf->altsetting->desc.bInterfaceNumber); - for (n = 0; n < interface->desc.bNumEndpoints; n++) - if (usb_endpoint_is_int_in(&interface->endpoint[n].desc)) - has_in++; - if (!has_in) { + ret = usb_find_int_in_endpoint(interface, &ep); + if (ret) { hid_err(intf, "couldn't find an input interrupt endpoint\n"); return -ENODEV; } From 0c97e68b5180f96dfb07adf78b4147032deef34c Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:13:53 +0200 Subject: [PATCH 256/295] HID: core: do not allow parsing 0-sized reports JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 590204185d84635961b0ce2460784749c959a9b4 Author: Dmitry Torokhov Date: Tue Mar 31 23:04:13 2026 -0700 HID: core: do not allow parsing 0-sized reports Commit d7db259bd6df ("HID: core: factor out hid_parse_collections()") reworked collection parsing code and inadvertently allowed returning "success" when parsing 0-sized reports where old code returned -EINVAL. Restore the original behavior by doing an explicit check. Note that the error message now differs from the generic "item fetching failed at offset %u/%u" that is now used only for non-empty descriptors. Fixes: d7db259bd6df ("HID: core: factor out hid_parse_collections()") Signed-off-by: Dmitry Torokhov Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 5f3769395246..361ba84b872f 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1272,6 +1272,11 @@ static int hid_parse_collections(struct hid_device *device) device->collection[i].parent_idx = -1; ret = -EINVAL; + if (start == end) { + hid_err(device, "rejecting 0-sized report descriptor\n"); + goto out; + } + while ((next = fetch_item(start, end, &item)) != NULL) { start = next; From aab900b52c3bd129d0e74f586aa3ada2f52ce7ff Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:13:54 +0200 Subject: [PATCH 257/295] HID: winwing: Enable rumble effects JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 42d020b54edc57d03749bed75976f1e962a9fbfa Author: Ivan Gorinov Date: Sat Mar 7 05:22:46 2026 +0000 HID: winwing: Enable rumble effects Enable rumble motor control on TGRIP-15E and TGRIP-15EX throttle grips by sending haptic feedback commands (EV_FF events) to the input device. Signed-off-by: Ivan Gorinov Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-winwing.c | 196 +++++++++++++++++++++++++++++++++++--- 1 file changed, 182 insertions(+), 14 deletions(-) diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c index ab65dc12d1e0..9cd25a77999e 100644 --- a/drivers/hid/hid-winwing.c +++ b/drivers/hid/hid-winwing.c @@ -12,6 +12,7 @@ #include #include #include +#include #define MAX_REPORT 16 @@ -35,10 +36,14 @@ static const struct winwing_led_info led_info[3] = { struct winwing_drv_data { struct hid_device *hdev; - __u8 *report_buf; - struct mutex lock; - int map_more_buttons; - unsigned int num_leds; + struct mutex lights_lock; + __u8 *report_lights; + __u8 *report_rumble; + struct work_struct rumble_work; + struct ff_rumble_effect rumble; + int rumble_left; + int rumble_right; + int has_grip15; struct winwing_led leds[]; }; @@ -47,11 +52,15 @@ static int winwing_led_write(struct led_classdev *cdev, { struct winwing_led *led = (struct winwing_led *) cdev; struct winwing_drv_data *data = hid_get_drvdata(led->hdev); - __u8 *buf = data->report_buf; + __u8 *buf = data->report_lights; int ret; - mutex_lock(&data->lock); + mutex_lock(&data->lights_lock); + /* + * Mimicking requests captured by usbmon when LEDs + * are controlled by the vendor's app in a VM. + */ buf[0] = 0x02; buf[1] = 0x60; buf[2] = 0xbe; @@ -69,7 +78,7 @@ static int winwing_led_write(struct led_classdev *cdev, ret = hid_hw_output_report(led->hdev, buf, 14); - mutex_unlock(&data->lock); + mutex_unlock(&data->lights_lock); return ret; } @@ -87,9 +96,9 @@ static int winwing_init_led(struct hid_device *hdev, if (!data) return -EINVAL; - data->report_buf = devm_kmalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL); + data->report_lights = devm_kzalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL); - if (!data->report_buf) + if (!data->report_lights) return -ENOMEM; for (i = 0; i < 3; i += 1) { @@ -117,7 +126,7 @@ static int winwing_init_led(struct hid_device *hdev, return ret; } -static int winwing_map_button(int button, int map_more_buttons) +static int winwing_map_button(int button, int has_grip15) { if (button < 1) return KEY_RESERVED; @@ -141,7 +150,7 @@ static int winwing_map_button(int button, int map_more_buttons) return (button - 65) + BTN_TRIGGER_HAPPY17; } - if (!map_more_buttons) { + if (!has_grip15) { /* * Not mapping numbers [33 .. 64] which * are not assigned to any real buttons @@ -194,13 +203,149 @@ static int winwing_input_mapping(struct hid_device *hdev, /* Button numbers start with 1 */ button = usage->hid & HID_USAGE; - code = winwing_map_button(button, data->map_more_buttons); + code = winwing_map_button(button, data->has_grip15); hid_map_usage(hi, usage, bit, max, EV_KEY, code); return 1; } +/* + * If x ≤ 0, return 0; + * if x is in [1 .. 65535], return a value in [1 .. 255] + */ +static inline int convert_magnitude(int x) +{ + if (x < 1) + return 0; + + return ((x * 255) >> 16) + 1; +} + +static int winwing_haptic_rumble(struct winwing_drv_data *data) +{ + __u8 *buf; + __u8 m; + + if (!data) + return -EINVAL; + + if (!data->hdev) + return -EINVAL; + + buf = data->report_rumble; + + if (!buf) + return -EINVAL; + + m = convert_magnitude(data->rumble.strong_magnitude); + if (m != data->rumble_left) { + int ret; + + /* + * Mimicking requests captured by usbmon when rumble + * is activated by the vendor's app in a VM. + */ + buf[0] = 0x02; + buf[1] = 0x01; + buf[2] = 0xbf; + buf[3] = 0x00; + buf[4] = 0x00; + buf[5] = 0x03; + buf[6] = 0x49; + buf[7] = 0x00; + buf[8] = m; + buf[9] = 0x00; + buf[10] = 0; + buf[11] = 0; + buf[12] = 0; + buf[13] = 0; + + ret = hid_hw_output_report(data->hdev, buf, 14); + if (ret < 0) { + hid_err(data->hdev, "error %d (%*ph)\n", ret, 14, buf); + return ret; + } + data->rumble_left = m; + } + + m = convert_magnitude(data->rumble.weak_magnitude); + if (m != data->rumble_right) { + int ret; + + /* + * Mimicking requests captured by usbmon when rumble + * is activated by the vendor's app in a VM. + */ + buf[0] = 0x02; + buf[1] = 0x03; + buf[2] = 0xbf; + buf[3] = 0x00; + buf[4] = 0x00; + buf[5] = 0x03; + buf[6] = 0x49; + buf[7] = 0x00; + buf[8] = m; + buf[9] = 0x00; + buf[10] = 0; + buf[11] = 0; + buf[12] = 0; + buf[13] = 0; + + ret = hid_hw_output_report(data->hdev, buf, 14); + if (ret < 0) { + hid_err(data->hdev, "error %d (%*ph)\n", ret, 14, buf); + return ret; + } + data->rumble_right = m; + } + + return 0; +} + + +static void winwing_haptic_rumble_cb(struct work_struct *work) +{ + struct winwing_drv_data *data; + + data = container_of(work, struct winwing_drv_data, rumble_work); + winwing_haptic_rumble(data); +} + +static int winwing_play_effect(struct input_dev *dev, void *context, + struct ff_effect *effect) +{ + struct winwing_drv_data *data = (struct winwing_drv_data *) context; + + if (effect->type != FF_RUMBLE) + return 0; + + if (!data) + return -EINVAL; + + data->rumble = effect->u.rumble; + + return schedule_work(&data->rumble_work); +} + +static int winwing_init_ff(struct hid_device *hdev, struct hid_input *hidinput) +{ + struct winwing_drv_data *data; + + data = (struct winwing_drv_data *) hid_get_drvdata(hdev); + if (!data) + return -EINVAL; + + data->report_rumble = devm_kzalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL); + data->rumble_left = -1; + data->rumble_right = -1; + + input_set_capability(hidinput->input, EV_FF, FF_RUMBLE); + + return input_ff_create_memless(hidinput->input, data, + winwing_play_effect); +} + static int winwing_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -219,10 +364,12 @@ static int winwing_probe(struct hid_device *hdev, if (!data) return -ENOMEM; - data->map_more_buttons = id->driver_data; - + data->hdev = hdev; + data->has_grip15 = id->driver_data; hid_set_drvdata(hdev, data); + INIT_WORK(&data->rumble_work, winwing_haptic_rumble_cb); + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { hid_err(hdev, "hw start failed\n"); @@ -232,19 +379,39 @@ static int winwing_probe(struct hid_device *hdev, return 0; } +static void winwing_remove(struct hid_device *hdev) +{ + struct winwing_drv_data *data; + + data = (struct winwing_drv_data *) hid_get_drvdata(hdev); + + if (data) + cancel_work_sync(&data->rumble_work); + + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + static int winwing_input_configured(struct hid_device *hdev, struct hid_input *hidinput) { + struct winwing_drv_data *data; int ret; + data = (struct winwing_drv_data *) hid_get_drvdata(hdev); + ret = winwing_init_led(hdev, hidinput->input); if (ret) hid_err(hdev, "led init failed\n"); + if (data->has_grip15) + winwing_init_ff(hdev, hidinput); + return ret; } +/* Set driver_data to 1 for grips with rumble motor and more than 32 buttons */ static const struct hid_device_id winwing_devices[] = { { HID_USB_DEVICE(0x4098, 0xbd65), .driver_data = 1 }, /* TGRIP-15E */ { HID_USB_DEVICE(0x4098, 0xbd64), .driver_data = 1 }, /* TGRIP-15EX */ @@ -261,6 +428,7 @@ static struct hid_driver winwing_driver = { .input_configured = winwing_input_configured, .input_mapping = winwing_input_mapping, .probe = winwing_probe, + .remove = winwing_remove, }; module_hid_driver(winwing_driver); From b81aed2eb4cec7a555ed6cad281f775e0809af4a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:13:54 +0200 Subject: [PATCH 258/295] HID: logitech-hidpp: fix race condition when accessing stale stack pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit e2aaf2d3ad92ac4a8afa6b69ad4c38e7747d3d6e Author: Benoît Sevens Date: Wed Apr 1 14:48:11 2026 +0000 HID: logitech-hidpp: fix race condition when accessing stale stack pointer The driver uses hidpp->send_receive_buf to point to a stack-allocated buffer in the synchronous command path (__do_hidpp_send_message_sync). However, this pointer is not cleared when the function returns. If an event is processed (e.g. by a different thread) while the send_mutex is held by a new command, but before that command has updated send_receive_buf, the handler (hidpp_raw_hidpp_event) will observe that the mutex is locked and dereference the stale pointer. This results in an out-of-bounds access on a different thread's kernel stack (or a NULL pointer dereference on the very first command). Fix this by: 1. Clearing hidpp->send_receive_buf to NULL before releasing the mutex in the synchronous command path. 2. Moving the assignment of the local 'question' and 'answer' pointers inside the mutex_is_locked() block in the handler, and adding a NULL check before dereferencing. Signed-off-by: Benoît Sevens Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 5f63f1d2303a..b1330d23bd2d 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -306,21 +306,22 @@ static int __do_hidpp_send_message_sync(struct hidpp_device *hidpp, if (ret) { dbg_hid("__hidpp_send_report returned err: %d\n", ret); memset(response, 0, sizeof(struct hidpp_report)); - return ret; + goto out; } if (!wait_event_timeout(hidpp->wait, hidpp->answer_available, 5*HZ)) { dbg_hid("%s:timeout waiting for response\n", __func__); memset(response, 0, sizeof(struct hidpp_report)); - return -ETIMEDOUT; + ret = -ETIMEDOUT; + goto out; } if (response->report_id == REPORT_ID_HIDPP_SHORT && response->rap.sub_id == HIDPP_ERROR) { ret = response->rap.params[1]; dbg_hid("%s:got hidpp error %02X\n", __func__, ret); - return ret; + goto out; } if ((response->report_id == REPORT_ID_HIDPP_LONG || @@ -328,10 +329,14 @@ static int __do_hidpp_send_message_sync(struct hidpp_device *hidpp, response->fap.feature_index == HIDPP20_ERROR) { ret = response->fap.params[1]; dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret); - return ret; + goto out; } - return 0; + ret = 0; + +out: + hidpp->send_receive_buf = NULL; + return ret; } /* @@ -3843,8 +3848,7 @@ static int hidpp_input_configured(struct hid_device *hdev, static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, int size) { - struct hidpp_report *question = hidpp->send_receive_buf; - struct hidpp_report *answer = hidpp->send_receive_buf; + struct hidpp_report *question, *answer; struct hidpp_report *report = (struct hidpp_report *)data; int ret; int last_online; @@ -3854,6 +3858,12 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, * previously sent command. */ if (unlikely(mutex_is_locked(&hidpp->send_mutex))) { + question = hidpp->send_receive_buf; + answer = hidpp->send_receive_buf; + + if (!question) + return 0; + /* * Check for a correct hidpp20 answer or the corresponding * error From f5a8ffac0c58a0c5641ac6aa07242b8b431c2b0b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:18:23 +0200 Subject: [PATCH 259/295] HID: logitech-dj: fix wrong detection of bad DJ_SHORT output report JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 8b9a097eb2fc37b486afd81388c693bf3ab44466 Author: Benjamin Tissoires Date: Fri Apr 10 16:03:07 2026 +0200 HID: logitech-dj: fix wrong detection of bad DJ_SHORT output report commit b6a57912854e ("HID: logitech-dj: Prevent REPORT_ID_DJ_SHORT related user initiated OOB write") assumed that all HID devices attached to the logitech-dj driver was having an output report of DJ_SHORT. However, on the receiver itself, we have 2 other HID device we attach here: the mouse emulation and the keyboard emulation. For those devices the value of rep is NULL and we are triggered a segfault here. This is doubly required because logitech-dj also handles non DJ devices that might not have the DJ collection. Fixes: b6a57912854e ("HID: logitech-dj: Prevent REPORT_ID_DJ_SHORT related user initiated OOB write") Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-dj.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 2a5d6c4e6f65..381e4dc5aba7 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -1907,7 +1907,8 @@ static int logi_dj_probe(struct hid_device *hdev, output_report_enum = &hdev->report_enum[HID_OUTPUT_REPORT]; rep = output_report_enum->report_id_hash[REPORT_ID_DJ_SHORT]; - if (rep->maxfield < 1 || rep->field[0]->report_count != DJREPORT_SHORT_LENGTH - 1) { + if (rep && (rep->maxfield < 1 || + rep->field[0]->report_count != DJREPORT_SHORT_LENGTH - 1)) { hid_err(hdev, "Expected size of DJ short report is %d, but got %d", DJREPORT_SHORT_LENGTH - 1, rep->field[0]->report_count); return -EINVAL; From 908635cfd0290e4d0afa0838fa19039ab526d0ab Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:33 +0200 Subject: [PATCH 260/295] HID: core: clamp report_size in s32ton() to avoid undefined shift JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 CVE: CVE-2026-31624 commit 69c02ffde6ed4d535fa4e693a9e572729cad3d0d Author: Greg Kroah-Hartman Date: Mon Apr 6 16:04:10 2026 +0200 HID: core: clamp report_size in s32ton() to avoid undefined shift s32ton() shifts by n-1 where n is the field's report_size, a value that comes directly from a HID device. The HID parser bounds report_size only to <= 256, so a broken HID device can supply a report descriptor with a wide field that triggers shift exponents up to 256 on a 32-bit type when an output report is built via hid_output_field() or hid_set_field(). Commit ec61b41918587 ("HID: core: fix shift-out-of-bounds in hid_report_raw_event") added the same n > 32 clamp to the function snto32(), but s32ton() was never given the same fix as I guess syzbot hadn't figured out how to fuzz a device the same way. Fix this up by just clamping the max value of n, just like snto32() does. Cc: stable Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: linux-input@vger.kernel.org Assisted-by: gregkh_clanker_t1000 Signed-off-by: Greg Kroah-Hartman Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 361ba84b872f..e5cac535d7a4 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -71,6 +71,9 @@ static u32 s32ton(__s32 value, unsigned int n) if (!value || !n) return 0; + if (n > 32) + n = 32; + a = value >> (n - 1); if (a && a != -1) return value < 0 ? 1 << (n - 1) : (1 << (n - 1)) - 1; From 67e9bb7644e19b1bf14549f91e2e304ea3ac323e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:34 +0200 Subject: [PATCH 261/295] HID: sony: fix incorrect force-feedback check in sony_suspend() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 278dd0487907112de8e34e1a97ac6145a8081523 Author: Rosalie Wanders Date: Fri Apr 10 21:53:54 2026 +0200 HID: sony: fix incorrect force-feedback check in sony_suspend() This commit fixes the incorrect force-feedback check in sony_suspend(), without this the check will always be true due to checking a constant define that is never 0. Signed-off-by: Rosalie Wanders Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-sony.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index b5e724676c1d..23db406092ef 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -2456,11 +2456,10 @@ static void sony_remove(struct hid_device *hdev) static int sony_suspend(struct hid_device *hdev, pm_message_t message) { #ifdef CONFIG_SONY_FF + struct sony_sc *sc = hid_get_drvdata(hdev); /* On suspend stop any running force-feedback events */ - if (SONY_FF_SUPPORT) { - struct sony_sc *sc = hid_get_drvdata(hdev); - + if (sc->quirks & SONY_FF_SUPPORT) { sc->left = sc->right = 0; sony_send_output_report(sc); } From b4b04cb04d8962c05464e78baf01f5cf4b9c4e65 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:34 +0200 Subject: [PATCH 262/295] HID: ft260: validate i2c input report length MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 80c4bbb2b38513e9c3d84805fa61a0ee16d79c45 Author: Michael Zaidman Date: Sat Apr 11 09:24:37 2026 +0300 HID: ft260: validate i2c input report length Add two checks to ft260_raw_event() to prevent out-of-bounds reads from malicious or malfunctioning devices: First, reject reports shorter than the 2-byte header (report ID + length fields). Without this, even accessing xfer->length on a 1-byte report is an OOB read. Second, validate xfer->length against the actual data capacity of the received HID report. Each I2C data report ID (0xD0 through 0xDE) defines a different report size in the HID descriptor, so the available payload varies per report. A corrupted length field could cause memcpy to read beyond the report buffer. Reported-by: Sebastián Josué Alba Vives Signed-off-by: Michael Zaidman Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ft260.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index 333341e80b0e..70e2eedb465a 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -1068,10 +1068,22 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report, struct ft260_device *dev = hid_get_drvdata(hdev); struct ft260_i2c_input_report *xfer = (void *)data; + if (size < offsetof(struct ft260_i2c_input_report, data)) { + hid_err(hdev, "short report %d\n", size); + return -1; + } + if (xfer->report >= FT260_I2C_REPORT_MIN && xfer->report <= FT260_I2C_REPORT_MAX) { - ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report, - xfer->length); + ft260_dbg("i2c resp: rep %#02x len %d size %d\n", + xfer->report, xfer->length, size); + + if (xfer->length > size - + offsetof(struct ft260_i2c_input_report, data)) { + hid_err(hdev, "report %#02x: length %d exceeds HID report size\n", + xfer->report, xfer->length); + return -1; + } if ((dev->read_buf == NULL) || (xfer->length > dev->read_len - dev->read_idx)) { From 93cf8e4ae373253e9c15d83e2a3405f6c7c57638 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:35 +0200 Subject: [PATCH 263/295] HID: sony: remove unneeded WARN_ON() in sony_leds_init() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 0f2b8466fb744a8b3313a9c1e2008f8cd53b2db7 Author: Rosalie Wanders Date: Sat Apr 11 17:32:48 2026 +0200 HID: sony: remove unneeded WARN_ON() in sony_leds_init() This commit removes the unneeded WARN_ON() macro usage in sony_leds_init(), this is unneeded because the sony_leds_init() function call is already gated behind a SONY_LED_SUPPORT check in sony_input_configured() Signed-off-by: Rosalie Wanders Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-sony.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 23db406092ef..6a860b9ef677 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -1640,9 +1640,6 @@ static int sony_leds_init(struct sony_sc *sc) u8 max_brightness[MAX_LEDS] = { [0 ... (MAX_LEDS - 1)] = 1 }; u8 use_hw_blink[MAX_LEDS] = { 0 }; - if (WARN_ON(!(sc->quirks & SONY_LED_SUPPORT))) - return -EINVAL; - if (sc->quirks & BUZZ_CONTROLLER) { sc->led_count = 4; use_color_names = 0; From 42452059b56ed77fa406eb60f92a40d1adacb9c4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:36 +0200 Subject: [PATCH 264/295] HID: sony: add missing size validation for SMK-Link remotes JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit a4170b63eda999d20ad6dc39ddc3ce5c1ac619e6 Author: Rosalie Wanders Date: Sun Apr 12 03:08:06 2026 +0200 HID: sony: add missing size validation for SMK-Link remotes This commit adds the missing size validation for SMK-Link remotes in sony_raw_event(), this prevents a malicious device from allowing hid-sony to read out of bounds of the provided buffer. I do not own these devices so the size check only forces that the buffer is large enough for nsg_mrxu_parse_report(). Signed-off-by: Rosalie Wanders Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-sony.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 6a860b9ef677..13fe7a3e57d7 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -1169,10 +1169,9 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, sixaxis_parse_report(sc, rd, size); } else if ((sc->quirks & MOTION_CONTROLLER_BT) && rd[0] == 0x01 && size == 49) { sixaxis_parse_report(sc, rd, size); - } else if ((sc->quirks & NAVIGATION_CONTROLLER) && rd[0] == 0x01 && - size == 49) { + } else if ((sc->quirks & NAVIGATION_CONTROLLER) && rd[0] == 0x01 && size == 49) { sixaxis_parse_report(sc, rd, size); - } else if ((sc->quirks & NSG_MRXU_REMOTE) && rd[0] == 0x02) { + } else if ((sc->quirks & NSG_MRXU_REMOTE) && rd[0] == 0x02 && size >= 12) { nsg_mrxu_parse_report(sc, rd, size); return 1; } else if ((sc->quirks & RB4_GUITAR_PS4_USB) && rd[0] == 0x01 && size == 64) { From 1c71dc4aed079207dfd9f6848dbc65ed1c3c032e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:37 +0200 Subject: [PATCH 265/295] HID: sony: add missing size validation for Rock Band 3 Pro instruments JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 12bd440b66ed8968afffc46928233967b5b79b98 Author: Rosalie Wanders Date: Sun Apr 12 03:12:03 2026 +0200 HID: sony: add missing size validation for Rock Band 3 Pro instruments This commit adds the missing size validation for Rock Band 3 PS3 Pro instruments in sony_raw_event(), this prevents a malicious device from allowing hid-sony to read out of bounds of the provided buffer. Signed-off-by: Rosalie Wanders Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-sony.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 13fe7a3e57d7..315343415e8f 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -1188,7 +1188,7 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, /* Rock Band 3 PS3 Pro instruments set rd[24] to 0xE0 when they're * sending full reports, and 0x02 when only sending navigation. */ - if ((sc->quirks & RB3_PRO_INSTRUMENT) && rd[24] == 0x02) { + if ((sc->quirks & RB3_PRO_INSTRUMENT) && size >= 25 && rd[24] == 0x02) { /* Only attempt to enable full report every 8 seconds */ if (time_after(jiffies, sc->rb3_pro_poke_jiffies)) { sc->rb3_pro_poke_jiffies = jiffies + secs_to_jiffies(8); From 107be0facf663ae9fd08fd3f18a0fd60bce4e2c8 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:37 +0200 Subject: [PATCH 266/295] HID: elan: Add support for ELAN SB974D touchpad MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 55ce1858848132ed074fe907f00b5ce1ccab0ce1 Author: Damien Dejean Date: Tue Apr 14 13:38:58 2026 +0000 HID: elan: Add support for ELAN SB974D touchpad Elan SB974D touchpad uses ELAN_MT_I2C format to send HID reports. Add an entry to match for the device and parse its vendor specific format. Signed-off-by: Damien Dejean Signed-off-by: Kornel Dulęba Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-elan.c | 1 + drivers/hid/hid-ids.h | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/hid/hid-elan.c b/drivers/hid/hid-elan.c index 76d93fc48f6a..0190ad567ce4 100644 --- a/drivers/hid/hid-elan.c +++ b/drivers/hid/hid-elan.c @@ -513,6 +513,7 @@ static const struct hid_device_id elan_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_HP_X2_10_COVER), .driver_data = ELAN_HAS_LED }, { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_TOSHIBA_CLICK_L9W) }, + { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_SB974D) }, { } }; MODULE_DEVICE_TABLE(hid, elan_devices); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 6e9a6c16a4fb..de55d3baadfc 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -453,6 +453,7 @@ #define USB_DEVICE_ID_EDIFIER_QR30 0xa101 /* EDIFIER Hal0 2.0 SE */ #define USB_VENDOR_ID_ELAN 0x04f3 +#define USB_DEVICE_ID_SB974D 0x0400 #define USB_DEVICE_ID_TOSHIBA_CLICK_L9W 0x0401 #define USB_DEVICE_ID_HP_X2 0x074d #define USB_DEVICE_ID_HP_X2_10_COVER 0x0755 From b01a155789bc681c500a5b0ef2d85df4618b6ba5 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:38 +0200 Subject: [PATCH 267/295] HID: hid-lenovo-go-s: restore OS_TYPE after resume from s2idle JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 3524900cc571bd922a1a6b6a0eb0c2705cdb3559 Author: Matthew Schwartz Date: Mon Apr 20 11:15:22 2026 -0700 HID: hid-lenovo-go-s: restore OS_TYPE after resume from s2idle The controller MCU does not persist OS_TYPE across power cycles. During s2idle resume, the USB device may be power-cycled, causing the OS_TYPE setting to revert to the default Windows value. Add a reset_resume callback so that this is correctly restored after resume. Fixes: a23f3497bf208c59ad ("HID: hid-lenovo-go-s: Add Lenovo Legion Go S Series HID Driver") Reviewed-by: Derek J. Clark Signed-off-by: Matthew Schwartz Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go-s.c | 44 +++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 01c7bdd4fbe0..ff1782a75191 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -1369,6 +1369,14 @@ static void cfg_setup(struct work_struct *work) "Failed to retrieve IMU Manufacturer: %i\n", ret); return; } + + ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, FEATURE_OS_MODE, + NULL, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve OS Mode: %i\n", ret); + return; + } } static int hid_gos_cfg_probe(struct hid_device *hdev, @@ -1427,6 +1435,27 @@ static void hid_gos_cfg_remove(struct hid_device *hdev) hid_set_drvdata(hdev, NULL); } +static int hid_gos_cfg_reset_resume(struct hid_device *hdev) +{ + u8 os_mode = drvdata.os_mode; + int ret; + + ret = mcu_property_out(drvdata.hdev, SET_GAMEPAD_CFG, + FEATURE_OS_MODE, &os_mode, 1); + if (ret < 0) + return ret; + + ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, + FEATURE_OS_MODE, NULL, 0); + if (ret < 0) + return ret; + + if (drvdata.os_mode != os_mode) + return -ENODEV; + + return 0; +} + static int hid_gos_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -1481,6 +1510,20 @@ static void hid_gos_remove(struct hid_device *hdev) } } +static int hid_gos_reset_resume(struct hid_device *hdev) +{ + int ep = get_endpoint_address(hdev); + + switch (ep) { + case GO_S_CFG_INTF_IN: + return hid_gos_cfg_reset_resume(hdev); + default: + break; + } + + return 0; +} + static const struct hid_device_id hid_gos_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_QHE, USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT) }, @@ -1496,6 +1539,7 @@ static struct hid_driver hid_lenovo_go_s = { .probe = hid_gos_probe, .remove = hid_gos_remove, .raw_event = hid_gos_raw_event, + .reset_resume = hid_gos_reset_resume, }; module_hid_driver(hid_lenovo_go_s); From d187a43661025413a273b3b597a62ea643ba34ae Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:39 +0200 Subject: [PATCH 268/295] HID: intel-thc-hid: Intel-quickspi: Fix some error codes JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit ae4ac077332ea3341a0f4c0973556c6b7ac5b7a1 Author: Dan Carpenter Date: Thu Apr 23 10:10:02 2026 +0300 HID: intel-thc-hid: Intel-quickspi: Fix some error codes If we have a partial read that is supposed to be treated as failure but in this code we forgot to set the error code. Return -EINVAL. Fixes: 9d8d51735a3a ("HID: intel-thc-hid: intel-quickspi: Add HIDSPI protocol implementation") Signed-off-by: Dan Carpenter Reviewed-by: Even Xu Reviewed-by: Mark Pearson Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c index 16f780bc879b..cb19057f1191 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c @@ -94,7 +94,7 @@ static int quickspi_get_device_descriptor(struct quickspi_device *qsdev) dev_err_once(qsdev->dev, "Read DEVICE_DESCRIPTOR failed, ret = %d\n", ret); dev_err_once(qsdev->dev, "DEVICE_DESCRIPTOR expected len = %u, actual read = %u\n", input_len, read_len); - return ret; + return ret ?: -EINVAL; } input_rep_type = ((struct input_report_body_header *)read_buf)->input_report_type; @@ -318,7 +318,7 @@ int reset_tic(struct quickspi_device *qsdev) dev_err_once(qsdev->dev, "Read RESET_RESPONSE body failed, ret = %d\n", ret); dev_err_once(qsdev->dev, "RESET_RESPONSE body expected len = %u, actual = %u\n", read_len, actual_read_len); - return ret; + return ret ?: -EINVAL; } input_rep_type = FIELD_GET(HIDSPI_IN_REP_BDY_HDR_REP_TYPE, reset_response); From aaaf93143f2c609ae4223f5b31ca12b14d5940ef Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:40 +0200 Subject: [PATCH 269/295] HID: uclogic: Fix regression of input name assignment JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 487359284509a6745e14b8c0518768bc277809b0 Author: Takashi Iwai Date: Tue Apr 28 10:33:16 2026 +0200 HID: uclogic: Fix regression of input name assignment The previous fix for adding the devm_kasprintf() return check in the commit bd07f751208b ("HID: uclogic: Add NULL check in uclogic_input_configured()") changed the condition of hi->input->name assignment, and it resulted in missing the proper input device name when no custom suffix is defined. Restore the conditional to the original content to address the regression. Fixes: bd07f751208b ("HID: uclogic: Add NULL check in uclogic_input_configured()") Signed-off-by: Takashi Iwai Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-uclogic-core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index bd7f93e96e4e..b73f09d26688 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -184,7 +184,9 @@ static int uclogic_input_configured(struct hid_device *hdev, suffix = "System Control"; break; } - } else { + } + + if (suffix) { hi->input->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", hdev->name, suffix); if (!hi->input->name) From 9a0f60cf16f3c731516b86b0dd33916868a3bf3a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:40 +0200 Subject: [PATCH 270/295] HID: hid-sjoy: race between init and usage JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 17ee873dba04d05090dfc5b2b9e08cfc8e4f147f Author: Oliver Neukum Date: Tue Mar 3 10:48:54 2026 +0100 HID: hid-sjoy: race between init and usage The driver uses an initial IO to set the device to a default state. That initialization is currently being done after the device node has been created. That means that the single buffer used for output can be altered while IO is in progress. Move the intialization before announcement to user space. Fixes: fac733f029251 ("HID: force feedback support for SmartJoy PLUS PS2/USB adapter") Signed-off-by: Oliver Neukum Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-sjoy.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-sjoy.c b/drivers/hid/hid-sjoy.c index bab93d71b760..963c45113204 100644 --- a/drivers/hid/hid-sjoy.c +++ b/drivers/hid/hid-sjoy.c @@ -91,17 +91,17 @@ static int sjoyff_init(struct hid_device *hid) set_bit(FF_RUMBLE, dev->ffbit); - error = input_ff_create_memless(dev, sjoyff, hid_sjoyff_play); - if (error) { - kfree(sjoyff); - return error; - } - sjoyff->report = report; sjoyff->report->field[0]->value[0] = 0x01; sjoyff->report->field[0]->value[1] = 0x00; sjoyff->report->field[0]->value[2] = 0x00; hid_hw_request(hid, sjoyff->report, HID_REQ_SET_REPORT); + + error = input_ff_create_memless(dev, sjoyff, hid_sjoyff_play); + if (error) { + kfree(sjoyff); + return error; + } } hid_info(hid, "Force feedback for SmartJoy PLUS PS2/USB adapter\n"); From 546f424942b3cfb00d8667f7f0fe634643f676c6 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:41 +0200 Subject: [PATCH 271/295] HID: quirks: really enable the intended work around for appledisplay JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 5f90dcfa8dc32a488581b78e575cdd7808ba5c78 Author: Lukas Bulwahn Date: Thu Feb 5 09:11:31 2026 +0100 HID: quirks: really enable the intended work around for appledisplay Commit c7fabe4ad921 ("HID: quirks: work around VID/PID conflict for appledisplay") intends to add a quirk for kernels built with Apple Cinema Display support, but it refers to the non-existing config option CONFIG_APPLEDISPLAY, whereas the config option for Apple Cinema Display support is named CONFIG_USB_APPLEDISPLAY. Refer to the intended config option CONFIG_USB_APPLEDISPLAY in the ifdef directive. Fixes: c7fabe4ad921 ("HID: quirks: work around VID/PID conflict for appledisplay") Signed-off-by: Lukas Bulwahn Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-quirks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 9e88c9d6c6dc..512049963978 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -235,7 +235,7 @@ static const struct hid_device_id hid_quirks[] = { * used as a driver. See hid_scan_report(). */ static const struct hid_device_id hid_have_special_driver[] = { -#if IS_ENABLED(CONFIG_APPLEDISPLAY) +#if IS_ENABLED(CONFIG_USB_APPLEDISPLAY) { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9218) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9219) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x921c) }, From 6dadc15e5d4250004c86c4b6370830546051294e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:42 +0200 Subject: [PATCH 272/295] HID: mcp2221: fix OOB write in mcp2221_raw_event() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit f097d246677b03db814c5862f368cea341b76a00 Author: Florian Pradines Date: Sat May 9 09:45:17 2026 +0000 HID: mcp2221: fix OOB write in mcp2221_raw_event() mcp2221_raw_event() copies device-supplied data into mcp->rxbuf at offset rxbuf_idx without checking that the copy fits within the destination buffer. A device responding with up to 60 bytes to a small I2C/SMBus read can overflow the buffer. Add a rxbuf_size field to struct mcp2221, set it alongside rxbuf in mcp_i2c_smbus_read(), and check rxbuf_idx + data[3] <= rxbuf_size before the memcpy. Reported-by: Benoît Sevens Signed-off-by: Florian Pradines Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-mcp2221.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c index 1443be66e0da..395a2db5c2f8 100644 --- a/drivers/hid/hid-mcp2221.c +++ b/drivers/hid/hid-mcp2221.c @@ -128,6 +128,7 @@ struct mcp2221 { u8 *rxbuf; u8 txbuf[64]; int rxbuf_idx; + int rxbuf_size; int status; u8 cur_i2c_clk_div; struct gpio_chip *gc; @@ -330,12 +331,14 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp, mcp->txbuf[3] = (u8)(msg->addr << 1); total_len = msg->len; mcp->rxbuf = msg->buf; + mcp->rxbuf_size = msg->len; } else { mcp->txbuf[1] = smbus_len; mcp->txbuf[2] = 0; mcp->txbuf[3] = (u8)(smbus_addr << 1); total_len = smbus_len; mcp->rxbuf = smbus_buf; + mcp->rxbuf_size = smbus_len; } ret = mcp_send_data_req_status(mcp, mcp->txbuf, 4); @@ -919,6 +922,10 @@ static int mcp2221_raw_event(struct hid_device *hdev, mcp->status = -EINVAL; break; } + if (mcp->rxbuf_idx + data[3] > mcp->rxbuf_size) { + mcp->status = -EINVAL; + break; + } buf = mcp->rxbuf; memcpy(&buf[mcp->rxbuf_idx], &data[4], data[3]); mcp->rxbuf_idx = mcp->rxbuf_idx + data[3]; From 57f77bfe63a8d804a7e3e2ded1e0af7dd1da89a0 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:43 +0200 Subject: [PATCH 273/295] HID: magicmouse: Prevent out-of-bounds (OOB) read during DOUBLE_REPORT_ID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit d93ba918a185aca2594da63e92fdc5495b559c0f Author: Lee Jones Date: Thu Apr 16 14:16:54 2026 +0100 HID: magicmouse: Prevent out-of-bounds (OOB) read during DOUBLE_REPORT_ID It is currently possible for a malicious or misconfigured USB device to cause an out-of-bounds (OOB) read when submitting reports using DOUBLE_REPORT_ID by specifying a large report length and providing a smaller one. Let's prevent that by comparing the specified report length with the actual size of the data read in from userspace. If the actual data length ends up being smaller than specified, we'll politely warn the user and prevent any further processing. Signed-off-by: Lee Jones Reviewed-by: Günther Noack Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-magicmouse.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index e70bd3dc07ab..802a3479e24b 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -390,6 +390,10 @@ static int magicmouse_raw_event(struct hid_device *hdev, struct input_dev *input = msc->input; int x = 0, y = 0, ii, clicks = 0, npoints; + /* Protect against zero sized recursive calls from DOUBLE_REPORT_ID */ + if (size < 1) + return 0; + switch (data[0]) { case TRACKPAD_REPORT_ID: case TRACKPAD2_BT_REPORT_ID: @@ -490,6 +494,18 @@ static int magicmouse_raw_event(struct hid_device *hdev, /* Sometimes the trackpad sends two touch reports in one * packet. */ + + /* Ensure that we have at least 2 elements (report type and size) */ + if (size < 2) + return 0; + + if (size < data[1] + 2) { + hid_warn(hdev, + "received report length (%d) was smaller than specified (%d)", + size, data[1] + 2); + return 0; + } + magicmouse_raw_event(hdev, report, data + 2, data[1]); magicmouse_raw_event(hdev, report, data + 2 + data[1], size - 2 - data[1]); From 1c53d36bcaa368074f280762f0ed5d3f15870ce5 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:43 +0200 Subject: [PATCH 274/295] HID: playstation: Clamp num_touch_reports JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 CVE: CVE-2026-46232 commit cac61b58a3b6340c52afa06bb15eac033158db2f Author: T.J. Mercier Date: Fri Apr 17 08:47:02 2026 -0700 HID: playstation: Clamp num_touch_reports A device would never lie about the number of touch reports would it? If it does the loop in dualshock4_parse_report will read off the end of the touch_reports array, up to about 2 KiB for the maximum number of 256 loop iteraions. The data that is read is emitted via evdev if the DS4_TOUCH_POINT_INACTIVE bit happens to be set. Protect against this by clamping the num_touch_reports value provided by the device to the maximum size of the touch_reports array. Fixes: 752038248808 ("HID: playstation: add DualShock4 touchpad support.") Cc: stable@vger.kernel.org Reported-by: Xingyu Jin Signed-off-by: T.J. Mercier Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-playstation.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index c43caac20b61..e48537331675 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -2384,7 +2384,8 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * } ds4_report = &usb->common; - num_touch_reports = usb->num_touch_reports; + num_touch_reports = min_t(u8, usb->num_touch_reports, + ARRAY_SIZE(usb->touch_reports)); touch_reports = usb->touch_reports; } else if (hdev->bus == BUS_BLUETOOTH && report->id == DS4_INPUT_REPORT_BT && size == DS4_INPUT_REPORT_BT_SIZE) { @@ -2404,7 +2405,8 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * } ds4_report = &bt->common; - num_touch_reports = bt->num_touch_reports; + num_touch_reports = min_t(u8, bt->num_touch_reports, + ARRAY_SIZE(bt->touch_reports)); touch_reports = bt->touch_reports; } else if (hdev->bus == BUS_BLUETOOTH && report->id == DS4_INPUT_REPORT_BT_MINIMAL && From d7b3776f01eb210683358317c1152a8e91080563 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:44 +0200 Subject: [PATCH 275/295] HID: appletb-kbd: fix UAF in inactivity-timer cleanup path JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 CVE: CVE-2026-46213 commit 4db2af929279c799b5653a39eb0795c72baffca4 Author: Sangyun Kim Date: Mon Apr 20 14:13:17 2026 +0900 HID: appletb-kbd: fix UAF in inactivity-timer cleanup path Commit 38224c472a03 ("HID: appletb-kbd: fix slab use-after-free bug in appletb_kbd_probe") added timer_delete_sync(&kbd->inactivity_timer) to both the probe close_hw error path and appletb_kbd_remove(), but the way it was wired in left the inactivity timer reachable during driver tear-down via two distinct windows. Window A -- put_device() before timer_delete_sync(): put_device(&kbd->backlight_dev->dev); timer_delete_sync(&kbd->inactivity_timer); The inactivity_timer softirq reads kbd->backlight_dev and calls backlight_device_set_brightness() -> mutex_lock(&ops_lock). If a concurrent hid_appletb_bl unbind drops the last devm reference between these two calls, the backlight_device is freed and the mutex_lock() touches freed memory. Window B -- backlight cleanup before hid_hw_stop(): if (kbd->backlight_dev) { timer_delete_sync(...); put_device(...); } hid_hw_close(hdev); hid_hw_stop(hdev); Even after Window A is closed, hid_hw_close()/hid_hw_stop() still run afterwards, so a late ".event" callback from the HID core (USB URB completion on real Apple hardware) can arrive after timer_delete_sync() drained the softirq but before put_device() drops the reference. That callback reaches reset_inactivity_timer(), which calls mod_timer() and re-arms the timer. The freshly re-armed timer can then fire on the about-to-be-freed backlight_device. Both windows produce the same KASAN slab-use-after-free: BUG: KASAN: slab-use-after-free in __mutex_lock+0x1aab/0x21c0 Read of size 8 at addr ffff88803ee9a108 by task swapper/0/0 Call Trace: __mutex_lock backlight_device_set_brightness appletb_inactivity_timer call_timer_fn run_timer_softirq handle_softirqs Allocated by task N: devm_backlight_device_register appletb_bl_probe Freed by task M: (concurrent hid_appletb_bl unbind path) Close both windows at once by reworking the tear-down in appletb_kbd_remove() and in the probe close_hw error path so that 1) hid_hw_close()/hid_hw_stop() run before the backlight cleanup, guaranteeing no further .event callback can fire and re-arm the timer, and 2) inside the "if (kbd->backlight_dev)" block, timer_delete_sync() runs before put_device(), so the softirq is drained before the final reference is dropped. Fixes: 38224c472a03 ("HID: appletb-kbd: fix slab use-after-free bug in appletb_kbd_probe") Cc: stable@vger.kernel.org Signed-off-by: Sangyun Kim Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-appletb-kbd.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/hid/hid-appletb-kbd.c b/drivers/hid/hid-appletb-kbd.c index 0fdc0968b9ef..8feac9e3589b 100644 --- a/drivers/hid/hid-appletb-kbd.c +++ b/drivers/hid/hid-appletb-kbd.c @@ -440,13 +440,13 @@ static int appletb_kbd_probe(struct hid_device *hdev, const struct hid_device_id unregister_handler: input_unregister_handler(&kbd->inp_handler); close_hw: - if (kbd->backlight_dev) { - put_device(&kbd->backlight_dev->dev); - timer_delete_sync(&kbd->inactivity_timer); - } hid_hw_close(hdev); stop_hw: hid_hw_stop(hdev); + if (kbd->backlight_dev) { + timer_delete_sync(&kbd->inactivity_timer); + put_device(&kbd->backlight_dev->dev); + } return ret; } @@ -457,13 +457,13 @@ static void appletb_kbd_remove(struct hid_device *hdev) appletb_kbd_set_mode(kbd, APPLETB_KBD_MODE_OFF); input_unregister_handler(&kbd->inp_handler); - if (kbd->backlight_dev) { - put_device(&kbd->backlight_dev->dev); - timer_delete_sync(&kbd->inactivity_timer); - } - hid_hw_close(hdev); hid_hw_stop(hdev); + + if (kbd->backlight_dev) { + timer_delete_sync(&kbd->inactivity_timer); + put_device(&kbd->backlight_dev->dev); + } } static int appletb_kbd_suspend(struct hid_device *hdev, pm_message_t msg) From 91813aac1e06b3dc1857e399d62e08265923a16f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:45 +0200 Subject: [PATCH 276/295] HID: appletb-kbd: run inactivity autodim from workqueues JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 CVE: CVE-2026-46202 commit 1654e53349d4e657b331de354313461f401f5063 Author: Sangyun Kim Date: Mon Apr 20 14:13:18 2026 +0900 HID: appletb-kbd: run inactivity autodim from workqueues The autodim code in hid-appletb-kbd takes backlight_device->ops_lock via backlight_device_set_brightness() -> mutex_lock() from two different atomic contexts: * appletb_inactivity_timer() is a struct timer_list callback, so it runs in softirq context. Every expiry triggers BUG: sleeping function called from invalid context at kernel/locking/mutex.c:591 Call Trace: __might_resched __mutex_lock backlight_device_set_brightness appletb_inactivity_timer call_timer_fn run_timer_softirq * reset_inactivity_timer() is called from appletb_kbd_hid_event() and appletb_kbd_inp_event(). On real USB hardware these run in softirq/IRQ context (URB completion and input-event dispatch). When the Touch Bar has already been dimmed or turned off, the reset path calls backlight_device_set_brightness() directly to restore brightness, producing the same warning. Both call sites hit the same mutex_lock()-from-atomic bug. Fix them together by moving the blocking work onto the system workqueue: * Convert the inactivity timer from struct timer_list to struct delayed_work; the callback (appletb_inactivity_work) now runs in process context where mutex_lock() is legal. * Add a dedicated struct work_struct restore_brightness_work and have reset_inactivity_timer() schedule it instead of calling backlight_device_set_brightness() directly. Cancel both works synchronously during driver tear-down alongside the existing backlight reference drop. The semantics are unchanged (same delays, same state transitions on dim, turn-off and user activity); only the execution context of the sleeping call changes. The timer field and callback are renamed to match their new type; reset_inactivity_timer() keeps its name because it is invoked from input event paths that read naturally as "reset the inactivity timer". Fixes: 93a0fc489481 ("HID: hid-appletb-kbd: add support for automatic brightness control while using the touchbar") Cc: stable@vger.kernel.org Signed-off-by: Sangyun Kim Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-appletb-kbd.c | 44 ++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/drivers/hid/hid-appletb-kbd.c b/drivers/hid/hid-appletb-kbd.c index 8feac9e3589b..462010a75899 100644 --- a/drivers/hid/hid-appletb-kbd.c +++ b/drivers/hid/hid-appletb-kbd.c @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include "hid-ids.h" @@ -62,7 +62,8 @@ struct appletb_kbd { struct input_handle kbd_handle; struct input_handle tpd_handle; struct backlight_device *backlight_dev; - struct timer_list inactivity_timer; + struct delayed_work inactivity_work; + struct work_struct restore_brightness_work; bool has_dimmed; bool has_turned_off; u8 saved_mode; @@ -164,16 +165,18 @@ static int appletb_tb_key_to_slot(unsigned int code) } } -static void appletb_inactivity_timer(struct timer_list *t) +static void appletb_inactivity_work(struct work_struct *work) { - struct appletb_kbd *kbd = timer_container_of(kbd, t, inactivity_timer); + struct appletb_kbd *kbd = container_of(to_delayed_work(work), + struct appletb_kbd, + inactivity_work); if (kbd->backlight_dev && appletb_tb_autodim) { if (!kbd->has_dimmed) { backlight_device_set_brightness(kbd->backlight_dev, 1); kbd->has_dimmed = true; - mod_timer(&kbd->inactivity_timer, - jiffies + secs_to_jiffies(appletb_tb_idle_timeout)); + mod_delayed_work(system_wq, &kbd->inactivity_work, + secs_to_jiffies(appletb_tb_idle_timeout)); } else if (!kbd->has_turned_off) { backlight_device_set_brightness(kbd->backlight_dev, 0); kbd->has_turned_off = true; @@ -181,16 +184,25 @@ static void appletb_inactivity_timer(struct timer_list *t) } } +static void appletb_restore_brightness_work(struct work_struct *work) +{ + struct appletb_kbd *kbd = container_of(work, struct appletb_kbd, + restore_brightness_work); + + if (kbd->backlight_dev) + backlight_device_set_brightness(kbd->backlight_dev, 2); +} + static void reset_inactivity_timer(struct appletb_kbd *kbd) { if (kbd->backlight_dev && appletb_tb_autodim) { if (kbd->has_dimmed || kbd->has_turned_off) { - backlight_device_set_brightness(kbd->backlight_dev, 2); kbd->has_dimmed = false; kbd->has_turned_off = false; + schedule_work(&kbd->restore_brightness_work); } - mod_timer(&kbd->inactivity_timer, - jiffies + secs_to_jiffies(appletb_tb_dim_timeout)); + mod_delayed_work(system_wq, &kbd->inactivity_work, + secs_to_jiffies(appletb_tb_dim_timeout)); } } @@ -408,9 +420,11 @@ static int appletb_kbd_probe(struct hid_device *hdev, const struct hid_device_id dev_err_probe(dev, -ENODEV, "Failed to get backlight device\n"); } else { backlight_device_set_brightness(kbd->backlight_dev, 2); - timer_setup(&kbd->inactivity_timer, appletb_inactivity_timer, 0); - mod_timer(&kbd->inactivity_timer, - jiffies + secs_to_jiffies(appletb_tb_dim_timeout)); + INIT_DELAYED_WORK(&kbd->inactivity_work, appletb_inactivity_work); + INIT_WORK(&kbd->restore_brightness_work, + appletb_restore_brightness_work); + mod_delayed_work(system_wq, &kbd->inactivity_work, + secs_to_jiffies(appletb_tb_dim_timeout)); } kbd->inp_handler.event = appletb_kbd_inp_event; @@ -444,7 +458,8 @@ close_hw: stop_hw: hid_hw_stop(hdev); if (kbd->backlight_dev) { - timer_delete_sync(&kbd->inactivity_timer); + cancel_delayed_work_sync(&kbd->inactivity_work); + cancel_work_sync(&kbd->restore_brightness_work); put_device(&kbd->backlight_dev->dev); } return ret; @@ -461,7 +476,8 @@ static void appletb_kbd_remove(struct hid_device *hdev) hid_hw_stop(hdev); if (kbd->backlight_dev) { - timer_delete_sync(&kbd->inactivity_timer); + cancel_delayed_work_sync(&kbd->inactivity_work); + cancel_work_sync(&kbd->restore_brightness_work); put_device(&kbd->backlight_dev->dev); } } From 1cc948bf499828db219379f0d9015a8fc3d082bd Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:46 +0200 Subject: [PATCH 277/295] HID: google: hammer: stop hardware on devres action failure JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit b08665fe80fab0956e64741c07d9bbcec635c34d Author: Myeonghun Pak Date: Fri Apr 24 21:50:41 2026 +0900 HID: google: hammer: stop hardware on devres action failure hammer_probe() starts the HID hardware before registering the devres action that stops it. If devm_add_action() fails, probe returns an error with the hardware still started because the cleanup action was never registered and the driver's remove callback is not called after a failed probe. Use devm_add_action_or_reset() so the stop action runs immediately on registration failure while preserving the existing devres-managed cleanup path for later probe failures and remove. Signed-off-by: Myeonghun Pak Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-google-hammer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index 9eee26e558e9..d3fbf5e378f1 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -496,7 +496,7 @@ static int hammer_probe(struct hid_device *hdev, if (error) return error; - error = devm_add_action(&hdev->dev, hammer_stop, hdev); + error = devm_add_action_or_reset(&hdev->dev, hammer_stop, hdev); if (error) return error; From 42ac07fd2f4a531917111ac4c943a990cc43d8d2 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:47 +0200 Subject: [PATCH 278/295] HID: pass the buffer size to hid_report_raw_event JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 2c85c61d1332e1e16f020d76951baf167dcb6f7a Author: Benjamin Tissoires Date: Mon May 4 10:47:22 2026 +0200 HID: pass the buffer size to hid_report_raw_event commit 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing bogus memset()") enforced the provided data to be at least the size of the declared buffer in the report descriptor to prevent a buffer overflow. However, we can try to be smarter by providing both the buffer size and the data size, meaning that hid_report_raw_event() can make better decision whether we should plaining reject the buffer (buffer overflow attempt) or if we can safely memset it to 0 and pass it to the rest of the stack. Fixes: 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing bogus memset()") Cc: stable@vger.kernel.org Signed-off-by: Benjamin Tissoires Acked-by: Johan Hovold Reviewed-by: Greg Kroah-Hartman Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/hid_bpf_dispatch.c | 6 +++-- drivers/hid/hid-core.c | 42 ++++++++++++++++++++---------- drivers/hid/hid-gfrm.c | 4 +-- drivers/hid/hid-logitech-hidpp.c | 2 +- drivers/hid/hid-multitouch.c | 2 +- drivers/hid/hid-primax.c | 2 +- drivers/hid/hid-vivaldi-common.c | 2 +- drivers/hid/wacom_sys.c | 6 ++--- drivers/staging/greybus/hid.c | 2 +- include/linux/hid.h | 4 +-- include/linux/hid_bpf.h | 14 ++++++---- 11 files changed, 53 insertions(+), 33 deletions(-) diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index 50c7b45c59e3..d0130658091b 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -24,7 +24,8 @@ EXPORT_SYMBOL(hid_ops); u8 * dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type, u8 *data, - u32 *size, int interrupt, u64 source, bool from_bpf) + size_t *buf_size, u32 *size, int interrupt, u64 source, + bool from_bpf) { struct hid_bpf_ctx_kern ctx_kern = { .ctx = { @@ -74,6 +75,7 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type *size = ret; } + *buf_size = ctx_kern.ctx.allocated_size; return ctx_kern.data; } EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event); @@ -505,7 +507,7 @@ __hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *b if (ret) return ret; - return hid_ops->hid_input_report(ctx->hid, type, buf, size, 0, (u64)(long)ctx, true, + return hid_ops->hid_input_report(ctx->hid, type, buf, size, size, 0, (u64)(long)ctx, true, lock_already_taken); } diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index e5cac535d7a4..8ceecfbb3104 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2033,24 +2033,32 @@ int __hid_request(struct hid_device *hid, struct hid_report *report, } EXPORT_SYMBOL_GPL(__hid_request); -int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size, - int interrupt) +int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data, + size_t bufsize, u32 size, int interrupt) { struct hid_report_enum *report_enum = hid->report_enum + type; struct hid_report *report; struct hid_driver *hdrv; int max_buffer_size = HID_MAX_BUFFER_SIZE; u32 rsize, csize = size; + size_t bsize = bufsize; u8 *cdata = data; int ret = 0; report = hid_get_report(report_enum, data); if (!report) - goto out; + return 0; + + if (unlikely(bsize < csize)) { + hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n", + report->id, csize, bsize); + return -EINVAL; + } if (report_enum->numbered) { cdata++; csize--; + bsize--; } rsize = hid_compute_report_size(report); @@ -2063,11 +2071,16 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 * else if (rsize > max_buffer_size) rsize = max_buffer_size; + if (bsize < rsize) { + hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %ld)\n", + report->id, rsize, bsize); + return -EINVAL; + } + if (csize < rsize) { - hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %d)\n", - report->id, rsize, csize); - ret = -EINVAL; - goto out; + dbg_hid("report %d is too short, (%d < %d)\n", report->id, + csize, rsize); + memset(cdata + csize, 0, rsize - csize); } if ((hid->claimed & HID_CLAIMED_HIDDEV) && hid->hiddev_report_event) @@ -2075,7 +2088,7 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 * if (hid->claimed & HID_CLAIMED_HIDRAW) { ret = hidraw_report_event(hid, data, size); if (ret) - goto out; + return ret; } if (hid->claimed != HID_CLAIMED_HIDRAW && report->maxfield) { @@ -2087,15 +2100,15 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 * if (hid->claimed & HID_CLAIMED_INPUT) hidinput_report_event(hid, report); -out: + return ret; } EXPORT_SYMBOL_GPL(hid_report_raw_event); static int __hid_input_report(struct hid_device *hid, enum hid_report_type type, - u8 *data, u32 size, int interrupt, u64 source, bool from_bpf, - bool lock_already_taken) + u8 *data, size_t bufsize, u32 size, int interrupt, u64 source, + bool from_bpf, bool lock_already_taken) { struct hid_report_enum *report_enum; struct hid_driver *hdrv; @@ -2120,7 +2133,8 @@ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type, report_enum = hid->report_enum + type; hdrv = hid->driver; - data = dispatch_hid_bpf_device_event(hid, type, data, &size, interrupt, source, from_bpf); + data = dispatch_hid_bpf_device_event(hid, type, data, &bufsize, &size, interrupt, + source, from_bpf); if (IS_ERR(data)) { ret = PTR_ERR(data); goto unlock; @@ -2149,7 +2163,7 @@ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type, goto unlock; } - ret = hid_report_raw_event(hid, type, data, size, interrupt); + ret = hid_report_raw_event(hid, type, data, bufsize, size, interrupt); unlock: if (!lock_already_taken) @@ -2171,7 +2185,7 @@ unlock: int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size, int interrupt) { - return __hid_input_report(hid, type, data, size, interrupt, 0, + return __hid_input_report(hid, type, data, size, size, interrupt, 0, false, /* from_bpf */ false /* lock_already_taken */); } diff --git a/drivers/hid/hid-gfrm.c b/drivers/hid/hid-gfrm.c index 699186ff2349..d2a56bf92b41 100644 --- a/drivers/hid/hid-gfrm.c +++ b/drivers/hid/hid-gfrm.c @@ -66,7 +66,7 @@ static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report, switch (data[1]) { case GFRM100_SEARCH_KEY_DOWN: ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_dn, - sizeof(search_key_dn), 1); + sizeof(search_key_dn), sizeof(search_key_dn), 1); break; case GFRM100_SEARCH_KEY_AUDIO_DATA: @@ -74,7 +74,7 @@ static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report, case GFRM100_SEARCH_KEY_UP: ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_up, - sizeof(search_key_up), 1); + sizeof(search_key_up), sizeof(search_key_up), 1); break; default: diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index b1330d23bd2d..b3ff9265377b 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -3673,7 +3673,7 @@ static int hidpp10_consumer_keys_raw_event(struct hidpp_device *hidpp, memcpy(&consumer_report[1], &data[3], 4); /* We are called from atomic context */ hid_report_raw_event(hidpp->hid_dev, HID_INPUT_REPORT, - consumer_report, 5, 1); + consumer_report, sizeof(consumer_report), 5, 1); return 1; } diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index e82a3c4e5b44..eeab0b6e32cc 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -533,7 +533,7 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) } ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, buf, - size, 0); + size, size, 0); if (ret) dev_warn(&hdev->dev, "failed to report feature\n"); } diff --git a/drivers/hid/hid-primax.c b/drivers/hid/hid-primax.c index e44d79dff8de..8db054280afb 100644 --- a/drivers/hid/hid-primax.c +++ b/drivers/hid/hid-primax.c @@ -44,7 +44,7 @@ static int px_raw_event(struct hid_device *hid, struct hid_report *report, data[0] |= (1 << (data[idx] - 0xE0)); data[idx] = 0; } - hid_report_raw_event(hid, HID_INPUT_REPORT, data, size, 0); + hid_report_raw_event(hid, HID_INPUT_REPORT, data, size, size, 0); return 1; default: /* unknown report */ diff --git a/drivers/hid/hid-vivaldi-common.c b/drivers/hid/hid-vivaldi-common.c index bf734055d4b6..b12bb5cc091a 100644 --- a/drivers/hid/hid-vivaldi-common.c +++ b/drivers/hid/hid-vivaldi-common.c @@ -85,7 +85,7 @@ void vivaldi_feature_mapping(struct hid_device *hdev, } ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, report_data, - report_len, 0); + report_len, report_len, 0); if (ret) { dev_warn(&hdev->dev, "failed to report feature %d\n", field->report->id); diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 0d1c6d90fe21..a32320b351e3 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -90,7 +90,7 @@ static void wacom_wac_queue_flush(struct hid_device *hdev, kfree(buf); continue; } - err = hid_report_raw_event(hdev, HID_INPUT_REPORT, buf, size, false); + err = hid_report_raw_event(hdev, HID_INPUT_REPORT, buf, size, size, false); if (err) { hid_warn(hdev, "%s: unable to flush event due to error %d\n", __func__, err); @@ -334,7 +334,7 @@ static void wacom_feature_mapping(struct hid_device *hdev, data, n, WAC_CMD_RETRIES); if (ret == n && features->type == HID_GENERIC) { ret = hid_report_raw_event(hdev, - HID_FEATURE_REPORT, data, n, 0); + HID_FEATURE_REPORT, data, n, n, 0); } else if (ret == 2 && features->type != HID_GENERIC) { features->touch_max = data[1]; } else { @@ -395,7 +395,7 @@ static void wacom_feature_mapping(struct hid_device *hdev, data, n, WAC_CMD_RETRIES); if (ret == n) { ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, - data, n, 0); + data, n, n, 0); } else { hid_warn(hdev, "%s: could not retrieve sensor offsets\n", __func__); diff --git a/drivers/staging/greybus/hid.c b/drivers/staging/greybus/hid.c index 63c77a3df591..afa78c96ede8 100644 --- a/drivers/staging/greybus/hid.c +++ b/drivers/staging/greybus/hid.c @@ -201,7 +201,7 @@ static void gb_hid_init_report(struct gb_hid *ghid, struct hid_report *report) * we just need to setup the input fields, so using * hid_report_raw_event is safe. */ - hid_report_raw_event(ghid->hid, report->type, ghid->inbuf, size, 1); + hid_report_raw_event(ghid->hid, report->type, ghid->inbuf, ghid->bufsize, size, 1); } static void gb_hid_init_reports(struct gb_hid *ghid) diff --git a/include/linux/hid.h b/include/linux/hid.h index 442a80d79e89..ac432a2ef415 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -1298,8 +1298,8 @@ static inline u32 hid_report_len(struct hid_report *report) return DIV_ROUND_UP(report->size, 8) + (report->id > 0); } -int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size, - int interrupt); +int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data, + size_t bufsize, u32 size, int interrupt); /* HID quirks API */ unsigned long hid_lookup_quirk(const struct hid_device *hdev); diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index a2e47dbcf82c..19fffa4574a4 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -72,8 +72,8 @@ struct hid_ops { int (*hid_hw_output_report)(struct hid_device *hdev, __u8 *buf, size_t len, u64 source, bool from_bpf); int (*hid_input_report)(struct hid_device *hid, enum hid_report_type type, - u8 *data, u32 size, int interrupt, u64 source, bool from_bpf, - bool lock_already_taken); + u8 *data, size_t bufsize, u32 size, int interrupt, u64 source, + bool from_bpf, bool lock_already_taken); struct module *owner; const struct bus_type *bus_type; }; @@ -200,7 +200,8 @@ struct hid_bpf { #ifdef CONFIG_HID_BPF u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type, u8 *data, - u32 *size, int interrupt, u64 source, bool from_bpf); + size_t *buf_size, u32 *size, int interrupt, u64 source, + bool from_bpf); int dispatch_hid_bpf_raw_requests(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, u32 size, enum hid_report_type rtype, @@ -215,8 +216,11 @@ int hid_bpf_device_init(struct hid_device *hid); const u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, const u8 *rdesc, unsigned int *size); #else /* CONFIG_HID_BPF */ static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type, - u8 *data, u32 *size, int interrupt, - u64 source, bool from_bpf) { return data; } + u8 *data, size_t *buf_size, u32 *size, + int interrupt, u64 source, bool from_bpf) +{ + return data; +} static inline int dispatch_hid_bpf_raw_requests(struct hid_device *hdev, unsigned char reportnum, u8 *buf, u32 size, enum hid_report_type rtype, From 892580148cfec12c04913e57d45171ae44308814 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:47 +0200 Subject: [PATCH 279/295] HID: core: introduce hid_safe_input_report() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 206342541fc887ae919774a43942dc883161fece Author: Benjamin Tissoires Date: Mon May 4 10:47:23 2026 +0200 HID: core: introduce hid_safe_input_report() hid_input_report() is used in too many places to have a commit that doesn't cross subsystem borders. Instead of changing the API, introduce a new one when things matters in the transport layers: - usbhid - i2chid This effectively revert to the old behavior for those two transport layers. Fixes: 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing bogus memset()") Cc: stable@vger.kernel.org Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 25 +++++++++++++++++++++++++ drivers/hid/i2c-hid/i2c-hid-core.c | 7 ++++--- drivers/hid/usbhid/hid-core.c | 11 ++++++----- include/linux/hid.h | 2 ++ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 8ceecfbb3104..db40c993a0f2 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2181,6 +2181,7 @@ unlock: * @interrupt: distinguish between interrupt and control transfers * * This is data entry for lower layers. + * Legacy, please use hid_safe_input_report() instead. */ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size, int interrupt) @@ -2191,6 +2192,30 @@ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data } EXPORT_SYMBOL_GPL(hid_input_report); +/** + * hid_safe_input_report - report data from lower layer (usb, bt...) + * + * @hid: hid device + * @type: HID report type (HID_*_REPORT) + * @data: report contents + * @bufsize: allocated size of the data buffer + * @size: useful size of data parameter + * @interrupt: distinguish between interrupt and control transfers + * + * This is data entry for lower layers. + * Please use this function instead of the non safe version because we provide + * here the size of the buffer, allowing hid-core to make smarter decisions + * regarding the incoming buffer. + */ +int hid_safe_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, + size_t bufsize, u32 size, int interrupt) +{ + return __hid_input_report(hid, type, data, bufsize, size, interrupt, 0, + false, /* from_bpf */ + false /* lock_already_taken */); +} +EXPORT_SYMBOL_GPL(hid_safe_input_report); + bool hid_match_one_id(const struct hid_device *hdev, const struct hid_device_id *id) { diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 5a183af3d5c6..e0a302544cef 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -574,9 +574,10 @@ static void i2c_hid_get_input(struct i2c_hid *ihid) if (ihid->hid->group != HID_GROUP_RMI) pm_wakeup_event(&ihid->client->dev, 0); - hid_input_report(ihid->hid, HID_INPUT_REPORT, - ihid->inbuf + sizeof(__le16), - ret_size - sizeof(__le16), 1); + hid_safe_input_report(ihid->hid, HID_INPUT_REPORT, + ihid->inbuf + sizeof(__le16), + ihid->bufsize - sizeof(__le16), + ret_size - sizeof(__le16), 1); } return; diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index fbbfc0f60829..5af93b9b1fb5 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -283,9 +283,9 @@ static void hid_irq_in(struct urb *urb) break; usbhid_mark_busy(usbhid); if (!test_bit(HID_RESUME_RUNNING, &usbhid->iofl)) { - hid_input_report(urb->context, HID_INPUT_REPORT, - urb->transfer_buffer, - urb->actual_length, 1); + hid_safe_input_report(urb->context, HID_INPUT_REPORT, + urb->transfer_buffer, urb->transfer_buffer_length, + urb->actual_length, 1); /* * autosuspend refused while keys are pressed * because most keyboards don't wake up when @@ -482,9 +482,10 @@ static void hid_ctrl(struct urb *urb) switch (status) { case 0: /* success */ if (usbhid->ctrl[usbhid->ctrltail].dir == USB_DIR_IN) - hid_input_report(urb->context, + hid_safe_input_report(urb->context, usbhid->ctrl[usbhid->ctrltail].report->type, - urb->transfer_buffer, urb->actual_length, 0); + urb->transfer_buffer, urb->transfer_buffer_length, + urb->actual_length, 0); break; case -ESHUTDOWN: /* unplug */ unplug = 1; diff --git a/include/linux/hid.h b/include/linux/hid.h index ac432a2ef415..bfb9859f391e 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -1030,6 +1030,8 @@ struct hid_field *hid_find_field(struct hid_device *hdev, unsigned int report_ty int hid_set_field(struct hid_field *, unsigned, __s32); int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size, int interrupt); +int hid_safe_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, + size_t bufsize, u32 size, int interrupt); struct hid_field *hidinput_get_led_field(struct hid_device *hid); unsigned int hidinput_count_leds(struct hid_device *hid); __s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code); From 83bde175901e11476288aac35cf2610d75532c20 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:48 +0200 Subject: [PATCH 280/295] HID: i2c-hid: add reset quirk for BLTP7853 touchpad JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit a991aa5e89365ba1959fae6847fd288125b209e5 Author: Xu Rao Date: Sat May 9 16:21:32 2026 +0800 HID: i2c-hid: add reset quirk for BLTP7853 touchpad The BLTP7853 I2C HID touchpad may fail to probe after reboot or reprobe because reset completion is not signalled to the host. The driver then waits for the reset-complete interrupt until it times out and the device probe fails: i2c_hid i2c-BLTP7853:00: failed to reset device. i2c_hid i2c-BLTP7853:00: can't add hid device: -61 i2c_hid: probe of i2c-BLTP7853:00 failed with error -61 Add I2C_HID_QUIRK_NO_IRQ_AFTER_RESET for the device so i2c-hid does not wait for a reset interrupt that may never arrive. Signed-off-by: Xu Rao Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 3 +++ drivers/hid/i2c-hid/i2c-hid-core.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index de55d3baadfc..0848b9f7e445 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -275,6 +275,9 @@ #define USB_VENDOR_ID_BIGBEN 0x146b #define USB_DEVICE_ID_BIGBEN_PS3OFMINIPAD 0x0902 +#define I2C_VENDOR_ID_BLTP 0x36b6 +#define I2C_PRODUCT_ID_BLTP7853 0xc001 + #define USB_VENDOR_ID_BTC 0x046e #define USB_DEVICE_ID_BTC_EMPREX_REMOTE 0x5578 #define USB_DEVICE_ID_BTC_EMPREX_REMOTE_2 0x5577 diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index e0a302544cef..3adb16366e93 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -149,6 +149,8 @@ static const struct i2c_hid_quirks { I2C_HID_QUIRK_BOGUS_IRQ }, { I2C_VENDOR_ID_GOODIX, I2C_DEVICE_ID_GOODIX_0D42, I2C_HID_QUIRK_DELAY_WAKEUP_AFTER_RESUME }, + { I2C_VENDOR_ID_BLTP, I2C_PRODUCT_ID_BLTP7853, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { 0, 0 } }; From ee3cbede68ae6d0b07f1c85f3b4f4b2b42f939d4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:49 +0200 Subject: [PATCH 281/295] HID: pidff: Fix integer overflow in pidff_rescale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 48d1677779ad6816978ad4a4f7588aec5ec960fe Author: Tomasz Pakuła Date: Sun May 10 14:23:52 2026 +0200 HID: pidff: Fix integer overflow in pidff_rescale Rescaling values close to the max (U16_MAX) temporarily creates values that exceed the s32 range. This caused value overflow in case when, for example, a periodic effect phase was higer than 180 degrees. In turn, rescale function could return values outised of the logical range of the HID field. Fix by using 64 bit signed integer to store the value during calculation but still return only 32 bit integer. Closes: https://github.com/JacKeTUs/universal-pidff/issues/116 Fixes: 224ee88fe395 ("Input: add force feedback driver for PID devices") Cc: stable@vger.kernel.org Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index aee8a4443305..c45f182d0448 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -11,6 +11,7 @@ #include "hid-pidff.h" #include #include +#include #include #include #include @@ -326,8 +327,10 @@ static s32 pidff_clamp(s32 i, struct hid_field *field) */ static int pidff_rescale(int i, int max, struct hid_field *field) { - return i * (field->logical_maximum - field->logical_minimum) / max + - field->logical_minimum; + /* 64 bits needed for big values during rescale */ + s64 result = field->logical_maximum - field->logical_minimum; + + return div_s64(result * i, max) + field->logical_minimum; } /* From a99443831230cdf8290f4c9210a62c3b0ddf1bf8 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:50 +0200 Subject: [PATCH 282/295] HID: logitech-hidpp: Add support for newer Bluetooth keyboards JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 64ffa2e5e02ff54b23221d0282155f37283fabea Author: Alain Michaud Date: Tue May 12 13:22:44 2026 +0000 HID: logitech-hidpp: Add support for newer Bluetooth keyboards Add product IDs (PIDs) for several newer Logitech Bluetooth keyboards to the hidpp_devices matching table, enabling full HID++ support for them. The added keyboards are: - Logitech Signature K650 & B2B - Logitech Pebble Keys 2 K380S - Logitech Casa Pop-Up Desk & B2B - Logitech Wave Keys & B2B - Logitech Signature Slim K950 & B2B - Logitech MX Keys S & B2B - Logitech Keys-To-Go 2 - Logitech Pop Icon Keys - Logitech MX Keys Mini & B2B - Logitech Signature Slim Solar+ K980 B2B - Logitech Bluetooth Keyboard K250/K251 - Logitech Signature Comfort K880 & B2B Signed-off-by: Alain Michaud Reviewed-by: Olivier Gay Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index b3ff9265377b..ccbf28869a96 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -4685,6 +4685,44 @@ static const struct hid_device_id hidpp_devices[] = { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb391) }, { /* MX Master 4 mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb042) }, + { /* Logitech Signature K650 over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb36f) }, + { /* Logitech Signature K650 B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb370) }, + { /* Logitech Pebble Keys 2 K380S over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb377) }, + { /* Logitech Casa Pop-Up Desk over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb371) }, + { /* Logitech Casa Pop-Up Desk B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb374) }, + { /* Logitech Wave Keys over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb383) }, + { /* Logitech Wave Keys B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb384) }, + { /* Logitech Signature Slim K950 over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb386) }, + { /* Logitech Signature Slim K950 B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb388) }, + { /* Logitech MX Keys S over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb378) }, + { /* Logitech MX Keys S B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb380) }, + { /* Logitech Keys-To-Go 2 over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb38c) }, + { /* Logitech Pop Icon Keys over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb38f) }, + { /* Logitech MX Keys Mini over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb369) }, + { /* Logitech MX Keys Mini B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb36e) }, + { /* Logitech Signature Slim Solar+ K980 B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb394) }, + { /* Logitech Bluetooth Keyboard K250/K251 over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb397) }, + { /* Logitech Signature Comfort K880 over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb39c) }, + { /* Logitech Signature Comfort K880 B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb39d) }, {} }; From 26943f094d72b87bfa290bad2c8be33c944bc1eb Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:50 +0200 Subject: [PATCH 283/295] HID: core: Fix size_t specifier in hid_report_raw_event() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 4d3a2a466b8d68d852a1f3bbf11204b718428dc4 Author: Nathan Chancellor Date: Sun May 17 13:51:01 2026 +0900 HID: core: Fix size_t specifier in hid_report_raw_event() When building for 32-bit platforms, for which 'size_t' is 'unsigned int', there are warnings around using the incorrect format specifier to print bsize in hid_report_raw_event(): drivers/hid/hid-core.c:2054:29: error: format specifies type 'long' but the argument has type 'size_t' (aka 'unsigned int') [-Werror,-Wformat] 2053 | hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n", | ~~~ | %zu 2054 | report->id, csize, bsize); | ^~~~~ drivers/hid/hid-core.c:2076:29: error: format specifies type 'long' but the argument has type 'size_t' (aka 'unsigned int') [-Werror,-Wformat] 2075 | hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %ld)\n", | ~~~ | %zu 2076 | report->id, rsize, bsize); | ^~~~~ Use the proper 'size_t' format specifier, '%zu', to clear up the warnings. Cc: stable@vger.kernel.org Fixes: 2c85c61d1332 ("HID: pass the buffer size to hid_report_raw_event") Reported-by: Miguel Ojeda Closes: https://lore.kernel.org/20260516020430.110135-1-ojeda@kernel.org/ Signed-off-by: Nathan Chancellor Signed-off-by: Linus Torvalds Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index db40c993a0f2..81c91235c5e0 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2050,7 +2050,7 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 * return 0; if (unlikely(bsize < csize)) { - hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n", + hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %zu)\n", report->id, csize, bsize); return -EINVAL; } @@ -2072,7 +2072,7 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 * rsize = max_buffer_size; if (bsize < rsize) { - hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %ld)\n", + hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %zu)\n", report->id, rsize, bsize); return -EINVAL; } From 030ab15578fa5d971f82516c1d1cb78cc766949f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:51 +0200 Subject: [PATCH 284/295] HID: u2fzero: free allocated URB on probe errors JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 2e78b21864dd6e21b76160753ea632b5e758fdbd Author: Myeonghun Pak Date: Fri Apr 24 22:21:31 2026 +0900 HID: u2fzero: free allocated URB on probe errors u2fzero_fill_in_urb() allocates dev->urb with usb_alloc_urb(), but u2fzero_probe() ignored its return value and only freed the URB from u2fzero_remove(). If LED or hwrng registration fails after the URB allocation, probe returns an error and the driver core does not call .remove(), leaking the URB. A failed URB setup was also allowed to continue probing with an unusable device. Check the URB setup result and add the missing probe-error unwind so the URB is freed before returning from later errors. Signed-off-by: Myeonghun Pak Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-u2fzero.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/drivers/hid/hid-u2fzero.c b/drivers/hid/hid-u2fzero.c index 744a91e6e78c..82404b6e2d25 100644 --- a/drivers/hid/hid-u2fzero.c +++ b/drivers/hid/hid-u2fzero.c @@ -341,29 +341,33 @@ static int u2fzero_probe(struct hid_device *hdev, if (ret) return ret; - u2fzero_fill_in_urb(dev); + ret = u2fzero_fill_in_urb(dev); + if (ret) + goto err_hid_hw_stop; dev->present = true; minor = ((struct hidraw *) hdev->hidraw)->minor; ret = u2fzero_init_led(dev, minor); - if (ret) { - hid_hw_stop(hdev); - return ret; - } + if (ret) + goto err_free_urb; hid_info(hdev, "%s LED initialised\n", hw_configs[dev->hw_revision].name); ret = u2fzero_init_hwrng(dev, minor); - if (ret) { - hid_hw_stop(hdev); - return ret; - } + if (ret) + goto err_free_urb; hid_info(hdev, "%s RNG initialised\n", hw_configs[dev->hw_revision].name); return 0; + +err_free_urb: + usb_free_urb(dev->urb); +err_hid_hw_stop: + hid_hw_stop(hdev); + return ret; } static void u2fzero_remove(struct hid_device *hdev) From 88440d575229d3e38d6d28e3c0dba08c5d1bc809 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:52 +0200 Subject: [PATCH 285/295] HID: remove duplicate hid_warn_ratelimited definition JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit dd2147375a8fe7c5bc3f1f1b1d3a9567c26faefa Author: Liu Kai Date: Thu May 7 16:32:04 2026 +0800 HID: remove duplicate hid_warn_ratelimited definition The hid_warn_ratelimited macro is defined twice in include/linux/hid.h: - first one added by commit 4051ead99888 ("HID: rate-limit hid_warn to prevent log flooding") - second one added by commit 1d64624243af ("HID: core: Add printk_ratelimited variants to hid_warn() etc")). The second definition is correctly grouped with other ratelimited macros. Remove the duplicate definition. Fixes: 1d64624243af ("HID: core: Add printk_ratelimited variants to hid_warn() etc") Signed-off-by: Liu Kai [bentiss: edited commit message] Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- include/linux/hid.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/linux/hid.h b/include/linux/hid.h index bfb9859f391e..47dc0bc89fa4 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -1316,8 +1316,6 @@ void hid_quirks_exit(__u16 bus); dev_notice(&(hid)->dev, fmt, ##__VA_ARGS__) #define hid_warn(hid, fmt, ...) \ dev_warn(&(hid)->dev, fmt, ##__VA_ARGS__) -#define hid_warn_ratelimited(hid, fmt, ...) \ - dev_warn_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__) #define hid_info(hid, fmt, ...) \ dev_info(&(hid)->dev, fmt, ##__VA_ARGS__) #define hid_dbg(hid, fmt, ...) \ From f0fa8d9ed17f23d9455a988aab1aa55370ef3742 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:52 +0200 Subject: [PATCH 286/295] HID: quirks: Add ALWAYS_POLL quirk for SIGMACHIP USB mouse JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 07466fc91c55532edcfb5c6a7ccd2ea52728d6bd Author: hlleng Date: Tue May 12 09:57:37 2026 +0800 HID: quirks: Add ALWAYS_POLL quirk for SIGMACHIP USB mouse The SIGMACHIP USB mouse with VID/PID 1c4f:0034 can disconnect and re-enumerate repeatedly after it has been enumerated if its interrupt endpoint is not continuously polled. This was observed with the device reporting itself as "SIGMACHIP Usb Mouse". Keeping the input event device open avoids the disconnects. Add HID_QUIRK_ALWAYS_POLL for this device so the HID core keeps polling it even when there is no userspace input consumer. Cc: stable@vger.kernel.org Signed-off-by: hlleng Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-quirks.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 0848b9f7e445..e1fe941c30da 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1282,6 +1282,7 @@ #define USB_VENDOR_ID_SIGMA_MICRO 0x1c4f #define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD 0x0002 +#define USB_DEVICE_ID_SIGMA_MICRO_USB_MOUSE 0x0034 #define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD2 0x0059 #define USB_VENDOR_ID_SIGMATEL 0x066F diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 512049963978..57d8efdd9b89 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -187,6 +187,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD), HID_QUIRK_NO_INIT_REPORTS }, { HID_USB_DEVICE(USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB), HID_QUIRK_NOGET }, { HID_USB_DEVICE(USB_VENDOR_ID_SIGMA_MICRO, USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD), HID_QUIRK_NO_INIT_REPORTS }, + { HID_USB_DEVICE(USB_VENDOR_ID_SIGMA_MICRO, USB_DEVICE_ID_SIGMA_MICRO_USB_MOUSE), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_SIGMATEL, USB_DEVICE_ID_SIGMATEL_STMP3780), HID_QUIRK_NOGET }, { HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS1030_TOUCH), HID_QUIRK_NOGET }, { HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS817_TOUCH), HID_QUIRK_NOGET }, From f3913c14367fc250c37a2e49841b4838ac5f6f11 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:53 +0200 Subject: [PATCH 287/295] HID: lenovo: Fix buffer over-read and unaligned access in X12 Tab raw_event handler JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit c7ee0b73c8c4dfb7eafa49aaef5247890862a948 Author: Kean Date: Thu May 14 20:58:38 2026 +0800 HID: lenovo: Fix buffer over-read and unaligned access in X12 Tab raw_event handler In lenovo_raw_event(), the X12 Tab keyboard handler reads a 4-byte little-endian value from the raw HID report buffer but: 1. The size guard is size >= 3, while the access reads 4 bytes. A malformed 3-byte report with ID 0x03 would over-read the buffer by one byte. 2. Casting u8 *data directly to __le32 * can trigger unaligned access faults on architectures like ARM, MIPS, and SPARC, because HID input buffers carry no alignment guarantee. (e.g. uhid payloads start at offset 6 in struct uhid_event, giving only 2-byte alignment.) Fix both by tightening the size check to >= 4 and replacing the open-coded cast + le32_to_cpu() with get_unaligned_le32(), which handles the LE-to-CPU conversion safely regardless of alignment. Link: https://sashiko.dev/#/message/20260512044911.99B6DC2BCB0%40smtp.kernel.org Assisted-by: CLAUDE:claude-4-sonnet Signed-off-by: Kean Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index a6b73e03c16b..c11957ae8b77 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "hid-ids.h" @@ -793,8 +794,8 @@ static int lenovo_raw_event(struct hid_device *hdev, */ if (unlikely((hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB || hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB2) - && size >= 3 && report->id == 0x03)) - return lenovo_raw_event_TP_X12_tab(hdev, le32_to_cpu(*(__le32 *)data)); + && size >= 4 && report->id == 0x03)) + return lenovo_raw_event_TP_X12_tab(hdev, get_unaligned_le32(data)); return 0; } From 384b313c99c3b7734ae359927362229965b9cdec Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:54 +0200 Subject: [PATCH 288/295] HID: lenovo-go: reject non-USB transports in probe JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit 2ee7e632405b022319f42c01635eb6fbbd86414a Author: Louis Clinckx Date: Fri May 15 14:57:39 2026 +0000 HID: lenovo-go: reject non-USB transports in probe These drivers only match HID_USB_DEVICE() entries and assume the underlying bus is USB. Make that explicit at probe by rejecting any non-USB hdev, following the pattern used by other HID drivers. Signed-off-by: Louis Clinckx Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go-s.c | 3 +++ drivers/hid/hid-lenovo-go.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index ff1782a75191..0444d84498bd 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -1461,6 +1461,9 @@ static int hid_gos_probe(struct hid_device *hdev, { int ret, ep; + if (!hid_is_usb(hdev)) + return -EINVAL; + ret = hid_parse(hdev); if (ret) { hid_err(hdev, "Parse failed\n"); diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index d4d26c783356..3fa1fe83f7e5 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -2419,6 +2419,9 @@ static int hid_go_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret, ep; + if (!hid_is_usb(hdev)) + return -EINVAL; + hdev->quirks |= HID_QUIRK_INPUT_PER_APP | HID_QUIRK_MULTI_INPUT; ret = hid_parse(hdev); From 948aa229ba95f757e563f5437323e8ab0df4aa68 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:55 +0200 Subject: [PATCH 289/295] HID: lenovo-go: drop dead NULL check on to_usb_interface() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit da7f96a68c39de9eb1c351a261e7fbf716375c91 Author: Louis Clinckx Date: Fri May 15 14:57:40 2026 +0000 HID: lenovo-go: drop dead NULL check on to_usb_interface() to_usb_interface() is a container_of_const() macro: it performs pointer arithmetic and never returns NULL. The if (!intf) and if (intf) tests in get_endpoint_address() can never fire. Remove them in both drivers. No functional change. Suggested-by: Derek J. Clark Signed-off-by: Louis Clinckx Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go-s.c | 8 +++----- drivers/hid/hid-lenovo-go.c | 3 --- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 0444d84498bd..a72f7f748cb5 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -382,11 +382,9 @@ static int get_endpoint_address(struct hid_device *hdev) struct usb_interface *intf = to_usb_interface(hdev->dev.parent); struct usb_host_endpoint *ep; - if (intf) { - ep = intf->cur_altsetting->endpoint; - if (ep) - return ep->desc.bEndpointAddress; - } + ep = intf->cur_altsetting->endpoint; + if (ep) + return ep->desc.bEndpointAddress; return -ENODEV; } diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index 3fa1fe83f7e5..e0c9d5ec9451 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -641,9 +641,6 @@ static int get_endpoint_address(struct hid_device *hdev) struct usb_interface *intf = to_usb_interface(hdev->dev.parent); struct usb_host_endpoint *ep; - if (!intf) - return -ENODEV; - ep = intf->cur_altsetting->endpoint; if (!ep) return -ENODEV; From e6485e22605676168384304ed70ba0ac7fbf0865 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:19:55 +0200 Subject: [PATCH 290/295] HID: wacom: Fix OOB write in wacom_hid_set_device_mode() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.1 commit c0a8899e02ddebd51e2589835182c239c2e224ae Author: Lee Jones Date: Wed May 27 17:05:26 2026 +0100 HID: wacom: Fix OOB write in wacom_hid_set_device_mode() wacom_hid_set_device_mode() currently assumes that the HID_DG_INPUTMODE usage is always located in the first field (field[0]) of the feature report. However, a device can specify HID_DG_INPUTMODE in a different field. If HID_DG_INPUTMODE is in a field other than the first one and the first field has a report_count smaller than the usage_index of HID_DG_INPUTMODE, this leads to an out-of-bounds write to r->field[0]->value. Fix this by storing the field index of HID_DG_INPUTMODE in 'struct hid_data' during feature mapping. In wacom_hid_set_device_mode(), use this stored field index to access the correct field and add bounds checks to ensure both the field index and the value index are within valid ranges before writing. Cc: stable@vger.kernel.org Fixes: 5ae6e89f7409 ("HID: wacom: implement the finger part of the HID generic handling") Tested-by: Ping Cheng Reviewed-by: Ping Cheng Signed-off-by: Lee Jones Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/wacom_sys.c | 13 ++++++++++--- drivers/hid/wacom_wac.h | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index a32320b351e3..2220168bf116 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -356,6 +356,7 @@ static void wacom_feature_mapping(struct hid_device *hdev, hid_data->inputmode = field->report->id; hid_data->inputmode_index = usage->usage_index; + hid_data->inputmode_field_index = field->index; break; case HID_UP_DIGITIZER: @@ -571,9 +572,14 @@ static int wacom_hid_set_device_mode(struct hid_device *hdev) re = &(hdev->report_enum[HID_FEATURE_REPORT]); r = re->report_id_hash[hid_data->inputmode]; - if (r) { - r->field[0]->value[hid_data->inputmode_index] = 2; - hid_hw_request(hdev, r, HID_REQ_SET_REPORT); + if (r && hid_data->inputmode_field_index >= 0 && + hid_data->inputmode_field_index < r->maxfield) { + struct hid_field *field = r->field[hid_data->inputmode_field_index]; + + if (field && hid_data->inputmode_index < field->report_count) { + field->value[hid_data->inputmode_index] = 2; + hid_hw_request(hdev, r, HID_REQ_SET_REPORT); + } } return 0; } @@ -2846,6 +2852,7 @@ static int wacom_probe(struct hid_device *hdev, return -ENODEV; wacom_wac->hid_data.inputmode = -1; + wacom_wac->hid_data.inputmode_field_index = -1; wacom_wac->mode_report = -1; if (hid_is_usb(hdev)) { diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index d4f7d8ca1e7e..126bec6e5c0c 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -295,6 +295,7 @@ struct wacom_shared { struct hid_data { __s16 inputmode; /* InputMode HID feature, -1 if non-existent */ __s16 inputmode_index; /* InputMode HID feature index in the report */ + __s16 inputmode_field_index; /* InputMode HID feature field index in the report */ bool sense_state; bool inrange_state; bool eraser; From f815b716633f0da997667bb37861def4812a1513 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 10 Jun 2026 16:51:51 +0200 Subject: [PATCH 291/295] [redhat] New configs in drivers/hid JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: RHEL only, https://gitlab.com/cki-project/kernel-ark commit 7cd4744799c68e4d9da1dd70dfe845b8c469e163 Author: Fedora Kernel Team Date: Sat Apr 18 10:38:15 2026 +0000 [redhat] New configs in drivers/hid Hi, As part of the ongoing rebase effort, the following configuration options need to be reviewed. As a reminder, the ARK configuration flow involves moving unreviewed configuration options from the pending directory to the ark directory. In the diff below, options are removed from the pending directory and added to the ark hierarchy. The final options that need to be ACKed are the files that are being added to the ark hierarchy. If the value for a file that is added should be changed, please reply with a better option. ~~~ Symbol: HID_HUAWEI [=n] Type : tristate Defined at drivers/hid/Kconfig:1448 Prompt: Huawei HID devices support Depends on: HID_SUPPORT [=y] && HID [=y] && USB_HID [=y] Location: -> Device Drivers -> HID bus support (HID_SUPPORT [=y]) -> HID bus core support (HID [=y]) -> Special HID drivers -> Huawei HID devices support (HID_HUAWEI [=n]) ~~~ Commit: e93faaca84b7 (HID: huawei: fix CD30 keyboard report descriptor issue) --- Signed-off-by: Fedora Kernel Team Signed-off-by: Benjamin Tissoires --- redhat/configs/rhel/generic/CONFIG_HID_HUAWEI | 1 + 1 file changed, 1 insertion(+) create mode 100644 redhat/configs/rhel/generic/CONFIG_HID_HUAWEI diff --git a/redhat/configs/rhel/generic/CONFIG_HID_HUAWEI b/redhat/configs/rhel/generic/CONFIG_HID_HUAWEI new file mode 100644 index 000000000000..4b697df184db --- /dev/null +++ b/redhat/configs/rhel/generic/CONFIG_HID_HUAWEI @@ -0,0 +1 @@ +# CONFIG_HID_HUAWEI is not set From 95817b2fc304debd76fe774326b098c2257e8200 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 29 Jun 2026 15:55:07 +0200 Subject: [PATCH 292/295] Input: add ABS_SND_PROFILE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v6.19 commit 733a8924229ff8c0385121a30fcd00bf70644743 Author: Gergo Koteles Date: Thu Nov 13 17:02:58 2025 +0100 Input: add ABS_SND_PROFILE ABS_SND_PROFILE used to describe the state of a multi-value sound profile switch. This will be used for the alert-slider on OnePlus phones or other phones. Profile values added as SND_PROFLE_(SILENT|VIBRATE|RING) identifiers to input-event-codes.h so they can be used from DTS. Signed-off-by: Gergo Koteles Reviewed-by: Bjorn Andersson Tested-by: Guido Günther # oneplus,fajita & oneplus,enchilada Reviewed-by: Guido Günther Signed-off-by: David Heidelberg Reviewed-by: Pavel Machek Link: https://patch.msgid.link/20251113-op6-tri-state-v8-1-54073f3874bc@ixit.cz Signed-off-by: Dmitry Torokhov Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-debug.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index 6fb76c0c389d..f44e6e708404 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -3519,6 +3519,7 @@ static const char *absolutes[ABS_CNT] = { [ABS_DISTANCE] = "Distance", [ABS_TILT_X] = "XTilt", [ABS_TILT_Y] = "YTilt", [ABS_TOOL_WIDTH] = "ToolWidth", [ABS_VOLUME] = "Volume", [ABS_PROFILE] = "Profile", + [ABS_SND_PROFILE] = "SoundProfile", [ABS_MISC] = "Misc", [ABS_MT_SLOT] = "MTSlot", [ABS_MT_TOUCH_MAJOR] = "MTMajor", From ce1f46ab89da41e42cd856f6aaf77c39c6e95260 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 29 Jun 2026 17:14:46 +0200 Subject: [PATCH 293/295] HID: Input: Add battery list cleanup with devm action JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.2-rc1 commit 426e5846eba75feaf1c9c6c119cb153610192da1 Author: Rafael Passos Date: Tue Jun 2 00:05:19 2026 -0300 HID: Input: Add battery list cleanup with devm action The batteries list (hdev->batteries) is not cleaned up during hidinput_disconnect(), but struct hid_battery entries are allocated with devm_kzalloc. When a driver is unbound (e.g. during devicereprobe), devm frees those entries while their list_head nodesremain dangling in hdev->batteries, which persists across rebinds. Link: https://lore.kernel.org/all/20260602011949.2825852-1-rafael@rcpassos.me/ Fixes: 4a58ae85c3f9 ("HID: input: Add support for multiple batteries per device") Signed-off-by: Rafael Passos Acked-by: Lucas Zampieri Signed-off-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-input.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index d73cfa2e73d3..c7b8c4ff7a33 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -519,6 +519,13 @@ static struct hid_battery *hidinput_find_battery(struct hid_device *dev, return NULL; } +static void hidinput_cleanup_battery(void *res) +{ + struct hid_battery *bat = res; + + list_del(&bat->list); +} + static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, struct hid_field *field, bool is_percentage) { @@ -610,6 +617,12 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, power_supply_powers(bat->ps, &dev->dev); list_add_tail(&bat->list, &dev->batteries); + + error = devm_add_action_or_reset(&dev->dev, + hidinput_cleanup_battery, bat); + if (error) + return error; + return 0; err_free_name: From 1d2bdecbe0ab0debd7144b889d4db8b34616aa7c Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 29 Jun 2026 17:14:48 +0200 Subject: [PATCH 294/295] HID: hid-lenovo-go: cancel cfg_setup work in hid_go_cfg_remove() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.2-rc1 commit 73fde0cbff7d9d618591774a12c23434232752c1 Author: Manish Khadka Date: Fri May 15 23:30:11 2026 +0545 HID: hid-lenovo-go: cancel cfg_setup work in hid_go_cfg_remove() hid_go_cfg_probe() initialises drvdata.go_cfg_setup and schedules it to run 2 ms later: INIT_DELAYED_WORK(&drvdata.go_cfg_setup, &cfg_setup); schedule_delayed_work(&drvdata.go_cfg_setup, msecs_to_jiffies(2)); cfg_setup() dereferences drvdata.hdev to issue MCU command requests. hid_go_cfg_remove() tears down sysfs and stops the HID device, but never drains the delayed work. If the device is unbound within the 2 ms scheduling delay (a probe failure rolling back via remove, or a fast rmmod after probe), the work fires after hid_destroy_device() has dropped its reference and released the underlying hdev struct, leaving cfg_setup() with a stale drvdata.hdev pointer. Mirror the sibling driver hid-lenovo-go-s.c, whose hid_gos_cfg_remove() already calls cancel_delayed_work_sync() on its analogous work, and drain go_cfg_setup at the top of hid_go_cfg_remove(). The cancel must come before guard(mutex)(&drvdata.cfg_mutex) because cfg_setup() acquires that mutex; reversing the order would deadlock. Fixes: d69ccfcbc955 ("HID: hid-lenovo-go: Add Lenovo Legion Go Series HID Driver") Cc: stable@vger.kernel.org Signed-off-by: Manish Khadka Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-lenovo-go.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index e0c9d5ec9451..318b1152ff8b 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -2405,6 +2405,15 @@ static int hid_go_cfg_probe(struct hid_device *hdev, static void hid_go_cfg_remove(struct hid_device *hdev) { + /* + * cfg_setup is scheduled from hid_go_cfg_probe() with a 2 ms delay + * and dereferences drvdata.hdev. Drain it here before tearing + * down so the workqueue cannot run after hid_destroy_device()'s + * put_device() has released the underlying hdev and dereference + * a stale drvdata.hdev pointer. + */ + cancel_delayed_work_sync(&drvdata.go_cfg_setup); + guard(mutex)(&drvdata.cfg_mutex); sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups); hid_hw_close(hdev); From d4e4b7c7a40c60b2381b0b41a9307946ed826b11 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 29 Jun 2026 17:20:35 +0200 Subject: [PATCH 295/295] HID: uhid: convert to hid_safe_input_report() JIRA: https://issues.redhat.com/browse/RHEL-170872 Upstream Status: since v7.2-rc1 commit 63a694c51bf120a37550890b8e7736b4888985e9 Author: Carlos Llamas Date: Sat Jun 6 18:15:52 2026 +0000 HID: uhid: convert to hid_safe_input_report() Commit 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing bogus memset()"), added a check in hid_report_raw_event() to reject reports if the received data size is smaller than expected. This was intended to prevent OOB errors by no longer allowing zeroing-out of shorter reports due to the lack of buffer size information. However, this leads to regressions in hid_report_raw_event(), where shorter than expected reports are rejected, even though their buffers are sufficiently large to be zero-padded. To solve this issue, Benjamin introduced a safer alternative in commit 206342541fc8 ("HID: core: introduce hid_safe_input_report()"), which forwards the buffer size and allows hid_report_raw_event() to safely zero-pad the data. Convert uhid to use hid_safe_input_report() and pass UHID_DATA_MAX as the buffer size. This prevents the reported regressions [1], allowing hid core to zero-pad the shorter reports safely as expected. Cc: stable@vger.kernel.org Fixes: 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing bogus memset()") Closes: https://lore.kernel.org/all/ahsh0UtTX6e0ZeHa@google.com/ [1] Signed-off-by: Carlos Llamas Reviewed-by: Lee Jones Closes: https://lore.kernel.org/all/ahsh0UtTX6e0ZeHa@google.com/ Signed-off-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/uhid.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c index 524b53a3c87b..37b60c3aaf66 100644 --- a/drivers/hid/uhid.c +++ b/drivers/hid/uhid.c @@ -595,8 +595,8 @@ static int uhid_dev_input(struct uhid_device *uhid, struct uhid_event *ev) if (!READ_ONCE(uhid->running)) return -EINVAL; - hid_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input.data, - min_t(size_t, ev->u.input.size, UHID_DATA_MAX), 0); + hid_safe_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input.data, UHID_DATA_MAX, + min_t(size_t, ev->u.input.size, UHID_DATA_MAX), 0); return 0; } @@ -606,8 +606,8 @@ static int uhid_dev_input2(struct uhid_device *uhid, struct uhid_event *ev) if (!READ_ONCE(uhid->running)) return -EINVAL; - hid_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input2.data, - min_t(size_t, ev->u.input2.size, UHID_DATA_MAX), 0); + hid_safe_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input2.data, UHID_DATA_MAX, + min_t(size_t, ev->u.input2.size, UHID_DATA_MAX), 0); return 0; }