mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user