* [PATCH] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
@ 2026-05-31 21:52 David Glushkov
2026-05-31 22:06 ` sashiko-bot
` (4 more replies)
0 siblings, 5 replies; 8+ messages in thread
From: David Glushkov @ 2026-05-31 21:52 UTC (permalink / raw)
To: Jiri Kosina; +Cc: Benjamin Tissoires, linux-input, linux-kernel
The MSI Raider A18 HX A9WJG exposes two internal SteelSeries USB HID
devices for RGB lighting: KLC (1038:1122) for the keyboard and ALC
(1038:1161) for the lightbar/logo zones.
Add DMI-gated support for these devices and expose them as multicolor
LED class devices. The driver sends the same HID class SET_REPORT
control transfer as the tested userspace implementation for this
machine and writes a uniform RGB value to all known keyboard keys or
ALC zones.
Limit binding to USB interface 0 and the tested DMI system because the
KLC product ID is shared across MSI laptop designs and the key layout
mapping is model-specific.
Tested on MSI Raider A18 HX A9WJG. Both internal SteelSeries ALC
(1038:1161) and KLC (1038:1122) HID devices bind on interface 0 and
create steelseries::lightbar and steelseries::kbd_backlight. Setting
multi_intensity and brightness changes the keyboard and lightbar
colors.
Signed-off-by: David Glushkov <david.glushkov@sntiq.com>
---
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-steelseries.c | 176 +++++++++++++++++++++++++++++++++-
2 files changed, 176 insertions(+), 2 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 426ff78..83599bd 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1368,6 +1368,8 @@
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
+#define USB_DEVICE_ID_STEELSERIES_MSI_KLC 0x1122
+#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f984356..0492814 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -10,16 +10,23 @@
*/
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
#define STEELSERIES_ARCTIS_1 BIT(1)
#define STEELSERIES_ARCTIS_9 BIT(2)
+#define STEELSERIES_MSI_RGB BIT(3)
+
+#define STEELSERIES_HAS_LEDS_MULTICOLOR \
+ (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
struct steelseries_device {
struct hid_device *hdev;
@@ -34,6 +41,13 @@ struct steelseries_device {
uint8_t battery_capacity;
bool headset_connected;
bool battery_charging;
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[3];
+ struct mutex rgb_lock; /* protects rgb_buf */
+ u8 *rgb_buf;
+#endif
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -528,6 +542,140 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
hdev->rdesc[2] == 0xff;
}
+static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
+ },
+ },
+ { }
+};
+
+static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
+{
+ return interface_to_usbdev(to_usb_interface(hdev->dev.parent));
+}
+
+static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
+{
+ return to_usb_interface(hdev->dev.parent) ==
+ usb_ifnum_to_if(steelseries_hid_to_usb_dev(hdev), 0);
+}
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+
+static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct steelseries_device *sd = container_of(mc_cdev, struct steelseries_device, mc_cdev);
+ struct hid_device *hdev = sd->hdev;
+ struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
+ int i, ret;
+ u8 r, g, b;
+
+ static const u8 keys[] = {
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
+ 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
+ };
+ static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+
+ mutex_lock(&sd->rgb_lock);
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ r = mc_cdev->subled_info[0].brightness;
+ g = mc_cdev->subled_info[1].brightness;
+ b = mc_cdev->subled_info[2].brightness;
+
+ memset(sd->rgb_buf, 0, 524);
+ sd->rgb_buf[0] = 0x0c;
+ sd->rgb_buf[1] = 0x00;
+ sd->rgb_buf[3] = 0x00;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
+ sd->rgb_buf[2] = 0x66;
+ for (i = 0; i < ARRAY_SIZE(keys); i++) {
+ sd->rgb_buf[4 + i * 4] = keys[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ } else {
+ sd->rgb_buf[2] = 0x06;
+ for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
+ sd->rgb_buf[4 + i * 4] = alc_zones[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ }
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ HID_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x0300, 0,
+ sd->rgb_buf, 524, USB_CTRL_SET_TIMEOUT);
+
+ mutex_unlock(&sd->rgb_lock);
+ return ret < 0 ? ret : 0;
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ struct hid_device *hdev = sd->hdev;
+ struct led_classdev *led_cdev;
+
+ sd->rgb_buf = devm_kzalloc(&hdev->dev, 524, GFP_KERNEL);
+ if (!sd->rgb_buf)
+ return -ENOMEM;
+
+ mutex_init(&sd->rgb_lock);
+
+ sd->subled_info[0].color_index = LED_COLOR_ID_RED;
+ sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ sd->subled_info[0].intensity = 255;
+ sd->subled_info[1].intensity = 255;
+ sd->subled_info[2].intensity = 255;
+ sd->subled_info[0].channel = 0;
+ sd->subled_info[1].channel = 1;
+ sd->subled_info[2].channel = 2;
+
+ sd->mc_cdev.subled_info = sd->subled_info;
+ sd->mc_cdev.num_colors = 3;
+
+ led_cdev = &sd->mc_cdev.led_cdev;
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
+ led_cdev->name = "steelseries::kbd_backlight";
+ else
+ led_cdev->name = "steelseries::lightbar";
+
+ led_cdev->max_brightness = 255;
+ led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
+
+ return devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
+}
+#else
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ return -ENODEV;
+}
+#endif
+
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct steelseries_device *sd;
@@ -549,6 +697,12 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
sd->hdev = hdev;
sd->quirks = id->driver_data;
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
+ !steelseries_msi_rgb_is_interface0(hdev))
+ return -ENODEV;
+ }
+
ret = hid_parse(hdev);
if (ret)
return ret;
@@ -567,7 +721,15 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
if (ret)
return ret;
- if (steelseries_headset_battery_register(sd) < 0)
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ ret = steelseries_msi_rgb_register(sd);
+ if (ret)
+ hid_err(hdev, "Failed to register MSI RGB LEDs: %d\n", ret);
+ return ret;
+ }
+
+ if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
+ steelseries_headset_battery_register(sd) < 0)
hid_err(sd->hdev,
"Failed to register battery for headset\n");
@@ -635,7 +797,7 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
unsigned long flags;
/* Not a headset */
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ if (!(sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9)))
return 0;
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
@@ -732,6 +894,16 @@ static const struct hid_device_id steelseries_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = STEELSERIES_ARCTIS_9 },
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ { /* MSI Raider A18 KLC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_KLC),
+ .driver_data = STEELSERIES_MSI_RGB },
+
+ { /* MSI Raider A18 ALC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
+ .driver_data = STEELSERIES_MSI_RGB },
+#endif
+
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
2026-05-31 21:52 [PATCH] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support David Glushkov
@ 2026-05-31 22:06 ` sashiko-bot
2026-05-31 22:21 ` [PATCH v2] " David Glushkov
` (3 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-05-31 22:06 UTC (permalink / raw)
To: David Glushkov; +Cc: dmitry.torokhov, linux-input
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Type Confusion / Out-of-Bounds Read due to unsafe cast to `usb_interface`
- [High] `steelseries_remove()` unconditionally calls `cancel_delayed_work_sync()` on an uninitialized `delayed_work` struct.
- [Medium] Missing `hid_hw_stop()` and `hid_hw_close()` on probe error path.
- [Medium] Missing runtime PM management during direct USB control transfer, breaking LED updates when the device is idle.
--
commit 827dd1dcdf0039cad78b73365e7fd12bdc59d84e
Author: David Glushkov <david.glushkov@sntiq.com>
HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
This patch adds support for MSI Raider A18 HX A9WJG internal RGB devices
(KLC and ALC) to the steelseries HID driver. It exposes them as multicolor
LED class devices and sets uniform RGB values across zones.
> diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
> index f98435631aa18..0492814791753 100644
> --- a/drivers/hid/hid-steelseries.c
> +++ b/drivers/hid/hid-steelseries.c
[ ... ]
> @@ -528,6 +542,140 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
> hdev->rdesc[2] == 0xff;
> }
>
> +static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
> + {
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
> + DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
> + DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
> + },
> + },
> + { }
> +};
> +
> +static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
> +{
> + return interface_to_usbdev(to_usb_interface(hdev->dev.parent));
> +}
[Severity: High]
Could this cause an out-of-bounds read if the device is not a USB device?
If a local user injects a virtual HID device with the MSI RGB vendor and
product IDs via the uhid subsystem, the device's parent will be the uhid
device, not a usb_interface.
Calling to_usb_interface() and interface_to_usbdev() without first verifying
the transport using hid_is_usb(hdev) could interpret the uhid parent memory
as a usb_interface, leading to wild pointer dereferences and a panic.
> +
> +static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
> +{
> + return to_usb_interface(hdev->dev.parent) ==
> + usb_ifnum_to_if(steelseries_hid_to_usb_dev(hdev), 0);
> +}
> +
> +#if STEELSERIES_HAS_LEDS_MULTICOLOR
> +
> +static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
> + enum led_brightness brightness)
> +{
[ ... ]
> + if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
> + sd->rgb_buf[2] = 0x66;
> + for (i = 0; i < ARRAY_SIZE(keys); i++) {
> + sd->rgb_buf[4 + i * 4] = keys[i];
> + sd->rgb_buf[5 + i * 4] = r;
> + sd->rgb_buf[6 + i * 4] = g;
> + sd->rgb_buf[7 + i * 4] = b;
> + }
> + } else {
> + sd->rgb_buf[2] = 0x06;
> + for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
> + sd->rgb_buf[4 + i * 4] = alc_zones[i];
> + sd->rgb_buf[5 + i * 4] = r;
> + sd->rgb_buf[6 + i * 4] = g;
> + sd->rgb_buf[7 + i * 4] = b;
> + }
> + }
> +
> + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
> + HID_REQ_SET_REPORT,
> + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
> + 0x0300, 0,
> + sd->rgb_buf, 524, USB_CTRL_SET_TIMEOUT);
[Severity: Medium]
Will this USB control message fail if the device has entered runtime suspend?
Because this directly uses usb_control_msg() and bypasses the HID transport
layer, it doesn't appear to wake the device. Without explicitly waking the
device via hid_hw_power(hdev, PM_HINT_FULLON) or usb_autopm_get_interface(),
the transfer might drop LED updates when the keyboard is idle.
> +
> + mutex_unlock(&sd->rgb_lock);
> + return ret < 0 ? ret : 0;
> +}
[ ... ]
> @@ -567,7 +721,15 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
> if (ret)
> return ret;
>
> - if (steelseries_headset_battery_register(sd) < 0)
> + if (sd->quirks & STEELSERIES_MSI_RGB) {
> + ret = steelseries_msi_rgb_register(sd);
> + if (ret)
> + hid_err(hdev, "Failed to register MSI RGB LEDs: %d\n", ret);
> + return ret;
> + }
[Severity: Medium]
Does this error path leak the hardware state initialized by hid_hw_start()
and hid_hw_open() called earlier in steelseries_probe()?
Returning directly here skips calling hid_hw_stop() and hid_hw_close(),
which could leave active URBs and orphaned hidinput/hidraw device nodes
exposed to userspace.
> +
> + if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
> + steelseries_headset_battery_register(sd) < 0)
> hid_err(sd->hdev,
> "Failed to register battery for headset\n");
[Severity: High]
Since steelseries_headset_battery_register() and INIT_DELAYED_WORK() are
skipped for MSI RGB devices here, what happens when the device is removed?
If steelseries_remove() unconditionally calls cancel_delayed_work_sync() on
sd->battery_work, passing a zero-initialized work struct will trigger a
debug objects (ODEBUG) warning and splat on device removal.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260531215204.172030-1-david.glushkov@sntiq.com?part=1
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
2026-05-31 21:52 [PATCH] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support David Glushkov
2026-05-31 22:06 ` sashiko-bot
@ 2026-05-31 22:21 ` David Glushkov
2026-05-31 22:34 ` sashiko-bot
2026-05-31 22:53 ` [PATCH v3] " David Glushkov
` (2 subsequent siblings)
4 siblings, 1 reply; 8+ messages in thread
From: David Glushkov @ 2026-05-31 22:21 UTC (permalink / raw)
To: Jiri Kosina; +Cc: Benjamin Tissoires, linux-input, linux-kernel
---
v2:
- Fixed unsafe to_usb_interface cast by checking hid_is_usb() first.
- Fixed uninitialized delayed_work warning by restricting cancel_delayed_work_sync to headset devices.
- Fixed error path leaks in probe (hid_hw_stop / hid_hw_close).
- Added hid_hw_power PM wrappers around direct usb_control_msg transfers.
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-steelseries.c | 193 +++++++++++++++++++++++++++++++++-
2 files changed, 192 insertions(+), 3 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 4657d96fb..4af4397b8 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1367,6 +1367,8 @@
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
+#define USB_DEVICE_ID_STEELSERIES_MSI_KLC 0x1122
+#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f98435631..e8910b222 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -10,16 +10,23 @@
*/
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
#define STEELSERIES_ARCTIS_1 BIT(1)
#define STEELSERIES_ARCTIS_9 BIT(2)
+#define STEELSERIES_MSI_RGB BIT(3)
+
+#define STEELSERIES_HAS_LEDS_MULTICOLOR \
+ (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
struct steelseries_device {
struct hid_device *hdev;
@@ -34,6 +41,13 @@ struct steelseries_device {
uint8_t battery_capacity;
bool headset_connected;
bool battery_charging;
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[3];
+ struct mutex rgb_lock; /* protects rgb_buf */
+ u8 *rgb_buf;
+#endif
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -528,6 +542,150 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
hdev->rdesc[2] == 0xff;
}
+static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
+ },
+ },
+ { }
+};
+
+static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
+{
+ return interface_to_usbdev(to_usb_interface(hdev->dev.parent));
+}
+
+static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
+{
+ if (!hid_is_usb(hdev))
+ return false;
+
+ return to_usb_interface(hdev->dev.parent) ==
+ usb_ifnum_to_if(steelseries_hid_to_usb_dev(hdev), 0);
+}
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+
+static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct steelseries_device *sd = container_of(mc_cdev, struct steelseries_device, mc_cdev);
+ struct hid_device *hdev = sd->hdev;
+ struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
+ int i, ret;
+ u8 r, g, b;
+
+ static const u8 keys[] = {
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
+ 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
+ };
+ static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+
+ mutex_lock(&sd->rgb_lock);
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ r = mc_cdev->subled_info[0].brightness;
+ g = mc_cdev->subled_info[1].brightness;
+ b = mc_cdev->subled_info[2].brightness;
+
+ memset(sd->rgb_buf, 0, 524);
+ sd->rgb_buf[0] = 0x0c;
+ sd->rgb_buf[1] = 0x00;
+ sd->rgb_buf[3] = 0x00;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
+ sd->rgb_buf[2] = 0x66;
+ for (i = 0; i < ARRAY_SIZE(keys); i++) {
+ sd->rgb_buf[4 + i * 4] = keys[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ } else {
+ sd->rgb_buf[2] = 0x06;
+ for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
+ sd->rgb_buf[4 + i * 4] = alc_zones[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ }
+
+ ret = hid_hw_power(hdev, PM_HINT_FULLON);
+ if (ret < 0)
+ goto out_unlock;
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ HID_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x0300, 0,
+ sd->rgb_buf, 524, USB_CTRL_SET_TIMEOUT);
+
+ hid_hw_power(hdev, PM_HINT_NORMAL);
+
+out_unlock:
+ mutex_unlock(&sd->rgb_lock);
+ return ret < 0 ? ret : 0;
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ struct hid_device *hdev = sd->hdev;
+ struct led_classdev *led_cdev;
+
+ sd->rgb_buf = devm_kzalloc(&hdev->dev, 524, GFP_KERNEL);
+ if (!sd->rgb_buf)
+ return -ENOMEM;
+
+ mutex_init(&sd->rgb_lock);
+
+ sd->subled_info[0].color_index = LED_COLOR_ID_RED;
+ sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ sd->subled_info[0].intensity = 255;
+ sd->subled_info[1].intensity = 255;
+ sd->subled_info[2].intensity = 255;
+ sd->subled_info[0].channel = 0;
+ sd->subled_info[1].channel = 1;
+ sd->subled_info[2].channel = 2;
+
+ sd->mc_cdev.subled_info = sd->subled_info;
+ sd->mc_cdev.num_colors = 3;
+
+ led_cdev = &sd->mc_cdev.led_cdev;
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
+ led_cdev->name = "steelseries::kbd_backlight";
+ else
+ led_cdev->name = "steelseries::lightbar";
+
+ led_cdev->max_brightness = 255;
+ led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
+
+ return devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
+}
+#else
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ return -ENODEV;
+}
+#endif
+
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct steelseries_device *sd;
@@ -549,6 +707,12 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
sd->hdev = hdev;
sd->quirks = id->driver_data;
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
+ !steelseries_msi_rgb_is_interface0(hdev))
+ return -ENODEV;
+ }
+
ret = hid_parse(hdev);
if (ret)
return ret;
@@ -567,7 +731,17 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
if (ret)
return ret;
- if (steelseries_headset_battery_register(sd) < 0)
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ ret = steelseries_msi_rgb_register(sd);
+ if (ret) {
+ hid_err(hdev, "Failed to register MSI RGB LEDs: %d\n", ret);
+ goto err_close;
+ }
+ return 0;
+ }
+
+ if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
+ steelseries_headset_battery_register(sd) < 0)
hid_err(sd->hdev,
"Failed to register battery for headset\n");
@@ -593,7 +767,10 @@ static void steelseries_remove(struct hid_device *hdev)
sd->removed = true;
spin_unlock_irqrestore(&sd->lock, flags);
- cancel_delayed_work_sync(&sd->battery_work);
+ if (sd) {
+ if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9))
+ cancel_delayed_work_sync(&sd->battery_work);
+ }
hid_hw_close(hdev);
hid_hw_stop(hdev);
@@ -635,7 +812,7 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
unsigned long flags;
/* Not a headset */
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ if (!(sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9)))
return 0;
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
@@ -732,6 +909,16 @@ static const struct hid_device_id steelseries_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = STEELSERIES_ARCTIS_9 },
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ { /* MSI Raider A18 KLC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_KLC),
+ .driver_data = STEELSERIES_MSI_RGB },
+
+ { /* MSI Raider A18 ALC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
+ .driver_data = STEELSERIES_MSI_RGB },
+#endif
+
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
2026-05-31 22:21 ` [PATCH v2] " David Glushkov
@ 2026-05-31 22:34 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-05-31 22:34 UTC (permalink / raw)
To: David Glushkov; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Type confusion and undefined behavior for SRWS1 devices in steelseries_headset_raw_event().
- [High] DMA API violation: devm_kzalloc buffer passed to usb_control_msg() for DMA.
- [Low] Build failure due to missing 'err_close' label on the error path of steelseries_probe().
Pre-existing issues:
- [High] Kernel crash due to uninitialized workqueue object when battery registration fails.
--
commit a675d7bbdbccedaef6c839f514204764ec470211
Author: David Glushkov <david.glushkov@sntiq.com>
HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
This commit adds support for MSI Raider A18 HX A9WJG RGB keyboards via the
steelseries driver. It introduces multicolor LED support using a devres
managed buffer and adds device specific quirks mapping.
> diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
> --- a/drivers/hid/hid-steelseries.c
> +++ b/drivers/hid/hid-steelseries.c
[ ... ]
> +static int steelseries_msi_rgb_register(struct steelseries_device *sd)
> +{
> + struct hid_device *hdev = sd->hdev;
> + struct led_classdev *led_cdev;
> +
> + sd->rgb_buf = devm_kzalloc(&hdev->dev, 524, GFP_KERNEL);
[Severity: High]
Memory allocated with devm allocators includes a devres header, which breaks
cacheline alignment. Does passing this sd->rgb_buf to usb_control_msg() in
steelseries_msi_rgb_set_blocking() cause memory corruption on architectures
with non-coherent DMA?
> + if (!sd->rgb_buf)
> + return -ENOMEM;
[ ... ]
> @@ -567,7 +731,17 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
> if (ret)
> return ret;
>
> - if (steelseries_headset_battery_register(sd) < 0)
> + if (sd->quirks & STEELSERIES_MSI_RGB) {
> + ret = steelseries_msi_rgb_register(sd);
> + if (ret) {
> + hid_err(hdev, "Failed to register MSI RGB LEDs: %d\n", ret);
> + goto err_close;
[Severity: Low]
Since there is no err_close label defined in steelseries_probe() or the rest
of the file, does this goto statement cause a build failure?
> + }
> + return 0;
> + }
> +
> + if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
> + steelseries_headset_battery_register(sd) < 0)
> hid_err(sd->hdev,
> "Failed to register battery for headset\n");
[Severity: High]
This is a pre-existing issue, but if steelseries_headset_battery_register()
fails, the error is logged but probe successfully returns 0. This leaves
sd->battery_work zero-initialized.
Will a subsequent raw input event or device removal cause a kernel panic when
schedule_delayed_work() or cancel_delayed_work_sync() is called on this
uninitialized structure?
[ ... ]
> @@ -635,7 +812,7 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
> unsigned long flags;
>
> /* Not a headset */
> - if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
> + if (!(sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9)))
> return 0;
[Severity: High]
For SRWS1 devices, the driver data is a completely different structure, not
a struct steelseries_device. As a result, sd aliases a kernel pointer.
Does reading sd->battery_capacity earlier in the function, and checking
sd->quirks here, lead to type confusion and undefined behavior since it
replaces the safe hdev->product ID check?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260531222123.179923-1-david.glushkov@sntiq.com?part=1
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v3] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
2026-05-31 21:52 [PATCH] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support David Glushkov
2026-05-31 22:06 ` sashiko-bot
2026-05-31 22:21 ` [PATCH v2] " David Glushkov
@ 2026-05-31 22:53 ` David Glushkov
2026-05-31 23:12 ` sashiko-bot
2026-05-31 23:22 ` [PATCH v4] " David Glushkov
2026-05-31 23:39 ` [PATCH v5] " David Glushkov
4 siblings, 1 reply; 8+ messages in thread
From: David Glushkov @ 2026-05-31 22:53 UTC (permalink / raw)
To: Jiri Kosina; +Cc: Benjamin Tissoires, linux-input, linux-kernel
The MSI Raider A18 HX A9WJG exposes two internal SteelSeries USB HID
devices for RGB lighting: KLC (1038:1122) for the keyboard and ALC
(1038:1161) for the lightbar/logo zones.
Add DMI-gated support for these devices and expose them as multicolor
LED class devices. The driver sends the same HID class SET_REPORT
control transfer as the tested userspace implementation for this
machine and writes a uniform RGB value to all known keyboard keys or
ALC zones.
Limit binding to USB interface 0 and the tested DMI system because the
KLC product ID is shared across MSI laptop designs and the key layout
mapping is model-specific.
Tested on MSI Raider A18 HX A9WJG. Both internal SteelSeries ALC
(1038:1161) and KLC (1038:1122) HID devices bind on interface 0 and
create steelseries::lightbar and steelseries::kbd_backlight. Setting
multi_intensity and brightness changes the keyboard and lightbar
colors.
Signed-off-by: David Glushkov <david.glushkov@sntiq.com>
---
v3:
- Fix build failure (added missing err_close label to steelseries_probe).
- Fix DMA API violation (use kzalloc instead of devm_kzalloc for usb transfer buffer).
- Fix C syntax declaration-after-statement warning.
- Fix type confusion for SRWS1 (add early check in raw_event before hid_get_drvdata).
- Fix delayed_work crash (add battery_registered flag).
v2:
- Fixed unsafe to_usb_interface cast by checking hid_is_usb() first.
- Fixed uninitialized delayed_work warning by restricting cancel_delayed_work_sync to headset devices.
- Fixed error path leaks in probe (hid_hw_stop / hid_hw_close).
- Added hid_hw_power PM wrappers around direct usb_control_msg transfers.
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-steelseries.c | 267 ++++++++++++++++++++++++++++++++--
2 files changed, 260 insertions(+), 9 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 4657d96fb..4af4397b8 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1367,6 +1367,8 @@
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
+#define USB_DEVICE_ID_STEELSERIES_MSI_KLC 0x1122
+#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f98435631..3d3a92611 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -10,16 +10,25 @@
*/
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
#define STEELSERIES_ARCTIS_1 BIT(1)
#define STEELSERIES_ARCTIS_9 BIT(2)
+#define STEELSERIES_MSI_RGB BIT(3)
+
+#define STEELSERIES_MSI_RGB_REPORT_LEN 524
+
+#define STEELSERIES_HAS_LEDS_MULTICOLOR \
+ (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
struct steelseries_device {
struct hid_device *hdev;
@@ -34,6 +43,14 @@ struct steelseries_device {
uint8_t battery_capacity;
bool headset_connected;
bool battery_charging;
+ bool battery_registered;
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[3];
+ struct mutex rgb_lock; /* protects rgb_buf */
+ u8 *rgb_buf;
+#endif
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -510,6 +527,7 @@ 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);
+ sd->battery_registered = true;
steelseries_headset_fetch_battery(sd->hdev);
if (sd->quirks & STEELSERIES_ARCTIS_9) {
@@ -528,6 +546,188 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
hdev->rdesc[2] == 0xff;
}
+static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
+ },
+ },
+ { }
+};
+
+static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev)
+{
+ if (!hid_is_usb(hdev))
+ return NULL;
+
+ return to_usb_interface(hdev->dev.parent);
+}
+
+static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+
+ if (!intf)
+ return NULL;
+
+ return interface_to_usbdev(intf);
+}
+
+static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+ struct usb_device *udev;
+
+ if (!intf)
+ return false;
+
+ udev = interface_to_usbdev(intf);
+
+ return intf == usb_ifnum_to_if(udev, 0);
+}
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+
+static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct steelseries_device *sd = container_of(mc_cdev, struct steelseries_device, mc_cdev);
+ struct hid_device *hdev = sd->hdev;
+ struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
+ int i, ret;
+
+ u8 r, g, b;
+
+ static const u8 keys[] = {
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
+ 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
+ };
+ static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+\n if (!udev)
+ return -ENODEV;
+
+ mutex_lock(&sd->rgb_lock);
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ r = mc_cdev->subled_info[0].brightness;
+ g = mc_cdev->subled_info[1].brightness;
+ b = mc_cdev->subled_info[2].brightness;
+
+ memset(sd->rgb_buf, 0, STEELSERIES_MSI_RGB_REPORT_LEN);
+ sd->rgb_buf[0] = 0x0c;
+ sd->rgb_buf[1] = 0x00;
+ sd->rgb_buf[3] = 0x00;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
+ sd->rgb_buf[2] = 0x66;
+ for (i = 0; i < ARRAY_SIZE(keys); i++) {
+ sd->rgb_buf[4 + i * 4] = keys[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ } else {
+ sd->rgb_buf[2] = 0x06;
+ for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
+ sd->rgb_buf[4 + i * 4] = alc_zones[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ }
+
+ ret = hid_hw_power(hdev, PM_HINT_FULLON);
+ if (ret < 0)
+ goto out_unlock;
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ HID_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x0300, 0,
+ sd->rgb_buf, STEELSERIES_MSI_RGB_REPORT_LEN, USB_CTRL_SET_TIMEOUT);
+
+ hid_hw_power(hdev, PM_HINT_NORMAL);
+
+out_unlock:
+ mutex_unlock(&sd->rgb_lock);
+ return ret < 0 ? ret : 0;
+}
+
+static void steelseries_msi_rgb_free_buf(void *data)
+{
+ kfree(data);
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ struct hid_device *hdev = sd->hdev;
+ struct led_classdev *led_cdev;
+
+ int ret;
+
+ sd->rgb_buf = kzalloc(STEELSERIES_MSI_RGB_REPORT_LEN, GFP_KERNEL);
+ if (!sd->rgb_buf)
+ return -ENOMEM;
+
+ ret = devm_add_action_or_reset(&hdev->dev,
+ steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ if (ret)
+ return ret;
+
+ mutex_init(&sd->rgb_lock);
+
+ sd->subled_info[0].color_index = LED_COLOR_ID_RED;
+ sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ sd->subled_info[0].intensity = 255;
+ sd->subled_info[1].intensity = 255;
+ sd->subled_info[2].intensity = 255;
+ sd->subled_info[0].channel = 0;
+ sd->subled_info[1].channel = 1;
+ sd->subled_info[2].channel = 2;
+
+ sd->mc_cdev.subled_info = sd->subled_info;
+ sd->mc_cdev.num_colors = 3;
+
+ led_cdev = &sd->mc_cdev.led_cdev;
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
+ led_cdev->name = "steelseries::kbd_backlight";
+ else
+ led_cdev->name = "steelseries::lightbar";
+
+ led_cdev->max_brightness = 255;
+ led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
+
+ return devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
+}
+#else
+static void steelseries_msi_rgb_free_buf(void *data)
+{
+ kfree(data);
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ return -ENODEV;
+}
+#endif
+
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct steelseries_device *sd;
@@ -549,6 +749,12 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
sd->hdev = hdev;
sd->quirks = id->driver_data;
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
+ !steelseries_msi_rgb_is_interface0(hdev))
+ return -ENODEV;
+ }
+
ret = hid_parse(hdev);
if (ret)
return ret;
@@ -565,12 +771,28 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
ret = hid_hw_open(hdev);
if (ret)
- return ret;
+ goto err_stop;
+
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ ret = steelseries_msi_rgb_register(sd);
+ if (ret) {
+ hid_err(hdev, "Failed to register MSI RGB LEDs: %d\n", ret);
+ goto err_close;
+ }
+ return 0;
+ }
- if (steelseries_headset_battery_register(sd) < 0)
+ if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
+ steelseries_headset_battery_register(sd) < 0)
hid_err(sd->hdev,
"Failed to register battery for headset\n");
+ return 0;
+
+err_close:
+ hid_hw_close(hdev);
+err_stop:
+ hid_hw_stop(hdev);
return ret;
}
@@ -588,12 +810,15 @@ static void steelseries_remove(struct hid_device *hdev)
}
sd = hid_get_drvdata(hdev);
+ if (!sd)
+ return;
spin_lock_irqsave(&sd->lock, flags);
sd->removed = true;
spin_unlock_irqrestore(&sd->lock, flags);
- cancel_delayed_work_sync(&sd->battery_work);
+ if (sd->battery_registered)
+ cancel_delayed_work_sync(&sd->battery_work);
hid_hw_close(hdev);
hid_hw_stop(hdev);
@@ -624,20 +849,34 @@ static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in
return (capacity - min_in) * 100 / (max_in - min_in);
}
+static bool steelseries_is_headset(struct hid_device *hdev)
+{
+ return hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+ hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9;
+}
+
static int steelseries_headset_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *read_buf,
int size)
{
- struct steelseries_device *sd = hid_get_drvdata(hdev);
- int capacity = sd->battery_capacity;
- bool connected = sd->headset_connected;
- bool charging = sd->battery_charging;
+ struct steelseries_device *sd;
+ int capacity;
+ bool connected;
+ bool charging;
unsigned long flags;
- /* Not a headset */
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ if (!steelseries_is_headset(hdev))
+ return 0;
+
+ sd = hid_get_drvdata(hdev);
+ if (!sd || !sd->battery_registered)
return 0;
+ capacity = sd->battery_capacity;
+ connected = sd->headset_connected;
+ charging = sd->battery_charging;
+
+
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
@@ -732,6 +971,16 @@ static const struct hid_device_id steelseries_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = STEELSERIES_ARCTIS_9 },
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ { /* MSI Raider A18 KLC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_KLC),
+ .driver_data = STEELSERIES_MSI_RGB },
+
+ { /* MSI Raider A18 ALC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
+ .driver_data = STEELSERIES_MSI_RGB },
+#endif
+
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v3] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
2026-05-31 22:53 ` [PATCH v3] " David Glushkov
@ 2026-05-31 23:12 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-05-31 23:12 UTC (permalink / raw)
To: David Glushkov; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Returning `-ENODEV` prevents `hid-generic` fallback, permanently breaking the keyboard on other laptops and breaking unsupported interfaces.
- [Low] Unused static function `steelseries_msi_rgb_free_buf` in the `#else` fallback block causes a compiler warning.
--
commit 6fad0ba2106ff9dc9bf3d9ad4063720dca63e6b1
Author: David Glushkov <david.glushkov@sntiq.com>
HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
This commit adds DMI-gated support for MSI Raider A18 HX A9WJG internal
SteelSeries USB HID devices. The driver exposes them as multicolor LED
class devices on USB interface 0.
> diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
> index f98435631aa18..f0ba59bb28f97 100644
> --- a/drivers/hid/hid-steelseries.c
> +++ b/drivers/hid/hid-steelseries.c
[ ... ]
> @@ -546,6 +546,188 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
[ ... ]
> +#else
> +static void steelseries_msi_rgb_free_buf(void *data)
> +{
> + kfree(data);
> +}
> +
> +static int steelseries_msi_rgb_register(struct steelseries_device *sd)
> +{
> + return -ENODEV;
> +}
> +#endif
[Severity: Low]
Is steelseries_msi_rgb_free_buf() ever called when
CONFIG_LEDS_CLASS_MULTICOLOR is disabled?
Since it is a static function, defining it without a caller in the #else
fallback block will trigger a -Wunused-function compiler warning.
> @@ -549,6 +749,12 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
> sd->hdev = hdev;
> sd->quirks = id->driver_data;
>
> + if (sd->quirks & STEELSERIES_MSI_RGB) {
> + if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
> + !steelseries_msi_rgb_is_interface0(hdev))
> + return -ENODEV;
> + }
[Severity: High]
Does returning -ENODEV here prevent the device from functioning as a standard
keyboard on other laptops or on unsupported interfaces?
Because the device's USB IDs are explicitly listed in the steelseries_devices[]
id_table, the HID core's fallback logic prevents hid-generic from binding to it.
Returning -ENODEV instead of clearing the quirk flag and continuing
initialization abandons the device entirely.
> +
> ret = hid_parse(hdev);
> if (ret)
> return ret;
[ ... ]
> @@ -565,12 +771,28 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
>
> ret = hid_hw_open(hdev);
> if (ret)
> - return ret;
> + goto err_stop;
> +
> + if (sd->quirks & STEELSERIES_MSI_RGB) {
> + ret = steelseries_msi_rgb_register(sd);
> + if (ret) {
> + hid_err(hdev, "Failed to register MSI RGB LEDs: %d\n", ret);
> + goto err_close;
> + }
> + return 0;
> + }
[Severity: High]
Similarly, if LED registration fails (for example, out of memory, or if the LED
subsystem is disabled and returns -ENODEV), does returning an error here also
leave the device completely unbound?
>
> - if (steelseries_headset_battery_register(sd) < 0)
> + if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
> + steelseries_headset_battery_register(sd) < 0)
> hid_err(sd->hdev,
> "Failed to register battery for headset\n");
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260531225329.201327-1-david.glushkov@sntiq.com?part=1
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v4] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
2026-05-31 21:52 [PATCH] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support David Glushkov
` (2 preceding siblings ...)
2026-05-31 22:53 ` [PATCH v3] " David Glushkov
@ 2026-05-31 23:22 ` David Glushkov
2026-05-31 23:39 ` [PATCH v5] " David Glushkov
4 siblings, 0 replies; 8+ messages in thread
From: David Glushkov @ 2026-05-31 23:22 UTC (permalink / raw)
To: Jiri Kosina; +Cc: Benjamin Tissoires, linux-input, linux-kernel
The MSI Raider A18 HX A9WJG exposes two internal SteelSeries USB HID
devices for RGB lighting: KLC (1038:1122) for the keyboard and ALC
(1038:1161) for the lightbar/logo zones.
Add DMI-gated support for these devices and expose them as multicolor
LED class devices. The driver sends the same HID class SET_REPORT
control transfer as the tested userspace implementation for this
machine and writes a uniform RGB value to all known keyboard keys or
ALC zones.
Limit RGB support to USB interface 0 and the tested DMI system because
the KLC product ID is shared across MSI laptop designs and the key
layout mapping is model-specific. If the DMI or interface check does
not match, keep the device bound as a regular HID device instead of
failing probe.
Tested on MSI Raider A18 HX A9WJG. Both internal SteelSeries ALC
(1038:1161) and KLC (1038:1122) HID devices bind on interface 0 and
create steelseries::lightbar and steelseries::kbd_backlight. Setting
multi_intensity and brightness changes the keyboard and lightbar
colors.
Signed-off-by: David Glushkov <david.glushkov@sntiq.com>
---
v4:
- Fix literal \n typo in C code.
- Fix trailing whitespace and style nits.
- Remove unused steelseries_msi_rgb_free_buf from #else block.
- Do not fail probe on unsupported DMI/interface; clear MSI RGB quirk
and continue normal HID initialization.
- Do not fail probe when RGB LED registration fails; keep the HID
device usable without RGB LED support.
- Add explicit linux/slab.h include for kzalloc/kfree.
v3:
- Fix build failure (added missing err_close label to steelseries_probe).
- Fix DMA API violation (use kzalloc instead of devm_kzalloc for usb
transfer buffer).
- Fix C syntax declaration-after-statement warning.
- Fix type confusion for SRWS1 (add early check in raw_event before
hid_get_drvdata).
- Fix delayed_work crash (add battery_registered flag).
v2:
- Fixed unsafe to_usb_interface cast by checking hid_is_usb() first.
- Fixed uninitialized delayed_work warning by restricting
cancel_delayed_work_sync to headset devices.
- Fixed error path leaks in probe (hid_hw_stop / hid_hw_close).
- Added hid_hw_power PM wrappers around direct usb_control_msg transfers.
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-steelseries.c | 265 ++++++++++++++++++++++++++++++++--
2 files changed, 256 insertions(+), 11 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 4657d96fb..4af4397b8 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1367,6 +1367,8 @@
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
+#define USB_DEVICE_ID_STEELSERIES_MSI_KLC 0x1122
+#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f98435631..3a4f4c2b1 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -10,16 +10,26 @@
*/
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/slab.h>
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
#define STEELSERIES_ARCTIS_1 BIT(1)
#define STEELSERIES_ARCTIS_9 BIT(2)
+#define STEELSERIES_MSI_RGB BIT(3)
+
+#define STEELSERIES_MSI_RGB_REPORT_LEN 524
+
+#define STEELSERIES_HAS_LEDS_MULTICOLOR \
+ (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
struct steelseries_device {
struct hid_device *hdev;
@@ -34,6 +44,14 @@ struct steelseries_device {
uint8_t battery_capacity;
bool headset_connected;
bool battery_charging;
+ bool battery_registered;
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[3];
+ struct mutex rgb_lock; /* protects rgb_buf */
+ u8 *rgb_buf;
+#endif
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -510,6 +528,7 @@ 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);
+ sd->battery_registered = true;
steelseries_headset_fetch_battery(sd->hdev);
if (sd->quirks & STEELSERIES_ARCTIS_9) {
@@ -528,6 +547,190 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
hdev->rdesc[2] == 0xff;
}
+static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
+ },
+ },
+ { }
+};
+
+static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev)
+{
+ if (!hid_is_usb(hdev))
+ return NULL;
+
+ return to_usb_interface(hdev->dev.parent);
+}
+
+static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+
+ if (!intf)
+ return NULL;
+
+ return interface_to_usbdev(intf);
+}
+
+static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+ struct usb_device *udev;
+
+ if (!intf)
+ return false;
+
+ udev = interface_to_usbdev(intf);
+
+ return intf == usb_ifnum_to_if(udev, 0);
+}
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+
+static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct steelseries_device *sd = container_of(mc_cdev,
+ struct steelseries_device,
+ mc_cdev);
+ struct hid_device *hdev = sd->hdev;
+ struct usb_device *udev;
+ u8 r, g, b;
+ int i, ret, pm_ret;
+
+ static const u8 keys[] = {
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
+ 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
+ };
+ static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+
+ udev = steelseries_hid_to_usb_dev(hdev);
+ if (!udev)
+ return -ENODEV;
+
+ mutex_lock(&sd->rgb_lock);
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ r = mc_cdev->subled_info[0].brightness;
+ g = mc_cdev->subled_info[1].brightness;
+ b = mc_cdev->subled_info[2].brightness;
+
+ memset(sd->rgb_buf, 0, STEELSERIES_MSI_RGB_REPORT_LEN);
+ sd->rgb_buf[0] = 0x0c;
+ sd->rgb_buf[1] = 0x00;
+ sd->rgb_buf[3] = 0x00;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
+ sd->rgb_buf[2] = 0x66;
+ for (i = 0; i < ARRAY_SIZE(keys); i++) {
+ sd->rgb_buf[4 + i * 4] = keys[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ } else {
+ sd->rgb_buf[2] = 0x06;
+ for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
+ sd->rgb_buf[4 + i * 4] = alc_zones[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ }
+
+ ret = hid_hw_power(hdev, PM_HINT_FULLON);
+ if (ret < 0)
+ goto out_unlock;
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ HID_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x0300, 0,
+ sd->rgb_buf, STEELSERIES_MSI_RGB_REPORT_LEN,
+ USB_CTRL_SET_TIMEOUT);
+
+ pm_ret = hid_hw_power(hdev, PM_HINT_NORMAL);
+ if (ret >= 0)
+ ret = pm_ret;
+
+out_unlock:
+ mutex_unlock(&sd->rgb_lock);
+ return ret < 0 ? ret : 0;
+}
+
+static void steelseries_msi_rgb_free_buf(void *data)
+{
+ kfree(data);
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ struct hid_device *hdev = sd->hdev;
+ struct led_classdev *led_cdev;
+ int ret;
+
+ sd->rgb_buf = kzalloc(STEELSERIES_MSI_RGB_REPORT_LEN, GFP_KERNEL);
+ if (!sd->rgb_buf)
+ return -ENOMEM;
+
+ ret = devm_add_action_or_reset(&hdev->dev,
+ steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ if (ret) {
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ mutex_init(&sd->rgb_lock);
+
+ sd->subled_info[0].color_index = LED_COLOR_ID_RED;
+ sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ sd->subled_info[0].intensity = 255;
+ sd->subled_info[1].intensity = 255;
+ sd->subled_info[2].intensity = 255;
+ sd->subled_info[0].channel = 0;
+ sd->subled_info[1].channel = 1;
+ sd->subled_info[2].channel = 2;
+
+ sd->mc_cdev.subled_info = sd->subled_info;
+ sd->mc_cdev.num_colors = 3;
+
+ led_cdev = &sd->mc_cdev.led_cdev;
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
+ led_cdev->name = "steelseries::kbd_backlight";
+ else
+ led_cdev->name = "steelseries::lightbar";
+
+ led_cdev->max_brightness = 255;
+ led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
+
+ return devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
+}
+#else
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ return -ENODEV;
+}
+#endif
+
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct steelseries_device *sd;
@@ -549,6 +752,14 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
sd->hdev = hdev;
sd->quirks = id->driver_data;
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
+ !steelseries_msi_rgb_is_interface0(hdev)) {
+ hid_dbg(hdev, "MSI RGB quirk not applicable, using generic HID path\n");
+ sd->quirks &= ~STEELSERIES_MSI_RGB;
+ }
+ }
+
ret = hid_parse(hdev);
if (ret)
return ret;
@@ -565,12 +776,30 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
ret = hid_hw_open(hdev);
if (ret)
- return ret;
+ goto err_stop;
- if (steelseries_headset_battery_register(sd) < 0)
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ ret = steelseries_msi_rgb_register(sd);
+ if (ret) {
+ hid_warn(hdev,
+ "Failed to register MSI RGB LEDs: %d, continuing without RGB support\n",
+ ret);
+ sd->quirks &= ~STEELSERIES_MSI_RGB;
+ }
+ return 0;
+ }
+
+ if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
+ steelseries_headset_battery_register(sd) < 0)
hid_err(sd->hdev,
"Failed to register battery for headset\n");
+ return 0;
+
+err_stop:
+ hid_hw_stop(hdev);
return ret;
}
@@ -588,12 +817,15 @@ static void steelseries_remove(struct hid_device *hdev)
}
sd = hid_get_drvdata(hdev);
+ if (!sd)
+ return;
spin_lock_irqsave(&sd->lock, flags);
sd->removed = true;
spin_unlock_irqrestore(&sd->lock, flags);
- cancel_delayed_work_sync(&sd->battery_work);
+ if (sd->battery_registered)
+ cancel_delayed_work_sync(&sd->battery_work);
hid_hw_close(hdev);
hid_hw_stop(hdev);
@@ -624,20 +856,33 @@ static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in
return (capacity - min_in) * 100 / (max_in - min_in);
}
+static bool steelseries_is_headset(struct hid_device *hdev)
+{
+ return hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+ hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9;
+}
+
static int steelseries_headset_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *read_buf,
int size)
{
- struct steelseries_device *sd = hid_get_drvdata(hdev);
- int capacity = sd->battery_capacity;
- bool connected = sd->headset_connected;
- bool charging = sd->battery_charging;
+ struct steelseries_device *sd;
+ int capacity;
+ bool connected;
+ bool charging;
unsigned long flags;
- /* Not a headset */
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ if (!steelseries_is_headset(hdev))
+ return 0;
+
+ sd = hid_get_drvdata(hdev);
+ if (!sd || !sd->battery_registered)
return 0;
+ capacity = sd->battery_capacity;
+ connected = sd->headset_connected;
+ charging = sd->battery_charging;
+
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
@@ -732,6 +977,16 @@ static const struct hid_device_id steelseries_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = STEELSERIES_ARCTIS_9 },
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ { /* MSI Raider A18 KLC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_KLC),
+ .driver_data = STEELSERIES_MSI_RGB },
+
+ { /* MSI Raider A18 ALC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
+ .driver_data = STEELSERIES_MSI_RGB },
+#endif
+
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v5] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
2026-05-31 21:52 [PATCH] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support David Glushkov
` (3 preceding siblings ...)
2026-05-31 23:22 ` [PATCH v4] " David Glushkov
@ 2026-05-31 23:39 ` David Glushkov
4 siblings, 0 replies; 8+ messages in thread
From: David Glushkov @ 2026-05-31 23:39 UTC (permalink / raw)
To: Jiri Kosina; +Cc: Benjamin Tissoires, linux-input, linux-kernel
The MSI Raider A18 HX A9WJG exposes two internal SteelSeries USB HID
devices for RGB lighting: KLC (1038:1122) for the keyboard and ALC
(1038:1161) for the lightbar/logo zones.
Add DMI-gated support for these devices and expose them as multicolor
LED class devices. The driver sends the same HID class SET_REPORT
control transfer as the tested userspace implementation for this
machine and writes a uniform RGB value to all known keyboard keys or
ALC zones.
Limit RGB support to USB interface 0 and the tested DMI system because
the KLC product ID is shared across MSI laptop designs and the key
layout mapping is model-specific. If the DMI or interface check does
not match, keep the device bound as a regular HID device instead of
failing probe.
Tested on MSI Raider A18 HX A9WJG. Both internal SteelSeries ALC
(1038:1161) and KLC (1038:1122) HID devices bind on interface 0 and
create steelseries::lightbar and steelseries::kbd_backlight. Setting
multi_intensity and brightness changes the keyboard and lightbar
colors.
Signed-off-by: David Glushkov <david.glushkov@sntiq.com>
---
v5:
- Drop pm_ret handling and ignore PM_HINT_NORMAL cleanup errors.
- Fix LED registration error handling to clean up rgb_buf on failure.
- Fix trailing whitespaces and formatting style nits.
- Update commit message to accurately reflect the DMI fallback behavior.
v4:
- Fix literal \n typo in C code.
- Remove unused steelseries_msi_rgb_free_buf from #else block.
- Do not fail probe on unsupported DMI/interface; clear MSI RGB quirk and continue normal HID initialization.
- Do not fail probe when RGB LED registration fails; keep the HID device usable without RGB LED support.
- Add explicit linux/slab.h include for kzalloc/kfree.
v3:
- Fix build failure (added missing err_close label to steelseries_probe).
- Fix DMA API violation (use kzalloc instead of devm_kzalloc for usb transfer buffer).
- Fix C syntax declaration-after-statement warning.
- Fix type confusion for SRWS1 (add early check in raw_event before hid_get_drvdata).
- Fix delayed_work crash (add battery_registered flag).
v2:
- Fixed unsafe to_usb_interface cast by checking hid_is_usb() first.
- Fixed uninitialized delayed_work warning by restricting cancel_delayed_work_sync to headset devices.
- Fixed error path leaks in probe (hid_hw_stop / hid_hw_close).
- Added hid_hw_power PM wrappers around direct usb_control_msg transfers.
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-steelseries.c | 277 ++++++++++++++++++++++++++++++++--
2 files changed, 270 insertions(+), 9 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 4657d96fb..4af4397b8 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1367,6 +1367,8 @@
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
+#define USB_DEVICE_ID_STEELSERIES_MSI_KLC 0x1122
+#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f98435631..c269db06d 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -10,16 +10,26 @@
*/
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/slab.h>
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
#define STEELSERIES_ARCTIS_1 BIT(1)
#define STEELSERIES_ARCTIS_9 BIT(2)
+#define STEELSERIES_MSI_RGB BIT(3)
+
+#define STEELSERIES_MSI_RGB_REPORT_LEN 524
+
+#define STEELSERIES_HAS_LEDS_MULTICOLOR \
+ (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
struct steelseries_device {
struct hid_device *hdev;
@@ -34,6 +44,14 @@ struct steelseries_device {
uint8_t battery_capacity;
bool headset_connected;
bool battery_charging;
+ bool battery_registered;
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[3];
+ struct mutex rgb_lock; /* protects rgb_buf */
+ u8 *rgb_buf;
+#endif
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -510,6 +528,7 @@ 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);
+ sd->battery_registered = true;
steelseries_headset_fetch_battery(sd->hdev);
if (sd->quirks & STEELSERIES_ARCTIS_9) {
@@ -528,6 +547,196 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
hdev->rdesc[2] == 0xff;
}
+static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
+ },
+ },
+ { }
+};
+
+static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev)
+{
+ if (!hid_is_usb(hdev))
+ return NULL;
+
+ return to_usb_interface(hdev->dev.parent);
+}
+
+static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+
+ if (!intf)
+ return NULL;
+
+ return interface_to_usbdev(intf);
+}
+
+static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+ struct usb_device *udev;
+
+ if (!intf)
+ return false;
+
+ udev = interface_to_usbdev(intf);
+
+ return intf == usb_ifnum_to_if(udev, 0);
+}
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+
+static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct steelseries_device *sd = container_of(mc_cdev,
+ struct steelseries_device,
+ mc_cdev);
+ struct hid_device *hdev = sd->hdev;
+ struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
+ int i, ret;
+ u8 r, g, b;
+
+ static const u8 keys[] = {
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
+ 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
+ };
+ static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+
+ if (!udev)
+ return -ENODEV;
+
+ mutex_lock(&sd->rgb_lock);
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ r = mc_cdev->subled_info[0].brightness;
+ g = mc_cdev->subled_info[1].brightness;
+ b = mc_cdev->subled_info[2].brightness;
+
+ memset(sd->rgb_buf, 0, STEELSERIES_MSI_RGB_REPORT_LEN);
+ sd->rgb_buf[0] = 0x0c;
+ sd->rgb_buf[1] = 0x00;
+ sd->rgb_buf[3] = 0x00;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
+ sd->rgb_buf[2] = 0x66;
+ for (i = 0; i < ARRAY_SIZE(keys); i++) {
+ sd->rgb_buf[4 + i * 4] = keys[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ } else {
+ sd->rgb_buf[2] = 0x06;
+ for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
+ sd->rgb_buf[4 + i * 4] = alc_zones[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ }
+
+ ret = hid_hw_power(hdev, PM_HINT_FULLON);
+ if (ret < 0)
+ goto out_unlock;
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ HID_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x0300, 0,
+ sd->rgb_buf, STEELSERIES_MSI_RGB_REPORT_LEN,
+ USB_CTRL_SET_TIMEOUT);
+
+ hid_hw_power(hdev, PM_HINT_NORMAL);
+
+out_unlock:
+ mutex_unlock(&sd->rgb_lock);
+ return ret < 0 ? ret : 0;
+}
+
+static void steelseries_msi_rgb_free_buf(void *data)
+{
+ kfree(data);
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ struct hid_device *hdev = sd->hdev;
+ struct led_classdev *led_cdev;
+ int ret;
+
+ sd->rgb_buf = kzalloc(STEELSERIES_MSI_RGB_REPORT_LEN, GFP_KERNEL);
+ if (!sd->rgb_buf)
+ return -ENOMEM;
+
+ ret = devm_add_action_or_reset(&hdev->dev,
+ steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ if (ret) {
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ mutex_init(&sd->rgb_lock);
+
+ sd->subled_info[0].color_index = LED_COLOR_ID_RED;
+ sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ sd->subled_info[0].intensity = 255;
+ sd->subled_info[1].intensity = 255;
+ sd->subled_info[2].intensity = 255;
+ sd->subled_info[0].channel = 0;
+ sd->subled_info[1].channel = 1;
+ sd->subled_info[2].channel = 2;
+
+ sd->mc_cdev.subled_info = sd->subled_info;
+ sd->mc_cdev.num_colors = 3;
+
+ led_cdev = &sd->mc_cdev.led_cdev;
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
+ led_cdev->name = "steelseries::kbd_backlight";
+ else
+ led_cdev->name = "steelseries::lightbar";
+
+ led_cdev->max_brightness = 255;
+ led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
+
+ ret = devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
+ if (ret) {
+ devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ kfree(sd->rgb_buf);
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+#else
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ return -ENODEV;
+}
+#endif
+
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct steelseries_device *sd;
@@ -549,6 +758,14 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
sd->hdev = hdev;
sd->quirks = id->driver_data;
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
+ !steelseries_msi_rgb_is_interface0(hdev)) {
+ hid_dbg(hdev, "MSI RGB quirk not applicable, using generic HID path\n");
+ sd->quirks &= ~STEELSERIES_MSI_RGB;
+ }
+ }
+
ret = hid_parse(hdev);
if (ret)
return ret;
@@ -565,12 +782,28 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
ret = hid_hw_open(hdev);
if (ret)
- return ret;
+ goto err_stop;
+
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ ret = steelseries_msi_rgb_register(sd);
+ if (ret) {
+ hid_warn(hdev,
+ "Failed to register MSI RGB LEDs: %d, continuing without RGB support\n",
+ ret);
+ sd->quirks &= ~STEELSERIES_MSI_RGB;
+ }
+ return 0;
+ }
- if (steelseries_headset_battery_register(sd) < 0)
+ if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
+ steelseries_headset_battery_register(sd) < 0)
hid_err(sd->hdev,
"Failed to register battery for headset\n");
+ return 0;
+
+err_stop:
+ hid_hw_stop(hdev);
return ret;
}
@@ -588,12 +821,15 @@ static void steelseries_remove(struct hid_device *hdev)
}
sd = hid_get_drvdata(hdev);
+ if (!sd)
+ return;
spin_lock_irqsave(&sd->lock, flags);
sd->removed = true;
spin_unlock_irqrestore(&sd->lock, flags);
- cancel_delayed_work_sync(&sd->battery_work);
+ if (sd->battery_registered)
+ cancel_delayed_work_sync(&sd->battery_work);
hid_hw_close(hdev);
hid_hw_stop(hdev);
@@ -624,20 +860,33 @@ static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in
return (capacity - min_in) * 100 / (max_in - min_in);
}
+static bool steelseries_is_headset(struct hid_device *hdev)
+{
+ return hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+ hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9;
+}
+
static int steelseries_headset_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *read_buf,
int size)
{
- struct steelseries_device *sd = hid_get_drvdata(hdev);
- int capacity = sd->battery_capacity;
- bool connected = sd->headset_connected;
- bool charging = sd->battery_charging;
+ struct steelseries_device *sd;
+ int capacity;
+ bool connected;
+ bool charging;
unsigned long flags;
- /* Not a headset */
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ if (!steelseries_is_headset(hdev))
+ return 0;
+
+ sd = hid_get_drvdata(hdev);
+ if (!sd || !sd->battery_registered)
return 0;
+ capacity = sd->battery_capacity;
+ connected = sd->headset_connected;
+ charging = sd->battery_charging;
+
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
@@ -732,6 +981,16 @@ static const struct hid_device_id steelseries_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = STEELSERIES_ARCTIS_9 },
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ { /* MSI Raider A18 KLC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_KLC),
+ .driver_data = STEELSERIES_MSI_RGB },
+
+ { /* MSI Raider A18 ALC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
+ .driver_data = STEELSERIES_MSI_RGB },
+#endif
+
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-05-31 23:41 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-31 21:52 [PATCH] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support David Glushkov
2026-05-31 22:06 ` sashiko-bot
2026-05-31 22:21 ` [PATCH v2] " David Glushkov
2026-05-31 22:34 ` sashiko-bot
2026-05-31 22:53 ` [PATCH v3] " David Glushkov
2026-05-31 23:12 ` sashiko-bot
2026-05-31 23:22 ` [PATCH v4] " David Glushkov
2026-05-31 23:39 ` [PATCH v5] " David Glushkov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox