* Re: [PATCH 4/9] iio: pressure: hid-sensor-press: Fix race between callback registration and device exposure
From: sashiko-bot @ 2026-06-06 11:50 UTC (permalink / raw)
To: Sanjay Chitroda; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260606-5-june-hid-iio-race-fixes-v1-4-27a848c5758f@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Critical] Reordering teardown introduces list corruption and UAF via concurrent buffer access.
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the lack of synchronization in sensor hub callback removal leads to Use-After-Free, and this patch exposes the race on the probe error path.
--
commit 82bc619c3ad12fb028f336e0c55f37d3a740bf6e
Author: Sanjay Chitroda <sanjayembeddedse@gmail.com>
iio: pressure: hid-sensor-press: Fix race between callback registration and device exposure
The driver registers the IIO device before completing sensor hub
callback registration and unregisters callbacks while the IIO device
is still exposed during teardown. This patch reorders callback
registration and IIO device registration in probe and remove paths.
> diff --git a/drivers/iio/pressure/hid-sensor-press.c b/drivers/iio/pressure/hid-sensor-press.c
> index a039b99d9851d..09795473948a9 100644
> --- a/drivers/iio/pressure/hid-sensor-press.c
> +++ b/drivers/iio/pressure/hid-sensor-press.c
[ ... ]
> @@ -304,13 +298,19 @@ static int hid_press_probe(struct platform_device *pdev)
> &press_state->callbacks);
> if (ret < 0) {
> dev_err(&pdev->dev, "callback reg failed\n");
> - goto error_iio_unreg;
> + goto error_remove_trigger;
> + }
> +
> + ret = iio_device_register(indio_dev);
> + if (ret) {
> + dev_err(&pdev->dev, "device register failed\n");
> + goto error_remove_callback;
> }
>
> return ret;
>
> -error_iio_unreg:
> - iio_device_unregister(indio_dev);
> +error_remove_callback:
> + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PRESSURE);
[Severity: High]
This is a pre-existing issue, but the lack of synchronization in sensor
hub callback removal might lead to a use-after-free, and this patch
exposes it on the probe error path.
When sensor_hub_remove_callback() deletes the list entry:
drivers/hid/hid-sensor-hub.c:sensor_hub_remove_callback() {
list_del(&callback->list);
kfree(callback);
...
}
it doesn't appear to wait for active readers to finish processing. If
iio_device_register() fails, the driver calls sensor_hub_remove_callback()
and returns, allowing devres to free the press_state memory.
Could a suspended or delayed sensor_hub_raw_event() thread resume and
dereference the freed memory?
> error_remove_trigger:
> hid_sensor_remove_trigger(indio_dev, &press_state->common_attributes);
> return ret;
> @@ -323,8 +323,8 @@ static void hid_press_remove(struct platform_device *pdev)
> struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> struct press_state *press_state = iio_priv(indio_dev);
>
> - sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PRESSURE);
> iio_device_unregister(indio_dev);
> + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PRESSURE);
[Severity: Critical]
Does this reversed teardown order introduce list corruption or a
use-after-free through concurrent buffer access?
By calling iio_device_unregister() before removing the sensor hub
callback, incoming HID events can still trigger press_proc_event().
This locklessly pushes to buffers:
drivers/iio/pressure/hid-sensor-press.c:press_proc_event()
iio_push_to_buffers_with_ts()
iio_push_to_buffers()
list_for_each_entry(buf, &iio_dev_opaque->buffer_list, buffer_list)
While this runs, iio_device_unregister() actively removes buffers
from the list:
iio_device_unregister()
iio_disable_all_buffers()
list_del_init()
Could iterating over the buffer list while it is being modified by
list_del_init() cause the lockless reader to follow the self-referential
next pointer, leading to an infinite loop or use-after-free?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260606-5-june-hid-iio-race-fixes-v1-0-27a848c5758f@gmail.com?part=4
^ permalink raw reply
* [PATCH 9/9] iio: accel: hid-sensor-accel-3d: Fix race between callback registration and device exposure
From: Sanjay Chitroda @ 2026-06-06 11:37 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, David Lechner,
Nuno Sá, Andy Shevchenko, Archana Patni, Song Hongyan
Cc: linux-input, linux-iio, linux-kernel, srinivas pandruvada,
Sanjay Chitroda
In-Reply-To: <20260606-5-june-hid-iio-race-fixes-v1-0-27a848c5758f@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
The driver registers the IIO device before completing sensor hub
callback registration and unregisters callbacks while the IIO device
is still exposed during teardown.
This creates race windows in both probe and remove paths, which can
lead to NULL pointer dereferences or use-after-free.
Fix this by correct ordering of callback registration and
IIO device registration in probe and remove paths.
Fixes: 45fe6f7d002c ("iio: hid-sensors: Added accelerometer 3D")
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
---
drivers/iio/accel/hid-sensor-accel-3d.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/iio/accel/hid-sensor-accel-3d.c b/drivers/iio/accel/hid-sensor-accel-3d.c
index 2bf05ab5235e..c000e001c001 100644
--- a/drivers/iio/accel/hid-sensor-accel-3d.c
+++ b/drivers/iio/accel/hid-sensor-accel-3d.c
@@ -386,12 +386,6 @@ static int hid_accel_3d_probe(struct platform_device *pdev)
return ret;
}
- ret = iio_device_register(indio_dev);
- if (ret) {
- dev_err(&pdev->dev, "device register failed\n");
- goto error_remove_trigger;
- }
-
accel_state->callbacks.send_event = accel_3d_proc_event;
accel_state->callbacks.capture_sample = accel_3d_capture_sample;
accel_state->callbacks.pdev = pdev;
@@ -399,13 +393,19 @@ static int hid_accel_3d_probe(struct platform_device *pdev)
&accel_state->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
- goto error_iio_unreg;
+ goto error_remove_trigger;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "device register failed\n");
+ goto error_remove_callback;
}
return ret;
-error_iio_unreg:
- iio_device_unregister(indio_dev);
+error_remove_callback:
+ sensor_hub_remove_callback(hsdev, hsdev->usage);
error_remove_trigger:
hid_sensor_remove_trigger(indio_dev, &accel_state->common_attributes);
return ret;
@@ -418,8 +418,8 @@ static void hid_accel_3d_remove(struct platform_device *pdev)
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct accel_3d_state *accel_state = iio_priv(indio_dev);
- sensor_hub_remove_callback(hsdev, hsdev->usage);
iio_device_unregister(indio_dev);
+ sensor_hub_remove_callback(hsdev, hsdev->usage);
hid_sensor_remove_trigger(indio_dev, &accel_state->common_attributes);
}
--
2.34.1
^ permalink raw reply related
* [PATCH 8/9] iio: magnetometer: hid-sensor-magn-3d: Fix race between callback registration and device exposure
From: Sanjay Chitroda @ 2026-06-06 11:37 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, David Lechner,
Nuno Sá, Andy Shevchenko, Archana Patni, Song Hongyan
Cc: linux-input, linux-iio, linux-kernel, srinivas pandruvada,
Sanjay Chitroda
In-Reply-To: <20260606-5-june-hid-iio-race-fixes-v1-0-27a848c5758f@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
The driver registers the IIO device before completing sensor hub
callback registration and unregisters callbacks while the IIO device
is still exposed during teardown.
This creates race windows in both probe and remove paths, which can
lead to NULL pointer dereferences or use-after-free.
Fix this by correct ordering of callback registration and
IIO device registration in probe and remove paths.
Fixes: bc1d57ba0669 ("iio: hid-sensors: Added Compass/Magnetometer 3D")
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
---
drivers/iio/magnetometer/hid-sensor-magn-3d.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/iio/magnetometer/hid-sensor-magn-3d.c b/drivers/iio/magnetometer/hid-sensor-magn-3d.c
index 23884825eb00..acae77273bd5 100644
--- a/drivers/iio/magnetometer/hid-sensor-magn-3d.c
+++ b/drivers/iio/magnetometer/hid-sensor-magn-3d.c
@@ -513,12 +513,6 @@ static int hid_magn_3d_probe(struct platform_device *pdev)
return ret;
}
- ret = iio_device_register(indio_dev);
- if (ret) {
- dev_err(&pdev->dev, "device register failed\n");
- goto error_remove_trigger;
- }
-
magn_state->callbacks.send_event = magn_3d_proc_event;
magn_state->callbacks.capture_sample = magn_3d_capture_sample;
magn_state->callbacks.pdev = pdev;
@@ -526,13 +520,19 @@ static int hid_magn_3d_probe(struct platform_device *pdev)
&magn_state->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
- goto error_iio_unreg;
+ goto error_remove_trigger;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "device register failed\n");
+ goto error_remove_callback;
}
return ret;
-error_iio_unreg:
- iio_device_unregister(indio_dev);
+error_remove_callback:
+ sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_COMPASS_3D);
error_remove_trigger:
hid_sensor_remove_trigger(indio_dev, &magn_state->magn_flux_attributes);
return ret;
@@ -545,8 +545,8 @@ static void hid_magn_3d_remove(struct platform_device *pdev)
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct magn_3d_state *magn_state = iio_priv(indio_dev);
- sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_COMPASS_3D);
iio_device_unregister(indio_dev);
+ sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_COMPASS_3D);
hid_sensor_remove_trigger(indio_dev, &magn_state->magn_flux_attributes);
}
--
2.34.1
^ permalink raw reply related
* [PATCH 7/9] iio: light: hid-sensor-als: Fix race between callback registration and device exposure
From: Sanjay Chitroda @ 2026-06-06 11:37 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, David Lechner,
Nuno Sá, Andy Shevchenko, Archana Patni, Song Hongyan
Cc: linux-input, linux-iio, linux-kernel, srinivas pandruvada,
Sanjay Chitroda
In-Reply-To: <20260606-5-june-hid-iio-race-fixes-v1-0-27a848c5758f@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
The driver registers the IIO device before completing sensor hub
callback registration and unregisters callbacks while the IIO device
is still exposed during teardown.
This creates race windows in both probe and remove paths, which can
lead to NULL pointer dereferences or use-after-free.
Fix this by correct ordering of callback registration and
IIO device registration in probe and remove paths.
Fixes: ed5514c925a0 ("iio: hid-sensors: Added ALS")
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
---
drivers/iio/light/hid-sensor-als.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c
index d72e260b8266..93d603848690 100644
--- a/drivers/iio/light/hid-sensor-als.c
+++ b/drivers/iio/light/hid-sensor-als.c
@@ -406,25 +406,25 @@ static int hid_als_probe(struct platform_device *pdev)
return ret;
}
- ret = iio_device_register(indio_dev);
- if (ret) {
- dev_err(&pdev->dev, "device register failed\n");
- goto error_remove_trigger;
- }
-
als_state->callbacks.send_event = als_proc_event;
als_state->callbacks.capture_sample = als_capture_sample;
als_state->callbacks.pdev = pdev;
ret = sensor_hub_register_callback(hsdev, hsdev->usage, &als_state->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
- goto error_iio_unreg;
+ goto error_remove_trigger;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "device register failed\n");
+ goto error_remove_callback;
}
return ret;
-error_iio_unreg:
- iio_device_unregister(indio_dev);
+error_remove_callback:
+ sensor_hub_remove_callback(hsdev, hsdev->usage);
error_remove_trigger:
hid_sensor_remove_trigger(indio_dev, &als_state->common_attributes);
return ret;
@@ -437,8 +437,8 @@ static void hid_als_remove(struct platform_device *pdev)
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct als_state *als_state = iio_priv(indio_dev);
- sensor_hub_remove_callback(hsdev, hsdev->usage);
iio_device_unregister(indio_dev);
+ sensor_hub_remove_callback(hsdev, hsdev->usage);
hid_sensor_remove_trigger(indio_dev, &als_state->common_attributes);
}
--
2.34.1
^ permalink raw reply related
* [PATCH 6/9] iio: light: hid-sensor-prox: Fix race between callback registration and device exposure
From: Sanjay Chitroda @ 2026-06-06 11:37 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, David Lechner,
Nuno Sá, Andy Shevchenko, Archana Patni, Song Hongyan
Cc: linux-input, linux-iio, linux-kernel, srinivas pandruvada,
Sanjay Chitroda
In-Reply-To: <20260606-5-june-hid-iio-race-fixes-v1-0-27a848c5758f@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
The driver registers the IIO device before completing sensor hub
callback registration and unregisters callbacks while the IIO device
is still exposed during teardown.
This creates race windows in both probe and remove paths, which can
lead to NULL pointer dereferences or use-after-free.
Fix this by correct ordering of callback registration and
IIO device registration in probe and remove paths.
Fixes: 39a3a0138f61 ("iio: hid-sensors: Added Proximity Sensor Driver")
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
---
drivers/iio/light/hid-sensor-prox.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/iio/light/hid-sensor-prox.c b/drivers/iio/light/hid-sensor-prox.c
index edc9274a2c07..3df7f28d3b44 100644
--- a/drivers/iio/light/hid-sensor-prox.c
+++ b/drivers/iio/light/hid-sensor-prox.c
@@ -312,12 +312,6 @@ static int hid_prox_probe(struct platform_device *pdev)
return ret;
}
- ret = iio_device_register(indio_dev);
- if (ret) {
- dev_err(&pdev->dev, "device register failed\n");
- goto error_remove_trigger;
- }
-
prox_state->callbacks.send_event = prox_proc_event;
prox_state->callbacks.capture_sample = prox_capture_sample;
prox_state->callbacks.pdev = pdev;
@@ -325,13 +319,19 @@ static int hid_prox_probe(struct platform_device *pdev)
&prox_state->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
- goto error_iio_unreg;
+ goto error_remove_trigger;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "device register failed\n");
+ goto error_remove_callback;
}
return ret;
-error_iio_unreg:
- iio_device_unregister(indio_dev);
+error_remove_callback:
+ sensor_hub_remove_callback(hsdev, hsdev->usage);
error_remove_trigger:
hid_sensor_remove_trigger(indio_dev, &prox_state->common_attributes);
return ret;
@@ -344,8 +344,8 @@ static void hid_prox_remove(struct platform_device *pdev)
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct prox_state *prox_state = iio_priv(indio_dev);
- sensor_hub_remove_callback(hsdev, hsdev->usage);
iio_device_unregister(indio_dev);
+ sensor_hub_remove_callback(hsdev, hsdev->usage);
hid_sensor_remove_trigger(indio_dev, &prox_state->common_attributes);
}
--
2.34.1
^ permalink raw reply related
* [PATCH 5/9] iio: temperature: hid-sensor-temperature: switch to non-devm iio_device_register()
From: Sanjay Chitroda @ 2026-06-06 11:37 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, David Lechner,
Nuno Sá, Andy Shevchenko, Archana Patni, Song Hongyan
Cc: linux-input, linux-iio, linux-kernel, srinivas pandruvada,
Sanjay Chitroda
In-Reply-To: <20260606-5-june-hid-iio-race-fixes-v1-0-27a848c5758f@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
Avoid using devm_iio_device_register() as this driver requires explicit
error handling paths. Mixing devm_* APIs with goto-based unwinding
breaks the expected LIFO resource release model and may introduce race
windows during teardown, potentially leading to use-after-free issues.
Add explicit iio_device_unregister() call in the teardown path to ensure
deterministic cleanup and follow kernel resource management conventions.
Fixes: 59d0f2da3569 ("iio: hid: Add temperature sensor support")
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
---
drivers/iio/temperature/hid-sensor-temperature.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/iio/temperature/hid-sensor-temperature.c b/drivers/iio/temperature/hid-sensor-temperature.c
index 9f628a8e5cfb..34bff7e9f3a3 100644
--- a/drivers/iio/temperature/hid-sensor-temperature.c
+++ b/drivers/iio/temperature/hid-sensor-temperature.c
@@ -244,7 +244,7 @@ static int hid_temperature_probe(struct platform_device *pdev)
if (ret)
goto error_remove_trigger;
- ret = devm_iio_device_register(indio_dev->dev.parent, indio_dev);
+ ret = iio_device_register(indio_dev);
if (ret)
goto error_remove_callback;
@@ -264,6 +264,7 @@ static void hid_temperature_remove(struct platform_device *pdev)
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct temperature_state *temp_st = iio_priv(indio_dev);
+ iio_device_unregister(indio_dev);
sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_TEMPERATURE);
hid_sensor_remove_trigger(indio_dev, &temp_st->common_attributes);
}
--
2.34.1
^ permalink raw reply related
* [PATCH 4/9] iio: pressure: hid-sensor-press: Fix race between callback registration and device exposure
From: Sanjay Chitroda @ 2026-06-06 11:37 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, David Lechner,
Nuno Sá, Andy Shevchenko, Archana Patni, Song Hongyan
Cc: linux-input, linux-iio, linux-kernel, srinivas pandruvada,
Sanjay Chitroda
In-Reply-To: <20260606-5-june-hid-iio-race-fixes-v1-0-27a848c5758f@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
The driver registers the IIO device before completing sensor hub
callback registration and unregisters callbacks while the IIO device
is still exposed during teardown.
This creates race windows in both probe and remove paths, which can
lead to NULL pointer dereferences or use-after-free.
Fix this by correct ordering of callback registration and
IIO device registration in probe and remove paths.
Fixes: f64a799b8a49 ("iio: hid-sensors: Added Pressure Sensor driver")
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
---
drivers/iio/pressure/hid-sensor-press.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/iio/pressure/hid-sensor-press.c b/drivers/iio/pressure/hid-sensor-press.c
index a039b99d9851..09795473948a 100644
--- a/drivers/iio/pressure/hid-sensor-press.c
+++ b/drivers/iio/pressure/hid-sensor-press.c
@@ -291,12 +291,6 @@ static int hid_press_probe(struct platform_device *pdev)
return ret;
}
- ret = iio_device_register(indio_dev);
- if (ret) {
- dev_err(&pdev->dev, "device register failed\n");
- goto error_remove_trigger;
- }
-
press_state->callbacks.send_event = press_proc_event;
press_state->callbacks.capture_sample = press_capture_sample;
press_state->callbacks.pdev = pdev;
@@ -304,13 +298,19 @@ static int hid_press_probe(struct platform_device *pdev)
&press_state->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
- goto error_iio_unreg;
+ goto error_remove_trigger;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "device register failed\n");
+ goto error_remove_callback;
}
return ret;
-error_iio_unreg:
- iio_device_unregister(indio_dev);
+error_remove_callback:
+ sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PRESSURE);
error_remove_trigger:
hid_sensor_remove_trigger(indio_dev, &press_state->common_attributes);
return ret;
@@ -323,8 +323,8 @@ static void hid_press_remove(struct platform_device *pdev)
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct press_state *press_state = iio_priv(indio_dev);
- sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PRESSURE);
iio_device_unregister(indio_dev);
+ sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PRESSURE);
hid_sensor_remove_trigger(indio_dev, &press_state->common_attributes);
}
--
2.34.1
^ permalink raw reply related
* [PATCH 3/9] iio: gyro: hid-sensor-gyro-3d: Fix race between callback registration and device exposure
From: Sanjay Chitroda @ 2026-06-06 11:37 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, David Lechner,
Nuno Sá, Andy Shevchenko, Archana Patni, Song Hongyan
Cc: linux-input, linux-iio, linux-kernel, srinivas pandruvada,
Sanjay Chitroda
In-Reply-To: <20260606-5-june-hid-iio-race-fixes-v1-0-27a848c5758f@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
The driver registers the IIO device before completing sensor hub
callback registration and unregisters callbacks while the IIO device
is still exposed during teardown.
This creates race windows in both probe and remove paths, which can
lead to NULL pointer dereferences or use-after-free.
Fix this by correct ordering of callback registration and
IIO device registration in probe and remove paths.
Fixes: c5bdbef704ba ("iio: hid-sensors: Added Gyroscope 3D")
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
---
drivers/iio/gyro/hid-sensor-gyro-3d.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/iio/gyro/hid-sensor-gyro-3d.c b/drivers/iio/gyro/hid-sensor-gyro-3d.c
index e48c25c87b6d..dd871eacdaf4 100644
--- a/drivers/iio/gyro/hid-sensor-gyro-3d.c
+++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c
@@ -324,12 +324,6 @@ static int hid_gyro_3d_probe(struct platform_device *pdev)
return ret;
}
- ret = iio_device_register(indio_dev);
- if (ret) {
- dev_err(&pdev->dev, "device register failed\n");
- goto error_remove_trigger;
- }
-
gyro_state->callbacks.send_event = gyro_3d_proc_event;
gyro_state->callbacks.capture_sample = gyro_3d_capture_sample;
gyro_state->callbacks.pdev = pdev;
@@ -337,13 +331,19 @@ static int hid_gyro_3d_probe(struct platform_device *pdev)
&gyro_state->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
- goto error_iio_unreg;
+ goto error_remove_trigger;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "device register failed\n");
+ goto error_remove_callback;
}
return ret;
-error_iio_unreg:
- iio_device_unregister(indio_dev);
+error_remove_callback:
+ sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D);
error_remove_trigger:
hid_sensor_remove_trigger(indio_dev, &gyro_state->common_attributes);
return ret;
@@ -356,8 +356,8 @@ static void hid_gyro_3d_remove(struct platform_device *pdev)
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct gyro_3d_state *gyro_state = iio_priv(indio_dev);
- sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D);
iio_device_unregister(indio_dev);
+ sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D);
hid_sensor_remove_trigger(indio_dev, &gyro_state->common_attributes);
}
--
2.34.1
^ permalink raw reply related
* [PATCH 2/9] iio: orientation: hid-sensor-incl-3d: Fix race between callback registration and device exposure
From: Sanjay Chitroda @ 2026-06-06 11:37 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, David Lechner,
Nuno Sá, Andy Shevchenko, Archana Patni, Song Hongyan
Cc: linux-input, linux-iio, linux-kernel, srinivas pandruvada,
Sanjay Chitroda
In-Reply-To: <20260606-5-june-hid-iio-race-fixes-v1-0-27a848c5758f@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
The driver registers the IIO device before completing sensor hub
callback registration and unregisters callbacks while the IIO device
is still exposed during teardown.
This creates race windows in both probe and remove paths, which can
lead to NULL pointer dereferences or use-after-free.
Fix this by correct ordering of callback registration and
IIO device registration in probe and remove paths.
Fixes: 098d3beccfb6 ("iio: hid-sensors: Added Inclinometer 3D")
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
---
drivers/iio/orientation/hid-sensor-incl-3d.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/iio/orientation/hid-sensor-incl-3d.c b/drivers/iio/orientation/hid-sensor-incl-3d.c
index 4e23a598a3fb..5e3d2bb9b5bf 100644
--- a/drivers/iio/orientation/hid-sensor-incl-3d.c
+++ b/drivers/iio/orientation/hid-sensor-incl-3d.c
@@ -356,12 +356,6 @@ static int hid_incl_3d_probe(struct platform_device *pdev)
return ret;
}
- ret = iio_device_register(indio_dev);
- if (ret) {
- dev_err(&pdev->dev, "device register failed\n");
- goto error_remove_trigger;
- }
-
incl_state->callbacks.send_event = incl_3d_proc_event;
incl_state->callbacks.capture_sample = incl_3d_capture_sample;
incl_state->callbacks.pdev = pdev;
@@ -370,13 +364,19 @@ static int hid_incl_3d_probe(struct platform_device *pdev)
&incl_state->callbacks);
if (ret) {
dev_err(&pdev->dev, "callback reg failed\n");
- goto error_iio_unreg;
+ goto error_remove_trigger;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "device register failed\n");
+ goto error_remove_callback;
}
return 0;
-error_iio_unreg:
- iio_device_unregister(indio_dev);
+error_remove_callback:
+ sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_INCLINOMETER_3D);
error_remove_trigger:
hid_sensor_remove_trigger(indio_dev, &incl_state->common_attributes);
return ret;
@@ -389,8 +389,8 @@ static void hid_incl_3d_remove(struct platform_device *pdev)
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct incl_3d_state *incl_state = iio_priv(indio_dev);
- sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_INCLINOMETER_3D);
iio_device_unregister(indio_dev);
+ sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_INCLINOMETER_3D);
hid_sensor_remove_trigger(indio_dev, &incl_state->common_attributes);
}
--
2.34.1
^ permalink raw reply related
* [PATCH 1/9] iio: orientation: hid-sensor-rotation: Fix race between callback registration and device exposure
From: Sanjay Chitroda @ 2026-06-06 11:37 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, David Lechner,
Nuno Sá, Andy Shevchenko, Archana Patni, Song Hongyan
Cc: linux-input, linux-iio, linux-kernel, srinivas pandruvada,
Sanjay Chitroda
In-Reply-To: <20260606-5-june-hid-iio-race-fixes-v1-0-27a848c5758f@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
The driver registers the IIO device before completing sensor hub
callback registration and unregisters callbacks while the IIO device
is still exposed during teardown.
This creates race windows in both probe and remove paths, which can
lead to NULL pointer dereferences or use-after-free.
Fix this by correct ordering of callback registration and
IIO device registration in probe and remove paths.
Fixes: fc18dddc0625 ("iio: hid-sensors: Added device rotation support")
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
---
drivers/iio/orientation/hid-sensor-rotation.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/iio/orientation/hid-sensor-rotation.c b/drivers/iio/orientation/hid-sensor-rotation.c
index 4a11e4555099..fd9beb93cefb 100644
--- a/drivers/iio/orientation/hid-sensor-rotation.c
+++ b/drivers/iio/orientation/hid-sensor-rotation.c
@@ -332,12 +332,6 @@ static int hid_dev_rot_probe(struct platform_device *pdev)
return ret;
}
- ret = iio_device_register(indio_dev);
- if (ret) {
- dev_err(&pdev->dev, "device register failed\n");
- goto error_remove_trigger;
- }
-
rot_state->callbacks.send_event = dev_rot_proc_event;
rot_state->callbacks.capture_sample = dev_rot_capture_sample;
rot_state->callbacks.pdev = pdev;
@@ -345,13 +339,19 @@ static int hid_dev_rot_probe(struct platform_device *pdev)
&rot_state->callbacks);
if (ret) {
dev_err(&pdev->dev, "callback reg failed\n");
- goto error_iio_unreg;
+ goto error_remove_trigger;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "device register failed\n");
+ goto error_remove_callback;
}
return 0;
-error_iio_unreg:
- iio_device_unregister(indio_dev);
+error_remove_callback:
+ sensor_hub_remove_callback(hsdev, hsdev->usage);
error_remove_trigger:
hid_sensor_remove_trigger(indio_dev, &rot_state->common_attributes);
return ret;
@@ -364,8 +364,8 @@ static void hid_dev_rot_remove(struct platform_device *pdev)
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct dev_rot_state *rot_state = iio_priv(indio_dev);
- sensor_hub_remove_callback(hsdev, hsdev->usage);
iio_device_unregister(indio_dev);
+ sensor_hub_remove_callback(hsdev, hsdev->usage);
hid_sensor_remove_trigger(indio_dev, &rot_state->common_attributes);
}
--
2.34.1
^ permalink raw reply related
* [PATCH 0/9] HID: iio: Fix race condition between callback registration and device exposure
From: Sanjay Chitroda @ 2026-06-06 11:37 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, David Lechner,
Nuno Sá, Andy Shevchenko, Archana Patni, Song Hongyan
Cc: linux-input, linux-iio, linux-kernel, srinivas pandruvada,
Sanjay Chitroda
Hi all,
This series fixes a race condition in HID IIO drivers related to the
ordering between callback registration and device exposure.
Currently, several HID IIO drivers register the IIO device (making it
visible to userspace and other kernel consumers) before all required
callbacks and resources are fully initialized, or rely on devm-based
cleanup in a way that does not guarantee correct teardown ordering.
This creates a window where the device can be accessed while it is
not fully initialized or is being torn down, potentially leading NULL
dereference or use-after-free.
To address this, the series ensures that:
- All required callbacks and resources are set up before the device
is registered with the IIO core
- Resource cleanup is performed explicitly where ordering matters
PS: This is prepratory series to convert all HID IIO driver to devm.
Testing:
- Compiled with W=1 for each patch in series
- Build-tested on QEMU x86_64
---
Sanjay Chitroda (9):
iio: orientation: hid-sensor-rotation: Fix race between callback registration and device exposure
iio: orientation: hid-sensor-incl-3d: Fix race between callback registration and device exposure
iio: gyro: hid-sensor-gyro-3d: Fix race between callback registration and device exposure
iio: pressure: hid-sensor-press: Fix race between callback registration and device exposure
iio: temperature: hid-sensor-temperature: switch to non-devm iio_device_register()
iio: light: hid-sensor-prox: Fix race between callback registration and device exposure
iio: light: hid-sensor-als: Fix race between callback registration and device exposure
iio: magnetometer: hid-sensor-magn-3d: Fix race between callback registration and device exposure
iio: accel: hid-sensor-accel-3d: Fix race between callback registration and device exposure
drivers/iio/accel/hid-sensor-accel-3d.c | 20 ++++++++++----------
drivers/iio/gyro/hid-sensor-gyro-3d.c | 20 ++++++++++----------
drivers/iio/light/hid-sensor-als.c | 20 ++++++++++----------
drivers/iio/light/hid-sensor-prox.c | 20 ++++++++++----------
drivers/iio/magnetometer/hid-sensor-magn-3d.c | 20 ++++++++++----------
drivers/iio/orientation/hid-sensor-incl-3d.c | 20 ++++++++++----------
drivers/iio/orientation/hid-sensor-rotation.c | 20 ++++++++++----------
drivers/iio/pressure/hid-sensor-press.c | 20 ++++++++++----------
drivers/iio/temperature/hid-sensor-temperature.c | 3 ++-
9 files changed, 82 insertions(+), 81 deletions(-)
---
base-commit: ae696dfa47c30016cd429b9db5e70b259b8f509e
change-id: 20260605-5-june-hid-iio-race-fixes-f8b981f82b80
Best regards,
--
Sanjay Chitroda <sanjayembeddedse@gmail.com>
^ permalink raw reply
* [PATCH] HID: wacom: handle kmemdup failure in Bluetooth IRQ path
From: Ruoyu Wang @ 2026-06-06 4:03 UTC (permalink / raw)
To: Ping Cheng
Cc: Jason Gerecke, Jiri Kosina, Benjamin Tissoires, linux-input,
linux-kernel, Ruoyu Wang
wacom_intuos_bt_irq() duplicates the input report with kmemdup() and then
uses data[0] to dispatch the report type. If the allocation fails, the
switch statement dereferences a NULL pointer.
Handle allocation failure by dropping the report. This keeps the
interrupt path from dereferencing a NULL buffer while preserving the
existing return convention for ignored or malformed reports.
Signed-off-by: Ruoyu Wang <ruoyuw560@gmail.com>
---
drivers/hid/wacom_wac.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
index da1f0ea85625d..c42af15e7dba0 100644
--- a/drivers/hid/wacom_wac.c
+++ b/drivers/hid/wacom_wac.c
@@ -1206,6 +1206,9 @@ static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
int i = 1;
unsigned power_raw, battery_capacity, bat_charging, ps_connected;
+ if (!data)
+ return 0;
+
switch (data[0]) {
case 0x04:
if (len < 32) {
--
2.34.1
^ permalink raw reply related
* Re: [linux-6.1.y 1/3] HID: core: Add printk_ratelimited variants to hid_warn() etc
From: Sasha Levin @ 2026-06-05 19:37 UTC (permalink / raw)
To: lee, Jiri Kosina, Benjamin Tissoires, Filipe Laíns,
Bastien Nocera, Ping Cheng, Jason Gerecke, Viresh Kumar,
Johan Hovold, Alex Elder, Greg Kroah-Hartman, linux-input,
linux-kernel, greybus-dev, linux-staging
Cc: Sasha Levin, stable, Vicki Pfau, Jiri Kosina
In-Reply-To: <20260603163022.3301081-1-lee@kernel.org>
> [linux-6.1.y 1/3] HID: core: Add printk_ratelimited variants to hid_warn() etc
Whole series queued for 6.1.y, thanks.
--
Thanks,
Sasha
^ permalink raw reply
* Re: [linux-5.15.y 1/3] HID: core: Add printk_ratelimited variants to hid_warn() etc
From: Sasha Levin @ 2026-06-05 19:37 UTC (permalink / raw)
To: lee, Jiri Kosina, Benjamin Tissoires, Viresh Kumar, Johan Hovold,
Alex Elder, Greg Kroah-Hartman, linux-input, linux-kernel,
greybus-dev, linux-staging
Cc: Sasha Levin, stable, Vicki Pfau, Jiri Kosina
In-Reply-To: <20260604092659.3953067-1-lee@kernel.org>
> [linux-5.15.y 1/3] HID: core: Add printk_ratelimited variants to hid_warn() etc
Whole series queued for 5.15.y, thanks.
--
Thanks,
Sasha
^ permalink raw reply
* [PATCH v10] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
From: David Glushkov @ 2026-06-05 17:50 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, kernel test robot
In-Reply-To: <20260531215204.172030-1-david.glushkov@sntiq.com>
The MSI Raider A18 HX A9WJG exposes two internal SteelSeries USB HID
devices for RGB lighting: KLC (1038:1122) for the keyboard and ALC
(1038:1161) for the lightbar/logo zones.
Add DMI-gated support for these devices and expose them as multicolor
LED class devices. The driver sends the same HID class SET_REPORT
control transfer as the tested userspace implementation for this
machine and writes a uniform RGB value to all known keyboard keys or
ALC zones.
The ALC payload uses sparse LED IDs on this chassis: 0x00, 0x01,
0x02 and 0x03 are physical zones, while 0x04 and 0x05 do not appear
to map to physical LEDs. Unused payload LED ID slots are initialized
to 0xff so they are ignored by the controller instead of defaulting
to LED ID 0x00.
Limit RGB support to USB interface 0 and the tested DMI system because
the KLC product ID is shared across MSI laptop designs and the key
layout mapping is model-specific. If the DMI or interface check does
not match, keep the device bound as a regular HID device instead of
failing probe.
Also make the existing Arctis 9 vendor usage-page check defensive by
returning false for report descriptors shorter than three bytes before
inspecting hdev->rdesc[0..2].
Tested on MSI Raider A18 HX A9WJG. Both internal SteelSeries ALC
(1038:1161) and KLC (1038:1122) HID devices bind on interface 0 and
create steelseries::lightbar and steelseries::kbd_backlight. Setting
multi_intensity and brightness changes the keyboard and lightbar
colors.
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202606010709.X0QYNjFZ-lkp@intel.com/
Signed-off-by: David Glushkov <david.glushkov@sntiq.com>
---
v10:
- Guard steelseries_is_vendor_usage_page() against report descriptors
shorter than three bytes before reading hdev->rdesc[0..2].
- Keep the corrected ALC LED ID list at 0x00, 0x01, 0x02 and 0x03.
- Restore the full revision history below the commit message.
v9:
- Use only the confirmed physical ALC LED IDs: 0x00, 0x01,
0x02 and 0x03.
v8:
- Use only the confirmed physical ALC LED IDs: 0x00, 0x01,
0x02 and 0x05.
- Initialize unused MSI RGB payload LED IDs to 0xff before filling
active entries, so sparse ALC layouts do not leave trailing slots as
LED ID 0x00 and accidentally override Lightbar 1.
- Use named defines for the MSI RGB opcode/modes/wValue and document
the 524-byte report layout.
- Parenthesise the headset quirk-mask test in steelseries_probe().
- Use devm_mutex_init() for rgb_lock so it is destroyed after the LED
class device is unregistered, instead of mutex_init() plus a manual
mutex_destroy() in steelseries_remove().
v7:
- Use smp_store_release()/smp_load_acquire() when publishing and reading
battery_registered, so raw_event cannot observe the flag before the
delayed work initialization is visible.
v6:
- Fix W=1 build warning when CONFIG_LEDS_CLASS_MULTICOLOR is disabled by
moving steelseries_hid_to_usb_dev() into the multicolor LED code path.
v5:
- Drop pm_ret handling and ignore PM_HINT_NORMAL cleanup errors.
- Fix LED registration error handling to clean up rgb_buf on failure.
- Fix trailing whitespaces and formatting style nits.
- Update commit message to accurately reflect the DMI fallback behavior.
v4:
- Fix literal \n typo in C code.
- Remove unused steelseries_msi_rgb_free_buf from #else block.
- Do not fail probe on unsupported DMI/interface; clear MSI RGB quirk and
continue normal HID initialization.
- Do not fail probe when RGB LED registration fails; keep the HID device usable
without RGB LED support.
- Add explicit linux/slab.h include for kzalloc/kfree.
v3:
- Fix build failure (added missing err_close label to steelseries_probe).
- Fix DMA API violation (use kzalloc instead of devm_kzalloc for usb transfer buffer).
- Fix C syntax declaration-after-statement warning.
- Fix type confusion for SRWS1 (add early check in raw_event before hid_get_drvdata).
- Fix delayed_work crash (add battery_registered flag).
v2:
- Fixed unsafe to_usb_interface cast by checking hid_is_usb() first.
- Fixed uninitialized delayed_work warning by restricting cancel_delayed_work_sync to headset devices.
- Fixed error path leaks in probe (hid_hw_stop / hid_hw_close).
- Added hid_hw_power PM wrappers around direct usb_control_msg transfers.
---
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-steelseries.c | 313 +++++++++++++++++++++++++++++++++-
2 files changed, 306 insertions(+), 9 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 4657d96fb..4af4397b8 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1367,6 +1367,8 @@
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
+#define USB_DEVICE_ID_STEELSERIES_MSI_KLC 0x1122
+#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f98435631..73f77dd07 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -10,16 +10,30 @@
*/
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/slab.h>
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
#define STEELSERIES_ARCTIS_1 BIT(1)
#define STEELSERIES_ARCTIS_9 BIT(2)
+#define STEELSERIES_MSI_RGB BIT(3)
+
+#define STEELSERIES_MSI_RGB_WVALUE 0x0300 /* Feature report, ID 0 */
+#define STEELSERIES_MSI_RGB_REPORT_LEN 524
+#define STEELSERIES_MSI_RGB_OPCODE 0x0c
+#define STEELSERIES_MSI_RGB_KLC_MODE 0x66
+#define STEELSERIES_MSI_RGB_ALC_MODE 0x06
+
+#define STEELSERIES_HAS_LEDS_MULTICOLOR \
+ (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
struct steelseries_device {
struct hid_device *hdev;
@@ -34,6 +48,14 @@ struct steelseries_device {
uint8_t battery_capacity;
bool headset_connected;
bool battery_charging;
+ bool battery_registered;
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[3];
+ struct mutex rgb_lock; /* protects rgb_buf */
+ u8 *rgb_buf;
+#endif
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -510,6 +532,8 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
power_supply_powers(sd->battery, &sd->hdev->dev);
INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick);
+ /* Pairs with smp_load_acquire() in raw_event and remove paths */
+ smp_store_release(&sd->battery_registered, true);
steelseries_headset_fetch_battery(sd->hdev);
if (sd->quirks & STEELSERIES_ARCTIS_9) {
@@ -523,11 +547,230 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t usage_page)
{
+ if (hdev->rsize < 3)
+ return false;
+
return hdev->rdesc[0] == 0x06 &&
hdev->rdesc[1] == usage_page &&
hdev->rdesc[2] == 0xff;
}
+static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
+ },
+ },
+ { }
+};
+
+static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev)
+{
+ if (!hid_is_usb(hdev))
+ return NULL;
+
+ return to_usb_interface(hdev->dev.parent);
+}
+
+static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+ struct usb_device *udev;
+
+ if (!intf)
+ return false;
+
+ udev = interface_to_usbdev(intf);
+
+ return intf == usb_ifnum_to_if(udev, 0);
+}
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+
+static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+
+ if (!intf)
+ return NULL;
+
+ return interface_to_usbdev(intf);
+}
+
+static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct steelseries_device *sd = container_of(mc_cdev,
+ struct steelseries_device,
+ mc_cdev);
+ struct hid_device *hdev = sd->hdev;
+ struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
+ int i, ret;
+ u8 r, g, b;
+
+ static const u8 keys[] = {
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
+ 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
+ };
+ static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03 };
+
+ if (!udev)
+ return -ENODEV;
+
+ mutex_lock(&sd->rgb_lock);
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ r = mc_cdev->subled_info[0].brightness;
+ g = mc_cdev->subled_info[1].brightness;
+ b = mc_cdev->subled_info[2].brightness;
+
+ /*
+ * Report layout (524 bytes):
+ * Byte 0: Opcode (0x0c)
+ * Byte 1: 0x00
+ * Byte 2: Mode (0x66 for Keyboard, 0x06 for Lightbar)
+ * Byte 3: 0x00
+ * Bytes 4+: 4-byte chunks per LED (Index, R, G, B)
+ */
+ memset(sd->rgb_buf, 0, STEELSERIES_MSI_RGB_REPORT_LEN);
+ sd->rgb_buf[0] = STEELSERIES_MSI_RGB_OPCODE;
+ sd->rgb_buf[1] = 0x00;
+ sd->rgb_buf[3] = 0x00;
+
+ for (i = 0; i < (STEELSERIES_MSI_RGB_REPORT_LEN - 4) / 4; i++)
+ sd->rgb_buf[4 + i * 4] = 0xff;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
+ sd->rgb_buf[2] = STEELSERIES_MSI_RGB_KLC_MODE;
+ for (i = 0; i < ARRAY_SIZE(keys); i++) {
+ sd->rgb_buf[4 + i * 4] = keys[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ } else {
+ sd->rgb_buf[2] = STEELSERIES_MSI_RGB_ALC_MODE;
+ for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
+ sd->rgb_buf[4 + i * 4] = alc_zones[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ }
+
+ /*
+ * Send the vendor report verbatim with usb_control_msg(): byte 0 is a
+ * protocol opcode (0x0c), not a HID report ID, and the controller
+ * expects it under report ID 0 (wValue 0x0300). hid_hw_raw_request()
+ * would write the report number into byte 0, so the direct control
+ * transfer is used to keep the payload byte-identical to the tested
+ * userspace implementation.
+ */
+ ret = hid_hw_power(hdev, PM_HINT_FULLON);
+ if (ret < 0)
+ goto out_unlock;
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ HID_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ STEELSERIES_MSI_RGB_WVALUE, 0,
+ sd->rgb_buf, STEELSERIES_MSI_RGB_REPORT_LEN,
+ USB_CTRL_SET_TIMEOUT);
+
+ hid_hw_power(hdev, PM_HINT_NORMAL);
+
+out_unlock:
+ mutex_unlock(&sd->rgb_lock);
+ return ret < 0 ? ret : 0;
+}
+
+static void steelseries_msi_rgb_free_buf(void *data)
+{
+ kfree(data);
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ struct hid_device *hdev = sd->hdev;
+ struct led_classdev *led_cdev;
+ int ret;
+
+ sd->rgb_buf = kzalloc(STEELSERIES_MSI_RGB_REPORT_LEN, GFP_KERNEL);
+ if (!sd->rgb_buf)
+ return -ENOMEM;
+
+ ret = devm_add_action_or_reset(&hdev->dev,
+ steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ if (ret) {
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ ret = devm_mutex_init(&hdev->dev, &sd->rgb_lock);
+ if (ret) {
+ devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ kfree(sd->rgb_buf);
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ sd->subled_info[0].color_index = LED_COLOR_ID_RED;
+ sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ sd->subled_info[0].intensity = 255;
+ sd->subled_info[1].intensity = 255;
+ sd->subled_info[2].intensity = 255;
+ sd->subled_info[0].channel = 0;
+ sd->subled_info[1].channel = 1;
+ sd->subled_info[2].channel = 2;
+
+ sd->mc_cdev.subled_info = sd->subled_info;
+ sd->mc_cdev.num_colors = 3;
+
+ led_cdev = &sd->mc_cdev.led_cdev;
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
+ led_cdev->name = "steelseries::kbd_backlight";
+ else
+ led_cdev->name = "steelseries::lightbar";
+
+ led_cdev->max_brightness = 255;
+ led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
+
+ ret = devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
+ if (ret) {
+ devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ kfree(sd->rgb_buf);
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+#else
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ return -ENODEV;
+}
+#endif
+
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct steelseries_device *sd;
@@ -549,6 +792,14 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
sd->hdev = hdev;
sd->quirks = id->driver_data;
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
+ !steelseries_msi_rgb_is_interface0(hdev)) {
+ hid_dbg(hdev, "MSI RGB quirk not applicable, using generic HID path\n");
+ sd->quirks &= ~STEELSERIES_MSI_RGB;
+ }
+ }
+
ret = hid_parse(hdev);
if (ret)
return ret;
@@ -565,12 +816,28 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
ret = hid_hw_open(hdev);
if (ret)
- return ret;
+ goto err_stop;
+
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ ret = steelseries_msi_rgb_register(sd);
+ if (ret) {
+ hid_warn(hdev,
+ "Failed to register MSI RGB LEDs: %d, continuing without RGB support\n",
+ ret);
+ sd->quirks &= ~STEELSERIES_MSI_RGB;
+ }
+ return 0;
+ }
- if (steelseries_headset_battery_register(sd) < 0)
+ if ((sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9)) &&
+ steelseries_headset_battery_register(sd) < 0)
hid_err(sd->hdev,
"Failed to register battery for headset\n");
+ return 0;
+
+err_stop:
+ hid_hw_stop(hdev);
return ret;
}
@@ -588,12 +855,16 @@ static void steelseries_remove(struct hid_device *hdev)
}
sd = hid_get_drvdata(hdev);
+ if (!sd)
+ return;
spin_lock_irqsave(&sd->lock, flags);
sd->removed = true;
spin_unlock_irqrestore(&sd->lock, flags);
- cancel_delayed_work_sync(&sd->battery_work);
+ /* Pairs with smp_store_release() in steelseries_headset_battery_register() */
+ if (smp_load_acquire(&sd->battery_registered))
+ cancel_delayed_work_sync(&sd->battery_work);
hid_hw_close(hdev);
hid_hw_stop(hdev);
@@ -624,20 +895,34 @@ static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in
return (capacity - min_in) * 100 / (max_in - min_in);
}
+static bool steelseries_is_headset(struct hid_device *hdev)
+{
+ return hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+ hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9;
+}
+
static int steelseries_headset_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *read_buf,
int size)
{
- struct steelseries_device *sd = hid_get_drvdata(hdev);
- int capacity = sd->battery_capacity;
- bool connected = sd->headset_connected;
- bool charging = sd->battery_charging;
+ struct steelseries_device *sd;
+ int capacity;
+ bool connected;
+ bool charging;
unsigned long flags;
- /* Not a headset */
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ if (!steelseries_is_headset(hdev))
return 0;
+ sd = hid_get_drvdata(hdev);
+ /* Pairs with smp_store_release() in steelseries_headset_battery_register() */
+ if (!sd || !smp_load_acquire(&sd->battery_registered))
+ return 0;
+
+ capacity = sd->battery_capacity;
+ connected = sd->headset_connected;
+ charging = sd->battery_charging;
+
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
@@ -732,6 +1017,16 @@ static const struct hid_device_id steelseries_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = STEELSERIES_ARCTIS_9 },
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ { /* MSI Raider A18 KLC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_KLC),
+ .driver_data = STEELSERIES_MSI_RGB },
+
+ { /* MSI Raider A18 ALC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
+ .driver_data = STEELSERIES_MSI_RGB },
+#endif
+
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
--
2.54.0
^ permalink raw reply related
* Re: [REGRESSION] Sino Wealth 258a:002a keyboard enters stuck shift state on USB disconnect
From: Orlando Ulises Aguilar Rojas @ 2026-06-05 17:27 UTC (permalink / raw)
To: Michal Pecio
Cc: Benjamin Tissoires, linux-input, linux-usb, benjamin.tissoires
In-Reply-To: <20260605110003.435619d2.michal.pecio@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 1636 bytes --]
Hi Benjamin and Michal,
Thank you both for your time and insights.
@Benjamin:
I downgraded and tested on kernel v6.18.3 as requested.
As you strongly suspected, the exact same stuck-shift behavior occurs
here when the mouse is disconnected. The 7.0.7 behavior was indeed a
temporary regression that accidentally "fixed" my device's broken
firmware.
I completely agree that a HID-BPF program is the most elegant solution
here rather than adding a generic quirk for a badly coded device. I
will read the documentation you linked and start working on a BPF
program to discard these short reports from the spurious mouse node.
@Michal:
Your theory about bus perturbation makes a lot of sense. The spurious
mouse interface emitting a corrupted short report due to a hub
topology change aligns perfectly with the fact that the zero-fill path
now processes that garbage.
Regarding your question about unplugging the keyboard:
When I physically disconnect the Machenike keyboard after the bug
triggers, the OS input subsystem remains completely stuck holding the
Shift modifier. I have tested all possible plug/unplug permutations
using a completely different, secondary keyboard, and the result is
exactly the same: it continues to type shifted characters ("!@#"). The
kernel module retains this corrupted state system-wide, and absolutely
no physical replug combination resolves it; a system reboot is
strictly required.
I have captured a packet trace using tshark during the exact
unbind/remove event of the mouse. Since the mailing list drops binary
attachments, I have compressed and uploaded the PCAPNG file
Best regards,
Orlando
[-- Attachment #2: usb_crash_trace.pcapng.gz --]
[-- Type: application/gzip, Size: 195467 bytes --]
^ permalink raw reply
* Re: [PATCH v9] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
From: sashiko-bot @ 2026-06-05 16:54 UTC (permalink / raw)
To: David Glushkov; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260605164133.14354-1-david.glushkov@sntiq.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Potential out-of-bounds read in `steelseries_is_vendor_usage_page` when a malicious device provides a tiny report descriptor.
--
commit 45b36559149afdf0789fb9e7119bb71f74c98387
Author: David Glushkov <david.glushkov@sntiq.com>
HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
This commit adds DMI-gated support for the MSI Raider A18 HX A9WJG internal
SteelSeries USB HID devices for RGB lighting. It exposes them as multicolor
LED class devices and maps the ALC zones.
Closes: https://lore.kernel.org/oe-kbuild-all/202606010709.X0QYNjFZ-lkp@intel.com/
> diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
> index f98435631aa18..2eaa167455bba 100644
> --- a/drivers/hid/hid-steelseries.c
> +++ b/drivers/hid/hid-steelseries.c
[ ... ]
> @@ -528,6 +552,222 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
> hdev->rdesc[2] == 0xff;
> }
[Severity: High]
This is a pre-existing issue, but can this read out of bounds if a malicious
device provides a tiny report descriptor?
The function steelseries_is_vendor_usage_page() unconditionally reads the
first 3 bytes of the report descriptor (hdev->rdesc[0..2]) when handling an
Arctis 9 device. The kernel's HID parsing logic allows report descriptors of
size 1 or 2.
If a malicious USB device supplies a 1- or 2-byte descriptor, the dynamically
allocated hdev->rdesc will be exactly that size. Reading hdev->rdesc[2]
accesses memory past the allocation, which could result in a
slab-out-of-bounds read and panic the kernel.
Could we add a check to verify hdev->rsize >= 3 before accessing the array?
> +
> +static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260605164133.14354-1-david.glushkov@sntiq.com?part=1
^ permalink raw reply
* [PATCH v9] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
From: David Glushkov @ 2026-06-05 16:41 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, kernel test robot
In-Reply-To: <20260531215204.172030-1-david.glushkov@sntiq.com>
The MSI Raider A18 HX A9WJG exposes two internal SteelSeries USB HID
devices for RGB lighting: KLC (1038:1122) for the keyboard and ALC
(1038:1161) for the lightbar/logo zones.
Add DMI-gated support for these devices and expose them as multicolor
LED class devices. The driver sends the same HID class SET_REPORT
control transfer as the tested userspace implementation for this
machine and writes a uniform RGB value to all known keyboard keys or
ALC zones.
The ALC payload uses sparse LED IDs on this chassis: 0x00, 0x01,
0x02 and 0x03 are physical zones, while 0x04 and 0x05 do not appear
to map to physical LEDs. Unused payload LED ID slots are initialized
to 0xff so they are ignored by the controller instead of defaulting
to LED ID 0x00.
Limit RGB support to USB interface 0 and the tested DMI system because
the KLC product ID is shared across MSI laptop designs and the key
layout mapping is model-specific. If the DMI or interface check does
not match, keep the device bound as a regular HID device instead of
failing probe.
Tested on MSI Raider A18 HX A9WJG. Both internal SteelSeries ALC
(1038:1161) and KLC (1038:1122) HID devices bind on interface 0 and
create steelseries::lightbar and steelseries::kbd_backlight. Setting
multi_intensity and brightness changes the keyboard and lightbar
colors.
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202606010709.X0QYNjFZ-lkp@intel.com/
Signed-off-by: David Glushkov <david.glushkov@sntiq.com>
---
v9:
- Use only the confirmed physical ALC LED IDs: 0x00, 0x01,
0x02 and 0x03.
- Initialize unused MSI RGB payload LED IDs to 0xff before filling
active entries, so sparse ALC layouts do not leave trailing slots as
LED ID 0x00 and accidentally override Lightbar 1.
- Use named defines for the MSI RGB opcode/modes/wValue and document
the 524-byte report layout.
- Parenthesise the headset quirk-mask test in steelseries_probe().
- Use devm_mutex_init() for rgb_lock so it is destroyed after the LED
class device is unregistered, instead of mutex_init() plus a manual
mutex_destroy() in steelseries_remove().
v7:
- Use smp_store_release()/smp_load_acquire() when publishing and reading
battery_registered, so raw_event cannot observe the flag before the
delayed work initialization is visible.
v6:
- Fix W=1 build warning when CONFIG_LEDS_CLASS_MULTICOLOR is disabled by
moving steelseries_hid_to_usb_dev() into the multicolor LED code path.
v5:
- Drop pm_ret handling and ignore PM_HINT_NORMAL cleanup errors.
- Fix LED registration error handling to clean up rgb_buf on failure.
- Fix trailing whitespaces and formatting style nits.
- Update commit message to accurately reflect the DMI fallback behavior.
v4:
- Fix literal \n typo in C code.
- Remove unused steelseries_msi_rgb_free_buf from #else block.
- Do not fail probe on unsupported DMI/interface; clear MSI RGB quirk and
continue normal HID initialization.
- Do not fail probe when RGB LED registration fails; keep the HID device usable
without RGB LED support.
- Add explicit linux/slab.h include for kzalloc/kfree.
v3:
- Fix build failure (added missing err_close label to steelseries_probe).
- Fix DMA API violation (use kzalloc instead of devm_kzalloc for usb transfer buffer).
- Fix C syntax declaration-after-statement warning.
- Fix type confusion for SRWS1 (add early check in raw_event before hid_get_drvdata).
- Fix delayed_work crash (add battery_registered flag).
v2:
- Fixed unsafe to_usb_interface cast by checking hid_is_usb() first.
- Fixed uninitialized delayed_work warning by restricting cancel_delayed_work_sync to headset devices.
- Fixed error path leaks in probe (hid_hw_stop / hid_hw_close).
- Added hid_hw_power PM wrappers around direct usb_control_msg transfers.
---
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-steelseries.c | 310 +++++++++++++++++++++++++++++++++-
2 files changed, 303 insertions(+), 9 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 4657d96fb..4af4397b8 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1367,6 +1367,8 @@
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
+#define USB_DEVICE_ID_STEELSERIES_MSI_KLC 0x1122
+#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f98435631..01121739e 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -10,16 +10,30 @@
*/
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/slab.h>
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
#define STEELSERIES_ARCTIS_1 BIT(1)
#define STEELSERIES_ARCTIS_9 BIT(2)
+#define STEELSERIES_MSI_RGB BIT(3)
+
+#define STEELSERIES_MSI_RGB_WVALUE 0x0300 /* Feature report, ID 0 */
+#define STEELSERIES_MSI_RGB_REPORT_LEN 524
+#define STEELSERIES_MSI_RGB_OPCODE 0x0c
+#define STEELSERIES_MSI_RGB_KLC_MODE 0x66
+#define STEELSERIES_MSI_RGB_ALC_MODE 0x06
+
+#define STEELSERIES_HAS_LEDS_MULTICOLOR \
+ (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
struct steelseries_device {
struct hid_device *hdev;
@@ -34,6 +48,14 @@ struct steelseries_device {
uint8_t battery_capacity;
bool headset_connected;
bool battery_charging;
+ bool battery_registered;
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[3];
+ struct mutex rgb_lock; /* protects rgb_buf */
+ u8 *rgb_buf;
+#endif
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -510,6 +532,8 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
power_supply_powers(sd->battery, &sd->hdev->dev);
INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick);
+ /* Pairs with smp_load_acquire() in raw_event and remove paths */
+ smp_store_release(&sd->battery_registered, true);
steelseries_headset_fetch_battery(sd->hdev);
if (sd->quirks & STEELSERIES_ARCTIS_9) {
@@ -528,6 +552,222 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
hdev->rdesc[2] == 0xff;
}
+static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
+ },
+ },
+ { }
+};
+
+static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev)
+{
+ if (!hid_is_usb(hdev))
+ return NULL;
+
+ return to_usb_interface(hdev->dev.parent);
+}
+
+static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+ struct usb_device *udev;
+
+ if (!intf)
+ return false;
+
+ udev = interface_to_usbdev(intf);
+
+ return intf == usb_ifnum_to_if(udev, 0);
+}
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+
+static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+
+ if (!intf)
+ return NULL;
+
+ return interface_to_usbdev(intf);
+}
+
+static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct steelseries_device *sd = container_of(mc_cdev,
+ struct steelseries_device,
+ mc_cdev);
+ struct hid_device *hdev = sd->hdev;
+ struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
+ int i, ret;
+ u8 r, g, b;
+
+ static const u8 keys[] = {
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
+ 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
+ };
+ static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03 };
+
+ if (!udev)
+ return -ENODEV;
+
+ mutex_lock(&sd->rgb_lock);
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ r = mc_cdev->subled_info[0].brightness;
+ g = mc_cdev->subled_info[1].brightness;
+ b = mc_cdev->subled_info[2].brightness;
+
+ /*
+ * Report layout (524 bytes):
+ * Byte 0: Opcode (0x0c)
+ * Byte 1: 0x00
+ * Byte 2: Mode (0x66 for Keyboard, 0x06 for Lightbar)
+ * Byte 3: 0x00
+ * Bytes 4+: 4-byte chunks per LED (Index, R, G, B)
+ */
+ memset(sd->rgb_buf, 0, STEELSERIES_MSI_RGB_REPORT_LEN);
+ sd->rgb_buf[0] = STEELSERIES_MSI_RGB_OPCODE;
+ sd->rgb_buf[1] = 0x00;
+ sd->rgb_buf[3] = 0x00;
+
+ for (i = 0; i < (STEELSERIES_MSI_RGB_REPORT_LEN - 4) / 4; i++)
+ sd->rgb_buf[4 + i * 4] = 0xff;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
+ sd->rgb_buf[2] = STEELSERIES_MSI_RGB_KLC_MODE;
+ for (i = 0; i < ARRAY_SIZE(keys); i++) {
+ sd->rgb_buf[4 + i * 4] = keys[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ } else {
+ sd->rgb_buf[2] = STEELSERIES_MSI_RGB_ALC_MODE;
+ for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
+ sd->rgb_buf[4 + i * 4] = alc_zones[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ }
+
+ /*
+ * Send the vendor report verbatim with usb_control_msg(): byte 0 is a
+ * protocol opcode (0x0c), not a HID report ID, and the controller
+ * expects it under report ID 0 (wValue 0x0300). hid_hw_raw_request()
+ * would write the report number into byte 0, so the direct control
+ * transfer is used to keep the payload byte-identical to the tested
+ * userspace implementation.
+ */
+ ret = hid_hw_power(hdev, PM_HINT_FULLON);
+ if (ret < 0)
+ goto out_unlock;
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ HID_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ STEELSERIES_MSI_RGB_WVALUE, 0,
+ sd->rgb_buf, STEELSERIES_MSI_RGB_REPORT_LEN,
+ USB_CTRL_SET_TIMEOUT);
+
+ hid_hw_power(hdev, PM_HINT_NORMAL);
+
+out_unlock:
+ mutex_unlock(&sd->rgb_lock);
+ return ret < 0 ? ret : 0;
+}
+
+static void steelseries_msi_rgb_free_buf(void *data)
+{
+ kfree(data);
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ struct hid_device *hdev = sd->hdev;
+ struct led_classdev *led_cdev;
+ int ret;
+
+ sd->rgb_buf = kzalloc(STEELSERIES_MSI_RGB_REPORT_LEN, GFP_KERNEL);
+ if (!sd->rgb_buf)
+ return -ENOMEM;
+
+ ret = devm_add_action_or_reset(&hdev->dev,
+ steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ if (ret) {
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ ret = devm_mutex_init(&hdev->dev, &sd->rgb_lock);
+ if (ret) {
+ devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ kfree(sd->rgb_buf);
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ sd->subled_info[0].color_index = LED_COLOR_ID_RED;
+ sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ sd->subled_info[0].intensity = 255;
+ sd->subled_info[1].intensity = 255;
+ sd->subled_info[2].intensity = 255;
+ sd->subled_info[0].channel = 0;
+ sd->subled_info[1].channel = 1;
+ sd->subled_info[2].channel = 2;
+
+ sd->mc_cdev.subled_info = sd->subled_info;
+ sd->mc_cdev.num_colors = 3;
+
+ led_cdev = &sd->mc_cdev.led_cdev;
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
+ led_cdev->name = "steelseries::kbd_backlight";
+ else
+ led_cdev->name = "steelseries::lightbar";
+
+ led_cdev->max_brightness = 255;
+ led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
+
+ ret = devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
+ if (ret) {
+ devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ kfree(sd->rgb_buf);
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+#else
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ return -ENODEV;
+}
+#endif
+
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct steelseries_device *sd;
@@ -549,6 +789,14 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
sd->hdev = hdev;
sd->quirks = id->driver_data;
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
+ !steelseries_msi_rgb_is_interface0(hdev)) {
+ hid_dbg(hdev, "MSI RGB quirk not applicable, using generic HID path\n");
+ sd->quirks &= ~STEELSERIES_MSI_RGB;
+ }
+ }
+
ret = hid_parse(hdev);
if (ret)
return ret;
@@ -565,12 +813,28 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
ret = hid_hw_open(hdev);
if (ret)
- return ret;
+ goto err_stop;
+
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ ret = steelseries_msi_rgb_register(sd);
+ if (ret) {
+ hid_warn(hdev,
+ "Failed to register MSI RGB LEDs: %d, continuing without RGB support\n",
+ ret);
+ sd->quirks &= ~STEELSERIES_MSI_RGB;
+ }
+ return 0;
+ }
- if (steelseries_headset_battery_register(sd) < 0)
+ if ((sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9)) &&
+ steelseries_headset_battery_register(sd) < 0)
hid_err(sd->hdev,
"Failed to register battery for headset\n");
+ return 0;
+
+err_stop:
+ hid_hw_stop(hdev);
return ret;
}
@@ -588,12 +852,16 @@ static void steelseries_remove(struct hid_device *hdev)
}
sd = hid_get_drvdata(hdev);
+ if (!sd)
+ return;
spin_lock_irqsave(&sd->lock, flags);
sd->removed = true;
spin_unlock_irqrestore(&sd->lock, flags);
- cancel_delayed_work_sync(&sd->battery_work);
+ /* Pairs with smp_store_release() in steelseries_headset_battery_register() */
+ if (smp_load_acquire(&sd->battery_registered))
+ cancel_delayed_work_sync(&sd->battery_work);
hid_hw_close(hdev);
hid_hw_stop(hdev);
@@ -624,20 +892,34 @@ static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in
return (capacity - min_in) * 100 / (max_in - min_in);
}
+static bool steelseries_is_headset(struct hid_device *hdev)
+{
+ return hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+ hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9;
+}
+
static int steelseries_headset_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *read_buf,
int size)
{
- struct steelseries_device *sd = hid_get_drvdata(hdev);
- int capacity = sd->battery_capacity;
- bool connected = sd->headset_connected;
- bool charging = sd->battery_charging;
+ struct steelseries_device *sd;
+ int capacity;
+ bool connected;
+ bool charging;
unsigned long flags;
- /* Not a headset */
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ if (!steelseries_is_headset(hdev))
return 0;
+ sd = hid_get_drvdata(hdev);
+ /* Pairs with smp_store_release() in steelseries_headset_battery_register() */
+ if (!sd || !smp_load_acquire(&sd->battery_registered))
+ return 0;
+
+ capacity = sd->battery_capacity;
+ connected = sd->headset_connected;
+ charging = sd->battery_charging;
+
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
@@ -732,6 +1014,16 @@ static const struct hid_device_id steelseries_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = STEELSERIES_ARCTIS_9 },
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ { /* MSI Raider A18 KLC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_KLC),
+ .driver_data = STEELSERIES_MSI_RGB },
+
+ { /* MSI Raider A18 ALC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
+ .driver_data = STEELSERIES_MSI_RGB },
+#endif
+
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v4 4/7] dt-bindings: input: sun4i-lradc-keys: Add A100/A133 compatible
From: Conor Dooley @ 2026-06-05 16:11 UTC (permalink / raw)
To: Alexander Sverdlin
Cc: linux-arm-kernel, linux-sunxi, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Hans de Goede, Dmitry Torokhov, Andre Przywara, Jun Yan,
Lukas Schmid, J. Neuschäfer, Eric Biggers, Michal Simek,
Luca Weiss, Sven Peter, Maxime Ripard, devicetree, linux-kernel,
linux-input
In-Reply-To: <20260605070923.3045073-5-alexander.sverdlin@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v3 1/2] dt-bindings: input: focaltech,ft8112: Add focaltech,ft3d81 compatible
From: Krzysztof Kozlowski @ 2026-06-05 12:31 UTC (permalink / raw)
To: Pradyot Kumar Nayak
Cc: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Daniel Peng, Bjorn Andersson, Konrad Dybcio, Dmitry Baryshkov,
Abel Vesa, linux-arm-msm, linux-input, devicetree, linux-kernel,
Konrad Dybcio
In-Reply-To: <20260603-arm64-dts-glymur-crd-add-reset-gpio-to-v3-1-3453ef577bcf@oss.qualcomm.com>
On Wed, Jun 03, 2026 at 08:41:19AM +0530, Pradyot Kumar Nayak wrote:
> The Focaltech ft3d81 is fully compatible with the ft8112 i.e.
> it uses the same I2C-HID protocol and the same power-on/reset sequencing,
> DT nodes for boards carrying an ft3d81,can therefore bind to the existing
> ft8112 driver without any additional changes.
>
> Signed-off-by: Pradyot Kumar Nayak <pradyot.nayak@oss.qualcomm.com>
> ---
> Documentation/devicetree/bindings/input/focaltech,ft8112.yaml | 9 +++++++--
> 1 file changed, 7 insertions(+), 2 deletions(-)
>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply
* [PATCH v2] HID: i2c-hid: Refactor _DSM helper and add i2c-hid-acpi-prp0001 driver
From: 谢致邦 (XIE Zhibang) @ 2026-06-05 12:20 UTC (permalink / raw)
To: linux-input, hansg, dmitry.torokhov, bentiss, sashiko-bot
Cc: sashiko-reviews, 谢致邦 (XIE Zhibang),
Jiri Kosina, Mario Limonciello (AMD), Douglas Anderson,
linux-kernel
In-Reply-To: <20260605014101.1889E1F00893@smtp.kernel.org>
Move the _DSM call that gets the HID descriptor address from
i2c-hid-acpi.c into i2c-hid-acpi.h as a static inline so both the ACPI
and the new PRP0001 driver can use it. While refactoring, move the
blacklist check and the _DSM call to the top of probe() to avoid a
pointless alloc when the device is blacklisted or does not implement the
_DSM.
Some devices, for example the Lenovo KaiTian N60d and Inspur CP300L3,
are declared with _HID "PRP0001" and _DSD compatible "hid-over-i2c" but
lack "hid-descr-addr" from the _DSD and provide the HID descriptor
address only through an ACPI _DSM. The OF driver fails to probe them
because it requires hid-descr-addr. Add a new driver that handles these
devices by calling the shared _DSM helper.
Fixes: b33752c30023 ("HID: i2c-hid: Reorganize so ACPI and OF are separate modules")
Signed-off-by: 谢致邦 (XIE Zhibang) <Yeking@Red54.com>
---
v2: Name the unused parameter and document why
acpi_device_fix_up_power() is skipped.
drivers/hid/i2c-hid/Kconfig | 18 ++++
drivers/hid/i2c-hid/Makefile | 1 +
drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c | 96 ++++++++++++++++++++++
drivers/hid/i2c-hid/i2c-hid-acpi.c | 48 +++--------
drivers/hid/i2c-hid/i2c-hid-acpi.h | 32 ++++++++
5 files changed, 159 insertions(+), 36 deletions(-)
create mode 100644 drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c
create mode 100644 drivers/hid/i2c-hid/i2c-hid-acpi.h
diff --git a/drivers/hid/i2c-hid/Kconfig b/drivers/hid/i2c-hid/Kconfig
index e8d51f410cc1..7db8b2abff78 100644
--- a/drivers/hid/i2c-hid/Kconfig
+++ b/drivers/hid/i2c-hid/Kconfig
@@ -22,6 +22,24 @@ config I2C_HID_ACPI
will be called i2c-hid-acpi. It will also build/depend on the
module i2c-hid.
+config I2C_HID_ACPI_PRP0001
+ tristate "Driver for PRP0001 devices missing hid-descr-addr"
+ depends on ACPI
+ depends on DRM || !DRM
+ select I2C_HID_CORE
+ help
+ Say Y here if you want support for I2C HID touchpads that
+ are declared with _HID "PRP0001" and _DSD compatible
+ "hid-over-i2c" but lack the "hid-descr-addr" property from
+ the _DSD. The HID descriptor address is instead provided
+ through an ACPI _DSM. Known affected devices include the
+ Lenovo KaiTian N60d and Inspur CP300L3.
+
+ If unsure, say N.
+
+ This support is also available as a module. If so, the
+ module will be called i2c-hid-acpi-prp0001.
+
config I2C_HID_OF
tristate "HID over I2C transport layer Open Firmware driver"
# No "depends on OF" because this can also be used for manually
diff --git a/drivers/hid/i2c-hid/Makefile b/drivers/hid/i2c-hid/Makefile
index 55bd5e0f35af..da420c1a8ec6 100644
--- a/drivers/hid/i2c-hid/Makefile
+++ b/drivers/hid/i2c-hid/Makefile
@@ -9,6 +9,7 @@ i2c-hid-objs = i2c-hid-core.o
i2c-hid-$(CONFIG_DMI) += i2c-hid-dmi-quirks.o
obj-$(CONFIG_I2C_HID_ACPI) += i2c-hid-acpi.o
+obj-$(CONFIG_I2C_HID_ACPI_PRP0001) += i2c-hid-acpi-prp0001.o
obj-$(CONFIG_I2C_HID_OF) += i2c-hid-of.o
obj-$(CONFIG_I2C_HID_OF_ELAN) += i2c-hid-of-elan.o
obj-$(CONFIG_I2C_HID_OF_GOODIX) += i2c-hid-of-goodix.o
diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c b/drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c
new file mode 100644
index 000000000000..b6b64e868f36
--- /dev/null
+++ b/drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HID over I2C driver for PRP0001 devices missing hid-descr-addr
+ *
+ * Some devices, for example the Lenovo KaiTian N60d and Inspur CP300L3, use
+ * _HID "PRP0001" with _DSD compatible "hid-over-i2c" but lack "hid-descr-addr"
+ * from the _DSD. The HID descriptor address is provided only through an ACPI
+ * _DSM. The TPD0 node in the DSDT shows _DSM Function 1 returning 0x20.
+ *
+ * Copyright (C) 2026 谢致邦 (XIE Zhibang) <Yeking@Red54.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+#include "i2c-hid.h"
+#include "i2c-hid-acpi.h"
+
+static int i2c_hid_acpi_prp0001_power_up(struct i2chid_ops *ops)
+{
+ /* give the device time to power up */
+ msleep(250);
+ return 0;
+}
+
+static struct i2chid_ops i2c_hid_acpi_prp0001_ops = {
+ .power_up = i2c_hid_acpi_prp0001_power_up,
+ /*
+ * No .restore_sequence needed: the _DSM on these devices returns a
+ * constant (0x20) with no side effects, unlike some PNP0C50 _DSM
+ * implementations that switch the hardware between PS/2 and I2C modes.
+ */
+};
+
+static int i2c_hid_acpi_prp0001_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct acpi_device *adev;
+ u16 hid_descriptor_address;
+ int ret;
+
+ /* If hid-descr-addr is present, let i2c-hid-of handle it */
+ if (device_property_present(dev, "hid-descr-addr"))
+ return -ENODEV;
+
+ adev = ACPI_COMPANION(dev);
+ if (!adev)
+ return -ENODEV;
+
+ ret = i2c_hid_acpi_get_descriptor(adev);
+ if (ret < 0)
+ return ret;
+ hid_descriptor_address = ret;
+
+ /*
+ * No acpi_device_fix_up_power() needed: TPD0 has no _PS0, _PS3, _PSC
+ * or _PRx methods and follows I2C bus power.
+ */
+ return i2c_hid_core_probe(client, &i2c_hid_acpi_prp0001_ops,
+ hid_descriptor_address, 0);
+}
+
+static const struct of_device_id i2c_hid_acpi_prp0001_of_match[] = {
+ { .compatible = "hid-over-i2c" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, i2c_hid_acpi_prp0001_of_match);
+
+static const struct i2c_device_id i2c_hid_acpi_prp0001_id[] = {
+ { .name = "hid-over-i2c" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, i2c_hid_acpi_prp0001_id);
+
+static struct i2c_driver i2c_hid_acpi_prp0001_driver = {
+ .driver = {
+ .name = "i2c_hid_acpi_prp0001",
+ .pm = &i2c_hid_core_pm,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .of_match_table = of_match_ptr(i2c_hid_acpi_prp0001_of_match),
+ },
+
+ .probe = i2c_hid_acpi_prp0001_probe,
+ .remove = i2c_hid_core_remove,
+ .shutdown = i2c_hid_core_shutdown,
+ .id_table = i2c_hid_acpi_prp0001_id,
+};
+
+module_i2c_driver(i2c_hid_acpi_prp0001_driver);
+
+MODULE_DESCRIPTION("HID over I2C driver for PRP0001 devices missing hid-descr-addr");
+MODULE_AUTHOR("谢致邦 (XIE Zhibang) <Yeking@Red54.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c
index abd700a101f4..f0bcfb2663ef 100644
--- a/drivers/hid/i2c-hid/i2c-hid-acpi.c
+++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c
@@ -25,9 +25,9 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pm.h>
-#include <linux/uuid.h>
#include "i2c-hid.h"
+#include "i2c-hid-acpi.h"
struct i2c_hid_acpi {
struct i2chid_ops ops;
@@ -48,39 +48,11 @@ static const struct acpi_device_id i2c_hid_acpi_blacklist[] = {
{ }
};
-/* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */
-static guid_t i2c_hid_guid =
- GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
- 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);
-
-static int i2c_hid_acpi_get_descriptor(struct i2c_hid_acpi *ihid_acpi)
-{
- struct acpi_device *adev = ihid_acpi->adev;
- acpi_handle handle = acpi_device_handle(adev);
- union acpi_object *obj;
- u16 hid_descriptor_address;
-
- if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0)
- return -ENODEV;
-
- obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid, 1, 1, NULL,
- ACPI_TYPE_INTEGER);
- if (!obj) {
- acpi_handle_err(handle, "Error _DSM call to get HID descriptor address failed\n");
- return -ENODEV;
- }
-
- hid_descriptor_address = obj->integer.value;
- ACPI_FREE(obj);
-
- return hid_descriptor_address;
-}
-
static void i2c_hid_acpi_restore_sequence(struct i2chid_ops *ops)
{
struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops);
- i2c_hid_acpi_get_descriptor(ihid_acpi);
+ i2c_hid_acpi_get_descriptor(ihid_acpi->adev);
}
static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops)
@@ -93,23 +65,27 @@ static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops)
static int i2c_hid_acpi_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
+ struct acpi_device *adev = ACPI_COMPANION(dev);
struct i2c_hid_acpi *ihid_acpi;
u16 hid_descriptor_address;
int ret;
+ if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0)
+ return -ENODEV;
+
+ ret = i2c_hid_acpi_get_descriptor(adev);
+ if (ret < 0)
+ return ret;
+ hid_descriptor_address = ret;
+
ihid_acpi = devm_kzalloc(&client->dev, sizeof(*ihid_acpi), GFP_KERNEL);
if (!ihid_acpi)
return -ENOMEM;
- ihid_acpi->adev = ACPI_COMPANION(dev);
+ ihid_acpi->adev = adev;
ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
ihid_acpi->ops.restore_sequence = i2c_hid_acpi_restore_sequence;
- ret = i2c_hid_acpi_get_descriptor(ihid_acpi);
- if (ret < 0)
- return ret;
- hid_descriptor_address = ret;
-
acpi_device_fix_up_power(ihid_acpi->adev);
return i2c_hid_core_probe(client, &ihid_acpi->ops,
diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.h b/drivers/hid/i2c-hid/i2c-hid-acpi.h
new file mode 100644
index 000000000000..8cebbeebcc23
--- /dev/null
+++ b/drivers/hid/i2c-hid/i2c-hid-acpi.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef _I2C_HID_ACPI_H
+#define _I2C_HID_ACPI_H
+
+#include <linux/acpi.h>
+#include <linux/uuid.h>
+
+static inline int i2c_hid_acpi_get_descriptor(struct acpi_device *adev)
+{
+ /* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */
+ static const guid_t i2c_hid_guid =
+ GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
+ 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);
+
+ acpi_handle handle = acpi_device_handle(adev);
+ union acpi_object *obj;
+ u16 addr;
+
+ obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid,
+ 1, 1, NULL, ACPI_TYPE_INTEGER);
+ if (!obj) {
+ acpi_handle_err(handle, "Error _DSM call to get HID descriptor address failed\n");
+ return -ENODEV;
+ }
+
+ addr = obj->integer.value;
+ ACPI_FREE(obj);
+ return addr;
+}
+
+#endif
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] HID: usbhid: skip interrupt IN polling for devices with no input reports
From: Antheas Kapenekakis @ 2026-06-05 12:02 UTC (permalink / raw)
To: Ahmed Yaseen
Cc: Jiri Kosina, Benjamin Tissoires, Denis Benato, Ilpo Järvinen,
Kerim Kabirov, GameBurrow, linux-usb, linux-input, linux-kernel
In-Reply-To: <20260605113952.38435-1-yaseen@ghoul.dev>
On Fri, 5 Jun 2026 at 13:40, Ahmed Yaseen <yaseen@ghoul.dev> wrote:
>
> usbhid starts polling a device's interrupt IN endpoint on open
> (usbhid_open() -> hid_start_in()). If the report descriptor declares no
> input reports there is nothing to read there, so the poll is useless,
> and on some composite devices it is also harmful.
If it did have input reports, would starting the polling still cause
issues? Because if it would, the issue is in the polling itself.
Given the creativity of manufacturers when implementing hid protocols,
I find it certain that they do use the in endpoint even without input
reports. E.g., for feature reports. This could cause regressions.
> The ASUS ROG N-Key keyboards expose a second, input-less interface used
> only for RGB control via feature reports. Opening its hidraw node (any
> hidraw reader does, including SDL/Steam Input or a plain cat) starts the
cating a hidraw causing issues would be expected, so let's focus on the former.
Asusctl has a bug where if you add the quirk that separates the event
nodes per hid, this bug is reproduced as well. I chucked it to
complicated threading getting out of control. It is the reason we
skipped that patch that was in my series.
Now, you say SDL/Steam do a spurious read as well, can you identify
the codepath so we can look into it? What devices are affected? The
early return fixes a warning on the Z13, but it also feeds through the
universal lamp interface on the new Xbox Allies. Is this a bug on
those devices or keyboards? If yes, it could be caused by userspace
hanging on that node
Antheas
> pointless IN poll and keypress reports on the keyboard interface get
> dropped for as long as the node stays open: a lost key-down drops a
> letter, a lost key-up leaves the key stuck. usbmon shows the dropped
> reports never reach the URB layer.
>
> The useless poll itself is long-standing; commit 4ac74ea68f64 ("HID:
> asus: early return for ROG devices") is what exposes it on these
> devices by keeping the input-less interface alive instead of ejecting
> it, so its hidraw node can be opened and the poll started.
>
> Skip the poll in usbhid_open() when the device has no input reports.
> Feature reports and hidraw output keep working over the control and OUT
> endpoints, so the interface is otherwise unaffected.
>
> Fixes: 4ac74ea68f64 ("HID: asus: early return for ROG devices")
> Tested-by: Kerim Kabirov <the.privat33r+linux@pm.me>
> Tested-by: GameBurrow <gameburrow@pm.me>
> Signed-off-by: Ahmed Yaseen <yaseen@ghoul.dev>
> ---
> drivers/hid/usbhid/hid-core.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
> index 96b0181cf819..90a8b34d9305 100644
> --- a/drivers/hid/usbhid/hid-core.c
> +++ b/drivers/hid/usbhid/hid-core.c
> @@ -688,7 +688,8 @@ static int usbhid_open(struct hid_device *hid)
>
> set_bit(HID_OPENED, &usbhid->iofl);
>
> - if (hid->quirks & HID_QUIRK_ALWAYS_POLL) {
> + if ((hid->quirks & HID_QUIRK_ALWAYS_POLL) ||
> + list_empty(&hid->report_enum[HID_INPUT_REPORT].report_list)) {
> res = 0;
> goto Done;
> }
> --
> 2.54.0
>
>
>
^ permalink raw reply
* [PATCH] HID: usbhid: skip interrupt IN polling for devices with no input reports
From: Ahmed Yaseen @ 2026-06-05 11:40 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Denis Benato, Antheas Kapenekakis, Ilpo Järvinen,
Kerim Kabirov, GameBurrow, linux-usb, linux-input, linux-kernel,
Ahmed Yaseen
usbhid starts polling a device's interrupt IN endpoint on open
(usbhid_open() -> hid_start_in()). If the report descriptor declares no
input reports there is nothing to read there, so the poll is useless,
and on some composite devices it is also harmful.
The ASUS ROG N-Key keyboards expose a second, input-less interface used
only for RGB control via feature reports. Opening its hidraw node (any
hidraw reader does, including SDL/Steam Input or a plain cat) starts the
pointless IN poll and keypress reports on the keyboard interface get
dropped for as long as the node stays open: a lost key-down drops a
letter, a lost key-up leaves the key stuck. usbmon shows the dropped
reports never reach the URB layer.
The useless poll itself is long-standing; commit 4ac74ea68f64 ("HID:
asus: early return for ROG devices") is what exposes it on these
devices by keeping the input-less interface alive instead of ejecting
it, so its hidraw node can be opened and the poll started.
Skip the poll in usbhid_open() when the device has no input reports.
Feature reports and hidraw output keep working over the control and OUT
endpoints, so the interface is otherwise unaffected.
Fixes: 4ac74ea68f64 ("HID: asus: early return for ROG devices")
Tested-by: Kerim Kabirov <the.privat33r+linux@pm.me>
Tested-by: GameBurrow <gameburrow@pm.me>
Signed-off-by: Ahmed Yaseen <yaseen@ghoul.dev>
---
drivers/hid/usbhid/hid-core.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
index 96b0181cf819..90a8b34d9305 100644
--- a/drivers/hid/usbhid/hid-core.c
+++ b/drivers/hid/usbhid/hid-core.c
@@ -688,7 +688,8 @@ static int usbhid_open(struct hid_device *hid)
set_bit(HID_OPENED, &usbhid->iofl);
- if (hid->quirks & HID_QUIRK_ALWAYS_POLL) {
+ if ((hid->quirks & HID_QUIRK_ALWAYS_POLL) ||
+ list_empty(&hid->report_enum[HID_INPUT_REPORT].report_list)) {
res = 0;
goto Done;
}
--
2.54.0
^ permalink raw reply related
* Re: [linux-6.6.y 1/3] HID: core: Add printk_ratelimited variants to hid_warn() etc
From: patchwork-bot+netdevbpf @ 2026-06-05 10:05 UTC (permalink / raw)
To: Lee Jones
Cc: jikos, benjamin.tissoires, lains, hadess, ping.cheng,
jason.gerecke, vireshk, johan, elder, gregkh, sashal, linux-input,
linux-kernel, greybus-dev, linux-staging, bpf, stable, vi,
jkosina
In-Reply-To: <20260603133140.3069226-1-lee@kernel.org>
Hello:
This series was applied to bpf/bpf-next.git (net)
by Linus Torvalds <torvalds@linux-foundation.org>:
On Wed, 3 Jun 2026 14:31:25 +0100 you wrote:
> From: Vicki Pfau <vi@endrift.com>
>
> hid_warn_ratelimited() is needed. Add the others as part of the block.
>
> Signed-off-by: Vicki Pfau <vi@endrift.com>
> Signed-off-by: Jiri Kosina <jkosina@suse.com>
> (cherry picked from commit 1d64624243af8329b4b219d8c39e28ea448f9929)
> Signed-off-by: Lee Jones <lee@kernel.org>
>
> [...]
Here is the summary with links:
- [linux-6.6.y,1/3] HID: core: Add printk_ratelimited variants to hid_warn() etc
(no matching commit)
- [linux-6.6.y,2/3] HID: pass the buffer size to hid_report_raw_event
(no matching commit)
- [linux-6.6.y,3/3] HID: core: Fix size_t specifier in hid_report_raw_event()
https://git.kernel.org/bpf/bpf-next/c/4d3a2a466b8d
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* Re: [REGRESSION] Sino Wealth 258a:002a keyboard enters stuck shift state on USB disconnect
From: Michal Pecio @ 2026-06-05 9:00 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: Orlando Ulises Aguilar Rojas, linux-input, linux-usb,
benjamin.tissoires
In-Reply-To: <aiJvV_DEOOFcqTtZ@beelink>
On Fri, 5 Jun 2026 09:03:42 +0200, Benjamin Tissoires wrote:
> > Trigger captured via udevadm monitor: when a separate Realtek
> > wireless mouse receiver fires unbind -> remove on the USB bus, the
> > Sino Wealth spurious mouse interface reacts to the hub topology
> > change, emits a short report, and the zero-fill behavior causes the
> > keyboard to output "!@#!@#" (Shift held).
>
> That is seriously wrong. It's not uncommon for keyboards to expose a
> mouse node when the support "macros" that you can configure in their
> firmware. That allows them to send button events, or even control the
> mouse.
>
> That being said, if your keyboard reacts to USB events from other
> devices, then there is something seriously wrong in the firmware.
>
> In any cases, the fix is not to revert the restored behavior, but to
> fix your bogus device.
It's practically impossible for a USB device to respond to disconnection
of another device, but I recall reports of disconnections apparently
causing corruption of other ongoing transactions. These should complete
with -EPIPE, -EPROTO or similar status then. Possibly the kernel or the
device doesn't handle things rigth.
Perhaps a packet trace from usbmon or Wireshark would help understand
what's going on.
> > Physically unplugging and replugging the
> > keyboard does not clear this state; a system reboot is required to
> > restore normal functionality.
When I disconnect a keyboard while holding the SHIFT key, it turns off.
Is there more to this bug than just failing to ignore bogus reports?
Regards,
Michal
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox