summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-26 21:52:11 +0100
committerMark Brown <broonie@kernel.org>2026-07-26 21:52:11 +0100
commitc64ed6d2698bde8d64fc37dfa200a3ec5e0a08b8 (patch)
tree52494cf7dc4cd6b80426778cd2ba0ed2507f811f
parent867d2f171800453d242fa9c293c4d9d4ec9dcdf0 (diff)
parent194a48576843858afcd9c00ed4f105ec2b398e6c (diff)
downloadlinux-next-c64ed6d2698bde8d64fc37dfa200a3ec5e0a08b8.tar.gz
linux-next-c64ed6d2698bde8d64fc37dfa200a3ec5e0a08b8.zip
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git
-rw-r--r--drivers/hid/Kconfig9
-rw-r--r--drivers/hid/hid-core.c7
-rw-r--r--drivers/hid/hid-ids.h2
-rw-r--r--drivers/hid/hid-nintendo.c1
-rw-r--r--drivers/hid/hid-roccat-kone.c65
-rw-r--r--drivers/hid/hid-sony.c245
-rw-r--r--drivers/hid/hid-steelseries.c313
-rw-r--r--drivers/hid/wacom_wac.c8
8 files changed, 516 insertions, 134 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index f9bcaeb66385..03f36899e458 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -1079,6 +1079,15 @@ config HID_ROCCAT
Say Y here if you have a Roccat mouse or keyboard and want
support for its special functionalities.
+config HID_ROCCAT_KONE_KUNIT_TEST
+ bool "KUnit tests for the Roccat Kone driver" if !KUNIT_ALL_TESTS
+ depends on HID_ROCCAT=y && KUNIT=y
+ default KUNIT_ALL_TESTS
+ help
+ Enable the KUnit regression tests for the Roccat Kone driver,
+ covering bounds checking of device-supplied profile indices.
+ If unsure, say N.
+
config HID_SAITEK
tristate "Saitek (Mad Catz) non-fully HID-compliant devices"
help
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index cf123347a2af..f107f5103b35 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2447,9 +2447,16 @@ EXPORT_SYMBOL_GPL(hid_hw_start);
*
* This is usually called from remove function or from probe when something
* failed and hid_hw_start was called already.
+ *
+ * If the caller enabled HID input via hid_device_io_start() and is unwinding
+ * without an explicit hid_device_io_stop(), quiesce input first so that
+ * in-flight reports cannot reach handlers (e.g. hidraw_report_event) whose
+ * backing objects hid_disconnect() is about to free.
*/
void hid_hw_stop(struct hid_device *hdev)
{
+ if (hdev->io_started)
+ hid_device_io_stop(hdev);
hid_disconnect(hdev);
hdev->ll_driver->stop(hdev);
}
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 1059922baaac..b70f719b3b07 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1379,6 +1379,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-nintendo.c b/drivers/hid/hid-nintendo.c
index e7302ec01ff1..acf0adac6b84 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -1474,7 +1474,6 @@ static void joycon_parse_imu_report(struct joycon_ctlr *ctlr,
dropped_threshold = ctlr->imu_avg_delta_ms * 3 / 2;
dropped_pkts = (delta - min(delta, dropped_threshold)) /
ctlr->imu_avg_delta_ms;
- ctlr->imu_timestamp_us += 1000 * ctlr->imu_avg_delta_ms;
if (dropped_pkts > JC_IMU_DROPPED_PKT_WARNING) {
hid_warn_ratelimited(ctlr->hdev,
"compensating for %u dropped IMU reports\n",
diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c
index 58654cf78f0d..3dae9eaa0b6f 100644
--- a/drivers/hid/hid-roccat-kone.c
+++ b/drivers/hid/hid-roccat-kone.c
@@ -36,6 +36,8 @@ static uint profile_numbers[5] = {0, 1, 2, 3, 4};
static void kone_profile_activated(struct kone_device *kone, uint new_profile)
{
+ if (new_profile < 1 || new_profile > ARRAY_SIZE(kone->profiles))
+ new_profile = 1;
kone->actual_profile = new_profile;
kone->actual_dpi = kone->profiles[new_profile - 1].startup_dpi;
}
@@ -793,8 +795,10 @@ static void kone_keep_values_up_to_date(struct kone_device *kone,
{
switch (event->event) {
case kone_mouse_event_switch_profile:
- kone->actual_dpi = kone->profiles[event->value - 1].
- startup_dpi;
+ if (event->value >= 1 &&
+ event->value <= ARRAY_SIZE(kone->profiles))
+ kone->actual_dpi =
+ kone->profiles[event->value - 1].startup_dpi;
fallthrough;
case kone_mouse_event_osd_profile:
kone->actual_profile = event->value;
@@ -915,3 +919,60 @@ module_exit(kone_exit);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat Kone driver");
MODULE_LICENSE("GPL v2");
+
+#if IS_ENABLED(CONFIG_HID_ROCCAT_KONE_KUNIT_TEST)
+#include <kunit/test.h>
+
+/*
+ * Regression test for the out-of-bounds read in
+ * kone_keep_values_up_to_date(): a malicious USB device sends a
+ * "switch profile" HID event (event == kone_mouse_event_switch_profile)
+ * with an attacker-chosen value in 0..255, which is used unbounded as
+ * profiles[value - 1]. On an unpatched kernel the attack case triggers a
+ * KASAN slab-out-of-bounds read; the fix must leave actual_dpi unchanged.
+ */
+static void kone_profile_index_oob_test(struct kunit *test)
+{
+ struct kone_device *kone;
+ struct kone_mouse_event ev = {};
+ /*
+ * Allocate only up to the end of profiles[] so that any index past
+ * the 5-element array is IMMEDIATELY out of bounds and lands in the
+ * KASAN redzone (a far over-read would hit unrelated valid memory and
+ * escape KASAN).
+ */
+ size_t sz = offsetof(struct kone_device, profiles) +
+ sizeof(kone->profiles);
+
+ kone = kunit_kzalloc(test, sz, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, kone);
+ kone->profiles[0].startup_dpi = 0x42;
+
+ /* benign control: a valid in-range value drives the SAME path and
+ * must succeed (proves the trigger reaches the real code).
+ */
+ ev.event = kone_mouse_event_switch_profile;
+ ev.value = 1;
+ kone_keep_values_up_to_date(kone, &ev);
+ KUNIT_EXPECT_EQ(test, kone->actual_dpi, 0x42);
+
+ /* attack: value == ARRAY_SIZE(profiles) + 1 reads profiles[5], one
+ * element past the array end -> KASAN slab-out-of-bounds read on an
+ * unpatched kernel. The fix must reject it (actual_dpi unchanged).
+ */
+ ev.value = ARRAY_SIZE(kone->profiles) + 1;
+ kone_keep_values_up_to_date(kone, &ev);
+ KUNIT_EXPECT_EQ(test, kone->actual_dpi, 0x42);
+}
+
+static struct kunit_case kone_test_cases[] = {
+ KUNIT_CASE(kone_profile_index_oob_test),
+ {}
+};
+
+static struct kunit_suite kone_test_suite = {
+ .name = "hid-roccat-kone",
+ .test_cases = kone_test_cases,
+};
+kunit_test_suite(kone_test_suite);
+#endif /* CONFIG_HID_ROCCAT_KONE_KUNIT_TEST */
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index e75246d29e16..253fff4066eb 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -29,6 +29,7 @@
* There will be no PIN request from the device.
*/
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
@@ -81,6 +82,7 @@
#define SONY_FF_SUPPORT (SIXAXIS_CONTROLLER | MOTION_CONTROLLER)
#define SONY_BT_DEVICE (SIXAXIS_CONTROLLER_BT | MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER_BT)
#define NSG_MRXU_REMOTE (NSG_MR5U_REMOTE_BT | NSG_MR7U_REMOTE_BT)
+#define RB4_GUITAR_PS4 (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT)
#define MAX_LEDS 4
#define NSG_MRXU_MAX_X 1667
@@ -521,10 +523,6 @@ static DEFINE_SPINLOCK(sony_dev_list_lock);
static LIST_HEAD(sony_device_list);
static DEFINE_IDA(sony_device_id_allocator);
-enum sony_worker {
- SONY_WORKER_STATE
-};
-
struct sony_sc {
spinlock_t lock;
struct list_head list_node;
@@ -534,6 +532,7 @@ struct sony_sc {
struct input_dev *sensor_dev;
struct led_classdev *leds[MAX_LEDS];
unsigned long quirks;
+ int (*raw_event)(struct sony_sc *sc, u8 *rd, int size);
struct work_struct state_worker;
void (*send_output_report)(struct sony_sc *sc);
struct power_supply *battery;
@@ -566,19 +565,11 @@ struct sony_sc {
static void sony_set_leds(struct sony_sc *sc);
-static inline void sony_schedule_work(struct sony_sc *sc,
- enum sony_worker which)
+static inline void sony_schedule_work(struct sony_sc *sc)
{
- unsigned long flags;
-
- switch (which) {
- case SONY_WORKER_STATE:
- spin_lock_irqsave(&sc->lock, flags);
- if (!sc->defer_initialization && sc->state_worker_initialized)
- schedule_work(&sc->state_worker);
- spin_unlock_irqrestore(&sc->lock, flags);
- break;
- }
+ guard(spinlock_irqsave)(&sc->lock);
+ if (!sc->defer_initialization && sc->state_worker_initialized)
+ schedule_work(&sc->state_worker);
}
static void ghl_magic_poke_cb(struct urb *urb)
@@ -946,15 +937,39 @@ static const u8 *sony_report_fixup(struct hid_device *hdev, u8 *rdesc,
return rdesc;
}
-static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size)
+static int sixaxis_raw_event(struct sony_sc *sc, u8 *rd, int size)
{
static const u8 sixaxis_battery_capacity[] = { 0, 1, 25, 50, 75, 100 };
- unsigned long flags;
int offset;
u8 index;
u8 battery_capacity;
int battery_status;
+ if (unlikely(size != 49 || rd[0] != 0x01))
+ return 0;
+
+ if (sc->quirks & SIXAXIS_CONTROLLER) {
+ /*
+ * When connected via Bluetooth the Sixaxis occasionally sends
+ * a report with the second byte 0xff and the rest zeroed.
+ *
+ * This report does not reflect the actual state of the
+ * controller must be ignored to avoid generating false input
+ * events.
+ */
+ if (rd[1] == 0xff)
+ return -EINVAL;
+
+ /*
+ * Sixaxis HID report has acclerometers/gyro with MSByte first, this
+ * has to be BYTE_SWAPPED before passing up to joystick interface
+ */
+ swap(rd[41], rd[42]);
+ swap(rd[43], rd[44]);
+ swap(rd[45], rd[46]);
+ swap(rd[47], rd[48]);
+ }
+
/*
* The sixaxis is charging if the battery value is 0xee
* and it is fully charged if the value is 0xef.
@@ -972,10 +987,10 @@ static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size)
battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
}
- spin_lock_irqsave(&sc->lock, flags);
- sc->battery_capacity = battery_capacity;
- sc->battery_status = battery_status;
- spin_unlock_irqrestore(&sc->lock, flags);
+ scoped_guard(spinlock_irqsave, &sc->lock) {
+ sc->battery_capacity = battery_capacity;
+ sc->battery_status = battery_status;
+ }
if (sc->quirks & SIXAXIS_CONTROLLER) {
int val;
@@ -993,13 +1008,18 @@ static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size)
input_sync(sc->sensor_dev);
}
+
+ return 0;
}
-static void nsg_mrxu_parse_report(struct sony_sc *sc, u8 *rd, int size)
+static int nsg_mrxu_raw_event(struct sony_sc *sc, u8 *rd, int size)
{
int n, offset, relx, rely;
u8 active;
+ if (unlikely(size < 12 || rd[0] != 0x02))
+ return 0;
+
/*
* The NSG-MRxU multi-touch trackpad data starts at offset 1 and
* the touch-related data starts at offset 2.
@@ -1067,10 +1087,33 @@ static void nsg_mrxu_parse_report(struct sony_sc *sc, u8 *rd, int size)
input_mt_sync_frame(sc->touchpad);
input_sync(sc->touchpad);
+ return 0;
+}
+
+static int rb3_pro_instrument_raw_event(struct sony_sc *sc, u8 *rd, int size)
+{
+ /* Rock Band 3 PS3 Pro instruments set rd[24] to 0xE0 when they're
+ * sending full reports, and 0x02 when only sending navigation.
+ */
+ if (size < 25 || rd[24] != 0x02)
+ return 0;
+
+ /* Only attempt to enable full report every 8 seconds */
+ if (time_after(jiffies, sc->rb3_pro_poke_jiffies)) {
+ sc->rb3_pro_poke_jiffies = jiffies + secs_to_jiffies(8);
+ rb3_pro_instrument_enable_full_report(sc);
+ }
+
+ return 0;
}
-static void rb4_ps4_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
+static int rb4_ps4_guitar_raw_event(struct sony_sc *sc, u8 *rd, int size)
{
+ const int expected_size = (sc->quirks & RB4_GUITAR_PS4_BT) ? 78 : 64;
+
+ if (unlikely(size != expected_size || rd[0] != 0x01))
+ return 0;
+
/*
* Rock Band 4 PS4 guitars have whammy and
* tilt functionality, they're located at
@@ -1084,15 +1127,18 @@ static void rb4_ps4_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
input_report_abs(sc->input_dev, ABS_RZ, rd[45]);
input_sync(sc->input_dev);
+ return 0;
}
-static void rb4_ps5_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
+static int rb4_ps5_guitar_raw_event(struct sony_sc *sc, u8 *rd, int size)
{
u8 charging_status;
u8 battery_data;
u8 battery_capacity;
u8 battery_status;
- unsigned long flags;
+
+ if (unlikely(size != 64 || rd[0] != 0x01))
+ return 0;
/*
* Rock Band 4 PS5 guitars have whammy and
@@ -1132,73 +1178,30 @@ static void rb4_ps5_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
break;
}
- spin_lock_irqsave(&sc->lock, flags);
- sc->battery_capacity = battery_capacity;
- sc->battery_status = battery_status;
- spin_unlock_irqrestore(&sc->lock, flags);
+ scoped_guard(spinlock_irqsave, &sc->lock) {
+ sc->battery_capacity = battery_capacity;
+ sc->battery_status = battery_status;
+ }
input_sync(sc->input_dev);
+ return 0;
}
static int sony_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *rd, int size)
{
struct sony_sc *sc = hid_get_drvdata(hdev);
+ int ret;
- /*
- * Sixaxis HID report has acclerometers/gyro with MSByte first, this
- * has to be BYTE_SWAPPED before passing up to joystick interface
- */
- if ((sc->quirks & SIXAXIS_CONTROLLER) && rd[0] == 0x01 && size == 49) {
- /*
- * When connected via Bluetooth the Sixaxis occasionally sends
- * a report with the second byte 0xff and the rest zeroed.
- *
- * This report does not reflect the actual state of the
- * controller must be ignored to avoid generating false input
- * events.
- */
- if (rd[1] == 0xff)
- return -EINVAL;
-
- swap(rd[41], rd[42]);
- swap(rd[43], rd[44]);
- swap(rd[45], rd[46]);
- swap(rd[47], rd[48]);
-
- sixaxis_parse_report(sc, rd, size);
- } else if ((sc->quirks & MOTION_CONTROLLER_BT) && rd[0] == 0x01 && size == 49) {
- sixaxis_parse_report(sc, rd, size);
- } else if ((sc->quirks & NAVIGATION_CONTROLLER) && rd[0] == 0x01 && size == 49) {
- sixaxis_parse_report(sc, rd, size);
- } else if ((sc->quirks & NSG_MRXU_REMOTE) && rd[0] == 0x02 && size >= 12) {
- nsg_mrxu_parse_report(sc, rd, size);
- return 1;
- } else if ((sc->quirks & RB4_GUITAR_PS4_USB) && rd[0] == 0x01 && size == 64) {
- rb4_ps4_guitar_parse_report(sc, rd, size);
- return 1;
- } else if ((sc->quirks & RB4_GUITAR_PS4_BT) && rd[0] == 0x01 && size == 78) {
- rb4_ps4_guitar_parse_report(sc, rd, size);
- return 1;
- } else if ((sc->quirks & RB4_GUITAR_PS5) && rd[0] == 0x01 && size == 64) {
- rb4_ps5_guitar_parse_report(sc, rd, size);
- return 1;
- }
-
- /* Rock Band 3 PS3 Pro instruments set rd[24] to 0xE0 when they're
- * sending full reports, and 0x02 when only sending navigation.
- */
- if ((sc->quirks & RB3_PRO_INSTRUMENT) && size >= 25 && rd[24] == 0x02) {
- /* Only attempt to enable full report every 8 seconds */
- if (time_after(jiffies, sc->rb3_pro_poke_jiffies)) {
- sc->rb3_pro_poke_jiffies = jiffies + secs_to_jiffies(8);
- rb3_pro_instrument_enable_full_report(sc);
- }
+ if (sc->raw_event) {
+ ret = sc->raw_event(sc, rd, size);
+ if (unlikely(ret < 0))
+ return ret;
}
- if (sc->defer_initialization) {
+ if (unlikely(sc->defer_initialization)) {
sc->defer_initialization = 0;
- sony_schedule_work(sc, SONY_WORKER_STATE);
+ sony_schedule_work(sc);
}
return 0;
@@ -1256,7 +1259,7 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
if (sc->quirks & DJH_TURNTABLE)
return djh_turntable_mapping(hdev, hi, field, usage, bit, max);
- if (sc->quirks & (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT))
+ if (sc->quirks & RB4_GUITAR_PS4)
return rb4_guitar_mapping(hdev, hi, field, usage, bit, max);
if (sc->quirks & RB4_GUITAR_PS5)
@@ -1269,8 +1272,6 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
static int sony_register_touchpad(struct sony_sc *sc, int touch_count,
int w, int h, int touch_major, int touch_minor, int orientation)
{
- size_t name_sz;
- char *name;
int ret;
sc->touchpad = devm_input_allocate_device(&sc->hdev->dev);
@@ -1292,12 +1293,10 @@ static int sony_register_touchpad(struct sony_sc *sc, int touch_count,
* a suffix. Other devices which were added later like Sony TV remotes
* inhirited this suffix.
*/
- name_sz = strlen(sc->hdev->name) + sizeof(TOUCHPAD_SUFFIX);
- name = devm_kzalloc(&sc->hdev->dev, name_sz, GFP_KERNEL);
- if (!name)
+ sc->touchpad->name = devm_kasprintf(&sc->hdev->dev, GFP_KERNEL, "%s" TOUCHPAD_SUFFIX,
+ sc->hdev->name);
+ if (!sc->touchpad->name)
return -ENOMEM;
- snprintf(name, name_sz, "%s" TOUCHPAD_SUFFIX, sc->hdev->name);
- sc->touchpad->name = name;
/* We map the button underneath the touchpad to BTN_LEFT. */
__set_bit(EV_KEY, sc->touchpad->evbit);
@@ -1334,8 +1333,6 @@ static int sony_register_touchpad(struct sony_sc *sc, int touch_count,
static int sony_register_sensors(struct sony_sc *sc)
{
- size_t name_sz;
- char *name;
int ret;
sc->sensor_dev = devm_input_allocate_device(&sc->hdev->dev);
@@ -1354,12 +1351,10 @@ static int sony_register_sensors(struct sony_sc *sc)
/* Append a suffix to the controller name as there are various
* DS4 compatible non-Sony devices with different names.
*/
- name_sz = strlen(sc->hdev->name) + sizeof(SENSOR_SUFFIX);
- name = devm_kzalloc(&sc->hdev->dev, name_sz, GFP_KERNEL);
- if (!name)
+ sc->sensor_dev->name = devm_kasprintf(&sc->hdev->dev, GFP_KERNEL, "%s" SENSOR_SUFFIX,
+ sc->hdev->name);
+ if (!sc->sensor_dev->name)
return -ENOMEM;
- snprintf(name, name_sz, "%s" SENSOR_SUFFIX, sc->hdev->name);
- sc->sensor_dev->name = name;
if (sc->quirks & SIXAXIS_CONTROLLER) {
/* For the DS3 we only support the accelerometer, which works
@@ -1507,7 +1502,7 @@ static void buzz_set_leds(struct sony_sc *sc)
static void sony_set_leds(struct sony_sc *sc)
{
if (!(sc->quirks & BUZZ_CONTROLLER))
- sony_schedule_work(sc, SONY_WORKER_STATE);
+ sony_schedule_work(sc);
else
buzz_set_leds(sc);
}
@@ -1618,7 +1613,7 @@ static int sony_led_blink_set(struct led_classdev *led, unsigned long *delay_on,
new_off != drv_data->led_delay_off[n]) {
drv_data->led_delay_on[n] = new_on;
drv_data->led_delay_off[n] = new_off;
- sony_schedule_work(drv_data, SONY_WORKER_STATE);
+ sony_schedule_work(drv_data);
}
return 0;
@@ -1846,7 +1841,7 @@ static int sony_play_effect(struct input_dev *dev, void *data,
sc->left = effect->u.rumble.strong_magnitude / 256;
sc->right = effect->u.rumble.weak_magnitude / 256;
- sony_schedule_work(sc, SONY_WORKER_STATE);
+ sony_schedule_work(sc);
return 0;
}
@@ -1869,15 +1864,14 @@ static int sony_battery_get_property(struct power_supply *psy,
union power_supply_propval *val)
{
struct sony_sc *sc = power_supply_get_drvdata(psy);
- unsigned long flags;
int ret = 0;
u8 battery_capacity;
int battery_status;
- spin_lock_irqsave(&sc->lock, flags);
- battery_capacity = sc->battery_capacity;
- battery_status = sc->battery_status;
- spin_unlock_irqrestore(&sc->lock, flags);
+ scoped_guard(spinlock_irqsave, &sc->lock) {
+ battery_capacity = sc->battery_capacity;
+ battery_status = sc->battery_status;
+ }
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
@@ -1959,10 +1953,9 @@ static inline int sony_compare_connection_type(struct sony_sc *sc0,
static int sony_check_add_dev_list(struct sony_sc *sc)
{
struct sony_sc *entry;
- unsigned long flags;
int ret;
- spin_lock_irqsave(&sony_dev_list_lock, flags);
+ guard(spinlock_irqsave)(&sony_dev_list_lock);
list_for_each_entry(entry, &sony_device_list, list_node) {
ret = memcmp(sc->mac_address, entry->mac_address,
@@ -1976,26 +1969,23 @@ static int sony_check_add_dev_list(struct sony_sc *sc)
"controller with MAC address %pMR already connected\n",
sc->mac_address);
}
- goto unlock;
+ goto out;
}
}
ret = 0;
list_add(&(sc->list_node), &sony_device_list);
-unlock:
- spin_unlock_irqrestore(&sony_dev_list_lock, flags);
+out:
return ret;
}
static void sony_remove_dev_list(struct sony_sc *sc)
{
- unsigned long flags;
-
if (sc->list_node.next) {
- spin_lock_irqsave(&sony_dev_list_lock, flags);
- list_del(&(sc->list_node));
- spin_unlock_irqrestore(&sony_dev_list_lock, flags);
+ scoped_guard(spinlock_irqsave, &sony_dev_list_lock) {
+ list_del(&(sc->list_node));
+ }
}
}
@@ -2110,6 +2100,12 @@ static void sony_release_device_id(struct sony_sc *sc)
}
}
+static inline void sony_init_raw_event_handler(struct sony_sc *sc,
+ int (*raw_event)(struct sony_sc *, u8 *, int))
+{
+ sc->raw_event = raw_event;
+}
+
static inline void sony_init_output_report(struct sony_sc *sc,
void (*send_output_report)(struct sony_sc *))
{
@@ -2123,12 +2119,10 @@ static inline void sony_init_output_report(struct sony_sc *sc,
static inline void sony_cancel_work_sync(struct sony_sc *sc)
{
- unsigned long flags;
-
if (sc->state_worker_initialized) {
- spin_lock_irqsave(&sc->lock, flags);
- sc->state_worker_initialized = 0;
- spin_unlock_irqrestore(&sc->lock, flags);
+ scoped_guard(spinlock_irqsave, &sc->lock) {
+ sc->state_worker_initialized = 0;
+ }
cancel_work_sync(&sc->state_worker);
}
}
@@ -2185,6 +2179,7 @@ static int sony_input_configured(struct hid_device *hdev,
goto err_stop;
}
+ sony_init_raw_event_handler(sc, sixaxis_raw_event);
sony_init_output_report(sc, sixaxis_send_output_report);
} else if (sc->quirks & NAVIGATION_CONTROLLER_BT) {
/*
@@ -2199,6 +2194,7 @@ static int sony_input_configured(struct hid_device *hdev,
goto err_stop;
}
+ sony_init_raw_event_handler(sc, sixaxis_raw_event);
sony_init_output_report(sc, sixaxis_send_output_report);
} else if (sc->quirks & RB3_PRO_INSTRUMENT) {
/*
@@ -2213,6 +2209,8 @@ static int sony_input_configured(struct hid_device *hdev,
*/
hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP;
hdev->quirks |= HID_QUIRK_SKIP_OUTPUT_REPORT_ID;
+
+ sony_init_raw_event_handler(sc, rb3_pro_instrument_raw_event);
} else if (sc->quirks & SIXAXIS_CONTROLLER_USB) {
/*
* The Sony Sixaxis does not handle HID Output Reports on the
@@ -2237,6 +2235,7 @@ static int sony_input_configured(struct hid_device *hdev,
goto err_stop;
}
+ sony_init_raw_event_handler(sc, sixaxis_raw_event);
sony_init_output_report(sc, sixaxis_send_output_report);
} else if (sc->quirks & SIXAXIS_CONTROLLER_BT) {
/*
@@ -2258,6 +2257,7 @@ static int sony_input_configured(struct hid_device *hdev,
goto err_stop;
}
+ sony_init_raw_event_handler(sc, sixaxis_raw_event);
sony_init_output_report(sc, sixaxis_send_output_report);
} else if (sc->quirks & NSG_MRXU_REMOTE) {
/*
@@ -2273,8 +2273,15 @@ static int sony_input_configured(struct hid_device *hdev,
goto err_stop;
}
+ sony_init_raw_event_handler(sc, nsg_mrxu_raw_event);
} else if (sc->quirks & MOTION_CONTROLLER) {
+ if (sc->quirks & MOTION_CONTROLLER_BT)
+ sony_init_raw_event_handler(sc, sixaxis_raw_event);
sony_init_output_report(sc, motion_send_output_report);
+ } else if (sc->quirks & RB4_GUITAR_PS4) {
+ sony_init_raw_event_handler(sc, rb4_ps4_guitar_raw_event);
+ } else if (sc->quirks & RB4_GUITAR_PS5) {
+ sony_init_raw_event_handler(sc, rb4_ps5_guitar_raw_event);
}
if (sc->quirks & SONY_LED_SUPPORT) {
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f98435631aa1..73f77dd07110 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -10,16 +10,30 @@
*/
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/slab.h>
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
#define STEELSERIES_ARCTIS_1 BIT(1)
#define STEELSERIES_ARCTIS_9 BIT(2)
+#define STEELSERIES_MSI_RGB BIT(3)
+
+#define STEELSERIES_MSI_RGB_WVALUE 0x0300 /* Feature report, ID 0 */
+#define STEELSERIES_MSI_RGB_REPORT_LEN 524
+#define STEELSERIES_MSI_RGB_OPCODE 0x0c
+#define STEELSERIES_MSI_RGB_KLC_MODE 0x66
+#define STEELSERIES_MSI_RGB_ALC_MODE 0x06
+
+#define STEELSERIES_HAS_LEDS_MULTICOLOR \
+ (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
struct steelseries_device {
struct hid_device *hdev;
@@ -34,6 +48,14 @@ struct steelseries_device {
uint8_t battery_capacity;
bool headset_connected;
bool battery_charging;
+ bool battery_registered;
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[3];
+ struct mutex rgb_lock; /* protects rgb_buf */
+ u8 *rgb_buf;
+#endif
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -510,6 +532,8 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
power_supply_powers(sd->battery, &sd->hdev->dev);
INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick);
+ /* Pairs with smp_load_acquire() in raw_event and remove paths */
+ smp_store_release(&sd->battery_registered, true);
steelseries_headset_fetch_battery(sd->hdev);
if (sd->quirks & STEELSERIES_ARCTIS_9) {
@@ -523,11 +547,230 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t usage_page)
{
+ if (hdev->rsize < 3)
+ return false;
+
return hdev->rdesc[0] == 0x06 &&
hdev->rdesc[1] == usage_page &&
hdev->rdesc[2] == 0xff;
}
+static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
+ },
+ },
+ { }
+};
+
+static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev)
+{
+ if (!hid_is_usb(hdev))
+ return NULL;
+
+ return to_usb_interface(hdev->dev.parent);
+}
+
+static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+ struct usb_device *udev;
+
+ if (!intf)
+ return false;
+
+ udev = interface_to_usbdev(intf);
+
+ return intf == usb_ifnum_to_if(udev, 0);
+}
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+
+static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+
+ if (!intf)
+ return NULL;
+
+ return interface_to_usbdev(intf);
+}
+
+static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct steelseries_device *sd = container_of(mc_cdev,
+ struct steelseries_device,
+ mc_cdev);
+ struct hid_device *hdev = sd->hdev;
+ struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
+ int i, ret;
+ u8 r, g, b;
+
+ static const u8 keys[] = {
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
+ 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
+ };
+ static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03 };
+
+ if (!udev)
+ return -ENODEV;
+
+ mutex_lock(&sd->rgb_lock);
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ r = mc_cdev->subled_info[0].brightness;
+ g = mc_cdev->subled_info[1].brightness;
+ b = mc_cdev->subled_info[2].brightness;
+
+ /*
+ * Report layout (524 bytes):
+ * Byte 0: Opcode (0x0c)
+ * Byte 1: 0x00
+ * Byte 2: Mode (0x66 for Keyboard, 0x06 for Lightbar)
+ * Byte 3: 0x00
+ * Bytes 4+: 4-byte chunks per LED (Index, R, G, B)
+ */
+ memset(sd->rgb_buf, 0, STEELSERIES_MSI_RGB_REPORT_LEN);
+ sd->rgb_buf[0] = STEELSERIES_MSI_RGB_OPCODE;
+ sd->rgb_buf[1] = 0x00;
+ sd->rgb_buf[3] = 0x00;
+
+ for (i = 0; i < (STEELSERIES_MSI_RGB_REPORT_LEN - 4) / 4; i++)
+ sd->rgb_buf[4 + i * 4] = 0xff;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
+ sd->rgb_buf[2] = STEELSERIES_MSI_RGB_KLC_MODE;
+ for (i = 0; i < ARRAY_SIZE(keys); i++) {
+ sd->rgb_buf[4 + i * 4] = keys[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ } else {
+ sd->rgb_buf[2] = STEELSERIES_MSI_RGB_ALC_MODE;
+ for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
+ sd->rgb_buf[4 + i * 4] = alc_zones[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ }
+
+ /*
+ * Send the vendor report verbatim with usb_control_msg(): byte 0 is a
+ * protocol opcode (0x0c), not a HID report ID, and the controller
+ * expects it under report ID 0 (wValue 0x0300). hid_hw_raw_request()
+ * would write the report number into byte 0, so the direct control
+ * transfer is used to keep the payload byte-identical to the tested
+ * userspace implementation.
+ */
+ ret = hid_hw_power(hdev, PM_HINT_FULLON);
+ if (ret < 0)
+ goto out_unlock;
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ HID_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ STEELSERIES_MSI_RGB_WVALUE, 0,
+ sd->rgb_buf, STEELSERIES_MSI_RGB_REPORT_LEN,
+ USB_CTRL_SET_TIMEOUT);
+
+ hid_hw_power(hdev, PM_HINT_NORMAL);
+
+out_unlock:
+ mutex_unlock(&sd->rgb_lock);
+ return ret < 0 ? ret : 0;
+}
+
+static void steelseries_msi_rgb_free_buf(void *data)
+{
+ kfree(data);
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ struct hid_device *hdev = sd->hdev;
+ struct led_classdev *led_cdev;
+ int ret;
+
+ sd->rgb_buf = kzalloc(STEELSERIES_MSI_RGB_REPORT_LEN, GFP_KERNEL);
+ if (!sd->rgb_buf)
+ return -ENOMEM;
+
+ ret = devm_add_action_or_reset(&hdev->dev,
+ steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ if (ret) {
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ ret = devm_mutex_init(&hdev->dev, &sd->rgb_lock);
+ if (ret) {
+ devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ kfree(sd->rgb_buf);
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ sd->subled_info[0].color_index = LED_COLOR_ID_RED;
+ sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ sd->subled_info[0].intensity = 255;
+ sd->subled_info[1].intensity = 255;
+ sd->subled_info[2].intensity = 255;
+ sd->subled_info[0].channel = 0;
+ sd->subled_info[1].channel = 1;
+ sd->subled_info[2].channel = 2;
+
+ sd->mc_cdev.subled_info = sd->subled_info;
+ sd->mc_cdev.num_colors = 3;
+
+ led_cdev = &sd->mc_cdev.led_cdev;
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
+ led_cdev->name = "steelseries::kbd_backlight";
+ else
+ led_cdev->name = "steelseries::lightbar";
+
+ led_cdev->max_brightness = 255;
+ led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
+
+ ret = devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
+ if (ret) {
+ devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ kfree(sd->rgb_buf);
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+#else
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ return -ENODEV;
+}
+#endif
+
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct steelseries_device *sd;
@@ -549,6 +792,14 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
sd->hdev = hdev;
sd->quirks = id->driver_data;
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
+ !steelseries_msi_rgb_is_interface0(hdev)) {
+ hid_dbg(hdev, "MSI RGB quirk not applicable, using generic HID path\n");
+ sd->quirks &= ~STEELSERIES_MSI_RGB;
+ }
+ }
+
ret = hid_parse(hdev);
if (ret)
return ret;
@@ -565,12 +816,28 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
ret = hid_hw_open(hdev);
if (ret)
- return ret;
+ goto err_stop;
+
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ ret = steelseries_msi_rgb_register(sd);
+ if (ret) {
+ hid_warn(hdev,
+ "Failed to register MSI RGB LEDs: %d, continuing without RGB support\n",
+ ret);
+ sd->quirks &= ~STEELSERIES_MSI_RGB;
+ }
+ return 0;
+ }
- if (steelseries_headset_battery_register(sd) < 0)
+ if ((sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9)) &&
+ steelseries_headset_battery_register(sd) < 0)
hid_err(sd->hdev,
"Failed to register battery for headset\n");
+ return 0;
+
+err_stop:
+ hid_hw_stop(hdev);
return ret;
}
@@ -588,12 +855,16 @@ static void steelseries_remove(struct hid_device *hdev)
}
sd = hid_get_drvdata(hdev);
+ if (!sd)
+ return;
spin_lock_irqsave(&sd->lock, flags);
sd->removed = true;
spin_unlock_irqrestore(&sd->lock, flags);
- cancel_delayed_work_sync(&sd->battery_work);
+ /* Pairs with smp_store_release() in steelseries_headset_battery_register() */
+ if (smp_load_acquire(&sd->battery_registered))
+ cancel_delayed_work_sync(&sd->battery_work);
hid_hw_close(hdev);
hid_hw_stop(hdev);
@@ -624,20 +895,34 @@ static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in
return (capacity - min_in) * 100 / (max_in - min_in);
}
+static bool steelseries_is_headset(struct hid_device *hdev)
+{
+ return hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+ hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9;
+}
+
static int steelseries_headset_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *read_buf,
int size)
{
- struct steelseries_device *sd = hid_get_drvdata(hdev);
- int capacity = sd->battery_capacity;
- bool connected = sd->headset_connected;
- bool charging = sd->battery_charging;
+ struct steelseries_device *sd;
+ int capacity;
+ bool connected;
+ bool charging;
unsigned long flags;
- /* Not a headset */
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ if (!steelseries_is_headset(hdev))
return 0;
+ sd = hid_get_drvdata(hdev);
+ /* Pairs with smp_store_release() in steelseries_headset_battery_register() */
+ if (!sd || !smp_load_acquire(&sd->battery_registered))
+ return 0;
+
+ capacity = sd->battery_capacity;
+ connected = sd->headset_connected;
+ charging = sd->battery_charging;
+
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
@@ -732,6 +1017,16 @@ static const struct hid_device_id steelseries_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = STEELSERIES_ARCTIS_9 },
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ { /* MSI Raider A18 KLC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_KLC),
+ .driver_data = STEELSERIES_MSI_RGB },
+
+ { /* MSI Raider A18 ALC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
+ .driver_data = STEELSERIES_MSI_RGB },
+#endif
+
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
index da1f0ea85625..a29bf051ada7 100644
--- a/drivers/hid/wacom_wac.c
+++ b/drivers/hid/wacom_wac.c
@@ -1192,8 +1192,11 @@ static int int_dist(int x1, int y1, int x2, int y2)
static void wacom_intuos_bt_process_data(struct wacom_wac *wacom,
unsigned char *data)
{
- memcpy(wacom->data, data, 10);
+ u8 *saved_data = wacom->data;
+
+ wacom->data = data;
wacom_intuos_irq(wacom);
+ wacom->data = saved_data;
input_sync(wacom->pen_input);
if (wacom->pad_input)
@@ -1202,7 +1205,7 @@ static void wacom_intuos_bt_process_data(struct wacom_wac *wacom,
static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
{
- u8 *data = kmemdup(wacom->data, len, GFP_KERNEL);
+ u8 *data = wacom->data;
int i = 1;
unsigned power_raw, battery_capacity, bat_charging, ps_connected;
@@ -1242,7 +1245,6 @@ static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
break;
}
- kfree(data);
return 0;
}