* [PATCH v7 1/8] HID: steelseries: Fix ARCTIS_1_X device mislabeling
2026-08-03 19:15 [PATCH v7 0/8] HID: steelseries: split out Arctis driver and add Nova 5X/Nova 7 support Sriman Achanta
@ 2026-08-03 19:15 ` Sriman Achanta
2026-08-03 19:15 ` [PATCH v7 2/8] HID: steelseries: Split Arctis headset driver into separate module Sriman Achanta
` (7 subsequent siblings)
8 siblings, 0 replies; 14+ messages in thread
From: Sriman Achanta @ 2026-08-03 19:15 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, Bastien Nocera, Simon Wood,
Christian Mayer, Sriman Achanta
The SteelSeries Arctis 1 Wireless for Xbox (0x12b6) was labelled as the
plain Arctis 1 Wireless. Rename USB_DEVICE_ID_STEELSERIES_ARCTIS_1 to
USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X, along with the matching quirk flag
and device table entry. The device ID value is unchanged.
Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
drivers/hid/hid-ids.h | 4 ++--
drivers/hid/hid-quirks.c | 2 +-
drivers/hid/hid-steelseries.c | 14 +++++++-------
3 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index b70f719b3b07..70b30e13b96f 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1377,8 +1377,8 @@
#define USB_VENDOR_ID_STEELSERIES 0x1038
#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_ARCTIS_1_X 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
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 57d8efdd9b89..f546179858c2 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -747,7 +747,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
#endif
#if IS_ENABLED(CONFIG_HID_STEELSERIES)
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
- { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9) },
#endif
#if IS_ENABLED(CONFIG_HID_SUNPLUS)
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index 73f77dd07110..54efa70d128d 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -21,7 +21,7 @@
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
-#define STEELSERIES_ARCTIS_1 BIT(1)
+#define STEELSERIES_ARCTIS_1_X BIT(1)
#define STEELSERIES_ARCTIS_9 BIT(2)
#define STEELSERIES_MSI_RGB BIT(3)
@@ -396,7 +396,7 @@ static void steelseries_headset_fetch_battery(struct hid_device *hdev)
{
int ret = 0;
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1)
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X)
ret = steelseries_headset_request_battery(hdev,
arctis_1_battery_request, sizeof(arctis_1_battery_request));
else if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9)
@@ -829,7 +829,7 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
return 0;
}
- if ((sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9)) &&
+ if ((sd->quirks & (STEELSERIES_ARCTIS_1_X | STEELSERIES_ARCTIS_9)) &&
steelseries_headset_battery_register(sd) < 0)
hid_err(sd->hdev,
"Failed to register battery for headset\n");
@@ -897,7 +897,7 @@ static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in
static bool steelseries_is_headset(struct hid_device *hdev)
{
- return hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+ return hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X ||
hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9;
}
@@ -923,7 +923,7 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
connected = sd->headset_connected;
charging = sd->battery_charging;
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
@@ -1010,8 +1010,8 @@ static const struct hid_device_id steelseries_devices[] = {
.driver_data = STEELSERIES_SRWS1 },
{ /* SteelSeries Arctis 1 Wireless for XBox */
- HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1),
- .driver_data = STEELSERIES_ARCTIS_1 },
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X),
+ .driver_data = STEELSERIES_ARCTIS_1_X },
{ /* SteelSeries Arctis 9 Wireless for XBox */
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v7 2/8] HID: steelseries: Split Arctis headset driver into separate module
2026-08-03 19:15 [PATCH v7 0/8] HID: steelseries: split out Arctis driver and add Nova 5X/Nova 7 support Sriman Achanta
2026-08-03 19:15 ` [PATCH v7 1/8] HID: steelseries: Fix ARCTIS_1_X device mislabeling Sriman Achanta
@ 2026-08-03 19:15 ` Sriman Achanta
2026-08-03 19:15 ` [PATCH v7 3/8] HID: steelseries: Refactor Arctis driver to use device_info framework Sriman Achanta
` (6 subsequent siblings)
8 siblings, 0 replies; 14+ messages in thread
From: Sriman Achanta @ 2026-08-03 19:15 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, Bastien Nocera, Simon Wood,
Christian Mayer, Sriman Achanta
Move all Arctis headset code (battery monitoring, wireless status,
power supply registration, raw event handling) from hid-steelseries.c
into the new hid-steelseries-arctis.c driver module.
hid-steelseries.c keeps the SRWS1 racing wheel and the MSI RGB LED
devices, while hid-steelseries-arctis.c handles the Arctis 1 (Xbox) and
Arctis 9 wireless headsets with their own device table, probe, remove,
and raw_event implementations.
Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
drivers/hid/Makefile | 2 +-
drivers/hid/hid-steelseries-arctis.c | 426 +++++++++++++++++++++++++++
drivers/hid/hid-steelseries.c | 366 +----------------------
3 files changed, 428 insertions(+), 366 deletions(-)
create mode 100644 drivers/hid/hid-steelseries-arctis.c
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 23e6e3dd0c56..4a172bd27b11 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -134,7 +134,7 @@ obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
obj-$(CONFIG_HID_SONY) += hid-sony.o
obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o
obj-$(CONFIG_HID_STEAM) += hid-steam.o
-obj-$(CONFIG_HID_STEELSERIES) += hid-steelseries.o
+obj-$(CONFIG_HID_STEELSERIES) += hid-steelseries.o hid-steelseries-arctis.o
obj-$(CONFIG_HID_SUNPLUS) += hid-sunplus.o
obj-$(CONFIG_HID_GREENASIA) += hid-gaff.o
obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o hid-thrustmaster.o
diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c
new file mode 100644
index 000000000000..6cae2e00bd8b
--- /dev/null
+++ b/drivers/hid/hid-steelseries-arctis.c
@@ -0,0 +1,426 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HID driver for Steelseries arctis headsets
+ *
+ * Copyright (c) 2023 Bastien Nocera
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#include "hid-ids.h"
+
+#define STEELSERIES_ARCTIS_1_X BIT(0)
+#define STEELSERIES_ARCTIS_9 BIT(1)
+
+struct steelseries_device {
+ struct hid_device *hdev;
+ unsigned long quirks;
+
+ struct delayed_work battery_work;
+ spinlock_t lock;
+ bool removed;
+
+ struct power_supply_desc battery_desc;
+ struct power_supply *battery;
+ uint8_t battery_capacity;
+ bool headset_connected;
+ bool battery_charging;
+ bool battery_registered;
+};
+
+#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
+
+#define ARCTIS_1_BATTERY_RESPONSE_LEN 8
+#define ARCTIS_9_BATTERY_RESPONSE_LEN 64
+static const char arctis_1_battery_request[] = { 0x06, 0x12 };
+static const char arctis_9_battery_request[] = { 0x00, 0x20 };
+
+static int steelseries_headset_request_battery(struct hid_device *hdev,
+ const char *request, size_t len)
+{
+ u8 *write_buf;
+ int ret;
+
+ /* Request battery information */
+ write_buf = kmemdup(request, len, GFP_KERNEL);
+ if (!write_buf)
+ return -ENOMEM;
+
+ hid_dbg(hdev, "Sending battery request report");
+ ret = hid_hw_raw_request(hdev, request[0], write_buf, len,
+ HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
+ if (ret < (int)len) {
+ hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
+ ret = -ENODATA;
+ }
+
+ kfree(write_buf);
+ return ret;
+}
+
+static void steelseries_headset_fetch_battery(struct hid_device *hdev)
+{
+ int ret = 0;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X)
+ ret = steelseries_headset_request_battery(hdev,
+ arctis_1_battery_request, sizeof(arctis_1_battery_request));
+ else if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9)
+ ret = steelseries_headset_request_battery(hdev,
+ arctis_9_battery_request, sizeof(arctis_9_battery_request));
+
+ if (ret < 0)
+ hid_dbg(hdev,
+ "Battery query failed (err: %d)\n", ret);
+}
+
+static int battery_capacity_to_level(int capacity)
+{
+ if (capacity >= 50)
+ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ if (capacity >= 20)
+ return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+}
+
+static void steelseries_headset_battery_timer_tick(struct work_struct *work)
+{
+ struct steelseries_device *sd = container_of(work,
+ struct steelseries_device, battery_work.work);
+ struct hid_device *hdev = sd->hdev;
+
+ steelseries_headset_fetch_battery(hdev);
+}
+
+#define STEELSERIES_PREFIX "SteelSeries "
+#define STEELSERIES_PREFIX_LEN strlen(STEELSERIES_PREFIX)
+
+static int steelseries_headset_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct steelseries_device *sd = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = sd->hdev->name;
+ while (!strncmp(val->strval, STEELSERIES_PREFIX, STEELSERIES_PREFIX_LEN))
+ val->strval += STEELSERIES_PREFIX_LEN;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "SteelSeries";
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ if (sd->headset_connected) {
+ val->intval = sd->battery_charging ?
+ POWER_SUPPLY_STATUS_CHARGING :
+ POWER_SUPPLY_STATUS_DISCHARGING;
+ } else
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = sd->battery_capacity;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ val->intval = battery_capacity_to_level(sd->battery_capacity);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static void
+steelseries_headset_set_wireless_status(struct hid_device *hdev,
+ bool connected)
+{
+ struct usb_interface *intf;
+
+ if (!hid_is_usb(hdev))
+ return;
+
+ intf = to_usb_interface(hdev->dev.parent);
+ usb_set_wireless_status(intf, connected ?
+ USB_WIRELESS_STATUS_CONNECTED :
+ USB_WIRELESS_STATUS_DISCONNECTED);
+}
+
+static enum power_supply_property steelseries_headset_battery_props[] = {
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_SCOPE,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+};
+
+static int steelseries_headset_battery_register(struct steelseries_device *sd)
+{
+ static atomic_t battery_no = ATOMIC_INIT(0);
+ struct power_supply_config battery_cfg = { .drv_data = sd, };
+ unsigned long n;
+ int ret;
+
+ sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ sd->battery_desc.properties = steelseries_headset_battery_props;
+ sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_headset_battery_props);
+ sd->battery_desc.get_property = steelseries_headset_battery_get_property;
+ sd->battery_desc.use_for_apm = 0;
+ n = atomic_inc_return(&battery_no) - 1;
+ sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
+ "steelseries_headset_battery_%ld", n);
+ if (!sd->battery_desc.name)
+ return -ENOMEM;
+
+ /* avoid the warning of 0% battery while waiting for the first info */
+ steelseries_headset_set_wireless_status(sd->hdev, false);
+ sd->battery_capacity = 100;
+ sd->battery_charging = false;
+
+ sd->battery = devm_power_supply_register(&sd->hdev->dev,
+ &sd->battery_desc, &battery_cfg);
+ if (IS_ERR(sd->battery)) {
+ ret = PTR_ERR(sd->battery);
+ hid_err(sd->hdev,
+ "%s:power_supply_register failed with error %d\n",
+ __func__, ret);
+ return ret;
+ }
+ 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) {
+ /* The first fetch_battery request can remain unanswered in some cases */
+ schedule_delayed_work(&sd->battery_work,
+ msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
+ }
+
+ return 0;
+}
+
+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 int steelseries_arctis_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct steelseries_device *sd;
+ int ret;
+
+ sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
+ if (!sd)
+ return -ENOMEM;
+ hid_set_drvdata(hdev, sd);
+ sd->hdev = hdev;
+ sd->quirks = id->driver_data;
+
+ ret = hid_parse(hdev);
+ if (ret)
+ return ret;
+
+ if (sd->quirks & STEELSERIES_ARCTIS_9 &&
+ !steelseries_is_vendor_usage_page(hdev, 0xc0))
+ return -ENODEV;
+
+ spin_lock_init(&sd->lock);
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret)
+ return ret;
+
+ ret = hid_hw_open(hdev);
+ if (ret)
+ goto err_stop;
+
+ if (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;
+}
+
+static void steelseries_arctis_remove(struct hid_device *hdev)
+{
+ struct steelseries_device *sd;
+ unsigned long flags;
+
+ sd = hid_get_drvdata(hdev);
+ if (!sd)
+ return;
+
+ spin_lock_irqsave(&sd->lock, flags);
+ sd->removed = true;
+ spin_unlock_irqrestore(&sd->lock, flags);
+
+ /* 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);
+}
+
+static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in, uint8_t max_in)
+{
+ if (capacity >= max_in)
+ return 100;
+ if (capacity <= min_in)
+ return 0;
+ return (capacity - min_in) * 100 / (max_in - min_in);
+}
+
+static int steelseries_arctis_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;
+ bool connected;
+ bool charging;
+ unsigned long flags;
+
+ /* 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_X) {
+ hid_dbg(sd->hdev,
+ "Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
+ if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
+ memcmp(read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_request))) {
+ if (!delayed_work_pending(&sd->battery_work))
+ goto request_battery;
+ return 0;
+ }
+ if (read_buf[2] == 0x01) {
+ connected = false;
+ capacity = 100;
+ } else {
+ connected = true;
+ capacity = read_buf[3];
+ }
+ }
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) {
+ hid_dbg(sd->hdev,
+ "Parsing raw event for Arctis 9 headset (%*ph)\n", size, read_buf);
+ if (size < ARCTIS_9_BATTERY_RESPONSE_LEN) {
+ if (!delayed_work_pending(&sd->battery_work))
+ goto request_battery;
+ return 0;
+ }
+
+ if (read_buf[0] == 0xaa && read_buf[1] == 0x01) {
+ connected = true;
+ charging = read_buf[4] == 0x01;
+
+ /*
+ * Found no official documentation about min and max.
+ * Values defined by testing.
+ */
+ capacity = steelseries_headset_map_capacity(read_buf[3], 0x68, 0x9d);
+ } else {
+ /*
+ * Device is off and sends the last known status read_buf[1] == 0x03 or
+ * there is no known status of the device read_buf[0] == 0x55
+ */
+ connected = false;
+ charging = false;
+ }
+ }
+
+ if (connected != sd->headset_connected) {
+ hid_dbg(sd->hdev,
+ "Connected status changed from %sconnected to %sconnected\n",
+ sd->headset_connected ? "" : "not ",
+ connected ? "" : "not ");
+ sd->headset_connected = connected;
+ steelseries_headset_set_wireless_status(hdev, connected);
+ }
+
+ if (capacity != sd->battery_capacity) {
+ hid_dbg(sd->hdev,
+ "Battery capacity changed from %d%% to %d%%\n",
+ sd->battery_capacity, capacity);
+ sd->battery_capacity = capacity;
+ power_supply_changed(sd->battery);
+ }
+
+ if (charging != sd->battery_charging) {
+ hid_dbg(sd->hdev,
+ "Battery charging status changed from %scharging to %scharging\n",
+ sd->battery_charging ? "" : "not ",
+ charging ? "" : "not ");
+ sd->battery_charging = charging;
+ power_supply_changed(sd->battery);
+ }
+
+request_battery:
+ spin_lock_irqsave(&sd->lock, flags);
+ if (!sd->removed)
+ schedule_delayed_work(&sd->battery_work,
+ msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
+ spin_unlock_irqrestore(&sd->lock, flags);
+
+ return 0;
+}
+
+static const struct hid_device_id steelseries_arctis_devices[] = {
+ { /* SteelSeries Arctis 1 Wireless for XBox */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X),
+ .driver_data = STEELSERIES_ARCTIS_1_X },
+
+ { /* SteelSeries Arctis 9 Wireless for XBox */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
+ .driver_data = STEELSERIES_ARCTIS_9 },
+
+ { }
+};
+MODULE_DEVICE_TABLE(hid, steelseries_arctis_devices);
+
+static struct hid_driver steelseries_arctis_driver = {
+ .name = "hid-steelseries-arctis",
+ .id_table = steelseries_arctis_devices,
+ .probe = steelseries_arctis_probe,
+ .remove = steelseries_arctis_remove,
+ .raw_event = steelseries_arctis_raw_event,
+};
+
+module_hid_driver(steelseries_arctis_driver);
+MODULE_DESCRIPTION("HID driver for Steelseries arctis headsets");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Christian Mayer <git@mayer-bgk.de>");
+MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index 54efa70d128d..7292da0313e1 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -3,7 +3,6 @@
* HID driver for Steelseries devices
*
* Copyright (c) 2013 Simon Wood
- * Copyright (c) 2023 Bastien Nocera
*/
/*
@@ -21,9 +20,7 @@
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
-#define STEELSERIES_ARCTIS_1_X BIT(1)
-#define STEELSERIES_ARCTIS_9 BIT(2)
-#define STEELSERIES_MSI_RGB BIT(3)
+#define STEELSERIES_MSI_RGB BIT(1)
#define STEELSERIES_MSI_RGB_WVALUE 0x0300 /* Feature report, ID 0 */
#define STEELSERIES_MSI_RGB_REPORT_LEN 524
@@ -39,17 +36,6 @@ struct steelseries_device {
struct hid_device *hdev;
unsigned long quirks;
- struct delayed_work battery_work;
- spinlock_t lock;
- bool removed;
-
- struct power_supply_desc battery_desc;
- struct power_supply *battery;
- 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];
@@ -362,199 +348,6 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
}
#endif
-#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
-
-#define ARCTIS_1_BATTERY_RESPONSE_LEN 8
-#define ARCTIS_9_BATTERY_RESPONSE_LEN 64
-static const char arctis_1_battery_request[] = { 0x06, 0x12 };
-static const char arctis_9_battery_request[] = { 0x00, 0x20 };
-
-static int steelseries_headset_request_battery(struct hid_device *hdev,
- const char *request, size_t len)
-{
- u8 *write_buf;
- int ret;
-
- /* Request battery information */
- write_buf = kmemdup(request, len, GFP_KERNEL);
- if (!write_buf)
- return -ENOMEM;
-
- hid_dbg(hdev, "Sending battery request report");
- ret = hid_hw_raw_request(hdev, request[0], write_buf, len,
- HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
- if (ret < (int)len) {
- hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
- ret = -ENODATA;
- }
-
- kfree(write_buf);
- return ret;
-}
-
-static void steelseries_headset_fetch_battery(struct hid_device *hdev)
-{
- int ret = 0;
-
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X)
- ret = steelseries_headset_request_battery(hdev,
- arctis_1_battery_request, sizeof(arctis_1_battery_request));
- else if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9)
- ret = steelseries_headset_request_battery(hdev,
- arctis_9_battery_request, sizeof(arctis_9_battery_request));
-
- if (ret < 0)
- hid_dbg(hdev,
- "Battery query failed (err: %d)\n", ret);
-}
-
-static int battery_capacity_to_level(int capacity)
-{
- if (capacity >= 50)
- return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
- if (capacity >= 20)
- return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
- return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
-}
-
-static void steelseries_headset_battery_timer_tick(struct work_struct *work)
-{
- struct steelseries_device *sd = container_of(work,
- struct steelseries_device, battery_work.work);
- struct hid_device *hdev = sd->hdev;
-
- steelseries_headset_fetch_battery(hdev);
-}
-
-#define STEELSERIES_PREFIX "SteelSeries "
-#define STEELSERIES_PREFIX_LEN strlen(STEELSERIES_PREFIX)
-
-static int steelseries_headset_battery_get_property(struct power_supply *psy,
- enum power_supply_property psp,
- union power_supply_propval *val)
-{
- struct steelseries_device *sd = power_supply_get_drvdata(psy);
- int ret = 0;
-
- switch (psp) {
- case POWER_SUPPLY_PROP_MODEL_NAME:
- val->strval = sd->hdev->name;
- while (!strncmp(val->strval, STEELSERIES_PREFIX, STEELSERIES_PREFIX_LEN))
- val->strval += STEELSERIES_PREFIX_LEN;
- break;
- case POWER_SUPPLY_PROP_MANUFACTURER:
- val->strval = "SteelSeries";
- break;
- case POWER_SUPPLY_PROP_PRESENT:
- val->intval = 1;
- break;
- case POWER_SUPPLY_PROP_STATUS:
- if (sd->headset_connected) {
- val->intval = sd->battery_charging ?
- POWER_SUPPLY_STATUS_CHARGING :
- POWER_SUPPLY_STATUS_DISCHARGING;
- } else
- val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
- break;
- case POWER_SUPPLY_PROP_SCOPE:
- val->intval = POWER_SUPPLY_SCOPE_DEVICE;
- break;
- case POWER_SUPPLY_PROP_CAPACITY:
- val->intval = sd->battery_capacity;
- break;
- case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
- val->intval = battery_capacity_to_level(sd->battery_capacity);
- break;
- default:
- ret = -EINVAL;
- break;
- }
- return ret;
-}
-
-static void
-steelseries_headset_set_wireless_status(struct hid_device *hdev,
- bool connected)
-{
- struct usb_interface *intf;
-
- if (!hid_is_usb(hdev))
- return;
-
- intf = to_usb_interface(hdev->dev.parent);
- usb_set_wireless_status(intf, connected ?
- USB_WIRELESS_STATUS_CONNECTED :
- USB_WIRELESS_STATUS_DISCONNECTED);
-}
-
-static enum power_supply_property steelseries_headset_battery_props[] = {
- POWER_SUPPLY_PROP_MODEL_NAME,
- POWER_SUPPLY_PROP_MANUFACTURER,
- POWER_SUPPLY_PROP_PRESENT,
- POWER_SUPPLY_PROP_STATUS,
- POWER_SUPPLY_PROP_SCOPE,
- POWER_SUPPLY_PROP_CAPACITY,
- POWER_SUPPLY_PROP_CAPACITY_LEVEL,
-};
-
-static int steelseries_headset_battery_register(struct steelseries_device *sd)
-{
- static atomic_t battery_no = ATOMIC_INIT(0);
- struct power_supply_config battery_cfg = { .drv_data = sd, };
- unsigned long n;
- int ret;
-
- sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
- sd->battery_desc.properties = steelseries_headset_battery_props;
- sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_headset_battery_props);
- sd->battery_desc.get_property = steelseries_headset_battery_get_property;
- sd->battery_desc.use_for_apm = 0;
- n = atomic_inc_return(&battery_no) - 1;
- sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
- "steelseries_headset_battery_%ld", n);
- if (!sd->battery_desc.name)
- return -ENOMEM;
-
- /* avoid the warning of 0% battery while waiting for the first info */
- steelseries_headset_set_wireless_status(sd->hdev, false);
- sd->battery_capacity = 100;
- sd->battery_charging = false;
-
- sd->battery = devm_power_supply_register(&sd->hdev->dev,
- &sd->battery_desc, &battery_cfg);
- if (IS_ERR(sd->battery)) {
- ret = PTR_ERR(sd->battery);
- hid_err(sd->hdev,
- "%s:power_supply_register failed with error %d\n",
- __func__, ret);
- return ret;
- }
- 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) {
- /* The first fetch_battery request can remain unanswered in some cases */
- schedule_delayed_work(&sd->battery_work,
- msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
- }
-
- return 0;
-}
-
-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 = {
@@ -804,12 +597,6 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
if (ret)
return ret;
- if (sd->quirks & STEELSERIES_ARCTIS_9 &&
- !steelseries_is_vendor_usage_page(hdev, 0xc0))
- return -ENODEV;
-
- spin_lock_init(&sd->lock);
-
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret)
return ret;
@@ -826,14 +613,8 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
ret);
sd->quirks &= ~STEELSERIES_MSI_RGB;
}
- return 0;
}
- if ((sd->quirks & (STEELSERIES_ARCTIS_1_X | STEELSERIES_ARCTIS_9)) &&
- steelseries_headset_battery_register(sd) < 0)
- hid_err(sd->hdev,
- "Failed to register battery for headset\n");
-
return 0;
err_stop:
@@ -843,9 +624,6 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
static void steelseries_remove(struct hid_device *hdev)
{
- struct steelseries_device *sd;
- unsigned long flags;
-
if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
@@ -854,18 +632,6 @@ static void steelseries_remove(struct hid_device *hdev)
return;
}
- sd = hid_get_drvdata(hdev);
- if (!sd)
- return;
-
- spin_lock_irqsave(&sd->lock, flags);
- sd->removed = true;
- spin_unlock_irqrestore(&sd->lock, flags);
-
- /* 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);
}
@@ -886,137 +652,10 @@ static const __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev,
return rdesc;
}
-static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in, uint8_t max_in)
-{
- if (capacity >= max_in)
- return 100;
- if (capacity <= min_in)
- return 0;
- 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_X ||
- 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;
- int capacity;
- bool connected;
- bool charging;
- unsigned long flags;
-
- 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_X) {
- hid_dbg(sd->hdev,
- "Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
- if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
- memcmp(read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_request))) {
- if (!delayed_work_pending(&sd->battery_work))
- goto request_battery;
- return 0;
- }
- if (read_buf[2] == 0x01) {
- connected = false;
- capacity = 100;
- } else {
- connected = true;
- capacity = read_buf[3];
- }
- }
-
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) {
- hid_dbg(sd->hdev,
- "Parsing raw event for Arctis 9 headset (%*ph)\n", size, read_buf);
- if (size < ARCTIS_9_BATTERY_RESPONSE_LEN) {
- if (!delayed_work_pending(&sd->battery_work))
- goto request_battery;
- return 0;
- }
-
- if (read_buf[0] == 0xaa && read_buf[1] == 0x01) {
- connected = true;
- charging = read_buf[4] == 0x01;
-
- /*
- * Found no official documentation about min and max.
- * Values defined by testing.
- */
- capacity = steelseries_headset_map_capacity(read_buf[3], 0x68, 0x9d);
- } else {
- /*
- * Device is off and sends the last known status read_buf[1] == 0x03 or
- * there is no known status of the device read_buf[0] == 0x55
- */
- connected = false;
- charging = false;
- }
- }
-
- if (connected != sd->headset_connected) {
- hid_dbg(sd->hdev,
- "Connected status changed from %sconnected to %sconnected\n",
- sd->headset_connected ? "" : "not ",
- connected ? "" : "not ");
- sd->headset_connected = connected;
- steelseries_headset_set_wireless_status(hdev, connected);
- }
-
- if (capacity != sd->battery_capacity) {
- hid_dbg(sd->hdev,
- "Battery capacity changed from %d%% to %d%%\n",
- sd->battery_capacity, capacity);
- sd->battery_capacity = capacity;
- power_supply_changed(sd->battery);
- }
-
- if (charging != sd->battery_charging) {
- hid_dbg(sd->hdev,
- "Battery charging status changed from %scharging to %scharging\n",
- sd->battery_charging ? "" : "not ",
- charging ? "" : "not ");
- sd->battery_charging = charging;
- power_supply_changed(sd->battery);
- }
-
-request_battery:
- spin_lock_irqsave(&sd->lock, flags);
- if (!sd->removed)
- schedule_delayed_work(&sd->battery_work,
- msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
- spin_unlock_irqrestore(&sd->lock, flags);
-
- return 0;
-}
-
static const struct hid_device_id steelseries_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1),
.driver_data = STEELSERIES_SRWS1 },
- { /* SteelSeries Arctis 1 Wireless for XBox */
- HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X),
- .driver_data = STEELSERIES_ARCTIS_1_X },
-
- { /* SteelSeries Arctis 9 Wireless for XBox */
- 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),
@@ -1037,12 +676,9 @@ static struct hid_driver steelseries_driver = {
.probe = steelseries_probe,
.remove = steelseries_remove,
.report_fixup = steelseries_srws1_report_fixup,
- .raw_event = steelseries_headset_raw_event,
};
module_hid_driver(steelseries_driver);
MODULE_DESCRIPTION("HID driver for Steelseries devices");
MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
MODULE_AUTHOR("Simon Wood <simon@mungewell.org>");
-MODULE_AUTHOR("Christian Mayer <git@mayer-bgk.de>");
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v7 3/8] HID: steelseries: Refactor Arctis driver to use device_info framework
2026-08-03 19:15 [PATCH v7 0/8] HID: steelseries: split out Arctis driver and add Nova 5X/Nova 7 support Sriman Achanta
2026-08-03 19:15 ` [PATCH v7 1/8] HID: steelseries: Fix ARCTIS_1_X device mislabeling Sriman Achanta
2026-08-03 19:15 ` [PATCH v7 2/8] HID: steelseries: Split Arctis headset driver into separate module Sriman Achanta
@ 2026-08-03 19:15 ` Sriman Achanta
2026-08-03 19:25 ` sashiko-bot
2026-08-03 19:15 ` [PATCH v7 4/8] HID: steelseries: Report POWER_SUPPLY_STATUS_FULL when full Sriman Achanta
` (5 subsequent siblings)
8 siblings, 1 reply; 14+ messages in thread
From: Sriman Achanta @ 2026-08-03 19:15 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, Bastien Nocera, Simon Wood,
Christian Mayer, Sriman Achanta
Replace the per-product if/else quirk bitmap with a
steelseries_device_info struct. Each model provides its capabilities,
sync_interface, and request_status/parse_status callbacks. Report
sending is folded into steelseries_send_report() and the feature and
output wrappers, and the battery identifiers lose their per-model names.
This is mostly a refactor, but it changes two things:
- Battery status is polled from a periodic delayed work (status_work)
instead of being requested from raw_event(). The interval stays at
3s.
- Arctis 1 no longer clamps the reported capacity to 100% while
disconnected. The connection state already controls how this is
shown to userspace.
ARCTIS_1_X and ARCTIS_9 keep working. No new devices are added.
Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
drivers/hid/hid-steelseries-arctis.c | 476 +++++++++++++++------------
1 file changed, 260 insertions(+), 216 deletions(-)
diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c
index 6cae2e00bd8b..c54c56db9ddd 100644
--- a/drivers/hid/hid-steelseries-arctis.c
+++ b/drivers/hid/hid-steelseries-arctis.c
@@ -3,6 +3,7 @@
* HID driver for Steelseries arctis headsets
*
* Copyright (c) 2023 Bastien Nocera
+ * Copyright (c) 2026 Sriman Achanta
*/
#include <linux/device.h>
@@ -15,71 +16,89 @@
#include "hid-ids.h"
-#define STEELSERIES_ARCTIS_1_X BIT(0)
-#define STEELSERIES_ARCTIS_9 BIT(1)
+#define SS_CAP_BATTERY BIT(0)
+
+struct steelseries_device;
+
+struct steelseries_device_info {
+ unsigned long capabilities;
+
+ u8 sync_interface;
+
+ int (*request_status)(struct hid_device *hdev);
+ void (*parse_status)(struct steelseries_device *sd, u8 *data, int size);
+};
struct steelseries_device {
struct hid_device *hdev;
- unsigned long quirks;
+ const struct steelseries_device_info *info;
- struct delayed_work battery_work;
- spinlock_t lock;
- bool removed;
+ struct delayed_work status_work;
struct power_supply_desc battery_desc;
struct power_supply *battery;
- uint8_t battery_capacity;
bool headset_connected;
+ u8 battery_capacity;
bool battery_charging;
- bool battery_registered;
-};
-#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
+ spinlock_t lock;
+ bool removed;
+};
-#define ARCTIS_1_BATTERY_RESPONSE_LEN 8
-#define ARCTIS_9_BATTERY_RESPONSE_LEN 64
-static const char arctis_1_battery_request[] = { 0x06, 0x12 };
-static const char arctis_9_battery_request[] = { 0x00, 0x20 };
+/*
+ * Headset report helpers
+ */
-static int steelseries_headset_request_battery(struct hid_device *hdev,
- const char *request, size_t len)
+static int steelseries_send_report(struct hid_device *hdev, const u8 *data,
+ int len, enum hid_report_type type)
{
- u8 *write_buf;
+ u8 *buf;
int ret;
- /* Request battery information */
- write_buf = kmemdup(request, len, GFP_KERNEL);
- if (!write_buf)
+ buf = kmemdup(data, len, GFP_KERNEL);
+ if (!buf)
return -ENOMEM;
- hid_dbg(hdev, "Sending battery request report");
- ret = hid_hw_raw_request(hdev, request[0], write_buf, len,
- HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
- if (ret < (int)len) {
- hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
- ret = -ENODATA;
- }
+ ret = hid_hw_raw_request(hdev, data[0], buf, len, type,
+ HID_REQ_SET_REPORT);
+ kfree(buf);
- kfree(write_buf);
- return ret;
+ if (ret < 0)
+ return ret;
+ if (ret < len)
+ return -EIO;
+
+ return 0;
}
-static void steelseries_headset_fetch_battery(struct hid_device *hdev)
+static inline int steelseries_send_output_report(struct hid_device *hdev,
+ const u8 *data, int len)
{
- int ret = 0;
+ return steelseries_send_report(hdev, data, len, HID_OUTPUT_REPORT);
+}
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X)
- ret = steelseries_headset_request_battery(hdev,
- arctis_1_battery_request, sizeof(arctis_1_battery_request));
- else if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9)
- ret = steelseries_headset_request_battery(hdev,
- arctis_9_battery_request, sizeof(arctis_9_battery_request));
+/*
+ * Headset status request functions
+ */
- if (ret < 0)
- hid_dbg(hdev,
- "Battery query failed (err: %d)\n", ret);
+static int steelseries_arctis_1_request_status(struct hid_device *hdev)
+{
+ const u8 data[] = { 0x06, 0x12 };
+
+ return steelseries_send_output_report(hdev, data, sizeof(data));
}
+static int steelseries_arctis_9_request_status(struct hid_device *hdev)
+{
+ const u8 data[] = { 0x00, 0x20 };
+
+ return steelseries_send_output_report(hdev, data, sizeof(data));
+}
+
+/*
+ * Headset battery helpers
+ */
+
static int battery_capacity_to_level(int capacity)
{
if (capacity >= 50)
@@ -89,30 +108,101 @@ static int battery_capacity_to_level(int capacity)
return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
}
-static void steelseries_headset_battery_timer_tick(struct work_struct *work)
+static u8 steelseries_map_capacity(u8 capacity, u8 min_in, u8 max_in)
{
- struct steelseries_device *sd = container_of(work,
- struct steelseries_device, battery_work.work);
- struct hid_device *hdev = sd->hdev;
+ if (capacity >= max_in)
+ return 100;
+ if (capacity <= min_in)
+ return 0;
+ return (capacity - min_in) * 100 / (max_in - min_in);
+}
+
+/*
+ * Headset status parse functions
+ */
- steelseries_headset_fetch_battery(hdev);
+static void steelseries_arctis_1_parse_status(struct steelseries_device *sd,
+ u8 *data, int size)
+{
+ /* Only the battery status report echoes the request header. */
+ if (size < 8 || data[0] != 0x06 || data[1] != 0x12)
+ return;
+
+ sd->headset_connected = (data[2] != 0x01);
+ sd->battery_capacity = data[3];
+}
+
+static void steelseries_arctis_9_parse_status(struct steelseries_device *sd,
+ u8 *data, int size)
+{
+ if (size < 5)
+ return;
+
+ if (data[0] == 0xaa && data[1] == 0x01) {
+ sd->headset_connected = true;
+ sd->battery_charging = (data[4] == 0x01);
+ sd->battery_capacity = steelseries_map_capacity(data[3], 0x68, 0x9d);
+ } else {
+ /* Device off: 0x55 (no status) or 0x03 (stale status). */
+ sd->headset_connected = false;
+ sd->battery_charging = false;
+ }
+}
+
+/*
+ * Device info definitions
+ */
+
+static const struct steelseries_device_info arctis_1_info = {
+ .sync_interface = 3,
+ .capabilities = SS_CAP_BATTERY,
+ .request_status = steelseries_arctis_1_request_status,
+ .parse_status = steelseries_arctis_1_parse_status,
+};
+
+static const struct steelseries_device_info arctis_9_info = {
+ .sync_interface = 0,
+ .capabilities = SS_CAP_BATTERY,
+ .request_status = steelseries_arctis_9_request_status,
+ .parse_status = steelseries_arctis_9_parse_status,
+};
+
+/*
+ * Headset wireless status and battery infrastructure
+ */
+
+#define STEELSERIES_HEADSET_STATUS_TIMEOUT_MS 3000
+
+static void
+steelseries_headset_set_wireless_status(struct hid_device *hdev,
+ bool connected)
+{
+ struct usb_interface *intf;
+
+ if (!hid_is_usb(hdev))
+ return;
+
+ intf = to_usb_interface(hdev->dev.parent);
+ usb_set_wireless_status(intf, connected ?
+ USB_WIRELESS_STATUS_CONNECTED :
+ USB_WIRELESS_STATUS_DISCONNECTED);
}
#define STEELSERIES_PREFIX "SteelSeries "
-#define STEELSERIES_PREFIX_LEN strlen(STEELSERIES_PREFIX)
-static int steelseries_headset_battery_get_property(struct power_supply *psy,
+static int steelseries_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct steelseries_device *sd = power_supply_get_drvdata(psy);
+ size_t prefix_len;
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = sd->hdev->name;
- while (!strncmp(val->strval, STEELSERIES_PREFIX, STEELSERIES_PREFIX_LEN))
- val->strval += STEELSERIES_PREFIX_LEN;
+ while ((prefix_len = str_has_prefix(val->strval, STEELSERIES_PREFIX)))
+ val->strval += prefix_len;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = "SteelSeries";
@@ -121,12 +211,12 @@ static int steelseries_headset_battery_get_property(struct power_supply *psy,
val->intval = 1;
break;
case POWER_SUPPLY_PROP_STATUS:
- if (sd->headset_connected) {
- val->intval = sd->battery_charging ?
- POWER_SUPPLY_STATUS_CHARGING :
- POWER_SUPPLY_STATUS_DISCHARGING;
- } else
+ if (!sd->headset_connected)
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ else if (sd->battery_charging)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
@@ -144,22 +234,7 @@ static int steelseries_headset_battery_get_property(struct power_supply *psy,
return ret;
}
-static void
-steelseries_headset_set_wireless_status(struct hid_device *hdev,
- bool connected)
-{
- struct usb_interface *intf;
-
- if (!hid_is_usb(hdev))
- return;
-
- intf = to_usb_interface(hdev->dev.parent);
- usb_set_wireless_status(intf, connected ?
- USB_WIRELESS_STATUS_CONNECTED :
- USB_WIRELESS_STATUS_DISCONNECTED);
-}
-
-static enum power_supply_property steelseries_headset_battery_props[] = {
+static enum power_supply_property steelseries_battery_props[] = {
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_PRESENT,
@@ -169,7 +244,26 @@ static enum power_supply_property steelseries_headset_battery_props[] = {
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
};
-static int steelseries_headset_battery_register(struct steelseries_device *sd)
+/*
+ * Delayed work handlers for status polling
+ */
+
+static void steelseries_status_timer_work_handler(struct work_struct *work)
+{
+ struct steelseries_device *sd = container_of(
+ work, struct steelseries_device, status_work.work);
+ unsigned long flags;
+
+ sd->info->request_status(sd->hdev);
+
+ spin_lock_irqsave(&sd->lock, flags);
+ if (!sd->removed)
+ schedule_delayed_work(&sd->status_work,
+ msecs_to_jiffies(STEELSERIES_HEADSET_STATUS_TIMEOUT_MS));
+ spin_unlock_irqrestore(&sd->lock, flags);
+}
+
+static int steelseries_battery_register(struct steelseries_device *sd)
{
static atomic_t battery_no = ATOMIC_INIT(0);
struct power_supply_config battery_cfg = { .drv_data = sd, };
@@ -177,25 +271,27 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
int ret;
sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
- sd->battery_desc.properties = steelseries_headset_battery_props;
- sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_headset_battery_props);
- sd->battery_desc.get_property = steelseries_headset_battery_get_property;
+ sd->battery_desc.properties = steelseries_battery_props;
+ sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_battery_props);
+ sd->battery_desc.get_property = steelseries_battery_get_property;
sd->battery_desc.use_for_apm = 0;
n = atomic_inc_return(&battery_no) - 1;
sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
- "steelseries_headset_battery_%ld", n);
+ "steelseries_headset_battery_%ld", n);
if (!sd->battery_desc.name)
return -ENOMEM;
/* avoid the warning of 0% battery while waiting for the first info */
- steelseries_headset_set_wireless_status(sd->hdev, false);
sd->battery_capacity = 100;
sd->battery_charging = false;
+ sd->headset_connected = false;
+ steelseries_headset_set_wireless_status(sd->hdev, false);
sd->battery = devm_power_supply_register(&sd->hdev->dev,
&sd->battery_desc, &battery_cfg);
if (IS_ERR(sd->battery)) {
ret = PTR_ERR(sd->battery);
+ sd->battery = NULL;
hid_err(sd->hdev,
"%s:power_supply_register failed with error %d\n",
__func__, ret);
@@ -203,52 +299,44 @@ 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) {
- /* The first fetch_battery request can remain unanswered in some cases */
- schedule_delayed_work(&sd->battery_work,
- msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
- }
-
return 0;
}
-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 int steelseries_arctis_probe(struct hid_device *hdev, const struct hid_device_id *id)
+static int steelseries_arctis_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
{
+ const struct steelseries_device_info *info =
+ (const struct steelseries_device_info *)id->driver_data;
struct steelseries_device *sd;
+ struct usb_interface *intf;
+ u8 interface_num;
int ret;
- sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
- if (!sd)
- return -ENOMEM;
- hid_set_drvdata(hdev, sd);
- sd->hdev = hdev;
- sd->quirks = id->driver_data;
+ if (hid_is_usb(hdev)) {
+ intf = to_usb_interface(hdev->dev.parent);
+ interface_num = intf->cur_altsetting->desc.bInterfaceNumber;
+ } else {
+ return -ENODEV;
+ }
ret = hid_parse(hdev);
if (ret)
return ret;
- if (sd->quirks & STEELSERIES_ARCTIS_9 &&
- !steelseries_is_vendor_usage_page(hdev, 0xc0))
- return -ENODEV;
+ /* Let hid-generic handle non-sync interfaces */
+ if (interface_num != info->sync_interface)
+ return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
+ if (!sd)
+ return -ENOMEM;
+
+ sd->hdev = hdev;
+ sd->info = info;
spin_lock_init(&sd->lock);
+ hid_set_drvdata(hdev, sd);
+
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret)
return ret;
@@ -257,9 +345,14 @@ static int steelseries_arctis_probe(struct hid_device *hdev, const struct hid_de
if (ret)
goto err_stop;
- if (steelseries_headset_battery_register(sd) < 0)
- hid_err(sd->hdev,
- "Failed to register battery for headset\n");
+ if (info->capabilities & SS_CAP_BATTERY) {
+ ret = steelseries_battery_register(sd);
+ if (ret < 0)
+ hid_warn(hdev, "Failed to register battery: %d\n", ret);
+ }
+
+ INIT_DELAYED_WORK(&sd->status_work, steelseries_status_timer_work_handler);
+ schedule_delayed_work(&sd->status_work, msecs_to_jiffies(100));
return 0;
@@ -272,142 +365,92 @@ static void steelseries_arctis_remove(struct hid_device *hdev)
{
struct steelseries_device *sd;
unsigned long flags;
+ struct usb_interface *intf;
+ u8 interface_num;
+
+ if (hid_is_usb(hdev)) {
+ intf = to_usb_interface(hdev->dev.parent);
+ interface_num = intf->cur_altsetting->desc.bInterfaceNumber;
+ } else {
+ return;
+ }
sd = hid_get_drvdata(hdev);
- if (!sd)
+
+ if (!sd) {
+ hid_hw_stop(hdev);
return;
+ }
- spin_lock_irqsave(&sd->lock, flags);
- sd->removed = true;
- spin_unlock_irqrestore(&sd->lock, flags);
+ if (interface_num == sd->info->sync_interface) {
+ spin_lock_irqsave(&sd->lock, flags);
+ sd->removed = true;
+ spin_unlock_irqrestore(&sd->lock, flags);
- /* Pairs with smp_store_release() in steelseries_headset_battery_register() */
- if (smp_load_acquire(&sd->battery_registered))
- cancel_delayed_work_sync(&sd->battery_work);
+ cancel_delayed_work_sync(&sd->status_work);
+ }
hid_hw_close(hdev);
hid_hw_stop(hdev);
}
-static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in, uint8_t max_in)
-{
- if (capacity >= max_in)
- return 100;
- if (capacity <= min_in)
- return 0;
- return (capacity - min_in) * 100 / (max_in - min_in);
-}
-
static int steelseries_arctis_raw_event(struct hid_device *hdev,
- struct hid_report *report, u8 *read_buf,
- int size)
+ struct hid_report *report, u8 *data, int size)
{
struct steelseries_device *sd = hid_get_drvdata(hdev);
- int capacity;
- bool connected;
- bool charging;
- unsigned long flags;
+ u8 old_capacity;
+ bool old_connected;
+ bool old_charging;
- /* Pairs with smp_store_release() in steelseries_headset_battery_register() */
- if (!sd || !smp_load_acquire(&sd->battery_registered))
+ if (!sd)
return 0;
- capacity = sd->battery_capacity;
- connected = sd->headset_connected;
- charging = sd->battery_charging;
-
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) {
- hid_dbg(sd->hdev,
- "Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
- if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
- memcmp(read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_request))) {
- if (!delayed_work_pending(&sd->battery_work))
- goto request_battery;
- return 0;
- }
- if (read_buf[2] == 0x01) {
- connected = false;
- capacity = 100;
- } else {
- connected = true;
- capacity = read_buf[3];
- }
- }
-
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) {
- hid_dbg(sd->hdev,
- "Parsing raw event for Arctis 9 headset (%*ph)\n", size, read_buf);
- if (size < ARCTIS_9_BATTERY_RESPONSE_LEN) {
- if (!delayed_work_pending(&sd->battery_work))
- goto request_battery;
- return 0;
- }
+ old_capacity = sd->battery_capacity;
+ old_connected = sd->headset_connected;
+ old_charging = sd->battery_charging;
- if (read_buf[0] == 0xaa && read_buf[1] == 0x01) {
- connected = true;
- charging = read_buf[4] == 0x01;
-
- /*
- * Found no official documentation about min and max.
- * Values defined by testing.
- */
- capacity = steelseries_headset_map_capacity(read_buf[3], 0x68, 0x9d);
- } else {
- /*
- * Device is off and sends the last known status read_buf[1] == 0x03 or
- * there is no known status of the device read_buf[0] == 0x55
- */
- connected = false;
- charging = false;
- }
- }
+ sd->info->parse_status(sd, data, size);
- if (connected != sd->headset_connected) {
- hid_dbg(sd->hdev,
+ if (sd->headset_connected != old_connected) {
+ hid_dbg(hdev,
"Connected status changed from %sconnected to %sconnected\n",
- sd->headset_connected ? "" : "not ",
- connected ? "" : "not ");
- sd->headset_connected = connected;
- steelseries_headset_set_wireless_status(hdev, connected);
+ old_connected ? "" : "not ",
+ sd->headset_connected ? "" : "not ");
+
+ if (sd->battery) {
+ steelseries_headset_set_wireless_status(sd->hdev,
+ sd->headset_connected);
+ power_supply_changed(sd->battery);
+ }
}
- if (capacity != sd->battery_capacity) {
- hid_dbg(sd->hdev,
- "Battery capacity changed from %d%% to %d%%\n",
- sd->battery_capacity, capacity);
- sd->battery_capacity = capacity;
- power_supply_changed(sd->battery);
+ if (sd->battery_capacity != old_capacity) {
+ hid_dbg(hdev, "Battery capacity changed from %d%% to %d%%\n",
+ old_capacity, sd->battery_capacity);
+ if (sd->battery)
+ power_supply_changed(sd->battery);
}
- if (charging != sd->battery_charging) {
- hid_dbg(sd->hdev,
+ if (sd->battery_charging != old_charging) {
+ hid_dbg(hdev,
"Battery charging status changed from %scharging to %scharging\n",
- sd->battery_charging ? "" : "not ",
- charging ? "" : "not ");
- sd->battery_charging = charging;
- power_supply_changed(sd->battery);
+ old_charging ? "" : "not ",
+ sd->battery_charging ? "" : "not ");
+ if (sd->battery)
+ power_supply_changed(sd->battery);
}
-request_battery:
- spin_lock_irqsave(&sd->lock, flags);
- if (!sd->removed)
- schedule_delayed_work(&sd->battery_work,
- msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
- spin_unlock_irqrestore(&sd->lock, flags);
-
return 0;
}
static const struct hid_device_id steelseries_arctis_devices[] = {
- { /* SteelSeries Arctis 1 Wireless for XBox */
- HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X),
- .driver_data = STEELSERIES_ARCTIS_1_X },
-
- { /* SteelSeries Arctis 9 Wireless for XBox */
- HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
- .driver_data = STEELSERIES_ARCTIS_9 },
-
- { }
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X),
+ .driver_data = (unsigned long)&arctis_1_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
+ .driver_data = (unsigned long)&arctis_9_info },
+ {}
};
MODULE_DEVICE_TABLE(hid, steelseries_arctis_devices);
@@ -424,3 +467,4 @@ MODULE_DESCRIPTION("HID driver for Steelseries arctis headsets");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Christian Mayer <git@mayer-bgk.de>");
MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
+MODULE_AUTHOR("Sriman Achanta <srimanachanta@gmail.com>");
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v7 3/8] HID: steelseries: Refactor Arctis driver to use device_info framework
2026-08-03 19:15 ` [PATCH v7 3/8] HID: steelseries: Refactor Arctis driver to use device_info framework Sriman Achanta
@ 2026-08-03 19:25 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-03 19:25 UTC (permalink / raw)
To: Sriman Achanta; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Race condition exposing ERR_PTR leading to kernel panic in raw_event.
--
--- Patch [3]: [PATCH v7 3/8] HID: steelseries: Refactor Arctis driver to use device_info framework ---
commit e1b5fdcaec23ba5cc7a2a2139ba1c9fa983d7894
Author: Sriman Achanta <srimanachanta@gmail.com>
HID: steelseries: Refactor Arctis driver to use device_info framework
This commit refactors the Arctis driver to use a device_info struct
instead of a quirk bitmap for device capabilities. It also changes
battery polling to use a periodic delayed work instead of being
requested from raw_event.
> diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c
> index 6cae2e00bd8ba..c54c56db9ddd7 100644
> --- a/drivers/hid/hid-steelseries-arctis.c
> +++ b/drivers/hid/hid-steelseries-arctis.c
[ ... ]
> @@ -177,25 +271,27 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
[ ... ]
> sd->battery = devm_power_supply_register(&sd->hdev->dev,
> &sd->battery_desc, &battery_cfg);
> if (IS_ERR(sd->battery)) {
> ret = PTR_ERR(sd->battery);
> + sd->battery = NULL;
> hid_err(sd->hdev,
> "%s:power_supply_register failed with error %d\n",
> __func__, ret);
[Severity: High]
Could this assignment expose an ERR_PTR to a concurrent event handler?
Because hid_hw_start() and hid_hw_open() are called before
steelseries_battery_register() in steelseries_arctis_probe(), USB interrupts
can trigger steelseries_arctis_raw_event() concurrently.
If devm_power_supply_register() fails due to memory pressure or other errors,
sd->battery will briefly hold an ERR_PTR before it is set to NULL. A
concurrent execution of raw_event() evaluates if (sd->battery). Since an
ERR_PTR is not NULL, the check passes, and power_supply_changed(sd->battery)
will dereference the invalid pointer, causing a kernel panic.
Would it be safer to assign the return value to a local variable first, and
only assign it to sd->battery upon success?
Additionally, does the unconditional assignment of sd->battery_capacity = 100
just before registration risk silently overwriting a real status update
received by raw_event() during this window?
[ ... ]
> @@ -272,142 +365,92 @@ static void steelseries_arctis_remove(struct hid_device *hdev)
[ ... ]
> - if (connected != sd->headset_connected) {
> - hid_dbg(sd->hdev,
> + if (sd->headset_connected != old_connected) {
> + hid_dbg(hdev,
> "Connected status changed from %sconnected to %sconnected\n",
> - sd->headset_connected ? "" : "not ",
> - connected ? "" : "not ");
> - sd->headset_connected = connected;
> - steelseries_headset_set_wireless_status(hdev, connected);
> + old_connected ? "" : "not ",
> + sd->headset_connected ? "" : "not ");
> +
> + if (sd->battery) {
> + steelseries_headset_set_wireless_status(sd->hdev,
> + sd->headset_connected);
> + power_supply_changed(sd->battery);
> + }
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260803191553.66368-1-srimanachanta@gmail.com?part=3
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v7 4/8] HID: steelseries: Report POWER_SUPPLY_STATUS_FULL when full
2026-08-03 19:15 [PATCH v7 0/8] HID: steelseries: split out Arctis driver and add Nova 5X/Nova 7 support Sriman Achanta
` (2 preceding siblings ...)
2026-08-03 19:15 ` [PATCH v7 3/8] HID: steelseries: Refactor Arctis driver to use device_info framework Sriman Achanta
@ 2026-08-03 19:15 ` Sriman Achanta
2026-08-03 19:15 ` [PATCH v7 5/8] HID: steelseries: Correct Arctis 9 battery calibration range Sriman Achanta
` (4 subsequent siblings)
8 siblings, 0 replies; 14+ messages in thread
From: Sriman Achanta @ 2026-08-03 19:15 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, Bastien Nocera, Simon Wood,
Christian Mayer, Sriman Achanta
Report POWER_SUPPLY_STATUS_FULL when the headset is connected, charging,
and at 100% capacity. It reported CHARGING in that case before.
The charging flag doubles as a power-present signal: the headset keeps
reporting charging while it sits docked at 100%, and clears the flag as
soon as it runs on battery. Gating FULL on the charging flag therefore
avoids reporting FULL for an unplugged headset that merely happens to be
at 100%, which would otherwise hide the discharge state from userspace.
Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
drivers/hid/hid-steelseries-arctis.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c
index c54c56db9ddd..7b8f5f2623b0 100644
--- a/drivers/hid/hid-steelseries-arctis.c
+++ b/drivers/hid/hid-steelseries-arctis.c
@@ -214,7 +214,9 @@ static int steelseries_battery_get_property(struct power_supply *psy,
if (!sd->headset_connected)
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
else if (sd->battery_charging)
- val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ val->intval = sd->battery_capacity >= 100 ?
+ POWER_SUPPLY_STATUS_FULL :
+ POWER_SUPPLY_STATUS_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v7 5/8] HID: steelseries: Correct Arctis 9 battery calibration range
2026-08-03 19:15 [PATCH v7 0/8] HID: steelseries: split out Arctis driver and add Nova 5X/Nova 7 support Sriman Achanta
` (3 preceding siblings ...)
2026-08-03 19:15 ` [PATCH v7 4/8] HID: steelseries: Report POWER_SUPPLY_STATUS_FULL when full Sriman Achanta
@ 2026-08-03 19:15 ` Sriman Achanta
2026-08-03 19:15 ` [PATCH v7 6/8] HID: steelseries: Manage battery lifetime with refcounting Sriman Achanta
` (3 subsequent siblings)
8 siblings, 0 replies; 14+ messages in thread
From: Sriman Achanta @ 2026-08-03 19:15 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, Bastien Nocera, Simon Wood,
Christian Mayer, Sriman Achanta
Map the Arctis 9 raw battery value over 0x64 (empty) to 0x9a (full)
instead of 0x68 to 0x9d. These values match the HeadsetControl project
[1] and fit the calibration points from an independent reverse
engineering of the battery tray (about 25% at raw 112, 50% at raw 125)
[2].
I do not have this headset. The values come from those references and
were not measured directly.
[1] https://github.com/Sapd/HeadsetControl/blob/master/lib/devices/steelseries_arctis_9.hpp
[2] https://magnier.io/reverse-engineering-arctis-9-battery-tray/
Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
drivers/hid/hid-steelseries-arctis.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c
index 7b8f5f2623b0..b54f20f081a2 100644
--- a/drivers/hid/hid-steelseries-arctis.c
+++ b/drivers/hid/hid-steelseries-arctis.c
@@ -141,7 +141,7 @@ static void steelseries_arctis_9_parse_status(struct steelseries_device *sd,
if (data[0] == 0xaa && data[1] == 0x01) {
sd->headset_connected = true;
sd->battery_charging = (data[4] == 0x01);
- sd->battery_capacity = steelseries_map_capacity(data[3], 0x68, 0x9d);
+ sd->battery_capacity = steelseries_map_capacity(data[3], 0x64, 0x9a);
} else {
/* Device off: 0x55 (no status) or 0x03 (stale status). */
sd->headset_connected = false;
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v7 6/8] HID: steelseries: Manage battery lifetime with refcounting
2026-08-03 19:15 [PATCH v7 0/8] HID: steelseries: split out Arctis driver and add Nova 5X/Nova 7 support Sriman Achanta
` (4 preceding siblings ...)
2026-08-03 19:15 ` [PATCH v7 5/8] HID: steelseries: Correct Arctis 9 battery calibration range Sriman Achanta
@ 2026-08-03 19:15 ` Sriman Achanta
2026-08-03 19:29 ` sashiko-bot
2026-08-03 19:15 ` [PATCH v7 7/8] HID: steelseries: Add async status interface support Sriman Achanta
` (2 subsequent siblings)
8 siblings, 1 reply; 14+ messages in thread
From: Sriman Achanta @ 2026-08-03 19:15 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, Bastien Nocera, Simon Wood,
Christian Mayer, Sriman Achanta
The next change shares one steelseries_device between two HID
interfaces, so the state can outlive either interface. Stop using devm
for it. Reference count the struct with a kref and free it from
steelseries_device_release(). Register and unregister the power supply
explicitly, and clear sd->battery under sd->lock in remove() so it is
not touched after it is unregistered.
Drop the global atomic battery counter and name the power supply after
the device (hdev->uniq, or dev_name() when empty), as hid-input and the
other HID battery drivers do.
No functional change for the current single-interface devices.
Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
drivers/hid/hid-steelseries-arctis.c | 47 +++++++++++++++++++++-------
1 file changed, 36 insertions(+), 11 deletions(-)
diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c
index b54f20f081a2..e534aa44e70a 100644
--- a/drivers/hid/hid-steelseries-arctis.c
+++ b/drivers/hid/hid-steelseries-arctis.c
@@ -8,6 +8,8 @@
#include <linux/device.h>
#include <linux/hid.h>
+#include <linux/kref.h>
+#include <linux/slab.h>
#include <linux/module.h>
#include <linux/power_supply.h>
#include <linux/spinlock.h>
@@ -30,6 +32,8 @@ struct steelseries_device_info {
};
struct steelseries_device {
+ struct kref refcnt;
+
struct hid_device *hdev;
const struct steelseries_device_info *info;
@@ -45,6 +49,14 @@ struct steelseries_device {
bool removed;
};
+static void steelseries_device_release(struct kref *ref)
+{
+ struct steelseries_device *sd =
+ container_of(ref, struct steelseries_device, refcnt);
+
+ kfree(sd);
+}
+
/*
* Headset report helpers
*/
@@ -267,9 +279,8 @@ static void steelseries_status_timer_work_handler(struct work_struct *work)
static int steelseries_battery_register(struct steelseries_device *sd)
{
- static atomic_t battery_no = ATOMIC_INIT(0);
struct power_supply_config battery_cfg = { .drv_data = sd, };
- unsigned long n;
+ struct power_supply *battery;
int ret;
sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
@@ -277,9 +288,10 @@ static int steelseries_battery_register(struct steelseries_device *sd)
sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_battery_props);
sd->battery_desc.get_property = steelseries_battery_get_property;
sd->battery_desc.use_for_apm = 0;
- n = atomic_inc_return(&battery_no) - 1;
sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
- "steelseries_headset_battery_%ld", n);
+ "steelseries_headset_battery_%s",
+ sd->hdev->uniq[0] ? sd->hdev->uniq :
+ dev_name(&sd->hdev->dev));
if (!sd->battery_desc.name)
return -ENOMEM;
@@ -289,17 +301,19 @@ static int steelseries_battery_register(struct steelseries_device *sd)
sd->headset_connected = false;
steelseries_headset_set_wireless_status(sd->hdev, false);
- sd->battery = devm_power_supply_register(&sd->hdev->dev,
+ battery = power_supply_register(&sd->hdev->dev,
&sd->battery_desc, &battery_cfg);
- if (IS_ERR(sd->battery)) {
- ret = PTR_ERR(sd->battery);
- sd->battery = NULL;
+ if (IS_ERR(battery)) {
+ ret = PTR_ERR(battery);
hid_err(sd->hdev,
"%s:power_supply_register failed with error %d\n",
__func__, ret);
return ret;
}
- power_supply_powers(sd->battery, &sd->hdev->dev);
+ power_supply_powers(battery, &sd->hdev->dev);
+
+ /* Assign on success only, so a concurrent raw_event never sees an ERR_PTR. */
+ sd->battery = battery;
return 0;
}
@@ -329,10 +343,11 @@ static int steelseries_arctis_probe(struct hid_device *hdev,
if (interface_num != info->sync_interface)
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
- sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
+ sd = kzalloc_obj(*sd, GFP_KERNEL);
if (!sd)
return -ENOMEM;
+ kref_init(&sd->refcnt);
sd->hdev = hdev;
sd->info = info;
spin_lock_init(&sd->lock);
@@ -341,7 +356,7 @@ static int steelseries_arctis_probe(struct hid_device *hdev,
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret)
- return ret;
+ goto err_put;
ret = hid_hw_open(hdev);
if (ret)
@@ -360,12 +375,15 @@ static int steelseries_arctis_probe(struct hid_device *hdev,
err_stop:
hid_hw_stop(hdev);
+err_put:
+ kref_put(&sd->refcnt, steelseries_device_release);
return ret;
}
static void steelseries_arctis_remove(struct hid_device *hdev)
{
struct steelseries_device *sd;
+ struct power_supply *battery;
unsigned long flags;
struct usb_interface *intf;
u8 interface_num;
@@ -387,13 +405,20 @@ static void steelseries_arctis_remove(struct hid_device *hdev)
if (interface_num == sd->info->sync_interface) {
spin_lock_irqsave(&sd->lock, flags);
sd->removed = true;
+ battery = sd->battery;
+ sd->battery = NULL;
spin_unlock_irqrestore(&sd->lock, flags);
cancel_delayed_work_sync(&sd->status_work);
+
+ if (battery)
+ power_supply_unregister(battery);
}
hid_hw_close(hdev);
hid_hw_stop(hdev);
+
+ kref_put(&sd->refcnt, steelseries_device_release);
}
static int steelseries_arctis_raw_event(struct hid_device *hdev,
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v7 6/8] HID: steelseries: Manage battery lifetime with refcounting
2026-08-03 19:15 ` [PATCH v7 6/8] HID: steelseries: Manage battery lifetime with refcounting Sriman Achanta
@ 2026-08-03 19:29 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-03 19:29 UTC (permalink / raw)
To: Sriman Achanta; +Cc: dmitry.torokhov, linux-input
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Concurrent execution of `steelseries_arctis_raw_event()` and explicit battery unregistration in `steelseries_arctis_remove()` introduces a Use-After-Free and NULL pointer dereference.
- [Medium] The power supply name string is allocated with `devm_kasprintf` but is embedded inside a manually reference-counted object `sd`, leading to a lifecycle mismatch.
--
--- Patch [6]: [PATCH v7 6/8] HID: steelseries: Manage battery lifetime with refcounting ---
commit fd37e70cee8d0953a9d3de05f0149d2fc76d513a
Author: Sriman Achanta <srimanachanta@gmail.com>
HID: steelseries: Manage battery lifetime with refcounting
This commit changes the steelseries_device structure to use a kref for
lifetime management instead of devm. It explicitly registers and
unregisters the battery power supply and nullifies the battery pointer
during removal.
> diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c
> index b54f20f081a26..e534aa44e70a0 100644
> --- a/drivers/hid/hid-steelseries-arctis.c
> +++ b/drivers/hid/hid-steelseries-arctis.c
[ ... ]
> @@ -277,9 +288,10 @@ static int steelseries_battery_register(struct steelseries_device *sd)
> sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_battery_props);
> sd->battery_desc.get_property = steelseries_battery_get_property;
> sd->battery_desc.use_for_apm = 0;
> - n = atomic_inc_return(&battery_no) - 1;
> sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
> - "steelseries_headset_battery_%ld", n);
> + "steelseries_headset_battery_%s",
> + sd->hdev->uniq[0] ? sd->hdev->uniq :
> + dev_name(&sd->hdev->dev));
[Severity: Medium]
Since the steelseries_device struct sd is now refcounted and can outlive
the device, will using devm_kasprintf() for sd->battery_desc.name leave a
dangling pointer inside sd when the device unbinds and devres frees the
string?
[ ... ]
> @@ -387,13 +405,20 @@ static void steelseries_arctis_remove(struct hid_device *hdev)
> if (interface_num == sd->info->sync_interface) {
> spin_lock_irqsave(&sd->lock, flags);
> sd->removed = true;
> + battery = sd->battery;
> + sd->battery = NULL;
> spin_unlock_irqrestore(&sd->lock, flags);
>
> cancel_delayed_work_sync(&sd->status_work);
> +
> + if (battery)
> + power_supply_unregister(battery);
> }
>
> hid_hw_close(hdev);
> hid_hw_stop(hdev);
[Severity: High]
Can an incoming HID event trigger steelseries_arctis_raw_event() concurrently
with this teardown, leading to a NULL pointer dereference or use-after-free?
Since hid_hw_stop() hasn't been called yet, hardware events can still fire.
If steelseries_arctis_raw_event() locklessly checks sd->battery and then
calls power_supply_changed(sd->battery), could remove() nullify and free the
battery in between the check and the use?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260803191553.66368-1-srimanachanta@gmail.com?part=6
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v7 7/8] HID: steelseries: Add async status interface support
2026-08-03 19:15 [PATCH v7 0/8] HID: steelseries: split out Arctis driver and add Nova 5X/Nova 7 support Sriman Achanta
` (5 preceding siblings ...)
2026-08-03 19:15 ` [PATCH v7 6/8] HID: steelseries: Manage battery lifetime with refcounting Sriman Achanta
@ 2026-08-03 19:15 ` Sriman Achanta
2026-08-03 19:29 ` sashiko-bot
2026-08-03 19:15 ` [PATCH v7 8/8] HID: steelseries: Add support for Arctis Nova 5X and Nova 7 families Sriman Achanta
2026-08-03 19:32 ` [PATCH v7 0/8] HID: steelseries: split out Arctis driver and add Nova 5X/Nova 7 support Jiri Kosina
8 siblings, 1 reply; 14+ messages in thread
From: Sriman Achanta @ 2026-08-03 19:15 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, Bastien Nocera, Simon Wood,
Christian Mayer, Sriman Achanta
Some headsets expose a second HID interface that sends battery and
connection updates on its own. Watching that interface lets the driver
stop polling the sync interface. Add a
steelseries_device_info::async_interface field and the code to handle
it:
- The driver binds both the sync and async interfaces. The async
interface shares the steelseries_device created by the sync
interface. It finds the sibling with usb_ifnum_to_if(), and before
trusting its intfdata it rejects non-HID siblings by descriptor
class and holds the sibling's device lock across the lookup, so a
crafted device cannot cause a type-confused read and a concurrent
unbind cannot free the hid_device from under it. It then takes a
reference and returns -EPROBE_DEFER until the sync interface has
probed. If the sync interface never binds, the async interface
defers forever, which is fine here.
- raw_event() now holds sd->lock and re-checks sd->removed so events
on either interface are serialised against removal.
- status_work runs once for async devices instead of rearming. A
single status request is sent when the headset connects to get the
initial battery level.
No device sets async_interface yet. This is the infrastructure for the
next commit.
Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
drivers/hid/hid-steelseries-arctis.c | 158 ++++++++++++++++++++++-----
1 file changed, 131 insertions(+), 27 deletions(-)
diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c
index e534aa44e70a..9960c0ec512b 100644
--- a/drivers/hid/hid-steelseries-arctis.c
+++ b/drivers/hid/hid-steelseries-arctis.c
@@ -26,6 +26,7 @@ struct steelseries_device_info {
unsigned long capabilities;
u8 sync_interface;
+ u8 async_interface;
int (*request_status)(struct hid_device *hdev);
void (*parse_status)(struct steelseries_device *sd, u8 *data, int size);
@@ -271,7 +272,8 @@ static void steelseries_status_timer_work_handler(struct work_struct *work)
sd->info->request_status(sd->hdev);
spin_lock_irqsave(&sd->lock, flags);
- if (!sd->removed)
+ /* Async devices push status events themselves; only poll once. */
+ if (!sd->removed && !sd->info->async_interface)
schedule_delayed_work(&sd->status_work,
msecs_to_jiffies(STEELSERIES_HEADSET_STATUS_TIMEOUT_MS));
spin_unlock_irqrestore(&sd->lock, flags);
@@ -318,6 +320,53 @@ static int steelseries_battery_register(struct steelseries_device *sd)
return 0;
}
+static struct hid_driver steelseries_arctis_driver;
+
+static struct steelseries_device *
+steelseries_get_sibling_sd(struct hid_device *hdev, int interface_num)
+{
+ struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+ struct usb_device *usb_dev = interface_to_usbdev(intf);
+ struct usb_interface *sibling_intf;
+ struct hid_device *sibling_hdev;
+ struct steelseries_device *sd = NULL;
+
+ sibling_intf = usb_ifnum_to_if(usb_dev, interface_num);
+ if (!sibling_intf)
+ return NULL;
+
+ /*
+ * usb_get_intfdata() only yields a hid_device when usbhid is bound;
+ * gate on the descriptor class so a non-HID sibling (e.g. a crafted
+ * device exposing storage or audio here) is never treated as one.
+ */
+ if (sibling_intf->cur_altsetting->desc.bInterfaceClass != USB_INTERFACE_CLASS_HID)
+ return NULL;
+
+ /*
+ * Take the sibling's device lock across the intfdata read and the
+ * kref_get so a concurrent unbind cannot free the hid_device underneath
+ * us; usbhid leaves intfdata dangling on disconnect, so dev.driver is
+ * the reliable "still bound" test under this lock. Use device_trylock()
+ * to stay off the lockdep chain of the interface being probed and let
+ * the caller retry via -EPROBE_DEFER if the sibling is momentarily busy.
+ */
+ if (!device_trylock(&sibling_intf->dev))
+ return NULL;
+ if (sibling_intf->dev.driver) {
+ sibling_hdev = usb_get_intfdata(sibling_intf);
+ if (sibling_hdev &&
+ sibling_hdev->driver == &steelseries_arctis_driver) {
+ sd = hid_get_drvdata(sibling_hdev);
+ if (sd)
+ kref_get(&sd->refcnt);
+ }
+ }
+ device_unlock(&sibling_intf->dev);
+
+ return sd;
+}
+
static int steelseries_arctis_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
@@ -339,43 +388,81 @@ static int steelseries_arctis_probe(struct hid_device *hdev,
if (ret)
return ret;
- /* Let hid-generic handle non-sync interfaces */
- if (interface_num != info->sync_interface)
+ /* Let hid-generic handle non-vendor or unknown interfaces */
+ if (interface_num != info->sync_interface &&
+ (!info->async_interface || interface_num != info->async_interface))
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
- sd = kzalloc_obj(*sd, GFP_KERNEL);
- if (!sd)
- return -ENOMEM;
-
- kref_init(&sd->refcnt);
- sd->hdev = hdev;
- sd->info = info;
- spin_lock_init(&sd->lock);
+ if (interface_num == info->sync_interface) {
+ sd = kzalloc_obj(*sd, GFP_KERNEL);
+ if (!sd)
+ return -ENOMEM;
+
+ kref_init(&sd->refcnt);
+ sd->hdev = hdev;
+ sd->info = info;
+ spin_lock_init(&sd->lock);
+ INIT_DELAYED_WORK(&sd->status_work, steelseries_status_timer_work_handler);
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret)
+ goto err_free;
+
+ ret = hid_hw_open(hdev);
+ if (ret)
+ goto err_stop;
+
+ if (info->capabilities & SS_CAP_BATTERY) {
+ ret = steelseries_battery_register(sd);
+ if (ret < 0)
+ hid_warn(hdev, "Failed to register battery: %d\n", ret);
+ }
- hid_set_drvdata(hdev, sd);
+ /*
+ * Publish drvdata only once fully initialised: the async sibling
+ * attaches by reading it, so it must never observe a half-built or
+ * failed instance. A failed probe never gets here, so the error
+ * path below has nothing to unpublish.
+ */
+ hid_set_drvdata(hdev, sd);
+ schedule_delayed_work(&sd->status_work, msecs_to_jiffies(100));
- ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
- if (ret)
- goto err_put;
+ return 0;
+ }
- ret = hid_hw_open(hdev);
- if (ret)
- goto err_stop;
+ /*
+ * The async interface shares the steelseries_device created by the
+ * sync interface. Defer until the sync interface has probed and
+ * published its drvdata.
+ */
+ if (info->async_interface && interface_num == info->async_interface) {
+ sd = steelseries_get_sibling_sd(hdev, info->sync_interface);
+ if (!sd)
+ return -EPROBE_DEFER;
+
+ hid_set_drvdata(hdev, sd);
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret) {
+ kref_put(&sd->refcnt, steelseries_device_release);
+ return ret;
+ }
- if (info->capabilities & SS_CAP_BATTERY) {
- ret = steelseries_battery_register(sd);
- if (ret < 0)
- hid_warn(hdev, "Failed to register battery: %d\n", ret);
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ hid_hw_stop(hdev);
+ kref_put(&sd->refcnt, steelseries_device_release);
+ return ret;
+ }
+ return 0;
}
- INIT_DELAYED_WORK(&sd->status_work, steelseries_status_timer_work_handler);
- schedule_delayed_work(&sd->status_work, msecs_to_jiffies(100));
-
- return 0;
+ return -ENODEV;
err_stop:
hid_hw_stop(hdev);
-err_put:
+err_free:
+ /* drvdata is unpublished until full success, so no sibling can hold sd. */
kref_put(&sd->refcnt, steelseries_device_release);
return ret;
}
@@ -428,10 +515,21 @@ static int steelseries_arctis_raw_event(struct hid_device *hdev,
u8 old_capacity;
bool old_connected;
bool old_charging;
+ bool is_async_interface;
+ unsigned long flags;
if (!sd)
return 0;
+ is_async_interface = (hdev != sd->hdev);
+
+ spin_lock_irqsave(&sd->lock, flags);
+
+ if (sd->removed) {
+ spin_unlock_irqrestore(&sd->lock, flags);
+ return 0;
+ }
+
old_capacity = sd->battery_capacity;
old_connected = sd->headset_connected;
old_charging = sd->battery_charging;
@@ -444,6 +542,10 @@ static int steelseries_arctis_raw_event(struct hid_device *hdev,
old_connected ? "" : "not ",
sd->headset_connected ? "" : "not ");
+ if (sd->headset_connected && !old_connected &&
+ sd->info->async_interface && is_async_interface)
+ schedule_delayed_work(&sd->status_work, 0);
+
if (sd->battery) {
steelseries_headset_set_wireless_status(sd->hdev,
sd->headset_connected);
@@ -467,6 +569,8 @@ static int steelseries_arctis_raw_event(struct hid_device *hdev,
power_supply_changed(sd->battery);
}
+ spin_unlock_irqrestore(&sd->lock, flags);
+
return 0;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v7 7/8] HID: steelseries: Add async status interface support
2026-08-03 19:15 ` [PATCH v7 7/8] HID: steelseries: Add async status interface support Sriman Achanta
@ 2026-08-03 19:29 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-03 19:29 UTC (permalink / raw)
To: Sriman Achanta; +Cc: dmitry.torokhov, linux-input
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The `steelseries_get_sibling_sd` function uses `usb_get_intfdata` on a sibling USB interface and unconditionally casts the result to `struct hid_device *`, which leads to type confusion if a different driver binds to it.
- [High] A concurrent sysfs unbind of the HID driver from the sync interface can cause `steelseries_get_sibling_sd` to return a freed `steelseries_device` (sd) and call `kref_get` on it, leading to a Use-After-Free.
--
--- Patch [7]: [PATCH v7 7/8] HID: steelseries: Add async status interface support ---
commit 816a244e480f8271ebf1dbc58fbdbe5e0bdbafab
Author: Sriman Achanta <srimanachanta@gmail.com>
HID: steelseries: Add async status interface support
Some headsets expose a second HID interface that sends battery and
connection updates on its own. Watching that interface lets the driver
stop polling the sync interface.
> diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c
> --- a/drivers/hid/hid-steelseries-arctis.c
> +++ b/drivers/hid/hid-steelseries-arctis.c
[ ... ]
> @@ -318,6 +320,53 @@ static int steelseries_battery_register(struct steelseries_device *sd)
> +static struct steelseries_device *
> +steelseries_get_sibling_sd(struct hid_device *hdev, int interface_num)
> +{
> + struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
> + struct usb_device *usb_dev = interface_to_usbdev(intf);
> + struct usb_interface *sibling_intf;
> + struct hid_device *sibling_hdev;
> + struct steelseries_device *sd = NULL;
> +
> + sibling_intf = usb_ifnum_to_if(usb_dev, interface_num);
> + if (!sibling_intf)
> + return NULL;
[ ... ]
> + if (!device_trylock(&sibling_intf->dev))
> + return NULL;
> + if (sibling_intf->dev.driver) {
> + sibling_hdev = usb_get_intfdata(sibling_intf);
> + if (sibling_hdev &&
> + sibling_hdev->driver == &steelseries_arctis_driver) {
[Severity: High]
Is it safe to assume sibling_intf is bound to usbhid here?
If an attacker plugs in a crafted USB device, the async interface could be
bound to a different driver (like usbfs or usbmouse). Since
usb_get_intfdata() is unconditionally treated as a struct hid_device
pointer, checking sibling_hdev->driver could result in an out-of-bounds
read if the bound driver's data structure is smaller.
> + sd = hid_get_drvdata(sibling_hdev);
> + if (sd)
> + kref_get(&sd->refcnt);
[Severity: High]
Could a concurrent unbind of the HID driver cause a use-after-free on sd?
While the USB interface lock (&sibling_intf->dev) is held here, this
doesn't serialize against a sysfs unbind of the HID driver. If a user
unbinds the HID driver from the sibling interface via sysfs, the HID core's
hid_device_remove() calls the driver's remove callback (which drops the
refcount and frees sd), and only clears hdev->driver afterwards.
This could allow hid_get_drvdata() to return a pointer to a freed
steelseries_device, leading to a kref_get() on freed memory.
> + }
> + }
> + device_unlock(&sibling_intf->dev);
> +
> + return sd;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260803191553.66368-1-srimanachanta@gmail.com?part=7
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v7 8/8] HID: steelseries: Add support for Arctis Nova 5X and Nova 7 families
2026-08-03 19:15 [PATCH v7 0/8] HID: steelseries: split out Arctis driver and add Nova 5X/Nova 7 support Sriman Achanta
` (6 preceding siblings ...)
2026-08-03 19:15 ` [PATCH v7 7/8] HID: steelseries: Add async status interface support Sriman Achanta
@ 2026-08-03 19:15 ` Sriman Achanta
2026-08-03 19:32 ` [PATCH v7 0/8] HID: steelseries: split out Arctis driver and add Nova 5X/Nova 7 support Jiri Kosina
8 siblings, 0 replies; 14+ messages in thread
From: Sriman Achanta @ 2026-08-03 19:15 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, Bastien Nocera, Simon Wood,
Christian Mayer, Sriman Achanta
The Arctis Nova 5X, Nova 7 Gen2, and Nova 7 2026 refresh headsets answer
status polls, but also send unsolicited battery and connection updates
on a second HID interface (interface 5). Use that interface through
async_interface so the driver does not have to poll. Add request and
parse helpers for the Nova status format (0xb0/0xb7/0xb9/0xbb opcodes).
The original (pre-Gen2) Arctis Nova 7 family answers the same status
opcodes on the same interfaces, but reports battery capacity as a
discrete 0-4 level instead of a raw percentage. Add a second parse
function that maps that level through steelseries_map_capacity(), and a
separate device_info so the two capacity encodings cannot get mixed up.
Add the fourteen USB product IDs that share these protocols:
0x2253 Arctis Nova 5X
0x2202 Arctis Nova 7
0x2206 Arctis Nova 7X
0x22a4 Arctis Nova 7X (alternate PID)
0x223a Arctis Nova 7 Diablo
0x227a Arctis Nova 7 World of Warcraft Edition
0x22a1 Arctis Nova 7 2026
0x22a7 Arctis Nova 7P 2026
0x22a5 Arctis Nova 7X 2026
0x22a9 Arctis Nova 7 Diablo 2026
0x227e Arctis Nova 7 Gen 2
0x2258 Arctis Nova 7X Gen 2
0x229e Arctis Nova 7X Gen 2 (alternate PID)
0x22ad Arctis Nova 7X Gen 2 (alternate PID)
Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
drivers/hid/hid-ids.h | 14 ++++
drivers/hid/hid-quirks.c | 14 ++++
drivers/hid/hid-steelseries-arctis.c | 117 +++++++++++++++++++++++++++
3 files changed, 145 insertions(+)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 70b30e13b96f..5586a03d00a3 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1379,6 +1379,20 @@
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X 0x12b6
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X 0x2253
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7 0x2202
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X 0x2206
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2 0x22a4
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO 0x223a
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW 0x227a
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026 0x22a1
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026 0x22a7
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026 0x22a5
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026 0x22a9
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2 0x227e
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2 0x2258
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2 0x229e
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3 0x22ad
#define USB_DEVICE_ID_STEELSERIES_MSI_KLC 0x1122
#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index f546179858c2..eef2c2106dd7 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -749,6 +749,20 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3) },
#endif
#if IS_ENABLED(CONFIG_HID_SUNPLUS)
{ HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c
index 9960c0ec512b..23fb0cebd72a 100644
--- a/drivers/hid/hid-steelseries-arctis.c
+++ b/drivers/hid/hid-steelseries-arctis.c
@@ -108,6 +108,13 @@ static int steelseries_arctis_9_request_status(struct hid_device *hdev)
return steelseries_send_output_report(hdev, data, sizeof(data));
}
+static int steelseries_arctis_nova_request_status(struct hid_device *hdev)
+{
+ const u8 data[] = { 0x00, 0xb0 };
+
+ return steelseries_send_output_report(hdev, data, sizeof(data));
+}
+
/*
* Headset battery helpers
*/
@@ -162,6 +169,58 @@ static void steelseries_arctis_9_parse_status(struct steelseries_device *sd,
}
}
+static void steelseries_arctis_nova_parse_status(struct steelseries_device *sd,
+ u8 *data, int size)
+{
+ if (size < 2)
+ return;
+
+ switch (data[0]) {
+ case 0xb0:
+ if (size < 4)
+ return;
+ sd->headset_connected = (data[1] == 0x03);
+ sd->battery_capacity = data[2];
+ sd->battery_charging = (data[3] == 0x01);
+ break;
+ case 0xb7:
+ sd->battery_capacity = data[1];
+ break;
+ case 0xb9:
+ sd->headset_connected = (data[1] == 0x03);
+ break;
+ case 0xbb:
+ sd->battery_charging = (data[1] == 0x01);
+ break;
+ }
+}
+
+static void steelseries_arctis_nova_7_parse_status(struct steelseries_device *sd,
+ u8 *data, int size)
+{
+ if (size < 2)
+ return;
+
+ switch (data[0]) {
+ case 0xb0:
+ if (size < 4)
+ return;
+ sd->headset_connected = (data[1] == 0x03);
+ sd->battery_capacity = steelseries_map_capacity(data[2], 0, 4);
+ sd->battery_charging = (data[3] == 0x01);
+ break;
+ case 0xb7:
+ sd->battery_capacity = steelseries_map_capacity(data[1], 0, 4);
+ break;
+ case 0xb9:
+ sd->headset_connected = (data[1] == 0x03);
+ break;
+ case 0xbb:
+ sd->battery_charging = (data[1] == 0x01);
+ break;
+ }
+}
+
/*
* Device info definitions
*/
@@ -180,6 +239,22 @@ static const struct steelseries_device_info arctis_9_info = {
.parse_status = steelseries_arctis_9_parse_status,
};
+static const struct steelseries_device_info arctis_nova_info = {
+ .sync_interface = 3,
+ .async_interface = 5,
+ .capabilities = SS_CAP_BATTERY,
+ .request_status = steelseries_arctis_nova_request_status,
+ .parse_status = steelseries_arctis_nova_parse_status,
+};
+
+static const struct steelseries_device_info arctis_nova_7_info = {
+ .sync_interface = 3,
+ .async_interface = 5,
+ .capabilities = SS_CAP_BATTERY,
+ .request_status = steelseries_arctis_nova_request_status,
+ .parse_status = steelseries_arctis_nova_7_parse_status,
+};
+
/*
* Headset wireless status and battery infrastructure
*/
@@ -581,6 +656,48 @@ static const struct hid_device_id steelseries_arctis_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = (unsigned long)&arctis_9_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X),
+ .driver_data = (unsigned long)&arctis_nova_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7),
+ .driver_data = (unsigned long)&arctis_nova_7_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X),
+ .driver_data = (unsigned long)&arctis_nova_7_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2),
+ .driver_data = (unsigned long)&arctis_nova_7_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO),
+ .driver_data = (unsigned long)&arctis_nova_7_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW),
+ .driver_data = (unsigned long)&arctis_nova_7_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026),
+ .driver_data = (unsigned long)&arctis_nova_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026),
+ .driver_data = (unsigned long)&arctis_nova_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026),
+ .driver_data = (unsigned long)&arctis_nova_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026),
+ .driver_data = (unsigned long)&arctis_nova_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2),
+ .driver_data = (unsigned long)&arctis_nova_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2),
+ .driver_data = (unsigned long)&arctis_nova_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2),
+ .driver_data = (unsigned long)&arctis_nova_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3),
+ .driver_data = (unsigned long)&arctis_nova_info },
{}
};
MODULE_DEVICE_TABLE(hid, steelseries_arctis_devices);
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v7 0/8] HID: steelseries: split out Arctis driver and add Nova 5X/Nova 7 support
2026-08-03 19:15 [PATCH v7 0/8] HID: steelseries: split out Arctis driver and add Nova 5X/Nova 7 support Sriman Achanta
` (7 preceding siblings ...)
2026-08-03 19:15 ` [PATCH v7 8/8] HID: steelseries: Add support for Arctis Nova 5X and Nova 7 families Sriman Achanta
@ 2026-08-03 19:32 ` Jiri Kosina
2026-08-05 0:26 ` Benjamin Wheeler
8 siblings, 1 reply; 14+ messages in thread
From: Jiri Kosina @ 2026-08-03 19:32 UTC (permalink / raw)
To: Sriman Achanta
Cc: Benjamin Tissoires, linux-input, linux-kernel, Bastien Nocera,
Simon Wood, Christian Mayer
On Mon, 3 Aug 2026, Sriman Achanta wrote:
> This series moves the SteelSeries Arctis headset support out of
> hid-steelseries.c into its own module, reworks it around a per-model
> device_info table, hardens the battery power-supply lifetime, adds an
> asynchronous status interface, and finally adds support for the Arctis
> Nova 5X and Nova 7 families. After the split, hid-steelseries.c is left
> handling the SRW-S1 racing wheel and the MSI RGB LED devices.
>
> Changes since v6:
> - Rebased onto hid.git#for-7.3/steelseries.
> - The split now leaves the MSI RGB code in hid-steelseries.c
> alongside SRW-S1, and strips only the headset members from
> struct steelseries_device. The MSI RGB quirk bit is renumbered
> to BIT(1) since the two Arctis bits are gone.
> - The Arctis hardening that came with the MSI commit moves into
> hid-steelseries-arctis.c with the rest of the headset code: the
> hdev->rsize < 3 guard in steelseries_is_vendor_usage_page(), the
> battery_registered store/load-acquire pair, the !sd checks, and
> the hid_hw_stop() on the hid_hw_open() error path. The
> steelseries_is_headset() dispatch is dropped, as the new module
> only binds headsets. The battery_registered flag then goes away
> in patch 3, where the device_info rework guards every
> power_supply_changed() on sd->battery instead.
> - The v6 patch "HID: steelseries: Inline and simplify SRWS1 wheel
> driver" is dropped. Its premise was that hid-steelseries.c only
> handles the SRW-S1 after the split, which is no longer true.
> - No other changes. The resulting hid-steelseries-arctis.c is
> byte-identical to v6.
Thanks a lot for such a brisk rebase. Now queued.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v7 0/8] HID: steelseries: split out Arctis driver and add Nova 5X/Nova 7 support
2026-08-03 19:32 ` [PATCH v7 0/8] HID: steelseries: split out Arctis driver and add Nova 5X/Nova 7 support Jiri Kosina
@ 2026-08-05 0:26 ` Benjamin Wheeler
0 siblings, 0 replies; 14+ messages in thread
From: Benjamin Wheeler @ 2026-08-05 0:26 UTC (permalink / raw)
To: Jiri Kosina, Sriman Achanta
Cc: Benjamin Tissoires, linux-input, linux-kernel, Bastien Nocera,
Simon Wood, Christian Mayer
I'm glad to hear this is getting merged! I have studied and tested older
revisions of Sriman's patchset on my Arctis 7 Gen2 and would love to
contribute a patch that adds support for it as well. I see you have a
branch called for-7.3/steelseries in the hid kernel fork. Should I base
my patchset off that? And should I aim to have this ready before the 7.3
merge window, or would 7.4 be more realistic? Thanks for the direction.
Sincerely,
Benjamin Wheeler
P.S. And thanks for working on this, Sriman!
On 8/3/26 3:32 PM, Jiri Kosina wrote:
> On Mon, 3 Aug 2026, Sriman Achanta wrote:
>
>> This series moves the SteelSeries Arctis headset support out of
>> hid-steelseries.c into its own module, reworks it around a per-model
>> device_info table, hardens the battery power-supply lifetime, adds an
>> asynchronous status interface, and finally adds support for the Arctis
>> Nova 5X and Nova 7 families. After the split, hid-steelseries.c is left
>> handling the SRW-S1 racing wheel and the MSI RGB LED devices.
>>
>> Changes since v6:
>> - Rebased onto hid.git#for-7.3/steelseries.
>> - The split now leaves the MSI RGB code in hid-steelseries.c
>> alongside SRW-S1, and strips only the headset members from
>> struct steelseries_device. The MSI RGB quirk bit is renumbered
>> to BIT(1) since the two Arctis bits are gone.
>> - The Arctis hardening that came with the MSI commit moves into
>> hid-steelseries-arctis.c with the rest of the headset code: the
>> hdev->rsize < 3 guard in steelseries_is_vendor_usage_page(), the
>> battery_registered store/load-acquire pair, the !sd checks, and
>> the hid_hw_stop() on the hid_hw_open() error path. The
>> steelseries_is_headset() dispatch is dropped, as the new module
>> only binds headsets. The battery_registered flag then goes away
>> in patch 3, where the device_info rework guards every
>> power_supply_changed() on sd->battery instead.
>> - The v6 patch "HID: steelseries: Inline and simplify SRWS1 wheel
>> driver" is dropped. Its premise was that hid-steelseries.c only
>> handles the SRW-S1 after the split, which is no longer true.
>> - No other changes. The resulting hid-steelseries-arctis.c is
>> byte-identical to v6.
> Thanks a lot for such a brisk rebase. Now queued.
>
^ permalink raw reply [flat|nested] 14+ messages in thread