Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH v3 1/4] HID: hid-msi: Add MSI Claw configuration driver
From: Derek J. Clark @ 2026-05-15  3:36 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Pierre-Loup A . Griffais, Denis Benato, Zhouwang Huang,
	Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260515033622.2095277-1-derekjohn.clark@gmail.com>

Adds configuration HID driver for the MSI Claw series of handheld PC's.
In this initial patch add the initial driver outline and attributes for
changing the gamepad mode, M-key behavior, and add a WO reset function.

Sending the SWITCH_MODE and RESET commands causes a USB disconnect in
the device. The completion will therefore never get hit and would trigger
an -EIO. To avoid showing the user an error for every write to these
attrs a bypass for the completion handling is introduced when timeout ==
0.

The initial version of this patch was written by Denis Benato, which
contained the initial reverse-engineering and implementation for the
gamepad mode switching. This work was later expanded by Zhouwang Huang
to include more gamepad modes. Finally, I refactored the drivers data
in/out flow and overall format to conform to kernel driver best
practices and style guides. Claude was used as an initial reviewer of
this patch.

Assisted-by: Claude:claude-sonnet-4-6
Co-developed-by: Denis Benato <denis.benato@linux.dev>
Signed-off-by: Denis Benato <denis.benato@linux.dev>
Co-developed-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v3:
  - Ensure claw_hw_output_report is properly guarded.
  - Reoder claw_probe to ensure all mutex, completion, and variable
    assignments are in place prior to setting drvdata.
  - Ensure gamepad_mode is set to a valid enum value in claw_probe.
v2:
  - Rename driver to hid-msi from hid-msi-claw.
  - Rename reusable/generic functions to msi_* from claw_*, retaining
    claw specific functions.
  - Add generic entrypoints for probe, remove, and raw event that route
    to claw specific functions.
---
 MAINTAINERS           |   6 +
 drivers/hid/Kconfig   |  12 +
 drivers/hid/Makefile  |   1 +
 drivers/hid/hid-ids.h |   6 +
 drivers/hid/hid-msi.c | 582 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 607 insertions(+)
 create mode 100644 drivers/hid/hid-msi.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 6f6517bf4f970..8e2de98b768f7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17965,6 +17965,12 @@ S:	Odd Fixes
 F:	Documentation/devicetree/bindings/net/ieee802154/mrf24j40.txt
 F:	drivers/net/ieee802154/mrf24j40.c
 
+MSI HID DRIVER
+M:	Derek J. Clark <derekjohn.clark@gmail.com>
+L:	linux-input@vger.kernel.org
+S:	Maintained
+F:	drivers/hid/hid-msi.c
+
 MSI EC DRIVER
 M:	Nikita Kravets <teackot@gmail.com>
 L:	platform-driver-x86@vger.kernel.org
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 10c12d8e65579..af146691bd481 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -492,6 +492,18 @@ config HID_GT683R
 	Currently the following devices are know to be supported:
 	  - MSI GT683R
 
+config HID_MSI
+	tristate "MSI Claw Gamepad Support"
+	depends on USB_HID
+	select LEDS_CLASS
+	select LEDS_CLASS_MULTICOLOR
+	help
+	Support for the MSI Claw RGB and controller configuration
+
+	Say Y here to include configuration interface support for the MSI Claw Line
+	of Handheld Console Controllers. Say M here to compile this driver as a
+	module. The module will be called hid-msi.
+
 config HID_KEYTOUCH
 	tristate "Keytouch HID devices"
 	help
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 07dfdb6a49c59..80925a17b059c 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -92,6 +92,7 @@ obj-$(CONFIG_HID_MAYFLASH)	+= hid-mf.o
 obj-$(CONFIG_HID_MEGAWORLD_FF)	+= hid-megaworld.o
 obj-$(CONFIG_HID_MICROSOFT)	+= hid-microsoft.o
 obj-$(CONFIG_HID_MONTEREY)	+= hid-monterey.o
+obj-$(CONFIG_HID_MSI)		+= hid-msi.o
 obj-$(CONFIG_HID_MULTITOUCH)	+= hid-multitouch.o
 obj-$(CONFIG_HID_NINTENDO)	+= hid-nintendo.o
 obj-$(CONFIG_HID_NTI)			+= hid-nti.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 933b7943bdb50..6d0d34806931f 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1047,7 +1047,13 @@
 #define USB_DEVICE_ID_MOZA_R16_R21_2	0x0010
 
 #define USB_VENDOR_ID_MSI		0x1770
+#define USB_VENDOR_ID_MSI_2		0x0db0
 #define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00
+#define USB_DEVICE_ID_MSI_CLAW_XINPUT	0x1901
+#define USB_DEVICE_ID_MSI_CLAW_DINPUT	0x1902
+#define USB_DEVICE_ID_MSI_CLAW_DESKTOP	0x1903
+#define USB_DEVICE_ID_MSI_CLAW_BIOS	0x1904
+
 
 #define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400
 #define USB_DEVICE_ID_N_S_HARMONY	0xc359
diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
new file mode 100644
index 0000000000000..89bb32f00bfc3
--- /dev/null
+++ b/drivers/hid/hid-msi.c
@@ -0,0 +1,582 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *  HID driver for MSI Claw Handheld PC gamepads.
+ *
+ *  Provides configuration support for the MSI Claw series of handheld PC
+ *  gamepads. Multiple iterations of the device firmware has led to some
+ *  quirks for how certain attributes are handled. The original firmware
+ *  did not support remapping of the M1 (right) and M2 (left) rear paddles.
+ *  Additionally, the MCU RAM address for writing configuration data has
+ *  changed twice. Checks are done during probe to enumerate these variances.
+ *
+ *  Copyright (c) 2026 Zhouwang Huang <honjow311@gmail.com>
+ *  Copyright (c) 2026 Denis Benato <denis.benato@linux.dev>
+ *  Copyright (c) 2026 Valve Corporation
+ */
+
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/kobject.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#include "hid-ids.h"
+
+#define CLAW_OUTPUT_REPORT_ID	0x0f
+#define CLAW_INPUT_REPORT_ID	0x10
+
+#define CLAW_PACKET_SIZE	64
+
+#define CLAW_DINPUT_CFG_INTF_IN	0x82
+#define CLAW_XINPUT_CFG_INTF_IN	0x83
+
+enum claw_command_index {
+	CLAW_COMMAND_TYPE_READ_PROFILE =		0x04,
+	CLAW_COMMAND_TYPE_READ_PROFILE_ACK =		0x05,
+	CLAW_COMMAND_TYPE_ACK =				0x06,
+	CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA =		0x21,
+	CLAW_COMMAND_TYPE_SYNC_TO_ROM =			0x22,
+	CLAW_COMMAND_TYPE_SWITCH_MODE =			0x24,
+	CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE =		0x26,
+	CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK =		0x27,
+	CLAW_COMMAND_TYPE_RESET_DEVICE =		0x28,
+};
+
+enum claw_gamepad_mode_index {
+	CLAW_GAMEPAD_MODE_XINPUT =	0x01,
+	CLAW_GAMEPAD_MODE_DINPUT =	0x02,
+	CLAW_GAMEPAD_MODE_DESKTOP =	0x04,
+};
+
+static const char * const claw_gamepad_mode_text[] = {
+	[CLAW_GAMEPAD_MODE_XINPUT] =	"xinput",
+	[CLAW_GAMEPAD_MODE_DINPUT] =	"dinput",
+	[CLAW_GAMEPAD_MODE_DESKTOP] =	"desktop",
+};
+
+enum claw_mkeys_function_index {
+	CLAW_MKEY_FUNCTION_MACRO,
+	CLAW_MKEY_FUNCTION_COMBO,
+	CLAW_MKEY_FUNCTION_DISABLED,
+};
+
+static const char * const claw_mkeys_function_text[] = {
+	[CLAW_MKEY_FUNCTION_MACRO] =	"macro",
+	[CLAW_MKEY_FUNCTION_COMBO] =	"combination",
+	[CLAW_MKEY_FUNCTION_DISABLED] =	"disabled",
+};
+
+struct claw_command_report {
+	u8 report_id;
+	u8 padding[2];
+	u8 header_tail;
+	u8 cmd;
+	u8 data[59];
+} __packed;
+
+struct claw_drvdata {
+	/* MCU General Variables */
+	struct completion send_cmd_complete;
+	struct delayed_work cfg_resume;
+	struct delayed_work cfg_setup;
+	struct hid_device *hdev;
+	struct mutex cfg_mutex; /* mutex for synchronous data */
+	u8 ep;
+
+	/* Gamepad Variables */
+	enum claw_mkeys_function_index mkeys_function;
+	enum claw_gamepad_mode_index gamepad_mode;
+};
+
+static int get_endpoint_address(struct hid_device *hdev)
+{
+	struct usb_host_endpoint *ep;
+	struct usb_interface *intf;
+
+	intf = to_usb_interface(hdev->dev.parent);
+	ep = intf->cur_altsetting->endpoint;
+	if (ep)
+		return ep->desc.bEndpointAddress;
+
+	return -ENODEV;
+}
+
+static int claw_gamepad_mode_event(struct claw_drvdata *drvdata,
+				   struct claw_command_report *cmd_rep)
+{
+	if (cmd_rep->data[0] >= ARRAY_SIZE(claw_gamepad_mode_text) ||
+	    !claw_gamepad_mode_text[cmd_rep->data[0]] ||
+	    cmd_rep->data[1] >= ARRAY_SIZE(claw_mkeys_function_text))
+		return -EINVAL;
+
+	drvdata->gamepad_mode = cmd_rep->data[0];
+	drvdata->mkeys_function = cmd_rep->data[1];
+
+	return 0;
+}
+
+static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *report,
+			  u8 *data, int size)
+{
+	struct claw_command_report *cmd_rep;
+	int ret = 0;
+
+	if (size != CLAW_PACKET_SIZE)
+		return 0;
+
+	cmd_rep = (struct claw_command_report *)data;
+
+	if (cmd_rep->report_id != CLAW_INPUT_REPORT_ID || cmd_rep->header_tail != 0x3c)
+		return 0;
+
+	dev_dbg(&drvdata->hdev->dev, "Rx data as raw input report: [%*ph]\n",
+		CLAW_PACKET_SIZE, data);
+
+	switch (cmd_rep->cmd) {
+	case CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK:
+		ret = claw_gamepad_mode_event(drvdata, cmd_rep);
+		break;
+	case CLAW_COMMAND_TYPE_ACK:
+		break;
+	default:
+		dev_dbg(&drvdata->hdev->dev, "Unknown command: %x\n", cmd_rep->cmd);
+		return 0;
+	}
+
+	complete(&drvdata->send_cmd_complete);
+
+	return ret;
+}
+
+static int msi_raw_event(struct hid_device *hdev, struct hid_report *report,
+			 u8 *data, int size)
+{
+	struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	if (!drvdata || (drvdata->ep != CLAW_XINPUT_CFG_INTF_IN &&
+			 drvdata->ep != CLAW_DINPUT_CFG_INTF_IN))
+		return 0;
+
+	return claw_raw_event(drvdata, report, data, size);
+}
+
+static int claw_hw_output_report(struct hid_device *hdev, u8 index, u8 *data,
+				 size_t len, unsigned int timeout)
+{
+	unsigned char *dmabuf __free(kfree) = NULL;
+	u8 header[] = { CLAW_OUTPUT_REPORT_ID, 0, 0, 0x3c, index };
+	struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+	size_t header_size = ARRAY_SIZE(header);
+	int ret;
+
+	if (header_size + len > CLAW_PACKET_SIZE)
+		return -EINVAL;
+
+	/* We can't use a devm_alloc reusable buffer without side effects during suspend */
+	dmabuf = kzalloc(CLAW_PACKET_SIZE, GFP_KERNEL);
+	if (!dmabuf)
+		return -ENOMEM;
+
+	memcpy(dmabuf, header, header_size);
+	if (data && len)
+		memcpy(dmabuf + header_size, data, len);
+
+	guard(mutex)(&drvdata->cfg_mutex);
+	if (timeout)
+		reinit_completion(&drvdata->send_cmd_complete);
+
+	dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n",
+		CLAW_PACKET_SIZE, dmabuf);
+
+	ret = hid_hw_output_report(hdev, dmabuf, CLAW_PACKET_SIZE);
+	if (ret < 0)
+		return ret;
+
+	ret = ret == CLAW_PACKET_SIZE ? 0 : -EIO;
+	if (ret)
+		return ret;
+
+	if (timeout) {
+		ret = wait_for_completion_interruptible_timeout(&drvdata->send_cmd_complete,
+								msecs_to_jiffies(timeout));
+
+		dev_dbg(&hdev->dev, "Remaining timeout: %u\n", ret);
+		if (ret >= 0) /* preserve errors */
+			ret = ret == 0 ? -EBUSY : 0; /* timeout occurred : time remained */
+	}
+
+	return ret;
+}
+
+static ssize_t gamepad_mode_store(struct device *dev, struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+	u8 data[2] = { 0x00, drvdata->mkeys_function };
+	int i, ret = -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(claw_gamepad_mode_text); i++) {
+		if (claw_gamepad_mode_text[i] && sysfs_streq(buf, claw_gamepad_mode_text[i])) {
+			ret = i;
+			break;
+		}
+	}
+	if (ret < 0)
+		return ret;
+
+	data[0] = ret;
+
+	ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data, ARRAY_SIZE(data), 0);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t gamepad_mode_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+	int ret, i;
+
+	ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 8);
+	if (ret)
+		return ret;
+
+	i = drvdata->gamepad_mode;
+
+	if (!claw_gamepad_mode_text[i] || claw_gamepad_mode_text[i][0] == '\0')
+		return sysfs_emit(buf, "unsupported\n");
+
+	return sysfs_emit(buf, "%s\n", claw_gamepad_mode_text[i]);
+}
+static DEVICE_ATTR_RW(gamepad_mode);
+
+static ssize_t gamepad_mode_index_show(struct device *dev,
+				       struct device_attribute *attr, char *buf)
+{
+	ssize_t count = 0;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(claw_gamepad_mode_text); i++) {
+		if (!claw_gamepad_mode_text[i] || claw_gamepad_mode_text[i][0] == '\0')
+			continue;
+		count += sysfs_emit_at(buf, count, "%s ", claw_gamepad_mode_text[i]);
+	}
+
+	buf[count - 1] = '\n';
+
+	return count;
+}
+static DEVICE_ATTR_RO(gamepad_mode_index);
+
+static ssize_t mkeys_function_store(struct device *dev, struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+	u8 data[2] = { drvdata->gamepad_mode, 0x00 };
+	int i, ret = -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(claw_mkeys_function_text); i++) {
+		if (claw_mkeys_function_text[i] && sysfs_streq(buf, claw_mkeys_function_text[i])) {
+			ret = i;
+			break;
+		}
+	}
+	if (ret < 0)
+		return ret;
+
+	data[1] = ret;
+
+	ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data, ARRAY_SIZE(data), 0);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t mkeys_function_show(struct device *dev, struct device_attribute *attr,
+				   char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+	int ret, i;
+
+	ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 8);
+	if (ret)
+		return ret;
+
+	i = drvdata->mkeys_function;
+
+	if (i >= ARRAY_SIZE(claw_mkeys_function_text))
+		return sysfs_emit(buf, "unsupported\n");
+
+	return sysfs_emit(buf, "%s\n", claw_mkeys_function_text[i]);
+}
+static DEVICE_ATTR_RW(mkeys_function);
+
+static ssize_t mkeys_function_index_show(struct device *dev,
+					 struct device_attribute *attr, char *buf)
+{
+	int i, count = 0;
+
+	for (i = 0; i < ARRAY_SIZE(claw_mkeys_function_text); i++)
+		count += sysfs_emit_at(buf, count, "%s ", claw_mkeys_function_text[i]);
+
+	buf[count - 1] = '\n';
+
+	return count;
+}
+static DEVICE_ATTR_RO(mkeys_function_index);
+
+static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	bool val;
+	int ret;
+
+	ret = kstrtobool(buf, &val);
+	if (ret)
+		return ret;
+
+	if (!val)
+		return -EINVAL;
+
+	ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_RESET_DEVICE, NULL, 0, 0);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+static DEVICE_ATTR_WO(reset);
+
+static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribute *attr,
+					    int n)
+{
+	struct hid_device *hdev = to_hid_device(kobj_to_dev(kobj));
+	struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	if (!drvdata) {
+		dev_warn(&hdev->dev,
+			 "Failed to get drvdata from kobj. Gamepad attributes are not available.\n");
+		return 0;
+	}
+
+	return attr->mode;
+}
+
+static struct attribute *claw_gamepad_attrs[] = {
+	&dev_attr_gamepad_mode.attr,
+	&dev_attr_gamepad_mode_index.attr,
+	&dev_attr_mkeys_function.attr,
+	&dev_attr_mkeys_function_index.attr,
+	&dev_attr_reset.attr,
+	NULL,
+};
+
+static const struct attribute_group claw_gamepad_attr_group = {
+	.attrs = claw_gamepad_attrs,
+	.is_visible = claw_gamepad_attr_is_visible,
+};
+
+static void cfg_setup_fn(struct work_struct *work)
+{
+	struct delayed_work *dwork = container_of(work, struct delayed_work, work);
+	struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, cfg_setup);
+	int ret;
+
+	ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 8);
+	if (ret) {
+		dev_err(&drvdata->hdev->dev,
+			"Failed to setup device, can't read gamepad mode: %d\n", ret);
+		return;
+	}
+
+	/* Add sysfs attributes after we get the device state */
+	ret = device_add_group(&drvdata->hdev->dev, &claw_gamepad_attr_group);
+	if (ret) {
+		dev_err(&drvdata->hdev->dev,
+			"Failed to setup device, can't create gamepad attrs: %d\n", ret);
+		return;
+	}
+
+	kobject_uevent(&drvdata->hdev->dev.kobj, KOBJ_CHANGE);
+}
+
+static void cfg_resume_fn(struct work_struct *work)
+{
+	struct delayed_work *dwork = container_of(work, struct delayed_work, work);
+	struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, cfg_resume);
+	u8 data[2] = { drvdata->gamepad_mode, drvdata->mkeys_function };
+	int ret;
+
+	ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data,
+				    ARRAY_SIZE(data), 0);
+	if (ret)
+		dev_err(&drvdata->hdev->dev, "Failed to set gamepad mode settings: %d\n", ret);
+}
+
+static int claw_probe(struct hid_device *hdev, u8 ep)
+{
+	struct claw_drvdata *drvdata;
+	int ret;
+
+	drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata)
+		return -ENOMEM;
+
+	drvdata->gamepad_mode = CLAW_GAMEPAD_MODE_XINPUT;
+	drvdata->hdev = hdev;
+	drvdata->ep = ep;
+
+	mutex_init(&drvdata->cfg_mutex);
+	init_completion(&drvdata->send_cmd_complete);
+	INIT_DELAYED_WORK(&drvdata->cfg_resume, &cfg_resume_fn);
+	INIT_DELAYED_WORK(&drvdata->cfg_setup, &cfg_setup_fn);
+
+	/* For control interface: open the HID transport for sending commands. */
+	ret = hid_hw_open(hdev);
+	if (ret)
+		return ret;
+
+	hid_set_drvdata(hdev, drvdata);
+	schedule_delayed_work(&drvdata->cfg_setup, msecs_to_jiffies(500));
+
+	return 0;
+}
+
+static int msi_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+	u8 ep;
+
+	if (!hid_is_usb(hdev)) {
+		ret = -ENODEV;
+		goto err_probe;
+	}
+
+	ret = hid_parse(hdev);
+	if (ret)
+		goto err_probe;
+
+	/* Set quirk to create separate input devices per HID application */
+	hdev->quirks |= HID_QUIRK_INPUT_PER_APP | HID_QUIRK_MULTI_INPUT;
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret)
+		goto err_probe;
+
+	/* For non-control interfaces (keyboard/mouse), allow userspace to grab the devices. */
+	ret = get_endpoint_address(hdev);
+	if (ret < 0)
+		goto err_stop_hw;
+
+	ep = ret;
+	if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) {
+		ret = claw_probe(hdev, ep);
+		if (ret)
+			goto err_stop_hw;
+	}
+
+	return 0;
+
+err_stop_hw:
+	hid_hw_stop(hdev);
+err_probe:
+	return dev_err_probe(&hdev->dev, ret, "Failed to init device\n");
+}
+
+static void claw_remove(struct hid_device *hdev)
+{
+	struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	if (!drvdata) {
+		hid_hw_stop(hdev);
+		return;
+	}
+
+	device_remove_group(&hdev->dev, &claw_gamepad_attr_group);
+	cancel_delayed_work_sync(&drvdata->cfg_setup);
+	cancel_delayed_work_sync(&drvdata->cfg_resume);
+	hid_hw_close(hdev);
+}
+
+static void msi_remove(struct hid_device *hdev)
+{
+	int ret;
+	u8 ep;
+
+	ret = get_endpoint_address(hdev);
+	if (ret <= 0)
+		goto hw_stop;
+
+	ep = ret;
+	if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN)
+		claw_remove(hdev);
+
+hw_stop:
+	hid_hw_stop(hdev);
+}
+
+static int claw_resume(struct hid_device *hdev)
+{
+	struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	/* MCU can take up to 500ms to be ready after resume */
+	schedule_delayed_work(&drvdata->cfg_resume, msecs_to_jiffies(500));
+	return 0;
+}
+
+static int msi_resume(struct hid_device *hdev)
+{
+	int ret;
+	u8 ep;
+
+	ret = get_endpoint_address(hdev);
+	if (ret <= 0)
+		return 0;
+
+	ep = ret;
+	if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN)
+		return claw_resume(hdev);
+
+	return 0;
+}
+
+static const struct hid_device_id msi_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_XINPUT) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_DINPUT) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_DESKTOP) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_BIOS) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, msi_devices);
+
+static struct hid_driver msi_driver = {
+	.name		= "hid-msi",
+	.id_table	= msi_devices,
+	.raw_event	= msi_raw_event,
+	.probe		= msi_probe,
+	.remove		= msi_remove,
+	.resume		= msi_resume,
+};
+module_hid_driver(msi_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Denis Benato <denis.benato@linux.dev>");
+MODULE_AUTHOR("Zhouwang Huang <honjow311@gmail.com>");
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("HID driver for MSI Claw Handheld PC gamepads");
-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 0/4] Add MSI Claw HID Configuration Driver
From: Derek J. Clark @ 2026-05-15  3:36 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Pierre-Loup A . Griffais, Denis Benato, Zhouwang Huang,
	Derek J . Clark, linux-input, linux-doc, linux-kernel

This series adds an HID Configuration driver for the MSI Claw line of
Handheld Gaming PC's. The MSI Claw HID interface provides multiple
features, such as the ability to switch between xinput, dinput, and a
desktop mode, RGB control, rumble intensity, and mapping of the rear "M"
keys. There are additional gamepad modes that are not included in this
driver as they appear to be used in assembly line testing or are
incomplete in the firmware. During my testing I found them to be unstable.

The initial version of this driver was written by Denis Benato, which
contained the initial reverse-engineering and implementation for the
gamepad mode switching. This work was later expanded by Zhouwang Huang
to include more gamepad modes and additional features. Finally, I
refactored the entire driver, fixed multiple bugs, and refined the overall
format to conform to kernel driver best practices and style guide.

Claude was used initially by Zhouwang Huang to quickly parse HID captures
during the reverse-engineering of some of the features. Since Claude had
already been used, as a test of its capabilities I had it implement the
rumble intensity attribute after I had already rewritten most of the
driver, which I then manually edited to fix some mistakes. I also used
Claude to review the driver and these patches for any mistakes and bugs.

Assisted-by: Claude:claude-sonnet-4-6
Co-developed-by: Denis Benato <denis.benato@linux.dev>
Signed-off-by: Denis Benato <denis.benato@linux.dev>
Co-developed-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v3:
  - Add mutex for read/write if rgb frame data.
  - Ensure claw_hw_output_report is properly guarded.
  - Remove setting rgb_frame_count when reading rgb profiles as it always
    returns garbage data.
  - Ensure rgb_speed is getting drvdata from a valid lookup (not hdev).
  - Use scoped_guard where necessary.
  - Reoder claw_probe to ensure all mutex, completion, and variable
    assignments are in place prior to setting drvdata.
  - Ensure gamepad_mode is set to a valid enum value in claw_probe.
v2:
  - Use mutexes to guard SYNC_TO_ROM calls and pending_profile calls.
  - Rename driver to hid-msi and add generic entrypoints for
    probe/resume/remove that call claw specific functions in order to
    future proof the driver for other MSI HID interfaces.
  - Fix various bugs and formatting issues.

Derek J. Clark (4):
  HID: hid-msi: Add MSI Claw configuration driver
  HID: hid-msi: Add M-key mapping attributes
  HID: hid-msi: Add RGB control interface
  HID: hid-msi: Add Rumble Intensity Attributes

 MAINTAINERS           |    6 +
 drivers/hid/Kconfig   |   12 +
 drivers/hid/Makefile  |    1 +
 drivers/hid/hid-ids.h |    6 +
 drivers/hid/hid-msi.c | 1662 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1687 insertions(+)
 create mode 100644 drivers/hid/hid-msi.c

-- 
2.53.0


^ permalink raw reply

* Re: [PATCH] HID: Expose LattePanda IOTA UPS as a power_supply device
From: Andrew Maney @ 2026-05-15  0:24 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: linux-input, Jiri Kosina, bentiss
In-Reply-To: <20260514065043.1F11AC2BCC7@smtp.kernel.org>

On Thu, May 14, 2026 at 2:50 AM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
> - [High] Kernel panic due to invalid type cast when transport is not USB (e.g. uhid).
> - [High] Kernel panic from dereferencing an ERR_PTR in the raw event handler.
> - [High] Exposing fabricated 0 values for unsupported TIME_TO_EMPTY properties causes immediate system shutdown.
> - [Medium] Hardcoded sysfs name prevents multiple devices and causes probe failure.
> - [Medium] Data race on `ups->charge_limit`.
> - [Low] Missing `POWER_SUPPLY` dependency in Kconfig causes build failures.
> - [Low] Driver synchronously blocks the probe path for 3 seconds.
> --
>
> commit 7d140ce589ec6c637bec5ce8b0c27bf8634322c6
> Author: Andrew Maney <andrewmaney05@gmail.com>
> Subject: HID: Expose LattePanda IOTA UPS as a power_supply device
>
> This commit adds a driver for the LattePanda IOTA UPS. It exposes battery
> status, capacity, and charging status via the power_supply subsystem by parsing
> custom HID reports.
>
> > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> > --- a/drivers/hid/Kconfig
> > +++ b/drivers/hid/Kconfig
> > @@ -105,6 +105,16 @@ config HID_HAPTIC
> >
> >  menu "Special HID drivers"
> >
> > +config HID_LATTEPANDA_IOTA_UPS
> > +     tristate "LattePanda IOTA UPS"
> > +     depends on USB_HID && X86
>
> If the kernel is configured with CONFIG_POWER_SUPPLY=m and
> CONFIG_HID_LATTEPANDA_IOTA_UPS=y, will the built-in HID driver fail to link
> against the modular power supply core? Should this include a dependency on
> POWER_SUPPLY?
>
> > diff --git a/drivers/hid/hid-lattepanda-iota-ups.c b/drivers/hid/hid-lattepanda-iota-ups.c
> > --- /dev/null
> > +++ b/drivers/hid/hid-lattepanda-iota-ups.c
> [ ... ]
> > +static enum power_supply_property iota_ups_properties[] = {
> > +     POWER_SUPPLY_PROP_STATUS,
> > +     POWER_SUPPLY_PROP_CAPACITY,
> > +     POWER_SUPPLY_PROP_PRESENT,
> > +     POWER_SUPPLY_PROP_ONLINE,
> > +     POWER_SUPPLY_PROP_SCOPE,
> > +     POWER_SUPPLY_PROP_TECHNOLOGY,
> > +     POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
> > +     POWER_SUPPLY_PROP_CAPACITY_LEVEL,
> > +     POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
> > +     POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
>
> If the hardware doesn't support reporting time remaining, is it safe to
> include these properties and return 0?
>
> Returning 0 seconds remaining while discharging can signal to userspace power
> daemons like UPower or systemd-logind that the battery is empty, which might
> trigger an immediate emergency suspend or shutdown. Could these unsupported
> properties be omitted from the array entirely?
>
> [ ... ]
> > +static int iota_ups_set_property(struct power_supply *supply,
> > +                              enum power_supply_property psp,
> > +                              const union power_supply_propval *val)
> > +{
> > +     struct iota_ups *ups = power_supply_get_drvdata(supply);
> > +
> > +     if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) {
> > +             /*
> > +              * V1.0 supports 80% and 100% charge limits only, set via
> > +              * DIP switch on the board. This property allows userspace
> > +              * to inform the driver which limit is configured.
> > +              */
> > +             if (val->intval != 80 && val->intval != 100)
> > +                     return -EINVAL;
> > +             ups->charge_limit = val->intval;
>
> Does this introduce a data race on ups->charge_limit?
>
> This field is modified here without holding ups->lock, but it is concurrently
> read in iota_ups_raw_event() and iota_ups_get_property() where it is
> protected by spin_lock_irqsave(). Should this assignment also be
> protected by the spinlock?
>
> [ ... ]
> > +static int iota_ups_raw_event(struct hid_device *hdev,
> > +                           struct hid_report *report,
> > +                           u8 *data, int size)
> > +{
> > +     struct iota_ups *ups = hid_get_drvdata(hdev);
> [ ... ]
> > +     /*
> > +      * Notify the power_supply core outside the spinlock. This
> > +      * triggers UPower's PropertiesChanged signal with the new values.
> > +      */
> > +     if (changed && ups->psu)
> > +             power_supply_changed(ups->psu);
>
> Could ups->psu contain an error pointer here?
>
> In iota_ups_probe(), if devm_power_supply_register() fails, it returns an
> error pointer like ERR_PTR(-ENOMEM), which gets directly assigned to ups->psu.
>
> Because hid_hw_start() is called earlier, raw HID events can be processed
> concurrently before the hardware is stopped in the error path. Since an error
> pointer is not NULL, the check (changed && ups->psu) would pass, and
> power_supply_changed() would be called with the error pointer, leading to a
> crash.
>
> Would it be safer to assign the return value of devm_power_supply_register()
> to a local variable first, and only update ups->psu on success?
>
> [ ... ]
> > +static int iota_ups_probe(struct hid_device *hdev,
> > +                       const struct hid_device_id *id)
> > +{
> [ ... ]
> > +     /* Retrieve serial number from the USB descriptor */
> > +     udev = to_usb_device(hdev->dev.parent->parent);
> > +     if (udev->serial)
> > +             strscpy(ups->serial, udev->serial, sizeof(ups->serial));
>
> Can this cause a kernel panic if the HID device is instantiated via userspace
> mock drivers like uhid?
>
> When using uhid (which allows setting the bus type to BUS_USB), the parent
> device is the uhid character device, not a usb_interface. Dereferencing the
> resulting struct usb_device pointer would perform an out-of-bounds memory
> read.
>
> Should the driver verify the transport layer using hid_is_usb() before
> dereferencing USB-specific structures, or simply use hdev->uniq instead?
>
> [ ... ]
> > +     /*
> > +      * Wait for both status and capacity reports before registering.
> > +      * The device sends reports every ~1 second; 3 seconds is safe.
> > +      */
> > +     wait_for_completion_timeout(&ups->got_initial_data,
> > +                                 msecs_to_jiffies(3000));
>
> Does blocking the probe path for 3 seconds cause issues for device
> enumeration?
>
> Calling wait_for_completion_timeout() synchronously stalls the kernel's
> device enumeration thread for that bus, which can unnecessarily delay the
> overall system boot process or stall the handling of other devices during
> hotplug.
>
> Could the registration be deferred, or could the device be registered
> immediately with an unknown state instead of blocking?
>
> > +
> > +     ups->psu_desc.name                      = "lattepanda-iota-ups";
>
> Is it possible to have multiple of these UPS devices connected to a single
> system?
>
> If a second device is plugged in, it will attempt to register a sysfs
> directory with the exact same hardcoded name, which fails with -EEXIST.
>
> This probe failure would then trigger the error pointer panic mentioned
> earlier. Should the driver dynamically construct a unique name using
> devm_kasprintf() with dev_name(&hdev->dev)?
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260513155723.230707-1-andrewmaney05@gmail.com?part=1

Thanks for the detailed review! I'll do the following in v3:
* Remove TIME_TO_EMPTY_NOW and TIME_TO_FULL_NOW to avoid spurious
shutdowns. The UPS board doesn't currently have any way to report
these values as of right now.
* Add the POWER_SUPPLY requirement to Kconfig.
* Fix the data race for ups->charge_limit in set_property.
* Address the 3 second probe delay, it may not be needed. Registering
the UPS immediately with an unknown state may not be safe if its
reports are misinterpreted.
* Fix ERR_PTR panic with a local variable.
* Fix uhid panic with hid_is_usb() before dereferencing.
* Use devm_kasprintf() for a unique sysfs name. It is not currently
possible to have more than one UPS board connected to the IOTA, but
I'll implement this as a safeguard in case future hardware revisions
add support for multiple boards.

Thanks,
Andrew

^ permalink raw reply

* Re: [PATCH] Input: analog: replace deprecated simple_strtoul() with kstrtouint()
From: Akash Sukhavasi @ 2026-05-14 20:44 UTC (permalink / raw)
  To: david.laight.linux
  Cc: akash.sukhavasi, dmitry.torokhov, linux-input, linux-kernel
In-Reply-To: <20260509105520.61f823a5@pumpkin>

On Sat, May 9, 2026 at 4:55 AM David Laight <david.laight.linux@gmail.com> wrote:

> However that might break existing systems.
> So I'm not at all sure it should be done.

Hi David,

You're right. It could introduce regressions for users of analog
joysticks passing module parameters via scripts or boot configs.

Additionally, digging through list history: Dmitry reviewed this
same call in 2011 [1] and chose to leave it, noting the complex
parsing semantics were intentional for this context.

Withdrawing the patch. Thanks for the review.

[1] https://lore.kernel.org/all/20111108034805.GA21927@core.coreip.homeip.net/

Thanks,
Akash

^ permalink raw reply

* [dtor-input:for-linus] BUILD SUCCESS 16ca52bc209fa4bf9239cd9e5643e95533476b58
From: kernel test robot @ 2026-05-14 19:46 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: linux-input

tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git for-linus
branch HEAD: 16ca52bc209fa4bf9239cd9e5643e95533476b58  Input: synaptics - add LEN2058 to SMBus passlist for ThinkPad E490

elapsed time: 731m

configs tested: 223
configs skipped: 2

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
arc                              allmodconfig    clang-16
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    clang-23
arc                   randconfig-001-20260514    clang-23
arc                   randconfig-001-20260515    clang-23
arc                   randconfig-002-20260514    clang-23
arc                   randconfig-002-20260515    clang-23
arm                               allnoconfig    gcc-15.2.0
arm                              allyesconfig    clang-16
arm                   randconfig-001-20260514    clang-23
arm                   randconfig-001-20260515    clang-23
arm                   randconfig-002-20260514    clang-23
arm                   randconfig-002-20260515    clang-23
arm                   randconfig-003-20260514    clang-23
arm                   randconfig-003-20260515    clang-23
arm                   randconfig-004-20260514    clang-23
arm                   randconfig-004-20260515    clang-23
arm64                            allmodconfig    clang-23
arm64                             allnoconfig    gcc-15.2.0
arm64                 randconfig-001-20260514    clang-23
arm64                 randconfig-001-20260515    gcc-11.5.0
arm64                 randconfig-002-20260514    clang-23
arm64                 randconfig-002-20260515    gcc-11.5.0
arm64                 randconfig-003-20260514    clang-23
arm64                 randconfig-003-20260515    gcc-11.5.0
arm64                 randconfig-004-20260514    clang-23
arm64                 randconfig-004-20260515    gcc-11.5.0
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                  randconfig-001-20260514    clang-23
csky                  randconfig-001-20260515    gcc-11.5.0
csky                  randconfig-002-20260514    clang-23
csky                  randconfig-002-20260515    gcc-11.5.0
hexagon                          allmodconfig    gcc-15.2.0
hexagon                           allnoconfig    gcc-15.2.0
hexagon               randconfig-001-20260514    gcc-10.5.0
hexagon               randconfig-002-20260514    gcc-10.5.0
i386                             allmodconfig    clang-20
i386                             allmodconfig    gcc-14
i386                              allnoconfig    gcc-15.2.0
i386                             allyesconfig    clang-20
i386                             allyesconfig    gcc-14
i386                 buildonly-randconfig-001    gcc-14
i386        buildonly-randconfig-001-20260514    gcc-14
i386                 buildonly-randconfig-002    gcc-14
i386        buildonly-randconfig-002-20260514    gcc-14
i386                 buildonly-randconfig-003    gcc-14
i386        buildonly-randconfig-003-20260514    gcc-14
i386                 buildonly-randconfig-004    gcc-14
i386        buildonly-randconfig-004-20260514    gcc-14
i386                 buildonly-randconfig-005    gcc-14
i386        buildonly-randconfig-005-20260514    gcc-14
i386                 buildonly-randconfig-006    gcc-14
i386        buildonly-randconfig-006-20260514    gcc-14
i386                  randconfig-001-20260514    clang-20
i386                  randconfig-001-20260515    clang-20
i386                  randconfig-002-20260514    clang-20
i386                  randconfig-002-20260515    clang-20
i386                  randconfig-003-20260514    clang-20
i386                  randconfig-003-20260515    clang-20
i386                  randconfig-004-20260514    clang-20
i386                  randconfig-004-20260515    clang-20
i386                  randconfig-005-20260514    clang-20
i386                  randconfig-005-20260515    clang-20
i386                  randconfig-006-20260514    clang-20
i386                  randconfig-006-20260515    clang-20
i386                  randconfig-007-20260514    clang-20
i386                  randconfig-007-20260515    clang-20
i386                  randconfig-011-20260514    clang-20
i386                  randconfig-012-20260514    clang-20
i386                  randconfig-013-20260514    clang-20
i386                  randconfig-014-20260514    clang-20
i386                  randconfig-015-20260514    clang-20
i386                  randconfig-016-20260514    clang-20
i386                  randconfig-017-20260514    clang-20
loongarch                        allmodconfig    clang-23
loongarch                         allnoconfig    gcc-15.2.0
loongarch                           defconfig    clang-19
loongarch             randconfig-001-20260514    gcc-10.5.0
loongarch             randconfig-002-20260514    gcc-10.5.0
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    clang-16
m68k                                defconfig    clang-19
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    clang-19
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
nios2                            allmodconfig    clang-23
nios2                             allnoconfig    clang-23
nios2                               defconfig    clang-19
nios2                 randconfig-001-20260514    gcc-10.5.0
nios2                 randconfig-002-20260514    gcc-10.5.0
openrisc                         allmodconfig    clang-23
openrisc                          allnoconfig    clang-23
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    clang-23
parisc                           allyesconfig    clang-19
parisc                              defconfig    gcc-15.2.0
parisc                         randconfig-001    gcc-13.4.0
parisc                randconfig-001-20260514    gcc-13.4.0
parisc                randconfig-001-20260515    gcc-8.5.0
parisc                         randconfig-002    gcc-13.4.0
parisc                randconfig-002-20260514    gcc-13.4.0
parisc                randconfig-002-20260515    gcc-8.5.0
parisc64                            defconfig    clang-19
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    clang-23
powerpc                  mpc885_ads_defconfig    clang-23
powerpc                        randconfig-001    gcc-13.4.0
powerpc               randconfig-001-20260514    gcc-13.4.0
powerpc               randconfig-001-20260515    gcc-8.5.0
powerpc                        randconfig-002    gcc-13.4.0
powerpc               randconfig-002-20260514    gcc-13.4.0
powerpc               randconfig-002-20260515    gcc-8.5.0
powerpc                     tqm8541_defconfig    clang-23
powerpc64                      randconfig-001    gcc-13.4.0
powerpc64             randconfig-001-20260514    gcc-13.4.0
powerpc64             randconfig-001-20260515    gcc-8.5.0
powerpc64                      randconfig-002    gcc-13.4.0
powerpc64             randconfig-002-20260514    gcc-13.4.0
powerpc64             randconfig-002-20260515    gcc-8.5.0
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    clang-23
riscv                            allyesconfig    clang-16
riscv                               defconfig    gcc-15.2.0
riscv                 randconfig-001-20260514    gcc-14.3.0
riscv                 randconfig-001-20260515    gcc-15.2.0
riscv                 randconfig-002-20260514    gcc-14.3.0
riscv                 randconfig-002-20260515    gcc-15.2.0
s390                             allmodconfig    clang-19
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    clang-23
s390                                defconfig    gcc-15.2.0
s390                  randconfig-001-20260514    gcc-14.3.0
s390                  randconfig-001-20260515    gcc-15.2.0
s390                  randconfig-002-20260514    gcc-14.3.0
s390                  randconfig-002-20260515    gcc-15.2.0
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    clang-23
sh                               allyesconfig    clang-19
sh                                  defconfig    gcc-14
sh                    randconfig-001-20260514    gcc-14.3.0
sh                    randconfig-001-20260515    gcc-15.2.0
sh                    randconfig-002-20260514    gcc-14.3.0
sh                    randconfig-002-20260515    gcc-15.2.0
sh                        sh7757lcr_defconfig    gcc-15.2.0
sparc                             allnoconfig    clang-23
sparc                               defconfig    gcc-15.2.0
sparc                          randconfig-001    gcc-15.2.0
sparc                 randconfig-001-20260514    gcc-15.2.0
sparc                          randconfig-002    gcc-15.2.0
sparc                 randconfig-002-20260514    gcc-15.2.0
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    gcc-14
sparc64                        randconfig-001    gcc-15.2.0
sparc64               randconfig-001-20260514    gcc-15.2.0
sparc64                        randconfig-002    gcc-15.2.0
sparc64               randconfig-002-20260514    gcc-15.2.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-15.2.0
um                                  defconfig    gcc-14
um                             i386_defconfig    gcc-14
um                             randconfig-001    gcc-15.2.0
um                    randconfig-001-20260514    gcc-15.2.0
um                             randconfig-002    gcc-15.2.0
um                    randconfig-002-20260514    gcc-15.2.0
um                           x86_64_defconfig    gcc-14
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-23
x86_64                           allyesconfig    clang-20
x86_64               buildonly-randconfig-001    clang-20
x86_64      buildonly-randconfig-001-20260514    clang-20
x86_64               buildonly-randconfig-002    clang-20
x86_64      buildonly-randconfig-002-20260514    clang-20
x86_64               buildonly-randconfig-003    clang-20
x86_64      buildonly-randconfig-003-20260514    clang-20
x86_64               buildonly-randconfig-004    clang-20
x86_64      buildonly-randconfig-004-20260514    clang-20
x86_64               buildonly-randconfig-005    clang-20
x86_64      buildonly-randconfig-005-20260514    clang-20
x86_64               buildonly-randconfig-006    clang-20
x86_64      buildonly-randconfig-006-20260514    clang-20
x86_64                              defconfig    gcc-14
x86_64                                  kexec    clang-20
x86_64                         randconfig-001    gcc-14
x86_64                randconfig-001-20260514    gcc-14
x86_64                         randconfig-002    gcc-14
x86_64                randconfig-002-20260514    gcc-14
x86_64                         randconfig-003    gcc-14
x86_64                randconfig-003-20260514    gcc-14
x86_64                         randconfig-004    gcc-14
x86_64                randconfig-004-20260514    gcc-14
x86_64                         randconfig-005    gcc-14
x86_64                randconfig-005-20260514    gcc-14
x86_64                         randconfig-006    gcc-14
x86_64                randconfig-006-20260514    gcc-14
x86_64                randconfig-071-20260514    clang-20
x86_64                randconfig-072-20260514    clang-20
x86_64                randconfig-073-20260514    clang-20
x86_64                randconfig-074-20260514    clang-20
x86_64                randconfig-075-20260514    clang-20
x86_64                randconfig-076-20260514    clang-20
x86_64                               rhel-9.4    clang-20
x86_64                           rhel-9.4-bpf    gcc-14
x86_64                          rhel-9.4-func    clang-20
x86_64                    rhel-9.4-kselftests    clang-20
x86_64                         rhel-9.4-kunit    gcc-14
x86_64                           rhel-9.4-ltp    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    clang-23
xtensa                           allyesconfig    clang-23
xtensa                         randconfig-001    gcc-15.2.0
xtensa                randconfig-001-20260514    gcc-15.2.0
xtensa                         randconfig-002    gcc-15.2.0
xtensa                randconfig-002-20260514    gcc-15.2.0

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* [PATCH] input: remove changelogs, tracked in git
From: Elliot Tester @ 2026-05-14 19:33 UTC (permalink / raw)
  To: dmitry.torokhov; +Cc: linux-input, linux-kernel, Elliot Tester

Signed-off-by: Elliot Tester <elliotctester1@gmail.com>
---
 drivers/input/misc/yealink.c  |  9 ---------
 drivers/input/tablet/aiptek.c | 31 -------------------------------
 2 files changed, 40 deletions(-)

diff --git a/drivers/input/misc/yealink.c b/drivers/input/misc/yealink.c
index 8786ed8b3..560f895a0 100644
--- a/drivers/input/misc/yealink.c
+++ b/drivers/input/misc/yealink.c
@@ -22,15 +22,6 @@
  *   - Olivier Vandorpe, for providing the usbb2k-api.
  *   - Martin Diehl, for spotting my memory allocation bug.
  *
- * History:
- *   20050527 henk	First version, functional keyboard. Keyboard events
- *			will pop-up on the ../input/eventX bus.
- *   20050531 henk	Added led, LCD, dialtone and sysfs interface.
- *   20050610 henk	Cleanups, make it ready for public consumption.
- *   20050630 henk	Cleanups, fixes in response to comments.
- *   20050701 henk	sysfs write serialisation, fix potential unload races
- *   20050801 henk	Added ringtone, restructure USB
- *   20050816 henk	Merge 2.6.13-rc6
  */
 
 #include <linux/kernel.h>
diff --git a/drivers/input/tablet/aiptek.c b/drivers/input/tablet/aiptek.c
index 6df24cee3..ba8109322 100644
--- a/drivers/input/tablet/aiptek.c
+++ b/drivers/input/tablet/aiptek.c
@@ -16,37 +16,6 @@
  *
  *  Many thanks to Oliver Kuechemann for his support.
  *
- *  ChangeLog:
- *      v0.1 - Initial release
- *      v0.2 - Hack to get around fake event 28's. (Bryan W. Headley)
- *      v0.3 - Make URB dynamic (Bryan W. Headley, Jun-8-2002)
- *             Released to Linux 2.4.19 and 2.5.x
- *      v0.4 - Rewrote substantial portions of the code to deal with
- *             corrected control sequences, timing, dynamic configuration,
- *             support of 6000U - 12000U, procfs, and macro key support
- *             (Jan-1-2003 - Feb-5-2003, Bryan W. Headley)
- *      v1.0 - Added support for diagnostic messages, count of messages
- *             received from URB - Mar-8-2003, Bryan W. Headley
- *      v1.1 - added support for tablet resolution, changed DV and proximity
- *             some corrections - Jun-22-2003, martin schneebacher
- *           - Added support for the sysfs interface, deprecating the
- *             procfs interface for 2.5.x kernel. Also added support for
- *             Wheel command. Bryan W. Headley July-15-2003.
- *      v1.2 - Reworked jitter timer as a kernel thread.
- *             Bryan W. Headley November-28-2003/Jan-10-2004.
- *      v1.3 - Repaired issue of kernel thread going nuts on single-processor
- *             machines, introduced programmableDelay as a command line
- *             parameter. Feb 7 2004, Bryan W. Headley.
- *      v1.4 - Re-wire jitter so it does not require a thread. Courtesy of
- *             Rene van Paassen. Added reporting of physical pointer device
- *             (e.g., stylus, mouse in reports 2, 3, 4, 5. We don't know
- *             for reports 1, 6.)
- *             what physical device reports for reports 1, 6.) Also enabled
- *             MOUSE and LENS tool button modes. Renamed "rubber" to "eraser".
- *             Feb 20, 2004, Bryan W. Headley.
- *      v1.5 - Added previousJitterable, so we don't do jitter delay when the
- *             user is holding a button down for periods of time.
- *
  * NOTE:
  *      This kernel driver is augmented by the "Aiptek" XFree86 input
  *      driver for your X server, as well as the Gaiptek GUI Front-end
-- 
2.54.0


^ permalink raw reply related

* [PATCH] HID: multitouch: Honor ContactCount for Yoga Book 9 to suppress ghost contacts
From: Dave Carey @ 2026-05-14 19:32 UTC (permalink / raw)
  To: linux-input; +Cc: Jiri Kosina, Benjamin Tissoires

The INGENIC 17EF:6161 firmware on the Lenovo Yoga Book 9 14IAH10
does not clear stale contact slots when fingers are lifted.  Each
HID report contains up to 10 finger slots, but only the first
ContactCount slots represent valid contacts; the remaining slots
retain TipSwitch=1 with positions from previous touches.

Raw HID capture confirms this: across a 60-second capture with
repeated multi-finger gestures, 90% of frames had more TipSwitch=1
slots than the reported ContactCount.  The ContactCount field itself
is always accurate.

Add MT_QUIRK_CONTACT_CNT_ACCURATE to the MT_CLS_YOGABOOK9I class so
the driver stops processing slots once ContactCount valid contacts
have been consumed, discarding the stale ghost entries per HID
specification section 17.  MT_QUIRK_NOT_SEEN_MEANS_UP (already in
the class) ensures that any slot skipped by this guard is released
via INPUT_MT_DROP_UNUSED at frame sync.

Signed-off-by: Dave Carey <carvsdriver@gmail.com>
Tested-by: Dave Carey <carvsdriver@gmail.com>
---
This follows commit 108ac841 ("HID: multitouch: Fix Yoga Book 9 14IAH10
touchscreen misclassification"), applied to hid/for-next on 2026-05-12,
which established MT_CLS_YOGABOOK9I and added MT_QUIRK_NOT_SEEN_MEANS_UP.

That fix made the touchscreens fully functional.  With three or more
fingers and repeated on/off gestures, ghost contacts then began appearing
because the firmware leaves stale TipSwitch=1 data in slots beyond
ContactCount.  The capture showed patterns like cc=1 with 7 active slots,
or cc=3 with 8 active slots, consistently across all gesture cycles.
ContactCount was always the correct count.  Verified ghost-free after this
change across 6+ repeated three-finger swipe sequences.

 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 648201e..0495152 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -445,6 +445,7 @@
                { .name = MT_CLS_YOGABOOK9I,
                .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP |
                        MT_QUIRK_ALWAYS_VALID |
+                       MT_QUIRK_CONTACT_CNT_ACCURATE |
                        MT_QUIRK_FORCE_MULTI_INPUT |
                        MT_QUIRK_SEPARATE_APP_REPORT |
                        MT_QUIRK_HOVERING |
--
2.47.0

^ permalink raw reply related

* Re: [PATCH 5/6] tools/build: selftests: Allow versioning LLVM lld
From: sashiko-bot @ 2026-05-14 18:45 UTC (permalink / raw)
  To: James Clark; +Cc: dmitry.torokhov, bpf, linux-input, linux-perf-users
In-Reply-To: <20260514-james-perf-llvm-version-v1-5-6cac1a9a4c8d@linaro.org>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Using `-fuse-ld=ld.lld` breaks compilation of BPF selftests with GCC.
--

commit 18e1876d18fad631b521da817483ba47293a9020
Author: James Clark <james.clark@linaro.org>

tools/build: selftests: Allow versioning LLVM lld

This commit updates Makefile.include to add an LLD variable, matching the
rest of the LLVM toolchain. It modifies the BPF selftests Makefile to use
this new LLD variable instead of hardcoding lld.

> diff --git a/tools/scripts/Makefile.include b/tools/scripts/Makefile.include
> index 380ad84ac51e6..5c2d505cba62b 100644
> --- a/tools/scripts/Makefile.include
> +++ b/tools/scripts/Makefile.include
[ ... ]
> @@ -91,6 +92,7 @@ else
>    # Some tools still require Clang, LLC and/or LLVM utils
>    $(call allow-override,CLANG,clang)
>    $(call allow-override,LLC,llc)
> +  $(call allow-override,LLD,ld.lld)

Could setting LLD to ld.lld when not using LLVM cause issues for BPF
selftests compilation with GCC?

>    $(call allow-override,LLVM_CONFIG,llvm-config)
>    $(call allow-override,LLVM_OBJCOPY,llvm-objcopy)
>    $(call allow-override,LLVM_STRIP,llvm-strip)
> diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> index 6ef6872adbc3e..44ba829e5d4dd 100644
> --- a/tools/testing/selftests/bpf/Makefile
> +++ b/tools/testing/selftests/bpf/Makefile
> @@ -245,9 +245,9 @@ $(OUTPUT)/%:%.c
>  
>  # LLVM's ld.lld doesn't support all the architectures, so use it only on x86
>  ifeq ($(SRCARCH),$(filter $(SRCARCH),x86 riscv))
> -LLD := lld
> +USE_LD := $(LLD)
>  else
> -LLD := $(shell command -v $(LD))
> +USE_LD := $(shell command -v $(LD))
>  endif
>  
>  # Filter out -static for liburandom_read.so and its dependent targets so that static builds
> @@ -258,7 +258,7 @@ $(OUTPUT)/liburandom_read.so: urandom_read_lib1.c urandom_read_lib2.c liburandom
>  		     $(filter-out -static,$(CFLAGS) $(LDFLAGS)) \
>  		     $(filter %.c,$^) $(filter-out -static,$(LDLIBS)) \
>  		     -Wno-unused-command-line-argument \
> -		     -fuse-ld=$(LLD) -Wl,-znoseparate-code -Wl,--build-id=sha1 \
> +		     -fuse-ld=$(USE_LD) -Wl,-znoseparate-code -Wl,--build-id=sha1 \

When LLVM=1 is not set, CC defaults to gcc. The Makefile uses
$(CC) ... -fuse-ld=$(USE_LD) to link liburandom_read.so.

Because USE_LD evaluates to ld.lld, gcc receives -fuse-ld=ld.lld.

However, GCC's command-line option parser is strictly hardcoded to accept
only specific linker names (lld, bfd, gold, mold). It does not recognize
-fuse-ld=ld.lld or absolute paths, and fails with an error like:

gcc: error: unrecognized command-line option '-fuse-ld=ld.lld'; did you mean '-fuse-ld=lld'?

Does this regression break the default GCC build of BPF selftests on x86
and riscv architectures?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260514-james-perf-llvm-version-v1-0-6cac1a9a4c8d@linaro.org?part=5

^ permalink raw reply

* [PATCH v2] HID: magicmouse: add haptic click configuration for Magic Trackpad 2
From: Christian Fressl @ 2026-05-14 17:15 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: linux-input, linux-kernel, Christian Fressl
In-Reply-To: <20260513001649.E9EE5C2BCB0@smtp.kernel.org>

Apple Magic Trackpad 2 devices support persistent haptic feedback
configuration through feature reports 0x22 and 0x23. Add an opt-in
module parameter to select either the verified silent-low profile or to
disable haptic feedback.

The default remains unchanged. The report payload format is
reverse-engineered, so keep the existing payload bytes fixed and vary
only the known 24-bit feedback value.

The USB-C Trackpad exposes multiple HID interfaces. Use the one-shot
actuator output report 0x53 only to identify the interface that accepts
the persistent configuration reports; do not use it for the persistent
setting itself.

Tested on Apple Magic Trackpad USB-C 05ac:0324 with Ubuntu
6.17.0-23-generic. Compile-tested against HID for-next.

Protocol information was derived from public reverse-engineering notes,
then independently tested with local hardware.

Link: https://github.com/mwyborski/Linux-Magic-Trackpad-2-Driver/issues/28#issuecomment-451625504
Signed-off-by: Christian Fressl <christian@fressl.at>
---
Changes in v2:
- Cache haptic_click with READ_ONCE() before applying the setting.

 drivers/hid/hid-magicmouse.c | 102 ++++++++++++++++++++++++++++++++++-
 1 file changed, 101 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index e70bd3dc07ab..e941c95f0703 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -51,6 +51,10 @@ static bool report_undeciphered;
 module_param(report_undeciphered, bool, 0644);
 MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event");
 
+static unsigned int haptic_click;
+module_param(haptic_click, uint, 0644);
+MODULE_PARM_DESC(haptic_click, "Haptic click feedback: 0=unchanged, 1=silent-low, 2=off");
+
 #define TRACKPAD2_2021_BT_VERSION 0x110
 #define TRACKPAD_2024_BT_VERSION 0x314
 
@@ -62,6 +66,17 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
 #define DOUBLE_REPORT_ID   0xf7
 #define USB_BATTERY_TIMEOUT_SEC 60
 
+#define TRACKPAD2_HAPTIC_CLICK_REPORT_ID     0x22
+#define TRACKPAD2_HAPTIC_RELEASE_REPORT_ID   0x23
+#define TRACKPAD2_HAPTIC_ACTUATOR_REPORT_ID  0x53
+#define TRACKPAD2_HAPTIC_REPORT_LEN          14
+#define TRACKPAD2_HAPTIC_CLICK_UNCHANGED     0
+#define TRACKPAD2_HAPTIC_CLICK_SILENT_LOW    1
+#define TRACKPAD2_HAPTIC_CLICK_OFF           2
+#define TRACKPAD2_HAPTIC_SILENT_CLICK        0x000015
+#define TRACKPAD2_HAPTIC_SILENT_RELEASE      0x000010
+#define TRACKPAD2_HAPTIC_OFF                 0x000000
+
 /* These definitions are not precise, but they're close enough.  (Bits
  * 0x03 seem to indicate the aspect ratio of the touch, bits 0x70 seem
  * to be some kind of bit mask -- 0x20 may be a near-field reading,
@@ -812,6 +827,82 @@ static bool is_usb_magictrackpad2(__u32 vendor, __u32 product)
 	       product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC;
 }
 
+static bool magicmouse_is_haptic_interface(struct hid_device *hdev)
+{
+	struct hid_report_enum *report_enum;
+
+	report_enum = &hdev->report_enum[HID_OUTPUT_REPORT];
+
+	/*
+	 * The persistent haptic configuration reports are accepted as feature
+	 * reports, but are not advertised in the feature report descriptor.
+	 * Report 0x53 is the one-shot actuator output report and identifies
+	 * the HID interface that accepts the persistent reports.
+	 */
+	return report_enum->report_id_hash[TRACKPAD2_HAPTIC_ACTUATOR_REPORT_ID];
+}
+
+static int magicmouse_send_haptic_report(struct hid_device *hdev, u8 report_id,
+					 u32 feedback)
+{
+	static const u8 report_template[TRACKPAD2_HAPTIC_REPORT_LEN] = {
+		0x00, 0x01, 0x00, 0x78, 0x02, 0x00, 0x24,
+		0x30, 0x06, 0x01, 0x00, 0x18, 0x48, 0x13,
+	};
+	u8 *buf;
+	int ret;
+
+	buf = kmemdup(report_template, sizeof(report_template), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	buf[0] = report_id;
+	buf[2] = feedback & 0xff;
+	buf[5] = (feedback >> 8) & 0xff;
+	buf[10] = (feedback >> 16) & 0xff;
+
+	ret = hid_hw_raw_request(hdev, buf[0], buf, TRACKPAD2_HAPTIC_REPORT_LEN,
+				 HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+	kfree(buf);
+
+	return ret;
+}
+
+static int magicmouse_apply_haptic_click(struct hid_device *hdev)
+{
+	unsigned int click = READ_ONCE(haptic_click);
+	u32 click_feedback;
+	u32 release_feedback;
+	int ret;
+
+	if (click == TRACKPAD2_HAPTIC_CLICK_UNCHANGED)
+		return 0;
+
+	switch (click) {
+	case TRACKPAD2_HAPTIC_CLICK_SILENT_LOW:
+		click_feedback = TRACKPAD2_HAPTIC_SILENT_CLICK;
+		release_feedback = TRACKPAD2_HAPTIC_SILENT_RELEASE;
+		break;
+	case TRACKPAD2_HAPTIC_CLICK_OFF:
+		click_feedback = TRACKPAD2_HAPTIC_OFF;
+		release_feedback = TRACKPAD2_HAPTIC_OFF;
+		break;
+	default:
+		hid_warn(hdev, "invalid haptic_click value %u\n", click);
+		return -EINVAL;
+	}
+
+	ret = magicmouse_send_haptic_report(hdev,
+					    TRACKPAD2_HAPTIC_CLICK_REPORT_ID,
+					    click_feedback);
+	if (ret < 0)
+		return ret;
+
+	return magicmouse_send_haptic_report(hdev,
+					     TRACKPAD2_HAPTIC_RELEASE_REPORT_ID,
+					     release_feedback);
+}
+
 static int magicmouse_fetch_battery(struct hid_device *hdev)
 {
 #ifdef CONFIG_HID_BATTERY_STRENGTH
@@ -894,8 +985,17 @@ static int magicmouse_probe(struct hid_device *hdev,
 
 	if (is_usb_magicmouse2(id->vendor, id->product) ||
 	    (is_usb_magictrackpad2(id->vendor, id->product) &&
-	     hdev->type != HID_TYPE_USBMOUSE))
+	     hdev->type != HID_TYPE_USBMOUSE)) {
+		if (is_usb_magictrackpad2(id->vendor, id->product) &&
+		    magicmouse_is_haptic_interface(hdev)) {
+			ret = magicmouse_apply_haptic_click(hdev);
+			if (ret < 0)
+				hid_warn(hdev,
+					 "unable to apply haptic click setting (%d)\n",
+					 ret);
+		}
 		return 0;
+	}
 
 	if (!msc->input) {
 		hid_err(hdev, "magicmouse input not registered\n");
-- 
2.43.0

^ permalink raw reply related

* Re: [PATCH v2 4/4] HID: hid-msi: Add Rumble Intensity Attributes
From: sashiko-bot @ 2026-05-14 16:29 UTC (permalink / raw)
  To: Derek J. Clark; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260513231445.3213501-5-derekjohn.clark@gmail.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Concurrent sysfs read and write operations interleave commands and corrupt hardware state due to a block-scoped guard() bug on cfg_mutex.
- [High] State machine corruption due to failure to defensively clear profile_pending on unexpected generic ACKs.
--

commit f1baff465a5e284998680961eb3326992b856326
Author: Derek J. Clark <derekjohn.clark@gmail.com>

HID: hid-msi: Add Rumble Intensity Attributes

This commit adds intensity adjustment for the left and right rumble motors.
The code was co-developed with AI assistance for data gathering and initial
framing of the sysfs show and store functions.

> diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
> index a628b77bfb7b5..cffd6ed253ec9 100644
> --- a/drivers/hid/hid-msi.c
> +++ b/drivers/hid/hid-msi.c

[ ... ]

> @@ -815,6 +831,126 @@ static ssize_t button_mapping_options_show(struct device *dev,
>  }
>  static DEVICE_ATTR_RO(button_mapping_options);
>  
> +static ssize_t rumble_intensity_left_store(struct device *dev,
> +					   struct device_attribute *attr,
> +					   const char *buf, size_t count)
> +{

[ ... ]

> +	guard(mutex)(&drvdata->rom_mutex);
> +	ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA,
> +				    data, ARRAY_SIZE(data), 8);

[ ... ]

> +static ssize_t rumble_intensity_left_show(struct device *dev,
> +					  struct device_attribute *attr,
> +					  char *buf)
> +{

[ ... ]

> +	guard(mutex)(&drvdata->profile_mutex);
> +	drvdata->profile_pending = CLAW_RUMBLE_LEFT_PENDING;
> +	ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, data,
> +				    ARRAY_SIZE(data), 8);
> +	if (ret) {
> +		drvdata->profile_pending = CLAW_NO_PENDING;
> +		return ret;
> +	}
> +
> +	return sysfs_emit(buf, "%u\n", drvdata->rumble_intensity_left);
> +}

Since rumble_intensity_left_store uses rom_mutex and
rumble_intensity_left_show uses profile_mutex, these functions can be
called concurrently.

Does claw_hw_output_report serialize these hardware requests?

Looking at claw_hw_output_report, it attempts to protect the transaction
with a block-scoped guard:

static int claw_hw_output_report(...)
{
        ...
        if (timeout) {
                guard(mutex)(&drvdata->cfg_mutex);
                reinit_completion(&drvdata->send_cmd_complete);
        }
        
        // drvdata->cfg_mutex is released here
        
        ...
        ret = hid_hw_output_report(hdev, dmabuf, CLAW_PACKET_SIZE);
        ...
        if (timeout) {
                ret = wait_for_completion_interruptible_timeout(
                                        &drvdata->send_cmd_complete, ...);
        ...
}

Because the guard is scoped only to the if statement, the lock is released
before the USB transmission and wait occur.

Will concurrent sysfs operations issue uncoordinated USB commands and wait
on the exact same send_cmd_complete object, resulting in desynchronized
responses?

If a concurrent store operation triggers a generic ACK that satisfies the
wait in the show thread, ret will be 0, but claw_profile_event will not
have executed.

In this success path, does drvdata->profile_pending remain stuck at
CLAW_RUMBLE_LEFT_PENDING instead of being defensively cleared?

If an unrelated READ_PROFILE_ACK arrives later, could claw_profile_event
incorrectly demultiplex it based on the stuck state, copying arbitrary
payload bytes into drvdata->rumble_intensity_left?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260513231445.3213501-1-derekjohn.clark@gmail.com?part=4

^ permalink raw reply

* Re: [PATCH v6 2/7] mfd: Add driver for ASUS Transformer embedded controller
From: Svyatoslav Ryhel @ 2026-05-14 16:06 UTC (permalink / raw)
  To: Lee Jones
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
	Pavel Machek, Sebastian Reichel, Ion Agorria,
	Michał Mirosław, devicetree, linux-kernel, linux-input,
	linux-leds, linux-pm
In-Reply-To: <20260514155004.GO305027@google.com>

чт, 14 трав. 2026 р. о 18:50 Lee Jones <lee@kernel.org> пише:
>
> On Thu, 14 May 2026, Svyatoslav Ryhel wrote:
>
> > чт, 14 трав. 2026 р. о 13:02 Lee Jones <lee@kernel.org> пише:
> > >
> > > On Sat, 02 May 2026, Svyatoslav Ryhel wrote:
> > >
> > > > From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> > > >
> > > > Support Nuvoton NPCE795-based ECs as used in Asus Transformer TF201,
> > > > TF300T, TF300TG, TF300TL and TF700T pad and dock, as well as TF101 dock
> > > > and TF600T, P1801-T and TF701T pad. This is a glue driver handling
> > > > detection and common operations for EC's functions.
> > > >
> > > > Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > > > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > > > Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> > > > ---
> > > >  drivers/mfd/Kconfig                     |  14 +
> > > >  drivers/mfd/Makefile                    |   1 +
> > > >  drivers/mfd/asus-transformer-ec.c       | 762 ++++++++++++++++++++++++
> > > >  include/linux/mfd/asus-transformer-ec.h | 162 +++++
> > > >  4 files changed, 939 insertions(+)
> > > >  create mode 100644 drivers/mfd/asus-transformer-ec.c
> > > >  create mode 100644 include/linux/mfd/asus-transformer-ec.h
>
> [...]
>
> > > > +     unsigned int num_devices;
> > > > +     bool clr_fmode; /* clear Factory Mode bit in EC control register */
> > > > +};
> > > > +
> > > > +struct asus_ec_data {
> > > > +     struct asusec_info info;
> > >
> > > You have 'data' and 'info' which a) using non-forthcoming nomenclature
> > > and doesn't tell me anything and then you b) put 'info' in the device's
> > > driver_data attribute which is very confusing.  driver_data should be
> > > for what we call ddata which I assume is expressed as 'data' here.
> > >
> >
> > asusec_info is shared among all child devices and is exposed while
> > remaining elements of this struct are for internal use only.
>
> Our terminology for that is usually ddata, that gets stored in
> 'struct devices' device_data attribute.
>
> > > > +     struct mutex ecreq_lock; /* prevent simultaneous access */
> > > > +     struct gpio_desc *ecreq;
> > >
> > > If I hadn't seen the declaration, I'd have no idea this was a GPIO
> > > descriptor.  Please improve the nomenclature throughout.
> > >
> > > > +     struct i2c_client *self;
> > >
> > > Again, please use standard naming conventions:
> > >
> > > % git grep "struct i2c_client" | grep "\*self" | wc -l
> > > 0
> > >
> > > % git grep "struct i2c_client" | grep "\*client" | wc -l
> > > 6304
> > >
> > > % git grep "struct i2c_client" | grep "\*i2c" | wc -l
> > > 903
> > >
> >
> > ok, noted.
> >
> > > > +     const struct asus_ec_chip_data *data;
> > >
> > > 'data', 'priv' and 'info' should be improved.
> > >
> > > > +     char ec_data[DOCKRAM_ENTRY_BUFSIZE];
> > >
> > > An array of chars called 'data'.  This could be anything.
> > >
> >
> > Do you have a comprehensive list of name conventions you find suitable?
>
> Anything descriptive that alludes to the type of data being held there.
>
> There are 100's of good examples, but a handful of generic / bad ones.
>
> > > > +     bool logging_disabled;
> > >
> > > This debugging tool is probably never going to be used again.
> > >
> > > Keep it local.
> > >
> > > > +};
> > > > +
> > > > +struct dockram_ec_data {
> > > > +     struct mutex ctl_lock; /* prevent simultaneous access */
> > > > +     char ctl_data[DOCKRAM_ENTRY_BUFSIZE];
> > > > +};
> > > > +
> > > > +#define to_ec_data(ec) \
> > > > +     container_of(ec, struct asus_ec_data, info)
> > > > +
> > > > +/**
> > > > + * asus_dockram_read - Read a register from the DockRAM device.
> > > > + * @client: Handle to the DockRAM device.
> > > > + * @reg: Register to read.
> > > > + * @buf: Byte array into which data will be read; must be large enough to
> > > > + *    hold the data returned by the DockRAM.
> > > > + *
> > > > + * This executes the DockRAM read based on the SMBus "block read" protocol
> > > > + * or its emulation. It extracts DOCKRAM_ENTRY_SIZE bytes from the set
> > > > + * register address.
> > > > + *
> > > > + * Returns a negative errno code else zero on success.
> > > > + */
> > > > +int asus_dockram_read(struct i2c_client *client, int reg, char *buf)
> > > > +{
> > >
> > > Have you considered using Regmap for register access instead of
> > > implementing custom functions?  Remaps already deals with caching and
> > > locking mechanisms that you'd get for free.
> > >
> > > This looks like it would be replaced with devm_regmap_init_i2c().
> > >
> >
> > I will consider this, thank you.
> >

It seems that regmap does not fit for this purpose, but I might switch
to plain i2c_smbus_read_i2c_block_data

> > > > +     struct device *dev = &client->dev;
> > > > +     int ret;
> > > > +
> > > > +     memset(buf, 0, DOCKRAM_ENTRY_BUFSIZE);
> > > > +     ret = i2c_smbus_read_i2c_block_data(client, reg,
> > > > +                                         DOCKRAM_ENTRY_BUFSIZE, buf);
> > > > +     if (ret < 0)
> > > > +             return ret;
> > > > +
> > > > +     if (buf[0] > DOCKRAM_ENTRY_SIZE) {
> > > > +             dev_err(dev, "bad data len; buffer: %*ph; ret: %d\n",
> > > > +                     DOCKRAM_ENTRY_BUFSIZE, buf, ret);
> > > > +             return -EPROTO;
> > > > +     }
> > > > +
> > > > +     dev_dbg(dev, "got data; buffer: %*ph; ret: %d\n",
> > > > +             DOCKRAM_ENTRY_BUFSIZE, buf, ret);
> > >
> > > Please remove all of these debug messages.
> > >
> >
> > Why debug messages cannot be preserved? They are specifically marked as dev_dbg
>
> It's a general convention.
>
> After initial development, they tend to just litter the code-base.
>
> Debug prints can be useful higher up the stack though.
>

I am fine with removing all debugs and logging but I strongly would
like to keep EC model and firmware version along with susb and factory
status. That may be quite useful in identifying EC used and its
behavior without need in rebuilding the kernel and digging huge piles
of downstream code in order to find how to dump these values.

> [...]
>
> > > > +/**
> > > > + * devm_asus_ec_register_notifier - Managed registration of notifier to an
> > > > + *                               ASUS EC blocking notifier chain.
> > > > + * @pdev: Device requesting the notifier (used for resource management).
> > > > + * @nb: Notifier block to be registered.
> > > > + *
> > > > + * Register a notifier to the ASUS EC blocking notifier chain. The notifier
> > > > + * will be automatically unregistered when the requesting device is detached.
> > > > + *
> > > > + * Return: 0 on success or a negative error code on failure.
> > > > + */
> > > > +int devm_asus_ec_register_notifier(struct platform_device *pdev,
> > > > +                                struct notifier_block *nb)
> > > > +{
> > >
> > > Hand-rolling devres managed resources is usually reserved for subsystem
> > > level API calls.  Why do the usual device driver .remove() handling work
> > > for you?
> > >
> >
> > This is used by 3 subdevices: serio, keys and charger, so this just
> > seems cleaner way to register and deregister notifier.
>
> Clean to me would be to use the infrastructure that's put in place
> already.  Unless I am missing the point of all of this.
>
> [...]
>
> > > > +     int ret;
> > > > +
> > > > +     if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
> > > > +             return dev_err_probe(dev, -ENXIO,
> > > > +                     "I2C bus is missing required SMBus block mode support\n");
> > > > +
> > > > +     priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> > > > +     if (!priv)
> > > > +             return -ENOMEM;
> > > > +
> > > > +     priv->data = device_get_match_data(dev);
> > > > +     if (!priv->data)
> > > > +             return -ENODEV;
> > > > +
> > > > +     i2c_set_clientdata(client, priv);
> > > > +     priv->self = client;
> > > > +
> > > > +     priv->info.dockram = devm_asus_dockram_get(dev);
> > > > +     if (IS_ERR(priv->info.dockram))
> > > > +             return dev_err_probe(dev, PTR_ERR(priv->info.dockram),
> > > > +                                  "failed to get dockram\n");
> > > > +
> > > > +     priv->ecreq = devm_gpiod_get(dev, "request", GPIOD_OUT_LOW);
> > > > +     if (IS_ERR(priv->ecreq))
> > > > +             return dev_err_probe(dev, PTR_ERR(priv->ecreq),
> > > > +                                  "failed to get request GPIO\n");
> > >
> > > "get" or "request"
> > >
> >
> > request is gpio's name, request gpio
>
> Ah yes.  Maybe use 's to help with that.  Right now is just reads strangely.
>
> [...]
>
> --
> Lee Jones

^ permalink raw reply

* Re: [PATCH v6 2/7] mfd: Add driver for ASUS Transformer embedded controller
From: Lee Jones @ 2026-05-14 15:50 UTC (permalink / raw)
  To: Svyatoslav Ryhel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
	Pavel Machek, Sebastian Reichel, Ion Agorria,
	Michał Mirosław, devicetree, linux-kernel, linux-input,
	linux-leds, linux-pm
In-Reply-To: <CAPVz0n07EKiF=Gi=Po0zFVSuU=g4pbhJam7VHgiQsPTwtT2wQg@mail.gmail.com>

On Thu, 14 May 2026, Svyatoslav Ryhel wrote:

> чт, 14 трав. 2026 р. о 13:02 Lee Jones <lee@kernel.org> пише:
> >
> > On Sat, 02 May 2026, Svyatoslav Ryhel wrote:
> >
> > > From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> > >
> > > Support Nuvoton NPCE795-based ECs as used in Asus Transformer TF201,
> > > TF300T, TF300TG, TF300TL and TF700T pad and dock, as well as TF101 dock
> > > and TF600T, P1801-T and TF701T pad. This is a glue driver handling
> > > detection and common operations for EC's functions.
> > >
> > > Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > > Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> > > ---
> > >  drivers/mfd/Kconfig                     |  14 +
> > >  drivers/mfd/Makefile                    |   1 +
> > >  drivers/mfd/asus-transformer-ec.c       | 762 ++++++++++++++++++++++++
> > >  include/linux/mfd/asus-transformer-ec.h | 162 +++++
> > >  4 files changed, 939 insertions(+)
> > >  create mode 100644 drivers/mfd/asus-transformer-ec.c
> > >  create mode 100644 include/linux/mfd/asus-transformer-ec.h

[...]

> > > +     unsigned int num_devices;
> > > +     bool clr_fmode; /* clear Factory Mode bit in EC control register */
> > > +};
> > > +
> > > +struct asus_ec_data {
> > > +     struct asusec_info info;
> >
> > You have 'data' and 'info' which a) using non-forthcoming nomenclature
> > and doesn't tell me anything and then you b) put 'info' in the device's
> > driver_data attribute which is very confusing.  driver_data should be
> > for what we call ddata which I assume is expressed as 'data' here.
> >
> 
> asusec_info is shared among all child devices and is exposed while
> remaining elements of this struct are for internal use only.

Our terminology for that is usually ddata, that gets stored in
'struct devices' device_data attribute.

> > > +     struct mutex ecreq_lock; /* prevent simultaneous access */
> > > +     struct gpio_desc *ecreq;
> >
> > If I hadn't seen the declaration, I'd have no idea this was a GPIO
> > descriptor.  Please improve the nomenclature throughout.
> >
> > > +     struct i2c_client *self;
> >
> > Again, please use standard naming conventions:
> >
> > % git grep "struct i2c_client" | grep "\*self" | wc -l
> > 0
> >
> > % git grep "struct i2c_client" | grep "\*client" | wc -l
> > 6304
> >
> > % git grep "struct i2c_client" | grep "\*i2c" | wc -l
> > 903
> >
> 
> ok, noted.
> 
> > > +     const struct asus_ec_chip_data *data;
> >
> > 'data', 'priv' and 'info' should be improved.
> >
> > > +     char ec_data[DOCKRAM_ENTRY_BUFSIZE];
> >
> > An array of chars called 'data'.  This could be anything.
> >
> 
> Do you have a comprehensive list of name conventions you find suitable?

Anything descriptive that alludes to the type of data being held there.

There are 100's of good examples, but a handful of generic / bad ones.

> > > +     bool logging_disabled;
> >
> > This debugging tool is probably never going to be used again.
> >
> > Keep it local.
> >
> > > +};
> > > +
> > > +struct dockram_ec_data {
> > > +     struct mutex ctl_lock; /* prevent simultaneous access */
> > > +     char ctl_data[DOCKRAM_ENTRY_BUFSIZE];
> > > +};
> > > +
> > > +#define to_ec_data(ec) \
> > > +     container_of(ec, struct asus_ec_data, info)
> > > +
> > > +/**
> > > + * asus_dockram_read - Read a register from the DockRAM device.
> > > + * @client: Handle to the DockRAM device.
> > > + * @reg: Register to read.
> > > + * @buf: Byte array into which data will be read; must be large enough to
> > > + *    hold the data returned by the DockRAM.
> > > + *
> > > + * This executes the DockRAM read based on the SMBus "block read" protocol
> > > + * or its emulation. It extracts DOCKRAM_ENTRY_SIZE bytes from the set
> > > + * register address.
> > > + *
> > > + * Returns a negative errno code else zero on success.
> > > + */
> > > +int asus_dockram_read(struct i2c_client *client, int reg, char *buf)
> > > +{
> >
> > Have you considered using Regmap for register access instead of
> > implementing custom functions?  Remaps already deals with caching and
> > locking mechanisms that you'd get for free.
> >
> > This looks like it would be replaced with devm_regmap_init_i2c().
> >
> 
> I will consider this, thank you.
> 
> > > +     struct device *dev = &client->dev;
> > > +     int ret;
> > > +
> > > +     memset(buf, 0, DOCKRAM_ENTRY_BUFSIZE);
> > > +     ret = i2c_smbus_read_i2c_block_data(client, reg,
> > > +                                         DOCKRAM_ENTRY_BUFSIZE, buf);
> > > +     if (ret < 0)
> > > +             return ret;
> > > +
> > > +     if (buf[0] > DOCKRAM_ENTRY_SIZE) {
> > > +             dev_err(dev, "bad data len; buffer: %*ph; ret: %d\n",
> > > +                     DOCKRAM_ENTRY_BUFSIZE, buf, ret);
> > > +             return -EPROTO;
> > > +     }
> > > +
> > > +     dev_dbg(dev, "got data; buffer: %*ph; ret: %d\n",
> > > +             DOCKRAM_ENTRY_BUFSIZE, buf, ret);
> >
> > Please remove all of these debug messages.
> >
> 
> Why debug messages cannot be preserved? They are specifically marked as dev_dbg

It's a general convention.

After initial development, they tend to just litter the code-base.

Debug prints can be useful higher up the stack though.

[...] 

> > > +/**
> > > + * devm_asus_ec_register_notifier - Managed registration of notifier to an
> > > + *                               ASUS EC blocking notifier chain.
> > > + * @pdev: Device requesting the notifier (used for resource management).
> > > + * @nb: Notifier block to be registered.
> > > + *
> > > + * Register a notifier to the ASUS EC blocking notifier chain. The notifier
> > > + * will be automatically unregistered when the requesting device is detached.
> > > + *
> > > + * Return: 0 on success or a negative error code on failure.
> > > + */
> > > +int devm_asus_ec_register_notifier(struct platform_device *pdev,
> > > +                                struct notifier_block *nb)
> > > +{
> >
> > Hand-rolling devres managed resources is usually reserved for subsystem
> > level API calls.  Why do the usual device driver .remove() handling work
> > for you?
> >
> 
> This is used by 3 subdevices: serio, keys and charger, so this just
> seems cleaner way to register and deregister notifier.

Clean to me would be to use the infrastructure that's put in place
already.  Unless I am missing the point of all of this.

[...]

> > > +     int ret;
> > > +
> > > +     if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
> > > +             return dev_err_probe(dev, -ENXIO,
> > > +                     "I2C bus is missing required SMBus block mode support\n");
> > > +
> > > +     priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> > > +     if (!priv)
> > > +             return -ENOMEM;
> > > +
> > > +     priv->data = device_get_match_data(dev);
> > > +     if (!priv->data)
> > > +             return -ENODEV;
> > > +
> > > +     i2c_set_clientdata(client, priv);
> > > +     priv->self = client;
> > > +
> > > +     priv->info.dockram = devm_asus_dockram_get(dev);
> > > +     if (IS_ERR(priv->info.dockram))
> > > +             return dev_err_probe(dev, PTR_ERR(priv->info.dockram),
> > > +                                  "failed to get dockram\n");
> > > +
> > > +     priv->ecreq = devm_gpiod_get(dev, "request", GPIOD_OUT_LOW);
> > > +     if (IS_ERR(priv->ecreq))
> > > +             return dev_err_probe(dev, PTR_ERR(priv->ecreq),
> > > +                                  "failed to get request GPIO\n");
> >
> > "get" or "request"
> >
> 
> request is gpio's name, request gpio

Ah yes.  Maybe use 's to help with that.  Right now is just reads strangely.

[...]

-- 
Lee Jones

^ permalink raw reply

* Re: [PATCH v5 1/2] dt-bindings: input: Document Imagis ISA1200 haptic motor driver
From: Svyatoslav Ryhel @ 2026-05-14 15:00 UTC (permalink / raw)
  To: Rob Herring
  Cc: Dmitry Torokhov, Krzysztof Kozlowski, Conor Dooley, Linus Walleij,
	linux-input, devicetree, linux-kernel
In-Reply-To: <20260514145541.GB472306-robh@kernel.org>

чт, 14 трав. 2026 р. о 17:55 Rob Herring <robh@kernel.org> пише:
>
> On Tue, May 12, 2026 at 01:24:44PM +0300, Svyatoslav Ryhel wrote:
> > Document the Imagis ISA1200 haptic motor driver, used primarily in mobile
> > handheld devices and capable of supporting up to two motors.
> >
> > The exact datasheet for the ISA1200 is not available; all data was modeled
> > based on available downstream kernel sources for various devices and
> > fragments of information scattered across the internet.
> >
> > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > ---
> >  .../bindings/input/imagis,isa1200.yaml        | 140 ++++++++++++++++++
> >  1 file changed, 140 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/input/imagis,isa1200.yaml
> >
> > diff --git a/Documentation/devicetree/bindings/input/imagis,isa1200.yaml b/Documentation/devicetree/bindings/input/imagis,isa1200.yaml
> > new file mode 100644
> > index 000000000000..bbe6f99d39c1
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/input/imagis,isa1200.yaml
> > @@ -0,0 +1,140 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/input/imagis,isa1200.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Imagis ISA1200 haptic motor driver
> > +
> > +maintainers:
> > +  - Svyatoslav Ryhel <clamor95@gmail.com>
> > +  - Linus Walleij <linusw@kernel.org>
> > +
> > +description:
> > +  The ISA1200 is a high-performance enhanced haptic motor driver designed
> > +  for mobile hand-held devices. It supports various voltages for both ERM
> > +  (Eccentric Rotating Mass) and LRA (Linear Resonant Actuator) type
> > +  actuators. Thanks to an embedded LDO, battery power can be used directly
> > +  in handheld applications.
> > +
> > +properties:
> > +  compatible:
> > +    const: imagis,isa1200
> > +
> > +  reg:
> > +    maxItems: 1
> > +
> > +  control-gpios:
> > +    description:
> > +      One or two GPIOs flagged as active high linked to HEN and LEN pins
>
>        minItems: 1
>

In theory there may be no GPIOs and both pins can be hooked to power.
This is unlikely scenario but since it is possible I did not set
minItems and did not make control-gpios a required property.

> With that,
>
> Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
>
> > +    maxItems: 2

^ permalink raw reply

* Re: [PATCH v5 1/2] dt-bindings: input: Document Imagis ISA1200 haptic motor driver
From: Rob Herring @ 2026-05-14 14:55 UTC (permalink / raw)
  To: Svyatoslav Ryhel
  Cc: Dmitry Torokhov, Krzysztof Kozlowski, Conor Dooley, Linus Walleij,
	linux-input, devicetree, linux-kernel
In-Reply-To: <20260512102445.55372-2-clamor95@gmail.com>

On Tue, May 12, 2026 at 01:24:44PM +0300, Svyatoslav Ryhel wrote:
> Document the Imagis ISA1200 haptic motor driver, used primarily in mobile
> handheld devices and capable of supporting up to two motors.
> 
> The exact datasheet for the ISA1200 is not available; all data was modeled
> based on available downstream kernel sources for various devices and
> fragments of information scattered across the internet.
> 
> Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> ---
>  .../bindings/input/imagis,isa1200.yaml        | 140 ++++++++++++++++++
>  1 file changed, 140 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/input/imagis,isa1200.yaml
> 
> diff --git a/Documentation/devicetree/bindings/input/imagis,isa1200.yaml b/Documentation/devicetree/bindings/input/imagis,isa1200.yaml
> new file mode 100644
> index 000000000000..bbe6f99d39c1
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/imagis,isa1200.yaml
> @@ -0,0 +1,140 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/input/imagis,isa1200.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Imagis ISA1200 haptic motor driver
> +
> +maintainers:
> +  - Svyatoslav Ryhel <clamor95@gmail.com>
> +  - Linus Walleij <linusw@kernel.org>
> +
> +description:
> +  The ISA1200 is a high-performance enhanced haptic motor driver designed
> +  for mobile hand-held devices. It supports various voltages for both ERM
> +  (Eccentric Rotating Mass) and LRA (Linear Resonant Actuator) type
> +  actuators. Thanks to an embedded LDO, battery power can be used directly
> +  in handheld applications.
> +
> +properties:
> +  compatible:
> +    const: imagis,isa1200
> +
> +  reg:
> +    maxItems: 1
> +
> +  control-gpios:
> +    description:
> +      One or two GPIOs flagged as active high linked to HEN and LEN pins

       minItems: 1

With that,

Reviewed-by: Rob Herring (Arm) <robh@kernel.org>

> +    maxItems: 2

^ permalink raw reply

* Re: [PATCH v5 1/2] dt-bindings: input: Document Imagis ISA1200 haptic motor driver
From: Rob Herring @ 2026-05-14 14:42 UTC (permalink / raw)
  To: sashiko-reviews
  Cc: Svyatoslav Ryhel, linux-input, conor+dt, dmitry.torokhov, krzk+dt,
	devicetree
In-Reply-To: <20260513201558.808C4C19425@smtp.kernel.org>

On Wed, May 13, 2026 at 08:15:57PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] Missing scalar type definitions for `imagis,period-ns` and `imagis,duty-cycle-ns` cause schema bounds validation to be silently bypassed.
> --
> 
> commit c2a7ef2ca3d62317c69bc5b6a450107172eeeac7
> Author: Svyatoslav Ryhel <clamor95@gmail.com>
> 
> dt-bindings: input: Document Imagis ISA1200 haptic motor driver
> 
> This commit documents the Imagis ISA1200 haptic motor driver. It introduces
> a new YAML binding schema for the driver, specifying properties like
> control-gpios, clocks, and internal PWM configurations.
> 
> > diff --git a/Documentation/devicetree/bindings/input/imagis,isa1200.yaml b/Documentation/devicetree/bindings/input/imagis,isa1200.yaml
> > new file mode 100644
> > index 0000000000000..bbe6f99d39c19
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/input/imagis,isa1200.yaml
> [ ... ]
> > @@ -0,0 +1,140 @@
> [ ... ]
> > +  imagis,mode:
> > +    $ref: /schemas/types.yaml#/definitions/uint32
> > +    description: |
> > +      Defines the motor type isa1200 drives
> > +      0 - LRA (Linear Resonant Actuator)
> > +      1 - ERM (Eccentric Rotating Mass)
> > +    enum: [0, 1]
> > +    default: 0
> > +
> > +  imagis,period-ns:
> > +    description:
> > +      Period of the internal PWM channel in nanoseconds.
> > +    minimum: 10000
> > +    maximum: 30000
> > +
> > +  imagis,duty-cycle-ns:
> > +    description:
> > +      Duty cycle of the external/internal PWM channel in nanoseconds,
> > +      defaults to 50% of the channel's period
> 
> Will the minimum and maximum bounds on imagis,period-ns be silently ignored
> during validation?
> 
> The core property-units.yaml schema automatically assigns an array type to
> properties ending in -ns. Without an explicit reference to
> /schemas/types.yaml#/definitions/uint32, these properties are evaluated as
> arrays rather than scalar integers. 
> 
> In standard JSON Schema, numeric constraints like minimum and maximum are
> ignored when applied to an array instance. Adding the uint32 type reference
> for both imagis,period-ns and imagis,duty-cycle-ns would ensure the
> constraints are properly enforced on a single scalar value.

Wrong.

While that's true for json-schema, the standard unit properties 
schemas will be fixed up to the right type.

Rob

^ permalink raw reply

* Re: [PATCH v2 3/4] HID: hid-msi: Add RGB control interface
From: Derek John Clark @ 2026-05-14 13:54 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260514125337.950E8C2BCB3@smtp.kernel.org>

On Thu, May 14, 2026 at 5:53 AM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
> - [Critical] The `speed_show` function incorrectly casts the LED class device to a HID device, resulting in an invalid pointer dereference and a guaranteed kernel panic.
> - [Critical] Missing bounds check on `frame->frame_count` from the USB device leads to an out-of-bounds read of kernel memory.
> - [High] Re-arming `rgb_queue` from sysfs attributes after it has been explicitly cancelled in `claw_remove` leads to a Use-After-Free.
> - [High] The `guard(mutex)` macro in `claw_hw_output_report()` is incorrectly used inside a block scope, providing absolutely no synchronization.
> - [High] Concurrent modification of shared RGB state without lock protection causes data races and memory corruption.
> - [High] Every RGB state change triggers a `SYNC_TO_ROM` command, causing severe flash memory degradation.
> - [Medium] An early return in `cfg_setup_fn` silently drops core gamepad sysfs attributes if RGB initialization fails.
> --
>
> commit a90ff4a0f03b6df7827bfbcd222f977138b9d280
> Author: Derek J. Clark <derekjohn.clark@gmail.com>
>
> HID: hid-msi: Add RGB control interface
>
> Adds RGB control interface for MSI Claw devices. The MSI Claw uses a
> fairly unique RGB interface. It has 9 total zones (4 per joystick ring
> and 1 for the ABXY buttons), and supports up to 8 sequential frames of
> RGB zone data.
>
> > --- a/drivers/hid/hid-msi.c
> > +++ b/drivers/hid/hid-msi.c
>
> [ ... ]
>
> > @@ -308,15 +368,52 @@ static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_
> >               for (i = 0; i < CLAW_KEYS_MAX; i++)
> >                       codes[i] = (cmd_rep->data[6 + i] != 0xff) ? cmd_rep->data[6 + i] : 0x00;
> >               break;
> > +     case CLAW_RGB_PENDING:
> > +             frame = (struct rgb_report *)cmd_rep->data;
> > +             rgb_addr = drvdata->rgb_addr;
> > +             read_addr = be16_to_cpu(frame->read_addr);
> > +             frame_calc = (read_addr - rgb_addr) / CLAW_RGB_FRAME_OFFSET;
> > +             if (frame_calc > U8_MAX) {
> > +                     dev_err(drvdata->led_mc.led_cdev.dev, "Got unsupported frame index: %x\n",
> > +                             frame_calc);
> > +                     ret = -EINVAL;
> > +                     goto err_pending;
> > +             }
> > +             f_idx = frame_calc;
> > +
> > +             if (f_idx >= CLAW_RGB_MAX_FRAMES) {
> > +                     dev_err(drvdata->led_mc.led_cdev.dev, "Got illegal frame index: %x\n",
> > +                             f_idx);
> > +                     ret = -EINVAL;
> > +                     goto err_pending;
> > +             }
> > +
> > +             /* Always treat the first frame as the truth for these constants */
> > +             if (f_idx == 0) {
> > +                     drvdata->rgb_frame_count = frame->frame_count;
>
> drvdata->rgb_frame_count is assigned directly from the hardware packet.
> Should there be a bounds check here to ensure it is less than or equal to
> CLAW_RGB_MAX_FRAMES?
>
> Later, claw_write_rgb_state() uses drvdata->rgb_frame_count as the upper
> bound for a loop reading from drvdata->rgb_frames. If a device sends a
> frame count greater than 8, could this read out-of-bounds kernel memory
> and send it back to the USB device?

I'll add a bounds check, though again it is unlikely with the previous checks.

> > +                     /* Invert device speed (20-0) to sysfs speed (0-20) */
> > +                     drvdata->rgb_speed = frame->speed;
> > +                     drvdata->led_mc.led_cdev.brightness = frame->brightness;
> > +                     drvdata->led_mc.subled_info[0].intensity = frame->zone_data.zone[0].red;
> > +                     drvdata->led_mc.subled_info[1].intensity = frame->zone_data.zone[0].green;
> > +                     drvdata->led_mc.subled_info[2].intensity = frame->zone_data.zone[0].blue;
> > +             }
> > +
> > +             memcpy(&drvdata->rgb_frames[f_idx], &frame->zone_data,
> > +                    sizeof(struct rgb_frame));
>
> claw_profile_event() modifies drvdata->rgb_frames and
> drvdata->rgb_frame_count from the HID raw event handler context.
> Simultaneously, claw_rgb_queue_fn() runs in a workqueue and calls
> functions that overwrite these exact same fields.
>
> Since there are no locks protecting these updates, could a background
> worker read torn state or corrupt the frame arrays while they are being
> actively updated by the asynchronous device response?

I'll add another mutex I suppose.

> [ ... ]
>
> > @@ -759,6 +856,397 @@ static const struct attribute_group claw_gamepad_attr_group = {
> >       .is_visible = claw_gamepad_attr_is_visible,
> >  };
> >
> > +/* Read RGB config from device */
> > +static int claw_read_rgb_config(struct hid_device *hdev)
> > +{
>
> [ ... ]
>
> > +/* Send RGB configuration to device */
> > +static int claw_write_rgb_state(struct claw_drvdata *drvdata)
> > +{
> > +     struct rgb_report report = { 0x01, 0x0000, CLAW_RGB_FRAME_OFFSET, 0x00,
> > +                     drvdata->rgb_frame_count, 0x09, drvdata->rgb_speed,
> > +                     drvdata->led_mc.led_cdev.brightness };
> > +     u16 write_addr = drvdata->rgb_addr;
> > +     size_t len = sizeof(report);
> > +     int f, ret;
> > +
> > +     if (!drvdata->rgb_addr)
> > +             return -ENODEV;
> > +
> > +     if (!drvdata->rgb_frame_count)
> > +             return -EINVAL;
> > +
> > +     guard(mutex)(&drvdata->rom_mutex);
> > +     /* Loop through (up to) 8 pages of RGB data */
> > +     for (f = 0; f < drvdata->rgb_frame_count; f++) {
> > +             report.zone_data = drvdata->rgb_frames[f];
> > +
> > +             /* Set the MCU address to write the frame data to */
> > +             report.read_addr = cpu_to_be16(write_addr);
> > +
> > +             /* Serialize the rgb_report and write it to MCU */
> > +             ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA,
> > +                                         (u8 *)&report, len, 8);
> > +             if (ret)
> > +                     return ret;
> > +
> > +             /* Increment the write addr by the offset for the next frame */
> > +             write_addr += CLAW_RGB_FRAME_OFFSET;
> > +     }
> > +
> > +     ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 8);
>
> Does sending CLAW_COMMAND_TYPE_SYNC_TO_ROM on every RGB state change
> cause excessive flash memory degradation?
>
> Because claw_apply_rgb_state() is triggered by unthrottled sysfs writes
> (such as rapidly changing a brightness slider), this might cause hundreds
> of NVRAM writes per second and potentially degrade the MCU's flash
> endurance.

This is throttled by mod_delayed_work, where each subsequent call
under 50ms resets the timer.

> [ ... ]
>
> > +static ssize_t effect_store(struct device *dev,
> > +                         struct device_attribute *attr,
> > +                         const char *buf, size_t count)
> > +{
> > +     struct led_classdev *led_cdev = dev_get_drvdata(dev);
> > +     struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
> > +     struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
> > +     int ret;
> > +
> > +     ret = sysfs_match_string(claw_rgb_effect_text, buf);
> > +     if (ret < 0)
> > +             return ret;
> > +
> > +     drvdata->rgb_effect = ret;
> > +     mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50));
> > +
> > +     return count;
> > +}
>
> Is there a potential use-after-free here if sysfs attributes are written
> after the device is removed?
>
> In claw_remove(), the driver attempts to tear down the worker using
> cancel_delayed_work_sync(&drvdata->rgb_queue). However, the sysfs files
> and LED endpoints remain active because devm cleanup executes after
> claw_remove() completes.
>
> A concurrent write to a sysfs attribute like effect_store could call
> mod_delayed_work() and re-arm the work item, causing it to execute after
> drvdata has been freed.
> [ ... ]
>
> > +static ssize_t speed_show(struct device *dev, struct device_attribute *attr,
> > +                       char *buf)
> > +{
> > +     struct hid_device *hdev = to_hid_device(dev);
> > +     struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
>
> Is the dev parameter passed to speed_show actually a hid_device?
>
I forgot to fix this.

> The rgb_attr_group sysfs group is attached to the LED class device
> (led_cdev.dev), not the parent HID device. The macro to_hid_device(dev)
> blindly performs a container_of cast, which will yield an invalid pointer
> for hdev.
>
> Dereferencing it with hid_get_drvdata(hdev) will likely cause a kernel
> panic when this world-readable attribute is accessed.
>
> [ ... ]
>
> > @@ -772,6 +1260,13 @@ static void cfg_setup_fn(struct work_struct *work)
> >               return;
> >       }
> >
> > +     ret = claw_read_rgb_config(drvdata->hdev);
> > +     if (ret) {
> > +             dev_err(drvdata->led_mc.led_cdev.dev,
> > +                     "Failed to setup device, can't read RGB config: %d\n", ret);
> > +             return;
> > +     }
> > +
> >       /* Add sysfs attributes after we get the device state */
> >       ret = devm_device_add_group(&drvdata->hdev->dev, &claw_gamepad_attr_group);
>
> If claw_read_rgb_config() fails due to a temporary USB error or
> unsupported firmware, this early return skips the subsequent
> devm_device_add_group call.
>
> Does this unintentionally break the device's core gamepad configuration
> interface simply because the RGB initialization failed?

The expectation is that if any of these error there is a problem with
the kernel or the device hardware. This is intentional.

> This isn't a new bug introduced by this patch, but does the guard() in
> claw_hw_output_report() provide any synchronization?
>
> claw_hw_output_report() {
>     if (timeout) {
>         guard(mutex)(&drvdata->cfg_mutex);
>         reinit_completion(&drvdata->send_cmd_complete);
>     }
>     ...
> }
>
> Because the guard is declared inside the block, the mutex is immediately
> released at the closing brace before hid_hw_output_report() and
> wait_for_completion_interruptible_timeout() are ever called. This seems
> to leave the I/O operations unprotected against concurrent threads, which
> this patch introduces via the new RGB workers.
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260513231445.3213501-1-derekjohn.clark@gmail.com?part=3

^ permalink raw reply

* Re: [PATCH v2 2/4] HID: hid-msi: Add M-key mapping attributes
From: Derek John Clark @ 2026-05-14 13:48 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260514122200.E52C4C2BCB3@smtp.kernel.org>

On Thu, May 14, 2026 at 5:22 AM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
> - [High] The `cfg_mutex` guard in `claw_hw_output_report()` is scoped to an `if` block, releasing the lock before the critical section executes.
> - [High] Lockless concurrent access to `profile_pending` between process and interrupt contexts causes data corruption and sequence mismatches on delayed responses.
> - [High] Unsafe assumption of USB transport in `claw_probe()` causes type confusion and invalid pointer dereference for non-USB devices.
> - [High] Returning an error from `claw_profile_event()` for a spurious event unconditionally triggers `complete()`, causing premature wakeups of unrelated commands.
> - [Medium] The function `claw_buttons_store()` illegally mixes scope-based `guard()` cleanup with `goto`-based error handling.
> --
>
> commit 42be71c046dc9b6b31d28598f5e0bb568d78b7a1
> Author: Derek J. Clark <derekjohn.clark@gmail.com>
>
> HID: hid-msi: Add M-key mapping attributes
>
> Adds attributes that allow for remapping the M-keys with up to 5 values
> when in macro mode.
>
> > diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
> > index 8915942af15e6..13ba2747fdb66 100644
> > --- a/drivers/hid/hid-msi.c
> > +++ b/drivers/hid/hid-msi.c
>
> [ ... ]
>
> > @@ -125,6 +294,31 @@ static int claw_gamepad_mode_event(struct claw_drvdata *drvdata,
> >       return 0;
> >  }
> >
> > +static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_report *cmd_rep)
> > +{
> > +     u8 *codes;
> > +     int i;
> > +
> > +     switch (drvdata->profile_pending) {
> > +     case CLAW_M1_PENDING:
> > +     case CLAW_M2_PENDING:
> > +             codes = (drvdata->profile_pending == CLAW_M1_PENDING) ?
> > +                     drvdata->m1_codes : drvdata->m2_codes;
> > +             /* Extract key codes; replace disabled (0xff) with 0x00, which is (null) in _show */
> > +             for (i = 0; i < CLAW_KEYS_MAX; i++)
> > +                     codes[i] = (cmd_rep->data[6 + i] != 0xff) ? cmd_rep->data[6 + i] : 0x00;
> > +             break;
>
> Is it safe to read and modify profile_pending locklessly here in the
> interrupt handler?
>
> Since claw_buttons_show() modifies this state in process context under
> profile_mutex, if a sysfs request times out and a subsequent request starts,
> could the delayed ACK from the first request cause this handler to see the
> new profile_pending state and corrupt the new request's buffer?

The lock covers this case as it is only released after the completion
fires or time out, after this.

> > +     default:
> > +             dev_warn(&drvdata->hdev->dev,
> > +                      "Got profile event without changes pending from command: %x\n",
> > +                      cmd_rep->cmd);
> > +             return -EINVAL;
> > +     }
> > +     drvdata->profile_pending = CLAW_NO_PENDING;
> > +
> > +     return 0;
> > +}
> > +
> >  static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *report,
> >                         u8 *data, int size)
> >  {
>
> [ ... ]
>
> > @@ -146,6 +340,9 @@ static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *repor
> >       case CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK:
> >               ret = claw_gamepad_mode_event(drvdata, cmd_rep);
> >               break;
> > +     case CLAW_COMMAND_TYPE_READ_PROFILE_ACK:
> > +             ret = claw_profile_event(drvdata, cmd_rep);
> > +             break;
> >       case CLAW_COMMAND_TYPE_ACK:
> >               break;
> >       default:
>
> When claw_profile_event() returns -EINVAL for a spurious event,
> claw_raw_event() breaks from the switch and unconditionally calls
> complete(&drvdata->send_cmd_complete).
That case is unlikely as the profile events are serialized by a mutex.
Holding the completion until timeout would only result in delaying
subsequent calls.

> Could this prematurely wake up a thread waiting on a completely different
> command, such as WRITE_PROFILE_DATA?
>
> [ ... ]
>
> > @@ -366,6 +563,161 @@ static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
> >  }
> >  static DEVICE_ATTR_WO(reset);
> >
> > +static int button_mapping_name_to_code(const char *name)
> > +{
>
> [ ... ]
>
> > +static int claw_buttons_store(struct device *dev, const char *buf, u8 mkey_idx)
> > +{
> > +     struct hid_device *hdev = to_hid_device(dev);
> > +     struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
> > +     u8 data[] = { 0x01, (drvdata->bmap_addr[mkey_idx] >> 8) & 0xff,
> > +                   drvdata->bmap_addr[mkey_idx] & 0xff, 0x07,
> > +                   0x04, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff };
> > +     size_t len = ARRAY_SIZE(data);
> > +     int ret, key_count, i;
> > +     char **raw_keys;
> > +
> > +     raw_keys = argv_split(GFP_KERNEL, buf, &key_count);
> > +     if (!raw_keys)
> > +             return -ENOMEM;
> > +
> > +     guard(mutex)(&drvdata->rom_mutex); /* all err_free paths must be in scope */
> > +     if (key_count > CLAW_KEYS_MAX) {
> > +             ret = -EINVAL;
> > +             goto err_free;
> > +     }
> > +
> > +     if (key_count == 0)
> > +             goto set_buttons;
> > +
> > +     for (i = 0; i < key_count; i++) {
> > +             ret = button_mapping_name_to_code(raw_keys[i]);
> > +             if (ret < 0)
> > +                     goto err_free;
> > +
> > +             data[6 + i] = ret;
> > +     }
> > +
> > +set_buttons:
> > +     ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, data, len, 8);
>
> While reviewing the addition of these sysfs attributes, I noticed a
> pre-existing issue in claw_hw_output_report() that is exacerbated by
> the concurrent sysfs requests added here:
>
> drivers/hid/hid-msi.c:claw_hw_output_report() {
>     ...
>     if (timeout) {
>         guard(mutex)(&drvdata->cfg_mutex);
>         reinit_completion(&drvdata->send_cmd_complete);
>     }
>     ...
> }
>
> Since guard() is scoped to the if block, the mutex is immediately released at
> the closing brace.
>
> Doesn't this mean hid_hw_output_report() and
> wait_for_completion_interruptible_timeout() execute entirely without
> the lock, defeating the intended serialization and allowing concurrent
> requests to race?
>
> > +     if (ret < 0)
> > +             goto err_free;
> > +
> > +     ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 8);
> > +
> > +err_free:
> > +     argv_free(raw_keys);
> > +     return ret;
> > +}
>
> Is it safe to mix goto-based error handling with scope-based guard() cleanup
> in the same function?

I'll make this a scoped guard over claw_hw_output_report.

> According to the cleanup guidelines, functions should either convert
> entirely to scope-based cleanup, for example by using a DEFINE_FREE
> wrapper for argv_free, or not use guard() at all to avoid confusing
> ownership semantics.
>
> [ ... ]
>
> > @@ -432,8 +796,31 @@ static void cfg_resume_fn(struct work_struct *work)
> >               dev_err(&drvdata->hdev->dev, "Failed to set gamepad mode settings: %d\n", ret);
> >  }
> >
> > +static void claw_features_supported(struct claw_drvdata *drvdata)
> > +{
>
> [ ... ]
>
> >  static int claw_probe(struct hid_device *hdev, u8 ep)
> >  {
> > +     struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
> > +     struct usb_device *udev = interface_to_usbdev(intf);
>
> Is it guaranteed that this device is always connected via USB?

This function is only called by msi_probe, which checks for a valid
USB device as a first step.

> If this driver is bound to a device connected over a non-USB transport like
> I2C, Bluetooth, or UHID, hdev->dev.parent would not be a valid USB
> interface, which could cause an invalid pointer dereference here.
>
> Could this code check hid_is_usb(hdev) first or read the version from
> hdev->version instead?
>
> >       struct claw_drvdata *drvdata;
> >       int ret;
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260513231445.3213501-1-derekjohn.clark@gmail.com?part=2

^ permalink raw reply

* Re: [PATCH v2 1/4] HID: hid-msi: Add MSI Claw configuration driver
From: Derek John Clark @ 2026-05-14 13:43 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260514115126.BCAD8C2BCB3@smtp.kernel.org>

On Thu, May 14, 2026 at 4:51 AM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
> - [High] The `guard(mutex)` macro in `claw_hw_output_report` is scoped to an `if` block, prematurely releasing the lock before the critical section.
> - [High] The driver publishes `drvdata` via `hid_set_drvdata()` before fully initializing its mutex and completion objects, creating a race condition.
> - [Medium] An invalid, uninitialized gamepad mode (0) may be sent to the hardware during system resume.
> - [Medium] The 8ms timeout used in `claw_hw_output_report()` is vulnerable to jiffies rounding, leading to spurious read failures.
> - [Medium] The commit message claims probe checks MCU RAM address variances, but the implementation is missing.
> --
>
> commit 13b388de96d7484eee908adeed127e8086fc289e
> Author: Derek J. Clark <derekjohn.clark@gmail.com>
>
> HID: hid-msi: Add MSI Claw configuration driver
>
> This commit adds a configuration HID driver for the MSI Claw series of
> handheld PCs. It provides support for switching the gamepad mode and changing
> the behavior of the M-keys.
>
> >     Additionally, the MCU RAM address for writing configuration data has
> >     changed twice. Checks are done during probe to enumerate these variances.
>
> The commit message states that checks are done during probe to enumerate MCU
> RAM address variances.
>
> Looking at claw_probe() and msi_probe(), there does not appear to be any code
> that checks firmware versions or handles these variances.
>
> Was this functionality dropped during refactoring, or should the commit message
> be updated?
>
> > diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
> > new file mode 100644
> > index 0000000000000..8915942af15e6
> > --- /dev/null
> > +++ b/drivers/hid/hid-msi.c
>
> [ ... ]
>
> > +static int claw_hw_output_report(struct hid_device *hdev, u8 index, u8 *data,
> > +                              size_t len, unsigned int timeout)
> > +{
> > +     unsigned char *dmabuf __free(kfree) = NULL;
> > +     u8 header[] = { CLAW_OUTPUT_REPORT_ID, 0, 0, 0x3c, index };
> > +     struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
> > +     size_t header_size = ARRAY_SIZE(header);
> > +     int ret;
> > +
> > +     if (header_size + len > CLAW_PACKET_SIZE)
> > +             return -EINVAL;
> > +
> > +     /* We can't use a devm_alloc reusable buffer without side effects during suspend */
> > +     dmabuf = kzalloc(CLAW_PACKET_SIZE, GFP_KERNEL);
> > +     if (!dmabuf)
> > +             return -ENOMEM;
> > +
> > +     memcpy(dmabuf, header, header_size);
> > +     if (data && len)
> > +             memcpy(dmabuf + header_size, data, len);
> > +
> > +     /* Don't hold a mutex when timeout=0, those commands cause USB disconnect */
> > +     if (timeout) {
> > +             guard(mutex)(&drvdata->cfg_mutex);
> > +             reinit_completion(&drvdata->send_cmd_complete);
> > +     }
>
> Does this drop the lock too early?
>
Hi robot,

Will fix.

> The guard() macro releases the lock at the end of its enclosing scope.
> Because it is declared inside the if (timeout) block, the mutex will be
> released immediately at the closing brace, leaving hid_hw_output_report() and
> the subsequent wait unprotected against concurrent access.
>
> > +
> > +     dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n",
> > +             CLAW_PACKET_SIZE, dmabuf);
> > +
> > +     ret = hid_hw_output_report(hdev, dmabuf, CLAW_PACKET_SIZE);
> > +     if (ret < 0)
> > +             return ret;
> > +
> > +     ret = ret == CLAW_PACKET_SIZE ? 0 : -EIO;
> > +     if (ret)
> > +             return ret;
> > +
> > +     if (timeout) {
> > +             ret = wait_for_completion_interruptible_timeout(&drvdata->send_cmd_complete,
> > +                                                             msecs_to_jiffies(timeout));
>
> Could this timeout evaluate to 1 jiffy on systems configured with HZ=100?
This seems like an unlikely config for a performance gaming system.
> Several callers, such as gamepad_mode_show(), pass an 8ms timeout to this
> function. A 1-jiffy timeout can expire almost instantaneously if the operation
> begins near the end of the current tick boundary, potentially leading to
> spurious -EBUSY timeouts.
>
> [ ... ]
>
> > +static void cfg_resume_fn(struct work_struct *work)
> > +{
> > +     struct delayed_work *dwork = container_of(work, struct delayed_work, work);
> > +     struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, cfg_resume);
> > +     u8 data[2] = { drvdata->gamepad_mode, drvdata->mkeys_function };
>
> If the system suspends before cfg_setup_fn() executes, or if the device read in
> cfg_setup_fn() fails, could drvdata->gamepad_mode still be 0 here?
>
> Since 0 is not a valid mode (valid modes like CLAW_GAMEPAD_MODE_XINPUT start
> at 1), will this send an uninitialized configuration state to the hardware?

I'll add a valid default value.

> > +     int ret;
> > +
> > +     ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data,
> > +                                 ARRAY_SIZE(data), 0);
> > +     if (ret)
> > +             dev_err(&drvdata->hdev->dev, "Failed to set gamepad mode settings: %d\n", ret);
> > +}
> > +
> > +static int claw_probe(struct hid_device *hdev, u8 ep)
> > +{
> > +     struct claw_drvdata *drvdata;
> > +     int ret;
> > +
> > +     drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
> > +     if (!drvdata)
> > +             return -ENOMEM;
> > +
> > +     hid_set_drvdata(hdev, drvdata);
> > +     drvdata->hdev = hdev;
> > +     drvdata->ep = ep;
> > +
> > +     mutex_init(&drvdata->cfg_mutex);
> > +     init_completion(&drvdata->send_cmd_complete);
>
> Is it safe to publish drvdata via hid_set_drvdata() before initializing the
> mutex and completion object?
>
> Since hid_hw_start() was already called earlier in msi_probe(), the hardware
> is active. If an input report arrives right after hid_set_drvdata(), the
> interrupt handler msi_raw_event() will process it and call complete() on an
> uninitialized waitqueue.
>
I'll reorder this.
> > +
> > +     /* For control interface: open the HID transport for sending commands. */
> > +     ret = hid_hw_open(hdev);
> > +     if (ret)
> > +             return ret;
> > +
> > +     INIT_DELAYED_WORK(&drvdata->cfg_resume, &cfg_resume_fn);
> > +     INIT_DELAYED_WORK(&drvdata->cfg_setup, &cfg_setup_fn);
> > +     schedule_delayed_work(&drvdata->cfg_setup, msecs_to_jiffies(500));
> > +
> > +     return 0;
> > +}
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260513231445.3213501-1-derekjohn.clark@gmail.com?part=1

^ permalink raw reply

* Re: [PATCH] input/evdev: move kill_fasync() outside buffer_lock to fix SOFTIRQ deadlock
From: Rik van Riel @ 2026-05-14 13:06 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, kernel-team
In-Reply-To: <agTXYr_fFGiJ7lAe@google.com>

On Wed, 13 May 2026 13:01:58 -0700
Dmitry Torokhov <dmitry.torokhov@gmail.com> wrote:
> On Wed, May 13, 2026 at 11:50:00AM -0400, Rik van Riel wrote:
> > buffer_lock is a SOFTIRQ-safe spinlock. kill_fasync() acquires fa_lock
> > (SOFTIRQ-unsafe), creating a potential SOFTIRQ-safe->SOFTIRQ-unsafe lock
> > ordering violation that lockdep flags as a deadlock.
> > 
> > Fix by moving the kill_fasync() call to evdev_pass_values() after
> > buffer_lock is released, alongside the existing wake_up_interruptible_poll().
> > 
> > The wakeup condition check is the same in __pass_event() and
> > evdev_pass_values()  
> 
> Does this really fix anything? This code is running holding
> input->event_lock with IRQs off...

You're right. The bug is real, but this patch does not fix it.
Would the Sashiko suggestion work, or is there a better way to
tackle it?

For reference, the lockdep splat is below:

=====================================================
WARNING: SOFTIRQ-safe -> SOFTIRQ-unsafe lock order detected
7.0.0-rc6-00259-g427a4f9708ee #82 Not tainted
-----------------------------------------------------
syz.6.15929/30382 [HC0[0]:SC0[0]:HE0:SE1] is trying to acquire:
ffff888101324558 (&new->fa_lock){...-}-{3:3}, at: kill_fasync_rcu fs/fcntl.c:1135 [inline]
ffff888101324558 (&new->fa_lock){...-}-{3:3}, at: kill_fasync fs/fcntl.c:1159 [inline]
ffff888101324558 (&new->fa_lock){...-}-{3:3}, at: kill_fasync+0x137/0x590 fs/fcntl.c:1152

and this task is already holding:
ffff88812f3d8028 (&client->buffer_lock){....}-{3:3}, at: spin_lock include/linux/spinlock.h:341 [inline]
ffff88812f3d8028 (&client->buffer_lock){....}-{3:3}, at: evdev_pass_values.part.0+0xf6/0x950 drivers/input/evdev.c:261
which would create a new lock dependency:
 (&client->buffer_lock){....}-{3:3} -> (&new->fa_lock){...-}-{3:3}

but this new dependency connects a SOFTIRQ-irq-safe lock:
 (&dev->event_lock){..-.}-{3:3}

... which became SOFTIRQ-irq-safe at:
  lock_acquire kernel/locking/lockdep.c:5868 [inline]
  lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
  __raw_spin_lock_irqsave include/linux/spinlock_api_smp.h:132 [inline]
  _raw_spin_lock_irqsave+0x3a/0x60 kernel/locking/spinlock.c:162
  class_spinlock_irqsave_constructor include/linux/spinlock.h:618 [inline]
  input_inject_event+0x9f/0x420 drivers/input/input.c:419
  __led_set_brightness drivers/leds/led-core.c:52 [inline]
  led_set_brightness_nopm drivers/leds/led-core.c:335 [inline]
  led_set_brightness_nosleep drivers/leds/led-core.c:369 [inline]
  led_set_brightness+0x217/0x290 drivers/leds/led-core.c:328
  led_trigger_event drivers/leds/led-triggers.c:420 [inline]
  led_trigger_event+0xda/0x2b0 drivers/leds/led-triggers.c:408
  kbd_propagate_led_state drivers/tty/vt/keyboard.c:1065 [inline]
  kbd_bh+0x263/0x350 drivers/tty/vt/keyboard.c:1244
  tasklet_action_common+0x240/0x3c0 kernel/softirq.c:925
  handle_softirqs+0x1b0/0x8d0 kernel/softirq.c:622
  __do_softirq kernel/softirq.c:656 [inline]
  invoke_softirq kernel/softirq.c:496 [inline]
  __irq_exit_rcu+0xc4/0x100 kernel/softirq.c:723
  irq_exit_rcu+0x9/0x20 kernel/softirq.c:739
  instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1056 [inline]
  sysvec_apic_timer_interrupt+0x70/0x80 arch/x86/kernel/apic/apic.c:1056
  asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:729
  __preempt_count_dec_and_test arch/x86/include/asm/preempt.h:95 [inline]
  __raw_spin_unlock_irqrestore include/linux/spinlock_api_smp.h:179 [inline]
  _raw_spin_unlock_irqrestore+0x34/0x50 kernel/locking/spinlock.c:194
  spin_unlock_irqrestore include/linux/spinlock.h:407 [inline]
  class_spinlock_irqsave_destructor include/linux/spinlock.h:618 [inline]
  input_inject_event+0x1bd/0x420 drivers/input/input.c:419
  evdev_write+0x30a/0x460 drivers/input/evdev.c:528
  vfs_write+0x2b1/0x11a0 fs/read_write.c:686
  ksys_write+0x1ef/0x240 fs/read_write.c:740
  do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
  do_syscall_64+0x111/0x680 arch/x86/entry/syscall_64.c:94
  entry_SYSCALL_64_after_hwframe+0x77/0x7f

to a SOFTIRQ-irq-unsafe lock:
 (tasklist_lock){.+.+}-{3:3}

... which became SOFTIRQ-irq-unsafe at:
...
  lock_acquire kernel/locking/lockdep.c:5868 [inline]
  lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
  __raw_read_lock include/linux/rwlock_api_smp.h:161 [inline]
  _raw_read_lock+0x5c/0x70 kernel/locking/spinlock.c:228
  __do_wait+0x105/0x880 kernel/exit.c:1678
  do_wait+0x1cb/0x5a0 kernel/exit.c:1722
  kernel_wait+0x9f/0x160 kernel/exit.c:1898
  call_usermodehelper_exec_sync kernel/umh.c:136 [inline]
  call_usermodehelper_exec_work+0xf9/0x180 kernel/umh.c:163
  process_one_work+0x920/0x1ac0 kernel/workqueue.c:3276
  process_scheduled_works kernel/workqueue.c:3359 [inline]
  worker_thread+0x693/0xeb0 kernel/workqueue.c:3440
  kthread+0x385/0x490 kernel/kthread.c:436
  ret_from_fork+0x67a/0xab0 arch/x86/kernel/process.c:158
  ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245

other info that might help us debug this:

Chain exists of:
  &dev->event_lock --> &client->buffer_lock --> tasklist_lock

 Possible interrupt unsafe locking scenario:

       CPU0                    CPU1
       ----                    ----
  lock(tasklist_lock);
                               local_irq_disable();
                               lock(&dev->event_lock);
                               lock(&client->buffer_lock);
  <Interrupt>
    lock(&dev->event_lock);

 *** DEADLOCK ***

7 locks held by syz.6.15929/30382:
 #0: ffff88810452a118 (&evdev->mutex){+.+.}-{4:4}, at: evdev_write+0x161/0x460 drivers/input/evdev.c:511
 #1: ffff8881038c5230 (&dev->event_lock){..-.}-{3:3}, at: class_spinlock_irqsave_constructor include/linux/spinlock.h:618 [inline]
 #1: ffff8881038c5230 (&dev->event_lock){..-.}-{3:3}, at: input_inject_event+0x9f/0x420 drivers/input/input.c:419
 #2: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:312 [inline]
 #2: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:850 [inline]
 #2: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: class_rcu_constructor include/linux/rcupdate.h:1193 [inline]
 #2: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: input_inject_event+0xbb/0x420 drivers/input/input.c:420
 #3: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:312 [inline]
 #3: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:850 [inline]
 #3: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: class_rcu_constructor include/linux/rcupdate.h:1193 [inline]
 #3: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: input_pass_values+0x80/0x8b0 drivers/input/input.c:119
 #4: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:312 [inline]
 #4: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:850 [inline]
 #4: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: evdev_events+0x80/0x4e0 drivers/input/evdev.c:298
 #5: ffff88812f3d8028 (&client->buffer_lock){....}-{3:3}, at: spin_lock include/linux/spinlock.h:341 [inline]
 #5: ffff88812f3d8028 (&client->buffer_lock){....}-{3:3}, at: evdev_pass_values.part.0+0xf6/0x950 drivers/input/evdev.c:261
 #6: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:312 [inline]
 #6: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:850 [inline]
 #6: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: kill_fasync fs/fcntl.c:1158 [inline]
 #6: ffffffffbbcac800 (rcu_read_lock){....}-{1:3}, at: kill_fasync+0x61/0x590 fs/fcntl.c:1152

the dependencies between SOFTIRQ-irq-safe lock and the holding lock:
 -> (&dev->event_lock){..-.}-{3:3} {
    IN-SOFTIRQ-W at:
                      lock_acquire kernel/locking/lockdep.c:5868 [inline]
                      lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
                      __raw_spin_lock_irqsave include/linux/spinlock_api_smp.h:132 [inline]
                      _raw_spin_lock_irqsave+0x3a/0x60 kernel/locking/spinlock.c:162
                      class_spinlock_irqsave_constructor include/linux/spinlock.h:618 [inline]
                      input_inject_event+0x9f/0x420 drivers/input/input.c:419
                      __led_set_brightness drivers/leds/led-core.c:52 [inline]
                      led_set_brightness_nopm drivers/leds/led-core.c:335 [inline]
                      led_set_brightness_nosleep drivers/leds/led-core.c:369 [inline]
                      led_set_brightness+0x217/0x290 drivers/leds/led-core.c:328
                      led_trigger_event drivers/leds/led-triggers.c:420 [inline]
                      led_trigger_event+0xda/0x2b0 drivers/leds/led-triggers.c:408
                      kbd_propagate_led_state drivers/tty/vt/keyboard.c:1065 [inline]
                      kbd_bh+0x263/0x350 drivers/tty/vt/keyboard.c:1244
                      tasklet_action_common+0x240/0x3c0 kernel/softirq.c:925
                      handle_softirqs+0x1b0/0x8d0 kernel/softirq.c:622
                      __do_softirq kernel/softirq.c:656 [inline]
                      invoke_softirq kernel/softirq.c:496 [inline]
                      __irq_exit_rcu+0xc4/0x100 kernel/softirq.c:723
                      irq_exit_rcu+0x9/0x20 kernel/softirq.c:739
                      instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1056 [inline]
                      sysvec_apic_timer_interrupt+0x70/0x80 arch/x86/kernel/apic/apic.c:1056
                      asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:729
                      __preempt_count_dec_and_test arch/x86/include/asm/preempt.h:95 [inline]
                      __raw_spin_unlock_irqrestore include/linux/spinlock_api_smp.h:179 [inline]
                      _raw_spin_unlock_irqrestore+0x34/0x50 kernel/locking/spinlock.c:194
                      spin_unlock_irqrestore include/linux/spinlock.h:407 [inline]
                      class_spinlock_irqsave_destructor include/linux/spinlock.h:618 [inline]
                      input_inject_event+0x1bd/0x420 drivers/input/input.c:419
                      evdev_write+0x30a/0x460 drivers/input/evdev.c:528
                      vfs_write+0x2b1/0x11a0 fs/read_write.c:686
                      ksys_write+0x1ef/0x240 fs/read_write.c:740
                      do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
                      do_syscall_64+0x111/0x680 arch/x86/entry/syscall_64.c:94
                      entry_SYSCALL_64_after_hwframe+0x77/0x7f
    INITIAL USE at:
                     lock_acquire kernel/locking/lockdep.c:5868 [inline]
                     lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
                     __raw_spin_lock_irqsave include/linux/spinlock_api_smp.h:132 [inline]
                     _raw_spin_lock_irqsave+0x3a/0x60 kernel/locking/spinlock.c:162
                     class_spinlock_irqsave_constructor include/linux/spinlock.h:618 [inline]
                     input_inject_event+0x9f/0x420 drivers/input/input.c:419
                     __led_set_brightness drivers/leds/led-core.c:52 [inline]
                     led_set_brightness_nopm drivers/leds/led-core.c:335 [inline]
                     led_set_brightness_nosleep drivers/leds/led-core.c:369 [inline]
                     led_set_brightness+0x217/0x290 drivers/leds/led-core.c:328
                     kbd_led_trigger_activate+0xcd/0x110 drivers/tty/vt/keyboard.c:1021
                     led_trigger_set+0x4c9/0xaa0 drivers/leds/led-triggers.c:220
                     led_match_default_trigger drivers/leds/led-triggers.c:277 [inline]
                     led_match_default_trigger drivers/leds/led-triggers.c:271 [inline]
                     led_trigger_set_default drivers/leds/led-triggers.c:300 [inline]
                     led_trigger_set_default+0x1e7/0x2e0 drivers/leds/led-triggers.c:284
                     led_classdev_register_ext+0x63a/0x980 drivers/leds/led-class.c:578
                     led_classdev_register include/linux/leds.h:274 [inline]
                     input_leds_connect+0x4c5/0x900 drivers/input/input-leds.c:145
                     input_attach_handler+0x17b/0x260 drivers/input/input.c:994
                     input_register_device+0xa1e/0x1070 drivers/input/input.c:2378
                     atkbd_connect+0x6c2/0xb60 drivers/input/keyboard/atkbd.c:1340
                     serio_connect_driver drivers/input/serio/serio.c:44 [inline]
                     serio_driver_probe+0x84/0xe0 drivers/input/serio/serio.c:748
                     call_driver_probe drivers/base/dd.c:643 [inline]
                     really_probe+0x260/0x840 drivers/base/dd.c:721
                     __driver_probe_device+0x1e7/0x390 drivers/base/dd.c:863
                     driver_probe_device+0x4e/0x2e0 drivers/base/dd.c:893
                     __driver_attach drivers/base/dd.c:1287 [inline]
                     __driver_attach+0x1d6/0x5d0 drivers/base/dd.c:1227
                     bus_for_each_dev+0x12c/0x1c0 drivers/base/bus.c:383
                     serio_attach_driver drivers/input/serio/serio.c:777 [inline]
                     serio_handle_event+0x234/0x980 drivers/input/serio/serio.c:214
                     process_one_work+0x920/0x1ac0 kernel/workqueue.c:3276
                     process_scheduled_works kernel/workqueue.c:3359 [inline]
                     worker_thread+0x693/0xeb0 kernel/workqueue.c:3440
                     kthread+0x385/0x490 kernel/kthread.c:436
                     ret_from_fork+0x67a/0xab0 arch/x86/kernel/process.c:158
                     ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
  }
  ... key      at: [<ffffffffbe892e60>] __key.4+0x0/0x40
-> (&client->buffer_lock){....}-{3:3} {
   INITIAL USE at:
                   lock_acquire kernel/locking/lockdep.c:5868 [inline]
                   lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
                   __raw_spin_lock_irq include/linux/spinlock_api_smp.h:142 [inline]
                   _raw_spin_lock_irq+0x33/0x50 kernel/locking/spinlock.c:170
                   spin_lock_irq include/linux/spinlock.h:371 [inline]
                   evdev_fetch_next_event drivers/input/evdev.c:543 [inline]
                   evdev_read+0x4ee/0xc70 drivers/input/evdev.c:584
                   vfs_read+0x1e6/0xc70 fs/read_write.c:572
                   ksys_read+0x1ef/0x240 fs/read_write.c:717
                   do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
                   do_syscall_64+0x111/0x680 arch/x86/entry/syscall_64.c:94
                   entry_SYSCALL_64_after_hwframe+0x77/0x7f
 }
 ... key      at: [<ffffffffbe893060>] __key.84+0x0/0x40
 ... acquired at:
   lock_acquire kernel/locking/lockdep.c:5868 [inline]
   lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
   __raw_spin_lock include/linux/spinlock_api_smp.h:158 [inline]
   _raw_spin_lock+0x2b/0x40 kernel/locking/spinlock.c:154
   spin_lock include/linux/spinlock.h:341 [inline]
   evdev_handle_get_val+0x70/0x620 drivers/input/evdev.c:898
   evdev_do_ioctl+0x908/0x1a80 drivers/input/evdev.c:1157
   evdev_ioctl_handler drivers/input/evdev.c:1270 [inline]
   evdev_ioctl+0x17e/0x1f0 drivers/input/evdev.c:1279
   vfs_ioctl fs/ioctl.c:51 [inline]
   __do_sys_ioctl fs/ioctl.c:597 [inline]
   __se_sys_ioctl fs/ioctl.c:583 [inline]
   __x64_sys_ioctl+0x18f/0x210 fs/ioctl.c:583
   do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
   do_syscall_64+0x111/0x680 arch/x86/entry/syscall_64.c:94
   entry_SYSCALL_64_after_hwframe+0x77/0x7f


the dependencies between the lock to be acquired
 and SOFTIRQ-irq-unsafe lock:
  -> (tasklist_lock){.+.+}-{3:3} {
     HARDIRQ-ON-R at:
                        lock_acquire kernel/locking/lockdep.c:5868 [inline]
                        lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
                        __raw_read_lock include/linux/rwlock_api_smp.h:161 [inline]
                        _raw_read_lock+0x5c/0x70 kernel/locking/spinlock.c:228
                        __do_wait+0x105/0x880 kernel/exit.c:1678
                        do_wait+0x1cb/0x5a0 kernel/exit.c:1722
                        kernel_wait+0x9f/0x160 kernel/exit.c:1898
                        call_usermodehelper_exec_sync kernel/umh.c:136 [inline]
                        call_usermodehelper_exec_work+0xf9/0x180 kernel/umh.c:163
                        process_one_work+0x920/0x1ac0 kernel/workqueue.c:3276
                        process_scheduled_works kernel/workqueue.c:3359 [inline]
                        worker_thread+0x693/0xeb0 kernel/workqueue.c:3440
                        kthread+0x385/0x490 kernel/kthread.c:436
                        ret_from_fork+0x67a/0xab0 arch/x86/kernel/process.c:158
                        ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
     SOFTIRQ-ON-R at:
                        lock_acquire kernel/locking/lockdep.c:5868 [inline]
                        lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
                        __raw_read_lock include/linux/rwlock_api_smp.h:161 [inline]
                        _raw_read_lock+0x5c/0x70 kernel/locking/spinlock.c:228
                        __do_wait+0x105/0x880 kernel/exit.c:1678
                        do_wait+0x1cb/0x5a0 kernel/exit.c:1722
                        kernel_wait+0x9f/0x160 kernel/exit.c:1898
                        call_usermodehelper_exec_sync kernel/umh.c:136 [inline]
                        call_usermodehelper_exec_work+0xf9/0x180 kernel/umh.c:163
                        process_one_work+0x920/0x1ac0 kernel/workqueue.c:3276
                        process_scheduled_works kernel/workqueue.c:3359 [inline]
                        worker_thread+0x693/0xeb0 kernel/workqueue.c:3440
                        kthread+0x385/0x490 kernel/kthread.c:436
                        ret_from_fork+0x67a/0xab0 arch/x86/kernel/process.c:158
                        ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
     INITIAL USE at:
                       lock_acquire kernel/locking/lockdep.c:5868 [inline]
                       lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
                       __raw_write_lock_irq include/linux/rwlock_api_smp.h:211 [inline]
                       _raw_write_lock_irq+0x33/0x50 kernel/locking/spinlock.c:326
                       copy_process+0x4547/0x7440 kernel/fork.c:2369
                       kernel_clone+0xea/0x830 kernel/fork.c:2653
                       user_mode_thread+0xc8/0x110 kernel/fork.c:2729
                       rest_init+0x25/0x320 init/main.c:725
                       start_kernel+0x400/0x530 init/main.c:1210
                       x86_64_start_reservations+0x18/0x30 arch/x86/kernel/head64.c:310
                       x86_64_start_kernel+0x112/0x130 arch/x86/kernel/head64.c:291
                       common_startup_64+0x13e/0x148
     INITIAL READ USE at:
                            lock_acquire kernel/locking/lockdep.c:5868 [inline]
                            lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
                            __raw_read_lock include/linux/rwlock_api_smp.h:161 [inline]
                            _raw_read_lock+0x5c/0x70 kernel/locking/spinlock.c:228
                            __do_wait+0x105/0x880 kernel/exit.c:1678
                            do_wait+0x1cb/0x5a0 kernel/exit.c:1722
                            kernel_wait+0x9f/0x160 kernel/exit.c:1898
                            call_usermodehelper_exec_sync kernel/umh.c:136 [inline]
                            call_usermodehelper_exec_work+0xf9/0x180 kernel/umh.c:163
                            process_one_work+0x920/0x1ac0 kernel/workqueue.c:3276
                            process_scheduled_works kernel/workqueue.c:3359 [inline]
                            worker_thread+0x693/0xeb0 kernel/workqueue.c:3440
                            kthread+0x385/0x490 kernel/kthread.c:436
                            ret_from_fork+0x67a/0xab0 arch/x86/kernel/process.c:158
                            ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
   }
   ... key      at: [<ffffffffbba0c098>] tasklist_lock+0x18/0x40
   ... acquired at:
   lock_acquire kernel/locking/lockdep.c:5868 [inline]
   lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
   __raw_read_lock include/linux/rwlock_api_smp.h:161 [inline]
   _raw_read_lock+0x5c/0x70 kernel/locking/spinlock.c:228
   send_sigio+0xb8/0x420 fs/fcntl.c:932
   kill_fasync_rcu fs/fcntl.c:1144 [inline]
   kill_fasync fs/fcntl.c:1159 [inline]
   kill_fasync+0x218/0x590 fs/fcntl.c:1152
   sock_wake_async+0xd6/0x160 net/socket.c:1509
   sk_wake_async_rcu include/net/sock.h:2579 [inline]
   sk_wake_async_rcu include/net/sock.h:2576 [inline]
   sock_def_readable+0x55f/0x660 net/core/sock.c:3613
   __netlink_sendskb net/netlink/af_netlink.c:1263 [inline]
   netlink_sendskb net/netlink/af_netlink.c:1269 [inline]
   netlink_unicast+0x745/0x870 net/netlink/af_netlink.c:1359
   nlmsg_unicast include/net/netlink.h:1198 [inline]
   netlink_ack+0x6b6/0xb90 net/netlink/af_netlink.c:2512
   netlink_rcv_skb+0x344/0x430 net/netlink/af_netlink.c:2556
   nfnetlink_rcv+0x1af/0x420 net/netfilter/nfnetlink.c:669
   netlink_unicast_kernel net/netlink/af_netlink.c:1318 [inline]
   netlink_unicast+0x5a7/0x870 net/netlink/af_netlink.c:1344
   netlink_sendmsg+0x8a3/0xda0 net/netlink/af_netlink.c:1894
   sock_sendmsg_nosec net/socket.c:727 [inline]
   __sock_sendmsg net/socket.c:742 [inline]
   ____sys_sendmsg+0x9c4/0xb30 net/socket.c:2592
   ___sys_sendmsg+0x11c/0x1b0 net/socket.c:2646
   __sys_sendmsg+0x150/0x200 net/socket.c:2678
   do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
   do_syscall_64+0x111/0x680 arch/x86/entry/syscall_64.c:94
   entry_SYSCALL_64_after_hwframe+0x77/0x7f

 -> (&f_owner->lock){....}-{3:3} {
    INITIAL USE at:
                     lock_acquire kernel/locking/lockdep.c:5868 [inline]
                     lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
                     __raw_write_lock_irq include/linux/rwlock_api_smp.h:211 [inline]
                     _raw_write_lock_irq+0x33/0x50 kernel/locking/spinlock.c:326
                     __f_setown+0x60/0x3c0 fs/fcntl.c:136
                     fcntl_dirnotify+0x623/0xb60 fs/notify/dnotify/dnotify.c:369
                     do_fcntl+0x235/0x1580 fs/fcntl.c:538
                     __do_sys_fcntl fs/fcntl.c:602 [inline]
                     __se_sys_fcntl fs/fcntl.c:587 [inline]
                     __x64_sys_fcntl+0x163/0x200 fs/fcntl.c:587
                     do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
                     do_syscall_64+0x111/0x680 arch/x86/entry/syscall_64.c:94
                     entry_SYSCALL_64_after_hwframe+0x77/0x7f
    INITIAL READ USE at:
                          lock_acquire kernel/locking/lockdep.c:5868 [inline]
                          lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
                          __raw_read_lock_irqsave include/linux/rwlock_api_smp.h:172 [inline]
                          _raw_read_lock_irqsave+0x75/0x90 kernel/locking/spinlock.c:236
                          send_sigio+0x31/0x420 fs/fcntl.c:918
                          kill_fasync_rcu fs/fcntl.c:1144 [inline]
                          kill_fasync fs/fcntl.c:1159 [inline]
                          kill_fasync+0x218/0x590 fs/fcntl.c:1152
                          lease_break_callback+0x23/0x30 fs/locks.c:577
                          __break_lease+0x7e4/0x1b50 fs/locks.c:1657
                          break_lease include/linux/filelock.h:484 [inline]
                          break_lease include/linux/filelock.h:469 [inline]
                          vfs_truncate+0x3e1/0x4e0 fs/open.c:112
                          do_sys_truncate+0xd6/0x180 fs/open.c:142
                          __do_sys_truncate fs/open.c:154 [inline]
                          __se_sys_truncate fs/open.c:152 [inline]
                          __x64_sys_truncate+0x54/0x80 fs/open.c:152
                          do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
                          do_syscall_64+0x111/0x680 arch/x86/entry/syscall_64.c:94
                          entry_SYSCALL_64_after_hwframe+0x77/0x7f
  }
  ... key      at: [<ffffffffbe845aa0>] __key.1+0x0/0x40
  ... acquired at:
   lock_acquire kernel/locking/lockdep.c:5868 [inline]
   lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
   __raw_read_lock_irqsave include/linux/rwlock_api_smp.h:172 [inline]
   _raw_read_lock_irqsave+0x75/0x90 kernel/locking/spinlock.c:236
   send_sigio+0x31/0x420 fs/fcntl.c:918
   kill_fasync_rcu fs/fcntl.c:1144 [inline]
   kill_fasync fs/fcntl.c:1159 [inline]
   kill_fasync+0x218/0x590 fs/fcntl.c:1152
   lease_break_callback+0x23/0x30 fs/locks.c:577
   __break_lease+0x7e4/0x1b50 fs/locks.c:1657
   break_lease include/linux/filelock.h:484 [inline]
   break_lease include/linux/filelock.h:469 [inline]
   vfs_truncate+0x3e1/0x4e0 fs/open.c:112
   do_sys_truncate+0xd6/0x180 fs/open.c:142
   __do_sys_truncate fs/open.c:154 [inline]
   __se_sys_truncate fs/open.c:152 [inline]
   __x64_sys_truncate+0x54/0x80 fs/open.c:152
   do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
   do_syscall_64+0x111/0x680 arch/x86/entry/syscall_64.c:94
   entry_SYSCALL_64_after_hwframe+0x77/0x7f

-> (&new->fa_lock){...-}-{3:3} {
   IN-SOFTIRQ-R at:
                    lock_acquire kernel/locking/lockdep.c:5868 [inline]
                    lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
                    __raw_read_lock_irqsave include/linux/rwlock_api_smp.h:172 [inline]
                    _raw_read_lock_irqsave+0x46/0x90 kernel/locking/spinlock.c:236
                    kill_fasync_rcu fs/fcntl.c:1135 [inline]
                    kill_fasync fs/fcntl.c:1159 [inline]
                    kill_fasync+0x137/0x590 fs/fcntl.c:1152
                    sock_wake_async+0xd6/0x160 net/socket.c:1509
                    sk_wake_async_rcu include/net/sock.h:2579 [inline]
                    sk_wake_async_rcu include/net/sock.h:2576 [inline]
                    sock_def_readable+0x55f/0x660 net/core/sock.c:3613
                    packet_rcv+0xec8/0x1740 net/packet/af_packet.c:2209
                    dev_queue_xmit_nit+0x713/0xb00 net/core/dev.c:2606
                    xmit_one net/core/dev.c:3884 [inline]
                    dev_hard_start_xmit+0x605/0x720 net/core/dev.c:3904
                    __dev_queue_xmit+0x1649/0x3f60 net/core/dev.c:4854
                    dev_queue_xmit include/linux/netdevice.h:3385 [inline]
                    neigh_hh_output include/net/neighbour.h:540 [inline]
                    neigh_output include/net/neighbour.h:554 [inline]
                    ip_finish_output2+0xb1c/0x1ce0 net/ipv4/ip_output.c:237
                    __ip_finish_output.part.0+0x1bb/0x350 net/ipv4/ip_output.c:315
                    __ip_finish_output net/ipv4/ip_output.c:303 [inline]
                    ip_finish_output net/ipv4/ip_output.c:325 [inline]
                    NF_HOOK_COND include/linux/netfilter.h:307 [inline]
                    ip_output+0x3a9/0xd00 net/ipv4/ip_output.c:438
                    dst_output include/net/dst.h:470 [inline]
                    ip_local_out+0x1b4/0x200 net/ipv4/ip_output.c:131
                    __ip_queue_xmit+0x899/0x1f40 net/ipv4/ip_output.c:534
                    __tcp_transmit_skb+0x2f93/0x4780 net/ipv4/tcp_output.c:1693
                    __tcp_send_ack.part.0+0x3ce/0x670 net/ipv4/tcp_output.c:4503
                    __tcp_send_ack net/ipv4/tcp_output.c:4509 [inline]
                    tcp_send_ack+0x83/0xa0 net/ipv4/tcp_output.c:4509
                    tcp_delack_timer_handler net/ipv4/tcp_timer.c:345 [inline]
                    tcp_delack_timer_handler+0x2b8/0x460 net/ipv4/tcp_timer.c:308
                    tcp_delack_timer+0x232/0x3c0 net/ipv4/tcp_timer.c:376
                    call_timer_fn+0x189/0x5c0 kernel/time/timer.c:1748
                    expire_timers kernel/time/timer.c:1799 [inline]
                    __run_timers+0x6cd/0xb00 kernel/time/timer.c:2373
                    __run_timer_base kernel/time/timer.c:2385 [inline]
                    __run_timer_base kernel/time/timer.c:2377 [inline]
                    run_timer_base kernel/time/timer.c:2394 [inline]
                    run_timer_softirq+0x117/0x210 kernel/time/timer.c:2404
                    handle_softirqs+0x1b0/0x8d0 kernel/softirq.c:622
                    __do_softirq kernel/softirq.c:656 [inline]
                    invoke_softirq kernel/softirq.c:496 [inline]
                    __irq_exit_rcu+0xc4/0x100 kernel/softirq.c:723
                    irq_exit_rcu+0x9/0x20 kernel/softirq.c:739
                    instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1056 [inline]
                    sysvec_apic_timer_interrupt+0x70/0x80 arch/x86/kernel/apic/apic.c:1056
                    asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:729
                    native_safe_halt arch/x86/include/asm/irqflags.h:48 [inline]
                    pv_native_safe_halt+0x1e/0x30 arch/x86/kernel/paravirt.c:62
                    arch_safe_halt arch/x86/include/asm/paravirt.h:73 [inline]
                    default_idle+0xe/0x20 arch/x86/kernel/process.c:767
                    default_idle_call+0x6c/0xb0 kernel/sched/idle.c:122
                    cpuidle_idle_call kernel/sched/idle.c:199 [inline]
                    do_idle+0x31f/0x580 kernel/sched/idle.c:352
                    cpu_startup_entry+0x4f/0x60 kernel/sched/idle.c:451
                    start_secondary+0x1c7/0x230 arch/x86/kernel/smpboot.c:312
                    common_startup_64+0x13e/0x148
   INITIAL USE at:
                   lock_acquire kernel/locking/lockdep.c:5868 [inline]
                   lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
                   __raw_write_lock_irq include/linux/rwlock_api_smp.h:211 [inline]
                   _raw_write_lock_irq+0x33/0x50 kernel/locking/spinlock.c:326
                   fasync_remove_entry+0xb2/0x1e0 fs/fcntl.c:1012
                   fasync_helper+0xa6/0xc0 fs/fcntl.c:1115
                   pipe_fasync+0xce/0x210 fs/pipe.c:758
                   __fput+0x94b/0xb50 fs/file_table.c:466
                   task_work_run+0x16b/0x260 kernel/task_work.c:233
                   exit_task_work include/linux/task_work.h:40 [inline]
                   do_exit+0x8c3/0x29e0 kernel/exit.c:976
                   __do_sys_exit kernel/exit.c:1085 [inline]
                   __se_sys_exit kernel/exit.c:1083 [inline]
                   __x64_sys_exit+0x42/0x50 kernel/exit.c:1083
                   x64_sys_call+0x18d8/0x18e0 arch/x86/include/generated/asm/syscalls_64.h:61
                   do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
                   do_syscall_64+0x111/0x680 arch/x86/entry/syscall_64.c:94
                   entry_SYSCALL_64_after_hwframe+0x77/0x7f
   INITIAL READ USE at:
                        lock_acquire kernel/locking/lockdep.c:5868 [inline]
                        lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
                        __raw_read_lock_irqsave include/linux/rwlock_api_smp.h:172 [inline]
                        _raw_read_lock_irqsave+0x75/0x90 kernel/locking/spinlock.c:236
                        kill_fasync_rcu fs/fcntl.c:1135 [inline]
                        kill_fasync fs/fcntl.c:1159 [inline]
                        kill_fasync+0x137/0x590 fs/fcntl.c:1152
                        fsnotify_insert_event+0x379/0x480 fs/notify/notification.c:128
                        fsnotify_add_event include/linux/fsnotify_backend.h:739 [inline]
                        inotify_handle_inode_event+0x2a7/0x420 fs/notify/inotify/inotify_fsnotify.c:126
                        fsnotify_handle_inode_event.isra.0+0x1df/0x410 fs/notify/fsnotify.c:272
                        fsnotify_handle_event fs/notify/fsnotify.c:327 [inline]
                        send_to_group fs/notify/fsnotify.c:375 [inline]
                        fsnotify+0x147d/0x1a10 fs/notify/fsnotify.c:592
                        __fsnotify_parent+0x781/0xca0 fs/notify/fsnotify.c:238
                        fsnotify_parent include/linux/fsnotify.h:96 [inline]
                        fsnotify_dentry include/linux/fsnotify.h:108 [inline]
                        fsnotify_change include/linux/fsnotify.h:495 [inline]
                        notify_change+0x96b/0x1330 fs/attr.c:561
                        chown_common+0x3fe/0x690 fs/open.c:778
                        do_fchownat+0x18b/0x1e0 fs/open.c:806
                        __do_sys_lchown fs/open.c:831 [inline]
                        __se_sys_lchown fs/open.c:829 [inline]
                        __x64_sys_lchown+0x7e/0xc0 fs/open.c:829
                        do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
                        do_syscall_64+0x111/0x680 arch/x86/entry/syscall_64.c:94
                        entry_SYSCALL_64_after_hwframe+0x77/0x7f
 }
 ... key      at: [<ffffffffbe845a60>] __key.0+0x0/0x40
 ... acquired at:
   check_prevs_add kernel/locking/lockdep.c:3284 [inline]
   validate_chain kernel/locking/lockdep.c:3908 [inline]
   __lock_acquire+0x15c0/0x2030 kernel/locking/lockdep.c:5237
   lock_acquire kernel/locking/lockdep.c:5868 [inline]
   lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
   __raw_read_lock_irqsave include/linux/rwlock_api_smp.h:172 [inline]
   _raw_read_lock_irqsave+0x75/0x90 kernel/locking/spinlock.c:236
   kill_fasync_rcu fs/fcntl.c:1135 [inline]
   kill_fasync fs/fcntl.c:1159 [inline]
   kill_fasync+0x137/0x590 fs/fcntl.c:1152
   __pass_event drivers/input/evdev.c:240 [inline]
   evdev_pass_values.part.0+0x63a/0x950 drivers/input/evdev.c:278
   evdev_pass_values drivers/input/evdev.c:253 [inline]
   evdev_events+0x282/0x4e0 drivers/input/evdev.c:306
   input_pass_values+0x767/0x8b0 drivers/input/input.c:128
   input_event_dispose drivers/input/input.c:342 [inline]
   input_handle_event+0xe43/0x1510 drivers/input/input.c:370
   input_inject_event+0x1e5/0x420 drivers/input/input.c:424
   evdev_write+0x30a/0x460 drivers/input/evdev.c:528
   vfs_write+0x2b1/0x11a0 fs/read_write.c:686
   ksys_write+0x1ef/0x240 fs/read_write.c:740
   do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
   do_syscall_64+0x111/0x680 arch/x86/entry/syscall_64.c:94
   entry_SYSCALL_64_after_hwframe+0x77/0x7f


stack backtrace:
CPU: 2 UID: 0 PID: 30382 Comm: syz.6.15929 Kdump: loaded Not tainted 7.0.0-rc6-00259-g427a4f9708ee #82 PREEMPT(lazy) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-5.el9 11/05/2023
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0xca/0x120 lib/dump_stack.c:120
 print_bad_irq_dependency kernel/locking/lockdep.c:2616 [inline]
 check_irq_usage+0x8a0/0xc50 kernel/locking/lockdep.c:2857
 check_prev_add+0xfd/0xcf0 kernel/locking/lockdep.c:3169
 check_prevs_add kernel/locking/lockdep.c:3284 [inline]
 validate_chain kernel/locking/lockdep.c:3908 [inline]
 __lock_acquire+0x15c0/0x2030 kernel/locking/lockdep.c:5237
 lock_acquire kernel/locking/lockdep.c:5868 [inline]
 lock_acquire+0x18c/0x300 kernel/locking/lockdep.c:5825
 __raw_read_lock_irqsave include/linux/rwlock_api_smp.h:172 [inline]
 _raw_read_lock_irqsave+0x75/0x90 kernel/locking/spinlock.c:236
 kill_fasync_rcu fs/fcntl.c:1135 [inline]
 kill_fasync fs/fcntl.c:1159 [inline]
 kill_fasync+0x137/0x590 fs/fcntl.c:1152
 __pass_event drivers/input/evdev.c:240 [inline]
 evdev_pass_values.part.0+0x63a/0x950 drivers/input/evdev.c:278
 evdev_pass_values drivers/input/evdev.c:253 [inline]
 evdev_events+0x282/0x4e0 drivers/input/evdev.c:306
 input_pass_values+0x767/0x8b0 drivers/input/input.c:128
 input_event_dispose drivers/input/input.c:342 [inline]
 input_handle_event+0xe43/0x1510 drivers/input/input.c:370
 input_inject_event+0x1e5/0x420 drivers/input/input.c:424
 evdev_write+0x30a/0x460 drivers/input/evdev.c:528
 vfs_write+0x2b1/0x11a0 fs/read_write.c:686
 ksys_write+0x1ef/0x240 fs/read_write.c:740
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x111/0x680 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fbb7b9b282d
Code: 5b 41 5c c3 66 0f 1f 84 00 00 00 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d bb 15 0f 00 f7 d8 64 89 01 48
RSP: 002b:00007fbb7a499cb8 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00000000005d61c0 RCX: 00007fbb7b9b282d
RDX: 0000000000000048 RSI: 0000200000000480 RDI: 0000000000000003
RBP: 00000000005d61c0 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00000000005d6258 R14: 00007fbb7b934fe0 R15: 0000000000000000
 </TASK>


-- 
All rights reversed.
 

^ permalink raw reply

* [PATCH] HID: lenovo: Fix buffer over-read and unaligned access in X12 Tab raw_event handler
From: Kean @ 2026-05-14 12:58 UTC (permalink / raw)
  To: sashiko-bot, derekjohn.clark
  Cc: linux-input, mpearson-lenovo, jikos, bentiss, linux-kernel, Kean
In-Reply-To: <20260512044911.99B6DC2BCB0@smtp.kernel.org>

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 <rh_king@163.com>
---
 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 <linux/hid.h>
 #include <linux/input.h>
 #include <linux/leds.h>
+#include <linux/unaligned.h>
 #include <linux/workqueue.h>
 
 #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;
 }
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH] HID: lenovo: Restore Fn-lock LED state on resume for tp10ubkbd devices
From: Kean @ 2026-05-14 12:54 UTC (permalink / raw)
  To: sashiko-bot, derekjohn.clark
  Cc: linux-input, dmitry.torokhov, mpearson-lenovo, jikos, bentiss,
	linux-kernel, Kean
In-Reply-To: <20260513222748.D7A5FC19425@smtp.kernel.org>

Hi,

Thank you for the review.

Regarding the potential issue you raised:

> [High] Unprotected state modifications / TOCTOU Race leading to Deadlock
>
> Could this new call to lenovo_led_set_tp10ubkbd() cause a deadlock during
> runtime resume?

In my initial testing, I performed system suspend/resume (S3) cycles and the
behavior was normal — the Fn-lock LED state was correctly restored after
resume with no hangs or lockups observed. I tested two scenarios:

1. Normal suspend/resume: suspend the system with the keyboard attached,
   then resume normally.
2. Detach-reconnect: suspend the system, physically detach the keyboard
   during suspend, resume the system, then re-attach the keyboard.

Both scenarios appeared to work correctly without issues.

However, I realize my testing only covered system suspend/resume (S3), not
runtime PM resume (which may hold different locks in the HID core). Your
analysis about a potential deadlock is reasonable and worth investigating
thoroughly. I need to:

- Test runtime resume scenarios specifically (e.g., autosuspend of the USB
  HID device).
- Verify there is no lock ordering conflict between led_report_mutex (held
  inside lenovo_led_set_tp10ubkbd) and any HID core locks that may already
  be held when reset_resume is called.

I will run additional tests covering these cases. If I can reproduce any
issue, I will submit a V2 with the appropriate fix. If not, I will follow
up with a detailed description of my testing methodology and conclusions.

Thanks again for your guidance.

Regards,
Kean


^ permalink raw reply

* Re: [PATCH v2 3/4] HID: hid-msi: Add RGB control interface
From: sashiko-bot @ 2026-05-14 12:53 UTC (permalink / raw)
  To: Derek J. Clark; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260513231445.3213501-4-derekjohn.clark@gmail.com>

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] The `speed_show` function incorrectly casts the LED class device to a HID device, resulting in an invalid pointer dereference and a guaranteed kernel panic.
- [Critical] Missing bounds check on `frame->frame_count` from the USB device leads to an out-of-bounds read of kernel memory.
- [High] Re-arming `rgb_queue` from sysfs attributes after it has been explicitly cancelled in `claw_remove` leads to a Use-After-Free.
- [High] The `guard(mutex)` macro in `claw_hw_output_report()` is incorrectly used inside a block scope, providing absolutely no synchronization.
- [High] Concurrent modification of shared RGB state without lock protection causes data races and memory corruption.
- [High] Every RGB state change triggers a `SYNC_TO_ROM` command, causing severe flash memory degradation.
- [Medium] An early return in `cfg_setup_fn` silently drops core gamepad sysfs attributes if RGB initialization fails.
--

commit a90ff4a0f03b6df7827bfbcd222f977138b9d280
Author: Derek J. Clark <derekjohn.clark@gmail.com>

HID: hid-msi: Add RGB control interface

Adds RGB control interface for MSI Claw devices. The MSI Claw uses a
fairly unique RGB interface. It has 9 total zones (4 per joystick ring
and 1 for the ABXY buttons), and supports up to 8 sequential frames of
RGB zone data.

> --- a/drivers/hid/hid-msi.c
> +++ b/drivers/hid/hid-msi.c

[ ... ]

> @@ -308,15 +368,52 @@ static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_
>  		for (i = 0; i < CLAW_KEYS_MAX; i++)
>  			codes[i] = (cmd_rep->data[6 + i] != 0xff) ? cmd_rep->data[6 + i] : 0x00;
>  		break;
> +	case CLAW_RGB_PENDING:
> +		frame = (struct rgb_report *)cmd_rep->data;
> +		rgb_addr = drvdata->rgb_addr;
> +		read_addr = be16_to_cpu(frame->read_addr);
> +		frame_calc = (read_addr - rgb_addr) / CLAW_RGB_FRAME_OFFSET;
> +		if (frame_calc > U8_MAX) {
> +			dev_err(drvdata->led_mc.led_cdev.dev, "Got unsupported frame index: %x\n",
> +				frame_calc);
> +			ret = -EINVAL;
> +			goto err_pending;
> +		}
> +		f_idx = frame_calc;
> +
> +		if (f_idx >= CLAW_RGB_MAX_FRAMES) {
> +			dev_err(drvdata->led_mc.led_cdev.dev, "Got illegal frame index: %x\n",
> +				f_idx);
> +			ret = -EINVAL;
> +			goto err_pending;
> +		}
> +
> +		/* Always treat the first frame as the truth for these constants */
> +		if (f_idx == 0) {
> +			drvdata->rgb_frame_count = frame->frame_count;

drvdata->rgb_frame_count is assigned directly from the hardware packet.
Should there be a bounds check here to ensure it is less than or equal to
CLAW_RGB_MAX_FRAMES?

Later, claw_write_rgb_state() uses drvdata->rgb_frame_count as the upper
bound for a loop reading from drvdata->rgb_frames. If a device sends a
frame count greater than 8, could this read out-of-bounds kernel memory
and send it back to the USB device?

> +			/* Invert device speed (20-0) to sysfs speed (0-20) */
> +			drvdata->rgb_speed = frame->speed;
> +			drvdata->led_mc.led_cdev.brightness = frame->brightness;
> +			drvdata->led_mc.subled_info[0].intensity = frame->zone_data.zone[0].red;
> +			drvdata->led_mc.subled_info[1].intensity = frame->zone_data.zone[0].green;
> +			drvdata->led_mc.subled_info[2].intensity = frame->zone_data.zone[0].blue;
> +		}
> +
> +		memcpy(&drvdata->rgb_frames[f_idx], &frame->zone_data,
> +		       sizeof(struct rgb_frame));

claw_profile_event() modifies drvdata->rgb_frames and
drvdata->rgb_frame_count from the HID raw event handler context.
Simultaneously, claw_rgb_queue_fn() runs in a workqueue and calls
functions that overwrite these exact same fields.

Since there are no locks protecting these updates, could a background
worker read torn state or corrupt the frame arrays while they are being
actively updated by the asynchronous device response?

[ ... ]

> @@ -759,6 +856,397 @@ static const struct attribute_group claw_gamepad_attr_group = {
>  	.is_visible = claw_gamepad_attr_is_visible,
>  };
>  
> +/* Read RGB config from device */
> +static int claw_read_rgb_config(struct hid_device *hdev)
> +{

[ ... ]

> +/* Send RGB configuration to device */
> +static int claw_write_rgb_state(struct claw_drvdata *drvdata)
> +{
> +	struct rgb_report report = { 0x01, 0x0000, CLAW_RGB_FRAME_OFFSET, 0x00,
> +			drvdata->rgb_frame_count, 0x09, drvdata->rgb_speed,
> +			drvdata->led_mc.led_cdev.brightness };
> +	u16 write_addr = drvdata->rgb_addr;
> +	size_t len = sizeof(report);
> +	int f, ret;
> +
> +	if (!drvdata->rgb_addr)
> +		return -ENODEV;
> +
> +	if (!drvdata->rgb_frame_count)
> +		return -EINVAL;
> +
> +	guard(mutex)(&drvdata->rom_mutex);
> +	/* Loop through (up to) 8 pages of RGB data */
> +	for (f = 0; f < drvdata->rgb_frame_count; f++) {
> +		report.zone_data = drvdata->rgb_frames[f];
> +
> +		/* Set the MCU address to write the frame data to */
> +		report.read_addr = cpu_to_be16(write_addr);
> +
> +		/* Serialize the rgb_report and write it to MCU */
> +		ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA,
> +					    (u8 *)&report, len, 8);
> +		if (ret)
> +			return ret;
> +
> +		/* Increment the write addr by the offset for the next frame */
> +		write_addr += CLAW_RGB_FRAME_OFFSET;
> +	}
> +
> +	ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 8);

Does sending CLAW_COMMAND_TYPE_SYNC_TO_ROM on every RGB state change
cause excessive flash memory degradation?

Because claw_apply_rgb_state() is triggered by unthrottled sysfs writes
(such as rapidly changing a brightness slider), this might cause hundreds
of NVRAM writes per second and potentially degrade the MCU's flash
endurance.

[ ... ]

> +static ssize_t effect_store(struct device *dev,
> +			    struct device_attribute *attr,
> +			    const char *buf, size_t count)
> +{
> +	struct led_classdev *led_cdev = dev_get_drvdata(dev);
> +	struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
> +	struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
> +	int ret;
> +
> +	ret = sysfs_match_string(claw_rgb_effect_text, buf);
> +	if (ret < 0)
> +		return ret;
> +
> +	drvdata->rgb_effect = ret;
> +	mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50));
> +
> +	return count;
> +}

Is there a potential use-after-free here if sysfs attributes are written
after the device is removed?

In claw_remove(), the driver attempts to tear down the worker using
cancel_delayed_work_sync(&drvdata->rgb_queue). However, the sysfs files
and LED endpoints remain active because devm cleanup executes after
claw_remove() completes.

A concurrent write to a sysfs attribute like effect_store could call
mod_delayed_work() and re-arm the work item, causing it to execute after
drvdata has been freed.

[ ... ]

> +static ssize_t speed_show(struct device *dev, struct device_attribute *attr,
> +			  char *buf)
> +{
> +	struct hid_device *hdev = to_hid_device(dev);
> +	struct claw_drvdata *drvdata = hid_get_drvdata(hdev);

Is the dev parameter passed to speed_show actually a hid_device?

The rgb_attr_group sysfs group is attached to the LED class device
(led_cdev.dev), not the parent HID device. The macro to_hid_device(dev)
blindly performs a container_of cast, which will yield an invalid pointer
for hdev.

Dereferencing it with hid_get_drvdata(hdev) will likely cause a kernel
panic when this world-readable attribute is accessed.

[ ... ]

> @@ -772,6 +1260,13 @@ static void cfg_setup_fn(struct work_struct *work)
>  		return;
>  	}
>  
> +	ret = claw_read_rgb_config(drvdata->hdev);
> +	if (ret) {
> +		dev_err(drvdata->led_mc.led_cdev.dev,
> +			"Failed to setup device, can't read RGB config: %d\n", ret);
> +		return;
> +	}
> +
>  	/* Add sysfs attributes after we get the device state */
>  	ret = devm_device_add_group(&drvdata->hdev->dev, &claw_gamepad_attr_group);

If claw_read_rgb_config() fails due to a temporary USB error or
unsupported firmware, this early return skips the subsequent
devm_device_add_group call.

Does this unintentionally break the device's core gamepad configuration
interface simply because the RGB initialization failed?

This isn't a new bug introduced by this patch, but does the guard() in
claw_hw_output_report() provide any synchronization?

claw_hw_output_report() {
    if (timeout) {
        guard(mutex)(&drvdata->cfg_mutex);
        reinit_completion(&drvdata->send_cmd_complete);
    }
    ...
}

Because the guard is declared inside the block, the mutex is immediately
released at the closing brace before hid_hw_output_report() and
wait_for_completion_interruptible_timeout() are ever called. This seems
to leave the I/O operations unprotected against concurrent threads, which
this patch introduces via the new RGB workers.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260513231445.3213501-1-derekjohn.clark@gmail.com?part=3

^ permalink raw reply

* Re: [PATCH v2 2/4] HID: hid-msi: Add M-key mapping attributes
From: sashiko-bot @ 2026-05-14 12:22 UTC (permalink / raw)
  To: Derek J. Clark; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260513231445.3213501-3-derekjohn.clark@gmail.com>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] The `cfg_mutex` guard in `claw_hw_output_report()` is scoped to an `if` block, releasing the lock before the critical section executes.
- [High] Lockless concurrent access to `profile_pending` between process and interrupt contexts causes data corruption and sequence mismatches on delayed responses.
- [High] Unsafe assumption of USB transport in `claw_probe()` causes type confusion and invalid pointer dereference for non-USB devices.
- [High] Returning an error from `claw_profile_event()` for a spurious event unconditionally triggers `complete()`, causing premature wakeups of unrelated commands.
- [Medium] The function `claw_buttons_store()` illegally mixes scope-based `guard()` cleanup with `goto`-based error handling.
--

commit 42be71c046dc9b6b31d28598f5e0bb568d78b7a1
Author: Derek J. Clark <derekjohn.clark@gmail.com>

HID: hid-msi: Add M-key mapping attributes

Adds attributes that allow for remapping the M-keys with up to 5 values
when in macro mode.

> diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
> index 8915942af15e6..13ba2747fdb66 100644
> --- a/drivers/hid/hid-msi.c
> +++ b/drivers/hid/hid-msi.c

[ ... ]

> @@ -125,6 +294,31 @@ static int claw_gamepad_mode_event(struct claw_drvdata *drvdata,
>  	return 0;
>  }
>  
> +static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_report *cmd_rep)
> +{
> +	u8 *codes;
> +	int i;
> +
> +	switch (drvdata->profile_pending) {
> +	case CLAW_M1_PENDING:
> +	case CLAW_M2_PENDING:
> +		codes = (drvdata->profile_pending == CLAW_M1_PENDING) ?
> +			drvdata->m1_codes : drvdata->m2_codes;
> +		/* Extract key codes; replace disabled (0xff) with 0x00, which is (null) in _show */
> +		for (i = 0; i < CLAW_KEYS_MAX; i++)
> +			codes[i] = (cmd_rep->data[6 + i] != 0xff) ? cmd_rep->data[6 + i] : 0x00;
> +		break;

Is it safe to read and modify profile_pending locklessly here in the
interrupt handler?

Since claw_buttons_show() modifies this state in process context under
profile_mutex, if a sysfs request times out and a subsequent request starts,
could the delayed ACK from the first request cause this handler to see the
new profile_pending state and corrupt the new request's buffer?

> +	default:
> +		dev_warn(&drvdata->hdev->dev,
> +			 "Got profile event without changes pending from command: %x\n",
> +			 cmd_rep->cmd);
> +		return -EINVAL;
> +	}
> +	drvdata->profile_pending = CLAW_NO_PENDING;
> +
> +	return 0;
> +}
> +
>  static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *report,
>  			  u8 *data, int size)
>  {

[ ... ]

> @@ -146,6 +340,9 @@ static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *repor
>  	case CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK:
>  		ret = claw_gamepad_mode_event(drvdata, cmd_rep);
>  		break;
> +	case CLAW_COMMAND_TYPE_READ_PROFILE_ACK:
> +		ret = claw_profile_event(drvdata, cmd_rep);
> +		break;
>  	case CLAW_COMMAND_TYPE_ACK:
>  		break;
>  	default:

When claw_profile_event() returns -EINVAL for a spurious event,
claw_raw_event() breaks from the switch and unconditionally calls
complete(&drvdata->send_cmd_complete).

Could this prematurely wake up a thread waiting on a completely different
command, such as WRITE_PROFILE_DATA?

[ ... ]

> @@ -366,6 +563,161 @@ static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
>  }
>  static DEVICE_ATTR_WO(reset);
>  
> +static int button_mapping_name_to_code(const char *name)
> +{

[ ... ]

> +static int claw_buttons_store(struct device *dev, const char *buf, u8 mkey_idx)
> +{
> +	struct hid_device *hdev = to_hid_device(dev);
> +	struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
> +	u8 data[] = { 0x01, (drvdata->bmap_addr[mkey_idx] >> 8) & 0xff,
> +		      drvdata->bmap_addr[mkey_idx] & 0xff, 0x07,
> +		      0x04, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff };
> +	size_t len = ARRAY_SIZE(data);
> +	int ret, key_count, i;
> +	char **raw_keys;
> +
> +	raw_keys = argv_split(GFP_KERNEL, buf, &key_count);
> +	if (!raw_keys)
> +		return -ENOMEM;
> +
> +	guard(mutex)(&drvdata->rom_mutex); /* all err_free paths must be in scope */
> +	if (key_count > CLAW_KEYS_MAX) {
> +		ret = -EINVAL;
> +		goto err_free;
> +	}
> +
> +	if (key_count == 0)
> +		goto set_buttons;
> +
> +	for (i = 0; i < key_count; i++) {
> +		ret = button_mapping_name_to_code(raw_keys[i]);
> +		if (ret < 0)
> +			goto err_free;
> +
> +		data[6 + i] = ret;
> +	}
> +
> +set_buttons:
> +	ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, data, len, 8);

While reviewing the addition of these sysfs attributes, I noticed a
pre-existing issue in claw_hw_output_report() that is exacerbated by
the concurrent sysfs requests added here:

drivers/hid/hid-msi.c:claw_hw_output_report() {
    ...
    if (timeout) {
        guard(mutex)(&drvdata->cfg_mutex);
        reinit_completion(&drvdata->send_cmd_complete);
    }
    ...
}

Since guard() is scoped to the if block, the mutex is immediately released at
the closing brace.

Doesn't this mean hid_hw_output_report() and
wait_for_completion_interruptible_timeout() execute entirely without
the lock, defeating the intended serialization and allowing concurrent
requests to race?

> +	if (ret < 0)
> +		goto err_free;
> +
> +	ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 8);
> +
> +err_free:
> +	argv_free(raw_keys);
> +	return ret;
> +}

Is it safe to mix goto-based error handling with scope-based guard() cleanup
in the same function?

According to the cleanup guidelines, functions should either convert
entirely to scope-based cleanup, for example by using a DEFINE_FREE
wrapper for argv_free, or not use guard() at all to avoid confusing
ownership semantics.

[ ... ]

> @@ -432,8 +796,31 @@ static void cfg_resume_fn(struct work_struct *work)
>  		dev_err(&drvdata->hdev->dev, "Failed to set gamepad mode settings: %d\n", ret);
>  }
>  
> +static void claw_features_supported(struct claw_drvdata *drvdata)
> +{

[ ... ]

>  static int claw_probe(struct hid_device *hdev, u8 ep)
>  {
> +	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
> +	struct usb_device *udev = interface_to_usbdev(intf);

Is it guaranteed that this device is always connected via USB?

If this driver is bound to a device connected over a non-USB transport like
I2C, Bluetooth, or UHID, hdev->dev.parent would not be a valid USB
interface, which could cause an invalid pointer dereference here.

Could this code check hid_is_usb(hdev) first or read the version from
hdev->version instead?

>  	struct claw_drvdata *drvdata;
>  	int ret;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260513231445.3213501-1-derekjohn.clark@gmail.com?part=2

^ permalink raw reply

* Re: [PATCH 7/7] hid-asus: add prod-id, quirk for Zenbook Duo keyboard
From: sashiko-bot @ 2026-05-14 12:03 UTC (permalink / raw)
  To: Paolo Pisati; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260513163248.16483-8-p.pisati@gmail.com>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] Out-of-bounds read and heap buffer overflow in report descriptor fixup logic.
--

commit ad6b37250b171e419ae8bd990515ef1b0181a049
Author: Luke Jones <luke@ljones.dev>

hid-asus: add prod-id, quirk for Zenbook Duo keyboard

This commit adds product IDs and Bluetooth IDs for the 2024/2025/2026 ASUS
Zenbook Duo keyboards. It enables keyboard control, backlight, and Fn-lock
by applying the appropriate quirks.

> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index 37e3aec612b6d..ba093f11f802f 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -1583,6 +1583,24 @@ static const struct hid_device_id asus_devices[] = {
>  	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
>  	    USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
>  	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
> +	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
> +	    USB_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406MA_KEYBOARD),
> +	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_HID_FN_LOCK | QUIRK_ZENBOOK_DUO_KEYBOARD },

This isn't a bug introduced in this patch, but adding these new device IDs with
QUIRK_ZENBOOK_DUO_KEYBOARD exposes them to an existing out-of-bounds read and
heap buffer overflow in asus_report_fixup().

When a device with this quirk connects, the driver attempts to strip trailing
zeros from its report descriptor:

drivers/hid/hid-asus.c:asus_report_fixup() {
    ...
    if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI | QUIRK_ZENBOOK_DUO_KEYBOARD)) {
        ...
        if (drvdata->quirks & QUIRK_ZENBOOK_DUO_KEYBOARD) {
            if (hid_is_usb(hdev)) {
                rsize_orig = 90;
                offs = 66;
            ...
        }
        
        if (*rsize == rsize_orig &&
            rdesc[offs] == 0x09 && rdesc[offs + 1] == 0x76) {
            __u8 *new_rdesc;
            unsigned int new_rsize = rsize_orig;

            /* Clear bogus trailing zero bytes. */
            while (rdesc[new_rsize + 1] == 0)
                --new_rsize;
    ...
}

Does this evaluate rdesc[new_rsize + 1] reading 2 bytes past the end of the
dynamically allocated descriptor buffer, since new_rsize is initialized to the
exact size of the descriptor?

Furthermore, if the out-of-bounds bytes are non-zero, new_rsize remains its
original value. The code then copies rsize_orig + 2 bytes from the
rsize_orig-sized buffer:

drivers/hid/hid-asus.c:asus_report_fixup() {
    ...
            new_rsize += 2;
            new_rdesc = devm_kzalloc(&hdev->dev, new_rsize, GFP_KERNEL);
            ...
            memcpy(new_rdesc, rdesc, new_rsize);
    ...
}

Can this result in an unconditional slab-out-of-bounds read?

Finally, if new_rsize shrank below offs + 14 due to trailing zeros, could the
subsequent memmove and assignments write past the end of the newly allocated
buffer, causing a heap buffer overflow?

drivers/hid/hid-asus.c:asus_report_fixup() {
    ...
            memmove(rdesc + offs + 4, rdesc + offs + 2, 12);
            ...
            rdesc[offs + 14] = 0x00;
    ...
}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260513163248.16483-1-p.pisati@gmail.com?part=7

^ permalink raw reply

* Re: [PATCH v2 1/4] HID: hid-msi: Add MSI Claw configuration driver
From: sashiko-bot @ 2026-05-14 11:51 UTC (permalink / raw)
  To: Derek J. Clark; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260513231445.3213501-2-derekjohn.clark@gmail.com>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] The `guard(mutex)` macro in `claw_hw_output_report` is scoped to an `if` block, prematurely releasing the lock before the critical section.
- [High] The driver publishes `drvdata` via `hid_set_drvdata()` before fully initializing its mutex and completion objects, creating a race condition.
- [Medium] An invalid, uninitialized gamepad mode (0) may be sent to the hardware during system resume.
- [Medium] The 8ms timeout used in `claw_hw_output_report()` is vulnerable to jiffies rounding, leading to spurious read failures.
- [Medium] The commit message claims probe checks MCU RAM address variances, but the implementation is missing.
--

commit 13b388de96d7484eee908adeed127e8086fc289e
Author: Derek J. Clark <derekjohn.clark@gmail.com>

HID: hid-msi: Add MSI Claw configuration driver

This commit adds a configuration HID driver for the MSI Claw series of
handheld PCs. It provides support for switching the gamepad mode and changing
the behavior of the M-keys.

>     Additionally, the MCU RAM address for writing configuration data has
>     changed twice. Checks are done during probe to enumerate these variances.

The commit message states that checks are done during probe to enumerate MCU
RAM address variances.

Looking at claw_probe() and msi_probe(), there does not appear to be any code
that checks firmware versions or handles these variances.

Was this functionality dropped during refactoring, or should the commit message
be updated?

> diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
> new file mode 100644
> index 0000000000000..8915942af15e6
> --- /dev/null
> +++ b/drivers/hid/hid-msi.c

[ ... ]

> +static int claw_hw_output_report(struct hid_device *hdev, u8 index, u8 *data,
> +				 size_t len, unsigned int timeout)
> +{
> +	unsigned char *dmabuf __free(kfree) = NULL;
> +	u8 header[] = { CLAW_OUTPUT_REPORT_ID, 0, 0, 0x3c, index };
> +	struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
> +	size_t header_size = ARRAY_SIZE(header);
> +	int ret;
> +
> +	if (header_size + len > CLAW_PACKET_SIZE)
> +		return -EINVAL;
> +
> +	/* We can't use a devm_alloc reusable buffer without side effects during suspend */
> +	dmabuf = kzalloc(CLAW_PACKET_SIZE, GFP_KERNEL);
> +	if (!dmabuf)
> +		return -ENOMEM;
> +
> +	memcpy(dmabuf, header, header_size);
> +	if (data && len)
> +		memcpy(dmabuf + header_size, data, len);
> +
> +	/* Don't hold a mutex when timeout=0, those commands cause USB disconnect */
> +	if (timeout) {
> +		guard(mutex)(&drvdata->cfg_mutex);
> +		reinit_completion(&drvdata->send_cmd_complete);
> +	}

Does this drop the lock too early?

The guard() macro releases the lock at the end of its enclosing scope.
Because it is declared inside the if (timeout) block, the mutex will be
released immediately at the closing brace, leaving hid_hw_output_report() and
the subsequent wait unprotected against concurrent access.

> +
> +	dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n",
> +		CLAW_PACKET_SIZE, dmabuf);
> +
> +	ret = hid_hw_output_report(hdev, dmabuf, CLAW_PACKET_SIZE);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = ret == CLAW_PACKET_SIZE ? 0 : -EIO;
> +	if (ret)
> +		return ret;
> +
> +	if (timeout) {
> +		ret = wait_for_completion_interruptible_timeout(&drvdata->send_cmd_complete,
> +								msecs_to_jiffies(timeout));

Could this timeout evaluate to 1 jiffy on systems configured with HZ=100?

Several callers, such as gamepad_mode_show(), pass an 8ms timeout to this
function. A 1-jiffy timeout can expire almost instantaneously if the operation
begins near the end of the current tick boundary, potentially leading to
spurious -EBUSY timeouts.

[ ... ]

> +static void cfg_resume_fn(struct work_struct *work)
> +{
> +	struct delayed_work *dwork = container_of(work, struct delayed_work, work);
> +	struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, cfg_resume);
> +	u8 data[2] = { drvdata->gamepad_mode, drvdata->mkeys_function };

If the system suspends before cfg_setup_fn() executes, or if the device read in
cfg_setup_fn() fails, could drvdata->gamepad_mode still be 0 here?

Since 0 is not a valid mode (valid modes like CLAW_GAMEPAD_MODE_XINPUT start
at 1), will this send an uninitialized configuration state to the hardware?

> +	int ret;
> +
> +	ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data,
> +				    ARRAY_SIZE(data), 0);
> +	if (ret)
> +		dev_err(&drvdata->hdev->dev, "Failed to set gamepad mode settings: %d\n", ret);
> +}
> +
> +static int claw_probe(struct hid_device *hdev, u8 ep)
> +{
> +	struct claw_drvdata *drvdata;
> +	int ret;
> +
> +	drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
> +	if (!drvdata)
> +		return -ENOMEM;
> +
> +	hid_set_drvdata(hdev, drvdata);
> +	drvdata->hdev = hdev;
> +	drvdata->ep = ep;
> +
> +	mutex_init(&drvdata->cfg_mutex);
> +	init_completion(&drvdata->send_cmd_complete);

Is it safe to publish drvdata via hid_set_drvdata() before initializing the
mutex and completion object?

Since hid_hw_start() was already called earlier in msi_probe(), the hardware
is active. If an input report arrives right after hid_set_drvdata(), the
interrupt handler msi_raw_event() will process it and call complete() on an
uninitialized waitqueue.

> +
> +	/* For control interface: open the HID transport for sending commands. */
> +	ret = hid_hw_open(hdev);
> +	if (ret)
> +		return ret;
> +
> +	INIT_DELAYED_WORK(&drvdata->cfg_resume, &cfg_resume_fn);
> +	INIT_DELAYED_WORK(&drvdata->cfg_setup, &cfg_setup_fn);
> +	schedule_delayed_work(&drvdata->cfg_setup, msecs_to_jiffies(500));
> +
> +	return 0;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260513231445.3213501-1-derekjohn.clark@gmail.com?part=1

^ permalink raw reply


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