This commit is contained in:
Mark Brown
2026-07-31 14:50:10 +01:00
8 changed files with 516 additions and 134 deletions
+9
View File
@@ -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
+7
View File
@@ -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);
}
+2
View File
@@ -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
-1
View File
@@ -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",
+63 -2
View File
@@ -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 */
+126 -119
View File
@@ -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 void rb4_ps4_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
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 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;
if (sc->raw_event) {
ret = sc->raw_event(sc, rd, size);
if (unlikely(ret < 0))
return ret;
}
/* 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->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) {
+304 -9
View File
@@ -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 (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 +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);
+5 -3
View File
@@ -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;
}