diff --git a/Documentation/hid/hidintro.rst b/Documentation/hid/hidintro.rst index 73523e315ebd..5d367dfca0b8 100644 --- a/Documentation/hid/hidintro.rst +++ b/Documentation/hid/hidintro.rst @@ -522,3 +522,53 @@ This should really be your last resort. vendor: 0x093a product: 0x2510 ... + +Input Device Registration and Lifecycle +======================================== + +HID drivers that rely on the HID core to register input devices (by using the +``HID_CONNECT_HIDINPUT`` flag, which is part of ``HID_CONNECT_DEFAULT``) +must be aware of the registration timing. + +When ``hid_hw_start(hdev, flags)`` is called with ``HID_CONNECT_HIDINPUT``, +the HID core immediately parses the report descriptor, allocates ``input_dev`` +structures, and calls ``input_register_device()`` for each of them. + +This means the input device becomes **live and visible to userspace** before +``hid_hw_start()`` returns. + +If a driver needs to perform additional configuration on the input device (such +as adding force-feedback support, setting extra bits in ``evbit``, or +assigning custom event handlers), doing so in the ``probe`` function after +``hid_hw_start()`` is **incorrect and racy**. Userspace may trigger +callbacks (like ``play_effect``) via ioctls immediately after registration, +leading to potential NULL pointer dereferences if the driver hasn't finished +initializing its private data. + +The correct way to augment an input device before it is registered is to use the +``.input_configured`` callback in ``struct hid_driver``. This hook is +called by the HID core after the ``input_dev`` is fully formed but **before** +``input_register_device()`` is invoked. + +Example: + +.. code-block:: c + + static int my_input_configured(struct hid_device *hdev, struct hid_input *hidinput) + { + struct input_dev *input = hidinput->input; + + /* Initialize private data and capabilities here */ + set_bit(EV_FF, input->evbit); + return input_ff_create_memless(input, NULL, my_play_effect); + } + + static struct hid_driver my_driver = { + .name = "my_driver", + .probe = my_probe, + .input_configured = my_input_configured, + }; + +Drivers that require even more control over the lifecycle should mask out +``HID_CONNECT_HIDINPUT`` and call ``input_register_device()`` manually +when they are ready. diff --git a/MAINTAINERS b/MAINTAINERS index 7c498af6dd59..a6332a1ec903 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1301,6 +1301,7 @@ L: linux-input@vger.kernel.org S: Maintained F: Documentation/hid/amd-sfh* F: drivers/hid/amd-sfh-hid/ +F: drivers/input/misc/amd_sfh_tabletmode.c AMD SPI DRIVER M: Raju Rangoju @@ -11514,6 +11515,12 @@ F: include/uapi/linux/hid* F: samples/hid/ F: tools/testing/selftests/hid/ +HID HYPERX DRIVER +M: Benjamin Blume +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-hyperx.c + HID LOGITECH DRIVERS R: Filipe Laíns L: linux-input@vger.kernel.org @@ -11550,6 +11557,12 @@ F: drivers/hid/hid-sensor-* F: drivers/iio/*/hid-* F: include/linux/hid-sensor-* +HID STEAM CONTROLLER +M: Vicki Pfau +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-steam.c + HID VRC-2 CAR CONTROLLER DRIVER M: Marcus Folkesson L: linux-input@vger.kernel.org @@ -18350,6 +18363,12 @@ S: Odd Fixes F: Documentation/devicetree/bindings/net/ieee802154/mrf24j40.txt F: drivers/net/ieee802154/mrf24j40.c +MSI HID DRIVER +M: Derek J. Clark +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-msi.c + MSI EC DRIVER M: Nikita Kravets L: platform-driver-x86@vger.kernel.org diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index f9bcaeb66385..aa7fa11a0197 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -433,6 +433,19 @@ config HOLTEK_FF Say Y here if you have a Holtek On Line Grip based game controller and want to have force feedback support for it. +config HID_HYPERX + tristate "HyperX microphones" + depends on USB_HID + help + Support for the mute button of HyperX microphones. + + The button is handled in the device firmware and only reports the + resulting mute state through a vendor-defined collection, so without + this driver userspace never learns that the microphone was muted. + + Supported devices: + - HyperX QuadCast 2 + config HID_VIVALDI_COMMON tristate help @@ -485,6 +498,19 @@ config HID_GT683R Currently the following devices are know to be supported: - MSI GT683R +config HID_MSI + tristate "MSI Claw Gamepad Support" + depends on USB_HID + select NEW_LEDS + select LEDS_CLASS + select LEDS_CLASS_MULTICOLOR + help + Support for the MSI Claw RGB and controller configuration + + Say Y here to include configuration interface support for the MSI Claw Line + of Handheld Console Controllers. Say M here to compile this driver as a + module. The module will be called hid-msi. + config HID_KEYTOUCH tristate "Keytouch HID devices" help @@ -1048,6 +1074,7 @@ config HID_PXRC config HID_RAPOO tristate "Rapoo non-fully HID-compliant devices" + depends on USB_HID help Support for Rapoo devices that are not fully compliant with the HID standard. @@ -1079,6 +1106,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 @@ -1215,6 +1251,16 @@ config HID_HYPERV_MOUSE help Select this option to enable the Hyper-V mouse driver. +config HID_HYPERV_MOUSE_KUNIT_TEST + bool "KUnit tests for Hyper-V mouse driver" if !KUNIT_ALL_TESTS + depends on KUNIT && HID_HYPERV_MOUSE + default KUNIT_ALL_TESTS + help + Builds unit tests for the Hyper-V synthetic HID driver. + These tests exercise the initial device-info parser with + malformed host-provided HID descriptors and are only useful + for kernel developers running KUnit. + config HID_SMARTJOYPLUS tristate "SmartJoy PLUS PS2/USB adapter support" help diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 23e6e3dd0c56..48a863b245ee 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_HID_HOLTEK) += hid-holtek-kbd.o obj-$(CONFIG_HID_HOLTEK) += hid-holtek-mouse.o obj-$(CONFIG_HID_HOLTEK) += hid-holtekff.o obj-$(CONFIG_HID_HYPERV_MOUSE) += hid-hyperv.o +obj-$(CONFIG_HID_HYPERX) += hid-hyperx.o obj-$(CONFIG_HID_ICADE) += hid-icade.o obj-$(CONFIG_HID_ITE) += hid-ite.o obj-$(CONFIG_HID_JABRA) += hid-jabra.o @@ -92,6 +93,7 @@ obj-$(CONFIG_HID_MAYFLASH) += hid-mf.o obj-$(CONFIG_HID_MEGAWORLD_FF) += hid-megaworld.o obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o +obj-$(CONFIG_HID_MSI) += hid-msi.o obj-$(CONFIG_HID_MULTITOUCH) += hid-multitouch.o obj-$(CONFIG_HID_NINTENDO) += hid-nintendo.o obj-$(CONFIG_HID_NTI) += hid-nti.o @@ -134,7 +136,7 @@ obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o obj-$(CONFIG_HID_SONY) += hid-sony.o obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o obj-$(CONFIG_HID_STEAM) += hid-steam.o -obj-$(CONFIG_HID_STEELSERIES) += hid-steelseries.o +obj-$(CONFIG_HID_STEELSERIES) += hid-steelseries.o hid-steelseries-arctis.o obj-$(CONFIG_HID_SUNPLUS) += hid-sunplus.o obj-$(CONFIG_HID_GREENASIA) += hid-gaff.o obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o hid-thrustmaster.o diff --git a/drivers/hid/amd-sfh-hid/Kconfig b/drivers/hid/amd-sfh-hid/Kconfig index 3291786a5ee6..d86d83ffebd7 100644 --- a/drivers/hid/amd-sfh-hid/Kconfig +++ b/drivers/hid/amd-sfh-hid/Kconfig @@ -6,6 +6,7 @@ menu "AMD SFH HID Support" config AMD_SFH_HID tristate "AMD Sensor Fusion Hub" depends on X86 + select AUXILIARY_BUS help If you say yes to this option, support will be included for the AMD Sensor Fusion Hub. diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_client.c b/drivers/hid/amd-sfh-hid/amd_sfh_client.c index 96ae792beeb6..ae6add0b9ce3 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_client.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_client.c @@ -383,3 +383,19 @@ int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata) return 0; } + +bool amd_sfh_op_idx_enabled(struct amd_mp2_dev *mp2) +{ + struct amdtp_cl_data *cl = mp2->cl_data; + int i; + + if (!cl) + return false; + + for (i = 0; i < cl->num_hid_devices; i++) + if (cl->sensor_idx[i] == op_idx && + READ_ONCE(cl->sensor_sts[i]) == SENSOR_ENABLED) + return true; + + return false; +} diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_common.h b/drivers/hid/amd-sfh-hid/amd_sfh_common.h index 78f830c133e5..f8c6b7fc34fb 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h +++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h @@ -10,6 +10,7 @@ #ifndef AMD_SFH_COMMON_H #define AMD_SFH_COMMON_H +#include #include #include #include "amd_sfh_hid.h" @@ -35,6 +36,11 @@ enum cmd_id { STOP_ALL_SENSORS = 8, }; +enum amd_mp2_version { + MP2_VER_V2 = 1, + MP2_VER_1_1 = 2, +}; + struct amd_mp2_sensor_info { u8 sensor_idx; u32 period; @@ -64,6 +70,8 @@ struct amd_mp2_dev { struct mutex lock; u8 init_done; u8 rver; + u8 mp2_ver; + struct auxiliary_device *tm_auxdev; }; struct amd_mp2_ops { @@ -100,4 +108,9 @@ static inline u64 amd_get_p2c_val(struct amd_mp2_dev *mp2, u32 idx) { return mp2->rver == 1 ? AMD_P2C_MSG_V1(idx) : AMD_P2C_MSG(idx); } + +bool amd_sfh_op_idx_enabled(struct amd_mp2_dev *mp2); +void sfh_set_emp2(struct amd_mp2_dev *mp2); +void sfh_deinit_emp2(void); + #endif diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c index b04f675d49b0..0a851aff7229 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c @@ -87,16 +87,17 @@ static int amdtp_wait_for_response(struct hid_device *hid) break; } - if (!cli_data->request_done[i]) + if (!cli_data->request_done[i]) { ret = wait_event_interruptible_timeout(hid_data->hid_wait, cli_data->request_done[i], msecs_to_jiffies(AMD_SFH_RESPONSE_TIMEOUT)); - if (ret == -ERESTARTSYS) - return -ERESTARTSYS; - else if (ret < 0) - return -ETIMEDOUT; - else - return 0; + if (ret == -ERESTARTSYS) + return -ERESTARTSYS; + if (ret <= 0) + return -ETIMEDOUT; + } + + return 0; } void amdtp_hid_wakeup(struct hid_device *hid) @@ -162,6 +163,7 @@ int amdtp_hid_probe(u32 cur_hid_dev, struct amdtp_cl_data *cli_data) return 0; err_hid_device: + cli_data->hid_sensor_hubs[cur_hid_dev] = NULL; kfree(hid_data); err_hid_data: hid_destroy_device(hid); diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c index 4b81cebdc335..eda26a094d3f 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c @@ -8,11 +8,13 @@ * Basavaraj Natikar */ +#include #include #include #include #include #include +#include #include #include #include @@ -122,19 +124,10 @@ static irqreturn_t amd_sfh_irq_handler(int irq, void *data) int amd_sfh_irq_init_v2(struct amd_mp2_dev *privdata) { - int rc; - pcim_intx(privdata->pdev, true); - rc = devm_request_irq(&privdata->pdev->dev, privdata->pdev->irq, - amd_sfh_irq_handler, 0, DRIVER_NAME, privdata); - if (rc) { - dev_err(&privdata->pdev->dev, "failed to request irq %d err=%d\n", - privdata->pdev->irq, rc); - return rc; - } - - return 0; + return devm_request_irq(&privdata->pdev->dev, privdata->pdev->irq, + amd_sfh_irq_handler, 0, DRIVER_NAME, privdata); } static int amd_sfh_dis_sts_v2(struct amd_mp2_dev *privdata) @@ -251,6 +244,8 @@ int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id) static void amd_mp2_pci_remove(void *privdata) { struct amd_mp2_dev *mp2 = privdata; + + sfh_deinit_emp2(); amd_sfh_hid_client_deinit(privdata); mp2->mp2_ops->stop_all(mp2); pcim_intx(mp2->pdev, false); @@ -285,6 +280,7 @@ static void mp2_select_ops(struct amd_mp2_dev *privdata) switch (acs) { case V2_STATUS: privdata->mp2_ops = &amd_sfh_ops_v2; + privdata->mp2_ver = MP2_VER_V2; break; default: privdata->mp2_ops = &amd_sfh_ops; @@ -386,6 +382,51 @@ static const struct attribute_group *amd_sfh_groups[] = { NULL, }; +static DEFINE_IDA(sfh_tm_ida); + +static void amd_sfh_maybe_register_tm(struct amd_mp2_dev *mp2) +{ + struct auxiliary_device *adev; + bool present; + int id; + + if (mp2->tm_auxdev) + return; + + present = mp2->sfh1_1_ops ? mp2->dev_en.is_sra_present + : (mp2->mp2_ver == MP2_VER_V2 && + amd_sfh_op_idx_enabled(mp2)); + if (!present) + return; + + id = ida_alloc(&sfh_tm_ida, GFP_KERNEL); + if (id < 0) + return; + + adev = auxiliary_device_create(&mp2->pdev->dev, KBUILD_MODNAME, + "tabletmode", NULL, id); + if (!adev) { + ida_free(&sfh_tm_ida, id); + dev_warn(&mp2->pdev->dev, "tabletmode auxdev create failed\n"); + return; + } + + mp2->tm_auxdev = adev; +} + +static void amd_sfh_unregister_tm(struct amd_mp2_dev *mp2) +{ + int id; + + if (!mp2->tm_auxdev) + return; + + id = mp2->tm_auxdev->id; + auxiliary_device_destroy(mp2->tm_auxdev); + mp2->tm_auxdev = NULL; + ida_free(&sfh_tm_ida, id); +} + static void sfh1_1_init_work(struct work_struct *work) { struct amd_mp2_dev *mp2 = container_of(work, struct amd_mp2_dev, work); @@ -402,6 +443,7 @@ static void sfh1_1_init_work(struct work_struct *work) if (rc) dev_warn(&mp2->pdev->dev, "failed to update sysfs group\n"); + amd_sfh_maybe_register_tm(mp2); } static void sfh_init_work(struct work_struct *work) @@ -418,8 +460,10 @@ static void sfh_init_work(struct work_struct *work) return; } + sfh_set_emp2(mp2); amd_sfh_clear_intr(mp2); mp2->init_done = 1; + amd_sfh_maybe_register_tm(mp2); } static void amd_sfh_remove(struct pci_dev *pdev) @@ -427,6 +471,7 @@ static void amd_sfh_remove(struct pci_dev *pdev) struct amd_mp2_dev *mp2 = pci_get_drvdata(pdev); flush_work(&mp2->work); + amd_sfh_unregister_tm(mp2); if (mp2->init_done) mp2->mp2_ops->remove(mp2); } @@ -447,6 +492,7 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i privdata->pdev = pdev; dev_set_drvdata(&pdev->dev, privdata); + rc = pcim_enable_device(pdev); if (rc) return rc; @@ -471,8 +517,9 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i if (rc) return rc; - privdata->sfh1_1_ops = (const struct amd_sfh1_1_ops *)id->driver_data; - if (privdata->sfh1_1_ops) { + privdata->mp2_ver = (enum amd_mp2_version)id->driver_data; + if (privdata->mp2_ver >= MP2_VER_1_1) { + privdata->sfh1_1_ops = &sfh1_1_ops; if (boot_cpu_data.x86 >= 0x1A) privdata->rver = 1; @@ -540,8 +587,7 @@ static SIMPLE_DEV_PM_OPS(amd_mp2_pm_ops, amd_mp2_pci_suspend, static const struct pci_device_id amd_mp2_pci_tbl[] = { { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2) }, - { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2_1_1), - .driver_data = (kernel_ulong_t)&sfh1_1_ops }, + { PCI_DEVICE_DATA(AMD, MP2_1_1, MP2_VER_1_1) }, { } }; MODULE_DEVICE_TABLE(pci, amd_mp2_pci_tbl); diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c index 837d59e7a661..097c5513ccd8 100644 --- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c @@ -8,12 +8,14 @@ * Author: Basavaraj Natikar */ #include +#include #include #include #include "amd_sfh_interface.h" static struct amd_mp2_dev *emp2; +static DEFINE_MUTEX(emp2_lock); static int amd_sfh_wait_response(struct amd_mp2_dev *mp2, u8 sid, u32 cmd_id) { @@ -78,15 +80,48 @@ static struct amd_mp2_ops amd_sfh_ops = { void sfh_deinit_emp2(void) { + guard(mutex)(&emp2_lock); emp2 = NULL; } +void sfh_set_emp2(struct amd_mp2_dev *mp2) +{ + guard(mutex)(&emp2_lock); + emp2 = mp2; +} + void sfh_interface_init(struct amd_mp2_dev *mp2) { mp2->mp2_ops = &amd_sfh_ops; + guard(mutex)(&emp2_lock); emp2 = mp2; } +static int amd_sfh_op_mode_info(u32 *op_mode) +{ + struct sfh_op_mode mode; + bool present; + + if (!op_mode) + return -EINVAL; + if (!emp2) + return -ENODEV; + + present = emp2->sfh1_1_ops ? emp2->dev_en.is_sra_present + : (emp2->mp2_ver == MP2_VER_V2 && + amd_sfh_op_idx_enabled(emp2)); + if (!present) + return -ENODEV; + + mode.val = readl(emp2->mmio + amd_get_c2p_val(emp2, 3)); + dev_dbg(&emp2->pdev->dev, "op-mode: %s (mode=%u)\n", + mode.op_mode.mode == SFH_MODE_TABLET ? "tablet" : "laptop", + mode.op_mode.mode); + *op_mode = mode.op_mode.mode; + + return 0; +} + static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement) { struct sfh_op_mode mode; @@ -160,6 +195,8 @@ static int amd_sfh_als_info(u32 *ambient_light) int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op) { + guard(mutex)(&emp2_lock); + if (sfh_info) { switch (op) { case MT_HPD: @@ -169,6 +206,8 @@ int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op) case MT_SRA: return amd_sfh_mode_info(&sfh_info->platform_type, &sfh_info->laptop_placement); + case MT_OP_MODE: + return amd_sfh_op_mode_info(&sfh_info->op_mode); } } return -EINVAL; diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h index 665c99ad779f..56258c4d1b3a 100644 --- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h +++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h @@ -185,7 +185,6 @@ struct sfh_op_mode { }; void sfh_interface_init(struct amd_mp2_dev *mp2); -void sfh_deinit_emp2(void); void amd_sfh1_1_set_desc_ops(struct amd_mp2_ops *mp2_ops); int amd_sfh_float_to_int(u32 flt32_val); #endif diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index bf7dd0fbf249..f6134c5820ff 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -91,6 +91,9 @@ struct apple_sc_backlight { struct hid_device *hdev; }; +/* T2 VHCI re-enumerates the internal keyboard across system resume. */ +static int apple_backlight_resume_brightness = -1; + struct apple_backlight_config_report { u8 report_id; u8 version; @@ -368,6 +371,8 @@ static const struct apple_non_apple_keyboard non_apple_keyboards[] = { { "TH87" }, /* EPOMAKER TH87 BT mode */ { "HFD Epomaker TH87" }, /* EPOMAKER TH87 USB mode */ { "2.4G Wireless Receiver" }, /* EPOMAKER TH87 dongle */ + { "Thock TKL Wireless" }, /* ENDORFY THOCK TKL BT mode */ + { "USB Dongle" }, /* ENDORFY THOCK TKL dongle */ }; static bool apple_is_non_apple_keyboard(struct hid_device *hdev) @@ -825,6 +830,7 @@ static int apple_backlight_led_set(struct led_classdev *led_cdev, static int apple_backlight_init(struct hid_device *hdev) { int ret; + int brightness; struct apple_sc *asc = hid_get_drvdata(hdev); struct apple_backlight_config_report *rep; @@ -857,16 +863,23 @@ static int apple_backlight_init(struct hid_device *hdev) } asc->backlight->hdev = hdev; - asc->backlight->cdev.name = "apple::kbd_backlight"; + asc->backlight->cdev.name = ":white:" LED_FUNCTION_KBD_BACKLIGHT; asc->backlight->cdev.max_brightness = rep->backlight_on_max; asc->backlight->cdev.brightness_set_blocking = apple_backlight_led_set; - asc->backlight->cdev.flags = LED_CORE_SUSPENDRESUME; + /* VHCI re-enumeration restores the cached brightness in the next probe. */ - ret = apple_backlight_set(hdev, 0, 0); + brightness = READ_ONCE(apple_backlight_resume_brightness); + if (brightness < 0) + brightness = LED_OFF; + else + brightness = min_t(int, brightness, rep->backlight_on_max); + + ret = apple_backlight_set(hdev, brightness, 0); if (ret < 0) { hid_err(hdev, "backlight set request failed: %d\n", ret); goto cleanup_and_exit; } + asc->backlight->cdev.brightness = brightness; ret = devm_led_classdev_register(&hdev->dev, &asc->backlight->cdev); @@ -999,6 +1012,9 @@ static void apple_remove(struct hid_device *hdev) if (asc->quirks & APPLE_RDESC_BATTERY) timer_delete_sync(&asc->battery_timer); + if (asc->backlight) + WRITE_ONCE(apple_backlight_resume_brightness, + asc->backlight->cdev.brightness); hid_hw_stop(hdev); } diff --git a/drivers/hid/hid-appletb-kbd.c b/drivers/hid/hid-appletb-kbd.c index 462010a75899..5cc27066f602 100644 --- a/drivers/hid/hid-appletb-kbd.c +++ b/drivers/hid/hid-appletb-kbd.c @@ -175,7 +175,7 @@ static void appletb_inactivity_work(struct work_struct *work) if (!kbd->has_dimmed) { backlight_device_set_brightness(kbd->backlight_dev, 1); kbd->has_dimmed = true; - mod_delayed_work(system_wq, &kbd->inactivity_work, + mod_delayed_work(system_dfl_wq, &kbd->inactivity_work, secs_to_jiffies(appletb_tb_idle_timeout)); } else if (!kbd->has_turned_off) { backlight_device_set_brightness(kbd->backlight_dev, 0); @@ -201,7 +201,7 @@ static void reset_inactivity_timer(struct appletb_kbd *kbd) kbd->has_turned_off = false; schedule_work(&kbd->restore_brightness_work); } - mod_delayed_work(system_wq, &kbd->inactivity_work, + mod_delayed_work(system_dfl_wq, &kbd->inactivity_work, secs_to_jiffies(appletb_tb_dim_timeout)); } } @@ -423,7 +423,7 @@ static int appletb_kbd_probe(struct hid_device *hdev, const struct hid_device_id INIT_DELAYED_WORK(&kbd->inactivity_work, appletb_inactivity_work); INIT_WORK(&kbd->restore_brightness_work, appletb_restore_brightness_work); - mod_delayed_work(system_wq, &kbd->inactivity_work, + mod_delayed_work(system_dfl_wq, &kbd->inactivity_work, secs_to_jiffies(appletb_tb_dim_timeout)); } diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 3f5e96900b67..ec966fc0a411 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -99,6 +99,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12) #define QUIRK_ROG_ALLY_XPAD BIT(13) #define QUIRK_HID_FN_LOCK BIT(14) +#define QUIRK_FILTER_CAMERA_COMPANION BIT(15) #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ QUIRK_NO_INIT_REPORTS | \ @@ -109,11 +110,36 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define TRKID_SGN ((TRKID_MAX + 1) >> 1) -struct asus_kbd_leds { - struct asus_hid_listener listener; +enum asus_work_action_type { + FN_LOCK_SYNC, + BRIGHTNESS_SET, + WMI_FAN, +}; + +struct hid_raw_event_data { + u8 report_data[FEATURE_KBD_REPORT_SIZE]; + size_t report_size; +}; + +struct asus_work_action { + struct list_head node; + enum asus_work_action_type type; + union { + /* Data for BRIGHTNESS_SET */ + unsigned int brightness; + + /* Data for FN_LOCK_SYNC */ + bool fn_lock; + + /* Data for WMI_FAN */ + struct hid_raw_event_data fan_hid_data; + } data; +}; + +struct asus_worker { struct hid_device *hdev; struct work_struct work; - unsigned int brightness; + struct list_head actions; spinlock_t lock; bool removed; }; @@ -133,7 +159,8 @@ struct asus_drvdata { struct hid_device *hdev; struct input_dev *input; struct input_dev *tp_kbd_input; - struct asus_kbd_leds *kbd_backlight; + struct asus_worker *worker; + unsigned int kbd_backlight_brightness; const struct asus_touchpad_info *tp; struct power_supply *battery; struct power_supply_desc battery_desc; @@ -141,7 +168,7 @@ struct asus_drvdata { int battery_stat; bool battery_in_query; unsigned long battery_next_query; - struct work_struct fn_lock_sync_work; + struct asus_hid_listener listener; bool fn_lock; }; @@ -211,6 +238,29 @@ static const u8 asus_report_id_init[] = { FEATURE_KBD_LED_REPORT_ID2 }; +/* + * Send events to asus-wmi driver for handling special keys + */ +static int asus_wmi_send_event(struct asus_drvdata *drvdata, u8 code) +{ + int err; + u32 retval; + + err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS, + ASUS_WMI_METHODID_NOTIF, code, &retval); + if (err) { + pr_warn("Failed to notify asus-wmi: %d\n", err); + return err; + } + + if (retval != 0) { + pr_warn("Failed to notify asus-wmi (retval): 0x%x\n", retval); + return -EIO; + } + + return 0; +} + static void asus_report_contact_down(struct asus_drvdata *drvdat, int toolType, u8 *data) { @@ -331,25 +381,71 @@ static int asus_e1239t_event(struct asus_drvdata *drvdat, u8 *data, int size) } /* - * Send events to asus-wmi driver for handling special keys + * Used in atomic contexts to schedule work involving sleeps operations or + * asus-wmi interactions. + * + * Caller is responsible to store relevant data in the structure to carry out + * the required action. + * + * This function must be called while the spin lock protecting the workqueue + * is already being held. */ -static int asus_wmi_send_event(struct asus_drvdata *drvdata, u8 code) +static void asus_worker_schedule(struct asus_worker *worker, struct asus_work_action *action) { - int err; - u32 retval; - - err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS, - ASUS_WMI_METHODID_NOTIF, code, &retval); - if (err) { - pr_warn("Failed to notify asus-wmi: %d\n", err); - return err; + if (worker->removed) { + kfree(action); + return; } - if (retval != 0) { - pr_warn("Failed to notify asus-wmi (retval): 0x%x\n", retval); - return -EIO; + list_add_tail(&action->node, &worker->actions); + schedule_work(&worker->work); +} + +static int asus_kbd_fn_lock_set(struct asus_drvdata *drvdata, bool enabled) +{ + struct asus_work_action *action; + unsigned long flags; + + action = kzalloc(sizeof(struct asus_work_action), GFP_ATOMIC); + if (!action) + return -ENOMEM; + + drvdata->fn_lock = enabled; + action->type = FN_LOCK_SYNC; + action->data.fn_lock = drvdata->fn_lock; + INIT_LIST_HEAD(&action->node); + + spin_lock_irqsave(&drvdata->worker->lock, flags); + asus_worker_schedule(drvdata->worker, action); + spin_unlock_irqrestore(&drvdata->worker->lock, flags); + + return 0; +} + +static int asus_kbd_wmi_fan_send(struct asus_drvdata *drvdata, u8 *report_data, + size_t report_size) +{ + struct asus_work_action *action; + unsigned long flags; + + if (report_size > FEATURE_KBD_REPORT_SIZE) { + hid_err(drvdata->hdev, "Invalid report size for fan event: %zu\n", report_size); + return -EINVAL; } + action = kzalloc(sizeof(struct asus_work_action), GFP_NOWAIT); + if (!action) + return -ENOMEM; + + action->type = WMI_FAN; + action->data.fan_hid_data.report_size = report_size; + memcpy(action->data.fan_hid_data.report_data, report_data, report_size); + INIT_LIST_HEAD(&action->node); + + spin_lock_irqsave(&drvdata->worker->lock, flags); + asus_worker_schedule(drvdata->worker, action); + spin_unlock_irqrestore(&drvdata->worker->lock, flags); + return 0; } @@ -357,6 +453,7 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value) { struct asus_drvdata *drvdata = hid_get_drvdata(hdev); + int ret; if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR && (usage->hid & HID_USAGE) != 0x00 && @@ -375,8 +472,11 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field, return !asus_hid_event(ASUS_EV_BRTTOGGLE); case KEY_FN_ESC: if (drvdata->quirks & QUIRK_HID_FN_LOCK) { - drvdata->fn_lock = !drvdata->fn_lock; - schedule_work(&drvdata->fn_lock_sync_work); + ret = asus_kbd_fn_lock_set(drvdata, !drvdata->fn_lock); + if (ret) { + hid_err(hdev, "Error while toggling FN lock: %d\n", ret); + return ret; + } } break; } @@ -389,6 +489,12 @@ static int asus_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { struct asus_drvdata *drvdata = hid_get_drvdata(hdev); + int ret; + + if (size < 2) { + hid_dbg(hdev, "Unexpected keyboard report size %d\n", size); + return 0; + } if (drvdata->battery && data[0] == BATTERY_REPORT_ID) return asus_report_battery(drvdata, data, size); @@ -414,19 +520,13 @@ static int asus_raw_event(struct hid_device *hdev, * pass to userspace so it can implement its own fan control. */ if (data[1] == ASUS_FAN_CTRL_KEY_CODE) { - int ret = asus_wmi_send_event(drvdata, ASUS_FAN_CTRL_KEY_CODE); + ret = asus_kbd_wmi_fan_send(drvdata, data, size); - if (ret == 0) { - /* Successfully handled by asus-wmi, block event */ + /* if execution deferred successfully block event */ + if (ret == 0) return -1; - } - /* - * Warn if asus-wmi failed (but not if it's unavailable). - * Let the event reach userspace in all failure cases. - */ - if (ret != -ENODEV) - hid_warn(hdev, "Failed to notify asus-wmi: %d\n", ret); + return ret; } /* @@ -460,6 +560,17 @@ static int asus_raw_event(struct hid_device *hdev, return -1; } + /* + * The camera-toggle key reports its vendor usage (0x85) together with a + * companion state byte in the same array report, e.g. "5a 85 01" and + * "5a 85 10" for the two toggle positions. The 0x10 companion aliases the + * brightness-down vendor usage and would spuriously dim the panel, so drop + * the companion slots and leave only the camera usage for input mapping. + */ + if (drvdata->quirks & QUIRK_FILTER_CAMERA_COMPANION && + report->id == FEATURE_KBD_REPORT_ID && size >= 3 && data[1] == 0x85) + memset(&data[2], 0, size - 2); + return 0; } @@ -569,59 +680,157 @@ static int asus_kbd_disable_oobe(struct hid_device *hdev) return 0; } -static int asus_kbd_set_fn_lock(struct hid_device *hdev, bool enabled) +static void asus_kbd_set_fn_lock(struct hid_device *hdev, bool enabled) { - u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xd0, 0x4e, !!enabled }; - - return asus_kbd_set_report(hdev, buf, sizeof(buf)); -} - -static void asus_sync_fn_lock(struct work_struct *work) -{ - struct asus_drvdata *drvdata = - container_of(work, struct asus_drvdata, fn_lock_sync_work); - - asus_kbd_set_fn_lock(drvdata->hdev, drvdata->fn_lock); -} - -static void asus_schedule_work(struct asus_kbd_leds *led) -{ - unsigned long flags; - - spin_lock_irqsave(&led->lock, flags); - if (!led->removed) - schedule_work(&led->work); - spin_unlock_irqrestore(&led->lock, flags); -} - -static void asus_kbd_backlight_set(struct asus_hid_listener *listener, - int brightness) -{ - struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds, - listener); - unsigned long flags; - - spin_lock_irqsave(&led->lock, flags); - led->brightness = brightness; - spin_unlock_irqrestore(&led->lock, flags); - - asus_schedule_work(led); -} - -static void asus_kbd_backlight_work(struct work_struct *work) -{ - struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work); - u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 }; + const u8 buf[FEATURE_KBD_REPORT_SIZE] = { FEATURE_KBD_REPORT_ID, 0xd0, 0x4e, !!enabled }; int ret; + + ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); + if (ret < 0) + hid_err(hdev, "Asus failed to set fn lock: %d\n", ret); +} + +static void asus_kbd_set_brightness(struct hid_device *hdev, u8 brightness) +{ + const u8 buf[FEATURE_KBD_REPORT_SIZE] = { + FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, brightness + }; + int ret; + + ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); + if (ret < 0) + hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret); +} + +static void asus_kbd_wmi_fan(struct hid_device *hdev, struct hid_raw_event_data *data) +{ + struct asus_drvdata *drvdata = hid_get_drvdata(hdev); + int ret; + + ret = asus_wmi_send_event(drvdata, ASUS_FAN_CTRL_KEY_CODE); + + /* + * Warn if asus-wmi failed (but not if it's unavailable). + * Let the event reach userspace in all failure cases. + */ + switch (ret) { + case -ENODEV: + break; + case 0: + return; + default: + hid_warn(hdev, "Failed to notify asus-wmi: %d\n", ret); + break; + } + + /* + * Fallback: pass the raw event to the HID core; to avoid + * racing against the hid_report_raw_event() that generated + * this event use the same locking mechanism and wait for + * that function to terminate and signal the deferred execution + * before raising the stored event. + */ + down(&hdev->driver_input_lock); + hid_report_raw_event(hdev, HID_INPUT_REPORT, + data->report_data, data->report_size, + data->report_size, 1); + up(&hdev->driver_input_lock); +} + +static void asus_kbd_backlight_set(struct asus_hid_listener *listener, int brightness) +{ + struct asus_drvdata *drvdata = container_of(listener, struct asus_drvdata, listener); + struct asus_worker *worker = drvdata->worker; + struct asus_work_action *action; unsigned long flags; - spin_lock_irqsave(&led->lock, flags); - buf[4] = led->brightness; - spin_unlock_irqrestore(&led->lock, flags); + drvdata->kbd_backlight_brightness = brightness; - ret = asus_kbd_set_report(led->hdev, buf, sizeof(buf)); - if (ret < 0) - hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret); + action = kzalloc(sizeof(struct asus_work_action), GFP_NOWAIT); + if (!action) + return; + + action->type = BRIGHTNESS_SET; + action->data.brightness = brightness; + INIT_LIST_HEAD(&action->node); + + spin_lock_irqsave(&worker->lock, flags); + asus_worker_schedule(worker, action); + spin_unlock_irqrestore(&worker->lock, flags); +} + +static void asus_work(struct work_struct *work) +{ + struct asus_worker *worker = container_of(work, struct asus_worker, work); + struct asus_work_action *action = NULL; + unsigned long flags; + + /* Save the action to be performed and clear the flag */ + spin_lock_irqsave(&worker->lock, flags); + if (!list_empty(&worker->actions)) { + action = list_first_entry(&worker->actions, + struct asus_work_action, node); + list_del(&action->node); + } + spin_unlock_irqrestore(&worker->lock, flags); + + if (!action) + return; + + switch (action->type) { + case BRIGHTNESS_SET: + asus_kbd_set_brightness(worker->hdev, action->data.brightness); + break; + case FN_LOCK_SYNC: + asus_kbd_set_fn_lock(worker->hdev, action->data.fn_lock); + break; + case WMI_FAN: + asus_kbd_wmi_fan(worker->hdev, &action->data.fan_hid_data); + break; + default: + hid_err(worker->hdev, "Invalid action type: %d\n", action->type); + break; + } + + kfree(action); + + /* Re-schedule if there are more pending actions */ + spin_lock_irqsave(&worker->lock, flags); + if (!list_empty(&worker->actions)) + schedule_work(&worker->work); + spin_unlock_irqrestore(&worker->lock, flags); +} + +static int asus_worker_create(struct hid_device *hdev, struct asus_drvdata *drvdata) +{ + drvdata->worker = devm_kzalloc(&hdev->dev, sizeof(struct asus_worker), GFP_KERNEL); + if (!drvdata->worker) + return -ENOMEM; + + drvdata->worker->removed = false; + drvdata->worker->hdev = hdev; + INIT_LIST_HEAD(&drvdata->worker->actions); + + INIT_WORK(&drvdata->worker->work, asus_work); + spin_lock_init(&drvdata->worker->lock); + + return 0; +} + +static void asus_worker_stop(struct asus_worker *worker) +{ + struct asus_work_action *action, *tmp; + unsigned long flags; + + spin_lock_irqsave(&worker->lock, flags); + worker->removed = true; + list_for_each_entry_safe(action, tmp, &worker->actions, node) { + list_del(&action->node); + kfree(action); + } + spin_unlock_irqrestore(&worker->lock, flags); + + cancel_work_sync(&worker->work); } /* @@ -644,7 +853,7 @@ static int mcu_parse_version_string(const u8 *response, size_t response_size) dots++; } - if (dots != 2 || p >= end || (p + 3) >= end) + if (dots != 2 || end - p < 3) return -EINVAL; memcpy(buf, p, 3); @@ -753,30 +962,18 @@ static int asus_kbd_register_leds(struct hid_device *hdev) return ret; } - if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) { + if ((drvdata->quirks & QUIRK_ROG_ALLY_XPAD) && hid_is_usb(hdev)) { intf = to_usb_interface(hdev->dev.parent); udev = interface_to_usbdev(intf); validate_mcu_fw_version(hdev, le16_to_cpu(udev->descriptor.idProduct)); } - drvdata->kbd_backlight = devm_kzalloc(&hdev->dev, - sizeof(struct asus_kbd_leds), - GFP_KERNEL); - if (!drvdata->kbd_backlight) - return -ENOMEM; - - drvdata->kbd_backlight->removed = false; - drvdata->kbd_backlight->brightness = 0; - drvdata->kbd_backlight->hdev = hdev; - drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set; - INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work); - spin_lock_init(&drvdata->kbd_backlight->lock); - - ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener); + drvdata->listener.brightness_set = asus_kbd_backlight_set; + ret = asus_hid_register_listener(&drvdata->listener); if (ret < 0) { - /* No need to have this still around */ - devm_kfree(&hdev->dev, drvdata->kbd_backlight); + hid_err(hdev, "Unable to register kbd brightness listener: %d\n", ret); + drvdata->listener.brightness_set = NULL; } return ret; @@ -998,11 +1195,9 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) drvdata->input = input; - if (drvdata->quirks & QUIRK_HID_FN_LOCK) { - drvdata->fn_lock = true; - INIT_WORK(&drvdata->fn_lock_sync_work, asus_sync_fn_lock); - asus_kbd_set_fn_lock(hdev, true); - } + if ((drvdata->quirks & QUIRK_HID_FN_LOCK) && + (asus_kbd_fn_lock_set(drvdata, true))) + hid_warn(hdev, "Error while setting FN lock to ON\n"); return 0; } @@ -1045,6 +1240,8 @@ static int asus_input_mapping(struct hid_device *hdev, case 0x6c: asus_map_key_clear(KEY_SLEEP); break; case 0x7c: asus_map_key_clear(KEY_MICMUTE); break; case 0x82: asus_map_key_clear(KEY_CAMERA); break; + case 0x85: asus_map_key_clear(KEY_CAMERA); break; + case 0x86: asus_map_key_clear(KEY_PROG1); break; /* MyASUS key */ case 0x88: asus_map_key_clear(KEY_RFKILL); break; case 0xb5: asus_map_key_clear(KEY_CALC); break; case 0xc4: asus_map_key_clear(KEY_KBDILLUMUP); break; @@ -1054,6 +1251,7 @@ static int asus_input_mapping(struct hid_device *hdev, case 0x7e: asus_map_key_clear(KEY_EMOJI_PICKER); break; case 0x8b: asus_map_key_clear(KEY_PROG1); break; /* ProArt Creator Hub key */ + case 0x5f: asus_map_key_clear(KEY_PROG2); break; /* S-shaped programmable key */ case 0x6b: asus_map_key_clear(KEY_F21); break; /* ASUS touchpad toggle */ case 0x38: asus_map_key_clear(KEY_PROG1); break; /* ROG key */ case 0xba: asus_map_key_clear(KEY_PROG2); break; /* Fn+C ASUS Splendid */ @@ -1165,20 +1363,16 @@ static int asus_start_multitouch(struct hid_device *hdev) static int __maybe_unused asus_resume(struct hid_device *hdev) { struct asus_drvdata *drvdata = hid_get_drvdata(hdev); - int ret = 0; - if (drvdata->kbd_backlight) { - const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, - drvdata->kbd_backlight->brightness }; - ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); - if (ret < 0) { - hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret); - goto asus_resume_err; - } - } + /* + * If we have a backlight listener registered, restore the previous state, + * in case of error do not fail: most models restore the backlight + * automatically, and the error is non-fatal. + */ + if (drvdata->listener.brightness_set) + asus_kbd_backlight_set(&drvdata->listener, drvdata->kbd_backlight_brightness); -asus_resume_err: - return ret; + return 0; } static int __maybe_unused asus_reset_resume(struct hid_device *hdev) @@ -1200,10 +1394,8 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) int ret; drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); - if (drvdata == NULL) { - hid_err(hdev, "Can't alloc Asus descriptor\n"); + if (drvdata == NULL) return -ENOMEM; - } hid_set_drvdata(hdev, drvdata); @@ -1288,8 +1480,15 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) is_vendor = true; } + ret = asus_worker_create(hdev, drvdata); + if (ret) { + hid_warn(hdev, "Failed to initialize worker: %d\n", ret); + return ret; + } + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { + asus_worker_stop(drvdata->worker); hid_err(hdev, "Asus hw start failed: %d\n", ret); return ret; } @@ -1337,6 +1536,10 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) return 0; err_stop_hw: + if (drvdata->listener.brightness_set) + asus_hid_unregister_listener(&drvdata->listener); + + asus_worker_stop(drvdata->worker); hid_hw_stop(hdev); return ret; } @@ -1344,21 +1547,11 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) static void asus_remove(struct hid_device *hdev) { struct asus_drvdata *drvdata = hid_get_drvdata(hdev); - unsigned long flags; - if (drvdata->kbd_backlight) { - asus_hid_unregister_listener(&drvdata->kbd_backlight->listener); - - spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags); - drvdata->kbd_backlight->removed = true; - spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags); - - cancel_work_sync(&drvdata->kbd_backlight->work); - } - - if (drvdata->quirks & QUIRK_HID_FN_LOCK) - cancel_work_sync(&drvdata->fn_lock_sync_work); + if (drvdata->listener.brightness_set) + asus_hid_unregister_listener(&drvdata->listener); + asus_worker_stop(drvdata->worker); hid_hw_stop(hdev); } @@ -1477,6 +1670,9 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc, static const struct hid_device_id asus_devices[] = { { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD), I2C_KEYBOARD_QUIRKS}, + { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_I2C_ZENBOOK_KEYBOARD), + I2C_KEYBOARD_QUIRKS | QUIRK_FILTER_CAMERA_COMPANION }, { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD), I2C_TOUCHPAD_QUIRKS }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, @@ -1494,6 +1690,9 @@ static const struct hid_device_id asus_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2), QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK }, + { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2), + QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR), QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, diff --git a/drivers/hid/hid-axff.c b/drivers/hid/hid-axff.c index 3c5c2bf02425..2f46447086df 100644 --- a/drivers/hid/hid-axff.c +++ b/drivers/hid/hid-axff.c @@ -59,30 +59,24 @@ static int axff_play(struct input_dev *dev, void *data, struct ff_effect *effect return 0; } -static int axff_init(struct hid_device *hid) +static int ax_input_configured(struct hid_device *hid, struct hid_input *hidinput) { struct axff_device *axff; struct hid_report *report; - struct hid_input *hidinput; - struct list_head *report_list =&hid->report_enum[HID_OUTPUT_REPORT].report_list; - struct input_dev *dev; + struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct input_dev *dev = hidinput->input; int field_count = 0; int i, j; int error; - if (list_empty(&hid->inputs)) { - hid_err(hid, "no inputs found\n"); - return -ENODEV; - } - hidinput = list_first_entry(&hid->inputs, struct hid_input, list); - dev = hidinput->input; + if (!list_is_first(&hidinput->list, &hid->inputs)) + return 0; - if (list_empty(report_list)) { + report = list_first_entry_or_null(report_list, struct hid_report, list); + if (!report) { hid_err(hid, "no output reports found\n"); return -ENODEV; } - - report = list_first_entry(report_list, struct hid_report, list); for (i = 0; i < report->maxfield; i++) { for (j = 0; j < report->field[i]->report_count; j++) { report->field[i]->value[j] = 0x00; @@ -100,13 +94,13 @@ static int axff_init(struct hid_device *hid) if (!axff) return -ENOMEM; + axff->report = report; set_bit(FF_RUMBLE, dev->ffbit); error = input_ff_create_memless(dev, axff, axff_play); if (error) goto err_free_mem; - axff->report = report; hid_hw_request(hid, axff->report, HID_REQ_SET_REPORT); hid_info(hid, "Force Feedback for ACRUX game controllers by Sergei Kolzun \n"); @@ -118,7 +112,8 @@ static int axff_init(struct hid_device *hid) return error; } #else -static inline int axff_init(struct hid_device *hid) +static inline int ax_input_configured(struct hid_device *hid, + struct hid_input *hidinput) { return 0; } @@ -136,23 +131,11 @@ static int ax_probe(struct hid_device *hdev, const struct hid_device_id *id) return error; } - error = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); + error = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (error) { hid_err(hdev, "hw start failed\n"); return error; } - - error = axff_init(hdev); - if (error) { - /* - * Do not fail device initialization completely as device - * may still be partially operable, just warn. - */ - hid_warn(hdev, - "Failed to enable force feedback support, error: %d\n", - error); - } - /* * We need to start polling device right away, otherwise * it will go into a coma. @@ -185,6 +168,7 @@ static struct hid_driver ax_driver = { .id_table = ax_devices, .probe = ax_probe, .remove = ax_remove, + .input_configured = ax_input_configured, }; module_hid_driver(ax_driver); diff --git a/drivers/hid/hid-betopff.c b/drivers/hid/hid-betopff.c index 8a7fe895926c..f802046a688a 100644 --- a/drivers/hid/hid-betopff.c +++ b/drivers/hid/hid-betopff.c @@ -52,31 +52,24 @@ static int hid_betopff_play(struct input_dev *dev, void *data, return 0; } -static int betopff_init(struct hid_device *hid) +static int betop_input_configured(struct hid_device *hid, struct hid_input *hidinput) { struct betopff_device *betopff; struct hid_report *report; - struct hid_input *hidinput; struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; - struct input_dev *dev; + struct input_dev *dev = hidinput->input; int error; int i, j; - if (list_empty(&hid->inputs)) { - hid_err(hid, "no inputs found\n"); - return -ENODEV; - } + if (!list_is_first(&hidinput->list, &hid->inputs)) + return 0; - hidinput = list_first_entry(&hid->inputs, struct hid_input, list); - dev = hidinput->input; - - if (list_empty(report_list)) { + report = list_first_entry_or_null(report_list, struct hid_report, list); + if (!report) { hid_err(hid, "no output reports found\n"); return -ENODEV; } - - report = list_first_entry(report_list, struct hid_report, list); /* * Actually there are 4 fields for 4 Bytes as below: * ----------------------------------------- @@ -104,6 +97,7 @@ static int betopff_init(struct hid_device *hid) if (!betopff) return -ENOMEM; + betopff->report = report; set_bit(FF_RUMBLE, dev->ffbit); error = input_ff_create_memless(dev, betopff, hid_betopff_play); @@ -112,7 +106,6 @@ static int betopff_init(struct hid_device *hid) return error; } - betopff->report = report; hid_hw_request(hid, betopff->report, HID_REQ_SET_REPORT); hid_info(hid, "Force feedback for betop devices by huangbo \n"); @@ -130,20 +123,15 @@ static int betop_probe(struct hid_device *hdev, const struct hid_device_id *id) ret = hid_parse(hdev); if (ret) { hid_err(hdev, "parse failed\n"); - goto err; + return ret; } - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { hid_err(hdev, "hw start failed\n"); - goto err; + return ret; } - - betopff_init(hdev); - return 0; -err: - return ret; } static const struct hid_device_id betop_devices[] = { @@ -159,6 +147,7 @@ static struct hid_driver betop_driver = { .name = "betop", .id_table = betop_devices, .probe = betop_probe, + .input_configured = betop_input_configured, }; module_hid_driver(betop_driver); diff --git a/drivers/hid/hid-bigbenff.c b/drivers/hid/hid-bigbenff.c index 9f05465358d9..3c87317ccc6b 100644 --- a/drivers/hid/hid-bigbenff.c +++ b/drivers/hid/hid-bigbenff.c @@ -366,58 +366,29 @@ static void bigben_remove(struct hid_device *hid) hid_hw_stop(hid); } -static int bigben_probe(struct hid_device *hid, - const struct hid_device_id *id) +static int bigben_input_configured(struct hid_device *hid, struct hid_input *hidinput) { - struct bigben_device *bigben; - struct hid_input *hidinput; + struct bigben_device *bigben = hid_get_drvdata(hid); + struct input_dev *input_dev = hidinput->input; struct led_classdev *led; char *name; size_t name_sz; int n, error; - bigben = devm_kzalloc(&hid->dev, sizeof(*bigben), GFP_KERNEL); - if (!bigben) - return -ENOMEM; - hid_set_drvdata(hid, bigben); - bigben->hid = hid; - bigben->removed = false; - - error = hid_parse(hid); - if (error) { - hid_err(hid, "parse failed\n"); - return error; - } - - error = hid_hw_start(hid, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); - if (error) { - hid_err(hid, "hw start failed\n"); - return error; - } + if (!list_is_first(&hidinput->list, &hid->inputs)) + return 0; bigben->report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 8); if (!bigben->report) { hid_err(hid, "no output report found\n"); - error = -ENODEV; - goto error_hw_stop; + return -ENODEV; } - if (list_empty(&hid->inputs)) { - hid_err(hid, "no inputs found\n"); - error = -ENODEV; - goto error_hw_stop; - } + set_bit(FF_RUMBLE, input_dev->ffbit); - hidinput = list_first_entry(&hid->inputs, struct hid_input, list); - set_bit(FF_RUMBLE, hidinput->input->ffbit); - - INIT_WORK(&bigben->worker, bigben_worker); - spin_lock_init(&bigben->lock); - - error = input_ff_create_memless(hidinput->input, NULL, - hid_bigben_play_effect); + error = input_ff_create_memless(input_dev, NULL, hid_bigben_play_effect); if (error) - goto error_hw_stop; + return error; name_sz = strlen(dev_name(&hid->dev)) + strlen(":red:bigben#") + 1; @@ -427,10 +398,9 @@ static int bigben_probe(struct hid_device *hid, sizeof(struct led_classdev) + name_sz, GFP_KERNEL ); - if (!led) { - error = -ENOMEM; - goto error_hw_stop; - } + if (!led) + return -ENOMEM; + name = (void *)(&led[1]); snprintf(name, name_sz, "%s:red:bigben%d", @@ -444,7 +414,7 @@ static int bigben_probe(struct hid_device *hid, bigben->leds[n] = led; error = devm_led_classdev_register(&hid->dev, led); if (error) - goto error_hw_stop; + return error; } /* initial state: LED1 is on, no rumble effect */ @@ -458,10 +428,36 @@ static int bigben_probe(struct hid_device *hid, hid_info(hid, "LED and force feedback support for BigBen gamepad\n"); return 0; +} -error_hw_stop: - hid_hw_stop(hid); - return error; +static int bigben_probe(struct hid_device *hid, const struct hid_device_id *id) +{ + struct bigben_device *bigben; + int error; + + bigben = devm_kzalloc(&hid->dev, sizeof(*bigben), GFP_KERNEL); + if (!bigben) + return -ENOMEM; + + hid_set_drvdata(hid, bigben); + bigben->hid = hid; + bigben->removed = false; + INIT_WORK(&bigben->worker, bigben_worker); + spin_lock_init(&bigben->lock); + + error = hid_parse(hid); + if (error) { + hid_err(hid, "parse failed\n"); + return error; + } + + error = hid_hw_start(hid, HID_CONNECT_DEFAULT); + if (error) { + hid_err(hid, "hw start failed\n"); + return error; + } + + return 0; } static const __u8 *bigben_report_fixup(struct hid_device *hid, __u8 *rdesc, @@ -487,6 +483,7 @@ static struct hid_driver bigben_driver = { .probe = bigben_probe, .report_fixup = bigben_report_fixup, .remove = bigben_remove, + .input_configured = bigben_input_configured, }; module_hid_driver(bigben_driver); diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index cf123347a2af..a3ff0514f9cd 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -379,6 +379,9 @@ static int hid_add_field(struct hid_parser *parser, unsigned report_type, unsign static u32 item_udata(struct hid_item *item) { + if (item->format != HID_ITEM_FORMAT_SHORT) + return 0; + switch (item->size) { case 1: return item->data.u8; case 2: return item->data.u16; @@ -389,6 +392,9 @@ static u32 item_udata(struct hid_item *item) static s32 item_sdata(struct hid_item *item) { + if (item->format != HID_ITEM_FORMAT_SHORT) + return 0; + switch (item->size) { case 1: return item->data.s8; case 2: return item->data.s16; @@ -1933,13 +1939,14 @@ int hid_set_field(struct hid_field *field, unsigned offset, __s32 value) size = field->report_size; - hid_dump_input(field->report->device, field->usage + offset, value); - if (offset >= field->report_count) { hid_err(field->report->device, "offset (%d) exceeds report_count (%d)\n", offset, field->report_count); return -1; } + + hid_dump_input(field->report->device, field->usage + offset, value); + if (field->logical_minimum < 0) { if (value != snto32(s32ton(value, size), size)) { hid_err(field->report->device, "value %d is out of range\n", value); @@ -2312,8 +2319,8 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) if (hid_hiddev(hdev)) connect_mask |= HID_CONNECT_HIDDEV_FORCE; - if ((connect_mask & HID_CONNECT_HIDINPUT) && !hidinput_connect(hdev, - connect_mask & HID_CONNECT_HIDINPUT_FORCE)) + if ((connect_mask & HID_CONNECT_HIDINPUT) && + !hidinput_connect(hdev, connect_mask)) hdev->claimed |= HID_CLAIMED_INPUT; if ((connect_mask & HID_CONNECT_HIDDEV) && hdev->hiddev_connect && @@ -2335,10 +2342,6 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) hid_process_ordering(hdev); - if ((hdev->claimed & HID_CLAIMED_INPUT) && - (connect_mask & HID_CONNECT_FF) && hdev->ff_init) - hdev->ff_init(hdev); - len = 0; if (hdev->claimed & HID_CLAIMED_INPUT) len += sprintf(buf + len, "input"); @@ -2447,9 +2450,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); } @@ -2842,6 +2852,8 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv) */ if (ret) { + if (hdev->io_started) + hid_device_io_stop(hdev); devres_release_group(&hdev->dev, hdev->devres_group_id); hid_close_report(hdev); hdev->driver = NULL; @@ -2907,6 +2919,45 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *a, } static DEVICE_ATTR_RO(modalias); +/* + * Expose this as bustype instead of bus as + * that's the name the input subsystem uses + */ +static ssize_t bustype_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + + return sysfs_emit(buf, "%04x\n", hdev->bus); +} +static DEVICE_ATTR_RO(bustype); + +#define HID_DEV_ID_ATTR(name) \ +static ssize_t name##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct hid_device *hdev = to_hid_device(dev); \ + \ + return sysfs_emit(buf, "%04x\n", hdev->name); \ +} \ +static DEVICE_ATTR_RO(name) + +HID_DEV_ID_ATTR(vendor); +HID_DEV_ID_ATTR(product); +HID_DEV_ID_ATTR(version); + +static struct attribute *hid_dev_id_attrs[] = { + &dev_attr_bustype.attr, + &dev_attr_vendor.attr, + &dev_attr_product.attr, + &dev_attr_version.attr, + NULL +}; +static const struct attribute_group hid_dev_id_attr_group = { + .name = "id", + .attrs = hid_dev_id_attrs, +}; static struct attribute *hid_dev_attrs[] = { &dev_attr_modalias.attr, NULL, @@ -2919,7 +2970,11 @@ static const struct attribute_group hid_dev_group = { .attrs = hid_dev_attrs, .bin_attrs = hid_dev_bin_attrs, }; -__ATTRIBUTE_GROUPS(hid_dev); +static const struct attribute_group *hid_dev_groups[] = { + &hid_dev_group, + &hid_dev_id_attr_group, + NULL +}; static int hid_uevent(const struct device *dev, struct kobj_uevent_env *env) { diff --git a/drivers/hid/hid-corsair-void.c b/drivers/hid/hid-corsair-void.c index 5e9a5b8f7f16..071a663a6c26 100644 --- a/drivers/hid/hid-corsair-void.c +++ b/drivers/hid/hid-corsair-void.c @@ -92,6 +92,9 @@ #define CORSAIR_VOID_STATUS_REPORT_ID 0x64 #define CORSAIR_VOID_FIRMWARE_REPORT_ID 0x66 +#define CORSAIR_VOID_STATUS_REPORT_SIZE 5 +#define CORSAIR_VOID_FIRMWARE_REPORT_SIZE 5 + #define CORSAIR_VOID_USB_SIDETONE_REQUEST 0x1 #define CORSAIR_VOID_USB_SIDETONE_REQUEST_TYPE 0x21 #define CORSAIR_VOID_USB_SIDETONE_VALUE 0x200 @@ -742,6 +745,13 @@ static int corsair_void_raw_event(struct hid_device *hid_dev, /* Description of packets are documented at the top of this file */ if (hid_report->id == CORSAIR_VOID_STATUS_REPORT_ID) { + if (size < CORSAIR_VOID_STATUS_REPORT_SIZE) { + hid_warn_ratelimited(hid_dev, + "unexpected status report of size %d", + size); + return 1; + } + drvdata->mic_up = FIELD_GET(CORSAIR_VOID_MIC_MASK, data[2]); drvdata->connected = (data[3] == CORSAIR_VOID_WIRELESS_CONNECTED) || drvdata->is_wired; @@ -750,6 +760,13 @@ static int corsair_void_raw_event(struct hid_device *hid_dev, FIELD_GET(CORSAIR_VOID_CAPACITY_MASK, data[2]), data[3], data[4]); } else if (hid_report->id == CORSAIR_VOID_FIRMWARE_REPORT_ID) { + if (size < CORSAIR_VOID_FIRMWARE_REPORT_SIZE) { + hid_warn_ratelimited(hid_dev, + "unexpected firmware report of size %d", + size); + return 1; + } + drvdata->fw_receiver_major = data[1]; drvdata->fw_receiver_minor = data[2]; drvdata->fw_headset_major = data[3]; diff --git a/drivers/hid/hid-corsair.c b/drivers/hid/hid-corsair.c index 21cd8b12a757..278c6efb565d 100644 --- a/drivers/hid/hid-corsair.c +++ b/drivers/hid/hid-corsair.c @@ -524,8 +524,8 @@ static void k90_cleanup_backlight(struct hid_device *dev) if (drvdata->backlight) { drvdata->backlight->removed = true; - led_classdev_unregister(&drvdata->backlight->cdev); cancel_work_sync(&drvdata->backlight->work); + led_classdev_unregister(&drvdata->backlight->cdev); kfree(drvdata->backlight->cdev.name); kfree(drvdata->backlight); } @@ -540,11 +540,12 @@ static void k90_cleanup_macro_functions(struct hid_device *dev) sysfs_remove_group(&dev->dev.kobj, &k90_attr_group); k90->record_led.removed = true; - led_classdev_unregister(&k90->record_led.cdev); cancel_work_sync(&k90->record_led.work); + led_classdev_unregister(&k90->record_led.cdev); kfree(k90->record_led.cdev.name); kfree(k90); + drvdata->k90 = NULL; } } @@ -596,10 +597,10 @@ static int corsair_probe(struct hid_device *dev, const struct hid_device_id *id) static void corsair_remove(struct hid_device *dev) { + hid_hw_stop(dev); + k90_cleanup_macro_functions(dev); k90_cleanup_backlight(dev); - - hid_hw_stop(dev); } static int corsair_event(struct hid_device *dev, struct hid_field *field, diff --git a/drivers/hid/hid-dr.c b/drivers/hid/hid-dr.c index 8a8f68a7feb0..a1e10ee8df4d 100644 --- a/drivers/hid/hid-dr.c +++ b/drivers/hid/hid-dr.c @@ -71,29 +71,26 @@ static int drff_play(struct input_dev *dev, void *data, return 0; } -static int drff_init(struct hid_device *hid) +static int dr_input_configured(struct hid_device *hid, struct hid_input *hidinput) { struct drff_device *drff; struct hid_report *report; - struct hid_input *hidinput; struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; - struct input_dev *dev; + struct input_dev *dev = hidinput->input; int error; - if (list_empty(&hid->inputs)) { - hid_err(hid, "no inputs found\n"); - return -ENODEV; - } - hidinput = list_first_entry(&hid->inputs, struct hid_input, list); - dev = hidinput->input; + if (!list_is_first(&hidinput->list, &hid->inputs)) + return 0; - if (list_empty(report_list)) { + if (hid->product != 0x0006) + return 0; + + report = list_first_entry_or_null(report_list, struct hid_report, list); + if (!report) { hid_err(hid, "no output reports found\n"); return -ENODEV; } - - report = list_first_entry(report_list, struct hid_report, list); if (report->maxfield < 1) { hid_err(hid, "no fields in the report\n"); return -ENODEV; @@ -108,6 +105,7 @@ static int drff_init(struct hid_device *hid) if (!drff) return -ENOMEM; + drff->report = report; set_bit(FF_RUMBLE, dev->ffbit); error = input_ff_create_memless(dev, drff, drff_play); @@ -116,7 +114,6 @@ static int drff_init(struct hid_device *hid) return error; } - drff->report = report; drff->report->field[0]->value[0] = 0xf3; drff->report->field[0]->value[1] = 0x00; drff->report->field[0]->value[2] = 0x00; @@ -132,7 +129,8 @@ static int drff_init(struct hid_device *hid) return 0; } #else -static inline int drff_init(struct hid_device *hid) +static inline int dr_input_configured(struct hid_device *hid, + struct hid_input *hidinput) { return 0; } @@ -266,43 +264,9 @@ static int dr_input_mapping(struct hid_device *hdev, struct hid_input *hi, return 0; } -static int dr_probe(struct hid_device *hdev, const struct hid_device_id *id) -{ - int ret; - - dev_dbg(&hdev->dev, "DragonRise Inc. HID hardware probe..."); - - ret = hid_parse(hdev); - if (ret) { - hid_err(hdev, "parse failed\n"); - goto err; - } - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); - if (ret) { - hid_err(hdev, "hw start failed\n"); - goto err; - } - - switch (hdev->product) { - case 0x0006: - ret = drff_init(hdev); - if (ret) { - dev_err(&hdev->dev, "force feedback init failed\n"); - hid_hw_stop(hdev); - goto err; - } - break; - } - - return 0; -err: - return ret; -} - static const struct hid_device_id dr_devices[] = { - { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006), }, - { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011), }, + { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006), }, + { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011), }, { } }; MODULE_DEVICE_TABLE(hid, dr_devices); @@ -311,8 +275,8 @@ static struct hid_driver dr_driver = { .name = "dragonrise", .id_table = dr_devices, .report_fixup = dr_report_fixup, - .probe = dr_probe, .input_mapping = dr_input_mapping, + .input_configured = dr_input_configured, }; module_hid_driver(dr_driver); diff --git a/drivers/hid/hid-emsff.c b/drivers/hid/hid-emsff.c index 1b4ad18f6051..d8e559e0524f 100644 --- a/drivers/hid/hid-emsff.c +++ b/drivers/hid/hid-emsff.c @@ -43,29 +43,23 @@ static int emsff_play(struct input_dev *dev, void *data, return 0; } -static int emsff_init(struct hid_device *hid) +static int ems_input_configured(struct hid_device *hid, struct hid_input *hidinput) { struct emsff_device *emsff; struct hid_report *report; - struct hid_input *hidinput; struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; - struct input_dev *dev; + struct input_dev *dev = hidinput->input; int error; - if (list_empty(&hid->inputs)) { - hid_err(hid, "no inputs found\n"); - return -ENODEV; - } - hidinput = list_first_entry(&hid->inputs, struct hid_input, list); - dev = hidinput->input; + if (!list_is_first(&hidinput->list, &hid->inputs)) + return 0; - if (list_empty(report_list)) { + report = list_first_entry_or_null(report_list, struct hid_report, list); + if (!report) { hid_err(hid, "no output reports found\n"); return -ENODEV; } - - report = list_first_entry(report_list, struct hid_report, list); if (report->maxfield < 1) { hid_err(hid, "no fields in the report\n"); return -ENODEV; @@ -80,6 +74,7 @@ static int emsff_init(struct hid_device *hid) if (!emsff) return -ENOMEM; + emsff->report = report; set_bit(FF_RUMBLE, dev->ffbit); error = input_ff_create_memless(dev, emsff, emsff_play); @@ -88,7 +83,6 @@ static int emsff_init(struct hid_device *hid) return error; } - emsff->report = report; emsff->report->field[0]->value[0] = 0x01; emsff->report->field[0]->value[1] = 0x00; emsff->report->field[0]->value[2] = 0x00; @@ -103,34 +97,6 @@ static int emsff_init(struct hid_device *hid) return 0; } -static int ems_probe(struct hid_device *hdev, const struct hid_device_id *id) -{ - int ret; - - ret = hid_parse(hdev); - if (ret) { - hid_err(hdev, "parse failed\n"); - goto err; - } - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); - if (ret) { - hid_err(hdev, "hw start failed\n"); - goto err; - } - - ret = emsff_init(hdev); - if (ret) { - dev_err(&hdev->dev, "force feedback init failed\n"); - hid_hw_stop(hdev); - goto err; - } - - return 0; -err: - return ret; -} - static const struct hid_device_id ems_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II) }, { } @@ -140,7 +106,7 @@ MODULE_DEVICE_TABLE(hid, ems_devices); static struct hid_driver ems_driver = { .name = "hkems", .id_table = ems_devices, - .probe = ems_probe, + .input_configured = ems_input_configured, }; module_hid_driver(ems_driver); diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index 70e2eedb465a..64c9c940db46 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -240,6 +240,8 @@ struct ft260_device { struct mutex lock; u8 write_buf[FT260_REPORT_MAX_LENGTH]; unsigned long need_wakeup_at; + /* Protects read_buf, read_idx and read_len against ft260_raw_event() */ + spinlock_t read_lock; u8 *read_buf; u16 read_idx; u16 read_len; @@ -501,16 +503,25 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data, int timeout, ret = 0; struct ft260_i2c_read_request_report rep; struct hid_device *hdev = dev->hdev; + unsigned long irqflags; u8 bus_busy = 0; + /* + * STOP terminates the last chunk; clear means hold the bus so a + * follow-up call continues the same I2C transaction. + */ + bool want_stop = !!(flag & FT260_FLAG_STOP); if ((flag & FT260_FLAG_START_REPEATED) == FT260_FLAG_START_REPEATED) flag = FT260_FLAG_START_REPEATED; - else + else if (flag & FT260_FLAG_START) flag = FT260_FLAG_START; + else + flag = 0; /* no fresh START - continue current transaction */ do { if (len <= rd_data_max) { rd_len = len; - flag |= FT260_FLAG_STOP; + if (want_stop) + flag |= FT260_FLAG_STOP; } else { rd_len = rd_data_max; } @@ -526,9 +537,11 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data, reinit_completion(&dev->wait); + spin_lock_irqsave(&dev->read_lock, irqflags); dev->read_idx = 0; dev->read_buf = data; dev->read_len = rd_len; + spin_unlock_irqrestore(&dev->read_lock, irqflags); ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep)); if (ret < 0) { @@ -543,7 +556,9 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data, goto ft260_i2c_read_exit; } + spin_lock_irqsave(&dev->read_lock, irqflags); dev->read_buf = NULL; + spin_unlock_irqrestore(&dev->read_lock, irqflags); if (flag & FT260_FLAG_STOP) bus_busy = FT260_I2C_STATUS_BUS_BUSY; @@ -562,7 +577,9 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data, } while (len > 0); ft260_i2c_read_exit: + spin_lock_irqsave(&dev->read_lock, irqflags); dev->read_buf = NULL; + spin_unlock_irqrestore(&dev->read_lock, irqflags); return ret; } @@ -708,14 +725,41 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags, break; case I2C_SMBUS_BLOCK_DATA: if (read_write == I2C_SMBUS_READ) { + u8 count = 0; + + /* + * SMBus 2.0 section 6.5.7 block read in one I2C + * transaction: + * + * S Addr+Wr A Reg A Sr Addr+Rd A Count A Data... P + * + * The count is read separately and validated + * before sizing the data read so a misbehaving + * slave cannot drive a write past data->block[]. + */ ret = ft260_smbus_write(dev, addr, cmd, NULL, 0, FT260_FLAG_START); if (ret) goto smbus_exit; - ret = ft260_i2c_read(dev, addr, data->block, - data->block[0] + 1, - FT260_FLAG_START_STOP_REPEATED); + ret = ft260_i2c_read(dev, addr, &count, 1, + FT260_FLAG_START_REPEATED); + if (ret) + goto smbus_exit; + + if (count == 0 || count > I2C_SMBUS_BLOCK_MAX) { + hid_warn(hdev, + "smbus block read: invalid count %u from slave 0x%02x\n", + count, addr); + ft260_i2c_reset(hdev); + ret = -EPROTO; + goto smbus_exit; + } + + data->block[0] = count; + + ret = ft260_i2c_read(dev, addr, data->block + 1, + count, FT260_FLAG_STOP); } else { ret = ft260_smbus_write(dev, addr, cmd, data->block, data->block[0] + 1, @@ -1018,6 +1062,7 @@ static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id) "FT260 usb-i2c bridge"); mutex_init(&dev->lock); + spin_lock_init(&dev->read_lock); init_completion(&dev->wait); ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY); @@ -1067,6 +1112,7 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report, { struct ft260_device *dev = hid_get_drvdata(hdev); struct ft260_i2c_input_report *xfer = (void *)data; + unsigned long irqflags; if (size < offsetof(struct ft260_i2c_input_report, data)) { hid_err(hdev, "short report %d\n", size); @@ -1075,6 +1121,8 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report, if (xfer->report >= FT260_I2C_REPORT_MIN && xfer->report <= FT260_I2C_REPORT_MAX) { + bool complete_read; + ft260_dbg("i2c resp: rep %#02x len %d size %d\n", xfer->report, xfer->length, size); @@ -1085,8 +1133,15 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report, return -1; } + /* + * Hold read_lock so a timed-out ft260_i2c_read() cannot + * clear read_buf between the NULL check and the memcpy. + */ + spin_lock_irqsave(&dev->read_lock, irqflags); + if ((dev->read_buf == NULL) || (xfer->length > dev->read_len - dev->read_idx)) { + spin_unlock_irqrestore(&dev->read_lock, irqflags); hid_err(hdev, "unexpected report %#02x, length %d\n", xfer->report, xfer->length); return -1; @@ -1095,8 +1150,11 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report, memcpy(&dev->read_buf[dev->read_idx], &xfer->data, xfer->length); dev->read_idx += xfer->length; + complete_read = dev->read_idx == dev->read_len; - if (dev->read_idx == dev->read_len) + spin_unlock_irqrestore(&dev->read_lock, irqflags); + + if (complete_read) complete(&dev->wait); } else { diff --git a/drivers/hid/hid-gaff.c b/drivers/hid/hid-gaff.c index 8b99686b63df..ec793a179b47 100644 --- a/drivers/hid/hid-gaff.c +++ b/drivers/hid/hid-gaff.c @@ -60,32 +60,23 @@ static int hid_gaff_play(struct input_dev *dev, void *data, return 0; } -static int gaff_init(struct hid_device *hid) +static int gaff_input_configured(struct hid_device *hid, struct hid_input *hidinput) { struct gaff_device *gaff; struct hid_report *report; - struct hid_input *hidinput; struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; - struct list_head *report_ptr = report_list; - struct input_dev *dev; + struct input_dev *dev = hidinput->input; int error; - if (list_empty(&hid->inputs)) { - hid_err(hid, "no inputs found\n"); - return -ENODEV; - } - hidinput = list_entry(hid->inputs.next, struct hid_input, list); - dev = hidinput->input; + if (!list_is_first(&hidinput->list, &hid->inputs)) + return 0; - if (list_empty(report_list)) { + report = list_first_entry_or_null(report_list, struct hid_report, list); + if (!report) { hid_err(hid, "no output reports found\n"); return -ENODEV; } - - report_ptr = report_ptr->next; - - report = list_entry(report_ptr, struct hid_report, list); if (report->maxfield < 1) { hid_err(hid, "no fields in the report\n"); return -ENODEV; @@ -100,6 +91,7 @@ static int gaff_init(struct hid_device *hid) if (!gaff) return -ENOMEM; + gaff->report = report; set_bit(FF_RUMBLE, dev->ffbit); error = input_ff_create_memless(dev, gaff, hid_gaff_play); @@ -108,7 +100,6 @@ static int gaff_init(struct hid_device *hid) return error; } - gaff->report = report; gaff->report->field[0]->value[0] = 0x51; gaff->report->field[0]->value[1] = 0x00; gaff->report->field[0]->value[2] = 0x00; @@ -125,37 +116,13 @@ static int gaff_init(struct hid_device *hid) return 0; } #else -static inline int gaff_init(struct hid_device *hdev) +static inline int gaff_input_configured(struct hid_device *hdev, + struct hid_input *hidinput) { return 0; } #endif -static int ga_probe(struct hid_device *hdev, const struct hid_device_id *id) -{ - int ret; - - dev_dbg(&hdev->dev, "Greenasia HID hardware probe..."); - - ret = hid_parse(hdev); - if (ret) { - hid_err(hdev, "parse failed\n"); - goto err; - } - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); - if (ret) { - hid_err(hdev, "hw start failed\n"); - goto err; - } - - gaff_init(hdev); - - return 0; -err: - return ret; -} - static const struct hid_device_id ga_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, 0x0012), }, { } @@ -165,7 +132,7 @@ MODULE_DEVICE_TABLE(hid, ga_devices); static struct hid_driver ga_driver = { .name = "greenasia", .id_table = ga_devices, - .probe = ga_probe, + .input_configured = gaff_input_configured, }; module_hid_driver(ga_driver); diff --git a/drivers/hid/hid-goodix-spi.c b/drivers/hid/hid-goodix-spi.c index 288cb827e9d6..03d549efbdce 100644 --- a/drivers/hid/hid-goodix-spi.c +++ b/drivers/hid/hid-goodix-spi.c @@ -722,11 +722,8 @@ static int goodix_spi_probe(struct spi_device *spi) error = devm_request_threaded_irq(&ts->spi->dev, ts->spi->irq, NULL, goodix_hid_irq, IRQF_ONESHOT, "goodix_spi_hid", ts); - if (error) { - dev_err(ts->dev, "could not register interrupt, irq = %d, %d", - ts->spi->irq, error); + if (error) goto err_destroy_hid; - } return 0; diff --git a/drivers/hid/hid-google-stadiaff.c b/drivers/hid/hid-google-stadiaff.c index 6b38d2421d3d..0214aae6b0fa 100644 --- a/drivers/hid/hid-google-stadiaff.c +++ b/drivers/hid/hid-google-stadiaff.c @@ -17,10 +17,7 @@ struct stadiaff_device { struct hid_device *hid; struct hid_report *report; - spinlock_t lock; - bool removed; - uint16_t strong_magnitude; - uint16_t weak_magnitude; + u32 magnitudes; struct work_struct work; }; @@ -29,12 +26,10 @@ static void stadiaff_work(struct work_struct *work) struct stadiaff_device *stadiaff = container_of(work, struct stadiaff_device, work); struct hid_field *rumble_field = stadiaff->report->field[0]; - unsigned long flags; + u32 mags = READ_ONCE(stadiaff->magnitudes); - spin_lock_irqsave(&stadiaff->lock, flags); - rumble_field->value[0] = stadiaff->strong_magnitude; - rumble_field->value[1] = stadiaff->weak_magnitude; - spin_unlock_irqrestore(&stadiaff->lock, flags); + rumble_field->value[0] = mags & 0xffff; + rumble_field->value[1] = (mags >> 16) & 0xffff; hid_hw_request(stadiaff->hid, stadiaff->report, HID_REQ_SET_REPORT); } @@ -44,33 +39,50 @@ static int stadiaff_play(struct input_dev *dev, void *data, { struct hid_device *hid = input_get_drvdata(dev); struct stadiaff_device *stadiaff = hid_get_drvdata(hid); - unsigned long flags; + u32 mags = (u32)effect->u.rumble.strong_magnitude | + ((u32)effect->u.rumble.weak_magnitude << 16); - spin_lock_irqsave(&stadiaff->lock, flags); - if (!stadiaff->removed) { - stadiaff->strong_magnitude = effect->u.rumble.strong_magnitude; - stadiaff->weak_magnitude = effect->u.rumble.weak_magnitude; - schedule_work(&stadiaff->work); - } - spin_unlock_irqrestore(&stadiaff->lock, flags); + WRITE_ONCE(stadiaff->magnitudes, mags); + schedule_work(&stadiaff->work); return 0; } -static int stadiaff_init(struct hid_device *hid) +static int stadia_input_open(struct input_dev *dev) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct stadiaff_device *stadiaff = hid_get_drvdata(hid); + int error; + + error = hid_hw_open(hid); + if (error) + return error; + + enable_work(&stadiaff->work); + return 0; +} + +static void stadia_input_close(struct input_dev *dev) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct stadiaff_device *stadiaff = hid_get_drvdata(hid); + + WRITE_ONCE(stadiaff->magnitudes, 0); + stadiaff_work(&stadiaff->work); + disable_work_sync(&stadiaff->work); + + hid_hw_close(hid); +} + +static int stadia_input_configured(struct hid_device *hid, struct hid_input *hidinput) { struct stadiaff_device *stadiaff; struct hid_report *report; - struct hid_input *hidinput; - struct input_dev *dev; + struct input_dev *dev = hidinput->input; int error; - if (list_empty(&hid->inputs)) { - hid_err(hid, "no inputs found\n"); - return -ENODEV; - } - hidinput = list_entry(hid->inputs.next, struct hid_input, list); - dev = hidinput->input; + if (!list_is_first(&hidinput->list, &hid->inputs)) + return 0; report = hid_validate_values(hid, HID_OUTPUT_REPORT, STADIA_FF_REPORT_ID, 0, 2); @@ -90,56 +102,19 @@ static int stadiaff_init(struct hid_device *hid) if (error) return error; - stadiaff->removed = false; stadiaff->hid = hid; stadiaff->report = report; INIT_WORK(&stadiaff->work, stadiaff_work); - spin_lock_init(&stadiaff->lock); + disable_work_sync(&stadiaff->work); + + dev->open = stadia_input_open; + dev->close = stadia_input_close; hid_info(hid, "Force Feedback for Google Stadia controller\n"); return 0; } -static int stadia_probe(struct hid_device *hdev, const struct hid_device_id *id) -{ - int ret; - - ret = hid_parse(hdev); - if (ret) { - hid_err(hdev, "parse failed\n"); - return ret; - } - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); - if (ret) { - hid_err(hdev, "hw start failed\n"); - return ret; - } - - ret = stadiaff_init(hdev); - if (ret) { - hid_err(hdev, "force feedback init failed\n"); - hid_hw_stop(hdev); - return ret; - } - - return 0; -} - -static void stadia_remove(struct hid_device *hid) -{ - struct stadiaff_device *stadiaff = hid_get_drvdata(hid); - unsigned long flags; - - spin_lock_irqsave(&stadiaff->lock, flags); - stadiaff->removed = true; - spin_unlock_irqrestore(&stadiaff->lock, flags); - - cancel_work_sync(&stadiaff->work); - hid_hw_stop(hid); -} - static const struct hid_device_id stadia_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) }, @@ -150,8 +125,7 @@ MODULE_DEVICE_TABLE(hid, stadia_devices); static struct hid_driver stadia_driver = { .name = "stadia", .id_table = stadia_devices, - .probe = stadia_probe, - .remove = stadia_remove, + .input_configured = stadia_input_configured, }; module_hid_driver(stadia_driver); diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c index deadab28cdbe..8760eeb08b2c 100644 --- a/drivers/hid/hid-haptic.c +++ b/drivers/hid/hid-haptic.c @@ -82,16 +82,24 @@ int hid_haptic_input_configured(struct hid_device *hdev, struct hid_haptic_device *haptic, struct hid_input *hi) { + int error; - if (hi->application == HID_DG_TOUCHPAD) { - if (haptic->auto_trigger_report && - haptic->manual_trigger_report) { - __set_bit(INPUT_PROP_PRESSUREPAD, hi->input->propbit); - return 1; - } + if (hi->application != HID_DG_TOUCHPAD) + return -1; + + if (!haptic->auto_trigger_report || !haptic->manual_trigger_report) + return 0; + + __set_bit(INPUT_PROP_PRESSUREPAD, hi->input->propbit); + + error = hid_haptic_init(hdev, haptic, hi->input); + if (error) { + dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n", + hdev->name); return 0; } - return -1; + + return 1; } EXPORT_SYMBOL_GPL(hid_haptic_input_configured); @@ -187,7 +195,7 @@ static void fill_effect_buf(struct hid_haptic_device *haptic, value = waveform_ordinal; break; default: - break; + continue; } field->value[j] = value; @@ -401,11 +409,9 @@ static void hid_haptic_destroy(struct ff_device *ff) } int hid_haptic_init(struct hid_device *hdev, - struct hid_haptic_device **haptic_ptr) + struct hid_haptic_device *haptic, + struct input_dev *dev) { - struct hid_haptic_device *haptic = *haptic_ptr; - struct input_dev *dev = NULL; - struct hid_input *hidinput; struct ff_device *ff; int ret = 0, r; struct ff_haptic_effect stop_effect = { @@ -447,19 +453,6 @@ int hid_haptic_init(struct hid_device *hdev, for (r = 0; r < haptic->auto_trigger_report->maxfield; r++) parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]); - list_for_each_entry(hidinput, &hdev->inputs, list) { - if (hidinput->application == HID_DG_TOUCHPAD) { - dev = hidinput->input; - break; - } - } - - if (!dev) { - dev_err(&hdev->dev, "Failed to find the input device\n"); - ret = -ENODEV; - goto duration_map; - } - haptic->input_dev = dev; haptic->manual_trigger_report_len = hid_report_len(haptic->manual_trigger_report); @@ -535,10 +528,6 @@ int hid_haptic_init(struct hid_device *hdev, input_free: input_ff_destroy(dev); - /* Do not let double free happen, input_ff_destroy will call - * hid_haptic_destroy. - */ - *haptic_ptr = NULL; /* Restore dev flush and event */ dev->flush = flush; dev->event = event; diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h index c6539ac04c1d..6332991a7844 100644 --- a/drivers/hid/hid-haptic.h +++ b/drivers/hid/hid-haptic.h @@ -69,7 +69,8 @@ int hid_haptic_input_mapping(struct hid_device *hdev, int hid_haptic_input_configured(struct hid_device *hdev, struct hid_haptic_device *haptic, struct hid_input *hi); -int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr); +int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device *haptic, + struct input_dev *dev); void hid_haptic_handle_press_release(struct hid_haptic_device *haptic); void hid_haptic_pressure_reset(struct hid_haptic_device *haptic); void hid_haptic_pressure_increase(struct hid_haptic_device *haptic, @@ -107,7 +108,8 @@ static inline void hid_haptic_reset(struct hid_device *hdev, struct hid_haptic_device *haptic) {} static inline -int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr) +int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device *haptic, + struct input_dev *dev) { return 0; } diff --git a/drivers/hid/hid-holtekff.c b/drivers/hid/hid-holtekff.c index 32d08f7a660d..4834d42b2fa6 100644 --- a/drivers/hid/hid-holtekff.c +++ b/drivers/hid/hid-holtekff.c @@ -120,30 +120,24 @@ static int holtekff_play(struct input_dev *dev, void *data, return 0; } -static int holtekff_init(struct hid_device *hid) +static int holtek_input_configured(struct hid_device *hid, struct hid_input *hidinput) { struct holtekff_device *holtekff; struct hid_report *report; - struct hid_input *hidinput; struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; - struct input_dev *dev; + struct input_dev *dev = hidinput->input; int error; - if (list_empty(&hid->inputs)) { - hid_err(hid, "no inputs found\n"); - return -ENODEV; - } - hidinput = list_entry(hid->inputs.next, struct hid_input, list); - dev = hidinput->input; + if (!list_is_first(&hidinput->list, &hid->inputs)) + return 0; - if (list_empty(report_list)) { + report = list_first_entry_or_null(report_list, struct hid_report, list); + if (!report) { hid_err(hid, "no output report found\n"); return -ENODEV; } - report = list_entry(report_list->next, struct hid_report, list); - if (report->maxfield < 1 || report->field[0]->report_count != 7) { hid_err(hid, "unexpected output report layout\n"); return -ENODEV; @@ -172,35 +166,13 @@ static int holtekff_init(struct hid_device *hid) return 0; } #else -static inline int holtekff_init(struct hid_device *hid) +static inline int holtek_input_configured(struct hid_device *hid, + struct hid_input *hidinput) { return 0; } #endif -static int holtek_probe(struct hid_device *hdev, const struct hid_device_id *id) -{ - int ret; - - ret = hid_parse(hdev); - if (ret) { - hid_err(hdev, "parse failed\n"); - goto err; - } - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); - if (ret) { - hid_err(hdev, "hw start failed\n"); - goto err; - } - - holtekff_init(hdev); - - return 0; -err: - return ret; -} - static const struct hid_device_id holtek_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) }, { } @@ -210,7 +182,7 @@ MODULE_DEVICE_TABLE(hid, holtek_devices); static struct hid_driver holtek_driver = { .name = "holtek", .id_table = holtek_devices, - .probe = holtek_probe, + .input_configured = holtek_input_configured, }; module_hid_driver(holtek_driver); diff --git a/drivers/hid/hid-huawei.c b/drivers/hid/hid-huawei.c index 6a616bf21b38..ee3fc6f68475 100644 --- a/drivers/hid/hid-huawei.c +++ b/drivers/hid/hid-huawei.c @@ -44,11 +44,12 @@ static const __u8 huawei_cd30_kbd_rdesc_fixed[] = { static const __u8 *huawei_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { - struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_interface *intf = hid_is_usb(hdev) ? + to_usb_interface(hdev->dev.parent) : NULL; switch (hdev->product) { case USB_DEVICE_ID_HUAWEI_CD30KBD: - if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { + if (!intf || intf->cur_altsetting->desc.bInterfaceNumber == 1) { if (*rsize != sizeof(huawei_cd30_kbd_rdesc_fixed) || memcmp(huawei_cd30_kbd_rdesc_fixed, rdesc, sizeof(huawei_cd30_kbd_rdesc_fixed)) != 0) { diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c index 7d2b0063df15..6579bd19da13 100644 --- a/drivers/hid/hid-hyperv.c +++ b/drivers/hid/hid-hyperv.c @@ -13,6 +13,9 @@ #include #include +#if IS_ENABLED(CONFIG_HID_HYPERV_MOUSE_KUNIT_TEST) +#include +#endif struct hv_input_dev_info { unsigned int size; @@ -171,18 +174,32 @@ static void mousevsc_free_device(struct mousevsc_dev *device) } static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device, - struct synthhid_device_info *device_info) + struct synthhid_device_info *device_info, + u32 device_info_size) { int ret = 0; struct hid_descriptor *desc; struct mousevsc_prt_msg ack; + size_t desc_offset; + size_t desc_size; input_device->dev_info_status = -ENOMEM; + if (device_info_size < sizeof(*device_info)) { + input_device->dev_info_status = -EINVAL; + goto cleanup; + } + input_device->hid_dev_info = device_info->hid_dev_info; desc = &device_info->hid_descriptor; + desc_offset = offsetof(struct synthhid_device_info, hid_descriptor); + desc_size = device_info_size - desc_offset; if (desc->bLength == 0) goto cleanup; + if (desc->bLength < sizeof(*desc) || desc->bLength > desc_size) { + input_device->dev_info_status = -EINVAL; + goto cleanup; + } /* The pointer is not NULL when we resume from hibernation */ kfree(input_device->hid_desc); @@ -197,6 +214,10 @@ static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device, input_device->dev_info_status = -EINVAL; goto cleanup; } + if (input_device->report_desc_size > desc_size - desc->bLength) { + input_device->dev_info_status = -EINVAL; + goto cleanup; + } /* The pointer is not NULL when we resume from hibernation */ kfree(input_device->report_desc); @@ -222,13 +243,18 @@ static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device, ack.ack.header.size = 1; ack.ack.reserved = 0; - ret = vmbus_sendpacket(input_device->device->channel, - &ack, - sizeof(struct pipe_prt_msg) + - sizeof(struct synthhid_device_info_ack), - (unsigned long)&ack, - VM_PKT_DATA_INBAND, - VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (IS_ENABLED(CONFIG_HID_HYPERV_MOUSE_KUNIT_TEST) && + !input_device->device) { + ret = 0; + } else { + ret = vmbus_sendpacket(input_device->device->channel, + &ack, + sizeof(struct pipe_prt_msg) + + sizeof(struct synthhid_device_info_ack), + (unsigned long)&ack, + VM_PKT_DATA_INBAND, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + } if (!ret) input_device->dev_info_status = 0; @@ -273,14 +299,17 @@ static void mousevsc_on_receive(struct hv_device *device, break; case SYNTH_HID_INITIAL_DEVICE_INFO: - WARN_ON(pipe_msg->size < sizeof(struct hv_input_dev_info)); + if (WARN_ON_ONCE(pipe_msg->size < + sizeof(struct synthhid_device_info))) + break; /* * Parse out the device info into device attr, * hid desc and report desc */ mousevsc_on_receive_device_info(input_dev, - (struct synthhid_device_info *)pipe_msg->data); + (struct synthhid_device_info *)pipe_msg->data, + pipe_msg->size); break; case SYNTH_HID_INPUT_REPORT: input_report = @@ -614,5 +643,100 @@ static void __exit mousevsc_exit(void) MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Microsoft Hyper-V Synthetic HID Driver"); +#if IS_ENABLED(CONFIG_HID_HYPERV_MOUSE_KUNIT_TEST) +static struct mousevsc_dev *mousevsc_kunit_alloc_dev(struct kunit *test) +{ + struct mousevsc_dev *input_dev; + + input_dev = kunit_kzalloc(test, sizeof(*input_dev), GFP_KERNEL); + if (!input_dev) + return NULL; + + init_completion(&input_dev->wait_event); + + return input_dev; +} + +static void mousevsc_device_info_zero_blength(struct kunit *test) +{ + struct synthhid_device_info *info; + struct mousevsc_dev *input_dev; + + input_dev = mousevsc_kunit_alloc_dev(test); + KUNIT_ASSERT_NOT_NULL(test, input_dev); + info = kunit_kzalloc(test, sizeof(*info), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, info); + + info->hid_descriptor.bLength = 0; + + mousevsc_on_receive_device_info(input_dev, info, sizeof(*info)); + + KUNIT_EXPECT_EQ(test, input_dev->dev_info_status, -ENOMEM); +} + +static void mousevsc_device_info_valid_descriptor(struct kunit *test) +{ + struct synthhid_device_info *info; + struct mousevsc_dev *input_dev; + u8 *report; + + input_dev = mousevsc_kunit_alloc_dev(test); + KUNIT_ASSERT_NOT_NULL(test, input_dev); + info = kunit_kzalloc(test, sizeof(*info) + 4, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, info); + + info->hid_descriptor.bLength = sizeof(struct hid_descriptor); + info->hid_descriptor.rpt_desc.wDescriptorLength = cpu_to_le16(4); + report = ((u8 *)&info->hid_descriptor) + info->hid_descriptor.bLength; + memset(report, 0x42, 4); + + mousevsc_on_receive_device_info(input_dev, info, sizeof(*info) + 4); + + KUNIT_EXPECT_EQ(test, input_dev->dev_info_status, 0); + KUNIT_EXPECT_EQ(test, input_dev->report_desc_size, 4); + KUNIT_EXPECT_MEMEQ(test, input_dev->report_desc, report, 4); + + kfree(input_dev->hid_desc); + kfree(input_dev->report_desc); +} + +static void mousevsc_device_info_report_desc_oob(struct kunit *test) +{ + struct synthhid_device_info *info; + struct mousevsc_dev *input_dev; + u8 *report; + + input_dev = mousevsc_kunit_alloc_dev(test); + KUNIT_ASSERT_NOT_NULL(test, input_dev); + info = kunit_kzalloc(test, sizeof(*info) + 8, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, info); + + info->hid_descriptor.bLength = sizeof(struct hid_descriptor); + info->hid_descriptor.rpt_desc.wDescriptorLength = cpu_to_le16(64); + report = ((u8 *)&info->hid_descriptor) + info->hid_descriptor.bLength; + memset(report, 0x42, 8); + + mousevsc_on_receive_device_info(input_dev, info, sizeof(*info) + 8); + + KUNIT_EXPECT_EQ(test, input_dev->dev_info_status, -EINVAL); + + kfree(input_dev->hid_desc); +} + +static struct kunit_case mousevsc_test_cases[] = { + KUNIT_CASE(mousevsc_device_info_zero_blength), + KUNIT_CASE(mousevsc_device_info_valid_descriptor), + KUNIT_CASE(mousevsc_device_info_report_desc_oob), + {} +}; + +static struct kunit_suite mousevsc_test_suite = { + .name = "hid_hyperv_mouse", + .test_cases = mousevsc_test_cases, +}; + +kunit_test_suite(mousevsc_test_suite); +#endif + module_init(mousevsc_init); module_exit(mousevsc_exit); diff --git a/drivers/hid/hid-hyperx.c b/drivers/hid/hid-hyperx.c new file mode 100644 index 000000000000..18d2dc24cc76 --- /dev/null +++ b/drivers/hid/hid-hyperx.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for the HyperX QuadCast 2 microphone + * + * The tap-to-mute button is handled entirely in the device firmware: it gates + * the audio internally and does not send the Telephony "Phone Mute" usage its + * own report descriptor advertises. The resulting mute state is only reported + * through a vendor-defined collection, which hid-input cannot map, so the + * button is invisible to userspace. + * + * Copyright (c) 2026 Benjamin Blume + */ + +#include +#include +#include + +#include "hid-ids.h" + +#define HYPERX_QC2_REPORT_ID 0x77 +#define HYPERX_QC2_EVENT_MUTE 0x06 +#define HYPERX_QC2_MUTE_LEN 3 + +struct hyperx_drvdata { + struct input_dev *input; + bool muted; + bool have_state; +}; + +static int hyperx_input_configured(struct hid_device *hdev, + struct hid_input *hi) +{ + struct hyperx_drvdata *drvdata = hid_get_drvdata(hdev); + + /* + * The device exposes several application collections. Prefer the + * telephony one, which already advertises KEY_MICMUTE, and fall back + * to the first collection otherwise. + */ + if (!drvdata->input || test_bit(KEY_MICMUTE, hi->input->keybit)) { + drvdata->input = hi->input; + input_set_capability(drvdata->input, EV_KEY, KEY_MICMUTE); + } + + return 0; +} + +static int hyperx_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct hyperx_drvdata *drvdata = hid_get_drvdata(hdev); + bool muted; + + if (!(hdev->claimed & HID_CLAIMED_INPUT) || !drvdata->input) + return 0; + + if (size < HYPERX_QC2_MUTE_LEN || data[0] != HYPERX_QC2_REPORT_ID || + data[1] != HYPERX_QC2_EVENT_MUTE) + return 0; + + muted = data[2]; + if (drvdata->have_state && muted == drvdata->muted) + return 0; + + drvdata->muted = muted; + drvdata->have_state = true; + + /* + * The device reports the resulting absolute state, while KEY_MICMUTE is + * a momentary key that userspace acts on as a toggle. Emit one + * keypress per state change. + */ + input_report_key(drvdata->input, KEY_MICMUTE, 1); + input_sync(drvdata->input); + input_report_key(drvdata->input, KEY_MICMUTE, 0); + input_sync(drvdata->input); + + return 0; +} + +static int hyperx_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct hyperx_drvdata *drvdata; + int ret; + + drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + hid_set_drvdata(hdev, drvdata); + + ret = hid_parse(hdev); + if (ret) + return ret; + + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); +} + +static const struct hid_device_id hyperx_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_HP, + USB_PRODUCT_ID_HP_HYPERX_QUADCAST_2) }, + { } +}; +MODULE_DEVICE_TABLE(hid, hyperx_devices); + +static struct hid_driver hyperx_driver = { + .name = "hyperx", + .id_table = hyperx_devices, + .probe = hyperx_probe, + .input_configured = hyperx_input_configured, + .raw_event = hyperx_raw_event, +}; +module_hid_driver(hyperx_driver); + +MODULE_DESCRIPTION("HID driver for the HyperX QuadCast 2 microphone"); +MODULE_AUTHOR("Benjamin Blume "); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 1059922baaac..341bf587863b 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -220,6 +220,7 @@ #define USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD 0x183d #define USB_DEVICE_ID_ASUSTEK_T304_KEYBOARD 0x184a #define USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD 0x8585 +#define USB_DEVICE_ID_ASUSTEK_I2C_ZENBOOK_KEYBOARD 0x4b42 #define USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD 0x0101 #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854 #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837 @@ -263,6 +264,9 @@ #define USB_VENDOR_ID_BAANTO 0x2453 #define USB_DEVICE_ID_BAANTO_MT_190W2 0x0100 +#define USB_VENDOR_ID_BEITONG 0x20dd +#define USB_DEVICE_ID_BEITONG_KP20D 0x5159 + #define USB_VENDOR_ID_BELKIN 0x050d #define USB_DEVICE_ID_FLIP_KVM 0x3201 @@ -688,6 +692,7 @@ #define USB_DEVICE_ID_HORI_WIRELESS_SWITCH_PAD 0x00f6 #define USB_VENDOR_ID_HP 0x03f0 +#define USB_PRODUCT_ID_HP_HYPERX_QUADCAST_2 0x07b4 #define USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A 0x464a #define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a #define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A 0x0b4a @@ -775,6 +780,7 @@ #define USB_DEVICE_ID_JESS_YUREX 0x1010 #define USB_DEVICE_ID_ASUS_MD_5112 0x5112 #define USB_DEVICE_ID_REDRAGON_ASURA 0x760b +#define USB_DEVICE_ID_AULA_MINI_60_HE_PRO 0xfefe #define USB_VENDOR_ID_JESS2 0x0f30 #define USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD 0x0111 @@ -967,6 +973,7 @@ #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2 0xc543 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3 0xc547 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4 0xc54d +#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_5 0xc545 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY 0xc53a #define USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER 0xc548 #define USB_DEVICE_ID_SPACETRAVELLER 0xc623 @@ -1071,7 +1078,12 @@ #define USB_DEVICE_ID_MOZA_R16_R21_2 0x0010 #define USB_VENDOR_ID_MSI 0x1770 +#define USB_VENDOR_ID_MSI_2 0x0db0 #define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00 +#define USB_DEVICE_ID_MSI_CLAW_XINPUT 0x1901 +#define USB_DEVICE_ID_MSI_CLAW_DINPUT 0x1902 +#define USB_DEVICE_ID_MSI_CLAW_DESKTOP 0x1903 +#define USB_DEVICE_ID_MSI_CLAW_BIOS 0x1904 #define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400 #define USB_DEVICE_ID_N_S_HARMONY 0xc359 @@ -1374,11 +1386,31 @@ #define USB_DEVICE_ID_STEAM_CONTROLLER 0x1102 #define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS 0x1142 #define USB_DEVICE_ID_STEAM_DECK 0x1205 +#define USB_DEVICE_ID_STEAM_CONTROLLER_IBEX 0x1302 +#define USB_DEVICE_ID_STEAM_CONTROLLER_IBEX_BLE 0x1303 +#define USB_DEVICE_ID_STEAM_CONTROLLER_PROTEUS 0x1304 +#define USB_DEVICE_ID_STEAM_CONTROLLER_NEREID 0x1305 #define USB_VENDOR_ID_STEELSERIES 0x1038 #define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410 -#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6 -#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X 0x12b6 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X 0x2253 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7 0x2202 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X 0x2206 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2 0x22a4 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO 0x223a +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW 0x227a +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026 0x22a1 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026 0x22a7 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026 0x22a5 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026 0x22a9 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2 0x227e +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2 0x2258 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2 0x229e +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3 0x22ad +#define USB_DEVICE_ID_STEELSERIES_MSI_KLC 0x1122 +#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161 #define USB_VENDOR_ID_SUN 0x0430 #define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 3487600cadb4..edd6bbf8342b 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -375,6 +375,9 @@ static const struct hid_device_id hid_battery_quirks[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICTRACKPAD), HID_BATTERY_QUIRK_IGNORE }, + { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, + USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC), + HID_BATTERY_QUIRK_AVOID_QUERY }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084), HID_BATTERY_QUIRK_IGNORE }, @@ -432,17 +435,25 @@ static int hidinput_scale_battery_capacity(struct hid_battery *bat, static int hidinput_query_battery_capacity(struct hid_battery *bat) { int ret; + /* + * The capacity field may not be the first field in the report: some + * devices (e.g. the Apple Magic Trackpad 2 over Bluetooth) precede it + * with status flags. Read it from its actual byte offset in the report + * (report_offset is in bits; the leading byte is the report id). + */ + int offset = 1 + bat->report_offset / 8; + int len = offset + 1; - u8 *buf __free(kfree) = kmalloc(4, GFP_KERNEL); + u8 *buf __free(kfree) = kmalloc(max(len, 4), GFP_KERNEL); if (!buf) return -ENOMEM; - ret = hid_hw_raw_request(bat->dev, bat->report_id, buf, 4, + ret = hid_hw_raw_request(bat->dev, bat->report_id, buf, max(len, 4), bat->report_type, HID_REQ_GET_REPORT); - if (ret < 2) + if (ret < len) return -ENODATA; - return hidinput_scale_battery_capacity(bat, buf[1]); + return hidinput_scale_battery_capacity(bat, buf[offset]); } static int hidinput_get_battery_property(struct power_supply *psy, @@ -593,6 +604,7 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, bat->max = max; bat->report_type = report_type; bat->report_id = field->report->id; + bat->report_offset = field->report_offset; bat->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; bat->status = HID_BATTERY_UNKNOWN; @@ -2317,7 +2329,19 @@ static inline void hidinput_configure_usages(struct hid_input *hidinput, * Read all reports and initialize the absolute field values. */ -int hidinput_connect(struct hid_device *hid, unsigned int force) +static bool hid_has_ff_input(struct hid_device *hdev) +{ + struct hid_input *hidinput; + + list_for_each_entry(hidinput, &hdev->inputs, list) { + if (test_bit(EV_FF, hidinput->input->evbit)) + return true; + } + + return false; +} + +int hidinput_connect(struct hid_device *hid, unsigned int connect_mask) { struct hid_driver *drv = hid->driver; struct hid_report *report; @@ -2330,7 +2354,7 @@ int hidinput_connect(struct hid_device *hid, unsigned int force) hid->status &= ~HID_STAT_DUP_DETECTED; - if (!force) { + if (!(connect_mask & HID_CONNECT_HIDINPUT_FORCE)) { for (i = 0; i < hid->maxcollection; i++) { struct hid_collection *col = &hid->collection[i]; if (col->type == HID_COLLECTION_APPLICATION || @@ -2396,6 +2420,11 @@ int hidinput_connect(struct hid_device *hid, unsigned int force) continue; } + if (list_is_first(&hidinput->list, &hid->inputs) && + (connect_mask & HID_CONNECT_FF) && hid->ff_init && + !hid_has_ff_input(hid)) + hid->ff_init(hid); + if (input_register_device(hidinput->input)) goto out_unwind; hidinput->registered = true; diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index e901fdb7d033..62f99a93210c 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -336,6 +336,8 @@ int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report, if (entry->wdata.combine) { switch (entry->wdata.product_id) { case USB_DEVICE_ID_LOGITECH_WHEEL: + if (size < 7) + return 0; rd[5] = rd[3]; rd[6] = 0x7F; return 1; @@ -343,10 +345,14 @@ int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report, case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG: case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL: case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2: + if (size < 6) + return 0; rd[4] = rd[3]; rd[5] = 0x7F; return 1; case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + if (size < 7) + return 0; rd[5] = rd[4]; rd[6] = 0x7F; return 1; @@ -366,6 +372,8 @@ int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report, } /* Compute a combined axis when wheel does not supply it */ + if (size <= offset + 1) + return 0; rd[offset] = (0xFF + rd[offset] - rd[offset+1]) >> 1; rd[offset+1] = 0x7F; return 1; diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 9c574ab8b60b..1d619d2345e1 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -121,6 +121,7 @@ enum recvr_type { recvr_type_27mhz, recvr_type_bluetooth, recvr_type_dinovo, + recvr_type_bolt, }; struct dj_report { @@ -1156,6 +1157,10 @@ static void logi_hidpp_recv_queue_notif(struct hid_device *hdev, logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem); workitem.reports_supported |= STD_KEYBOARD; break; + case 0x10: + device_type = "Bolt"; + logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem); + break; } /* custom receiver device (eg. powerplay) */ @@ -1745,6 +1750,24 @@ static int logi_dj_hidpp_event(struct hid_device *hdev, dj_dev = djrcv_dev->paired_dj_devices[device_index]; + /* + * Bolt receivers send explicit unpair notifications as HID++ events; + * queue device removal when we receive one. + */ + if (djrcv_dev->type == recvr_type_bolt && + hidpp_report->report_id == REPORT_ID_HIDPP_SHORT && + hidpp_report->sub_id == REPORT_TYPE_NOTIF_DEVICE_UNPAIRED) { + struct dj_workitem workitem = { + .device_index = device_index, + .type = WORKITEM_TYPE_UNPAIRED, + }; + + kfifo_in(&djrcv_dev->notif_fifo, &workitem, sizeof(workitem)); + schedule_work(&djrcv_dev->work); + spin_unlock_irqrestore(&djrcv_dev->lock, flags); + return false; + } + /* * With 27 MHz receivers, we do not get an explicit unpair event, * remove the old device if the user has paired a *different* device. @@ -1884,6 +1907,9 @@ static int logi_dj_probe(struct hid_device *hdev, * treat these as logitech-dj interfaces then this causes input events * reported through this extra interface to not be reported correctly. * To avoid this, we treat these as generic-hid devices. + * + * Bolt receivers only use LOGITECH_DJ_INTERFACE_NUMBER for receiver + * reporting. Treat all other Bolt interfaces as generic-hid devices. */ switch (id->driver_data) { case recvr_type_dj: no_dj_interfaces = 3; break; @@ -1897,10 +1923,20 @@ static int logi_dj_probe(struct hid_device *hdev, } if (hid_is_usb(hdev)) { intf = to_usb_interface(hdev->dev.parent); - if (intf && intf->altsetting->desc.bInterfaceNumber >= - no_dj_interfaces) { - hdev->quirks |= HID_QUIRK_INPUT_PER_APP; - return hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (intf) { + bool generic_hid_interface; + + if (id->driver_data == recvr_type_bolt) + generic_hid_interface = + intf->altsetting->desc.bInterfaceNumber != + LOGITECH_DJ_INTERFACE_NUMBER; + else + generic_hid_interface = + intf->altsetting->desc.bInterfaceNumber >= no_dj_interfaces; + if (generic_hid_interface) { + hdev->quirks |= HID_QUIRK_INPUT_PER_APP; + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); + } } } @@ -2103,10 +2139,18 @@ static const struct hid_device_id logi_dj_receivers[] = { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3), .driver_data = recvr_type_gaming_hidpp_ls_1_3}, + { /* Logitech Bolt receiver (0xc548) */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER), + .driver_data = recvr_type_bolt}, { /* Logitech lightspeed receiver (0xc54d) */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4), .driver_data = recvr_type_gaming_hidpp_ls_1_3}, + { /* Logitech lightspeed receiver (0xc545) */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_5), + .driver_data = recvr_type_gaming_hidpp_ls_1_3}, { /* Logitech 27 MHz HID++ 1.0 receiver (0xc513) */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER), diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 90b0184df777..1504de32b1c8 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -76,6 +76,7 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(28) #define HIDPP_QUIRK_WIRELESS_STATUS BIT(29) #define HIDPP_QUIRK_RESET_HI_RES_SCROLL BIT(30) +#define HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS BIT(31) /* These are just aliases for now */ #define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS @@ -178,6 +179,8 @@ struct hidpp_scroll_counter { unsigned long long last_time; }; +struct hidpp_reprog_control_mapping; + struct hidpp_device { struct hid_device *hid_dev; struct input_dev *input; @@ -205,6 +208,8 @@ struct hidpp_device { struct hidpp_scroll_counter vertical_wheel_counter; u8 wireless_feature_index; + u8 reprog_controls_feature_index; + const struct hidpp_reprog_control_mapping *reprog_controls; int hires_wheel_multiplier; u8 hires_wheel_feature_index; @@ -983,7 +988,8 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) } /* the device might not be connected */ - if (ret == HIDPP_ERROR_RESOURCE_ERROR || + if (ret == HIDPP_ERROR_CONNECT_FAIL || + ret == HIDPP_ERROR_RESOURCE_ERROR || ret == HIDPP_ERROR_UNKNOWN_DEVICE) return -EIO; @@ -2861,18 +2867,19 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, * ownership to FF core */ data = kmemdup(data, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; + if (!data) { + error = -ENOMEM; + goto err_destroy_ff; + } data->effect_ids = kzalloc_objs(int, num_slots); if (!data->effect_ids) { - kfree(data); - return -ENOMEM; + error = -ENOMEM; + goto err_free_data; } data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue"); if (!data->wq) { - kfree(data->effect_ids); - kfree(data); - return -ENOMEM; + error = -ENOMEM; + goto err_free_effect_ids; } data->hidpp = hidpp; @@ -2902,6 +2909,14 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, version); return 0; + +err_free_effect_ids: + kfree(data->effect_ids); +err_free_data: + kfree(data); +err_destroy_ff: + input_ff_destroy(dev); + return error; } /* ************************************************************************** */ @@ -3601,6 +3616,211 @@ static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp, return 1; } +/* -------------------------------------------------------------------------- */ +/* HID++2.0 reprogrammable controls */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_REPROG_CONTROLS_V4 0x1b04 + +#define HIDPP_REPROG_CONTROLS_GET_COUNT 0x00 +#define HIDPP_REPROG_CONTROLS_GET_CID_INFO 0x10 +#define HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING 0x30 + +#define HIDPP_REPROG_CONTROLS_FLAG_MOUSE BIT(0) +#define HIDPP_REPROG_CONTROLS_FLAG_DIVERT BIT(5) + +#define HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED BIT(0) +#define HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT BIT(1) + +#define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED 0x00 + +#define HIDPP_REPROG_CONTROL_BACK 0x0053 +#define HIDPP_REPROG_CONTROL_FORWARD 0x0056 + +#define HIDPP_PRODUCT_SIGNATURE_M650 0xb02a + +struct hidpp_reprog_control_mapping { + u16 control; + u16 code; +}; + +static const struct hidpp_reprog_control_mapping m650_reprog_control_mappings[] = { + { HIDPP_REPROG_CONTROL_BACK, BTN_BACK }, + { HIDPP_REPROG_CONTROL_FORWARD, BTN_FORWARD }, + { } +}; + +static const struct hidpp_reprog_control_mapping * +hidpp20_reprog_controls_get_mappings(struct hidpp_device *hidpp) +{ + switch (hidpp->hid_dev->product) { + case HIDPP_PRODUCT_SIGNATURE_M650: + return m650_reprog_control_mappings; + } + + return NULL; +} + +static int hidpp20_reprog_controls_get_count(struct hidpp_device *hidpp) +{ + struct hidpp_report response; + u8 feature_index = hidpp->reprog_controls_feature_index; + u8 cmd = HIDPP_REPROG_CONTROLS_GET_COUNT; + int ret; + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, NULL, 0, + &response); + if (ret > 0) + return -EPROTO; + if (ret) + return ret; + + return response.fap.params[0]; +} + +static int hidpp20_reprog_controls_get_cid_info(struct hidpp_device *hidpp, + u8 index, u16 *control, + u8 *flags) +{ + struct hidpp_report response; + u8 feature_index = hidpp->reprog_controls_feature_index; + u8 cmd = HIDPP_REPROG_CONTROLS_GET_CID_INFO; + int ret; + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, &index, + sizeof(index), &response); + if (ret > 0) + return -EPROTO; + if (ret) + return ret; + + *control = get_unaligned_be16(&response.fap.params[0]); + *flags = response.fap.params[4]; + + return 0; +} + +static bool hidpp20_reprog_controls_find_control(struct hidpp_device *hidpp, + u16 control) +{ + int count, ret; + u16 cid; + u8 flags; + int i; + + count = hidpp20_reprog_controls_get_count(hidpp); + if (count < 0) + return false; + + for (i = 0; i < count; i++) { + ret = hidpp20_reprog_controls_get_cid_info(hidpp, i, &cid, + &flags); + if (ret) + return false; + + if (cid == control) + return (flags & HIDPP_REPROG_CONTROLS_FLAG_MOUSE) && + (flags & HIDPP_REPROG_CONTROLS_FLAG_DIVERT); + } + + return false; +} + +static int hidpp20_reprog_controls_set_control_reporting(struct hidpp_device *hidpp, + u16 control, u8 flags) +{ + struct hidpp_report response; + u8 params[5]; + + put_unaligned_be16(control, ¶ms[0]); + params[2] = flags; + put_unaligned_be16(control, ¶ms[3]); + + return hidpp_send_fap_command_sync(hidpp, + hidpp->reprog_controls_feature_index, + HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING, + params, sizeof(params), &response); +} + +static void hidpp20_reprog_controls_connect(struct hidpp_device *hidpp) +{ + const struct hidpp_reprog_control_mapping *mapping; + u8 flags = HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED | + HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT; + + if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS)) + return; + + if (!hidpp->reprog_controls) + return; + + if (hidpp_root_get_feature(hidpp, HIDPP_PAGE_REPROG_CONTROLS_V4, + &hidpp->reprog_controls_feature_index)) + return; + + for (mapping = hidpp->reprog_controls; mapping->control; mapping++) { + if (!hidpp20_reprog_controls_find_control(hidpp, mapping->control)) + continue; + + hidpp20_reprog_controls_set_control_reporting(hidpp, + mapping->control, + flags); + } +} + +static int hidpp20_reprog_controls_raw_event(struct hidpp_device *hidpp, + u8 *data, int size) +{ + const struct hidpp_reprog_control_mapping *mapping; + struct hidpp_report *report = (struct hidpp_report *)data; + u16 controls[4]; + bool pressed; + unsigned int i, j; + + if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS) || + !hidpp->input || + !hidpp->reprog_controls || + hidpp->reprog_controls_feature_index == 0xff) + return 0; + + if (size < HIDPP_REPORT_LONG_LENGTH || + report->fap.feature_index != hidpp->reprog_controls_feature_index || + report->fap.funcindex_clientid != HIDPP_REPROG_CONTROLS_EVENT_DIVERTED) + return 0; + + for (i = 0; i < ARRAY_SIZE(controls); i++) + controls[i] = get_unaligned_be16(&report->fap.params[i * 2]); + + for (mapping = hidpp->reprog_controls; mapping->control; mapping++) { + pressed = false; + + for (j = 0; j < ARRAY_SIZE(controls); j++) { + if (controls[j] == mapping->control) { + pressed = true; + break; + } + } + + input_report_key(hidpp->input, mapping->code, pressed); + } + + input_sync(hidpp->input); + + return 1; +} + +static void hidpp20_reprog_controls_populate_input(struct hidpp_device *hidpp, + struct input_dev *input_dev) +{ + const struct hidpp_reprog_control_mapping *mapping; + + if (!hidpp->reprog_controls) + return; + + for (mapping = hidpp->reprog_controls; mapping->control; mapping++) + input_set_capability(input_dev, EV_KEY, mapping->code); +} + static void hidpp10_extra_mouse_buttons_populate_input( struct hidpp_device *hidpp, struct input_dev *input_dev) { @@ -3859,17 +4079,37 @@ static void hidpp_populate_input(struct hidpp_device *hidpp, if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS) hidpp10_extra_mouse_buttons_populate_input(hidpp, input); + + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS) + hidpp20_reprog_controls_populate_input(hidpp, input); } -static int hidpp_input_configured(struct hid_device *hdev, - struct hid_input *hidinput) +static int hidpp_input_configured(struct hid_device *hdev, struct hid_input *hidinput) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); struct input_dev *input = hidinput->input; + int ret; if (!hidpp) return 0; + if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { + struct hidpp_ff_private_data data; + + if (!list_is_first(&hidinput->list, &hdev->inputs)) + return 0; + + ret = g920_get_config(hidpp, &data); + if (!ret) + ret = hidpp_ff_init(hidpp, &data); + + if (ret) { + hid_warn(hidpp->hid_dev, + "Unable to initialize force feedback support, errno %d\n", + ret); + } + } + hidpp_populate_input(hidpp, input); return 0; @@ -3971,6 +4211,10 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, return ret; } + ret = hidpp20_reprog_controls_raw_event(hidpp, data, size); + if (ret != 0) + return ret; + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) { ret = hidpp10_consumer_keys_raw_event(hidpp, data, size); if (ret != 0) @@ -4161,8 +4405,50 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) return ret; } +static bool hidpp_is_bolt_child(struct hid_device *hdev) +{ + struct device *parent = hdev->dev.parent; + struct hid_device *receiver_hdev; + + if (!parent) + return false; + + receiver_hdev = to_hid_device(parent); + return receiver_hdev->vendor == USB_VENDOR_ID_LOGITECH && + receiver_hdev->product == USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER; +} + +static int hidpp_bolt_init(struct hidpp_device *hidpp) +{ + struct hid_device *hdev = hidpp->hid_dev; + char *name; + int ret; + + ret = hidpp_serial_init(hidpp); + if (ret) + return ret; + + name = hidpp_get_device_name(hidpp); + if (!name) + return -EIO; + + snprintf(hdev->name, sizeof(hdev->name), "%s", name); + dbg_hid("HID++ Bolt: Got name: %s\n", name); + + kfree(name); + return 0; +} + +static int hidpp_receiver_init(struct hidpp_device *hidpp) +{ + if (hidpp_is_bolt_child(hidpp->hid_dev)) + return hidpp_bolt_init(hidpp); + + return hidpp_unifying_init(hidpp); +} + /* Get name + serial for USB and Bluetooth HID++ devices */ -static void hidpp_non_unifying_init(struct hidpp_device *hidpp) +static void hidpp_non_receiver_init(struct hidpp_device *hidpp) { struct hid_device *hdev = hidpp->hid_dev; char *name; @@ -4264,6 +4550,8 @@ static void hidpp_connect_event(struct work_struct *work) return; } + hidpp20_reprog_controls_connect(hidpp); + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) { ret = hidpp10_consumer_keys_connect(hidpp); if (ret) @@ -4437,6 +4725,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) hidpp->hid_dev = hdev; hidpp->name = hdev->name; hidpp->quirks = id->driver_data; + hidpp->reprog_controls_feature_index = 0xff; + hidpp->reprog_controls = hidpp20_reprog_controls_get_mappings(hidpp); hid_set_drvdata(hdev, hidpp); ret = hid_parse(hdev); @@ -4510,9 +4800,9 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) /* Get name + serial, store in hdev->name + hdev->uniq */ if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE) - hidpp_unifying_init(hidpp); + hidpp_receiver_init(hidpp); else - hidpp_non_unifying_init(hidpp); + hidpp_non_receiver_init(hidpp); if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) connect_mask &= ~HID_CONNECT_HIDINPUT; @@ -4530,21 +4820,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) schedule_work(&hidpp->work); flush_work(&hidpp->work); - if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { - struct hidpp_ff_private_data data; - - ret = g920_get_config(hidpp, &data); - if (!ret) - ret = hidpp_ff_init(hidpp, &data); - - if (ret) { - hid_warn(hidpp->hid_dev, - "Unable to initialize force feedback support, errno %d\n", - ret); - ret = 0; - } - } - /* * This relies on logi_dj_ll_close() being a no-op so that DJ connection * events will still be received. @@ -4647,6 +4922,8 @@ static const struct hid_device_id hidpp_devices[] = { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC082) }, { /* Logitech G502 Lightspeed Wireless Gaming Mouse over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08D) }, + { /* Logitech G502 X Plus Wireless Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC095) }, { /* Logitech G703 Gaming Mouse over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC087) }, { /* Logitech G703 Hero Gaming Mouse over USB */ @@ -4659,8 +4936,6 @@ static const struct hid_device_id hidpp_devices[] = { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) }, { /* MX Vertical over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08A) }, - { /* Logitech G703 Hero Gaming Mouse over USB */ - HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC090) }, { /* Logitech G903 Hero Gaming Mouse over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC091) }, { /* Logitech G915 TKL Keyboard over USB */ @@ -4707,7 +4982,9 @@ static const struct hid_device_id hidpp_devices[] = { { /* MX Vertical mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) }, { /* Signature M650 over Bluetooth */ - HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) }, + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, + HIDPP_PRODUCT_SIGNATURE_M650), + .driver_data = HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS }, { /* MX Master 3 mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) }, { /* MX Anywhere 3 mouse over Bluetooth */ diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 802a3479e24b..d637c0477379 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -383,8 +383,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda } } -static int magicmouse_raw_event(struct hid_device *hdev, - struct hid_report *report, u8 *data, int size) +static int __magicmouse_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size, bool nested) { struct magicmouse_sc *msc = hid_get_drvdata(hdev); struct input_dev *input = msc->input; @@ -495,6 +495,15 @@ static int magicmouse_raw_event(struct hid_device *hdev, * packet. */ + /* + * A double report only ever wraps two normal reports, so it is + * never nested. Refuse to recurse a second time; otherwise a + * malicious device could chain DOUBLE_REPORT_ID packets to drive + * unbounded recursion and overflow the kernel stack. + */ + if (nested) + return 0; + /* Ensure that we have at least 2 elements (report type and size) */ if (size < 2) return 0; @@ -506,9 +515,9 @@ static int magicmouse_raw_event(struct hid_device *hdev, return 0; } - magicmouse_raw_event(hdev, report, data + 2, data[1]); - magicmouse_raw_event(hdev, report, data + 2 + data[1], - size - 2 - data[1]); + __magicmouse_raw_event(hdev, report, data + 2, data[1], true); + __magicmouse_raw_event(hdev, report, data + 2 + data[1], + size - 2 - data[1], true); return 0; default: return 0; @@ -534,6 +543,12 @@ static int magicmouse_raw_event(struct hid_device *hdev, return 1; } +static int magicmouse_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + return __magicmouse_raw_event(hdev, report, data, size, false); +} + static int magicmouse_event(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value) { @@ -828,6 +843,12 @@ static bool is_usb_magictrackpad2(__u32 vendor, __u32 product) product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC; } +static bool is_bt_magictrackpad2(__u32 vendor, __u32 product) +{ + return vendor == BT_VENDOR_ID_APPLE && + product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC; +} + static int magicmouse_fetch_battery(struct hid_device *hdev) { #ifdef CONFIG_HID_BATTERY_STRENGTH @@ -838,7 +859,8 @@ static int magicmouse_fetch_battery(struct hid_device *hdev) bat = hid_get_battery(hdev); if (!bat || (!is_usb_magicmouse2(hdev->vendor, hdev->product) && - !is_usb_magictrackpad2(hdev->vendor, hdev->product))) + !is_usb_magictrackpad2(hdev->vendor, hdev->product) && + !is_bt_magictrackpad2(hdev->vendor, hdev->product))) return -1; report_enum = &hdev->report_enum[bat->report_type]; @@ -900,6 +922,16 @@ static int magicmouse_probe(struct hid_device *hdev, return ret; } + /* + * When hidinput_connect() fails it frees every input device it + * created, but that does not fail hid_hw_start(): the core simply + * does not claim an input. msc->input, cached in ->input_mapping + * while the report descriptor was parsed, would then be a dangling + * pointer that passes every NULL check. Trust the core's claim. + */ + if (!(hdev->claimed & HID_CLAIMED_INPUT)) + msc->input = NULL; + if (is_usb_magicmouse2(id->vendor, id->product) || is_usb_magictrackpad2(id->vendor, id->product)) { timer_setup(&msc->battery_timer, magicmouse_battery_timer_tick, 0); @@ -971,6 +1003,16 @@ static int magicmouse_probe(struct hid_device *hdev, schedule_delayed_work(&msc->work, msecs_to_jiffies(500)); } + /* + * Query the Bluetooth Magic Trackpad USB-C battery as done for USB. + * Start io first: probe holds driver_input_lock and the synchronous + * GET_REPORT reply would otherwise be dropped. + */ + if (is_bt_magictrackpad2(id->vendor, id->product)) { + hid_device_io_start(hdev); + magicmouse_fetch_battery(hdev); + } + return 0; err_stop_hw: if (is_usb_magicmouse2(id->vendor, id->product) || @@ -995,6 +1037,22 @@ static void magicmouse_remove(struct hid_device *hdev) hid_hw_stop(hdev); } +#ifdef CONFIG_PM +static int magicmouse_reset_resume(struct hid_device *hdev) +{ + struct magicmouse_sc *msc = hid_get_drvdata(hdev); + + /* The device drops out of multitouch mode on resume; re-send the + * enable report. Only the HID_TYPE_USBMOUSE interface accepts it, and + * it must be deferred. Sending it inline here is too early. + */ + if (msc && hdev->type == HID_TYPE_USBMOUSE) + schedule_delayed_work(&msc->work, msecs_to_jiffies(500)); + + return 0; +} +#endif + static const __u8 *magicmouse_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { @@ -1058,6 +1116,9 @@ static struct hid_driver magicmouse_driver = { .event = magicmouse_event, .input_mapping = magicmouse_input_mapping, .input_configured = magicmouse_input_configured, +#ifdef CONFIG_PM + .reset_resume = magicmouse_reset_resume, +#endif }; module_hid_driver(magicmouse_driver); diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c index e4ddd8e9293b..d52ce3531ab7 100644 --- a/drivers/hid/hid-mcp2221.c +++ b/drivers/hid/hid-mcp2221.c @@ -343,7 +343,7 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp, ret = mcp_send_data_req_status(mcp, mcp->txbuf, 4); if (ret) - return ret; + goto out; mcp->rxbuf_idx = 0; @@ -365,7 +365,7 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp, } else { usleep_range(980, 1000); mcp_cancel_last_cmd(mcp); - return ret; + goto out; } } else { retries = 0; @@ -375,6 +375,10 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp, usleep_range(980, 1000); ret = mcp_chk_last_cmd_status_free_bus(mcp); +out: + mcp->rxbuf = NULL; + mcp->rxbuf_size = 0; + return ret; } @@ -861,6 +865,9 @@ static int mcp2221_raw_event(struct hid_device *hdev, u8 *buf; struct mcp2221 *mcp = hid_get_drvdata(hdev); + if (size < 4) + return 0; + switch (data[0]) { case MCP2221_I2C_WR_DATA: @@ -926,6 +933,10 @@ static int mcp2221_raw_event(struct hid_device *hdev, mcp->status = -EINVAL; break; } + if (4 + data[3] > size) { + mcp->status = -EINVAL; + break; + } buf = mcp->rxbuf; memcpy(&buf[mcp->rxbuf_idx], &data[4], data[3]); mcp->rxbuf_idx = mcp->rxbuf_idx + data[3]; @@ -1049,6 +1060,8 @@ static void mcp2221_hid_unregister(void *ptr) { struct hid_device *hdev = ptr; + if (hdev->io_started) + hid_device_io_stop(hdev); hid_hw_close(hdev); hid_hw_stop(hdev); } diff --git a/drivers/hid/hid-megaworld.c b/drivers/hid/hid-megaworld.c index 81acdbc3a00f..d5c868974275 100644 --- a/drivers/hid/hid-megaworld.c +++ b/drivers/hid/hid-megaworld.c @@ -35,21 +35,16 @@ static int mwctrl_play(struct input_dev *dev, void *data, return 0; } -static int mwctrl_init(struct hid_device *hid) +static int mwctrl_input_configured(struct hid_device *hid, struct hid_input *hidinput) { struct mwctrl_device *mwctrl; struct hid_report *report; - struct hid_input *hidinput; - struct input_dev *dev; + struct input_dev *dev = hidinput->input; int error; int i; - if (list_empty(&hid->inputs)) { - hid_err(hid, "no inputs found\n"); - return -ENODEV; - } - hidinput = list_entry(hid->inputs.next, struct hid_input, list); - dev = hidinput->input; + if (!list_is_first(&hidinput->list, &hid->inputs)) + return 0; for (i = 0; i < 4; i++) { report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, i, 1); @@ -61,16 +56,7 @@ static int mwctrl_init(struct hid_device *hid) if (!mwctrl) return -ENOMEM; - set_bit(FF_RUMBLE, dev->ffbit); - - error = input_ff_create_memless(dev, mwctrl, mwctrl_play); - if (error) { - kfree(mwctrl); - return error; - } - mwctrl->report = report; - /* Field 0 is always 2, and field 1 is always 0. The original * windows driver has a 5 bytes command, where the 5th byte is * a repeat of the 3rd byte, however the device has only 4 @@ -82,32 +68,17 @@ static int mwctrl_init(struct hid_device *hid) mwctrl->strong = &report->field[2]->value[0]; mwctrl->weak = &report->field[3]->value[0]; + set_bit(FF_RUMBLE, dev->ffbit); + + error = input_ff_create_memless(dev, mwctrl, mwctrl_play); + if (error) { + kfree(mwctrl); + return error; + } + return 0; } -static int mwctrl_probe(struct hid_device *hdev, const struct hid_device_id *id) -{ - int ret; - - ret = hid_parse(hdev); - if (ret) { - hid_err(hdev, "parse failed\n"); - return ret; - } - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); - if (ret) { - hid_err(hdev, "hw start failed\n"); - return ret; - } - - ret = mwctrl_init(hdev); - if (ret) - hid_hw_stop(hdev); - - return ret; -} - static const struct hid_device_id mwctrl_devices[] = { { HID_USB_DEVICE(USB_VENDOR_MEGAWORLD, USB_DEVICE_ID_MEGAWORLD_GAMEPAD) }, @@ -118,7 +89,7 @@ MODULE_DEVICE_TABLE(hid, mwctrl_devices); static struct hid_driver mwctrl_driver = { .name = "megaworld", .id_table = mwctrl_devices, - .probe = mwctrl_probe, + .input_configured = mwctrl_input_configured, }; module_hid_driver(mwctrl_driver); diff --git a/drivers/hid/hid-mf.c b/drivers/hid/hid-mf.c index 6ff54a1ec697..136e8b41d5f4 100644 --- a/drivers/hid/hid-mf.c +++ b/drivers/hid/hid-mf.c @@ -54,61 +54,45 @@ static int mf_play(struct input_dev *dev, void *data, struct ff_effect *effect) return 0; } -static int mf_init(struct hid_device *hid) +static int mf_input_configured(struct hid_device *hid, struct hid_input *hidinput) { struct mf_device *mf; - struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; - - struct list_head *report_ptr; struct hid_report *report; - - struct list_head *input_ptr = &hid->inputs; - struct hid_input *input; - - struct input_dev *dev; - + struct input_dev *dev = hidinput->input; int error; - /* Setup each of the four inputs */ - list_for_each(report_ptr, report_list) { - report = list_entry(report_ptr, struct hid_report, list); + if (!list_is_first(&hidinput->list, &hid->inputs)) + return 0; - if (report->maxfield < 1 || report->field[0]->report_count < 2) { - hid_err(hid, "Invalid report, this should never happen!\n"); - return -ENODEV; - } - - if (list_is_last(input_ptr, &hid->inputs)) { - hid_err(hid, "Missing input, this should never happen!\n"); - return -ENODEV; - } - - input_ptr = input_ptr->next; - input = list_entry(input_ptr, struct hid_input, list); - - mf = kzalloc_obj(struct mf_device); - if (!mf) - return -ENOMEM; - - dev = input->input; - set_bit(FF_RUMBLE, dev->ffbit); - - error = input_ff_create_memless(dev, mf, mf_play); - if (error) { - kfree(mf); - return error; - } - - mf->report = report; - mf->report->field[0]->value[0] = 0x00; - mf->report->field[0]->value[1] = 0x00; - hid_hw_request(hid, mf->report, HID_REQ_SET_REPORT); + report = list_first_entry_or_null(report_list, struct hid_report, list); + if (!report) { + hid_err(hid, "no output reports found\n"); + return -ENODEV; } - hid_info(hid, "Force feedback for HJZ Mayflash game controller " - "adapters by Marcel Hasler \n"); + if (report->maxfield < 1 || report->field[0]->report_count < 2) { + hid_err(hid, "Invalid report, this should never happen!\n"); + return -ENODEV; + } + + mf = kzalloc_obj(struct mf_device); + if (!mf) + return -ENOMEM; + + mf->report = report; + set_bit(FF_RUMBLE, dev->ffbit); + + error = input_ff_create_memless(dev, mf, mf_play); + if (error) { + kfree(mf); + return error; + } + + mf->report->field[0]->value[0] = 0x00; + mf->report->field[0]->value[1] = 0x00; + hid_hw_request(hid, mf->report, HID_REQ_SET_REPORT); return 0; } @@ -128,22 +112,14 @@ static int mf_probe(struct hid_device *hid, const struct hid_device_id *id) return error; } - error = hid_hw_start(hid, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); + error = hid_hw_start(hid, HID_CONNECT_DEFAULT); if (error) { hid_err(hid, "HID hw start failed\n"); return error; } - error = mf_init(hid); - if (error) { - hid_err(hid, "Force feedback init failed.\n"); - hid_hw_stop(hid); - return error; - } - return 0; } - static const struct hid_device_id mf_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_PS3), .driver_data = HID_QUIRK_MULTI_INPUT }, @@ -163,6 +139,7 @@ static struct hid_driver mf_driver = { .name = "hid_mf", .id_table = mf_devices, .probe = mf_probe, + .input_configured = mf_input_configured, }; module_hid_driver(mf_driver); diff --git a/drivers/hid/hid-microsoft.c b/drivers/hid/hid-microsoft.c index 18ac21c0bcb2..a7d3493a6141 100644 --- a/drivers/hid/hid-microsoft.c +++ b/drivers/hid/hid-microsoft.c @@ -323,22 +323,17 @@ static int ms_play_effect(struct input_dev *dev, void *data, return 0; } -static int ms_init_ff(struct hid_device *hdev) +static int ms_input_configured(struct hid_device *hdev, struct hid_input *hidinput) { - struct hid_input *hidinput; - struct input_dev *input_dev; struct ms_data *ms = hid_get_drvdata(hdev); - - if (list_empty(&hdev->inputs)) { - hid_err(hdev, "no inputs found\n"); - return -ENODEV; - } - hidinput = list_entry(hdev->inputs.next, struct hid_input, list); - input_dev = hidinput->input; + struct input_dev *input_dev = hidinput->input; if (!(ms->quirks & MS_QUIRK_FF)) return 0; + if (!list_is_first(&hidinput->list, &hdev->inputs)) + return 0; + ms->hdev = hdev; INIT_WORK(&ms->ff_worker, ms_ff_worker); @@ -352,16 +347,6 @@ static int ms_init_ff(struct hid_device *hdev) return input_ff_create_memless(input_dev, NULL, ms_play_effect); } -static void ms_remove_ff(struct hid_device *hdev) -{ - struct ms_data *ms = hid_get_drvdata(hdev); - - if (!(ms->quirks & MS_QUIRK_FF)) - return; - - cancel_work_sync(&ms->ff_worker); -} - static int ms_probe(struct hid_device *hdev, const struct hid_device_id *id) { unsigned long quirks = id->driver_data; @@ -385,29 +370,21 @@ static int ms_probe(struct hid_device *hdev, const struct hid_device_id *id) ret = hid_parse(hdev); if (ret) { hid_err(hdev, "parse failed\n"); - goto err_free; + return ret; } ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT | ((quirks & MS_HIDINPUT) ? HID_CONNECT_HIDINPUT_FORCE : 0)); if (ret) { hid_err(hdev, "hw start failed\n"); - goto err_free; + return ret; } - ret = ms_init_ff(hdev); - if (ret) - hid_err(hdev, "could not initialize ff, continuing anyway"); - return 0; -err_free: - return ret; } - static void ms_remove(struct hid_device *hdev) { hid_hw_stop(hdev); - ms_remove_ff(hdev); } static const struct hid_device_id ms_devices[] = { @@ -469,6 +446,7 @@ static struct hid_driver ms_driver = { .report_fixup = ms_report_fixup, .input_mapping = ms_input_mapping, .input_mapped = ms_input_mapped, + .input_configured = ms_input_configured, .event = ms_event, .probe = ms_probe, .remove = ms_remove, diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c new file mode 100644 index 000000000000..3225d3bf6125 --- /dev/null +++ b/drivers/hid/hid-msi.c @@ -0,0 +1,2063 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for MSI Claw Handheld PC gamepads. + * + * Provides configuration support for the MSI Claw series of handheld PC + * gamepads. Multiple iterations of the device firmware has led to some + * quirks for how certain attributes are handled. The original firmware + * did not support remapping of the M1 (right) and M2 (left) rear paddles. + * Additionally, the MCU RAM address for writing configuration data has + * changed twice. Checks are done during probe to enumerate these variances. + * + * Copyright (c) 2026 Zhouwang Huang + * Copyright (c) 2026 Denis Benato + * Copyright (c) 2026 Valve Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" + +#define CLAW_OUTPUT_REPORT_ID 0x0f +#define CLAW_INPUT_REPORT_ID 0x10 + +#define CLAW_PACKET_SIZE 64 + +#define CLAW_DINPUT_CFG_INTF_IN 0x82 +#define CLAW_XINPUT_CFG_INTF_IN 0x83 + +#define CLAW_KEYS_MAX 5 + +#define CLAW_RGB_ZONES 9 +#define CLAW_RGB_MAX_FRAMES 8 +#define CLAW_RGB_FRAME_OFFSET 0x24 + +enum claw_command_index { + CLAW_COMMAND_TYPE_NONE = 0x00, + CLAW_COMMAND_TYPE_READ_PROFILE = 0x04, + CLAW_COMMAND_TYPE_READ_PROFILE_ACK = 0x05, + CLAW_COMMAND_TYPE_ACK = 0x06, + CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA = 0x21, + CLAW_COMMAND_TYPE_SYNC_TO_ROM = 0x22, + CLAW_COMMAND_TYPE_SWITCH_MODE = 0x24, + CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE = 0x26, + CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK = 0x27, + CLAW_COMMAND_TYPE_RESET_DEVICE = 0x28, +}; + +enum claw_gamepad_mode_index { + CLAW_GAMEPAD_MODE_XINPUT = 0x01, + CLAW_GAMEPAD_MODE_DINPUT = 0x02, + CLAW_GAMEPAD_MODE_DESKTOP = 0x04, +}; + +static const char * const claw_gamepad_mode_text[] = { + [CLAW_GAMEPAD_MODE_XINPUT] = "xinput", + [CLAW_GAMEPAD_MODE_DINPUT] = "dinput", + [CLAW_GAMEPAD_MODE_DESKTOP] = "desktop", +}; + +enum claw_profile_ack_pending { + CLAW_NO_PENDING, + CLAW_M1_PENDING, + CLAW_M2_PENDING, + CLAW_RGB_PENDING, + CLAW_RUMBLE_LEFT_PENDING, + CLAW_RUMBLE_RIGHT_PENDING, +}; + +enum claw_key_index { + CLAW_KEY_M1, + CLAW_KEY_M2, +}; + +enum claw_mkeys_function_index { + CLAW_MKEY_FUNCTION_MACRO, + CLAW_MKEY_FUNCTION_DISABLED, + CLAW_MKEY_FUNCTION_COMBO, +}; + +enum claw_mode_field { + CLAW_FIELD_GAMEPAD_MODE, + CLAW_FIELD_MKEYS_FUNCTION, +}; + +static const char * const claw_mkeys_function_text[] = { + [CLAW_MKEY_FUNCTION_MACRO] = "macro", + [CLAW_MKEY_FUNCTION_DISABLED] = "disabled", + [CLAW_MKEY_FUNCTION_COMBO] = "combination", +}; + +static const struct { + u8 code; + const char *name; +} claw_button_mapping_key_map[] = { + /* Gamepad buttons */ + { 0x01, "ABS_HAT0Y_UP" }, + { 0x02, "ABS_HAT0Y_DOWN" }, + { 0x03, "ABS_HAT0X_LEFT" }, + { 0x04, "ABS_HAT0X_RIGHT" }, + { 0x05, "BTN_TL" }, + { 0x06, "BTN_TR" }, + { 0x07, "BTN_THUMBL" }, + { 0x08, "BTN_THUMBR" }, + { 0x09, "BTN_SOUTH" }, + { 0x0a, "BTN_EAST" }, + { 0x0b, "BTN_NORTH" }, + { 0x0c, "BTN_WEST" }, + { 0x0d, "BTN_MODE" }, + { 0x0e, "BTN_SELECT" }, + { 0x0f, "BTN_START" }, + { 0x13, "BTN_TL2"}, + { 0x14, "BTN_TR2"}, + { 0x15, "ABS_Y_UP"}, + { 0x16, "ABS_Y_DOWN"}, + { 0x17, "ABS_X_LEFT"}, + { 0x18, "ABS_X_RIGHT"}, + { 0x19, "ABS_RY_UP"}, + { 0x1a, "ABS_RY_DOWN"}, + { 0x1b, "ABS_RX_LEFT"}, + { 0x1c, "ABS_RX_RIGHT"}, + /* Keyboard keys */ + { 0x32, "KEY_ESC" }, + { 0x33, "KEY_F1" }, + { 0x34, "KEY_F2" }, + { 0x35, "KEY_F3" }, + { 0x36, "KEY_F4" }, + { 0x37, "KEY_F5" }, + { 0x38, "KEY_F6" }, + { 0x39, "KEY_F7" }, + { 0x3a, "KEY_F8" }, + { 0x3b, "KEY_F9" }, + { 0x3c, "KEY_F10" }, + { 0x3d, "KEY_F11" }, + { 0x3e, "KEY_F12" }, + { 0x3f, "KEY_GRAVE" }, + { 0x40, "KEY_1" }, + { 0x41, "KEY_2" }, + { 0x42, "KEY_3" }, + { 0x43, "KEY_4" }, + { 0x44, "KEY_5" }, + { 0x45, "KEY_6" }, + { 0x46, "KEY_7" }, + { 0x47, "KEY_8" }, + { 0x48, "KEY_9" }, + { 0x49, "KEY_0" }, + { 0x4a, "KEY_MINUS" }, + { 0x4b, "KEY_EQUAL" }, + { 0x4c, "KEY_BACKSPACE" }, + { 0x4d, "KEY_TAB" }, + { 0x4e, "KEY_Q" }, + { 0x4f, "KEY_W" }, + { 0x50, "KEY_E" }, + { 0x51, "KEY_R" }, + { 0x52, "KEY_T" }, + { 0x53, "KEY_Y" }, + { 0x54, "KEY_U" }, + { 0x55, "KEY_I" }, + { 0x56, "KEY_O" }, + { 0x57, "KEY_P" }, + { 0x58, "KEY_LEFTBRACE" }, + { 0x59, "KEY_RIGHTBRACE" }, + { 0x5a, "KEY_BACKSLASH" }, + { 0x5b, "KEY_CAPSLOCK" }, + { 0x5c, "KEY_A" }, + { 0x5d, "KEY_S" }, + { 0x5e, "KEY_D" }, + { 0x5f, "KEY_F" }, + { 0x60, "KEY_G" }, + { 0x61, "KEY_H" }, + { 0x62, "KEY_J" }, + { 0x63, "KEY_K" }, + { 0x64, "KEY_L" }, + { 0x65, "KEY_SEMICOLON" }, + { 0x66, "KEY_APOSTROPHE" }, + { 0x67, "KEY_ENTER" }, + { 0x68, "KEY_LEFTSHIFT" }, + { 0x69, "KEY_Z" }, + { 0x6a, "KEY_X" }, + { 0x6b, "KEY_C" }, + { 0x6c, "KEY_V" }, + { 0x6d, "KEY_B" }, + { 0x6e, "KEY_N" }, + { 0x6f, "KEY_M" }, + { 0x70, "KEY_COMMA" }, + { 0x71, "KEY_DOT" }, + { 0x72, "KEY_SLASH" }, + { 0x73, "KEY_RIGHTSHIFT" }, + { 0x74, "KEY_LEFTCTRL" }, + { 0x75, "KEY_LEFTMETA" }, + { 0x76, "KEY_LEFTALT" }, + { 0x77, "KEY_SPACE" }, + { 0x78, "KEY_RIGHTALT" }, + { 0x79, "KEY_RIGHTCTRL" }, + { 0x7a, "KEY_INSERT" }, + { 0x7b, "KEY_HOME" }, + { 0x7c, "KEY_PAGEUP" }, + { 0x7d, "KEY_DELETE" }, + { 0x7e, "KEY_END" }, + { 0x7f, "KEY_PAGEDOWN" }, + { 0x8a, "KEY_KPENTER" }, + { 0x8b, "KEY_KP0" }, + { 0x8c, "KEY_KP1" }, + { 0x8d, "KEY_KP2" }, + { 0x8e, "KEY_KP3" }, + { 0x8f, "KEY_KP4" }, + { 0x90, "KEY_KP5" }, + { 0x91, "KEY_KP6" }, + { 0x92, "KEY_KP7" }, + { 0x93, "KEY_KP8" }, + { 0x94, "KEY_KP9" }, + { 0x95, "MD_PLAY" }, + { 0x96, "MD_STOP" }, + { 0x97, "MD_NEXT" }, + { 0x98, "MD_PREV" }, + { 0x99, "MD_VOL_UP" }, + { 0x9a, "MD_VOL_DOWN" }, + { 0x9b, "MD_VOL_MUTE" }, + { 0x9c, "KEY_F23" }, + /* Mouse events */ + { 0xc8, "BTN_LEFT" }, + { 0xc9, "BTN_MIDDLE" }, + { 0xca, "BTN_RIGHT" }, + { 0xcb, "BTN_SIDE" }, + { 0xcc, "BTN_EXTRA" }, + { 0xcd, "REL_WHEEL_UP" }, + { 0xce, "REL_WHEEL_DOWN" }, + { 0xff, "DISABLED" }, +}; + +enum claw_rgb_effect_index { + CLAW_RGB_EFFECT_MONOCOLOR, + CLAW_RGB_EFFECT_BREATHE, + CLAW_RGB_EFFECT_CHROMA, + CLAW_RGB_EFFECT_RAINBOW, + CLAW_RGB_EFFECT_FROSTFIRE, +}; + +static const char * const claw_rgb_effect_text[] = { + [CLAW_RGB_EFFECT_MONOCOLOR] = "monocolor", + [CLAW_RGB_EFFECT_BREATHE] = "breathe", + [CLAW_RGB_EFFECT_CHROMA] = "chroma", + [CLAW_RGB_EFFECT_RAINBOW] = "rainbow", + [CLAW_RGB_EFFECT_FROSTFIRE] = "frostfire", +}; + +static const u16 button_mapping_addr_old[] = { + 0x007a, /* M1 */ + 0x011f, /* M2 */ +}; + +static const u16 button_mapping_addr_new[] = { + 0x00bb, /* M1 */ + 0x0164, /* M2 */ +}; + +static const u16 rgb_addr_old = 0x01fa; +static const u16 rgb_addr_new = 0x024a; + +static const u16 rumble_addr[] = { + 0x0022, /* left */ + 0x0023, /* right */ +}; + +struct claw_command_report { + u8 report_id; + u8 padding[2]; + u8 header_tail; + u8 cmd; + u8 data[59]; +} __packed; + +struct claw_profile_report { + u8 profile; + __be16 read_addr; +} __packed; + +struct claw_mkey_report { + struct claw_profile_report; + u8 padding_0; + u8 padding_1; + u8 padding_2; + u8 codes[5]; +} __packed; + +struct rgb_zone { + u8 red; + u8 green; + u8 blue; +}; + +struct rgb_frame { + struct rgb_zone zone[CLAW_RGB_ZONES]; +}; + +struct claw_rgb_report { + struct claw_profile_report; + u8 frame_bytes; + u8 padding; + u8 frame_count; + u8 state; /* Always 0x09 */ + u8 speed; + u8 brightness; + struct rgb_frame zone_data; +} __packed; + +struct claw_rumble_report { + struct claw_profile_report; + u8 padding; + u8 intensity; +} __packed; + +struct claw_drvdata { + /* MCU General Variables */ + enum claw_profile_ack_pending profile_pending; + struct completion orphan_ack_complete; + struct completion send_cmd_complete; + struct delayed_work cfg_resume; + struct delayed_work cfg_setup; + spinlock_t registration_lock; /* Lock for registration read/write */ + struct mutex profile_mutex; /* mutex for profile_pending calls */ + spinlock_t profile_lock; /* Lock for profile_pending read/write */ + struct hid_device *hdev; + bool orphan_ack_pending; + struct mutex cfg_mutex; /* mutex for synchronous data */ + struct mutex rom_mutex; /* mutex for SYNC_TO_ROM calls */ + spinlock_t cmd_lock; /* Lock for cmd data read/write */ + u8 waiting_cmd; + int cmd_status; + u16 bcd_device; + u8 ep; + + /* Gamepad Variables */ + enum claw_mkeys_function_index mkeys_function; + enum claw_gamepad_mode_index gamepad_mode; + u8 m1_codes[CLAW_KEYS_MAX]; + u8 m2_codes[CLAW_KEYS_MAX]; + u8 rumble_intensity_right; + u8 rumble_intensity_left; + const u16 *bmap_addr; + spinlock_t rumble_lock; /* lock for rumble_intensity read/write */ + spinlock_t mode_lock; /* Lock for mode data read/write */ + bool rumble_support; + bool gp_registered; + bool bmap_support; + + /* RGB Variables */ + struct rgb_frame rgb_frames[CLAW_RGB_MAX_FRAMES]; + enum claw_rgb_effect_index rgb_effect; + struct led_classdev_mc led_mc; + struct delayed_work rgb_queue; + spinlock_t frame_lock; /* lock for rgb_frames read/write */ + bool rgb_registered; + u8 rgb_frame_count; + bool rgb_enabled; + u8 rgb_speed; + u16 rgb_addr; +}; + +static int get_endpoint_address(struct hid_device *hdev) +{ + struct usb_host_endpoint *ep; + struct usb_interface *intf; + + intf = to_usb_interface(hdev->dev.parent); + ep = intf->cur_altsetting->endpoint; + if (ep) + return ep->desc.bEndpointAddress; + + return -ENODEV; +} + +static int claw_gamepad_mode_event(struct claw_drvdata *drvdata, + struct claw_command_report *cmd_rep) +{ + if (cmd_rep->data[0] >= ARRAY_SIZE(claw_gamepad_mode_text) || + !claw_gamepad_mode_text[cmd_rep->data[0]] || + cmd_rep->data[1] >= ARRAY_SIZE(claw_mkeys_function_text)) + return -EINVAL; + + scoped_guard(spinlock_irqsave, &drvdata->mode_lock) { + drvdata->gamepad_mode = cmd_rep->data[0]; + drvdata->mkeys_function = cmd_rep->data[1]; + } + + return 0; +} + +static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_report *cmd_rep) +{ + enum claw_profile_ack_pending profile; + struct claw_rumble_report *rumble; + struct claw_mkey_report *mkeys; + struct claw_rgb_report *frame; + u16 rgb_addr, read_addr; + u8 *codes, key, f_idx; + u16 frame_calc; + int i; + + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + profile = drvdata->profile_pending; + + switch (profile) { + case CLAW_M1_PENDING: + case CLAW_M2_PENDING: + key = (profile == CLAW_M1_PENDING) ? CLAW_KEY_M1 : CLAW_KEY_M2; + mkeys = (struct claw_mkey_report *)cmd_rep->data; + if (be16_to_cpu(mkeys->read_addr) != drvdata->bmap_addr[key]) + return -EAGAIN; + codes = (profile == CLAW_M1_PENDING) ? drvdata->m1_codes : drvdata->m2_codes; + for (i = 0; i < CLAW_KEYS_MAX; i++) + codes[i] = (mkeys->codes[i]); + break; + case CLAW_RGB_PENDING: + frame = (struct claw_rgb_report *)cmd_rep->data; + rgb_addr = drvdata->rgb_addr; + read_addr = be16_to_cpu(frame->read_addr); + + if (read_addr < drvdata->rgb_addr) + return -EAGAIN; + + frame_calc = (read_addr - rgb_addr) / CLAW_RGB_FRAME_OFFSET; + if (frame_calc >= CLAW_RGB_MAX_FRAMES) { + dev_err(&drvdata->hdev->dev, "Got unsupported frame index: %x\n", + frame_calc); + return -EAGAIN; + } + f_idx = frame_calc; + + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { + memcpy(&drvdata->rgb_frames[f_idx], &frame->zone_data, + sizeof(struct rgb_frame)); + + /* Only use frame 0 for remaining variable assignment */ + if (f_idx != 0) + break; + + drvdata->rgb_speed = frame->speed; + drvdata->led_mc.led_cdev.brightness = frame->brightness; + drvdata->led_mc.subled_info[0].intensity = frame->zone_data.zone[0].red; + drvdata->led_mc.subled_info[1].intensity = frame->zone_data.zone[0].green; + drvdata->led_mc.subled_info[2].intensity = frame->zone_data.zone[0].blue; + } + + break; + case CLAW_RUMBLE_LEFT_PENDING: + rumble = (struct claw_rumble_report *)cmd_rep->data; + if (be16_to_cpu(rumble->read_addr) != rumble_addr[0]) + return -EAGAIN; + scoped_guard(spinlock_irqsave, &drvdata->rumble_lock) + drvdata->rumble_intensity_left = rumble->intensity; + break; + case CLAW_RUMBLE_RIGHT_PENDING: + rumble = (struct claw_rumble_report *)cmd_rep->data; + if (be16_to_cpu(rumble->read_addr) != rumble_addr[1]) + return -EAGAIN; + scoped_guard(spinlock_irqsave, &drvdata->rumble_lock) + drvdata->rumble_intensity_right = rumble->intensity; + break; + default: + dev_dbg(&drvdata->hdev->dev, + "Got profile event without changes pending from command: %x\n", + cmd_rep->cmd); + return -EINVAL; + } + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->profile_pending = CLAW_NO_PENDING; + + return 0; +} + +static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *report, + u8 *data, int size) +{ + struct claw_command_report *cmd_rep; + int ret = 0; + + if (size != CLAW_PACKET_SIZE) + return 0; + + cmd_rep = (struct claw_command_report *)data; + + if (cmd_rep->report_id != CLAW_INPUT_REPORT_ID || cmd_rep->header_tail != 0x3c) + return 0; + + dev_dbg(&drvdata->hdev->dev, "Rx data as raw input report: [%*ph]\n", + CLAW_PACKET_SIZE, data); + + guard(spinlock_irqsave)(&drvdata->cmd_lock); + switch (cmd_rep->cmd) { + case CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK: + ret = claw_gamepad_mode_event(drvdata, cmd_rep); + if (drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE) { + drvdata->cmd_status = ret; + complete(&drvdata->send_cmd_complete); + } + + break; + case CLAW_COMMAND_TYPE_READ_PROFILE_ACK: + ret = claw_profile_event(drvdata, cmd_rep); + /* Stale address received, ignore and keep waiting */ + if (ret == -EAGAIN) + return 0; + if (drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_PROFILE) { + drvdata->cmd_status = ret; + complete(&drvdata->send_cmd_complete); + } + + break; + case CLAW_COMMAND_TYPE_ACK: + if (drvdata->orphan_ack_pending) { + drvdata->orphan_ack_pending = false; + complete(&drvdata->orphan_ack_complete); + break; + } + + if (drvdata->waiting_cmd == CLAW_COMMAND_TYPE_NONE) { + dev_warn(&drvdata->hdev->dev, "Got unexpected ACK from MCU, ignoring\n"); + break; + } + + drvdata->cmd_status = 0; + complete(&drvdata->send_cmd_complete); + + dev_dbg(&drvdata->hdev->dev, "Waiting CMD: %x\n", drvdata->waiting_cmd); + + break; + default: + dev_dbg(&drvdata->hdev->dev, "Unknown command: %x\n", cmd_rep->cmd); + return 0; + } + + return ret; +} + +static int msi_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + + if (!drvdata || (drvdata->ep != CLAW_XINPUT_CFG_INTF_IN && + drvdata->ep != CLAW_DINPUT_CFG_INTF_IN)) + return 0; + + return claw_raw_event(drvdata, report, data, size); +} + +/* Caller must hold drvdata->cfg_mutex. */ +static int __claw_hw_output_report(struct hid_device *hdev, u8 index, u8 *data, + size_t len, unsigned int timeout) +{ + unsigned char *dmabuf __free(kfree) = NULL; + u8 header[] = { CLAW_OUTPUT_REPORT_ID, 0, 0, 0x3c, index }; + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + size_t header_size = ARRAY_SIZE(header); + bool orphaned; + int ret; + + lockdep_assert_held(&drvdata->cfg_mutex); + + /* If expecting an orphan ack, hold next event until MCU has time to clear it */ + scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) + orphaned = drvdata->orphan_ack_pending; + + if (orphaned) { + wait_for_completion_timeout(&drvdata->orphan_ack_complete, msecs_to_jiffies(25)); + scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) + drvdata->orphan_ack_pending = false; + } + + if (header_size + len > CLAW_PACKET_SIZE) + return -EINVAL; + + /* We can't use a devm_alloc reusable buffer without side effects during suspend */ + dmabuf = kzalloc(CLAW_PACKET_SIZE, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + memcpy(dmabuf, header, header_size); + if (data && len) + memcpy(dmabuf + header_size, data, len); + + reinit_completion(&drvdata->send_cmd_complete); + + scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) { + if (timeout) { + drvdata->waiting_cmd = index; + drvdata->cmd_status = -ETIMEDOUT; + } else { + reinit_completion(&drvdata->orphan_ack_complete); + drvdata->waiting_cmd = CLAW_COMMAND_TYPE_NONE; + drvdata->orphan_ack_pending = true; + } + } + + dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n", + CLAW_PACKET_SIZE, dmabuf); + + ret = hid_hw_output_report(hdev, dmabuf, CLAW_PACKET_SIZE); + if (ret < 0) + goto err; + + ret = ret == CLAW_PACKET_SIZE ? 0 : -EIO; + if (ret) + goto err; + + if (timeout) { + ret = wait_for_completion_interruptible_timeout(&drvdata->send_cmd_complete, + msecs_to_jiffies(timeout)); + + dev_dbg(&hdev->dev, "Remaining timeout: %u\n", ret); + ret = ret > 0 ? drvdata->cmd_status : ret ?: -EBUSY; + if (ret) + goto err; + } + + scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) + drvdata->waiting_cmd = CLAW_COMMAND_TYPE_NONE; + + return ret; + +err: + scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) { + drvdata->waiting_cmd = CLAW_COMMAND_TYPE_NONE; + drvdata->orphan_ack_pending = false; + } + return ret; +} + +static int claw_hw_output_report(struct hid_device *hdev, u8 index, u8 *data, + size_t len, unsigned int timeout) +{ + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + + guard(mutex)(&drvdata->cfg_mutex); + return __claw_hw_output_report(hdev, index, data, len, timeout); +} + +static int claw_switch_mode(struct hid_device *hdev, enum claw_mode_field field, u8 val) +{ + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + u8 data[2]; + + guard(mutex)(&drvdata->cfg_mutex); + + scoped_guard(spinlock_irqsave, &drvdata->mode_lock) { + switch (field) { + case CLAW_FIELD_GAMEPAD_MODE: + data[0] = val; + data[1] = drvdata->mkeys_function; + break; + case CLAW_FIELD_MKEYS_FUNCTION: + data[0] = drvdata->gamepad_mode; + data[1] = val; + break; + } + } + + return __claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data, + ARRAY_SIZE(data), 0); +} + +static ssize_t gamepad_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + int i, ret = -EINVAL; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(claw_gamepad_mode_text); i++) { + if (claw_gamepad_mode_text[i] && sysfs_streq(buf, claw_gamepad_mode_text[i])) { + ret = i; + break; + } + } + if (ret < 0) + return ret; + + ret = claw_switch_mode(hdev, CLAW_FIELD_GAMEPAD_MODE, ret); + if (ret) + return ret; + + return count; +} + +static ssize_t gamepad_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + int ret, i; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 25); + if (ret) + return ret; + + scoped_guard(spinlock_irqsave, &drvdata->mode_lock) + i = drvdata->gamepad_mode; + + if (!claw_gamepad_mode_text[i] || claw_gamepad_mode_text[i][0] == '\0') + return sysfs_emit(buf, "unsupported\n"); + + return sysfs_emit(buf, "%s\n", claw_gamepad_mode_text[i]); +} +static DEVICE_ATTR_RW(gamepad_mode); + +static ssize_t gamepad_mode_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(claw_gamepad_mode_text); i++) { + if (!claw_gamepad_mode_text[i] || claw_gamepad_mode_text[i][0] == '\0') + continue; + count += sysfs_emit_at(buf, count, "%s ", claw_gamepad_mode_text[i]); + } + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(gamepad_mode_index); + +static ssize_t mkeys_function_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + int i, ret = -EINVAL; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(claw_mkeys_function_text); i++) { + if (claw_mkeys_function_text[i] && sysfs_streq(buf, claw_mkeys_function_text[i])) { + ret = i; + break; + } + } + if (ret < 0) + return ret; + + ret = claw_switch_mode(hdev, CLAW_FIELD_MKEYS_FUNCTION, ret); + if (ret) + return ret; + + return count; +} + +static ssize_t mkeys_function_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + int ret, i; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 25); + if (ret) + return ret; + + scoped_guard(spinlock_irqsave, &drvdata->mode_lock) + i = drvdata->mkeys_function; + + if (i >= ARRAY_SIZE(claw_mkeys_function_text)) + return sysfs_emit(buf, "unsupported\n"); + + return sysfs_emit(buf, "%s\n", claw_mkeys_function_text[i]); +} +static DEVICE_ATTR_RW(mkeys_function); + +static ssize_t mkeys_function_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, count = 0; + + for (i = 0; i < ARRAY_SIZE(claw_mkeys_function_text); i++) + count += sysfs_emit_at(buf, count, "%s ", claw_mkeys_function_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(mkeys_function_index); + +static ssize_t reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + bool val; + int ret; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + if (!val) + return -EINVAL; + + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_RESET_DEVICE, NULL, 0, 0); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_WO(reset); + +static int mkey_mapping_name_to_code(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) { + if (!strcmp(name, claw_button_mapping_key_map[i].name)) + return claw_button_mapping_key_map[i].code; + } + + return -EINVAL; +} + +static const char *mkey_mapping_code_to_name(u8 code) +{ + int i; + + if (code == 0xff) + return NULL; + + for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) { + if (claw_button_mapping_key_map[i].code == code) + return claw_button_mapping_key_map[i].name; + } + + return NULL; +} + +static int claw_mkey_store(struct device *dev, const char *buf, u8 mkey) +{ + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + struct claw_mkey_report report = { {0x01, cpu_to_be16(drvdata->bmap_addr[mkey])}, + 0x07, 0x04, 0x00, {0xff, 0xff, 0xff, 0xff, 0xff} }; + char **raw_keys __free(argv_free) = NULL; + int ret, key_count, i; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + raw_keys = argv_split(GFP_KERNEL, buf, &key_count); + if (!raw_keys) + return -ENOMEM; + + if (key_count > CLAW_KEYS_MAX) + return -EINVAL; + + if (key_count == 0) + goto set_buttons; + + for (i = 0; i < key_count; i++) { + ret = mkey_mapping_name_to_code(raw_keys[i]); + if (ret < 0) + return ret; + + report.codes[i] = ret; + } + +set_buttons: + scoped_guard(mutex, &drvdata->rom_mutex) { + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, + (u8 *)&report, sizeof(report), 25); + if (ret) + return ret; + /* MCU will not send ACK until the USB transaction completes. ACK is sent + * immediately after and will hit the stale state machine, before the next + * command re-arms the state machine. Timeout 0 ensures no deadlock waiting + * for ACK that ill never come. + */ + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0); + } + + return ret; +} + +static int claw_mkey_show(struct device *dev, char *buf, enum claw_key_index m_key) +{ + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + struct claw_mkey_report report = { {0x01, cpu_to_be16(drvdata->bmap_addr[m_key])}, 0x07 }; + int i, ret, count = 0; + const char *name; + u8 *codes; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + codes = (m_key == CLAW_KEY_M1) ? drvdata->m1_codes : drvdata->m2_codes; + + guard(mutex)(&drvdata->profile_mutex); + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->profile_pending = (m_key == CLAW_KEY_M1) ? CLAW_M1_PENDING + : CLAW_M2_PENDING; + + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, + (u8 *)&report, sizeof(report), 25); + if (ret) + return ret; + + for (i = 0; i < CLAW_KEYS_MAX; i++) { + name = mkey_mapping_code_to_name(codes[i]); + if (name) + count += sysfs_emit_at(buf, count, "%s ", name); + } + + if (!count) + return sysfs_emit(buf, "(not set)\n"); + + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t button_m1_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + + ret = claw_mkey_store(dev, buf, CLAW_KEY_M1); + if (ret) + return ret; + + return count; +} + +static ssize_t button_m1_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return claw_mkey_show(dev, buf, CLAW_KEY_M1); +} +static DEVICE_ATTR_RW(button_m1); + +static ssize_t button_m2_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + + ret = claw_mkey_store(dev, buf, CLAW_KEY_M2); + if (ret) + return ret; + + return count; +} + +static ssize_t button_m2_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return claw_mkey_show(dev, buf, CLAW_KEY_M2); +} +static DEVICE_ATTR_RW(button_m2); + +static ssize_t button_mapping_options_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, count = 0; + + for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) + count += sysfs_emit_at(buf, count, "%s ", claw_button_mapping_key_map[i].name); + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(button_mapping_options); + +static ssize_t rumble_intensity_left_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct claw_rumble_report report = { {0x01, cpu_to_be16(rumble_addr[0])}, 0x01 }; + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + u8 val; + int ret; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + + if (val > 100) + return -EINVAL; + + report.intensity = val; + + guard(mutex)(&drvdata->rom_mutex); + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, + (u8 *)&report, sizeof(report), 25); + if (ret) + return ret; + + /* MCU will not send ACK until the USB transaction completes. ACK is sent + * immediately after and will hit the stale state machine, before the next + * command re-arms the state machine. Timeout 0 ensures no deadlock waiting + * for ACK that ill never come. + */ + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0); + if (ret) + return ret; + + return count; +} + +static ssize_t rumble_intensity_left_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct claw_rumble_report report = { {0x01, cpu_to_be16(rumble_addr[0])}, 0x01 }; + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + int ret; + u8 val; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + guard(mutex)(&drvdata->profile_mutex); + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->profile_pending = CLAW_RUMBLE_LEFT_PENDING; + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, + (u8 *)&report, sizeof(report), 25); + if (ret) + return ret; + + scoped_guard(spinlock_irqsave, &drvdata->rumble_lock) + val = drvdata->rumble_intensity_left; + + return sysfs_emit(buf, "%u\n", val); +} +static DEVICE_ATTR_RW(rumble_intensity_left); + +static ssize_t rumble_intensity_right_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct claw_rumble_report report = { {0x01, cpu_to_be16(rumble_addr[1])}, 0x01 }; + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + u8 val; + int ret; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + + if (val > 100) + return -EINVAL; + + report.intensity = val; + + guard(mutex)(&drvdata->rom_mutex); + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, + (u8 *)&report, sizeof(report), 25); + if (ret) + return ret; + + /* MCU will not send ACK until the USB transaction completes. ACK is sent + * immediately after and will hit the stale state machine, before the next + * command re-arms the state machine. Timeout 0 ensures no deadlock waiting + * for ACK that ill never come. + */ + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0); + if (ret) + return ret; + + return count; +} + +static ssize_t rumble_intensity_right_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct claw_rumble_report report = { {0x01, cpu_to_be16(rumble_addr[1])}, 0x01 }; + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + int ret; + u8 val; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + guard(mutex)(&drvdata->profile_mutex); + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->profile_pending = CLAW_RUMBLE_RIGHT_PENDING; + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, + (u8 *)&report, sizeof(report), 25); + if (ret) + return ret; + + scoped_guard(spinlock_irqsave, &drvdata->rumble_lock) + val = drvdata->rumble_intensity_right; + + return sysfs_emit(buf, "%u\n", val); +} +static DEVICE_ATTR_RW(rumble_intensity_right); + +static ssize_t rumble_intensity_range_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "0-100\n"); +} +static DEVICE_ATTR_RO(rumble_intensity_range); + +static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribute *attr, + int n) +{ + struct hid_device *hdev = to_hid_device(kobj_to_dev(kobj)); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + + if (!drvdata) { + dev_warn(&hdev->dev, + "Failed to get drvdata from kobj. Gamepad attributes are not available.\n"); + return 0; + } + + /* Always show attrs available on all firmware */ + if (attr == &dev_attr_gamepad_mode.attr || + attr == &dev_attr_gamepad_mode_index.attr || + attr == &dev_attr_mkeys_function.attr || + attr == &dev_attr_mkeys_function_index.attr || + attr == &dev_attr_reset.attr) + return attr->mode; + + /* Hide rumble attrs if not supported */ + if (attr == &dev_attr_rumble_intensity_left.attr || + attr == &dev_attr_rumble_intensity_right.attr || + attr == &dev_attr_rumble_intensity_range.attr) + return drvdata->rumble_support ? attr->mode : 0; + + /* Hide button mapping attrs if it isn't supported */ + return drvdata->bmap_support ? attr->mode : 0; +} + +static struct attribute *claw_gamepad_attrs[] = { + &dev_attr_button_m1.attr, + &dev_attr_button_m2.attr, + &dev_attr_button_mapping_options.attr, + &dev_attr_gamepad_mode.attr, + &dev_attr_gamepad_mode_index.attr, + &dev_attr_mkeys_function.attr, + &dev_attr_mkeys_function_index.attr, + &dev_attr_reset.attr, + &dev_attr_rumble_intensity_left.attr, + &dev_attr_rumble_intensity_right.attr, + &dev_attr_rumble_intensity_range.attr, + NULL, +}; + +static const struct attribute_group claw_gamepad_attr_group = { + .attrs = claw_gamepad_attrs, + .is_visible = claw_gamepad_attr_is_visible, +}; + +/* Read RGB config from device */ +static int claw_read_rgb_config(struct hid_device *hdev) +{ + u8 data[4] = { 0x01, 0x00, 0x00, CLAW_RGB_FRAME_OFFSET }; + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + u16 read_addr = drvdata->rgb_addr; + size_t len = ARRAY_SIZE(data); + int ret, i; + + if (!drvdata->rgb_addr) + return -ENODEV; + + /* Loop through all 8 pages of RGB data */ + guard(mutex)(&drvdata->profile_mutex); + for (i = 0; i < CLAW_RGB_MAX_FRAMES; i++) { + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->profile_pending = CLAW_RGB_PENDING; + data[1] = (read_addr >> 8) & 0xff; + data[2] = read_addr & 0x00ff; + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, data, len, 25); + if (ret) + return ret; + + read_addr += CLAW_RGB_FRAME_OFFSET; + } + + return 0; +} + +/* Send RGB configuration to device */ +static int claw_write_rgb_state(struct claw_drvdata *drvdata) +{ + struct claw_rgb_report report = { {0x01, 0}, CLAW_RGB_FRAME_OFFSET, 0x00, + drvdata->rgb_frame_count, 0x09, drvdata->rgb_speed, + drvdata->led_mc.led_cdev.brightness }; + u16 write_addr = drvdata->rgb_addr; + int f, ret; + + if (!drvdata->rgb_addr) + return -ENODEV; + + if (!drvdata->rgb_frame_count) + return -EINVAL; + + guard(mutex)(&drvdata->rom_mutex); + /* Loop through (up to) 8 pages of RGB data */ + for (f = 0; f < drvdata->rgb_frame_count; f++) { + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) + report.zone_data = drvdata->rgb_frames[f]; + + /* Set the MCU address to write the frame data to */ + report.read_addr = cpu_to_be16(write_addr); + + /* Serialize the rgb_report and write it to MCU */ + ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, + (u8 *)&report, sizeof(report), 25); + if (ret) + return ret; + + /* Increment the write addr by the offset for the next frame */ + write_addr += CLAW_RGB_FRAME_OFFSET; + } + + /* MCU will not send ACK until the USB transaction completes. ACK is sent + * immediately after and will hit the stale state machine, before the next + * command re-arms the state machine. Timeout 0 ensures no deadlock waiting + * for ACK that ill never come. + */ + ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0); + + return ret; +} + +/* Fill all zones with the same color */ +static void claw_frame_fill_solid(struct rgb_frame *frame, struct rgb_zone zone) +{ + int z; + + for (z = 0; z < CLAW_RGB_ZONES; z++) + frame->zone[z] = zone; +} + +/* Apply solid effect (1 frame, no color) */ +static int claw_apply_disabled(struct claw_drvdata *drvdata) +{ + struct rgb_zone off = { 0x00, 0x00, 0x00}; + + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { + drvdata->rgb_frame_count = 1; + claw_frame_fill_solid(&drvdata->rgb_frames[0], off); + } + + return claw_write_rgb_state(drvdata); +} + +/* Apply solid effect (1 frame, all zones same color) */ +static int claw_apply_monocolor(struct claw_drvdata *drvdata) +{ + struct mc_subled *subleds = drvdata->led_mc.subled_info; + struct rgb_zone zone = { subleds[0].intensity, subleds[1].intensity, + subleds[2].intensity }; + + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { + drvdata->rgb_frame_count = 1; + claw_frame_fill_solid(&drvdata->rgb_frames[0], zone); + } + + return claw_write_rgb_state(drvdata); +} + +/* Apply breathe effect (2 frames: color -> off) */ +static int claw_apply_breathe(struct claw_drvdata *drvdata) +{ + struct mc_subled *subleds = drvdata->led_mc.subled_info; + struct rgb_zone zone = { subleds[0].intensity, subleds[1].intensity, + subleds[2].intensity }; + static const struct rgb_zone off = { 0, 0, 0 }; + + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { + drvdata->rgb_frame_count = 2; + claw_frame_fill_solid(&drvdata->rgb_frames[0], zone); + claw_frame_fill_solid(&drvdata->rgb_frames[1], off); + } + + return claw_write_rgb_state(drvdata); +} + +/* Apply chroma effect (6 frames: rainbow cycle, all zones sync) */ +static int claw_apply_chroma(struct claw_drvdata *drvdata) +{ + static const struct rgb_zone colors[] = { + {255, 0, 0}, /* red */ + {255, 255, 0}, /* yellow */ + { 0, 255, 0}, /* green */ + { 0, 255, 255}, /* cyan */ + { 0, 0, 255}, /* blue */ + {255, 0, 255}, /* magenta */ + }; + u8 frame_count = ARRAY_SIZE(colors); + int f; + + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { + drvdata->rgb_frame_count = frame_count; + + for (f = 0; f < frame_count; f++) + claw_frame_fill_solid(&drvdata->rgb_frames[f], colors[f]); + } + + return claw_write_rgb_state(drvdata); +} + +/* Apply rainbow effect (4 frames: rotating colors around joysticks) */ +static int claw_apply_rainbow(struct claw_drvdata *drvdata) +{ + static const struct rgb_zone colors[] = { + {255, 0, 0}, /* red */ + { 0, 255, 0}, /* green */ + { 0, 255, 255}, /* cyan */ + { 0, 0, 255}, /* blue */ + }; + u8 frame_count = ARRAY_SIZE(colors); + int f, z; + + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { + drvdata->rgb_frame_count = frame_count; + + for (f = 0; f < frame_count; f++) { + for (z = 0; z < 4; z++) { + drvdata->rgb_frames[f].zone[z] = colors[(z + f) % 4]; + drvdata->rgb_frames[f].zone[z + 4] = colors[(z + f) % 4]; + } + drvdata->rgb_frames[f].zone[8] = colors[f]; + } + } + + return claw_write_rgb_state(drvdata); +} + +/* + * Apply frostfire effect (4 frames: fire vs ice rotating) + * Right joystick: fire red -> dark -> ice blue -> dark (clockwise) + * Left joystick: ice blue -> dark -> fire red -> dark (counter-clockwise) + * ABXY: fire red -> dark -> ice blue -> dark + */ +static int claw_apply_frostfire(struct claw_drvdata *drvdata) +{ + static const struct rgb_zone colors[] = { + {255, 0, 0}, /* fire red */ + { 0, 0, 0}, /* dark */ + { 0, 0, 255}, /* ice blue */ + { 0, 0, 0}, /* dark */ + }; + u8 frame_count = ARRAY_SIZE(colors); + int f, z; + + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { + drvdata->rgb_frame_count = frame_count; + + for (f = 0; f < frame_count; f++) { + for (z = 0; z < 4; z++) { + drvdata->rgb_frames[f].zone[z] = colors[(z + f) % 4]; + drvdata->rgb_frames[f].zone[z + 4] = colors[(z - f + 6) % 4]; + } + drvdata->rgb_frames[f].zone[8] = colors[f]; + } + } + + return claw_write_rgb_state(drvdata); +} + +/* Apply current state to device */ +static int claw_apply_rgb_state(struct claw_drvdata *drvdata) +{ + if (!drvdata->rgb_enabled) + return claw_apply_disabled(drvdata); + + switch (drvdata->rgb_effect) { + case CLAW_RGB_EFFECT_MONOCOLOR: + return claw_apply_monocolor(drvdata); + case CLAW_RGB_EFFECT_BREATHE: + return claw_apply_breathe(drvdata); + case CLAW_RGB_EFFECT_CHROMA: + return claw_apply_chroma(drvdata); + case CLAW_RGB_EFFECT_RAINBOW: + return claw_apply_rainbow(drvdata); + case CLAW_RGB_EFFECT_FROSTFIRE: + return claw_apply_frostfire(drvdata); + default: + dev_err(&drvdata->hdev->dev, "No supported rgb_effect selected\n"); + return -EINVAL; + } +} + +static void claw_rgb_queue_fn(struct work_struct *work) +{ + struct delayed_work *dwork = container_of(work, struct delayed_work, work); + struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, rgb_queue); + int ret; + + if (!drvdata) + return; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return; + } + + ret = claw_apply_rgb_state(drvdata); + if (ret) + dev_err(&drvdata->hdev->dev, "Failed to apply RGB state: %d\n", ret); +} + +static ssize_t effect_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); + struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); + int ret; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return -ENODEV; + } + + ret = sysfs_match_string(claw_rgb_effect_text, buf); + if (ret < 0) + return ret; + + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->rgb_effect = ret; + + mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50)); + + return count; +} + +static ssize_t effect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); + struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return -ENODEV; + } + + if (drvdata->rgb_effect >= ARRAY_SIZE(claw_rgb_effect_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", claw_rgb_effect_text[drvdata->rgb_effect]); +} + +static DEVICE_ATTR_RW(effect); + +static ssize_t effect_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, count = 0; + + for (i = 0; i < ARRAY_SIZE(claw_rgb_effect_text); i++) + count += sysfs_emit_at(buf, count, "%s ", claw_rgb_effect_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(effect_index); + +static ssize_t enabled_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); + struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); + bool val; + int ret; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return -ENODEV; + } + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->rgb_enabled = val; + + mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50)); + + return count; +} + +static ssize_t enabled_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); + struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return -ENODEV; + } + + return sysfs_emit(buf, "%s\n", drvdata->rgb_enabled ? "true" : "false"); +} +static DEVICE_ATTR_RW(enabled); + +static ssize_t enabled_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "true false\n"); +} +static DEVICE_ATTR_RO(enabled_index); + +static ssize_t speed_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); + struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); + unsigned int val, speed; + int ret; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return -ENODEV; + } + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + + if (val > 20) + return -EINVAL; + + /* 0 is fastest, invert value for intuitive userspace speed */ + speed = 20 - val; + + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->rgb_speed = speed; + + mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50)); + + return count; +} + +static ssize_t speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); + struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); + u8 speed = 20 - drvdata->rgb_speed; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return -ENODEV; + } + + return sysfs_emit(buf, "%u\n", speed); +} +static DEVICE_ATTR_RW(speed); + +static ssize_t speed_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0-20\n"); +} +static DEVICE_ATTR_RO(speed_range); + +static void claw_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness _brightness) +{ + struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); + struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return; + } + + mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50)); +} + +static struct attribute *claw_rgb_attrs[] = { + &dev_attr_effect.attr, + &dev_attr_effect_index.attr, + &dev_attr_enabled.attr, + &dev_attr_enabled_index.attr, + &dev_attr_speed.attr, + &dev_attr_speed_range.attr, + NULL, +}; + +static const struct attribute_group claw_rgb_attr_group = { + .attrs = claw_rgb_attrs, +}; + +static struct mc_subled claw_rgb_subled_info[] = { + { + .color_index = LED_COLOR_ID_RED, + .channel = 0x1, + }, + { + .color_index = LED_COLOR_ID_GREEN, + .channel = 0x2, + }, + { + .color_index = LED_COLOR_ID_BLUE, + .channel = 0x3, + }, +}; + +static void cfg_setup_fn(struct work_struct *work) +{ + struct delayed_work *dwork = container_of(work, struct delayed_work, work); + struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, cfg_setup); + bool gamepad_ready = false, rgb_ready = false, gp_registered, rgb_registered; + int ret; + + ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, + NULL, 0, 25); + if (ret) { + dev_err(&drvdata->hdev->dev, + "Failed to read gamepad mode: %d\n", ret); + goto prep_rgb; + } + gamepad_ready = true; + +prep_rgb: + ret = claw_read_rgb_config(drvdata->hdev); + if (ret) { + dev_err(&drvdata->hdev->dev, + "Failed to read RGB config: %d\n", ret); + goto try_gamepad; + } + rgb_ready = true; + + /* Add sysfs attributes after we get the device state */ +try_gamepad: + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) + /* Pairs with smp_store_release from below */ + gp_registered = smp_load_acquire(&drvdata->gp_registered); + + if (!gp_registered && gamepad_ready) { + ret = device_add_group(&drvdata->hdev->dev, &claw_gamepad_attr_group); + if (ret) { + dev_err(&drvdata->hdev->dev, + "Failed to create gamepad attrs: %d\n", ret); + goto try_rgb; + } + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_load_acquire in attribute show/store functions */ + smp_store_release(&drvdata->gp_registered, true); + gp_registered = true; + } + } + +try_rgb: + /* Add and enable RGB interface once we have the device state */ + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) + /* Pairs with smp_store_release from below */ + rgb_registered = smp_load_acquire(&drvdata->rgb_registered); + + if (!rgb_registered && rgb_ready) { + ret = led_classdev_multicolor_register(&drvdata->hdev->dev, + &drvdata->led_mc); + if (ret) { + dev_err(&drvdata->hdev->dev, "Failed to create led device: %d\n", ret); + goto update_kobjects; + } + + ret = device_add_group(drvdata->led_mc.led_cdev.dev, &claw_rgb_attr_group); + if (ret) { + dev_err(&drvdata->hdev->dev, "Failed to create RGB attrs: %d\n", ret); + led_classdev_multicolor_unregister(&drvdata->led_mc); + goto update_kobjects; + } + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_load_acquire in attribute show/store functions */ + smp_store_release(&drvdata->rgb_registered, true); + rgb_registered = true; + } + } + +update_kobjects: + if (gp_registered) + kobject_uevent(&drvdata->hdev->dev.kobj, KOBJ_CHANGE); + if (rgb_registered) + kobject_uevent(&drvdata->led_mc.led_cdev.dev->kobj, KOBJ_CHANGE); +} + +static void cfg_resume_fn(struct work_struct *work) +{ + struct delayed_work *dwork = container_of(work, struct delayed_work, work); + struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, cfg_resume); + + guard(spinlock_irqsave)(&drvdata->registration_lock); + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered) || + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + !smp_load_acquire(&drvdata->rgb_registered)) + schedule_delayed_work(&drvdata->cfg_setup, msecs_to_jiffies(500)); +} + +static void claw_features_supported(struct claw_drvdata *drvdata) +{ + u8 major = (drvdata->bcd_device >> 8) & 0xff; + u8 minor = drvdata->bcd_device & 0xff; + + if (major == 0x01) { + drvdata->bmap_support = true; + if (minor >= 0x66) { + drvdata->bmap_addr = button_mapping_addr_new; + drvdata->rumble_support = true; + drvdata->rgb_addr = rgb_addr_new; + } else { + drvdata->bmap_addr = button_mapping_addr_old; + drvdata->rgb_addr = rgb_addr_old; + } + return; + } + + if ((major == 0x02 && minor >= 0x17) || major >= 0x03) { + drvdata->bmap_support = true; + drvdata->bmap_addr = button_mapping_addr_new; + drvdata->rumble_support = true; + drvdata->rgb_addr = rgb_addr_new; + return; + } + + drvdata->rgb_addr = rgb_addr_old; +} + +static int claw_probe(struct hid_device *hdev, u8 ep) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *udev = interface_to_usbdev(intf); + struct claw_drvdata *drvdata; + int ret; + + drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->gamepad_mode = CLAW_GAMEPAD_MODE_XINPUT; + drvdata->rgb_enabled = true; + drvdata->hdev = hdev; + drvdata->ep = ep; + + /* Determine feature level from firmware version */ + drvdata->bcd_device = le16_to_cpu(udev->descriptor.bcdDevice); + claw_features_supported(drvdata); + + if (!drvdata->bmap_support) + dev_dbg(&hdev->dev, "M-Key mapping is not supported. Update firmware to enable.\n"); + + /* Device is hardwired and name is guaranteed to be unique */ + drvdata->led_mc.led_cdev.name = "msi_claw:rgb:joystick_rings"; + drvdata->led_mc.led_cdev.brightness = 0x50; + drvdata->led_mc.led_cdev.max_brightness = 0x64; + drvdata->led_mc.led_cdev.color = LED_COLOR_ID_RGB; + drvdata->led_mc.led_cdev.brightness_set = claw_led_brightness_set; + drvdata->led_mc.num_colors = 3; + drvdata->led_mc.subled_info = devm_kmemdup(&hdev->dev, claw_rgb_subled_info, + sizeof(claw_rgb_subled_info), GFP_KERNEL); + if (!drvdata->led_mc.subled_info) + return -ENOMEM; + + mutex_init(&drvdata->cfg_mutex); + mutex_init(&drvdata->profile_mutex); + mutex_init(&drvdata->rom_mutex); + spin_lock_init(&drvdata->registration_lock); + spin_lock_init(&drvdata->cmd_lock); + spin_lock_init(&drvdata->mode_lock); + spin_lock_init(&drvdata->profile_lock); + spin_lock_init(&drvdata->frame_lock); + spin_lock_init(&drvdata->rumble_lock); + init_completion(&drvdata->orphan_ack_complete); + init_completion(&drvdata->send_cmd_complete); + INIT_DELAYED_WORK(&drvdata->cfg_resume, &cfg_resume_fn); + INIT_DELAYED_WORK(&drvdata->cfg_setup, &cfg_setup_fn); + INIT_DELAYED_WORK(&drvdata->rgb_queue, &claw_rgb_queue_fn); + + /* For control interface: open the HID transport for sending commands. */ + ret = hid_hw_open(hdev); + if (ret) + return ret; + + hid_set_drvdata(hdev, drvdata); + schedule_delayed_work(&drvdata->cfg_setup, msecs_to_jiffies(500)); + + return 0; +} + +static int msi_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + u8 ep; + + if (!hid_is_usb(hdev)) { + ret = -ENODEV; + goto err_probe; + } + + ret = hid_parse(hdev); + if (ret) + goto err_probe; + + /* Set quirk to create separate input devices per HID application */ + hdev->quirks |= HID_QUIRK_INPUT_PER_APP | HID_QUIRK_MULTI_INPUT; + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) + goto err_probe; + + /* For non-control interfaces (keyboard/mouse), allow userspace to grab the devices. */ + ret = get_endpoint_address(hdev); + if (ret < 0) + goto err_stop_hw; + + ep = ret; + if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) { + ret = claw_probe(hdev, ep); + if (ret) + goto err_stop_hw; + } + + return 0; + +err_stop_hw: + hid_hw_stop(hdev); +err_probe: + return dev_err_probe(&hdev->dev, ret, "Failed to init device\n"); +} + +static void claw_remove(struct hid_device *hdev) +{ + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + bool gp_registered; + bool rgb_registered; + + if (!drvdata) + return; + + cancel_delayed_work_sync(&drvdata->cfg_resume); + cancel_delayed_work_sync(&drvdata->cfg_setup); + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + gp_registered = smp_load_acquire(&drvdata->gp_registered); + /* Pairs with smp_load_acquire in attribute show/store functions */ + smp_store_release(&drvdata->gp_registered, false); + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + rgb_registered = smp_load_acquire(&drvdata->rgb_registered); + /* Pairs with smp_load_acquire in attribute show/store functions */ + smp_store_release(&drvdata->rgb_registered, false); + } + + if (gp_registered) + device_remove_group(&hdev->dev, &claw_gamepad_attr_group); + + if (rgb_registered) { + device_remove_group(drvdata->led_mc.led_cdev.dev, &claw_rgb_attr_group); + led_classdev_multicolor_unregister(&drvdata->led_mc); + } + cancel_delayed_work_sync(&drvdata->rgb_queue); + + hid_hw_close(hdev); +} + +static void msi_remove(struct hid_device *hdev) +{ + int ret; + u8 ep; + + /* Safe assumption. SET_INTERFACE ioctl can't be used while driver is bound */ + ret = get_endpoint_address(hdev); + if (ret <= 0) + goto hw_stop; + + ep = ret; + if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) + claw_remove(hdev); + +hw_stop: + hid_hw_stop(hdev); +} + +static int claw_resume(struct hid_device *hdev) +{ + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + + if (!drvdata) + return -ENODEV; + + /* MCU can take up to 500ms to be ready after resume */ + schedule_delayed_work(&drvdata->cfg_resume, msecs_to_jiffies(500)); + return 0; +} + +static int msi_resume(struct hid_device *hdev) +{ + int ret; + u8 ep; + + /* Safe assumption. SET_INTERFACE ioctl can't be used while driver is bound */ + ret = get_endpoint_address(hdev); + if (ret <= 0) + return 0; + + ep = ret; + if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) + return claw_resume(hdev); + + return 0; +} + +static int claw_suspend(struct hid_device *hdev) +{ + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + + if (!drvdata) + return -ENODEV; + + cancel_delayed_work_sync(&drvdata->cfg_resume); + cancel_delayed_work_sync(&drvdata->cfg_setup); + cancel_delayed_work_sync(&drvdata->rgb_queue); + + return 0; +} + +static int msi_suspend(struct hid_device *hdev, pm_message_t msg) +{ + int ret; + u8 ep; + + /* Safe assumption. SET_INTERFACE ioctl can't be used while driver is bound */ + ret = get_endpoint_address(hdev); + if (ret <= 0) + return 0; + + ep = ret; + if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) + return claw_suspend(hdev); + + return 0; +} + +static const struct hid_device_id msi_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_XINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_DINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_DESKTOP) }, + { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_BIOS) }, + { } +}; +MODULE_DEVICE_TABLE(hid, msi_devices); + +static struct hid_driver msi_driver = { + .name = "hid-msi", + .id_table = msi_devices, + .raw_event = msi_raw_event, + .probe = msi_probe, + .remove = msi_remove, + .resume = pm_ptr(msi_resume), + .suspend = pm_ptr(msi_suspend), +}; +module_hid_driver(msi_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Denis Benato "); +MODULE_AUTHOR("Zhouwang Huang "); +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("HID driver for MSI Claw Handheld PC gamepads"); diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index edb37b4c867e..2c41bacab1ca 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -2189,16 +2189,8 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); - if (td->is_haptic_touchpad) { - if (hid_haptic_init(hdev, &td->haptic)) { - dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n", - hdev->name); - td->is_haptic_touchpad = false; - devm_kfree(&hdev->dev, td->haptic); - } - } else { + if (!td->is_haptic_touchpad) devm_kfree(&hdev->dev, td->haptic); - } return 0; } @@ -2722,7 +2714,7 @@ static const struct hid_device_id mt_devices[] = { HID_ANY_ID) }, /* Hantick */ - { .driver_data = MT_CLS_NSMU, + { .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU, HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288) }, diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index e7302ec01ff1..43e0f2aaea3b 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -609,6 +609,8 @@ struct joycon_ctlr { unsigned int last_input_report_msecs; unsigned int last_subcmd_sent_msecs; unsigned int consecutive_valid_report_deltas; + unsigned int subcmd_rate_exhaustions; + bool subcmd_rate_relaxed; /* factory calibration data */ struct joycon_stick_cal left_stick_cal_x; @@ -841,10 +843,11 @@ static void joycon_wait_for_input_report(struct joycon_ctlr *ctlr) #define JC_SUBCMD_TX_OFFSET_MS 4 #define JC_SUBCMD_VALID_DELTA_REQ 3 #define JC_SUBCMD_RATE_MAX_ATTEMPTS 25 +#define JC_SUBCMD_RATE_MAX_FAILURES 4 #define JC_SUBCMD_RATE_LIMITER_USB_MS 20 #define JC_SUBCMD_RATE_LIMITER_BT_MS 60 #define JC_SUBCMD_RATE_LIMITER_MS(ctlr) ((ctlr)->hdev->bus == BUS_USB ? JC_SUBCMD_RATE_LIMITER_USB_MS : JC_SUBCMD_RATE_LIMITER_BT_MS) -static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr) +static void joycon_enforce_subcmd_rate_strict(struct joycon_ctlr *ctlr) { unsigned int current_ms; unsigned long subcmd_delta; @@ -872,6 +875,14 @@ static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr) if (attempts >= JC_SUBCMD_RATE_MAX_ATTEMPTS) { hid_warn(ctlr->hdev, "%s: exceeded max attempts", __func__); + + if (++ctlr->subcmd_rate_exhaustions == JC_SUBCMD_RATE_MAX_FAILURES) { + ctlr->subcmd_rate_relaxed = true; + hid_info(ctlr->hdev, + "input report cadence does not fit the %d-%dms window; using the legacy subcommand throttle\n", + JC_INPUT_REPORT_MIN_DELTA, + JC_INPUT_REPORT_MAX_DELTA); + } return; } @@ -886,6 +897,32 @@ static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr) msleep(JC_SUBCMD_TX_OFFSET_MS); } +/* The rate limiter as it was before commit d750d1480362, without the report + * cadence requirement. + */ +static void joycon_enforce_subcmd_rate_legacy(struct joycon_ctlr *ctlr) +{ + static const unsigned int max_subcmd_rate_ms = 25; + unsigned int current_ms = jiffies_to_msecs(jiffies); + unsigned int delta_ms = current_ms - ctlr->last_subcmd_sent_msecs; + + while (delta_ms < max_subcmd_rate_ms && + ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) { + joycon_wait_for_input_report(ctlr); + current_ms = jiffies_to_msecs(jiffies); + delta_ms = current_ms - ctlr->last_subcmd_sent_msecs; + } + ctlr->last_subcmd_sent_msecs = current_ms; +} + +static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr) +{ + if (ctlr->subcmd_rate_relaxed) + joycon_enforce_subcmd_rate_legacy(ctlr); + else + joycon_enforce_subcmd_rate_strict(ctlr); +} + static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len, u32 timeout) { @@ -1474,7 +1511,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", @@ -2162,10 +2198,6 @@ static int joycon_input_create(struct joycon_ctlr *ctlr) ctlr->input->phys = hdev->phys; input_set_drvdata(ctlr->input, ctlr); - ret = input_register_device(ctlr->input); - if (ret) - return ret; - if (joycon_type_is_right_joycon(ctlr)) { joycon_config_right_stick(ctlr->input); joycon_config_buttons(ctlr->input, right_joycon_button_mappings); @@ -2208,6 +2240,10 @@ static int joycon_input_create(struct joycon_ctlr *ctlr) if (joycon_has_rumble(ctlr)) joycon_config_rumble(ctlr); + ret = input_register_device(ctlr->input); + if (ret) + return ret; + return 0; } @@ -2607,7 +2643,12 @@ static int joycon_ctlr_read_handler(struct joycon_ctlr *ctlr, u8 *data, { if (data[0] == JC_INPUT_SUBCMD_REPLY || data[0] == JC_INPUT_IMU_DATA || data[0] == JC_INPUT_MCU_DATA) { - if (size >= 12) /* make sure it contains the input report */ + /* + * The whole struct is cast and parsed below, including the + * IMU/subcmd union, not just the 12-byte partial header this + * used to check for. + */ + if (size >= sizeof(struct joycon_input_report)) joycon_parse_report(ctlr, (struct joycon_input_report *)data); } @@ -2736,14 +2777,14 @@ static int nintendo_hid_probe(struct hid_device *hdev, ret = joycon_init(hdev); if (ret) { hid_err(hdev, "Failed to initialize controller; ret=%d\n", ret); - goto err_close; + goto err_io_stop; } /* Initialize the leds */ ret = joycon_leds_create(ctlr); if (ret) { hid_err(hdev, "Failed to create leds; ret=%d\n", ret); - goto err_close; + goto err_io_stop; } /* Initialize the battery power supply */ @@ -2766,7 +2807,8 @@ static int nintendo_hid_probe(struct hid_device *hdev, err_ida: ida_free(&nintendo_player_id_allocator, ctlr->player_id); -err_close: +err_io_stop: + hid_device_io_stop(hdev); hid_hw_close(hdev); err_stop: hid_hw_stop(hdev); diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c index 20a54f337220..d2ded6b08ce9 100644 --- a/drivers/hid/hid-oxp.c +++ b/drivers/hid/hid-oxp.c @@ -398,7 +398,7 @@ static int oxp_hid_raw_event_gen_2(struct hid_device *hdev, * Re-apply our settings after this has been received. */ if (data[3] == OXP_EFFECT_MONO_TRUE) { - mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50)); + mod_delayed_work(system_dfl_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50)); return 0; } @@ -788,7 +788,7 @@ static ssize_t map_button_store(struct device *dev, default: return -EINVAL; } - mod_delayed_work(system_wq, &drvdata.oxp_btn_queue, msecs_to_jiffies(50)); + mod_delayed_work(system_dfl_wq, &drvdata.oxp_btn_queue, msecs_to_jiffies(50)); return count; } @@ -1349,7 +1349,7 @@ static void oxp_rgb_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { led_cdev->brightness = brightness; - mod_delayed_work(system_wq, &drvdata.oxp_rgb_queue, msecs_to_jiffies(50)); + mod_delayed_work(system_dfl_wq, &drvdata.oxp_rgb_queue, msecs_to_jiffies(50)); } static struct attribute *oxp_rgb_attrs[] = { @@ -1502,7 +1502,7 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up) drvdata.rumble_intensity = 5; INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn); - mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50)); + mod_delayed_work(system_dfl_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50)); ret = devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group); if (ret) diff --git a/drivers/hid/hid-picolcd_debugfs.c b/drivers/hid/hid-picolcd_debugfs.c index 085847a92e07..1f7dfe60e9ba 100644 --- a/drivers/hid/hid-picolcd_debugfs.c +++ b/drivers/hid/hid-picolcd_debugfs.c @@ -99,6 +99,15 @@ static ssize_t picolcd_debug_eeprom_read(struct file *f, char __user *u, ret = resp->raw_data[2]; if (ret > s) ret = s; + /* + * raw_data[2] is a device-supplied length; also clamp it to + * what picolcd_raw_event() actually stored (raw_size), or a + * hostile device overruns the raw_data[] buffer. + */ + if (ret > resp->raw_size - 3) + ret = resp->raw_size - 3; + if (ret < 0) + ret = 0; if (copy_to_user(u, resp->raw_data+3, ret)) ret = -EFAULT; else diff --git a/drivers/hid/hid-pl.c b/drivers/hid/hid-pl.c index 8bba29ef6c7a..cae56a6c941b 100644 --- a/drivers/hid/hid-pl.c +++ b/drivers/hid/hid-pl.c @@ -62,15 +62,13 @@ static int hid_plff_play(struct input_dev *dev, void *data, return 0; } -static int plff_init(struct hid_device *hid) +static int pl_input_configured(struct hid_device *hid, struct hid_input *hidinput) { struct plff_device *plff; struct hid_report *report; - struct hid_input *hidinput; struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; - struct list_head *report_ptr = report_list; - struct input_dev *dev; + struct input_dev *dev = hidinput->input; int error; s32 maxval; s32 *strong; @@ -83,89 +81,80 @@ static int plff_init(struct hid_device *hid) The input reports also contain a field which contains 8 ff00.0001 usages and 8 boolean values. Their meaning is currently unknown. - + A version of the 0e8f:0003 exists that has all the values in separate fields and misses the extra input field, thus resembling Zeroplus (hid-zpff) devices. */ - if (list_empty(report_list)) { + if (!list_is_first(&hidinput->list, &hid->inputs)) + return 0; + + report = list_first_entry_or_null(report_list, struct hid_report, list); + if (!report) { hid_err(hid, "no output reports found\n"); return -ENODEV; } - - list_for_each_entry(hidinput, &hid->inputs, list) { - - report_ptr = report_ptr->next; - - if (report_ptr == report_list) { - hid_err(hid, "required output report is missing\n"); - return -ENODEV; - } - - report = list_entry(report_ptr, struct hid_report, list); - if (report->maxfield < 1) { - hid_err(hid, "no fields in the report\n"); - return -ENODEV; - } - - maxval = 0x7f; - if (report->field[0]->report_count >= 4) { - report->field[0]->value[0] = 0x00; - report->field[0]->value[1] = 0x00; - strong = &report->field[0]->value[2]; - weak = &report->field[0]->value[3]; - hid_dbg(hid, "detected single-field device"); - } else if (report->field[0]->maxusage == 1 && - report->field[0]->usage[0].hid == - (HID_UP_LED | 0x43) && - report->maxfield >= 4 && - report->field[0]->report_count >= 1 && - report->field[1]->report_count >= 1 && - report->field[2]->report_count >= 1 && - report->field[3]->report_count >= 1) { - report->field[0]->value[0] = 0x00; - report->field[1]->value[0] = 0x00; - strong = &report->field[2]->value[0]; - weak = &report->field[3]->value[0]; - if (hid->vendor == USB_VENDOR_ID_JESS2) - maxval = 0xff; - hid_dbg(hid, "detected 4-field device"); - } else { - hid_err(hid, "not enough fields or values\n"); - return -ENODEV; - } - - plff = kzalloc_obj(struct plff_device); - if (!plff) - return -ENOMEM; - - dev = hidinput->input; - - set_bit(FF_RUMBLE, dev->ffbit); - - error = input_ff_create_memless(dev, plff, hid_plff_play); - if (error) { - kfree(plff); - return error; - } - - plff->report = report; - plff->strong = strong; - plff->weak = weak; - plff->maxval = maxval; - - *strong = 0x00; - *weak = 0x00; - hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT); + if (report->maxfield < 1) { + hid_err(hid, "no fields in the report\n"); + return -ENODEV; } - hid_info(hid, "Force feedback for PantherLord/GreenAsia devices by Anssi Hannula \n"); + maxval = 0x7f; + if (report->field[0]->report_count >= 4) { + report->field[0]->value[0] = 0x00; + report->field[0]->value[1] = 0x00; + strong = &report->field[0]->value[2]; + weak = &report->field[0]->value[3]; + hid_dbg(hid, "detected single-field device"); + } else if (report->field[0]->maxusage == 1 && + report->field[0]->usage[0].hid == + (HID_UP_LED | 0x43) && + report->maxfield >= 4 && + report->field[0]->report_count >= 1 && + report->field[1]->report_count >= 1 && + report->field[2]->report_count >= 1 && + report->field[3]->report_count >= 1) { + report->field[0]->value[0] = 0x00; + report->field[1]->value[0] = 0x00; + strong = &report->field[2]->value[0]; + weak = &report->field[3]->value[0]; + if (hid->vendor == USB_VENDOR_ID_JESS2) + maxval = 0xff; + hid_dbg(hid, "detected 4-field device"); + } else { + hid_err(hid, "not enough fields or values\n"); + return -ENODEV; + } + + plff = kzalloc_obj(struct plff_device); + if (!plff) + return -ENOMEM; + + dev = hidinput->input; + + set_bit(FF_RUMBLE, dev->ffbit); + + error = input_ff_create_memless(dev, plff, hid_plff_play); + if (error) { + kfree(plff); + return error; + } + + plff->report = report; + plff->strong = strong; + plff->weak = weak; + plff->maxval = maxval; + + *strong = 0x00; + *weak = 0x00; + hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT); return 0; } #else -static inline int plff_init(struct hid_device *hid) +static inline int pl_input_configured(struct hid_device *hid, + struct hid_input *hidinput) { return 0; } @@ -181,27 +170,17 @@ static int pl_probe(struct hid_device *hdev, const struct hid_device_id *id) ret = hid_parse(hdev); if (ret) { hid_err(hdev, "parse failed\n"); - goto err; + return ret; } - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { hid_err(hdev, "hw start failed\n"); - goto err; + return ret; } - ret = plff_init(hdev); - if (ret) - goto stop; - return 0; - -stop: - hid_hw_stop(hdev); -err: - return ret; } - static const struct hid_device_id pl_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR), .driver_data = 1 }, /* Twin USB Joystick */ @@ -217,6 +196,7 @@ static struct hid_driver pl_driver = { .name = "pantherlord", .id_table = pl_devices, .probe = pl_probe, + .input_configured = pl_input_configured, }; module_hid_driver(pl_driver); diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 57d8efdd9b89..8a0b51d47040 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -45,6 +45,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS682), HID_QUIRK_NOGET }, { HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS692), HID_QUIRK_NOGET }, { HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_UC100KM), HID_QUIRK_NOGET }, + { HID_USB_DEVICE(USB_VENDOR_ID_BEITONG, USB_DEVICE_ID_BEITONG_KP20D), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_MULTI_TOUCH), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE2), HID_QUIRK_ALWAYS_POLL }, @@ -112,6 +113,8 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, USB_DEVICE_ID_IDEACOM_IDC6680), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_INNOMEDIA, USB_DEVICE_ID_INNEX_GENESIS_ATARI), HID_QUIRK_MULTI_INPUT }, + { HID_USB_DEVICE(USB_VENDOR_ID_JESS, USB_DEVICE_ID_AULA_MINI_60_HE_PRO), + HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE_ID2), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M406), HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M506), HID_QUIRK_MULTI_INPUT }, @@ -747,8 +750,22 @@ static const struct hid_device_id hid_have_special_driver[] = { #endif #if IS_ENABLED(CONFIG_HID_STEELSERIES) { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, - { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3) }, #endif #if IS_ENABLED(CONFIG_HID_SUNPLUS) { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) }, diff --git a/drivers/hid/hid-rapoo.c b/drivers/hid/hid-rapoo.c index 4c81f3086de4..5c9c396fabf7 100644 --- a/drivers/hid/hid-rapoo.c +++ b/drivers/hid/hid-rapoo.c @@ -36,7 +36,7 @@ static int rapoo_probe(struct hid_device *hdev, const struct hid_device_id *id) return ret; } - if (hdev->bus == BUS_USB) { + if (hid_is_usb(hdev)) { struct usb_interface *intf = to_usb_interface(hdev->dev.parent); if (intf->cur_altsetting->desc.bInterfaceNumber != 1) diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c index d4af17fdba46..2bd781f1e0f5 100644 --- a/drivers/hid/hid-rmi.c +++ b/drivers/hid/hid-rmi.c @@ -365,7 +365,7 @@ static int rmi_check_sanity(struct hid_device *hdev, u8 *data, int size) * such reports here. */ - while ((data[valid_size - 1] == 0xff) && valid_size > 0) + while (valid_size > 0 && data[valid_size - 1] == 0xff) valid_size--; return valid_size; 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 + +/* + * 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-roccat.c b/drivers/hid/hid-roccat.c index d6fff53d4ee7..4f15eb951039 100644 --- a/drivers/hid/hid-roccat.c +++ b/drivers/hid/hid-roccat.c @@ -70,6 +70,15 @@ static struct roccat_device *devices[ROCCAT_MAX_DEVICES]; /* protects modifications of devices array */ static DEFINE_MUTEX(devices_lock); +static void roccat_free_device(struct roccat_device *device) +{ + int i; + + for (i = 0; i < ROCCAT_CBUF_SIZE; i++) + kfree(device->cbuf[i].value); + kfree(device); +} + static ssize_t roccat_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { @@ -226,7 +235,7 @@ static int roccat_release(struct inode *inode, struct file *file) hid_hw_power(device->hid, PM_HINT_NORMAL); hid_hw_close(device->hid); } else { - kfree(device); + roccat_free_device(device); } } @@ -374,7 +383,7 @@ void roccat_disconnect(int minor) hid_hw_close(device->hid); wake_up_interruptible(&device->wait); } else { - kfree(device); + roccat_free_device(device); } } EXPORT_SYMBOL_GPL(roccat_disconnect); diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c index afffea894021..c2b425afd951 100644 --- a/drivers/hid/hid-sensor-custom.c +++ b/drivers/hid/hid-sensor-custom.c @@ -609,7 +609,7 @@ static int hid_sensor_custom_add_attributes(struct hid_sensor_custom &sensor_inst->fields[i]. hid_custom_attribute_group); if (ret) - break; + goto err_remove_groups; /* For power or report field store indexes */ if (sensor_inst->fields[i].attribute.attrib_id == @@ -621,6 +621,13 @@ static int hid_sensor_custom_add_attributes(struct hid_sensor_custom } return ret; + +err_remove_groups: + while (--i >= 0) + sysfs_remove_group(&sensor_inst->pdev->dev.kobj, + &sensor_inst->fields[i].hid_custom_attribute_group); + kfree(sensor_inst->fields); + return ret; } static void hid_sensor_custom_remove_attributes(struct hid_sensor_custom * @@ -1005,26 +1012,26 @@ static int hid_sensor_custom_probe(struct platform_device *pdev) return ret; } - ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj, - &enable_sensor_attr_group); + ret = hid_sensor_custom_add_attributes(sensor_inst); if (ret) goto err_remove_callback; - ret = hid_sensor_custom_add_attributes(sensor_inst); - if (ret) - goto err_remove_group; - - ret = hid_sensor_custom_dev_if_add(sensor_inst); + ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj, + &enable_sensor_attr_group); if (ret) goto err_remove_attributes; + ret = hid_sensor_custom_dev_if_add(sensor_inst); + if (ret) + goto err_remove_group; + return 0; -err_remove_attributes: - hid_sensor_custom_remove_attributes(sensor_inst); err_remove_group: sysfs_remove_group(&sensor_inst->pdev->dev.kobj, &enable_sensor_attr_group); +err_remove_attributes: + hid_sensor_custom_remove_attributes(sensor_inst); err_remove_callback: sensor_hub_remove_callback(hsdev, hsdev->usage); @@ -1042,9 +1049,10 @@ static void hid_sensor_custom_remove(struct platform_device *pdev) } hid_sensor_custom_dev_if_remove(sensor_inst); - hid_sensor_custom_remove_attributes(sensor_inst); + /* Remove enable_sensor first as it uses fields via power_state/report_state. */ sysfs_remove_group(&sensor_inst->pdev->dev.kobj, &enable_sensor_attr_group); + hid_sensor_custom_remove_attributes(sensor_inst); sensor_hub_remove_callback(hsdev, hsdev->usage); } diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 34f710c465b8..6470a290ebfc 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -239,12 +239,17 @@ int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, u32 field_index, int buffer_size, void *buffer) { struct hid_report *report; + struct hid_field *field; struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev); - int report_size; + size_t field_size; + size_t report_size; + size_t copied = 0; + size_t to_copy; int ret = 0; - u8 *val_ptr; - int buffer_index = 0; - int i; + unsigned int i; + + if (!buffer || buffer_size <= 0) + return -EINVAL; memset(buffer, 0, buffer_size); @@ -258,27 +263,30 @@ int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT); hid_hw_wait(hsdev->hdev); + field = report->field[field_index]; + /* calculate number of bytes required to read this field */ - report_size = DIV_ROUND_UP(report->field[field_index]->report_size, - 8) * - report->field[field_index]->report_count; - if (!report_size) { + field_size = DIV_ROUND_UP(field->report_size, 8); + /* HID core stores each parsed report value in a __s32 slot. */ + if (!field_size || field_size > sizeof(field->value[0])) { ret = -EINVAL; goto done_proc; } - ret = min(report_size, buffer_size); - - val_ptr = (u8 *)report->field[field_index]->value; - for (i = 0; i < report->field[field_index]->report_count; ++i) { - if (buffer_index >= ret) - break; - - memcpy(&((u8 *)buffer)[buffer_index], val_ptr, - report->field[field_index]->report_size / 8); - val_ptr += sizeof(__s32); - buffer_index += (report->field[field_index]->report_size / 8); + if (field->report_count > SIZE_MAX / field_size) { + ret = -EINVAL; + goto done_proc; } + report_size = field_size * field->report_count; + report_size = min_t(size_t, report_size, buffer_size); + + for (i = 0; i < field->report_count && copied < report_size; ++i) { + to_copy = min(field_size, report_size - copied); + memcpy(&((u8 *)buffer)[copied], &field->value[i], to_copy); + copied += to_copy; + } + ret = copied; + done_proc: mutex_unlock(&data->mutex); diff --git a/drivers/hid/hid-sjoy.c b/drivers/hid/hid-sjoy.c index 963c45113204..193ab2a6146e 100644 --- a/drivers/hid/hid-sjoy.c +++ b/drivers/hid/hid-sjoy.c @@ -48,68 +48,56 @@ static int hid_sjoyff_play(struct input_dev *dev, void *data, return 0; } -static int sjoyff_init(struct hid_device *hid) +static int sjoy_input_configured(struct hid_device *hid, struct hid_input *hidinput) { struct sjoyff_device *sjoyff; struct hid_report *report; - struct hid_input *hidinput; struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; - struct list_head *report_ptr = report_list; - struct input_dev *dev; + struct input_dev *dev = hidinput->input; int error; - if (list_empty(report_list)) { + if (!list_is_first(&hidinput->list, &hid->inputs)) + return 0; + + report = list_first_entry_or_null(report_list, struct hid_report, list); + if (!report) { hid_err(hid, "no output reports found\n"); return -ENODEV; } - - list_for_each_entry(hidinput, &hid->inputs, list) { - report_ptr = report_ptr->next; - - if (report_ptr == report_list) { - hid_err(hid, "required output report is missing\n"); - return -ENODEV; - } - - report = list_entry(report_ptr, struct hid_report, list); - if (report->maxfield < 1) { - hid_err(hid, "no fields in the report\n"); - return -ENODEV; - } - - if (report->field[0]->report_count < 3) { - hid_err(hid, "not enough values in the field\n"); - return -ENODEV; - } - - sjoyff = kzalloc_obj(struct sjoyff_device); - if (!sjoyff) - return -ENOMEM; - - dev = hidinput->input; - - set_bit(FF_RUMBLE, dev->ffbit); - - sjoyff->report = report; - sjoyff->report->field[0]->value[0] = 0x01; - sjoyff->report->field[0]->value[1] = 0x00; - sjoyff->report->field[0]->value[2] = 0x00; - hid_hw_request(hid, sjoyff->report, HID_REQ_SET_REPORT); - - error = input_ff_create_memless(dev, sjoyff, hid_sjoyff_play); - if (error) { - kfree(sjoyff); - return error; - } + if (report->maxfield < 1) { + hid_err(hid, "no fields in the report\n"); + return -ENODEV; } - hid_info(hid, "Force feedback for SmartJoy PLUS PS2/USB adapter\n"); + if (report->field[0]->report_count < 3) { + hid_err(hid, "not enough values in the field\n"); + return -ENODEV; + } + + sjoyff = kzalloc_obj(struct sjoyff_device); + if (!sjoyff) + return -ENOMEM; + + set_bit(FF_RUMBLE, dev->ffbit); + + sjoyff->report = report; + sjoyff->report->field[0]->value[0] = 0x01; + sjoyff->report->field[0]->value[1] = 0x00; + sjoyff->report->field[0]->value[2] = 0x00; + hid_hw_request(hid, sjoyff->report, HID_REQ_SET_REPORT); + + error = input_ff_create_memless(dev, sjoyff, hid_sjoyff_play); + if (error) { + kfree(sjoyff); + return error; + } return 0; } #else -static inline int sjoyff_init(struct hid_device *hid) +static inline int sjoy_input_configured(struct hid_device *hid, + struct hid_input *hidinput) { return 0; } @@ -124,20 +112,16 @@ static int sjoy_probe(struct hid_device *hdev, const struct hid_device_id *id) ret = hid_parse(hdev); if (ret) { hid_err(hdev, "parse failed\n"); - goto err; + return ret; } - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { hid_err(hdev, "hw start failed\n"); - goto err; + return ret; } - sjoyff_init(hdev); - return 0; -err: - return ret; } static const struct hid_device_id sjoy_devices[] = { @@ -165,6 +149,7 @@ static struct hid_driver sjoy_driver = { .name = "smartjoyplus", .id_table = sjoy_devices, .probe = sjoy_probe, + .input_configured = sjoy_input_configured, }; module_hid_driver(sjoy_driver); diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index e75246d29e16..50f5ad6bbeb4 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 #include #include #include @@ -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 @@ -503,7 +505,7 @@ struct motion_output_report_02 { u8 r, g, b; u8 zero2; u8 rumble; -}; +} __packed; static_assert(sizeof(struct motion_output_report_02) == 7); #define SIXAXIS_REPORT_0xF2_SIZE 17 @@ -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,27 +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; + guard(spinlock_irqsave)(&sony_dev_list_lock); - 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); - } + if (!list_empty(&sc->list_node)) + list_del_init(&sc->list_node); } static int sony_get_bt_devaddr(struct sony_sc *sc) @@ -2110,6 +2099,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,16 +2118,21 @@ 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); } } +static void sony_cleanup(struct sony_sc *sc) +{ + sony_cancel_work_sync(sc); + sony_remove_dev_list(sc); + sony_release_device_id(sc); +} + static int sony_input_configured(struct hid_device *hdev, struct hid_input *hidinput) { @@ -2185,6 +2185,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 +2200,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 +2215,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 +2241,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 +2263,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 +2279,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) { @@ -2306,9 +2319,7 @@ static int sony_input_configured(struct hid_device *hdev, err_close: hid_hw_close(hdev); err_stop: - sony_cancel_work_sync(sc); - sony_remove_dev_list(sc); - sony_release_device_id(sc); + sony_cleanup(sc); return ret; } @@ -2332,6 +2343,8 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) return -ENOMEM; spin_lock_init(&sc->lock); + INIT_LIST_HEAD(&sc->list_node); + sc->device_id = -1; sc->quirks = quirks; hid_set_drvdata(hdev, sc); @@ -2360,6 +2373,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) ret = hid_hw_start(hdev, connect_mask); if (ret) { hid_err(hdev, "hw start failed\n"); + sony_cleanup(sc); return ret; } @@ -2414,7 +2428,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) err: usb_free_urb(sc->ghl_urb); - + sony_cleanup(sc); hid_hw_stop(hdev); return ret; } @@ -2424,18 +2438,14 @@ static void sony_remove(struct hid_device *hdev) struct sony_sc *sc = hid_get_drvdata(hdev); if (sc->quirks & (GHL_GUITAR_PS3WIIU | GHL_GUITAR_PS4)) { - timer_delete_sync(&sc->ghl_poke_timer); + /* poison, not kill: a pending timer must not re-submit during teardown */ + usb_poison_urb(sc->ghl_urb); + timer_shutdown_sync(&sc->ghl_poke_timer); usb_free_urb(sc->ghl_urb); } hid_hw_close(hdev); - - sony_cancel_work_sync(sc); - - sony_remove_dev_list(sc); - - sony_release_device_id(sc); - + sony_cleanup(sc); hid_hw_stop(hdev); } diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c index 197126d6e081..ac08cb2d0368 100644 --- a/drivers/hid/hid-steam.c +++ b/drivers/hid/hid-steam.c @@ -43,11 +43,14 @@ #include #include #include +#include +#include #include "hid-ids.h" MODULE_DESCRIPTION("HID driver for Valve Steam Controller"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Rodrigo Rivas Costa "); +MODULE_AUTHOR("Vicki Pfau "); static bool lizard_mode = true; @@ -56,6 +59,8 @@ static LIST_HEAD(steam_devices); #define STEAM_QUIRK_WIRELESS BIT(0) #define STEAM_QUIRK_DECK BIT(1) +#define STEAM_QUIRK_IBEX BIT(2) +#define STEAM_QUIRK_BLE BIT(3) /* Touch pads are 40 mm in diameter and 65535 units */ #define STEAM_PAD_RESOLUTION 1638 @@ -68,13 +73,14 @@ static LIST_HEAD(steam_devices); /* Joystick runs are about 5 mm and 32768 units */ #define STEAM_DECK_JOYSTICK_RESOLUTION 6553 /* Accelerometer has 16 bit resolution and a range of +/- 2g */ -#define STEAM_DECK_ACCEL_RES_PER_G 16384 -#define STEAM_DECK_ACCEL_RANGE 32768 +#define STEAM_ACCEL_RES_PER_G 16384 +#define STEAM_ACCEL_RANGE 32768 +#define STEAM_ACCEL_FUZZ 128 #define STEAM_DECK_ACCEL_FUZZ 32 /* Gyroscope has 16 bit resolution and a range of +/- 2000 dps */ -#define STEAM_DECK_GYRO_RES_PER_DPS 16 -#define STEAM_DECK_GYRO_RANGE 32768 -#define STEAM_DECK_GYRO_FUZZ 1 +#define STEAM_GYRO_RES_PER_DPS 16 +#define STEAM_GYRO_RANGE 32768 +#define STEAM_GYRO_FUZZ 0 #define STEAM_PAD_FUZZ 256 @@ -149,7 +155,7 @@ enum { SETTING_USB_DEBUG_MODE, SETTING_LEFT_TRACKPAD_MODE, SETTING_RIGHT_TRACKPAD_MODE, - SETTING_MOUSE_POINTER_ENABLED, + SETTING_LIZARD_MODE, /* 10 */ SETTING_DPAD_DEADZONE, @@ -243,14 +249,39 @@ enum { /* Input report identifiers */ enum { - ID_CONTROLLER_STATE = 1, - ID_CONTROLLER_DEBUG = 2, - ID_CONTROLLER_WIRELESS = 3, - ID_CONTROLLER_STATUS = 4, - ID_CONTROLLER_DEBUG2 = 5, - ID_CONTROLLER_SECONDARY_STATE = 6, - ID_CONTROLLER_BLE_STATE = 7, - ID_CONTROLLER_DECK_STATE = 9 + ID_CONTROLLER_STATE = 1, + ID_CONTROLLER_DEBUG = 2, + ID_CONTROLLER_WIRELESS = 3, + ID_CONTROLLER_STATUS = 4, + ID_CONTROLLER_DEBUG2 = 5, + ID_CONTROLLER_SECONDARY_STATE = 6, + ID_CONTROLLER_BLE_STATE = 7, + ID_CONTROLLER_DECK_STATE = 9, +}; + +/* Read-only attributes */ +enum { + ATTRIB_UNIQUE_ID, // deprecated + ATTRIB_PRODUCT_ID, + ATTRIB_PRODUCT_REVISON, // deprecated + ATTRIB_CAPABILITIES = ATTRIB_PRODUCT_REVISON, // intentional aliasing + ATTRIB_FIRMWARE_VERSION, // deprecated + ATTRIB_FIRMWARE_BUILD_TIME, + ATTRIB_RADIO_FIRMWARE_BUILD_TIME, + ATTRIB_RADIO_DEVICE_ID0, + ATTRIB_RADIO_DEVICE_ID1, + ATTRIB_DONGLE_FIRMWARE_BUILD_TIME, + ATTRIB_HW_ID, // AKA BOARD_REVISION, + ATTRIB_BOOTLOADER_BUILD_TIME, + ATTRIB_CONNECTION_INTERVAL_IN_US, + ATTRIB_SECONDARY_FIRMWARE_BUILD_TIME, + ATTRIB_SECONDARY_BOOTLOADER_BUILD_TIME, + ATTRIB_SECONDARY_HW_ID, // AKA BOARD_REVISION, + ATTRIB_STREAMING, + ATTRIB_TRACKPAD_ID, + ATTRIB_SECONDARY_TRACKPAD_ID, + + ATTRIB_COUNT }; /* String attribute identifiers */ @@ -259,14 +290,14 @@ enum { ATTRIB_STR_UNIT_SERIAL, }; -/* Values for GYRO_MODE (bitmask) */ +/* Values for IMU_MODE (bitmask) */ enum { - SETTING_GYRO_MODE_OFF = 0, - SETTING_GYRO_MODE_STEERING = BIT(0), - SETTING_GYRO_MODE_TILT = BIT(1), - SETTING_GYRO_MODE_SEND_ORIENTATION = BIT(2), - SETTING_GYRO_MODE_SEND_RAW_ACCEL = BIT(3), - SETTING_GYRO_MODE_SEND_RAW_GYRO = BIT(4), + SETTING_IMU_MODE_OFF = 0, + SETTING_IMU_MODE_STEERING = BIT(0), + SETTING_IMU_MODE_TILT = BIT(1), + SETTING_IMU_MODE_SEND_ORIENTATION = BIT(2), + SETTING_IMU_MODE_SEND_RAW_ACCEL = BIT(3), + SETTING_IMU_MODE_SEND_RAW_GYRO = BIT(4), }; /* Trackpad modes */ @@ -282,6 +313,83 @@ enum { TRACKPAD_GESTURE_KEYBOARD, }; +/* Report identifiers (Ibex only) */ +enum { + /* Feature */ + REPORT_ID_FEATURES_CONTROLLER = 1, + REPORT_ID_FEATURES_DONGLE = 2, + + /* Input */ + REPORT_ID_INPUT = 0x42, + REPORT_ID_BATTERY = 0x43, + REPORT_ID_INPUT2 = 0x45, + REPORT_ID_WIRELESS_EVENT = 0x79, + + /* Output */ + REPORT_ID_HAPTIC_RUMBLE = 0x80, + REPORT_ID_HAPTIC_PULSE = 0x81, + REPORT_ID_HAPTIC_COMMAND = 0x82, + REPORT_ID_HAPTIC_LFO_TONE = 0x83, + REPORT_ID_HAPTIC_LOG_SWEEP = 0x84, + REPORT_ID_HAPTIC_SCRIPT = 0x85, +}; + +/* Ibex charge state */ +enum { + CHARGE_STATE_DISCHARGING = 1, + CHARGE_STATE_CHARGING = 2, + CHARGE_STATE_CHARGING_DONE = 4, +}; + +/* Ibex wireless events */ +enum { + WIRELESS_EVENT_DISCONNECT = 1, + WIRELESS_EVENT_CONNECT = 2, + WIRELESS_EVENT_PAIR = 3, +}; + +struct steam_controller_attribute { + unsigned char tag; + __le32 value; +} __packed; + +struct steam_ibex_battery_status { + u8 charge_state; + u8 battery_level; + __le16 battery_voltage; + __le16 system_voltage; + __le16 input_voltage; + __le16 battery_current; + __le16 input_current; + __le16 temperature; +}; + +struct steam_ibex_haptic_rumble { + u8 type; + __le16 intensity; + struct { + __le16 speed; + u8 gain; + } __packed left, right; +} __packed; +static_assert(sizeof(struct steam_ibex_haptic_rumble) == 9); + +struct steam_ibex_haptic_pulse { + u8 side; + __le16 on_us; + __le16 off_us; + __le16 repeat_count; +} __packed; +static_assert(sizeof(struct steam_ibex_haptic_pulse) == 7); + +struct steam_ibex_output_report { + u8 id; + union { + struct steam_ibex_haptic_rumble rumble; + struct steam_ibex_haptic_pulse pulse; + }; +} __packed; + /* Pad identifiers for the deck */ #define STEAM_PAD_LEFT 0 #define STEAM_PAD_RIGHT 1 @@ -295,41 +403,57 @@ struct steam_device { spinlock_t lock; struct hid_device *hdev, *client_hdev; struct mutex report_mutex; + struct mutex registration_mutex; unsigned long client_opened; struct input_dev __rcu *input; struct input_dev __rcu *sensors; unsigned long quirks; struct work_struct work_connect; bool connected; + bool registered; char serial_no[STEAM_SERIAL_LEN + 1]; struct power_supply_desc battery_desc; struct power_supply __rcu *battery; u8 battery_charge; - u16 voltage; + u8 battery_status; + u16 battery_temp; + u16 battery_voltage; + u16 battery_current; struct delayed_work mode_switch; bool did_mode_switch; bool gamepad_mode; struct work_struct rumble_work; + struct delayed_work coalesce_rumble_work; u16 rumble_left; u16 rumble_right; unsigned int sensor_timestamp_us; - struct work_struct unregister_work; + unsigned int sensor_update_rate_us; }; -static int steam_recv_report(struct steam_device *steam, - u8 *data, int size) +static int steam_recv_report_id(struct steam_device *steam, + u8 *data, int size, u8 report_id) { struct hid_report *r; u8 *buf; + unsigned int retries = 50; int ret; + u32 len; - r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0]; + /* + * All reports start with a two byte header. + * We must read at least two bytes to get a sensible output. + */ + if (size < 2) + return -EINVAL; + + r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[report_id]; if (!r) { - hid_err(steam->hdev, "No HID_FEATURE_REPORT submitted - nothing to read\n"); + hid_err(steam->hdev, "No HID_FEATURE_REPORT present for ID %u\n", report_id); return -EINVAL; } - if (hid_report_len(r) < 64) + len = hid_report_len(r); + if (len < 64) return -EINVAL; buf = hid_alloc_report_buf(r, GFP_KERNEL); @@ -337,43 +461,13 @@ static int steam_recv_report(struct steam_device *steam, return -ENOMEM; /* - * The report ID is always 0, so strip the first byte from the output. + * The report ID is consistent, so strip the first byte from the output. * hid_report_len() is not counting the report ID, so +1 to the length * or else we get a EOVERFLOW. We are safe from a buffer overflow * because hid_alloc_report_buf() allocates +7 bytes. */ - ret = hid_hw_raw_request(steam->hdev, 0x00, - buf, hid_report_len(r) + 1, - HID_FEATURE_REPORT, HID_REQ_GET_REPORT); - if (ret > 0) - memcpy(data, buf + 1, min(size, ret - 1)); - kfree(buf); - return ret; -} - -static int steam_send_report(struct steam_device *steam, - u8 *cmd, int size) -{ - struct hid_report *r; - u8 *buf; - unsigned int retries = 50; - int ret; - - r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0]; - if (!r) { - hid_err(steam->hdev, "No HID_FEATURE_REPORT submitted - nothing to read\n"); - return -EINVAL; - } - - if (hid_report_len(r) < 64) - return -EINVAL; - - buf = hid_alloc_report_buf(r, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - /* The report ID is always 0 */ - memcpy(buf + 1, cmd, size); + if (!(steam->quirks & STEAM_QUIRK_IBEX)) + len += 1; /* * Sometimes the wireless controller fails with EPIPE @@ -382,8 +476,85 @@ static int steam_send_report(struct steam_device *steam, * seems to fix that. */ do { - ret = hid_hw_raw_request(steam->hdev, 0, - buf, max(size, 64) + 1, + ret = hid_hw_raw_request(steam->hdev, report_id, + buf, len, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (ret != -EPIPE) + break; + msleep(20); + } while (--retries); + if (ret > 0) { + /* Remove the report ID from the return buffer */ + ret--; + size = min(size, ret); + memcpy(data, buf + 1, size); + } + + kfree(buf); + /* + * Don't log if the failure is -ENODEV, as this + * can happen normally on disconnect. + */ + if (ret < 0 && ret != -ENODEV) + hid_err(steam->hdev, "%s: error %d\n", __func__, ret); + else if (ret > 0) + hid_dbg(steam->hdev, "Received report %*ph\n", size, data); + if (ret < 0) + return ret; + + if (ret < 2) { + hid_err(steam->hdev, "%s: reply too short\n", __func__); + return -EPROTO; + } + if (ret < data[1] + 2) { + hid_err(steam->hdev, "%s: expected %u bytes, read %i\n", + __func__, data[1] + 2, ret); + return -EPROTO; + } + return size; +} + +static int steam_send_report_id(struct steam_device *steam, + u8 *cmd, int size, u8 report_id) +{ + struct hid_report *r; + u8 *buf; + unsigned int retries = 50; + int ret; + u32 len; + + r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[report_id]; + if (!r) { + hid_err(steam->hdev, "No HID_FEATURE_REPORT present for ID %u\n", report_id); + return -EINVAL; + } + + len = hid_report_len(r); + if (len < 64) + return -EINVAL; + + buf = hid_alloc_report_buf(r, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* The report ID is always consistent */ + buf[0] = report_id; + memcpy(buf + 1, cmd, size); + + hid_dbg(steam->hdev, "Sending report %*ph\n", size, cmd); + + if (!(steam->quirks & STEAM_QUIRK_IBEX)) + len += 1; + + /* + * Sometimes the wireless controller fails with EPIPE + * when sending a feature report. + * Doing a HID_REQ_SET_REPORT and waiting for a while + * seems to fix that. + */ + do { + ret = hid_hw_raw_request(steam->hdev, report_id, + buf, max(size + 1, len), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (ret != -EPIPE) break; @@ -391,12 +562,29 @@ static int steam_send_report(struct steam_device *steam, } while (--retries); kfree(buf); - if (ret < 0) + /* + * Don't log if the failure is -ENODEV, as this + * can happen normally on disconnect. + */ + if (ret < 0 && ret != -ENODEV) hid_err(steam->hdev, "%s: error %d (%*ph)\n", __func__, ret, size, cmd); return ret; } +static int steam_send_report(struct steam_device *steam, + u8 *cmd, int size) +{ + u8 report_id; + + if (steam->quirks & STEAM_QUIRK_IBEX) + report_id = REPORT_ID_FEATURES_CONTROLLER; + else + report_id = 0; + + return steam_send_report_id(steam, cmd, size, report_id); +} + static inline int steam_send_report_byte(struct steam_device *steam, u8 cmd) { return steam_send_report(steam, &cmd, 1); @@ -409,7 +597,6 @@ static int steam_write_settings(struct steam_device *steam, u8 reg; u16 val; u8 cmd[64] = {ID_SET_SETTINGS_VALUES, 0x00}; - int ret; va_list args; va_start(args, steam); @@ -425,16 +612,56 @@ static int steam_write_settings(struct steam_device *steam, } va_end(args); - ret = steam_send_report(steam, cmd, 2 + cmd[1]); - if (ret < 0) - return ret; + return steam_send_report(steam, cmd, 2 + cmd[1]); +} - /* - * Sometimes a lingering report for this command can - * get read back instead of the last set report if - * this isn't explicitly queried - */ - return steam_recv_report(steam, cmd, 2 + cmd[1]); +static int steam_exchange_report_id(struct steam_device *steam, u8 *cmd, int csize, + u8 *reply, int rsize, u8 report_id) +{ + unsigned int retries = 5; + int ret; + + do { + ret = steam_send_report_id(steam, cmd, csize, report_id); + if (ret < 0) + return ret; + ret = steam_recv_report_id(steam, reply, rsize, report_id); + /* + * Sometimes this can fail on the first few tries on the Steam + * Controller (2015). It appears to be a firmware bug, and Steam + * itself just retries, so we should also retry a few times to + * see if we get it. + */ + if (ret == -EPROTO) + continue; + if (ret < 0) { + hid_err(steam->hdev, "%s: error reading reply (%*ph)\n", + __func__, csize, cmd); + return ret; + } + if (reply[0] == cmd[0] && reply[1] >= 1) + break; + if (retries > 0) + continue; + hid_err(steam->hdev, "%s: invalid reply (%*ph)\n", __func__, + rsize, reply); + return -EPROTO; + } while (retries--); + + return ret; +} + +static int steam_exchange_report(struct steam_device *steam, u8 *cmd, int csize, + u8 *reply, int rsize) +{ + u8 report_id; + + if (steam->quirks & STEAM_QUIRK_IBEX) + report_id = REPORT_ID_FEATURES_CONTROLLER; + else + report_id = 0; + + return steam_exchange_report_id(steam, cmd, csize, reply, rsize, report_id); } static int steam_get_serial(struct steam_device *steam) @@ -445,39 +672,74 @@ static int steam_get_serial(struct steam_device *steam) */ int ret = 0; u8 cmd[] = {ID_GET_STRING_ATTRIBUTE, sizeof(steam->serial_no), ATTRIB_STR_UNIT_SERIAL}; - u8 reply[3 + STEAM_SERIAL_LEN + 1]; + u8 reply[3 + STEAM_SERIAL_LEN + 1] = {0}; - mutex_lock(&steam->report_mutex); - ret = steam_send_report(steam, cmd, sizeof(cmd)); + ret = steam_exchange_report(steam, cmd, sizeof(cmd), reply, sizeof(reply)); if (ret < 0) - goto out; - ret = steam_recv_report(steam, reply, sizeof(reply)); - if (ret < 0) - goto out; - if (reply[0] != ID_GET_STRING_ATTRIBUTE || reply[1] < 1 || - reply[1] > sizeof(steam->serial_no) || reply[2] != ATTRIB_STR_UNIT_SERIAL) { - ret = -EIO; - goto out; + return ret; + if (reply[1] > sizeof(steam->serial_no) || reply[2] != ATTRIB_STR_UNIT_SERIAL) { + hid_err(steam->hdev, "%s: invalid reply (%*ph)\n", __func__, + (int)sizeof(reply), reply); + return -EIO; } - reply[3 + STEAM_SERIAL_LEN] = 0; strscpy(steam->serial_no, reply + 3, reply[1]); -out: - mutex_unlock(&steam->report_mutex); return ret; } -/* - * This command requests the wireless adaptor to post an event - * with the connection status. Useful if this driver is loaded when - * the controller is already connected. - */ -static inline int steam_request_conn_status(struct steam_device *steam) +static int steam_get_attributes(struct steam_device *steam) { - int ret; - mutex_lock(&steam->report_mutex); - ret = steam_send_report_byte(steam, ID_DONGLE_GET_WIRELESS_STATE); - mutex_unlock(&steam->report_mutex); - return ret; + int ret = 0; + u8 cmd[] = {ID_GET_ATTRIBUTES_VALUES, 0}; + u8 reply[64] = {}; + u8 size; + int i; + struct steam_controller_attribute *attr; + + ret = steam_exchange_report(steam, cmd, sizeof(cmd), reply, sizeof(reply)); + if (ret < 0) + return ret; + if (reply[1] < 2) { + hid_err(steam->hdev, "%s: invalid reply (%*ph)\n", __func__, + (int)sizeof(reply), reply); + return -EIO; + } + + size = min(reply[1], sizeof(reply) - 2); + for (i = 0; i + sizeof(*attr) <= size; i += sizeof(*attr)) { + attr = (struct steam_controller_attribute *)&reply[i + 2]; + if (attr->tag == ATTRIB_CONNECTION_INTERVAL_IN_US) { + steam->sensor_update_rate_us = get_unaligned_le32(&attr->value); + hid_dbg(steam->hdev, "Sensor update rate: %uus\n", + steam->sensor_update_rate_us); + } + } + + return 0; +} + +static int steam_get_conn_status(struct steam_device *steam) +{ + int ret = 0; + u8 cmd[] = {ID_DONGLE_GET_WIRELESS_STATE}; + u8 reply[3] = {}; + u8 report_id; + + if (steam->quirks & STEAM_QUIRK_IBEX) + report_id = REPORT_ID_FEATURES_DONGLE; + else + report_id = 0; + + guard(mutex)(&steam->report_mutex); + ret = steam_exchange_report_id(steam, cmd, sizeof(cmd), reply, sizeof(reply), report_id); + if (ret < 0) + return ret; + if (reply[0] != ID_DONGLE_GET_WIRELESS_STATE || reply[1] < 1) { + hid_err(steam->hdev, "%s: invalid reply (%*ph)\n", __func__, + (int)sizeof(reply), reply); + return -EIO; + } + + return reply[2]; } /* @@ -490,24 +752,41 @@ static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad, u16 duration, u16 interval, u16 count, u8 gain) { int ret; - u8 report[10] = {ID_TRIGGER_HAPTIC_PULSE, 8}; /* Left and right are swapped on this report for legacy reasons */ if (pad < STEAM_PAD_BOTH) pad ^= 1; - report[2] = pad; - report[3] = duration & 0xFF; - report[4] = duration >> 8; - report[5] = interval & 0xFF; - report[6] = interval >> 8; - report[7] = count & 0xFF; - report[8] = count >> 8; - report[9] = gain; + if (steam->quirks & STEAM_QUIRK_IBEX) { + struct steam_ibex_output_report *report = + kzalloc(sizeof(struct steam_ibex_output_report), GFP_KERNEL); + + if (!report) + return -ENOMEM; + + report->id = REPORT_ID_HAPTIC_PULSE; + report->pulse.side = pad; + put_unaligned_le16(duration, &report->pulse.on_us); + put_unaligned_le16(interval, &report->pulse.off_us); + put_unaligned_le16(count, &report->pulse.repeat_count); + + ret = hid_hw_output_report(steam->hdev, (u8 *)report, 8); + + kfree(report); + } else { + u8 report[10] = {ID_TRIGGER_HAPTIC_PULSE, 8, pad}; + + report[3] = duration & 0xFF; + report[4] = duration >> 8; + report[5] = interval & 0xFF; + report[6] = interval >> 8; + report[7] = count & 0xFF; + report[8] = count >> 8; + report[9] = gain; + + ret = steam_send_report(steam, report, 10); + } - mutex_lock(&steam->report_mutex); - ret = steam_send_report(steam, report, sizeof(report)); - mutex_unlock(&steam->report_mutex); return ret; } @@ -516,20 +795,38 @@ static inline int steam_haptic_rumble(struct steam_device *steam, u8 left_gain, u8 right_gain) { int ret; - u8 report[11] = {ID_TRIGGER_RUMBLE_CMD, 9}; - report[3] = intensity & 0xFF; - report[4] = intensity >> 8; - report[5] = left_speed & 0xFF; - report[6] = left_speed >> 8; - report[7] = right_speed & 0xFF; - report[8] = right_speed >> 8; - report[9] = left_gain; - report[10] = right_gain; + if (steam->quirks & STEAM_QUIRK_IBEX) { + struct steam_ibex_output_report *report = + kzalloc(sizeof(struct steam_ibex_output_report), GFP_KERNEL); - mutex_lock(&steam->report_mutex); - ret = steam_send_report(steam, report, sizeof(report)); - mutex_unlock(&steam->report_mutex); + if (!report) + return -ENOMEM; + + report->id = REPORT_ID_HAPTIC_RUMBLE; + put_unaligned_le16(intensity, &report->rumble.intensity); + put_unaligned_le16(left_speed, &report->rumble.left.speed); + report->rumble.left.gain = left_gain; + put_unaligned_le16(right_speed, &report->rumble.right.speed); + report->rumble.right.gain = right_gain; + + ret = hid_hw_output_report(steam->hdev, (u8 *)report, 10); + + kfree(report); + } else { + u8 report[11] = {ID_TRIGGER_RUMBLE_CMD, 9}; + + report[3] = intensity & 0xFF; + report[4] = intensity >> 8; + report[5] = left_speed & 0xFF; + report[6] = left_speed >> 8; + report[7] = right_speed & 0xFF; + report[8] = right_speed >> 8; + report[9] = left_gain; + report[10] = right_gain; + + ret = steam_send_report(steam, report, sizeof(report)); + } return ret; } @@ -537,8 +834,26 @@ static void steam_haptic_rumble_cb(struct work_struct *work) { struct steam_device *steam = container_of(work, struct steam_device, rumble_work); + + mutex_lock(&steam->report_mutex); steam_haptic_rumble(steam, 0, steam->rumble_left, steam->rumble_right, 2, 0); + mutex_unlock(&steam->report_mutex); +} + +static void steam_coalesce_rumble_cb(struct work_struct *work) +{ + struct steam_device *steam = container_of(to_delayed_work(work), + struct steam_device, + coalesce_rumble_work); + + mutex_lock(&steam->report_mutex); + steam_haptic_rumble(steam, 0, steam->rumble_left, + steam->rumble_right, 2, 0); + mutex_unlock(&steam->report_mutex); + + if (steam->rumble_left || steam->rumble_right) + schedule_delayed_work(&steam->coalesce_rumble_work, HZ / 20); } #ifdef CONFIG_STEAM_FF @@ -550,6 +865,14 @@ static int steam_play_effect(struct input_dev *dev, void *data, steam->rumble_left = effect->u.rumble.strong_magnitude; steam->rumble_right = effect->u.rumble.weak_magnitude; + /* + * The interface gets somewhat overloaded when too many rumble + * packets are sent in a row, so Steam throttles it to 20 Hz + */ + if (delayed_work_pending(&steam->coalesce_rumble_work)) + return 0; + + schedule_delayed_work(&steam->coalesce_rumble_work, HZ / 20); return schedule_work(&steam->rumble_work); } #endif @@ -559,7 +882,6 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable) if (steam->gamepad_mode) enable = false; - mutex_lock(&steam->report_mutex); if (enable) { /* enable esc, enter, cursors */ steam_send_report_byte(steam, ID_SET_DEFAULT_DIGITAL_MAPPINGS); @@ -569,40 +891,38 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable) /* disable esc, enter, cursor */ steam_send_report_byte(steam, ID_CLEAR_DIGITAL_MAPPINGS); - if (steam->quirks & STEAM_QUIRK_DECK) { + if (steam->quirks & (STEAM_QUIRK_DECK | STEAM_QUIRK_IBEX)) steam_write_settings(steam, - SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */ - SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */ - SETTING_LEFT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable haptic click */ - SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable haptic click */ - SETTING_STEAM_WATCHDOG_ENABLE, 0, /* disable watchdog that tests if Steam is active */ + /* disable lizard mode */ + SETTING_LIZARD_MODE, 0, + /* disable watchdog that tests if Steam is active */ + SETTING_STEAM_WATCHDOG_ENABLE, 0, 0); - } else { + else steam_write_settings(steam, SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */ SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */ 0); - } } - mutex_unlock(&steam->report_mutex); } static int steam_input_open(struct input_dev *dev) { struct steam_device *steam = input_get_drvdata(dev); unsigned long flags; - bool set_lizard_mode; + bool client_opened; /* * Disabling lizard mode automatically is only done on the Steam * Controller. On the Steam Deck, this is toggled manually by holding * the options button instead, handled by steam_mode_switch_cb. */ - if (!(steam->quirks & STEAM_QUIRK_DECK)) { + if (!(steam->quirks & (STEAM_QUIRK_DECK | STEAM_QUIRK_IBEX))) { spin_lock_irqsave(&steam->lock, flags); - set_lizard_mode = !steam->client_opened && lizard_mode; + client_opened = steam->client_opened; spin_unlock_irqrestore(&steam->lock, flags); - if (set_lizard_mode) + guard(mutex)(&steam->report_mutex); + if (!client_opened && lizard_mode) steam_set_lizard_mode(steam, false); } @@ -613,17 +933,48 @@ static void steam_input_close(struct input_dev *dev) { struct steam_device *steam = input_get_drvdata(dev); unsigned long flags; - bool set_lizard_mode; + bool client_opened; - if (!(steam->quirks & STEAM_QUIRK_DECK)) { + if (!(steam->quirks & (STEAM_QUIRK_DECK | STEAM_QUIRK_IBEX))) { spin_lock_irqsave(&steam->lock, flags); - set_lizard_mode = !steam->client_opened && lizard_mode; + client_opened = steam->client_opened; spin_unlock_irqrestore(&steam->lock, flags); - if (set_lizard_mode) + guard(mutex)(&steam->report_mutex); + if (!client_opened && lizard_mode) steam_set_lizard_mode(steam, true); } } +static int steam_sensor_open(struct input_dev *dev) +{ + struct steam_device *steam = input_get_drvdata(dev); + + scoped_guard(spinlock_irqsave, &steam->lock) { + if (steam->client_opened) + return 0; + } + + guard(mutex)(&steam->report_mutex); + steam_write_settings(steam, SETTING_IMU_MODE, + SETTING_IMU_MODE_SEND_RAW_ACCEL | SETTING_IMU_MODE_SEND_RAW_GYRO, + 0); + + return 0; +} + +static void steam_sensor_close(struct input_dev *dev) +{ + struct steam_device *steam = input_get_drvdata(dev); + + scoped_guard(spinlock_irqsave, &steam->lock) { + if (steam->client_opened) + return; + } + + guard(mutex)(&steam->report_mutex); + steam_write_settings(steam, SETTING_IMU_MODE, 0, 0); +} + static enum power_supply_property steam_battery_props[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_SCOPE, @@ -637,25 +988,40 @@ static int steam_battery_get_property(struct power_supply *psy, { struct steam_device *steam = power_supply_get_drvdata(psy); unsigned long flags; - s16 volts; + u16 volts; + u16 curr; + u16 temp; u8 batt; + u8 status; int ret = 0; spin_lock_irqsave(&steam->lock, flags); - volts = steam->voltage; + volts = steam->battery_voltage; + curr = steam->battery_current; batt = steam->battery_charge; + temp = steam->battery_temp; + status = steam->battery_status; spin_unlock_irqrestore(&steam->lock, flags); switch (psp) { case POWER_SUPPLY_PROP_PRESENT: val->intval = 1; break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = status; + break; case POWER_SUPPLY_PROP_SCOPE: val->intval = POWER_SUPPLY_SCOPE_DEVICE; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = volts * 1000; /* mV -> uV */ break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = curr * 1000; /* mA -> uA */ + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = temp / 100; /* thousandths °C -> tenths °C */ + break; case POWER_SUPPLY_PROP_CAPACITY: val->intval = batt; break; @@ -666,6 +1032,16 @@ static int steam_battery_get_property(struct power_supply *psy, return ret; } +static enum power_supply_property steam_ibex_battery_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, +}; + static int steam_battery_register(struct steam_device *steam) { struct power_supply *battery; @@ -674,25 +1050,42 @@ static int steam_battery_register(struct steam_device *steam) int ret; steam->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY; - steam->battery_desc.properties = steam_battery_props; - steam->battery_desc.num_properties = ARRAY_SIZE(steam_battery_props); + if (steam->quirks & STEAM_QUIRK_IBEX) { + steam->battery_desc.properties = steam_ibex_battery_props; + steam->battery_desc.num_properties = ARRAY_SIZE(steam_ibex_battery_props); + /* + * Ibex needs a shorter name as it has a temperature and the + * thermal zone name length limit is 20 characters. It's more + * ambiguous sounding, so let's only use it when needed. + */ + steam->battery_desc.name = devm_kasprintf(&steam->hdev->dev, + GFP_KERNEL, "steam-%s", + steam->serial_no); + } else { + steam->battery_desc.properties = steam_battery_props; + steam->battery_desc.num_properties = ARRAY_SIZE(steam_battery_props); + steam->battery_desc.name = devm_kasprintf(&steam->hdev->dev, + GFP_KERNEL, "steam-controller-%s-battery", + steam->serial_no); + } steam->battery_desc.get_property = steam_battery_get_property; - steam->battery_desc.name = devm_kasprintf(&steam->hdev->dev, - GFP_KERNEL, "steam-controller-%s-battery", - steam->serial_no); if (!steam->battery_desc.name) return -ENOMEM; /* avoid the warning of 0% battery while waiting for the first info */ spin_lock_irqsave(&steam->lock, flags); - steam->voltage = 3000; + steam->battery_voltage = 3000; steam->battery_charge = 100; + steam->battery_current = 0; + steam->battery_temp = 20000; + steam->battery_status = POWER_SUPPLY_STATUS_UNKNOWN; spin_unlock_irqrestore(&steam->lock, flags); battery = power_supply_register(&steam->hdev->dev, &steam->battery_desc, &battery_cfg); if (IS_ERR(battery)) { ret = PTR_ERR(battery); + devm_kfree(&steam->hdev->dev, steam->battery_desc.name); hid_err(steam->hdev, "%s:power_supply_register failed with error %d\n", __func__, ret); @@ -757,7 +1150,7 @@ static int steam_input_register(struct steam_device *steam) input_set_capability(input, EV_KEY, BTN_THUMB2); input_set_capability(input, EV_KEY, BTN_GRIPL); input_set_capability(input, EV_KEY, BTN_GRIPR); - if (steam->quirks & STEAM_QUIRK_DECK) { + if (steam->quirks & (STEAM_QUIRK_DECK | STEAM_QUIRK_IBEX)) { input_set_capability(input, EV_KEY, BTN_BASE); input_set_capability(input, EV_KEY, BTN_GRIPL2); input_set_capability(input, EV_KEY, BTN_GRIPR2); @@ -771,7 +1164,7 @@ static int steam_input_register(struct steam_device *steam) input_set_abs_params(input, ABS_HAT0Y, -32767, 32767, STEAM_PAD_FUZZ, 0); - if (steam->quirks & STEAM_QUIRK_DECK) { + if (steam->quirks & (STEAM_QUIRK_DECK | STEAM_QUIRK_IBEX)) { input_set_abs_params(input, ABS_HAT2Y, 0, 32767, 0, 0); input_set_abs_params(input, ABS_HAT2X, 0, 32767, 0, 0); @@ -811,7 +1204,7 @@ static int steam_input_register(struct steam_device *steam) input_abs_set_res(input, ABS_HAT0Y, STEAM_PAD_RESOLUTION); #ifdef CONFIG_STEAM_FF - if (steam->quirks & STEAM_QUIRK_DECK) { + if (steam->quirks & (STEAM_QUIRK_DECK | STEAM_QUIRK_IBEX)) { input_set_capability(input, EV_FF, FF_RUMBLE); ret = input_ff_create_memless(input, NULL, steam_play_effect); if (ret) @@ -836,9 +1229,7 @@ static int steam_sensors_register(struct steam_device *steam) struct hid_device *hdev = steam->hdev; struct input_dev *sensors; int ret; - - if (!(steam->quirks & STEAM_QUIRK_DECK)) - return 0; + bool needs_open_close; rcu_read_lock(); sensors = rcu_dereference(steam->sensors); @@ -855,7 +1246,33 @@ static int steam_sensors_register(struct steam_device *steam) input_set_drvdata(sensors, steam); sensors->dev.parent = &hdev->dev; - sensors->name = "Steam Deck Motion Sensors"; + /* + * The open/close calls are needed in these specific cases: + * + * - Steam Controller (2015): Always + * - Steam Deck: Never + * - Steam Controller (2026): Only when wireless/BLE + */ + if (steam->quirks & STEAM_QUIRK_DECK) + needs_open_close = false; + else if (steam->quirks & (STEAM_QUIRK_WIRELESS | STEAM_QUIRK_BLE)) + /* Both wireless Steam Controller setups */ + needs_open_close = true; + else if (steam->quirks & STEAM_QUIRK_IBEX) + /* Wired Steam Controller (2026) */ + needs_open_close = false; + else + /* Wired Steam Controller (2015) */ + needs_open_close = true; + + if (needs_open_close) { + sensors->open = steam_sensor_open; + sensors->close = steam_sensor_close; + } + + sensors->name = steam->quirks & STEAM_QUIRK_DECK ? + "Steam Deck Motion Sensors" : + "Steam Controller Motion Sensors"; sensors->phys = hdev->phys; sensors->uniq = steam->serial_no; sensors->id.bustype = hdev->bus; @@ -867,25 +1284,34 @@ static int steam_sensors_register(struct steam_device *steam) __set_bit(EV_MSC, sensors->evbit); __set_bit(MSC_TIMESTAMP, sensors->mscbit); - input_set_abs_params(sensors, ABS_X, -STEAM_DECK_ACCEL_RANGE, - STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0); - input_set_abs_params(sensors, ABS_Y, -STEAM_DECK_ACCEL_RANGE, - STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0); - input_set_abs_params(sensors, ABS_Z, -STEAM_DECK_ACCEL_RANGE, - STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0); - input_abs_set_res(sensors, ABS_X, STEAM_DECK_ACCEL_RES_PER_G); - input_abs_set_res(sensors, ABS_Y, STEAM_DECK_ACCEL_RES_PER_G); - input_abs_set_res(sensors, ABS_Z, STEAM_DECK_ACCEL_RES_PER_G); + if (steam->quirks & STEAM_QUIRK_DECK) { + input_set_abs_params(sensors, ABS_X, -STEAM_ACCEL_RANGE, + STEAM_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0); + input_set_abs_params(sensors, ABS_Y, -STEAM_ACCEL_RANGE, + STEAM_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0); + input_set_abs_params(sensors, ABS_Z, -STEAM_ACCEL_RANGE, + STEAM_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0); + } else { + input_set_abs_params(sensors, ABS_X, -STEAM_ACCEL_RANGE, + STEAM_ACCEL_RANGE, STEAM_ACCEL_FUZZ, 0); + input_set_abs_params(sensors, ABS_Y, -STEAM_ACCEL_RANGE, + STEAM_ACCEL_RANGE, STEAM_ACCEL_FUZZ, 0); + input_set_abs_params(sensors, ABS_Z, -STEAM_ACCEL_RANGE, + STEAM_ACCEL_RANGE, STEAM_ACCEL_FUZZ, 0); + } + input_abs_set_res(sensors, ABS_X, STEAM_ACCEL_RES_PER_G); + input_abs_set_res(sensors, ABS_Y, STEAM_ACCEL_RES_PER_G); + input_abs_set_res(sensors, ABS_Z, STEAM_ACCEL_RES_PER_G); - input_set_abs_params(sensors, ABS_RX, -STEAM_DECK_GYRO_RANGE, - STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0); - input_set_abs_params(sensors, ABS_RY, -STEAM_DECK_GYRO_RANGE, - STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0); - input_set_abs_params(sensors, ABS_RZ, -STEAM_DECK_GYRO_RANGE, - STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0); - input_abs_set_res(sensors, ABS_RX, STEAM_DECK_GYRO_RES_PER_DPS); - input_abs_set_res(sensors, ABS_RY, STEAM_DECK_GYRO_RES_PER_DPS); - input_abs_set_res(sensors, ABS_RZ, STEAM_DECK_GYRO_RES_PER_DPS); + input_set_abs_params(sensors, ABS_RX, -STEAM_GYRO_RANGE, + STEAM_GYRO_RANGE, STEAM_GYRO_FUZZ, 0); + input_set_abs_params(sensors, ABS_RY, -STEAM_GYRO_RANGE, + STEAM_GYRO_RANGE, STEAM_GYRO_FUZZ, 0); + input_set_abs_params(sensors, ABS_RZ, -STEAM_GYRO_RANGE, + STEAM_GYRO_RANGE, STEAM_GYRO_FUZZ, 0); + input_abs_set_res(sensors, ABS_RX, STEAM_GYRO_RES_PER_DPS); + input_abs_set_res(sensors, ABS_RY, STEAM_GYRO_RES_PER_DPS); + input_abs_set_res(sensors, ABS_RZ, STEAM_GYRO_RES_PER_DPS); ret = input_register_device(sensors); if (ret) @@ -916,9 +1342,6 @@ static void steam_sensors_unregister(struct steam_device *steam) { struct input_dev *sensors; - if (!(steam->quirks & STEAM_QUIRK_DECK)) - return; - rcu_read_lock(); sensors = rcu_dereference(steam->sensors); rcu_read_unlock(); @@ -943,97 +1366,119 @@ static void steam_battery_unregister(struct steam_device *steam) RCU_INIT_POINTER(steam->battery, NULL); synchronize_rcu(); power_supply_unregister(battery); + devm_kfree(&steam->hdev->dev, steam->battery_desc.name); } static int steam_register(struct steam_device *steam) { int ret; - unsigned long client_opened; - unsigned long flags; + mutex_lock(&steam->registration_mutex); /* * This function can be called several times in a row with the * wireless adaptor, without steam_unregister() between them, because * another client send a get_connection_status command, for example. - * The battery and serial number are set just once per device. */ - if (!steam->serial_no[0]) { - /* - * Unlikely, but getting the serial could fail, and it is not so - * important, so make up a serial number and go on. - */ - if (steam_get_serial(steam) < 0) - strscpy(steam->serial_no, "XXXXXXXXXX", - sizeof(steam->serial_no)); - - hid_info(steam->hdev, "Steam Controller '%s' connected", - steam->serial_no); - - /* ignore battery errors, we can live without it */ - if (steam->quirks & STEAM_QUIRK_WIRELESS) - steam_battery_register(steam); - - mutex_lock(&steam_devices_lock); - if (list_empty(&steam->list)) - list_add(&steam->list, &steam_devices); - mutex_unlock(&steam_devices_lock); + if (steam->registered) { + mutex_unlock(&steam->registration_mutex); + return 0; } - spin_lock_irqsave(&steam->lock, flags); - client_opened = steam->client_opened; - spin_unlock_irqrestore(&steam->lock, flags); + mutex_lock(&steam->report_mutex); + /* + * Unlikely, but getting the serial could fail, and it is not so + * important, so make up a serial number and go on. + */ + if (steam_get_serial(steam) < 0 || !steam->serial_no[0]) + strscpy(steam->serial_no, "XXXXXXXXXX", + sizeof(steam->serial_no)); - if (!client_opened) { - steam_set_lizard_mode(steam, lizard_mode); - ret = steam_input_register(steam); - if (ret != 0) - goto steam_register_input_fail; - ret = steam_sensors_register(steam); - if (ret != 0) - goto steam_register_sensors_fail; - } + ret = steam_get_attributes(steam); + if (ret < 0) + hid_err(steam->hdev, + "%s:steam_get_attributes failed with error %d\n", + __func__, ret); + + hid_info(steam->hdev, "Steam %s '%s' connected", + steam->quirks & STEAM_QUIRK_DECK ? "Deck" : "Controller", + steam->serial_no); + + steam_set_lizard_mode(steam, lizard_mode); + mutex_unlock(&steam->report_mutex); + + /* ignore battery errors, we can live without it */ + if (steam->quirks & (STEAM_QUIRK_WIRELESS | STEAM_QUIRK_IBEX)) + steam_battery_register(steam); + + ret = steam_input_register(steam); + if (ret != 0) + goto steam_register_input_fail; + ret = steam_sensors_register(steam); + if (ret != 0) + goto steam_register_sensors_fail; + + steam->registered = true; + mutex_unlock(&steam->registration_mutex); + mutex_lock(&steam_devices_lock); + if (list_empty(&steam->list)) + list_add(&steam->list, &steam_devices); + mutex_unlock(&steam_devices_lock); return 0; steam_register_sensors_fail: steam_input_unregister(steam); steam_register_input_fail: + steam_battery_unregister(steam); + mutex_unlock(&steam->registration_mutex); + cancel_work_sync(&steam->rumble_work); + cancel_delayed_work_sync(&steam->mode_switch); + cancel_delayed_work_sync(&steam->coalesce_rumble_work); return ret; } static void steam_unregister(struct steam_device *steam) { + if (!steam->registered) + return; + + hid_info(steam->hdev, "Steam %s '%s' disconnected", + steam->quirks & STEAM_QUIRK_DECK ? "Deck" : "Controller", + steam->serial_no); + mutex_lock(&steam->registration_mutex); + steam->registered = false; steam_battery_unregister(steam); steam_sensors_unregister(steam); steam_input_unregister(steam); - if (steam->serial_no[0]) { - hid_info(steam->hdev, "Steam Controller '%s' disconnected", - steam->serial_no); - mutex_lock(&steam_devices_lock); - list_del_init(&steam->list); - mutex_unlock(&steam_devices_lock); - steam->serial_no[0] = 0; - } + mutex_unlock(&steam->registration_mutex); + cancel_work_sync(&steam->rumble_work); + cancel_delayed_work_sync(&steam->mode_switch); + cancel_delayed_work_sync(&steam->coalesce_rumble_work); + mutex_lock(&steam_devices_lock); + list_del_init(&steam->list); + mutex_unlock(&steam_devices_lock); } static void steam_work_connect_cb(struct work_struct *work) { struct steam_device *steam = container_of(work, struct steam_device, work_connect); + unsigned long flags; bool connected; + bool opened; int ret; spin_lock_irqsave(&steam->lock, flags); + opened = steam->client_opened; connected = steam->connected; spin_unlock_irqrestore(&steam->lock, flags); - if (connected) { + if (connected && !opened) { ret = steam_register(steam); - if (ret) { + if (ret) hid_err(steam->hdev, "%s:steam_register failed with error %d\n", __func__, ret); - } } else { steam_unregister(steam); } @@ -1045,68 +1490,142 @@ static void steam_mode_switch_cb(struct work_struct *work) struct steam_device, mode_switch); unsigned long flags; bool client_opened; + bool gamepad_mode; + if (!lizard_mode) return; + spin_lock_irqsave(&steam->lock, flags); steam->gamepad_mode = !steam->gamepad_mode; - if (steam->gamepad_mode) + gamepad_mode = steam->gamepad_mode; + client_opened = steam->client_opened; + spin_unlock_irqrestore(&steam->lock, flags); + + hid_dbg(steam->hdev, "%s: switching gamepad mode to %i\n", __func__, gamepad_mode); + if (gamepad_mode) { + guard(mutex)(&steam->report_mutex); steam_set_lizard_mode(steam, false); - else { - spin_lock_irqsave(&steam->lock, flags); - client_opened = steam->client_opened; - spin_unlock_irqrestore(&steam->lock, flags); - if (!client_opened) + } else { + struct input_dev *input; + struct input_dev *sensors; + + if (!client_opened) { + guard(mutex)(&steam->report_mutex); steam_set_lizard_mode(steam, lizard_mode); + } + + /* + * Zero out inputs so it doesn't look like we're holding + * anything indefinitely. + */ + guard(spinlock_irqsave)(&steam->lock); + rcu_read_lock(); + input = rcu_dereference(steam->input); + if (likely(input)) { + input_report_key(input, BTN_TR2, 0); + input_report_key(input, BTN_TL2, 0); + input_report_key(input, BTN_TR, 0); + input_report_key(input, BTN_TL, 0); + input_report_key(input, BTN_Y, 0); + input_report_key(input, BTN_B, 0); + input_report_key(input, BTN_X, 0); + input_report_key(input, BTN_A, 0); + input_report_key(input, BTN_DPAD_UP, 0); + input_report_key(input, BTN_DPAD_RIGHT, 0); + input_report_key(input, BTN_DPAD_LEFT, 0); + input_report_key(input, BTN_DPAD_DOWN, 0); + input_report_key(input, BTN_SELECT, 0); + input_report_key(input, BTN_MODE, 0); + input_report_key(input, BTN_START, 0); + input_report_key(input, BTN_THUMBR, 0); + input_report_key(input, BTN_THUMBL, 0); + input_report_key(input, BTN_THUMB, 0); + input_report_key(input, BTN_THUMB2, 0); + input_report_key(input, BTN_GRIPL, 0); + input_report_key(input, BTN_GRIPR, 0); + + input_report_abs(input, ABS_X, 0); + input_report_abs(input, ABS_Y, 0); + input_report_abs(input, ABS_RX, 0); + input_report_abs(input, ABS_RY, 0); + input_report_abs(input, ABS_HAT0X, 0); + input_report_abs(input, ABS_HAT0Y, 0); + input_report_abs(input, ABS_HAT2Y, 0); + input_report_abs(input, ABS_HAT2X, 0); + + if (steam->quirks & (STEAM_QUIRK_DECK | STEAM_QUIRK_IBEX)) { + input_report_key(input, BTN_BASE, 0); + input_report_key(input, BTN_GRIPL2, 0); + input_report_key(input, BTN_GRIPR2, 0); + + input_report_abs(input, ABS_HAT1X, 0); + input_report_abs(input, ABS_HAT1Y, 0); + } + + input_sync(input); + } + sensors = rcu_dereference(steam->sensors); + if (likely(sensors)) { + /* Skip accelerometers since 0 isn't a neutral input */ + + input_report_abs(sensors, ABS_RX, 0); + input_report_abs(sensors, ABS_RY, 0); + input_report_abs(sensors, ABS_RZ, 0); + + input_sync(sensors); + } + rcu_read_unlock(); } + guard(mutex)(&steam->report_mutex); steam_haptic_pulse(steam, STEAM_PAD_RIGHT, 0x190, 0, 1, 0); - if (steam->gamepad_mode) { + if (gamepad_mode) { steam_haptic_pulse(steam, STEAM_PAD_LEFT, 0x14D, 0x14D, 0x2D, 0); } else { steam_haptic_pulse(steam, STEAM_PAD_LEFT, 0x1F4, 0x1F4, 0x1E, 0); } } -static void steam_work_unregister_cb(struct work_struct *work) -{ - struct steam_device *steam = container_of(work, struct steam_device, - unregister_work); - unsigned long flags; - bool connected; - bool opened; - - spin_lock_irqsave(&steam->lock, flags); - opened = steam->client_opened; - connected = steam->connected; - spin_unlock_irqrestore(&steam->lock, flags); - - if (connected) { - if (opened) { - steam_sensors_unregister(steam); - steam_input_unregister(steam); - } else { - steam_set_lizard_mode(steam, lizard_mode); - steam_input_register(steam); - steam_sensors_register(steam); - } - } -} - -static bool steam_is_valve_interface(struct hid_device *hdev) +static bool steam_is_valve_interface(struct hid_device *hdev, int quirks) { struct hid_report_enum *rep_enum; /* - * The wired device creates 3 interfaces: + * The 2015 wired controller creates 3 interfaces: * 0: emulated mouse. * 1: emulated keyboard. * 2: the real game pad. - * The wireless device creates 5 interfaces: + * The 2015 wireless adapter creates 5 interfaces: * 0: emulated keyboard. - * 1-4: slots where up to 4 real game pads will be connected to. - * We know which one is the real gamepad interface because they are the - * only ones with a feature report. + * 1-4: slots where up to 4 real controllers will be connected to. + * The Steam Deck creates 5 interfaces: + * 0: emulated mouse. + * 1: emulated keyboard. + * 2: the real game pad. + * 3-4: internal device comms (not HID). + * The 2026 wired controller creates 1 unified interface. + * The 2026 wireless puck creates 7 interfaces: + * 0-1: internal device comms (not HID). + * 2-5: slots where up to 4 real controllers will be connected to. + * 6: basic pogo pin interface. + * + * We know which one is the real controller interface for the pre-2026 + * controllers because they are the only ones with a feature report. + * + * The puck's pogo pin interface should be ignored as it's stripped + * down. It has one collection with usage page FF00 with usage ID 2. */ + if (quirks & STEAM_QUIRK_IBEX) { + /* There is only one BLE HID interface */ + if (quirks & STEAM_QUIRK_BLE) + return true; + + if (hdev->maxcollection < 1) + return true; + + return hdev->collection[0].usage != 0xFF000002; + } + rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; return !list_empty(&rep_enum->report_list); } @@ -1137,7 +1656,7 @@ static int steam_client_ll_open(struct hid_device *hdev) steam->client_opened++; spin_unlock_irqrestore(&steam->lock, flags); - schedule_work(&steam->unregister_work); + schedule_work(&steam->work_connect); return 0; } @@ -1152,7 +1671,7 @@ static void steam_client_ll_close(struct hid_device *hdev) steam->client_opened--; spin_unlock_irqrestore(&steam->lock, flags); - schedule_work(&steam->unregister_work); + schedule_work(&steam->work_connect); } static int steam_client_ll_raw_request(struct hid_device *hdev, @@ -1166,6 +1685,14 @@ static int steam_client_ll_raw_request(struct hid_device *hdev, report_type, reqtype); } +static int steam_client_ll_output_report(struct hid_device *hdev, + u8 *buf, size_t count) +{ + struct steam_device *steam = hdev->driver_data; + + return hid_hw_output_report(steam->hdev, buf, count); +} + static const struct hid_ll_driver steam_client_ll_driver = { .parse = steam_client_ll_parse, .start = steam_client_ll_start, @@ -1173,6 +1700,7 @@ static const struct hid_ll_driver steam_client_ll_driver = { .open = steam_client_ll_open, .close = steam_client_ll_close, .raw_request = steam_client_ll_raw_request, + .output_report = steam_client_ll_output_report, }; static struct hid_device *steam_create_client_hid(struct hid_device *hdev) @@ -1227,7 +1755,7 @@ static int steam_probe(struct hid_device *hdev, * The non-valve interfaces (mouse and keyboard emulation) are * connected without changes. */ - if (!steam_is_valve_interface(hdev)) + if (!steam_is_valve_interface(hdev, id->driver_data)) return hid_hw_start(hdev, HID_CONNECT_DEFAULT); steam = devm_kzalloc(&hdev->dev, sizeof(*steam), GFP_KERNEL); @@ -1238,13 +1766,18 @@ static int steam_probe(struct hid_device *hdev, hid_set_drvdata(hdev, steam); spin_lock_init(&steam->lock); mutex_init(&steam->report_mutex); + mutex_init(&steam->registration_mutex); steam->quirks = id->driver_data; INIT_WORK(&steam->work_connect, steam_work_connect_cb); INIT_DELAYED_WORK(&steam->mode_switch, steam_mode_switch_cb); INIT_LIST_HEAD(&steam->list); INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb); + INIT_DELAYED_WORK(&steam->coalesce_rumble_work, steam_coalesce_rumble_cb); steam->sensor_timestamp_us = 0; - INIT_WORK(&steam->unregister_work, steam_work_unregister_cb); + if (steam->quirks & STEAM_QUIRK_DECK) + steam->sensor_update_rate_us = 4000; + else + steam->sensor_update_rate_us = 9000; /* * With the real steam controller interface, do not connect hidraw. @@ -1262,14 +1795,21 @@ static int steam_probe(struct hid_device *hdev, goto err_hw_stop; } + steam->connected = true; + if (steam->quirks & STEAM_QUIRK_WIRELESS) { hid_info(hdev, "Steam wireless receiver connected"); /* If using a wireless adaptor ask for connection status */ steam->connected = false; - steam_request_conn_status(steam); - } else { - /* A wired connection is always present */ - steam->connected = true; + ret = steam_get_conn_status(steam); + if (ret < 0) + hid_err(hdev, + "%s:steam_get_conn_status failed with error %d\n", + __func__, ret); + else if (ret == WIRELESS_EVENT_CONNECT) + steam->connected = true; + } + if (steam->connected) { ret = steam_register(steam); if (ret) { hid_err(hdev, @@ -1305,7 +1845,7 @@ static int steam_probe(struct hid_device *hdev, cancel_work_sync(&steam->work_connect); cancel_delayed_work_sync(&steam->mode_switch); cancel_work_sync(&steam->rumble_work); - cancel_work_sync(&steam->unregister_work); + cancel_delayed_work_sync(&steam->coalesce_rumble_work); return ret; } @@ -1313,36 +1853,32 @@ static int steam_probe(struct hid_device *hdev, static void steam_remove(struct hid_device *hdev) { struct steam_device *steam = hid_get_drvdata(hdev); + unsigned long flags; if (!steam || hdev->group == HID_GROUP_STEAM) { hid_hw_stop(hdev); return; } + hid_hw_close(hdev); hid_destroy_device(steam->client_hdev); - cancel_delayed_work_sync(&steam->mode_switch); - cancel_work_sync(&steam->work_connect); - cancel_work_sync(&steam->rumble_work); - cancel_work_sync(&steam->unregister_work); - steam->client_hdev = NULL; + spin_lock_irqsave(&steam->lock, flags); steam->client_opened = 0; + spin_unlock_irqrestore(&steam->lock, flags); + cancel_work_sync(&steam->work_connect); if (steam->quirks & STEAM_QUIRK_WIRELESS) { hid_info(hdev, "Steam wireless receiver disconnected"); } - hid_hw_close(hdev); - hid_hw_stop(hdev); steam_unregister(steam); + hid_hw_stop(hdev); } static void steam_do_connect_event(struct steam_device *steam, bool connected) { - unsigned long flags; bool changed; - spin_lock_irqsave(&steam->lock, flags); changed = steam->connected != connected; steam->connected = connected; - spin_unlock_irqrestore(&steam->lock, flags); if (changed && schedule_work(&steam->work_connect) == 0) dbg_hid("%s: connected=%d event already queued\n", @@ -1354,13 +1890,45 @@ static void steam_do_connect_event(struct steam_device *steam, bool connected) * Clamp the values to 32767..-32767 so that the range is * symmetrical and can be negated safely. */ -static inline s16 steam_le16(u8 *data) +static inline s16 steam_le16(const u8 *data) { - s16 x = (s16) le16_to_cpup((__le16 *)data); + s16 x = (s16) get_unaligned_le16((const __le16 *)data); return x == -32768 ? -32767 : x; } +struct steam_button_mapping { + int code; + u8 byte; + u8 bit; +}; + +struct steam_axis_mapping { + int code; + s8 sign; + u8 byte; +}; + +static void steam_map_buttons(struct input_dev *input, + const struct steam_button_mapping *mappings, const u8 *data) +{ + const struct steam_button_mapping *mapping; + + for (mapping = mappings; mapping->code; mapping++) + input_report_key(input, mapping->code, + data[mapping->byte] & BIT(mapping->bit)); +} + +static void steam_map_axes(struct input_dev *input, + const struct steam_axis_mapping *mappings, const u8 *data) +{ + const struct steam_axis_mapping *mapping; + + for (mapping = mappings; mapping->sign; mapping++) + input_report_abs(input, mapping->code, + mapping->sign * steam_le16(&data[mapping->byte])); +} + /* * The size for this message payload is 60. * The known values are: @@ -1413,9 +1981,9 @@ static inline s16 steam_le16(u8 *data) * 9.1 | BTN_DPAD_RIGHT | left-pad right * 9.2 | BTN_DPAD_LEFT | left-pad left * 9.3 | BTN_DPAD_DOWN | left-pad down - * 9.4 | BTN_SELECT | menu left + * 9.4 | BTN_SELECT | view * 9.5 | BTN_MODE | steam logo - * 9.6 | BTN_START | menu right + * 9.6 | BTN_START | menu * 9.7 | BTN_GRIPL | left back lever * 10.0 | BTN_GRIPR | right back lever * 10.1 | -- | left-pad clicked @@ -1427,18 +1995,52 @@ static inline s16 steam_le16(u8 *data) * 10.7 | -- | lpad_and_joy */ +static const struct steam_button_mapping steam_controller_button_mappings[] = { + { BTN_TR2, 8, 0 }, + { BTN_TL2, 8, 1 }, + { BTN_TR, 8, 2 }, + { BTN_TL, 8, 3 }, + { BTN_Y, 8, 4 }, + { BTN_B, 8, 5 }, + { BTN_X, 8, 6 }, + { BTN_A, 8, 7 }, + { BTN_SELECT, 9, 4 }, + { BTN_MODE, 9, 5 }, + { BTN_START, 9, 6 }, + { BTN_GRIPL, 9, 7 }, + { BTN_GRIPR, 10, 0 }, + { BTN_THUMBR, 10, 2 }, + { BTN_THUMBL, 10, 6 }, + { BTN_THUMB2, 10, 4 }, + { BTN_DPAD_UP, 9, 0 }, + { BTN_DPAD_RIGHT, 9, 1 }, + { BTN_DPAD_LEFT, 9, 2 }, + { BTN_DPAD_DOWN, 9, 3 }, + { /* sentinel */ }, +}; + +static const struct steam_axis_mapping steam_controller_axis_mappings[] = { + { ABS_RX, 1, 20 }, + { ABS_RY, -1, 22 }, + { /* sentinel */ }, +}; + +static const struct steam_axis_mapping steam_controller_imu_mappings[] = { + { ABS_X, 1, 28 }, + { ABS_Z, -1, 30 }, + { ABS_Y, 1, 32 }, + { ABS_RX, 1, 34 }, + { ABS_RZ, 1, 36 }, + { ABS_RY, 1, 38 }, + { /* sentinel */ }, +}; + static void steam_do_input_event(struct steam_device *steam, struct input_dev *input, u8 *data) { - /* 24 bits of buttons */ - u8 b8, b9, b10; s16 x, y; bool lpad_touched, lpad_and_joy; - b8 = data[8]; - b9 = data[9]; - b10 = data[10]; - input_report_abs(input, ABS_HAT2Y, data[11]); input_report_abs(input, ABS_HAT2X, data[12]); @@ -1450,8 +2052,8 @@ static void steam_do_input_event(struct steam_device *steam, * joystick values. * (lpad_touched || lpad_and_joy) tells if the lpad is really touched. */ - lpad_touched = b10 & BIT(3); - lpad_and_joy = b10 & BIT(7); + lpad_touched = data[10] & BIT(3); + lpad_and_joy = data[10] & BIT(7); x = steam_le16(data + 16); y = -steam_le16(data + 18); @@ -1467,35 +2069,25 @@ static void steam_do_input_event(struct steam_device *steam, input_report_abs(input, ABS_HAT0X, 0); input_report_abs(input, ABS_HAT0Y, 0); } + input_report_key(input, BTN_THUMB, lpad_touched || lpad_and_joy); - input_report_abs(input, ABS_RX, steam_le16(data + 20)); - input_report_abs(input, ABS_RY, -steam_le16(data + 22)); - - input_event(input, EV_KEY, BTN_TR2, !!(b8 & BIT(0))); - input_event(input, EV_KEY, BTN_TL2, !!(b8 & BIT(1))); - input_event(input, EV_KEY, BTN_TR, !!(b8 & BIT(2))); - input_event(input, EV_KEY, BTN_TL, !!(b8 & BIT(3))); - input_event(input, EV_KEY, BTN_Y, !!(b8 & BIT(4))); - input_event(input, EV_KEY, BTN_B, !!(b8 & BIT(5))); - input_event(input, EV_KEY, BTN_X, !!(b8 & BIT(6))); - input_event(input, EV_KEY, BTN_A, !!(b8 & BIT(7))); - input_event(input, EV_KEY, BTN_SELECT, !!(b9 & BIT(4))); - input_event(input, EV_KEY, BTN_MODE, !!(b9 & BIT(5))); - input_event(input, EV_KEY, BTN_START, !!(b9 & BIT(6))); - input_event(input, EV_KEY, BTN_GRIPL, !!(b9 & BIT(7))); - input_event(input, EV_KEY, BTN_GRIPR, !!(b10 & BIT(0))); - input_event(input, EV_KEY, BTN_THUMBR, !!(b10 & BIT(2))); - input_event(input, EV_KEY, BTN_THUMBL, !!(b10 & BIT(6))); - input_event(input, EV_KEY, BTN_THUMB, lpad_touched || lpad_and_joy); - input_event(input, EV_KEY, BTN_THUMB2, !!(b10 & BIT(4))); - input_event(input, EV_KEY, BTN_DPAD_UP, !!(b9 & BIT(0))); - input_event(input, EV_KEY, BTN_DPAD_RIGHT, !!(b9 & BIT(1))); - input_event(input, EV_KEY, BTN_DPAD_LEFT, !!(b9 & BIT(2))); - input_event(input, EV_KEY, BTN_DPAD_DOWN, !!(b9 & BIT(3))); + steam_map_buttons(input, steam_controller_button_mappings, data); + steam_map_axes(input, steam_controller_axis_mappings, data); input_sync(input); } +static void steam_do_sensors_event(struct steam_device *steam, + struct input_dev *sensors, u8 *data) +{ + steam->sensor_timestamp_us += steam->sensor_update_rate_us; + + input_event(sensors, EV_MSC, MSC_TIMESTAMP, steam->sensor_timestamp_us); + steam_map_axes(sensors, steam_controller_imu_mappings, data); + + input_sync(sensors); +} + /* * The size for this message payload is 56. * The known values are: @@ -1541,9 +2133,9 @@ static void steam_do_input_event(struct steam_device *steam, * 9.1 | BTN_DPAD_RIGHT | left-pad right * 9.2 | BTN_DPAD_LEFT | left-pad left * 9.3 | BTN_DPAD_DOWN | left-pad down - * 9.4 | BTN_SELECT | menu left + * 9.4 | BTN_SELECT | view * 9.5 | BTN_MODE | steam logo - * 9.6 | BTN_START | menu right + * 9.6 | BTN_START | menu * 9.7 | BTN_GRIPL2 | left bottom grip button * 10.0 | BTN_GRIPR2 | right bottom grip button * 10.1 | BTN_THUMB | left pad pressed @@ -1594,23 +2186,68 @@ static void steam_do_input_event(struct steam_device *steam, * 15.6 | -- | unknown * 15.7 | -- | unknown */ + +static const struct steam_button_mapping steam_deck_button_mappings[] = { + { BTN_TR2, 8, 0 }, + { BTN_TL2, 8, 1 }, + { BTN_TR, 8, 2 }, + { BTN_TL, 8, 3 }, + { BTN_Y, 8, 4 }, + { BTN_B, 8, 5 }, + { BTN_X, 8, 6 }, + { BTN_A, 8, 7 }, + { BTN_SELECT, 9, 4 }, + { BTN_MODE, 9, 5 }, + { BTN_START, 9, 6 }, + { BTN_GRIPL2, 9, 7 }, + { BTN_GRIPR2, 10, 0 }, + { BTN_THUMBL, 10, 6 }, + { BTN_THUMBR, 11, 2 }, + { BTN_DPAD_UP, 9, 0 }, + { BTN_DPAD_RIGHT, 9, 1 }, + { BTN_DPAD_LEFT, 9, 2 }, + { BTN_DPAD_DOWN, 9, 3 }, + { BTN_THUMB, 10, 1 }, + { BTN_THUMB2, 10, 2 }, + { BTN_GRIPL, 13, 1 }, + { BTN_GRIPR, 13, 2 }, + { BTN_BASE, 14, 2 }, + { /* sentinel */ }, +}; + +static const struct steam_axis_mapping steam_deck_axis_mappings[] = { + { ABS_X, 1, 48 }, + { ABS_Y, -1, 50 }, + { ABS_RX, 1, 52 }, + { ABS_RY, -1, 54 }, + { ABS_HAT2Y, 1, 44 }, + { ABS_HAT2X, 1, 46 }, + { /* sentinel */ }, +}; + +static const struct steam_axis_mapping steam_deck_imu_mappings[] = { + { ABS_X, 1, 24 }, + { ABS_Z, -1, 26 }, + { ABS_Y, 1, 28 }, + { ABS_RX, 1, 30 }, + { ABS_RZ, -1, 32 }, + { ABS_RY, 1, 34 }, + { /* sentinel */ }, +}; + static void steam_do_deck_input_event(struct steam_device *steam, struct input_dev *input, u8 *data) { - u8 b8, b9, b10, b11, b13, b14; + bool start_pressed; bool lpad_touched, rpad_touched; - b8 = data[8]; - b9 = data[9]; - b10 = data[10]; - b11 = data[11]; - b13 = data[13]; - b14 = data[14]; + start_pressed = data[9] & BIT(6); - if (!(b9 & BIT(6)) && steam->did_mode_switch) { + if (!start_pressed && steam->did_mode_switch) { steam->did_mode_switch = false; cancel_delayed_work(&steam->mode_switch); - } else if (!steam->client_opened && (b9 & BIT(6)) && !steam->did_mode_switch) { + } else if (!steam->client_opened && start_pressed && !steam->did_mode_switch) { + hid_dbg(steam->hdev, "%s: doing mode switch\n", __func__); steam->did_mode_switch = true; schedule_delayed_work(&steam->mode_switch, 45 * HZ / 100); } @@ -1618,8 +2255,8 @@ static void steam_do_deck_input_event(struct steam_device *steam, if (!steam->gamepad_mode && lizard_mode) return; - lpad_touched = b10 & BIT(3); - rpad_touched = b10 & BIT(4); + lpad_touched = data[10] & BIT(3); + rpad_touched = data[10] & BIT(4); if (lpad_touched) { input_report_abs(input, ABS_HAT0X, steam_le16(data + 16)); @@ -1637,38 +2274,8 @@ static void steam_do_deck_input_event(struct steam_device *steam, input_report_abs(input, ABS_HAT1Y, 0); } - input_report_abs(input, ABS_X, steam_le16(data + 48)); - input_report_abs(input, ABS_Y, -steam_le16(data + 50)); - input_report_abs(input, ABS_RX, steam_le16(data + 52)); - input_report_abs(input, ABS_RY, -steam_le16(data + 54)); - - input_report_abs(input, ABS_HAT2Y, steam_le16(data + 44)); - input_report_abs(input, ABS_HAT2X, steam_le16(data + 46)); - - input_event(input, EV_KEY, BTN_TR2, !!(b8 & BIT(0))); - input_event(input, EV_KEY, BTN_TL2, !!(b8 & BIT(1))); - input_event(input, EV_KEY, BTN_TR, !!(b8 & BIT(2))); - input_event(input, EV_KEY, BTN_TL, !!(b8 & BIT(3))); - input_event(input, EV_KEY, BTN_Y, !!(b8 & BIT(4))); - input_event(input, EV_KEY, BTN_B, !!(b8 & BIT(5))); - input_event(input, EV_KEY, BTN_X, !!(b8 & BIT(6))); - input_event(input, EV_KEY, BTN_A, !!(b8 & BIT(7))); - input_event(input, EV_KEY, BTN_SELECT, !!(b9 & BIT(4))); - input_event(input, EV_KEY, BTN_MODE, !!(b9 & BIT(5))); - input_event(input, EV_KEY, BTN_START, !!(b9 & BIT(6))); - input_event(input, EV_KEY, BTN_GRIPL2, !!(b9 & BIT(7))); - input_event(input, EV_KEY, BTN_GRIPR2, !!(b10 & BIT(0))); - input_event(input, EV_KEY, BTN_THUMBL, !!(b10 & BIT(6))); - input_event(input, EV_KEY, BTN_THUMBR, !!(b11 & BIT(2))); - input_event(input, EV_KEY, BTN_DPAD_UP, !!(b9 & BIT(0))); - input_event(input, EV_KEY, BTN_DPAD_RIGHT, !!(b9 & BIT(1))); - input_event(input, EV_KEY, BTN_DPAD_LEFT, !!(b9 & BIT(2))); - input_event(input, EV_KEY, BTN_DPAD_DOWN, !!(b9 & BIT(3))); - input_event(input, EV_KEY, BTN_THUMB, !!(b10 & BIT(1))); - input_event(input, EV_KEY, BTN_THUMB2, !!(b10 & BIT(2))); - input_event(input, EV_KEY, BTN_GRIPL, !!(b13 & BIT(1))); - input_event(input, EV_KEY, BTN_GRIPR, !!(b13 & BIT(2))); - input_event(input, EV_KEY, BTN_BASE, !!(b14 & BIT(2))); + steam_map_buttons(input, steam_deck_button_mappings, data); + steam_map_axes(input, steam_deck_axis_mappings, data); input_sync(input); } @@ -1676,25 +2283,13 @@ static void steam_do_deck_input_event(struct steam_device *steam, static void steam_do_deck_sensors_event(struct steam_device *steam, struct input_dev *sensors, u8 *data) { - /* - * The deck input report is received every 4 ms on average, - * with a jitter of +/- 4 ms even though the USB descriptor claims - * that it uses 1 kHz. - * Since the HID report does not include a sensor timestamp, - * use a fixed increment here. - */ - steam->sensor_timestamp_us += 4000; + steam->sensor_timestamp_us += steam->sensor_update_rate_us; if (!steam->gamepad_mode && lizard_mode) return; input_event(sensors, EV_MSC, MSC_TIMESTAMP, steam->sensor_timestamp_us); - input_report_abs(sensors, ABS_X, steam_le16(data + 24)); - input_report_abs(sensors, ABS_Z, -steam_le16(data + 26)); - input_report_abs(sensors, ABS_Y, steam_le16(data + 28)); - input_report_abs(sensors, ABS_RX, steam_le16(data + 30)); - input_report_abs(sensors, ABS_RZ, -steam_le16(data + 32)); - input_report_abs(sensors, ABS_RY, steam_le16(data + 34)); + steam_map_axes(sensors, steam_deck_imu_mappings, data); input_sync(sensors); } @@ -1712,8 +2307,6 @@ static void steam_do_deck_sensors_event(struct steam_device *steam, static void steam_do_battery_event(struct steam_device *steam, struct power_supply *battery, u8 *data) { - unsigned long flags; - s16 volts = steam_le16(data + 12); u8 batt = data[14]; @@ -1721,15 +2314,219 @@ static void steam_do_battery_event(struct steam_device *steam, rcu_read_lock(); battery = rcu_dereference(steam->battery); if (likely(battery)) { - spin_lock_irqsave(&steam->lock, flags); - steam->voltage = volts; + steam->battery_voltage = volts; steam->battery_charge = batt; - spin_unlock_irqrestore(&steam->lock, flags); power_supply_changed(battery); } rcu_read_unlock(); } +/* + * The size for this message payload is 53 in REPORT_ID_INPUT and 45 in REPORT_ID_INPUT2. + * The values are: + * (* values only in REPORT_ID_INPUT) + * Offset| Type | Mapped to |Meaning + * -------+-------+-----------+-------------------------- + * 1 | u8 | -- | sequence number + * 2-5 | u32 | see below | buttons + * 6-7 | s16 | ABS_HAT2Y | left trigger (uncalibrated) + * 8-9 | s16 | ABS_HAT2X | right trigger (uncalibrated) + * 10-11 | s16 | ABS_X | left joystick X + * 12-13 | s16 | ABS_Y | left joystick Y + * 14-15 | s16 | ABS_RX | right joystick X + * 16-17 | s16 | ABS_RY | right joystick Y + * 18-19 | s16 | ABS_HAT0X | left-pad X value + * 20-21 | s16 | ABS_HAT0Y | left-pad Y value + * 22-23 | u16 | -- | left pad pressure + * 24-25 | s16 | ABS_HAT1X | right-pad X value + * 26-27 | s16 | ABS_HAT1Y | right-pad Y value + * 28-29 | u16 | -- | right pad pressure + * 30-33 | u32 | IMU MSC_TIMESTAMP | IMU timestamp + * 34-35 | s16 | IMU ABS_X | accelerometer X value + * 36-37 | s16 | IMU ABS_Z | accelerometer Y value + * 38-39 | s16 | IMU ABS_Y | accelerometer Z value + * 40-41 | s16 | IMU ABS_RX | gyro X value + * 42-43 | s16 | IMU ABS_RZ | gyro Y value + * 44-45 | s16 | IMU ABS_RY | gyro Z value + * 46-47 | s16 | -- | * quaternion W value + * 48-49 | s16 | -- | * quaternion X value + * 50-51 | s16 | -- | * quaternion Y value + * 52-53 | s16 | -- | * quaternion Z value + * + * The buttons are: + * Bit | Mapped to | Description + * ------+------------+-------------------------------- + * 2.0 | BTN_A | button A + * 2.1 | BTN_B | button B + * 2.2 | BTN_X | button X + * 2.3 | BTN_Y | button Y + * 2.4 | BTN_BASE | quick access button + * 2.5 | BTN_THUMBR | right joystick clicked + * 2.6 | BTN_START | menu + * 2.7 | BTN_GRIPR | right top grip button + * 3.0 | BTN_GRIPR2 | right bottom grip button + * 3.1 | BTN_TR | right shoulder + * 3.2 | BTN_DPAD_DOWN | left-pad down + * 3.3 | BTN_DPAD_RIGHT | left-pad right + * 3.4 | BTN_DPAD_LEFT | left-pad left + * 3.5 | BTN_DPAD_UP | left-pad up + * 3.6 | BTN_SELECT | view + * 3.7 | BTN_THUMBL | left joystick clicked + * 4.0 | BTN_MODE | steam logo + * 4.1 | BTN_GRIPL | left top grip button + * 4.2 | BTN_GRIPL2 | left bottom grip button + * 4.3 | BTN_TL | left shoulder + * 4.4 | -- | right joystick touched + * 4.5 | -- | right pad touched + * 4.6 | BTN_THUMB2 | right pad pressed + * 4.7 | BTN_TR2 | right trigger fully pressed + * 5.0 | -- | left joystick touched + * 5.1 | -- | left pad touched + * 5.2 | BTN_THUMB | left pad pressed + * 5.3 | BTN_TL2 | left trigger fully pressed + * 5.4 | -- | right grip touch + * 5.5 | -- | left grip touch + * 5.6 | -- | unmapped + * 5.7 | -- | unmapped + */ + +static const struct steam_button_mapping steam_ibex_button_mappings[] = { + { BTN_A, 2, 0 }, + { BTN_B, 2, 1 }, + { BTN_X, 2, 2 }, + { BTN_Y, 2, 3 }, + { BTN_BASE, 2, 4 }, + { BTN_THUMBR, 2, 5 }, + { BTN_START, 2, 6 }, + { BTN_GRIPR, 2, 7 }, + { BTN_GRIPR2, 3, 0 }, + { BTN_TR, 3, 1 }, + { BTN_DPAD_DOWN, 3, 2 }, + { BTN_DPAD_RIGHT, 3, 3 }, + { BTN_DPAD_LEFT, 3, 4 }, + { BTN_DPAD_UP, 3, 5 }, + { BTN_SELECT, 3, 6 }, + { BTN_THUMBL, 3, 7 }, + { BTN_MODE, 4, 0 }, + { BTN_GRIPL, 4, 1 }, + { BTN_GRIPL2, 4, 2 }, + { BTN_TL, 4, 3 }, + { BTN_THUMB2, 4, 6 }, + { BTN_TR2, 4, 7 }, + { BTN_THUMB, 5, 2 }, + { BTN_TL2, 5, 3 }, + { /* sentinel */ }, +}; + +static const struct steam_axis_mapping steam_ibex_axis_mappings[] = { + { ABS_X, 1, 10 }, + { ABS_Y, -1, 12 }, + { ABS_RX, 1, 14 }, + { ABS_RY, -1, 16 }, + { ABS_HAT2Y, 1, 6 }, + { ABS_HAT2X, 1, 8 }, + { /* sentinel */ }, +}; + +static const struct steam_axis_mapping steam_ibex_imu_mappings[] = { + { ABS_X, 1, 34 }, + { ABS_Z, -1, 36 }, + { ABS_Y, 1, 38 }, + { ABS_RX, 1, 40 }, + { ABS_RZ, -1, 42 }, + { ABS_RY, 1, 44 }, + { /* sentinel */ }, +}; + +static void steam_do_ibex_input_event(struct steam_device *steam, + struct input_dev *input, const u8 *data) +{ + bool start_pressed; + bool lpad_touched, rpad_touched; + + start_pressed = data[2] & BIT(6); + + if (!start_pressed && steam->did_mode_switch) { + steam->did_mode_switch = false; + cancel_delayed_work(&steam->mode_switch); + } else if (!steam->client_opened && start_pressed && !steam->did_mode_switch) { + steam->did_mode_switch = true; + schedule_delayed_work(&steam->mode_switch, 45 * HZ / 100); + } + + if (!steam->gamepad_mode && lizard_mode) + return; + + lpad_touched = data[5] & BIT(1); + rpad_touched = data[4] & BIT(5); + + if (lpad_touched) { + input_report_abs(input, ABS_HAT0X, steam_le16(data + 18)); + input_report_abs(input, ABS_HAT0Y, steam_le16(data + 20)); + } else { + input_report_abs(input, ABS_HAT0X, 0); + input_report_abs(input, ABS_HAT0Y, 0); + } + + if (rpad_touched) { + input_report_abs(input, ABS_HAT1X, steam_le16(data + 24)); + input_report_abs(input, ABS_HAT1Y, steam_le16(data + 26)); + } else { + input_report_abs(input, ABS_HAT1X, 0); + input_report_abs(input, ABS_HAT1Y, 0); + } + steam_map_buttons(input, steam_ibex_button_mappings, data); + steam_map_axes(input, steam_ibex_axis_mappings, data); + + input_sync(input); +} + +static void steam_do_ibex_sensors_event(struct steam_device *steam, + struct input_dev *sensors, const u8 *data) +{ + u32 timestamp; + + if (!steam->gamepad_mode && lizard_mode) + return; + + timestamp = (u32) get_unaligned_le32((__le32 *)&data[30]); + input_event(sensors, EV_MSC, MSC_TIMESTAMP, timestamp); + steam_map_axes(sensors, steam_ibex_imu_mappings, data); + + input_sync(sensors); +} + +static void steam_do_ibex_battery_event(struct steam_device *steam, + struct power_supply *battery, + const struct steam_ibex_battery_status *data) +{ + /* Creating the battery may have failed */ + guard(rcu)(); + battery = rcu_dereference(steam->battery); + if (!likely(battery)) + return; + + steam->battery_voltage = get_unaligned_le16(&data->battery_voltage); + steam->battery_current = get_unaligned_le16(&data->battery_current); + steam->battery_temp = get_unaligned_le16(&data->temperature); + steam->battery_charge = data->battery_level; + switch (data->charge_state) { + case CHARGE_STATE_CHARGING: + steam->battery_status = POWER_SUPPLY_STATUS_CHARGING; + break; + case CHARGE_STATE_DISCHARGING: + steam->battery_status = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case CHARGE_STATE_CHARGING_DONE: + steam->battery_status = POWER_SUPPLY_STATUS_FULL; + break; + default: + steam->battery_status = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + power_supply_changed(battery); +} + static int steam_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) @@ -1742,9 +2539,86 @@ static int steam_raw_event(struct hid_device *hdev, if (!steam) return 0; + guard(spinlock_irqsave)(&steam->lock); if (steam->client_opened) - hid_input_report(steam->client_hdev, HID_FEATURE_REPORT, - data, size, 0); + hid_input_report(steam->client_hdev, report->type, data, size, 0); + + /* Ibex uses a different report format */ + if (steam->quirks & STEAM_QUIRK_IBEX) { + if (report->type != HID_INPUT_REPORT) + return 0; + + switch (report->id) { + case REPORT_ID_INPUT: + if (size != 54) + return 0; + if (steam->client_opened) + return 0; + rcu_read_lock(); + input = rcu_dereference(steam->input); + if (likely(input)) { + steam_do_ibex_input_event(steam, input, data); + } else { + dbg_hid("%s: input data without connect event\n", + __func__); + steam_do_connect_event(steam, true); + } + sensors = rcu_dereference(steam->sensors); + if (likely(sensors)) + steam_do_ibex_sensors_event(steam, sensors, data); + rcu_read_unlock(); + break; + case REPORT_ID_INPUT2: + if (size != 46) + return 0; + if (steam->client_opened) + return 0; + rcu_read_lock(); + input = rcu_dereference(steam->input); + if (likely(input)) { + steam_do_ibex_input_event(steam, input, data); + } else { + dbg_hid("%s: input data without connect event\n", + __func__); + steam_do_connect_event(steam, true); + } + sensors = rcu_dereference(steam->sensors); + if (likely(sensors)) + steam_do_ibex_sensors_event(steam, sensors, data); + rcu_read_unlock(); + break; + case REPORT_ID_BATTERY: + if (size != 15) + return 0; + rcu_read_lock(); + battery = rcu_dereference(steam->battery); + if (likely(battery)) { + steam_do_ibex_battery_event(steam, battery, + (const struct steam_ibex_battery_status *)&data[1]); + } else { + dbg_hid("%s: battery data without connect event\n", + __func__); + steam_do_connect_event(steam, true); + } + rcu_read_unlock(); + break; + case REPORT_ID_WIRELESS_EVENT: + if (size != 2) + return 0; + switch (data[1]) { + case WIRELESS_EVENT_DISCONNECT: + steam_do_connect_event(steam, false); + break; + case WIRELESS_EVENT_CONNECT: + steam_do_connect_event(steam, true); + break; + } + break; + } + + return 0; + } + /* * All messages are size=64, all values little-endian. * The format is: @@ -1773,6 +2647,9 @@ static int steam_raw_event(struct hid_device *hdev, input = rcu_dereference(steam->input); if (likely(input)) steam_do_input_event(steam, input, data); + sensors = rcu_dereference(steam->sensors); + if (likely(sensors)) + steam_do_sensors_event(steam, sensors, data); rcu_read_unlock(); break; case ID_CONTROLLER_DECK_STATE: @@ -1826,6 +2703,8 @@ static int steam_param_set_lizard_mode(const char *val, { struct steam_device *steam; int ret; + bool client_opened; + unsigned long flags; ret = param_set_bool(val, kp); if (ret) @@ -1833,8 +2712,13 @@ static int steam_param_set_lizard_mode(const char *val, mutex_lock(&steam_devices_lock); list_for_each_entry(steam, &steam_devices, list) { - if (!steam->client_opened) + spin_lock_irqsave(&steam->lock, flags); + client_opened = steam->client_opened; + spin_unlock_irqrestore(&steam->lock, flags); + if (!client_opened) { + guard(mutex)(&steam->report_mutex); steam_set_lizard_mode(steam, lizard_mode); + } } mutex_unlock(&steam_devices_lock); return 0; @@ -1850,11 +2734,11 @@ MODULE_PARM_DESC(lizard_mode, "Enable mouse and keyboard emulation (lizard mode) when the gamepad is not in use"); static const struct hid_device_id steam_controllers[] = { - { /* Wired Steam Controller */ + { /* Wired Steam Controller (2015) */ HID_USB_DEVICE(USB_VENDOR_ID_VALVE, USB_DEVICE_ID_STEAM_CONTROLLER) }, - { /* Wireless Steam Controller */ + { /* Wireless Steam Controller (2015) */ HID_USB_DEVICE(USB_VENDOR_ID_VALVE, USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS), .driver_data = STEAM_QUIRK_WIRELESS @@ -1864,6 +2748,26 @@ static const struct hid_device_id steam_controllers[] = { USB_DEVICE_ID_STEAM_DECK), .driver_data = STEAM_QUIRK_DECK }, + { /* Steam Controller (2026) wired */ + HID_USB_DEVICE(USB_VENDOR_ID_VALVE, + USB_DEVICE_ID_STEAM_CONTROLLER_IBEX), + .driver_data = STEAM_QUIRK_IBEX + }, + { /* Steam Controller (2026) BLE */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_VALVE, + USB_DEVICE_ID_STEAM_CONTROLLER_IBEX_BLE), + .driver_data = STEAM_QUIRK_IBEX | STEAM_QUIRK_BLE + }, + { /* Steam Controller (2026) Puck */ + HID_USB_DEVICE(USB_VENDOR_ID_VALVE, + USB_DEVICE_ID_STEAM_CONTROLLER_PROTEUS), + .driver_data = STEAM_QUIRK_IBEX | STEAM_QUIRK_WIRELESS + }, + { /* Steam Controller (2026) Steam Machine internal receiver */ + HID_USB_DEVICE(USB_VENDOR_ID_VALVE, + USB_DEVICE_ID_STEAM_CONTROLLER_NEREID), + .driver_data = STEAM_QUIRK_IBEX | STEAM_QUIRK_WIRELESS + }, {} }; diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c new file mode 100644 index 000000000000..23fb0cebd72a --- /dev/null +++ b/drivers/hid/hid-steelseries-arctis.c @@ -0,0 +1,718 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Steelseries arctis headsets + * + * Copyright (c) 2023 Bastien Nocera + * Copyright (c) 2026 Sriman Achanta + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" + +#define SS_CAP_BATTERY BIT(0) + +struct steelseries_device; + +struct steelseries_device_info { + unsigned long capabilities; + + u8 sync_interface; + u8 async_interface; + + int (*request_status)(struct hid_device *hdev); + void (*parse_status)(struct steelseries_device *sd, u8 *data, int size); +}; + +struct steelseries_device { + struct kref refcnt; + + struct hid_device *hdev; + const struct steelseries_device_info *info; + + struct delayed_work status_work; + + struct power_supply_desc battery_desc; + struct power_supply *battery; + bool headset_connected; + u8 battery_capacity; + bool battery_charging; + + spinlock_t lock; + bool removed; +}; + +static void steelseries_device_release(struct kref *ref) +{ + struct steelseries_device *sd = + container_of(ref, struct steelseries_device, refcnt); + + kfree(sd); +} + +/* + * Headset report helpers + */ + +static int steelseries_send_report(struct hid_device *hdev, const u8 *data, + int len, enum hid_report_type type) +{ + u8 *buf; + int ret; + + buf = kmemdup(data, len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, data[0], buf, len, type, + HID_REQ_SET_REPORT); + kfree(buf); + + if (ret < 0) + return ret; + if (ret < len) + return -EIO; + + return 0; +} + +static inline int steelseries_send_output_report(struct hid_device *hdev, + const u8 *data, int len) +{ + return steelseries_send_report(hdev, data, len, HID_OUTPUT_REPORT); +} + +/* + * Headset status request functions + */ + +static int steelseries_arctis_1_request_status(struct hid_device *hdev) +{ + const u8 data[] = { 0x06, 0x12 }; + + return steelseries_send_output_report(hdev, data, sizeof(data)); +} + +static int steelseries_arctis_9_request_status(struct hid_device *hdev) +{ + const u8 data[] = { 0x00, 0x20 }; + + return steelseries_send_output_report(hdev, data, sizeof(data)); +} + +static int steelseries_arctis_nova_request_status(struct hid_device *hdev) +{ + const u8 data[] = { 0x00, 0xb0 }; + + return steelseries_send_output_report(hdev, data, sizeof(data)); +} + +/* + * Headset battery helpers + */ + +static int battery_capacity_to_level(int capacity) +{ + if (capacity >= 50) + return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + if (capacity >= 20) + return POWER_SUPPLY_CAPACITY_LEVEL_LOW; + return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; +} + +static u8 steelseries_map_capacity(u8 capacity, u8 min_in, u8 max_in) +{ + if (capacity >= max_in) + return 100; + if (capacity <= min_in) + return 0; + return (capacity - min_in) * 100 / (max_in - min_in); +} + +/* + * Headset status parse functions + */ + +static void steelseries_arctis_1_parse_status(struct steelseries_device *sd, + u8 *data, int size) +{ + /* Only the battery status report echoes the request header. */ + if (size < 8 || data[0] != 0x06 || data[1] != 0x12) + return; + + sd->headset_connected = (data[2] != 0x01); + sd->battery_capacity = data[3]; +} + +static void steelseries_arctis_9_parse_status(struct steelseries_device *sd, + u8 *data, int size) +{ + if (size < 5) + return; + + if (data[0] == 0xaa && data[1] == 0x01) { + sd->headset_connected = true; + sd->battery_charging = (data[4] == 0x01); + sd->battery_capacity = steelseries_map_capacity(data[3], 0x64, 0x9a); + } else { + /* Device off: 0x55 (no status) or 0x03 (stale status). */ + sd->headset_connected = false; + sd->battery_charging = false; + } +} + +static void steelseries_arctis_nova_parse_status(struct steelseries_device *sd, + u8 *data, int size) +{ + if (size < 2) + return; + + switch (data[0]) { + case 0xb0: + if (size < 4) + return; + sd->headset_connected = (data[1] == 0x03); + sd->battery_capacity = data[2]; + sd->battery_charging = (data[3] == 0x01); + break; + case 0xb7: + sd->battery_capacity = data[1]; + break; + case 0xb9: + sd->headset_connected = (data[1] == 0x03); + break; + case 0xbb: + sd->battery_charging = (data[1] == 0x01); + break; + } +} + +static void steelseries_arctis_nova_7_parse_status(struct steelseries_device *sd, + u8 *data, int size) +{ + if (size < 2) + return; + + switch (data[0]) { + case 0xb0: + if (size < 4) + return; + sd->headset_connected = (data[1] == 0x03); + sd->battery_capacity = steelseries_map_capacity(data[2], 0, 4); + sd->battery_charging = (data[3] == 0x01); + break; + case 0xb7: + sd->battery_capacity = steelseries_map_capacity(data[1], 0, 4); + break; + case 0xb9: + sd->headset_connected = (data[1] == 0x03); + break; + case 0xbb: + sd->battery_charging = (data[1] == 0x01); + break; + } +} + +/* + * Device info definitions + */ + +static const struct steelseries_device_info arctis_1_info = { + .sync_interface = 3, + .capabilities = SS_CAP_BATTERY, + .request_status = steelseries_arctis_1_request_status, + .parse_status = steelseries_arctis_1_parse_status, +}; + +static const struct steelseries_device_info arctis_9_info = { + .sync_interface = 0, + .capabilities = SS_CAP_BATTERY, + .request_status = steelseries_arctis_9_request_status, + .parse_status = steelseries_arctis_9_parse_status, +}; + +static const struct steelseries_device_info arctis_nova_info = { + .sync_interface = 3, + .async_interface = 5, + .capabilities = SS_CAP_BATTERY, + .request_status = steelseries_arctis_nova_request_status, + .parse_status = steelseries_arctis_nova_parse_status, +}; + +static const struct steelseries_device_info arctis_nova_7_info = { + .sync_interface = 3, + .async_interface = 5, + .capabilities = SS_CAP_BATTERY, + .request_status = steelseries_arctis_nova_request_status, + .parse_status = steelseries_arctis_nova_7_parse_status, +}; + +/* + * Headset wireless status and battery infrastructure + */ + +#define STEELSERIES_HEADSET_STATUS_TIMEOUT_MS 3000 + +static void +steelseries_headset_set_wireless_status(struct hid_device *hdev, + bool connected) +{ + struct usb_interface *intf; + + if (!hid_is_usb(hdev)) + return; + + intf = to_usb_interface(hdev->dev.parent); + usb_set_wireless_status(intf, connected ? + USB_WIRELESS_STATUS_CONNECTED : + USB_WIRELESS_STATUS_DISCONNECTED); +} + +#define STEELSERIES_PREFIX "SteelSeries " + +static int steelseries_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct steelseries_device *sd = power_supply_get_drvdata(psy); + size_t prefix_len; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = sd->hdev->name; + while ((prefix_len = str_has_prefix(val->strval, STEELSERIES_PREFIX))) + val->strval += prefix_len; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "SteelSeries"; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_STATUS: + if (!sd->headset_connected) + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + else if (sd->battery_charging) + val->intval = sd->battery_capacity >= 100 ? + POWER_SUPPLY_STATUS_FULL : + POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_DEVICE; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = sd->battery_capacity; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = battery_capacity_to_level(sd->battery_capacity); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property steelseries_battery_props[] = { + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, +}; + +/* + * Delayed work handlers for status polling + */ + +static void steelseries_status_timer_work_handler(struct work_struct *work) +{ + struct steelseries_device *sd = container_of( + work, struct steelseries_device, status_work.work); + unsigned long flags; + + sd->info->request_status(sd->hdev); + + spin_lock_irqsave(&sd->lock, flags); + /* Async devices push status events themselves; only poll once. */ + if (!sd->removed && !sd->info->async_interface) + schedule_delayed_work(&sd->status_work, + msecs_to_jiffies(STEELSERIES_HEADSET_STATUS_TIMEOUT_MS)); + spin_unlock_irqrestore(&sd->lock, flags); +} + +static int steelseries_battery_register(struct steelseries_device *sd) +{ + struct power_supply_config battery_cfg = { .drv_data = sd, }; + struct power_supply *battery; + int ret; + + sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY; + sd->battery_desc.properties = steelseries_battery_props; + sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_battery_props); + sd->battery_desc.get_property = steelseries_battery_get_property; + sd->battery_desc.use_for_apm = 0; + sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL, + "steelseries_headset_battery_%s", + sd->hdev->uniq[0] ? sd->hdev->uniq : + dev_name(&sd->hdev->dev)); + if (!sd->battery_desc.name) + return -ENOMEM; + + /* avoid the warning of 0% battery while waiting for the first info */ + sd->battery_capacity = 100; + sd->battery_charging = false; + sd->headset_connected = false; + steelseries_headset_set_wireless_status(sd->hdev, false); + + battery = power_supply_register(&sd->hdev->dev, + &sd->battery_desc, &battery_cfg); + if (IS_ERR(battery)) { + ret = PTR_ERR(battery); + hid_err(sd->hdev, + "%s:power_supply_register failed with error %d\n", + __func__, ret); + return ret; + } + power_supply_powers(battery, &sd->hdev->dev); + + /* Assign on success only, so a concurrent raw_event never sees an ERR_PTR. */ + sd->battery = battery; + + return 0; +} + +static struct hid_driver steelseries_arctis_driver; + +static struct steelseries_device * +steelseries_get_sibling_sd(struct hid_device *hdev, int interface_num) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct usb_interface *sibling_intf; + struct hid_device *sibling_hdev; + struct steelseries_device *sd = NULL; + + sibling_intf = usb_ifnum_to_if(usb_dev, interface_num); + if (!sibling_intf) + return NULL; + + /* + * usb_get_intfdata() only yields a hid_device when usbhid is bound; + * gate on the descriptor class so a non-HID sibling (e.g. a crafted + * device exposing storage or audio here) is never treated as one. + */ + if (sibling_intf->cur_altsetting->desc.bInterfaceClass != USB_INTERFACE_CLASS_HID) + return NULL; + + /* + * Take the sibling's device lock across the intfdata read and the + * kref_get so a concurrent unbind cannot free the hid_device underneath + * us; usbhid leaves intfdata dangling on disconnect, so dev.driver is + * the reliable "still bound" test under this lock. Use device_trylock() + * to stay off the lockdep chain of the interface being probed and let + * the caller retry via -EPROBE_DEFER if the sibling is momentarily busy. + */ + if (!device_trylock(&sibling_intf->dev)) + return NULL; + if (sibling_intf->dev.driver) { + sibling_hdev = usb_get_intfdata(sibling_intf); + if (sibling_hdev && + sibling_hdev->driver == &steelseries_arctis_driver) { + sd = hid_get_drvdata(sibling_hdev); + if (sd) + kref_get(&sd->refcnt); + } + } + device_unlock(&sibling_intf->dev); + + return sd; +} + +static int steelseries_arctis_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + const struct steelseries_device_info *info = + (const struct steelseries_device_info *)id->driver_data; + struct steelseries_device *sd; + struct usb_interface *intf; + u8 interface_num; + int ret; + + if (hid_is_usb(hdev)) { + intf = to_usb_interface(hdev->dev.parent); + interface_num = intf->cur_altsetting->desc.bInterfaceNumber; + } else { + return -ENODEV; + } + + ret = hid_parse(hdev); + if (ret) + return ret; + + /* Let hid-generic handle non-vendor or unknown interfaces */ + if (interface_num != info->sync_interface && + (!info->async_interface || interface_num != info->async_interface)) + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); + + if (interface_num == info->sync_interface) { + sd = kzalloc_obj(*sd, GFP_KERNEL); + if (!sd) + return -ENOMEM; + + kref_init(&sd->refcnt); + sd->hdev = hdev; + sd->info = info; + spin_lock_init(&sd->lock); + INIT_DELAYED_WORK(&sd->status_work, steelseries_status_timer_work_handler); + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) + goto err_free; + + ret = hid_hw_open(hdev); + if (ret) + goto err_stop; + + if (info->capabilities & SS_CAP_BATTERY) { + ret = steelseries_battery_register(sd); + if (ret < 0) + hid_warn(hdev, "Failed to register battery: %d\n", ret); + } + + /* + * Publish drvdata only once fully initialised: the async sibling + * attaches by reading it, so it must never observe a half-built or + * failed instance. A failed probe never gets here, so the error + * path below has nothing to unpublish. + */ + hid_set_drvdata(hdev, sd); + schedule_delayed_work(&sd->status_work, msecs_to_jiffies(100)); + + return 0; + } + + /* + * The async interface shares the steelseries_device created by the + * sync interface. Defer until the sync interface has probed and + * published its drvdata. + */ + if (info->async_interface && interface_num == info->async_interface) { + sd = steelseries_get_sibling_sd(hdev, info->sync_interface); + if (!sd) + return -EPROBE_DEFER; + + hid_set_drvdata(hdev, sd); + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + kref_put(&sd->refcnt, steelseries_device_release); + return ret; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_hw_stop(hdev); + kref_put(&sd->refcnt, steelseries_device_release); + return ret; + } + return 0; + } + + return -ENODEV; + +err_stop: + hid_hw_stop(hdev); +err_free: + /* drvdata is unpublished until full success, so no sibling can hold sd. */ + kref_put(&sd->refcnt, steelseries_device_release); + return ret; +} + +static void steelseries_arctis_remove(struct hid_device *hdev) +{ + struct steelseries_device *sd; + struct power_supply *battery; + unsigned long flags; + struct usb_interface *intf; + u8 interface_num; + + if (hid_is_usb(hdev)) { + intf = to_usb_interface(hdev->dev.parent); + interface_num = intf->cur_altsetting->desc.bInterfaceNumber; + } else { + return; + } + + sd = hid_get_drvdata(hdev); + + if (!sd) { + hid_hw_stop(hdev); + return; + } + + if (interface_num == sd->info->sync_interface) { + spin_lock_irqsave(&sd->lock, flags); + sd->removed = true; + battery = sd->battery; + sd->battery = NULL; + spin_unlock_irqrestore(&sd->lock, flags); + + cancel_delayed_work_sync(&sd->status_work); + + if (battery) + power_supply_unregister(battery); + } + + hid_hw_close(hdev); + hid_hw_stop(hdev); + + kref_put(&sd->refcnt, steelseries_device_release); +} + +static int steelseries_arctis_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + struct steelseries_device *sd = hid_get_drvdata(hdev); + u8 old_capacity; + bool old_connected; + bool old_charging; + bool is_async_interface; + unsigned long flags; + + if (!sd) + return 0; + + is_async_interface = (hdev != sd->hdev); + + spin_lock_irqsave(&sd->lock, flags); + + if (sd->removed) { + spin_unlock_irqrestore(&sd->lock, flags); + return 0; + } + + old_capacity = sd->battery_capacity; + old_connected = sd->headset_connected; + old_charging = sd->battery_charging; + + sd->info->parse_status(sd, data, size); + + if (sd->headset_connected != old_connected) { + hid_dbg(hdev, + "Connected status changed from %sconnected to %sconnected\n", + old_connected ? "" : "not ", + sd->headset_connected ? "" : "not "); + + if (sd->headset_connected && !old_connected && + sd->info->async_interface && is_async_interface) + schedule_delayed_work(&sd->status_work, 0); + + if (sd->battery) { + steelseries_headset_set_wireless_status(sd->hdev, + sd->headset_connected); + power_supply_changed(sd->battery); + } + } + + if (sd->battery_capacity != old_capacity) { + hid_dbg(hdev, "Battery capacity changed from %d%% to %d%%\n", + old_capacity, sd->battery_capacity); + if (sd->battery) + power_supply_changed(sd->battery); + } + + if (sd->battery_charging != old_charging) { + hid_dbg(hdev, + "Battery charging status changed from %scharging to %scharging\n", + old_charging ? "" : "not ", + sd->battery_charging ? "" : "not "); + if (sd->battery) + power_supply_changed(sd->battery); + } + + spin_unlock_irqrestore(&sd->lock, flags); + + return 0; +} + +static const struct hid_device_id steelseries_arctis_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X), + .driver_data = (unsigned long)&arctis_1_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_9), + .driver_data = (unsigned long)&arctis_9_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X), + .driver_data = (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7), + .driver_data = (unsigned long)&arctis_nova_7_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X), + .driver_data = (unsigned long)&arctis_nova_7_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2), + .driver_data = (unsigned long)&arctis_nova_7_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO), + .driver_data = (unsigned long)&arctis_nova_7_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW), + .driver_data = (unsigned long)&arctis_nova_7_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026), + .driver_data = (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026), + .driver_data = (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026), + .driver_data = (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026), + .driver_data = (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2), + .driver_data = (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2), + .driver_data = (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2), + .driver_data = (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3), + .driver_data = (unsigned long)&arctis_nova_info }, + {} +}; +MODULE_DEVICE_TABLE(hid, steelseries_arctis_devices); + +static struct hid_driver steelseries_arctis_driver = { + .name = "hid-steelseries-arctis", + .id_table = steelseries_arctis_devices, + .probe = steelseries_arctis_probe, + .remove = steelseries_arctis_remove, + .raw_event = steelseries_arctis_raw_event, +}; + +module_hid_driver(steelseries_arctis_driver); +MODULE_DESCRIPTION("HID driver for Steelseries arctis headsets"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Christian Mayer "); +MODULE_AUTHOR("Bastien Nocera "); +MODULE_AUTHOR("Sriman Achanta "); diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c index f98435631aa1..7292da0313e1 100644 --- a/drivers/hid/hid-steelseries.c +++ b/drivers/hid/hid-steelseries.c @@ -3,37 +3,45 @@ * HID driver for Steelseries devices * * Copyright (c) 2013 Simon Wood - * Copyright (c) 2023 Bastien Nocera */ /* */ #include +#include #include #include #include #include +#include +#include #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(1) + +#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; unsigned long quirks; - struct delayed_work battery_work; - spinlock_t lock; - bool removed; - - struct power_supply_desc battery_desc; - struct power_supply *battery; - uint8_t battery_capacity; - bool headset_connected; - bool battery_charging; +#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) || \ @@ -340,193 +348,221 @@ static int steelseries_srws1_probe(struct hid_device *hdev, } #endif -#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000 - -#define ARCTIS_1_BATTERY_RESPONSE_LEN 8 -#define ARCTIS_9_BATTERY_RESPONSE_LEN 64 -static const char arctis_1_battery_request[] = { 0x06, 0x12 }; -static const char arctis_9_battery_request[] = { 0x00, 0x20 }; - -static int steelseries_headset_request_battery(struct hid_device *hdev, - const char *request, size_t len) -{ - u8 *write_buf; - int ret; - - /* Request battery information */ - write_buf = kmemdup(request, len, GFP_KERNEL); - if (!write_buf) - return -ENOMEM; - - hid_dbg(hdev, "Sending battery request report"); - ret = hid_hw_raw_request(hdev, request[0], write_buf, len, - HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); - if (ret < (int)len) { - hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret); - ret = -ENODATA; - } - - kfree(write_buf); - return ret; -} - -static void steelseries_headset_fetch_battery(struct hid_device *hdev) -{ - int ret = 0; - - if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) - ret = steelseries_headset_request_battery(hdev, - arctis_1_battery_request, sizeof(arctis_1_battery_request)); - else if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) - ret = steelseries_headset_request_battery(hdev, - arctis_9_battery_request, sizeof(arctis_9_battery_request)); - - if (ret < 0) - hid_dbg(hdev, - "Battery query failed (err: %d)\n", ret); -} - -static int battery_capacity_to_level(int capacity) -{ - if (capacity >= 50) - return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; - if (capacity >= 20) - return POWER_SUPPLY_CAPACITY_LEVEL_LOW; - return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; -} - -static void steelseries_headset_battery_timer_tick(struct work_struct *work) -{ - struct steelseries_device *sd = container_of(work, - struct steelseries_device, battery_work.work); - struct hid_device *hdev = sd->hdev; - - steelseries_headset_fetch_battery(hdev); -} - -#define STEELSERIES_PREFIX "SteelSeries " -#define STEELSERIES_PREFIX_LEN strlen(STEELSERIES_PREFIX) - -static int steelseries_headset_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct steelseries_device *sd = power_supply_get_drvdata(psy); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = sd->hdev->name; - while (!strncmp(val->strval, STEELSERIES_PREFIX, STEELSERIES_PREFIX_LEN)) - val->strval += STEELSERIES_PREFIX_LEN; - break; - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = "SteelSeries"; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = 1; - break; - case POWER_SUPPLY_PROP_STATUS: - if (sd->headset_connected) { - val->intval = sd->battery_charging ? - POWER_SUPPLY_STATUS_CHARGING : - POWER_SUPPLY_STATUS_DISCHARGING; - } else - val->intval = POWER_SUPPLY_STATUS_UNKNOWN; - break; - case POWER_SUPPLY_PROP_SCOPE: - val->intval = POWER_SUPPLY_SCOPE_DEVICE; - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = sd->battery_capacity; - break; - case POWER_SUPPLY_PROP_CAPACITY_LEVEL: - val->intval = battery_capacity_to_level(sd->battery_capacity); - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - -static void -steelseries_headset_set_wireless_status(struct hid_device *hdev, - bool connected) -{ - struct usb_interface *intf; - - if (!hid_is_usb(hdev)) - return; - - intf = to_usb_interface(hdev->dev.parent); - usb_set_wireless_status(intf, connected ? - USB_WIRELESS_STATUS_CONNECTED : - USB_WIRELESS_STATUS_DISCONNECTED); -} - -static enum power_supply_property steelseries_headset_battery_props[] = { - POWER_SUPPLY_PROP_MODEL_NAME, - POWER_SUPPLY_PROP_MANUFACTURER, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_SCOPE, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CAPACITY_LEVEL, +static 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 int steelseries_headset_battery_register(struct steelseries_device *sd) +static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev) { - static atomic_t battery_no = ATOMIC_INIT(0); - struct power_supply_config battery_cfg = { .drv_data = sd, }; - unsigned long n; + 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->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY; - sd->battery_desc.properties = steelseries_headset_battery_props; - sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_headset_battery_props); - sd->battery_desc.get_property = steelseries_headset_battery_get_property; - sd->battery_desc.use_for_apm = 0; - n = atomic_inc_return(&battery_no) - 1; - sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL, - "steelseries_headset_battery_%ld", n); - if (!sd->battery_desc.name) + sd->rgb_buf = kzalloc(STEELSERIES_MSI_RGB_REPORT_LEN, GFP_KERNEL); + if (!sd->rgb_buf) return -ENOMEM; - /* avoid the warning of 0% battery while waiting for the first info */ - steelseries_headset_set_wireless_status(sd->hdev, false); - sd->battery_capacity = 100; - sd->battery_charging = false; - - sd->battery = devm_power_supply_register(&sd->hdev->dev, - &sd->battery_desc, &battery_cfg); - if (IS_ERR(sd->battery)) { - ret = PTR_ERR(sd->battery); - hid_err(sd->hdev, - "%s:power_supply_register failed with error %d\n", - __func__, ret); + ret = devm_add_action_or_reset(&hdev->dev, + steelseries_msi_rgb_free_buf, + sd->rgb_buf); + if (ret) { + sd->rgb_buf = NULL; return ret; } - power_supply_powers(sd->battery, &sd->hdev->dev); - INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick); - steelseries_headset_fetch_battery(sd->hdev); + 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; + } - if (sd->quirks & STEELSERIES_ARCTIS_9) { - /* The first fetch_battery request can remain unanswered in some cases */ - schedule_delayed_work(&sd->battery_work, - msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS)); + 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; } - -static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t usage_page) +#else +static int steelseries_msi_rgb_register(struct steelseries_device *sd) { - return hdev->rdesc[0] == 0x06 && - hdev->rdesc[1] == usage_page && - hdev->rdesc[2] == 0xff; + return -ENODEV; } +#endif static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -549,36 +585,45 @@ 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; - if (sd->quirks & STEELSERIES_ARCTIS_9 && - !steelseries_is_vendor_usage_page(hdev, 0xc0)) - return -ENODEV; - - spin_lock_init(&sd->lock); - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) return ret; ret = hid_hw_open(hdev); if (ret) - return ret; + goto err_stop; - if (steelseries_headset_battery_register(sd) < 0) - hid_err(sd->hdev, - "Failed to register battery for headset\n"); + 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; + +err_stop: + hid_hw_stop(hdev); return ret; } static void steelseries_remove(struct hid_device *hdev) { - struct steelseries_device *sd; - unsigned long flags; - if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) { #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) @@ -587,14 +632,6 @@ static void steelseries_remove(struct hid_device *hdev) return; } - sd = hid_get_drvdata(hdev); - - spin_lock_irqsave(&sd->lock, flags); - sd->removed = true; - spin_unlock_irqrestore(&sd->lock, flags); - - cancel_delayed_work_sync(&sd->battery_work); - hid_hw_close(hdev); hid_hw_stop(hdev); } @@ -615,122 +652,19 @@ static const __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, return rdesc; } -static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in, uint8_t max_in) -{ - if (capacity >= max_in) - return 100; - if (capacity <= min_in) - return 0; - return (capacity - min_in) * 100 / (max_in - min_in); -} - -static 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; - unsigned long flags; - - /* Not a headset */ - if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) - return 0; - - 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); - if (size < ARCTIS_1_BATTERY_RESPONSE_LEN || - memcmp(read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_request))) { - if (!delayed_work_pending(&sd->battery_work)) - goto request_battery; - return 0; - } - if (read_buf[2] == 0x01) { - connected = false; - capacity = 100; - } else { - connected = true; - capacity = read_buf[3]; - } - } - - if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) { - hid_dbg(sd->hdev, - "Parsing raw event for Arctis 9 headset (%*ph)\n", size, read_buf); - if (size < ARCTIS_9_BATTERY_RESPONSE_LEN) { - if (!delayed_work_pending(&sd->battery_work)) - goto request_battery; - return 0; - } - - if (read_buf[0] == 0xaa && read_buf[1] == 0x01) { - connected = true; - charging = read_buf[4] == 0x01; - - /* - * Found no official documentation about min and max. - * Values defined by testing. - */ - capacity = steelseries_headset_map_capacity(read_buf[3], 0x68, 0x9d); - } else { - /* - * Device is off and sends the last known status read_buf[1] == 0x03 or - * there is no known status of the device read_buf[0] == 0x55 - */ - connected = false; - charging = false; - } - } - - if (connected != sd->headset_connected) { - hid_dbg(sd->hdev, - "Connected status changed from %sconnected to %sconnected\n", - sd->headset_connected ? "" : "not ", - connected ? "" : "not "); - sd->headset_connected = connected; - steelseries_headset_set_wireless_status(hdev, connected); - } - - if (capacity != sd->battery_capacity) { - hid_dbg(sd->hdev, - "Battery capacity changed from %d%% to %d%%\n", - sd->battery_capacity, capacity); - sd->battery_capacity = capacity; - power_supply_changed(sd->battery); - } - - if (charging != sd->battery_charging) { - hid_dbg(sd->hdev, - "Battery charging status changed from %scharging to %scharging\n", - sd->battery_charging ? "" : "not ", - charging ? "" : "not "); - sd->battery_charging = charging; - power_supply_changed(sd->battery); - } - -request_battery: - spin_lock_irqsave(&sd->lock, flags); - if (!sd->removed) - schedule_delayed_work(&sd->battery_work, - msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS)); - spin_unlock_irqrestore(&sd->lock, flags); - - return 0; -} - static const struct hid_device_id steelseries_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1), .driver_data = STEELSERIES_SRWS1 }, - { /* SteelSeries Arctis 1 Wireless for XBox */ - HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1), - .driver_data = STEELSERIES_ARCTIS_1 }, +#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 }, - { /* SteelSeries Arctis 9 Wireless for XBox */ - HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9), - .driver_data = STEELSERIES_ARCTIS_9 }, + { /* MSI Raider A18 ALC */ + HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC), + .driver_data = STEELSERIES_MSI_RGB }, +#endif { } }; @@ -742,12 +676,9 @@ static struct hid_driver steelseries_driver = { .probe = steelseries_probe, .remove = steelseries_remove, .report_fixup = steelseries_srws1_report_fixup, - .raw_event = steelseries_headset_raw_event, }; module_hid_driver(steelseries_driver); MODULE_DESCRIPTION("HID driver for Steelseries devices"); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Bastien Nocera "); MODULE_AUTHOR("Simon Wood "); -MODULE_AUTHOR("Christian Mayer "); diff --git a/drivers/hid/hid-tmff.c b/drivers/hid/hid-tmff.c index 423f395d01ac..303769b07628 100644 --- a/drivers/hid/hid-tmff.c +++ b/drivers/hid/hid-tmff.c @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -47,9 +48,9 @@ struct tmff_device { /* Changes values from 0 to 0xffff into values from minimum to maximum */ static inline int tmff_scale_u16(unsigned int in, int minimum, int maximum) { - int ret; + s64 ret; - ret = (in * (maximum - minimum) / 0xffff) + minimum; + ret = div_s64((s64)in * ((s64)maximum - minimum), 0xffff) + minimum; if (ret < minimum) return minimum; if (ret > maximum) @@ -60,9 +61,9 @@ static inline int tmff_scale_u16(unsigned int in, int minimum, int maximum) /* Changes values from -0x80 to 0x7f into values from minimum to maximum */ static inline int tmff_scale_s8(int in, int minimum, int maximum) { - int ret; + s64 ret; - ret = (((in + 0x80) * (maximum - minimum)) / 0xff) + minimum; + ret = div_s64((s64)(in + 0x80) * ((s64)maximum - minimum), 0xff) + minimum; if (ret < minimum) return minimum; if (ret > maximum) @@ -115,22 +116,25 @@ static int tmff_play(struct input_dev *dev, void *data, return 0; } -static int tmff_init(struct hid_device *hid, const signed short *ff_bits) +static int tm_input_configured(struct hid_device *hid, struct hid_input *hidinput) { struct tmff_device *tmff; struct hid_report *report; struct list_head *report_list; - struct hid_input *hidinput; - struct input_dev *input_dev; + struct input_dev *input_dev = hidinput->input; + const struct hid_device_id *id; + const signed short *ff_bits; int error; int i; - if (list_empty(&hid->inputs)) { - hid_err(hid, "no inputs found\n"); + if (!list_is_first(&hidinput->list, &hid->inputs)) + return 0; + + id = hid_match_device(hid, hid->driver); + if (!id) return -ENODEV; - } - hidinput = list_entry(hid->inputs.next, struct hid_input, list); - input_dev = hidinput->input; + + ff_bits = (void *)id->driver_data; tmff = kzalloc_obj(struct tmff_device); if (!tmff) @@ -204,35 +208,13 @@ static int tmff_init(struct hid_device *hid, const signed short *ff_bits) return error; } #else -static inline int tmff_init(struct hid_device *hid, const signed short *ff_bits) +static inline int tm_input_configured(struct hid_device *hid, + struct hid_input *hidinput) { return 0; } #endif -static int tm_probe(struct hid_device *hdev, const struct hid_device_id *id) -{ - int ret; - - ret = hid_parse(hdev); - if (ret) { - hid_err(hdev, "parse failed\n"); - goto err; - } - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); - if (ret) { - hid_err(hdev, "hw start failed\n"); - goto err; - } - - tmff_init(hdev, (void *)id->driver_data); - - return 0; -err: - return ret; -} - static const struct hid_device_id tm_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300), .driver_data = (unsigned long)ff_rumble }, @@ -261,7 +243,7 @@ MODULE_DEVICE_TABLE(hid, tm_devices); static struct hid_driver tm_driver = { .name = "thrustmaster", .id_table = tm_devices, - .probe = tm_probe, + .input_configured = tm_input_configured, }; module_hid_driver(tm_driver); diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index b73f09d26688..0b8a83fa6c5b 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -548,7 +548,17 @@ static void uclogic_remove(struct hid_device *hdev) { struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); - timer_delete_sync(&drvdata->inrange_timer); + /* + * Shut the in-range timer down before stopping the device. + * uclogic_raw_event_pen() re-arms inrange_timer on every pen report + * and keeps running until hid_hw_stop() stops the transport, so a + * plain timer_delete_sync() here can be undone by a report landing in + * the window before hid_hw_stop(). timer_shutdown_sync() cancels the + * timer and makes any later re-arm a no-op, so it is provably dead + * before hid_hw_stop() frees the input device drvdata->pen_input + * points at. + */ + timer_shutdown_sync(&drvdata->inrange_timer); hid_hw_stop(hdev); kfree(drvdata->desc_ptr); uclogic_params_cleanup(&drvdata->params); diff --git a/drivers/hid/hid-universal-pidff.c b/drivers/hid/hid-universal-pidff.c index 549dac555d40..60180467a0bf 100644 --- a/drivers/hid/hid-universal-pidff.c +++ b/drivers/hid/hid-universal-pidff.c @@ -104,12 +104,14 @@ static int universal_pidff_probe(struct hid_device *hdev, error = init_function(hdev, id->driver_data); if (error) { hid_warn(hdev, "Error initialising force feedback\n"); - goto err; + goto err_stop; } hid_info(hdev, "Universal pidff driver loaded successfully!"); return 0; +err_stop: + hid_hw_stop(hdev); err: return error; } diff --git a/drivers/hid/hid-zpff.c b/drivers/hid/hid-zpff.c index d8e023c8aa84..b565c59d3dfe 100644 --- a/drivers/hid/hid-zpff.c +++ b/drivers/hid/hid-zpff.c @@ -50,20 +50,15 @@ static int zpff_play(struct input_dev *dev, void *data, return 0; } -static int zpff_init(struct hid_device *hid) +static int zp_input_configured(struct hid_device *hid, struct hid_input *hidinput) { struct zpff_device *zpff; struct hid_report *report; - struct hid_input *hidinput; - struct input_dev *dev; + struct input_dev *dev = hidinput->input; int i, error; - if (list_empty(&hid->inputs)) { - hid_err(hid, "no inputs found\n"); - return -ENODEV; - } - hidinput = list_entry(hid->inputs.next, struct hid_input, list); - dev = hidinput->input; + if (!list_is_first(&hidinput->list, &hid->inputs)) + return 0; for (i = 0; i < 4; i++) { report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, i, 1); @@ -75,6 +70,7 @@ static int zpff_init(struct hid_device *hid) if (!zpff) return -ENOMEM; + zpff->report = report; set_bit(FF_RUMBLE, dev->ffbit); error = input_ff_create_memless(dev, zpff, zpff_play); @@ -83,7 +79,6 @@ static int zpff_init(struct hid_device *hid) return error; } - zpff->report = report; zpff->report->field[0]->value[0] = 0x00; zpff->report->field[1]->value[0] = 0x02; zpff->report->field[2]->value[0] = 0x00; @@ -95,35 +90,13 @@ static int zpff_init(struct hid_device *hid) return 0; } #else -static inline int zpff_init(struct hid_device *hid) +static inline int zp_input_configured(struct hid_device *hid, + struct hid_input *hidinput) { return 0; } #endif -static int zp_probe(struct hid_device *hdev, const struct hid_device_id *id) -{ - int ret; - - ret = hid_parse(hdev); - if (ret) { - hid_err(hdev, "parse failed\n"); - goto err; - } - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); - if (ret) { - hid_err(hdev, "hw start failed\n"); - goto err; - } - - zpff_init(hdev); - - return 0; -err: - return ret; -} - static const struct hid_device_id zp_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0005) }, { HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0030) }, @@ -134,7 +107,7 @@ MODULE_DEVICE_TABLE(hid, zp_devices); static struct hid_driver zp_driver = { .name = "zeroplus", .id_table = zp_devices, - .probe = zp_probe, + .input_configured = zp_input_configured, }; module_hid_driver(zp_driver); diff --git a/drivers/hid/i2c-hid/Makefile b/drivers/hid/i2c-hid/Makefile index 55bd5e0f35af..38d5d827f3ce 100644 --- a/drivers/hid/i2c-hid/Makefile +++ b/drivers/hid/i2c-hid/Makefile @@ -8,7 +8,7 @@ obj-$(CONFIG_I2C_HID_CORE) += i2c-hid.o i2c-hid-objs = i2c-hid-core.o i2c-hid-$(CONFIG_DMI) += i2c-hid-dmi-quirks.o -obj-$(CONFIG_I2C_HID_ACPI) += i2c-hid-acpi.o +obj-$(CONFIG_I2C_HID_ACPI) += i2c-hid-acpi.o i2c-hid-acpi-prp0001.o obj-$(CONFIG_I2C_HID_OF) += i2c-hid-of.o obj-$(CONFIG_I2C_HID_OF_ELAN) += i2c-hid-of-elan.o obj-$(CONFIG_I2C_HID_OF_GOODIX) += i2c-hid-of-goodix.o diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c b/drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c new file mode 100644 index 000000000000..d2cf4714ae7f --- /dev/null +++ b/drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID over I2C driver for PRP0001 devices missing hid-descr-addr + * + * Some devices, for example the Lenovo KaiTian N60d and Inspur CP300L3, use + * _HID "PRP0001" with _DSD compatible "hid-over-i2c" but lack "hid-descr-addr" + * from the _DSD. The HID descriptor address is provided only through an ACPI + * _DSM. The TPD0 node in the DSDT shows _DSM Function 1 returning 0x20. + * + * Copyright (C) 2026 谢致邦 (XIE Zhibang) + */ + +#include +#include +#include +#include +#include + +#include "i2c-hid.h" +#include "i2c-hid-acpi.h" + +static int i2c_hid_acpi_prp0001_power_up(struct i2chid_ops *ops) +{ + /* give the device time to power up */ + msleep(750); + return 0; +} + +static struct i2chid_ops i2c_hid_acpi_prp0001_ops = { + .power_up = i2c_hid_acpi_prp0001_power_up, + /* + * No .restore_sequence needed: the _DSM on these devices returns a + * constant (0x20) with no side effects, unlike some PNP0C50 _DSM + * implementations that switch the hardware between PS/2 and I2C modes. + */ +}; + +static int i2c_hid_acpi_prp0001_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct acpi_device *adev; + u16 hid_descriptor_address; + int ret; + + /* If hid-descr-addr is present, let i2c-hid-of handle it */ + if (device_property_present(dev, "hid-descr-addr")) + return -ENODEV; + + adev = ACPI_COMPANION(dev); + if (!adev) + return -ENODEV; + + ret = i2c_hid_acpi_get_descriptor(adev); + if (ret < 0) + return ret; + dev_warn(dev, + "hid-descr-addr device property NOT found, using ACPI _DSM fallback. Contact vendor for firmware update!\n"); + hid_descriptor_address = ret; + + /* + * No acpi_device_fix_up_power() needed: TPD0 has no _PS0, _PS3, _PSC + * or _PRx methods and follows I2C bus power. + */ + return i2c_hid_core_probe(client, &i2c_hid_acpi_prp0001_ops, + hid_descriptor_address, 0); +} + +static const struct of_device_id i2c_hid_acpi_prp0001_of_match[] = { + { .compatible = "hid-over-i2c" }, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_hid_acpi_prp0001_of_match); + +static const struct i2c_device_id i2c_hid_acpi_prp0001_id[] = { + { .name = "hid-over-i2c" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, i2c_hid_acpi_prp0001_id); + +static struct i2c_driver i2c_hid_acpi_prp0001_driver = { + .driver = { + .name = "i2c_hid_acpi_prp0001", + .pm = &i2c_hid_core_pm, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + /* + * of_match_ptr() makes this NULL when CONFIG_OF=n, but that's + * fine: the I2C id_table with "hid-over-i2c" handles matching + * via client->name (set by acpi_set_modalias() from the _DSD + * compatible property). + */ + .of_match_table = of_match_ptr(i2c_hid_acpi_prp0001_of_match), + }, + + .probe = i2c_hid_acpi_prp0001_probe, + .remove = i2c_hid_core_remove, + .shutdown = i2c_hid_core_shutdown, + .id_table = i2c_hid_acpi_prp0001_id, +}; + +module_i2c_driver(i2c_hid_acpi_prp0001_driver); + +MODULE_DESCRIPTION("HID over I2C driver for PRP0001 devices missing hid-descr-addr"); +MODULE_AUTHOR("谢致邦 (XIE Zhibang) "); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c index abd700a101f4..13f977d6aab6 100644 --- a/drivers/hid/i2c-hid/i2c-hid-acpi.c +++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c @@ -25,9 +25,9 @@ #include #include #include -#include #include "i2c-hid.h" +#include "i2c-hid-acpi.h" struct i2c_hid_acpi { struct i2chid_ops ops; @@ -48,39 +48,11 @@ static const struct acpi_device_id i2c_hid_acpi_blacklist[] = { { } }; -/* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */ -static guid_t i2c_hid_guid = - GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555, - 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE); - -static int i2c_hid_acpi_get_descriptor(struct i2c_hid_acpi *ihid_acpi) -{ - struct acpi_device *adev = ihid_acpi->adev; - acpi_handle handle = acpi_device_handle(adev); - union acpi_object *obj; - u16 hid_descriptor_address; - - if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0) - return -ENODEV; - - obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid, 1, 1, NULL, - ACPI_TYPE_INTEGER); - if (!obj) { - acpi_handle_err(handle, "Error _DSM call to get HID descriptor address failed\n"); - return -ENODEV; - } - - hid_descriptor_address = obj->integer.value; - ACPI_FREE(obj); - - return hid_descriptor_address; -} - static void i2c_hid_acpi_restore_sequence(struct i2chid_ops *ops) { struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops); - i2c_hid_acpi_get_descriptor(ihid_acpi); + i2c_hid_acpi_get_descriptor(ihid_acpi->adev); } static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops) @@ -93,24 +65,28 @@ static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops) static int i2c_hid_acpi_probe(struct i2c_client *client) { struct device *dev = &client->dev; + struct acpi_device *adev = ACPI_COMPANION(dev); struct i2c_hid_acpi *ihid_acpi; u16 hid_descriptor_address; int ret; - ihid_acpi = devm_kzalloc(&client->dev, sizeof(*ihid_acpi), GFP_KERNEL); - if (!ihid_acpi) - return -ENOMEM; + if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0) + return -ENODEV; - ihid_acpi->adev = ACPI_COMPANION(dev); - ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail; - ihid_acpi->ops.restore_sequence = i2c_hid_acpi_restore_sequence; - - ret = i2c_hid_acpi_get_descriptor(ihid_acpi); + ret = i2c_hid_acpi_get_descriptor(adev); if (ret < 0) return ret; hid_descriptor_address = ret; - acpi_device_fix_up_power(ihid_acpi->adev); + ihid_acpi = devm_kzalloc(dev, sizeof(*ihid_acpi), GFP_KERNEL); + if (!ihid_acpi) + return -ENOMEM; + + ihid_acpi->adev = adev; + ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail; + ihid_acpi->ops.restore_sequence = i2c_hid_acpi_restore_sequence; + + acpi_device_fix_up_power(adev); return i2c_hid_core_probe(client, &ihid_acpi->ops, hid_descriptor_address, 0); diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.h b/drivers/hid/i2c-hid/i2c-hid-acpi.h new file mode 100644 index 000000000000..0bbed1853313 --- /dev/null +++ b/drivers/hid/i2c-hid/i2c-hid-acpi.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _I2C_HID_ACPI_H +#define _I2C_HID_ACPI_H + +#include +#include + +static inline int i2c_hid_acpi_get_descriptor(struct acpi_device *adev) +{ + /* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */ + static const guid_t i2c_hid_guid = + GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555, + 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE); + + acpi_handle handle = acpi_device_handle(adev); + union acpi_object *obj; + u16 addr; + + obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid, + 1, 1, NULL, ACPI_TYPE_INTEGER); + if (!obj) { + acpi_handle_err(handle, + "Error _DSM call to get HID descriptor address failed\n"); + return -ENODEV; + } + + addr = obj->integer.value; + ACPI_FREE(obj); + return addr; +} + +#endif diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 3adb16366e93..0e725a0f0abe 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -792,7 +792,7 @@ static int i2c_hid_parse(struct hid_device *hid) ihid->hdesc.wReportDescRegister, rdesc, rsize); if (ret) { - hid_err(hid, "reading report descriptor failed\n"); + dev_err(&client->dev, "reading report descriptor failed\n"); goto out; } } diff --git a/drivers/hid/i2c-hid/i2c-hid-of-goodix.c b/drivers/hid/i2c-hid/i2c-hid-of-goodix.c index f1597ad67e7c..f4dbcd1d1d47 100644 --- a/drivers/hid/i2c-hid/i2c-hid-of-goodix.c +++ b/drivers/hid/i2c-hid/i2c-hid-of-goodix.c @@ -51,8 +51,10 @@ static int goodix_i2c_hid_power_up(struct i2chid_ops *ops) return ret; ret = regulator_enable(ihid_goodix->vddio); - if (ret) + if (ret) { + regulator_disable(ihid_goodix->vdd); return ret; + } if (ihid_goodix->timings->post_power_delay_ms) msleep(ihid_goodix->timings->post_power_delay_ms); diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c index 8d36ae96a3ee..e7196e429c8b 100644 --- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -232,10 +232,8 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent) ret = devm_request_irq(dev, pdev->irq, ish_irq_handler, irq_flag, KBUILD_MODNAME, ishtp); - if (ret) { - dev_err(dev, "ISH: request IRQ %d failed\n", pdev->irq); + if (ret) return ret; - } dev_set_drvdata(ishtp->devc, ishtp); diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c index 6d64008f2ce0..ec5e8afe77bd 100644 --- a/drivers/hid/intel-ish-hid/ishtp-hid-client.c +++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c @@ -113,8 +113,7 @@ static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf, switch (recv_msg->hdr.command & CMD_MASK) { case HOSTIF_DM_ENUM_DEVICES: - if ((!(recv_msg->hdr.command & ~CMD_MASK) || - client_data->init_done)) { + if (!(recv_msg->hdr.command & ~CMD_MASK)) { ++client_data->bad_recv_cnt; report_bad_packet(hid_ishtp_cl, recv_msg, cur_pos, @@ -122,6 +121,8 @@ static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf, ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl)); break; } + if (client_data->init_done) + break; client_data->hid_dev_count = (unsigned int)*payload; if (!client_data->hid_devices) client_data->hid_devices = devm_kcalloc( diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c index 46d3e9a01999..0d2ad7bc3648 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c @@ -175,7 +175,9 @@ static int quicki2c_get_acpi_resources(struct quicki2c_device *qcdev) if (i2c_param.addressing_mode != HIDI2C_ADDRESSING_MODE_7BIT) return -EOPNOTSUPP; - qcdev->i2c_slave_addr = i2c_param.device_address; + qcdev->i2c_config.addr_mode = HIDI2C_ADDRESSING_MODE_7BIT; + + qcdev->i2c_config.target_addr = i2c_param.device_address; ret = quicki2c_acpi_get_dsd_property(adev, QUICKI2C_ACPI_METHOD_NAME_ISUB, ACPI_TYPE_BUFFER, &i2c_config); @@ -184,24 +186,32 @@ static int quicki2c_get_acpi_resources(struct quicki2c_device *qcdev) if (i2c_param.connection_speed > 0 && i2c_param.connection_speed <= QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED) { - qcdev->i2c_speed_mode = THC_I2C_STANDARD; - qcdev->i2c_clock_hcnt = i2c_config.SMHX; - qcdev->i2c_clock_lcnt = i2c_config.SMLX; + qcdev->i2c_config.speed = THC_I2C_STANDARD; + qcdev->i2c_config.scl_hcnt = (u32)i2c_config.SMHX; + qcdev->i2c_config.scl_lcnt = (u32)i2c_config.SMLX; + qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.SMTD; + qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.SMRD; } else if (i2c_param.connection_speed > QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED && i2c_param.connection_speed <= QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED) { - qcdev->i2c_speed_mode = THC_I2C_FAST_AND_PLUS; - qcdev->i2c_clock_hcnt = i2c_config.FMHX; - qcdev->i2c_clock_lcnt = i2c_config.FMLX; + qcdev->i2c_config.speed = THC_I2C_FAST_AND_PLUS; + qcdev->i2c_config.scl_hcnt = (u32)i2c_config.FMHX; + qcdev->i2c_config.scl_lcnt = (u32)i2c_config.FMLX; + qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.FMTD; + qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.FMRD; } else if (i2c_param.connection_speed > QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED && i2c_param.connection_speed <= QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED) { - qcdev->i2c_speed_mode = THC_I2C_FAST_AND_PLUS; - qcdev->i2c_clock_hcnt = i2c_config.FPHX; - qcdev->i2c_clock_lcnt = i2c_config.FPLX; + qcdev->i2c_config.speed = THC_I2C_FAST_AND_PLUS; + qcdev->i2c_config.scl_hcnt = (u32)i2c_config.FPHX; + qcdev->i2c_config.scl_lcnt = (u32)i2c_config.FPLX; + qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.FPTD; + qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.FPRD; } else if (i2c_param.connection_speed > QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED && i2c_param.connection_speed <= QUICKI2C_SUBIP_HIGH_SPEED_MODE_MAX_SPEED) { - qcdev->i2c_speed_mode = THC_I2C_HIGH_SPEED; - qcdev->i2c_clock_hcnt = i2c_config.HMHX; - qcdev->i2c_clock_lcnt = i2c_config.HMLX; + qcdev->i2c_config.speed = THC_I2C_HIGH_SPEED; + qcdev->i2c_config.scl_hcnt = (u32)i2c_config.HMHX; + qcdev->i2c_config.scl_lcnt = (u32)i2c_config.HMLX; + qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.HMTD; + qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.HMRD; } else { return -EOPNOTSUPP; } @@ -245,28 +255,33 @@ static irqreturn_t quicki2c_irq_quick_handler(int irq, void *dev_id) } /** - * try_recover - Try to recovery THC and Device - * @qcdev: Pointer to quicki2c_device structure + * try_recover - Recover callback to recover THC + * @work: pointer to work_struct * * This function is an error handler, called when fatal error happens. - * It try to reset touch device and re-configure THC to recovery - * communication between touch device and THC. - * - * Return: 0 if successful or error code on failure + * It try to reset Touch Device and re-configure THC to recover + * transferring between Device and THC. */ -static int try_recover(struct quicki2c_device *qcdev) +static void try_recover(struct work_struct *work) { - int ret; + struct quicki2c_device *qcdev = container_of(work, struct quicki2c_device, recover_work); - thc_dma_unconfigure(qcdev->thc_hw); + if (READ_ONCE(qcdev->recovery_disabled)) + return; - ret = thc_dma_configure(qcdev->thc_hw); - if (ret) { - dev_err(qcdev->dev, "Reconfig DMA failed\n"); - return ret; + if (pm_runtime_resume_and_get(qcdev->dev)) + return; + + thc_interrupt_enable(qcdev->thc_hw, false); + + if (thc_rxdma_reset(qcdev->thc_hw)) { + qcdev->state = QUICKI2C_DISABLED; + dev_err(qcdev->dev, "RxDMA reset failed during recover, disable QuickI2C\n"); + } else { + thc_interrupt_enable(qcdev->thc_hw, true); } - return 0; + pm_runtime_put_autosuspend(qcdev->dev); } static int handle_input_report(struct quicki2c_device *qcdev) @@ -343,11 +358,10 @@ static irqreturn_t quicki2c_irq_thread_handler(int irq, void *dev_id) } exit: - thc_interrupt_enable(qcdev->thc_hw, true); - if (err_recover) - if (try_recover(qcdev)) - qcdev->state = QUICKI2C_DISABLED; + schedule_work(&qcdev->recover_work); + else + thc_interrupt_enable(qcdev->thc_hw, true); pm_runtime_put_autosuspend(qcdev->dev); @@ -386,6 +400,8 @@ static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __io qcdev->ddata = ddata; init_waitqueue_head(&qcdev->reset_ack_wq); + WRITE_ONCE(qcdev->recovery_disabled, false); + INIT_WORK(&qcdev->recover_work, try_recover); /* THC hardware init */ qcdev->thc_hw = thc_dev_init(qcdev->dev, qcdev->mem_addr); @@ -411,10 +427,7 @@ static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __io return ERR_PTR(ret); } - ret = thc_i2c_subip_init(qcdev->thc_hw, qcdev->i2c_slave_addr, - qcdev->i2c_speed_mode, - qcdev->i2c_clock_hcnt, - qcdev->i2c_clock_lcnt); + ret = thc_i2c_subip_init(qcdev->thc_hw, &qcdev->i2c_config); if (ret) return ERR_PTR(ret); @@ -439,6 +452,9 @@ static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __io */ static void quicki2c_dev_deinit(struct quicki2c_device *qcdev) { + WRITE_ONCE(qcdev->recovery_disabled, true); + cancel_work_sync(&qcdev->recover_work); + thc_interrupt_quiesce(qcdev->thc_hw, true); thc_interrupt_enable(qcdev->thc_hw, false); thc_ltr_unconfig(qcdev->thc_hw); @@ -682,11 +698,8 @@ static int quicki2c_probe(struct pci_dev *pdev, const struct pci_device_id *id) quicki2c_irq_thread_handler, IRQF_ONESHOT, KBUILD_MODNAME, qcdev); - if (ret) { - dev_err_once(&pdev->dev, - "Failed to request threaded IRQ, irq = %d.\n", pdev->irq); + if (ret) goto dev_deinit; - } ret = quicki2c_get_device_descriptor(qcdev); if (ret) { @@ -772,12 +785,14 @@ static void quicki2c_remove(struct pci_dev *pdev) return; quicki2c_hid_remove(qcdev); - quicki2c_dma_deinit(qcdev); - - pm_runtime_get_noresume(qcdev->dev); quicki2c_dev_deinit(qcdev); + quicki2c_dma_deinit(qcdev); + + pm_runtime_dont_use_autosuspend(qcdev->dev); + pm_runtime_get_noresume(qcdev->dev); + pci_clear_master(pdev); } @@ -796,10 +811,10 @@ static void quicki2c_shutdown(struct pci_dev *pdev) if (!qcdev) return; + quicki2c_dev_deinit(qcdev); + /* Must stop DMA before reboot to avoid DMA entering into unknown state */ quicki2c_dma_deinit(qcdev); - - quicki2c_dev_deinit(qcdev); } static int quicki2c_suspend(struct device *device) @@ -826,6 +841,9 @@ static int quicki2c_suspend(struct device *device) if (ret) return ret; + WRITE_ONCE(qcdev->recovery_disabled, true); + cancel_work_sync(&qcdev->recover_work); + ret = thc_interrupt_quiesce(qcdev->thc_hw, true); if (ret) return ret; @@ -867,6 +885,8 @@ static int quicki2c_resume(struct device *device) if (ret) return ret; + WRITE_ONCE(qcdev->recovery_disabled, false); + if (!device_may_wakeup(qcdev->dev)) return quicki2c_set_power(qcdev, HIDI2C_ON); @@ -883,6 +903,9 @@ static int quicki2c_freeze(struct device *device) if (!qcdev) return -ENODEV; + WRITE_ONCE(qcdev->recovery_disabled, true); + cancel_work_sync(&qcdev->recover_work); + ret = thc_interrupt_quiesce(qcdev->thc_hw, true); if (ret) return ret; @@ -914,6 +937,8 @@ static int quicki2c_thaw(struct device *device) if (ret) return ret; + WRITE_ONCE(qcdev->recovery_disabled, false); + return 0; } @@ -938,6 +963,8 @@ static int quicki2c_poweroff(struct device *device) thc_ltr_unconfig(qcdev->thc_hw); + quicki2c_dev_deinit(qcdev); + quicki2c_dma_deinit(qcdev); return 0; @@ -958,10 +985,7 @@ static int quicki2c_restore(struct device *device) if (ret) return ret; - ret = thc_i2c_subip_init(qcdev->thc_hw, qcdev->i2c_slave_addr, - qcdev->i2c_speed_mode, - qcdev->i2c_clock_hcnt, - qcdev->i2c_clock_lcnt); + ret = thc_i2c_subip_init(qcdev->thc_hw, &qcdev->i2c_config); if (ret) return ret; diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h index 61dbdece59a1..6d25a846153e 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h @@ -7,6 +7,8 @@ #include #include +#include "intel-thc-dev.h" + #define PCI_DEVICE_ID_INTEL_THC_LNL_DEVICE_ID_I2C_PORT1 0xA848 #define PCI_DEVICE_ID_INTEL_THC_LNL_DEVICE_ID_I2C_PORT2 0xA84A #define PCI_DEVICE_ID_INTEL_THC_PTL_H_DEVICE_ID_I2C_PORT1 0xE348 @@ -159,7 +161,6 @@ struct quicki2c_ddata { struct device; struct pci_dev; -struct thc_device; struct hid_device; struct acpi_device; @@ -174,13 +175,10 @@ struct acpi_device; * @state: THC I2C device state * @mem_addr: MMIO memory address * @dev_desc: Device descriptor for HIDI2C protocol - * @i2c_slave_addr: HIDI2C device slave address + * @i2c_config: I2C bus configuration * @hid_desc_addr: Register address for retrieve HID device descriptor * @active_ltr_val: THC active LTR value * @low_power_ltr_val: THC low power LTR value - * @i2c_speed_mode: 0 - standard mode, 1 - fast mode, 2 - fast mode plus - * @i2c_clock_hcnt: I2C CLK high period time (unit in cycle count) - * @i2c_clock_lcnt: I2C CLK low period time (unit in cycle count) * @report_descriptor: Store a copy of device report descriptor * @input_buf: Store a copy of latest input report data * @report_buf: Store a copy of latest input/output report packet from set/get feature @@ -191,6 +189,8 @@ struct acpi_device; * @i2c_max_frame_size: Max RX frame size (unit in Bytes) * @i2c_int_delay_enable: Indicate interrupt delay feature enabled or not * @i2c_int_delay: Interrupt detection delay value (unit in 10 us) + * @recover_work: Work structure for recovery + * @recovery_disabled: Whether recovery work is blocked during teardown */ struct quicki2c_device { struct device *dev; @@ -204,16 +204,12 @@ struct quicki2c_device { void __iomem *mem_addr; struct hidi2c_dev_descriptor dev_desc; - u8 i2c_slave_addr; + struct thc_i2c_config i2c_config; u16 hid_desc_addr; u32 active_ltr_val; u32 low_power_ltr_val; - u32 i2c_speed_mode; - u32 i2c_clock_hcnt; - u32 i2c_clock_lcnt; - u8 *report_descriptor; u8 *input_buf; u8 *report_buf; @@ -226,6 +222,9 @@ struct quicki2c_device { u32 i2c_max_frame_size; u32 i2c_int_delay_enable; u32 i2c_int_delay; + + struct work_struct recover_work; + bool recovery_disabled; }; #endif /* _QUICKI2C_DEV_H_ */ diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c index 4ae2e1718b30..89226f5ce45e 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c @@ -252,34 +252,33 @@ static irqreturn_t quickspi_irq_quick_handler(int irq, void *dev_id) } /** - * try_recover - Try to recovery THC and Device - * @qsdev: pointer to quickspi device + * try_recover - Recover callback to recover THC + * @work: pointer to work_struct * - * This function is a error handler, called when fatal error happens. - * It try to reset Touch Device and re-configure THC to recovery + * This function is an error handler, called when fatal error happens. + * It try to reset Touch Device and re-configure THC to recover * transferring between Device and THC. - * - * Return: 0 if successful or error code on failed. */ -static int try_recover(struct quickspi_device *qsdev) +static void try_recover(struct work_struct *work) { - int ret; + struct quickspi_device *qsdev = container_of(work, struct quickspi_device, recover_work); - ret = reset_tic(qsdev); - if (ret) { - dev_err(qsdev->dev, "Reset touch device failed, ret = %d\n", ret); - return ret; + if (READ_ONCE(qsdev->recovery_disabled)) + return; + + if (pm_runtime_resume_and_get(qsdev->dev)) + return; + + thc_interrupt_enable(qsdev->thc_hw, false); + + if (thc_rxdma_reset(qsdev->thc_hw)) { + qsdev->state = QUICKSPI_DISABLED; + dev_err(qsdev->dev, "RxDMA reset failed during recover, disable QuickSPI\n"); + } else { + thc_interrupt_enable(qsdev->thc_hw, true); } - thc_dma_unconfigure(qsdev->thc_hw); - - ret = thc_dma_configure(qsdev->thc_hw); - if (ret) { - dev_err(qsdev->dev, "Re-configure THC DMA failed, ret = %d\n", ret); - return ret; - } - - return 0; + pm_runtime_put_autosuspend(qsdev->dev); } /** @@ -337,11 +336,10 @@ static irqreturn_t quickspi_irq_thread_handler(int irq, void *dev_id) } end: - thc_interrupt_enable(qsdev->thc_hw, true); - if (err_recover) - if (try_recover(qsdev)) - qsdev->state = QUICKSPI_DISABLED; + schedule_work(&qsdev->recover_work); + else + thc_interrupt_enable(qsdev->thc_hw, true); pm_runtime_put_autosuspend(qsdev->dev); @@ -385,6 +383,8 @@ static struct quickspi_device *quickspi_dev_init(struct pci_dev *pdev, void __io init_waitqueue_head(&qsdev->report_desc_got_wq); init_waitqueue_head(&qsdev->get_report_cmpl_wq); init_waitqueue_head(&qsdev->set_report_cmpl_wq); + WRITE_ONCE(qsdev->recovery_disabled, false); + INIT_WORK(&qsdev->recover_work, try_recover); /* thc hw init */ qsdev->thc_hw = thc_dev_init(qsdev->dev, qsdev->mem_addr); @@ -461,6 +461,10 @@ static struct quickspi_device *quickspi_dev_init(struct pci_dev *pdev, void __io */ static void quickspi_dev_deinit(struct quickspi_device *qsdev) { + WRITE_ONCE(qsdev->recovery_disabled, true); + cancel_work_sync(&qsdev->recover_work); + + thc_interrupt_quiesce(qsdev->thc_hw, true); thc_interrupt_enable(qsdev->thc_hw, false); thc_ltr_unconfig(qsdev->thc_hw); thc_wot_unconfig(qsdev->thc_hw); @@ -555,7 +559,14 @@ static int quickspi_alloc_report_buf(struct quickspi_device *qsdev) max_report_len = max(le16_to_cpu(qsdev->dev_desc.max_output_len), le16_to_cpu(qsdev->dev_desc.max_input_len)); - qsdev->report_buf = devm_kzalloc(qsdev->dev, max_report_len, GFP_KERNEL); + /* + * write_cmd_to_txdma() writes the output report header ahead of the + * content in this buffer, so it has to hold both. + */ + qsdev->report_buf_size = HIDSPI_OUTPUT_REPORT_SIZE(max_report_len); + + qsdev->report_buf = devm_kzalloc(qsdev->dev, qsdev->report_buf_size, + GFP_KERNEL); if (!qsdev->report_buf) return -ENOMEM; @@ -636,11 +647,8 @@ static int quickspi_probe(struct pci_dev *pdev, quickspi_irq_thread_handler, IRQF_ONESHOT, KBUILD_MODNAME, qsdev); - if (ret) { - dev_err(&pdev->dev, - "Failed to request threaded IRQ, irq = %d.\n", pdev->irq); + if (ret) goto dev_deinit; - } ret = reset_tic(qsdev); if (ret) { @@ -711,12 +719,14 @@ static void quickspi_remove(struct pci_dev *pdev) return; quickspi_hid_remove(qsdev); - quickspi_dma_deinit(qsdev); - - pm_runtime_get_noresume(qsdev->dev); quickspi_dev_deinit(qsdev); + quickspi_dma_deinit(qsdev); + + pm_runtime_dont_use_autosuspend(qsdev->dev); + pm_runtime_get_noresume(qsdev->dev); + pci_clear_master(pdev); } @@ -737,10 +747,10 @@ static void quickspi_shutdown(struct pci_dev *pdev) if (!qsdev) return; + quickspi_dev_deinit(qsdev); + /* Must stop DMA before reboot to avoid DMA entering into unknown state */ quickspi_dma_deinit(qsdev); - - quickspi_dev_deinit(qsdev); } static int quickspi_suspend(struct device *device) @@ -759,6 +769,9 @@ static int quickspi_suspend(struct device *device) return ret; } + WRITE_ONCE(qsdev->recovery_disabled, true); + cancel_work_sync(&qsdev->recover_work); + ret = thc_interrupt_quiesce(qsdev->thc_hw, true); if (ret) return ret; @@ -784,6 +797,8 @@ static int quickspi_resume(struct device *device) if (ret) return ret; + WRITE_ONCE(qsdev->recovery_disabled, false); + /* * A wake-enabled device keeps its power and state across suspend, so * only restore the THC context. Resetting it here would discard a @@ -864,6 +879,9 @@ static int quickspi_freeze(struct device *device) if (!qsdev) return -ENODEV; + WRITE_ONCE(qsdev->recovery_disabled, true); + cancel_work_sync(&qsdev->recover_work); + ret = thc_interrupt_quiesce(qsdev->thc_hw, true); if (ret) return ret; @@ -895,6 +913,8 @@ static int quickspi_thaw(struct device *device) if (ret) return ret; + WRITE_ONCE(qsdev->recovery_disabled, false); + return 0; } @@ -919,6 +939,8 @@ static int quickspi_poweroff(struct device *device) thc_ltr_unconfig(qsdev->thc_hw); + quickspi_dev_deinit(qsdev); + quickspi_dma_deinit(qsdev); return 0; diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h index bf5e18f5a5f4..dc67a39ad546 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "quickspi-protocol.h" @@ -126,6 +127,8 @@ struct acpi_device; * @get_feature_cmpl: indicate get feature received or not * @set_feature_cmpl_wq: workqueue for waiting set feature to device * @set_feature_cmpl: indicate set feature send complete or not + * @recover_work: Work structure for recovery + * @recovery_disabled: Whether recovery work is blocked during teardown */ struct quickspi_device { struct device *dev; @@ -157,6 +160,7 @@ struct quickspi_device { u8 *report_descriptor; u8 *input_buf; u8 *report_buf; + u32 report_buf_size; u32 report_len; wait_queue_head_t reset_ack_wq; @@ -173,6 +177,9 @@ struct quickspi_device { wait_queue_head_t set_report_cmpl_wq; bool set_report_cmpl; + + struct work_struct recover_work; + bool recovery_disabled; }; #endif /* _QUICKSPI_DEV_H_ */ diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c index 91d5807b4a83..a60a0a7f16aa 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c @@ -61,7 +61,7 @@ static int quickspi_hid_raw_request(struct hid_device *hid, switch (reqtype) { case HID_REQ_GET_REPORT: - ret = quickspi_get_report(qsdev, rtype, reportnum, buf); + ret = quickspi_get_report(qsdev, rtype, reportnum, buf, len); break; case HID_REQ_SET_REPORT: ret = quickspi_set_report(qsdev, rtype, reportnum, buf, len); diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c index cb19057f1191..847c5ec55569 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c @@ -30,6 +30,9 @@ static int write_cmd_to_txdma(struct quickspi_device *qsdev, write_buf = (struct output_report *)qsdev->report_buf; + if (HIDSPI_OUTPUT_REPORT_SIZE(report_buf_len) > qsdev->report_buf_size) + return -EINVAL; + write_buf->output_hdr.report_type = report_type; write_buf->output_hdr.content_len = cpu_to_le16(report_buf_len); write_buf->output_hdr.content_id = report_id; @@ -342,10 +345,12 @@ int reset_tic(struct quickspi_device *qsdev) } int quickspi_get_report(struct quickspi_device *qsdev, - u8 report_type, unsigned int report_id, void *buf) + u8 report_type, unsigned int report_id, void *buf, + u32 buf_len) { int rep_type; int ret; + u32 report_len; if (report_type == HID_INPUT_REPORT) { rep_type = GET_INPUT_REPORT; @@ -372,9 +377,17 @@ int quickspi_get_report(struct quickspi_device *qsdev, } qsdev->get_report_cmpl = false; - memcpy(buf, qsdev->report_buf, qsdev->report_len); + /* quickspi_handle_input_data() updates this from IRQ context. */ + report_len = READ_ONCE(qsdev->report_len); + if (report_len > buf_len) { + dev_err_once(qsdev->dev, "Get report response too big, %u vs %u\n", + report_len, buf_len); + return -EINVAL; + } - return qsdev->report_len; + memcpy(buf, qsdev->report_buf, report_len); + + return report_len; } int quickspi_set_report(struct quickspi_device *qsdev, diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.h b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.h index 775e29c1ed13..8a2338bee808 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.h +++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.h @@ -12,7 +12,7 @@ struct quickspi_device; void quickspi_handle_input_data(struct quickspi_device *qsdev, u32 buf_len); int quickspi_get_report(struct quickspi_device *qsdev, u8 report_type, - unsigned int report_id, void *buf); + unsigned int report_id, void *buf, u32 buf_len); int quickspi_set_report(struct quickspi_device *qsdev, u8 report_type, unsigned int report_id, void *buf, u32 buf_len); int quickspi_get_report_descriptor(struct quickspi_device *qsdev); diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c index 9a8449428170..7b4a58e1416d 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c @@ -1422,14 +1422,25 @@ static int thc_i2c_subip_pio_write(struct thc_device *dev, const u32 address, #define I2C_SUBIP_DMA_TDLR_DEFAULT 7 #define I2C_SUBIP_DMA_RDLR_DEFAULT 7 -static int thc_i2c_subip_set_speed(struct thc_device *dev, const u32 speed, - const u32 hcnt, const u32 lcnt) +static int thc_i2c_subip_bus_config(struct thc_device *dev, const struct thc_i2c_config *i2c_config) { u32 hcnt_offset, lcnt_offset; - u32 val; + u32 read_size = sizeof(u32); + u32 val = 0; int ret; - switch (speed) { + ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_TAR_OFFSET, &read_size, &val); + if (ret < 0) + return ret; + + val &= ~(THC_I2C_IC_TAR_IC_TAR | THC_I2C_IC_TAR_IC_10BITADDR_MASTER); + val |= FIELD_PREP(THC_I2C_IC_TAR_IC_10BITADDR_MASTER, i2c_config->addr_mode); + val |= FIELD_PREP(THC_I2C_IC_TAR_IC_TAR, i2c_config->target_addr); + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_TAR_OFFSET, sizeof(u32), &val); + if (ret < 0) + return ret; + + switch (i2c_config->speed) { case THC_I2C_STANDARD: hcnt_offset = THC_I2C_IC_SS_SCL_HCNT_OFFSET; lcnt_offset = THC_I2C_IC_SS_SCL_LCNT_OFFSET; @@ -1446,25 +1457,43 @@ static int thc_i2c_subip_set_speed(struct thc_device *dev, const u32 speed, break; default: - dev_err_once(dev->dev, "Unsupported i2c speed %d\n", speed); + dev_err_once(dev->dev, "Unsupported i2c speed %d\n", i2c_config->speed); ret = -EINVAL; return ret; } - ret = thc_i2c_subip_pio_write(dev, hcnt_offset, sizeof(u32), &hcnt); + ret = thc_i2c_subip_pio_write(dev, hcnt_offset, sizeof(u32), &i2c_config->scl_hcnt); if (ret < 0) return ret; - ret = thc_i2c_subip_pio_write(dev, lcnt_offset, sizeof(u32), &lcnt); + ret = thc_i2c_subip_pio_write(dev, lcnt_offset, sizeof(u32), &i2c_config->scl_lcnt); if (ret < 0) return ret; val = I2C_SUBIP_CON_DEFAULT & ~THC_I2C_IC_CON_SPEED; - val |= FIELD_PREP(THC_I2C_IC_CON_SPEED, speed); + val |= FIELD_PREP(THC_I2C_IC_CON_SPEED, i2c_config->speed); ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_CON_OFFSET, sizeof(u32), &val); if (ret < 0) return ret; + ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_SDA_HOLD_OFFSET, &read_size, &val); + if (ret < 0) + return ret; + + if (i2c_config->sda_tx_hold) { + val &= ~THC_I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD; + val |= FIELD_PREP(THC_I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD, i2c_config->sda_tx_hold); + } + + if (i2c_config->sda_rx_hold) { + val &= ~THC_I2C_IC_SDA_HOLD_IC_SDA_RX_HOLD; + val |= FIELD_PREP(THC_I2C_IC_SDA_HOLD_IC_SDA_RX_HOLD, i2c_config->sda_rx_hold); + } + + ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_SDA_HOLD_OFFSET, sizeof(u32), &val); + if (ret < 0) + return ret; + return 0; } @@ -1474,6 +1503,7 @@ static u32 i2c_subip_regs[] = { THC_I2C_IC_INTR_MASK_OFFSET, THC_I2C_IC_RX_TL_OFFSET, THC_I2C_IC_TX_TL_OFFSET, + THC_I2C_IC_SDA_HOLD_OFFSET, THC_I2C_IC_DMA_CR_OFFSET, THC_I2C_IC_DMA_TDLR_OFFSET, THC_I2C_IC_DMA_RDLR_OFFSET, @@ -1490,20 +1520,19 @@ static u32 i2c_subip_regs[] = { * thc_i2c_subip_init - Initialize and configure THC I2C subsystem * * @dev: The pointer of THC private device context - * @target_address: Slave address of touch device (TIC) - * @speed: I2C bus frequency speed mode - * @hcnt: I2C clock SCL high count - * @lcnt: I2C clock SCL low count + * @i2c_config: The pointer of THC I2C bus configure structure * * Return: 0 on success, other error codes on failed. */ -int thc_i2c_subip_init(struct thc_device *dev, const u32 target_address, - const u32 speed, const u32 hcnt, const u32 lcnt) +int thc_i2c_subip_init(struct thc_device *dev, const struct thc_i2c_config *i2c_config) { u32 read_size = sizeof(u32); u32 val; int ret; + if (!dev || !i2c_config) + return -EINVAL; + ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_ENABLE_OFFSET, &read_size, &val); if (ret < 0) return ret; @@ -1513,17 +1542,7 @@ int thc_i2c_subip_init(struct thc_device *dev, const u32 target_address, if (ret < 0) return ret; - ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_TAR_OFFSET, &read_size, &val); - if (ret < 0) - return ret; - - val &= ~THC_I2C_IC_TAR_IC_TAR; - val |= FIELD_PREP(THC_I2C_IC_TAR_IC_TAR, target_address); - ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_TAR_OFFSET, sizeof(u32), &val); - if (ret < 0) - return ret; - - ret = thc_i2c_subip_set_speed(dev, speed, hcnt, lcnt); + ret = thc_i2c_subip_bus_config(dev, i2c_config); if (ret < 0) return ret; diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h index 0db435335e24..be8a9605d02f 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h @@ -49,6 +49,26 @@ enum thc_int_type { THC_UNKNOWN_INT }; +/** + * struct thc_i2c_config - THC I2C bus configuration + * @target_addr: Slave address of touch device (TIC) + * @addr_mode: Slave address mode of touch device (TIC), 7bit or 10bit + * @speed: I2C bus frequency speed mode + * @scl_hcnt: I2C clock SCL high count + * @scl_lcnt: I2C clock SCL low count + * @sda_tx_hold: I2C Data SDA transmit hold period + * @sda_rx_hold: I2C Data SDA receive hold period + */ +struct thc_i2c_config { + u16 target_addr; + u8 addr_mode; + u32 speed; + u32 scl_hcnt; + u32 scl_lcnt; + u32 sda_tx_hold; + u32 sda_rx_hold; +}; + /** * struct thc_device - THC private device struct * @thc_regmap: MMIO regmap structure for accessing THC registers @@ -121,8 +141,7 @@ int thc_spi_write_config(struct thc_device *dev, u32 spi_freq_val, u32 io_mode, u32 opcode, u32 spi_wr_mps, u32 perf_limit); void thc_spi_input_output_address_config(struct thc_device *dev, u32 input_hdr_addr, u32 input_bdy_addr, u32 output_addr); -int thc_i2c_subip_init(struct thc_device *dev, const u32 target_address, - const u32 speed, const u32 hcnt, const u32 lcnt); +int thc_i2c_subip_init(struct thc_device *dev, const struct thc_i2c_config *i2c_config); int thc_i2c_subip_regs_save(struct thc_device *dev); int thc_i2c_subip_regs_restore(struct thc_device *dev); int thc_i2c_set_rx_max_size(struct thc_device *dev, u32 max_rx_size); diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c index 6ee675e0a738..7ceb8aeeccd3 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c @@ -561,6 +561,57 @@ static int thc_wait_for_dma_pause(struct thc_device *dev, enum thc_dma_channel c return 0; } +/** + * thc_rxdma_reset - Reset all read DMA engines + * + * @dev: The pointer of THC private device context + * + * This is a helper function to reset RxDMA configure. It's typically used + * for RxDMA recovery when fatal error happens. + * + * Return: 0 if successful or error code on failure. + */ +int thc_rxdma_reset(struct thc_device *dev) +{ + int ret; + + if (mutex_lock_interruptible(&dev->thc_bus_lock)) + return -EINTR; + + ret = thc_interrupt_quiesce(dev, true); + if (ret) { + dev_err(dev->dev, "Quiesce interrupt failed during RxDMA reset\n"); + goto end; + } + + ret = thc_wait_for_dma_pause(dev, THC_RXDMA1); + if (ret) { + dev_err(dev->dev, "Wait for RxDMA1 pause failed during RxDMA reset\n"); + goto end; + } + + ret = thc_wait_for_dma_pause(dev, THC_RXDMA2); + if (ret) { + dev_err(dev->dev, "Wait for RxDMA2 pause failed during RxDMA reset\n"); + goto end; + } + + thc_dma_unconfigure(dev); + + ret = thc_dma_configure(dev); + if (ret) { + dev_err(dev->dev, "Re-config DMA failed during RxDMA reset\n"); + goto end; + } + + thc_interrupt_quiesce(dev, false); + +end: + mutex_unlock(&dev->thc_bus_lock); + return ret; +} +EXPORT_SYMBOL_NS_GPL(thc_rxdma_reset, "INTEL_THC"); + static int read_dma_buffer(struct thc_device *dev, struct thc_dma_configuration *read_config, u8 prd_table_index, void *read_buff) diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h index 541d33995baf..715423453a9d 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h @@ -145,6 +145,7 @@ int thc_dma_allocate(struct thc_device *dev); int thc_dma_configure(struct thc_device *dev); void thc_dma_unconfigure(struct thc_device *dev); void thc_dma_release(struct thc_device *dev); +int thc_rxdma_reset(struct thc_device *dev); int thc_rxdma_read(struct thc_device *dev, enum thc_dma_channel dma_channel, void *read_buff, size_t *read_len, int *read_finished); int thc_swdma_read(struct thc_device *dev, void *write_buff, size_t write_len, diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h index c6d026686b7a..a21222543ce4 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h @@ -887,4 +887,7 @@ enum THC_I2C_SPEED_MODE { #define THC_I2C_IC_DMA_CR_RDMAE BIT(0) #define THC_I2C_IC_DMA_CR_TDMAE BIT(1) +#define THC_I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD GENMASK(15, 0) +#define THC_I2C_IC_SDA_HOLD_IC_SDA_RX_HOLD GENMASK(23, 16) + #endif /* _INTEL_THC_HW_H_ */ diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 5f4395f7c645..22951b7ecd17 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -1539,13 +1539,20 @@ static int pidff_check_autocenter(struct pidff_device *pidff, int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks) { struct pidff_device *pidff; - struct hid_input *hidinput = - list_entry(hid->inputs.next, struct hid_input, list); - struct input_dev *dev = hidinput->input; + struct hid_input *hidinput; + struct input_dev *dev; struct ff_device *ff; int max_effects; int error; + if (list_empty(&hid->inputs)) { + hid_err(hid, "no inputs found\n"); + return -ENODEV; + } + + hidinput = list_first_entry(&hid->inputs, struct hid_input, list); + dev = hidinput->input; + hid_dbg(hid, "starting pid init\n"); if (list_empty(&hid->report_enum[HID_OUTPUT_REPORT].report_list)) { 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; } diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index abee43158d84..169768e3d5bb 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -914,6 +914,21 @@ config INPUT_SOC_BUTTON_ARRAY To compile this driver as a module, choose M here: the module will be called soc_button_array. +config INPUT_AMD_SFH_TABLETMODE + tristate "AMD SFH tablet-mode switch" + depends on AMD_SFH_HID + select AUXILIARY_BUS + help + Expose SW_TABLET_MODE for AMD convertible laptops whose + operation-mode sensor is provided by the AMD Sensor Fusion Hub. + + Userspace components such as systemd-logind and modern desktop + environments react to SW_TABLET_MODE when the device is folded + into tablet posture. + + To compile this driver as a module, choose M here: the module + will be called amd_sfh_tabletmode. + config INPUT_DRV260X_HAPTICS tristate "TI DRV260X haptics support" depends on INPUT && I2C diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index e9f85ca20c33..1339619f0769 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_INPUT_AD714X_SPI) += ad714x-spi.o obj-$(CONFIG_INPUT_ADXL34X) += adxl34x.o obj-$(CONFIG_INPUT_ADXL34X_I2C) += adxl34x-i2c.o obj-$(CONFIG_INPUT_ADXL34X_SPI) += adxl34x-spi.o +obj-$(CONFIG_INPUT_AMD_SFH_TABLETMODE) += amd_sfh_tabletmode.o obj-$(CONFIG_INPUT_APANEL) += apanel.o obj-$(CONFIG_INPUT_ARIEL_PWRBUTTON) += ariel-pwrbutton.o obj-$(CONFIG_INPUT_ARIZONA_HAPTICS) += arizona-haptics.o diff --git a/drivers/input/misc/amd_sfh_tabletmode.c b/drivers/input/misc/amd_sfh_tabletmode.c new file mode 100644 index 000000000000..3931a1c6eb74 --- /dev/null +++ b/drivers/input/misc/amd_sfh_tabletmode.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AMD SFH tablet-mode switch driver + * + * Copyright (c) 2026, Advanced Micro Devices, Inc. + * All Rights Reserved. + * + * Author: Basavaraj Natikar + */ + +#include +#include +#include +#include +#include + +#define POLL_INTERVAL_MS 200 + +static void sfh_tm_poll(struct input_dev *input) +{ + struct amd_sfh_info info = {}; + + if (amd_get_sfh_info(&info, MT_OP_MODE)) + return; + + input_report_switch(input, SW_TABLET_MODE, + info.op_mode == SFH_MODE_TABLET); + input_sync(input); +} + +static int sfh_tm_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct device *dev = &auxdev->dev; + struct amd_sfh_info info = {}; + struct input_dev *input; + int error; + + error = amd_get_sfh_info(&info, MT_OP_MODE); + if (error) + return error == -EINVAL ? -ENODEV : error; + + input = devm_input_allocate_device(dev); + if (!input) + return -ENOMEM; + + input->name = "AMD SFH tablet mode switch"; + input->phys = "amd-sfh/tabletmode"; + input->id.bustype = BUS_HOST; + input->id.vendor = PCI_VENDOR_ID_AMD; + input_set_capability(input, EV_SW, SW_TABLET_MODE); + + error = input_setup_polling(input, sfh_tm_poll); + if (error) + return error; + input_set_poll_interval(input, POLL_INTERVAL_MS); + + sfh_tm_poll(input); + + error = input_register_device(input); + if (error) + return error; + + return 0; +} + +static const struct auxiliary_device_id sfh_tm_id_table[] = { + { .name = "amd_sfh.tabletmode" }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, sfh_tm_id_table); + +static struct auxiliary_driver sfh_tm_driver = { + .name = "tabletmode", + .id_table = sfh_tm_id_table, + .probe = sfh_tm_probe, +}; +module_auxiliary_driver(sfh_tm_driver); + +MODULE_DESCRIPTION("AMD SFH tablet mode switch"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/amd-pmf-io.h b/include/linux/amd-pmf-io.h index 55198d2875cc..dc59c43bd8f2 100644 --- a/include/linux/amd-pmf-io.h +++ b/include/linux/amd-pmf-io.h @@ -19,11 +19,13 @@ * @MT_HPD: Message ID to know the Human presence info from MP2 FW * @MT_ALS: Message ID to know the Ambient light info from MP2 FW * @MT_SRA: Message ID to know the SRA data from MP2 FW + * @MT_OP_MODE: Message ID to know the operating-mode (tablet/laptop) info */ enum sfh_message_type { MT_HPD, MT_ALS, MT_SRA, + MT_OP_MODE, }; /** @@ -44,12 +46,24 @@ enum sfh_hpd_info { * @user_present: Populates the user presence information * @platform_type: Operating modes (clamshell, flat, tent, etc.) * @laptop_placement: Device states (ontable, onlap, outbag) + * @op_mode: Operating-mode field (see enum sfh_dev_mode); used for tablet detection */ struct amd_sfh_info { u32 ambient_light; u8 user_present; u32 platform_type; u32 laptop_placement; + u32 op_mode; +}; + +/** + * enum sfh_dev_mode - SFH operating-mode field (sfh_op_mode.mode, bits 0-2) + * @SFH_MODE_LAPTOP: Device is in laptop/clamshell posture + * @SFH_MODE_TABLET: Device is in tablet posture + */ +enum sfh_dev_mode { + SFH_MODE_LAPTOP = 1, + SFH_MODE_TABLET = 3, }; enum laptop_placement { diff --git a/include/linux/hid.h b/include/linux/hid.h index b240baa95ab5..8d17b741638c 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -642,6 +642,7 @@ enum hid_battery_status { * @max: maximum battery value from HID descriptor * @report_type: HID report type (input/feature) * @report_id: HID report ID for this battery + * @report_offset: bit offset of the capacity field within its report * @charge_status: current charging status * @status: battery reporting status * @capacity: current battery capacity (0-100) @@ -657,6 +658,7 @@ struct hid_battery { __s32 max; __s32 report_type; __s32 report_id; + __s32 report_offset; __s32 charge_status; enum hid_battery_status status; __s32 capacity; @@ -1021,7 +1023,7 @@ extern void hid_unregister_driver(struct hid_driver *); extern void hidinput_hid_event(struct hid_device *, struct hid_field *, struct hid_usage *, __s32); extern void hidinput_report_event(struct hid_device *hid, struct hid_report *report); -extern int hidinput_connect(struct hid_device *hid, unsigned int force); +extern int hidinput_connect(struct hid_device *hid, unsigned int connect_mask); extern void hidinput_disconnect(struct hid_device *); void hidinput_reset_resume(struct hid_device *hid); diff --git a/scripts/coccinelle/hid/ff_race.cocci b/scripts/coccinelle/hid/ff_race.cocci new file mode 100644 index 000000000000..479f5d1e3184 --- /dev/null +++ b/scripts/coccinelle/hid/ff_race.cocci @@ -0,0 +1,34 @@ +/// Detect HID drivers that initialize force-feedback after hid_hw_start() +/// when HID_CONNECT_HIDINPUT is used. This is a lifecycle violation as +/// the input device is already registered. +// +// Confidence: High +// Copyright: (C) 2026 Gemini. GPLv2. + +virtual report + +@r@ +identifier probe_fn; +expression hdev, flags; +position p1, p2; +@@ + +probe_fn(struct hid_device *hdev, ...) { + <... + hid_hw_start@p1(hdev, flags) + ... + \(input_ff_create\|input_ff_create_memless\)@p2(...) + ...> +} + +@script:python depends on report@ +p1 << r.p1; +p2 << r.p2; +flags << r.flags; +@@ + +# Check if flags include HID_CONNECT_HIDINPUT (0x01) or HID_CONNECT_DEFAULT (0x0f) +# Note: HID_CONNECT_DEFAULT is 0x0f, HID_CONNECT_HIDINPUT is 0x01 +if "HID_CONNECT_HIDINPUT" in flags or "HID_CONNECT_DEFAULT" in flags: + msg = "WARNING: force-feedback initialized after hid_hw_start() with HID_CONNECT_HIDINPUT. Input device is already registered at this point. Use .input_configured() instead." + coccilib.report.print_report(p2[0], msg)