diff options
Diffstat (limited to 'drivers/hid')
119 files changed, 9945 insertions, 2257 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index ff2f580b660b..c43e824428f0 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 @@ -766,6 +792,15 @@ config HID_MEGAWORLD_FF Say Y here if you have a Mega World based game controller and want to have force feedback support for it. +config HID_RAKK + tristate "Rakk support" + help + Support for Rakk gaming peripherals. + + Fixes the HID report descriptor of the Rakk Dasig X mouse, + which declares USAGE_MAXIMUM=3 (buttons 1-3) while actually + sending 5 button bits. This causes side buttons to be ignored. + config HID_REDRAGON tristate "Redragon keyboards" help @@ -896,6 +931,19 @@ config HID_ORTEK - Ortek WKB-2000 - Skycable wireless presenter +config HID_OXP + tristate "OneXPlayer handheld controller configuration support" + depends on USB_HID + depends on LEDS_CLASS + depends on LEDS_CLASS_MULTICOLOR + depends on DMI + help + Say Y here if you would like to enable support for OneXPlayer handheld + devices that come with RGB LED rings around the joysticks and macro buttons. + + To compile this driver as a module, choose M here: the module will + be called hid-oxp. + config HID_PANTHERLORD tristate "Pantherlord/GreenAsia game controller" help @@ -1026,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. @@ -1057,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 @@ -1152,7 +1210,8 @@ config HID_STEELSERIES depends on USB_HID help Support for Steelseries SRW-S1 steering wheel, and the Steelseries - Arctis 1 Wireless for XBox headset. + Arctis 1 Wireless for XBox, Arctis 7 (2018), Arctis 9, and Arctis + Nova headsets. config HID_SUNPLUS tristate "Sunplus wireless desktop" @@ -1193,6 +1252,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 = KUNIT || KUNIT = y) + 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 0597fd6a4ffd..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,12 +93,14 @@ 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 obj-$(CONFIG_HID_NTRIG) += hid-ntrig.o obj-$(CONFIG_HID_NVIDIA_SHIELD) += hid-nvidia-shield.o obj-$(CONFIG_HID_ORTEK) += hid-ortek.o +obj-$(CONFIG_HID_OXP) += hid-oxp.o obj-$(CONFIG_HID_PRODIKEYS) += hid-prodikeys.o obj-$(CONFIG_HID_PANTHERLORD) += hid-pl.o obj-$(CONFIG_HID_PENMOUNT) += hid-penmount.o @@ -115,6 +118,7 @@ obj-$(CONFIG_HID_PLANTRONICS) += hid-plantronics.o obj-$(CONFIG_HID_PLAYSTATION) += hid-playstation.o obj-$(CONFIG_HID_PRIMAX) += hid-primax.o obj-$(CONFIG_HID_PXRC) += hid-pxrc.o +obj-$(CONFIG_HID_RAKK) += hid-rakk.o obj-$(CONFIG_HID_RAPOO) += hid-rapoo.o obj-$(CONFIG_HID_RAZER) += hid-razer.o obj-$(CONFIG_HID_REDRAGON) += hid-redragon.o @@ -132,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..3d29119a3765 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h +++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h @@ -10,13 +10,18 @@ #ifndef AMD_SFH_COMMON_H #define AMD_SFH_COMMON_H +#include <linux/auxiliary_bus.h> #include <linux/mutex.h> #include <linux/pci.h> +#include <linux/sizes.h> #include "amd_sfh_hid.h" #define PCI_DEVICE_ID_AMD_MP2 0x15E4 #define PCI_DEVICE_ID_AMD_MP2_1_1 0x164A +/* The BAR 2 size must cover the highest register offset (0x10958) */ +#define AMD_SFH_MIN_BAR_SIZE SZ_128K + #define AMD_C2P_MSG(regno) (0x10500 + ((regno) * 4)) #define AMD_P2C_MSG(regno) (0x10680 + ((regno) * 4)) @@ -35,6 +40,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 +74,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 +112,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..061a63519d44 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 <Basavaraj.Natikar@amd.com> */ +#include <linux/auxiliary_bus.h> #include <linux/bitops.h> #include <linux/delay.h> #include <linux/devm-helpers.h> #include <linux/dma-mapping.h> #include <linux/dmi.h> +#include <linux/idr.h> #include <linux/interrupt.h> #include <linux/io-64-nonatomic-lo-hi.h> #include <linux/iopoll.h> @@ -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,10 +492,21 @@ 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; + if (!(pci_resource_flags(pdev, 2) & IORESOURCE_MEM)) { + dev_err(&pdev->dev, "BAR 2 is not IORESOURCE_MEM\n"); + return -ENODEV; + } + + if (pci_resource_len(pdev, 2) < AMD_SFH_MIN_BAR_SIZE) { + dev_err(&pdev->dev, "BAR 2 is too small\n"); + return -EINVAL; + } + rc = pcim_iomap_regions(pdev, BIT(2), DRIVER_NAME); if (rc) return rc; @@ -471,8 +527,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 +597,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..60c185a9cd50 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 @@ -7,13 +7,16 @@ * * Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com> */ +#include <linux/amd-pmf.h> #include <linux/amd-pmf-io.h> +#include <linux/cleanup.h> #include <linux/io-64-nonatomic-lo-hi.h> #include <linux/iopoll.h> #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 +81,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; @@ -102,20 +138,20 @@ static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement) *platform_type = mode.op_mode.devicemode; if (mode.op_mode.ontablestate == 1) { - *laptop_placement = ON_TABLE; + *laptop_placement = AMD_PMF_ON_TABLE; } else if (mode.op_mode.ontablestate == 2) { - *laptop_placement = ON_LAP_MOTION; + *laptop_placement = AMD_PMF_ON_LAP_MOTION; } else if (mode.op_mode.inbagstate == 1) { - *laptop_placement = IN_BAG; + *laptop_placement = AMD_PMF_IN_BAG; } else if (mode.op_mode.outbagstate == 1) { - *laptop_placement = OUT_OF_BAG; + *laptop_placement = AMD_PMF_OUT_OF_BAG; } else if (mode.op_mode.ontablestate == 0 || mode.op_mode.inbagstate == 0 || mode.op_mode.outbagstate == 0) { - *laptop_placement = LP_UNKNOWN; + *laptop_placement = AMD_PMF_LP_UNKNOWN; pr_warn_once("Unknown laptop placement\n"); } else if (mode.op_mode.ontablestate == 3 || mode.op_mode.inbagstate == 3 || mode.op_mode.outbagstate == 3) { - *laptop_placement = LP_UNDEFINED; + *laptop_placement = AMD_PMF_LP_UNDEFINED; pr_warn_once("Undefined laptop placement\n"); } @@ -160,6 +196,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 +207,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/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index 50c7b45c59e3..d46779b63660 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -17,6 +17,7 @@ #include <linux/kfifo.h> #include <linux/minmax.h> #include <linux/module.h> +#include <linux/overflow.h> #include "hid_bpf_dispatch.h" const struct hid_ops *hid_ops; @@ -24,7 +25,8 @@ EXPORT_SYMBOL(hid_ops); u8 * dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type, u8 *data, - u32 *size, int interrupt, u64 source, bool from_bpf) + size_t *buf_size, u32 *size, int interrupt, u64 source, + bool from_bpf) { struct hid_bpf_ctx_kern ctx_kern = { .ctx = { @@ -74,6 +76,7 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type *size = ret; } + *buf_size = ctx_kern.ctx.allocated_size; return ctx_kern.data; } EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event); @@ -294,10 +297,12 @@ __bpf_kfunc __u8 * hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t rdwr_buf_size) { struct hid_bpf_ctx_kern *ctx_kern; + size_t end; ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx); - if (rdwr_buf_size + offset > ctx->allocated_size) + if (check_add_overflow(rdwr_buf_size, offset, &end) || + end > ctx->allocated_size) return NULL; return ctx_kern->data + offset; @@ -354,7 +359,7 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx) static int __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, - enum hid_report_type rtype) + enum hid_report_type rtype, bool hw_request) { struct hid_report_enum *report_enum; struct hid_report *report; @@ -383,6 +388,10 @@ __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, report_len = hid_report_len(report); + /* unnumbered reports need to have a report ID reserved in the first byte */ + if (hw_request && report_enum->numbered == 0) + report_len += 1; + if (*buf__sz > report_len) *buf__sz = report_len; @@ -415,7 +424,7 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz, return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype, true); if (ret) return ret; @@ -475,7 +484,7 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz) return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT, true); if (ret) return ret; @@ -501,11 +510,11 @@ __hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *b return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, type); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, type, false); if (ret) return ret; - return hid_ops->hid_input_report(ctx->hid, type, buf, size, 0, (u64)(long)ctx, true, + return hid_ops->hid_input_report(ctx->hid, type, buf, size, size, 0, (u64)(long)ctx, true, lock_already_taken); } @@ -585,11 +594,11 @@ static const struct btf_kfunc_id_set hid_bpf_kfunc_set = { /* for syscall HID-BPF */ BTF_KFUNCS_START(hid_bpf_syscall_kfunc_ids) -BTF_ID_FLAGS(func, hid_bpf_allocate_context, KF_ACQUIRE | KF_RET_NULL) -BTF_ID_FLAGS(func, hid_bpf_release_context, KF_RELEASE) -BTF_ID_FLAGS(func, hid_bpf_hw_request) -BTF_ID_FLAGS(func, hid_bpf_hw_output_report) -BTF_ID_FLAGS(func, hid_bpf_input_report) +BTF_ID_FLAGS(func, hid_bpf_allocate_context, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE) +BTF_ID_FLAGS(func, hid_bpf_release_context, KF_RELEASE | KF_SLEEPABLE) +BTF_ID_FLAGS(func, hid_bpf_hw_request, KF_SLEEPABLE) +BTF_ID_FLAGS(func, hid_bpf_hw_output_report, KF_SLEEPABLE) +BTF_ID_FLAGS(func, hid_bpf_input_report, KF_SLEEPABLE) BTF_KFUNCS_END(hid_bpf_syscall_kfunc_ids) static const struct btf_kfunc_id_set hid_bpf_syscall_kfunc_set = { diff --git a/drivers/hid/bpf/hid_bpf_struct_ops.c b/drivers/hid/bpf/hid_bpf_struct_ops.c index 702c22fae136..c90b68956cb3 100644 --- a/drivers/hid/bpf/hid_bpf_struct_ops.c +++ b/drivers/hid/bpf/hid_bpf_struct_ops.c @@ -62,6 +62,10 @@ struct hid_bpf_offset_write_range { u32 end; }; +struct hid_bpf_ctx__safe_trusted { + struct hid_device *hid; +}; + static int hid_bpf_ops_btf_struct_access(struct bpf_verifier_log *log, const struct bpf_reg_state *reg, int off, int size) @@ -86,6 +90,8 @@ static int hid_bpf_ops_btf_struct_access(struct bpf_verifier_log *log, const char *cur = NULL; int i; + BTF_TYPE_EMIT(struct hid_bpf_ctx__safe_trusted); + t = btf_type_by_id(reg->btf, reg->btf_id); for (i = 0; i < ARRAY_SIZE(write_ranges); i++) { @@ -250,6 +256,11 @@ static void hid_bpf_unreg(void *kdata, struct bpf_link *link) mutex_lock(&hdev->bpf.prog_list_lock); + if (!ops->hdev) { + mutex_unlock(&hdev->bpf.prog_list_lock); + return; + } + list_del_rcu(&ops->list); synchronize_srcu(&hdev->bpf.srcu); ops->hdev = NULL; @@ -310,13 +321,17 @@ static struct bpf_struct_ops bpf_hid_bpf_ops = { void __hid_bpf_ops_destroy_device(struct hid_device *hdev) { struct hid_bpf_ops *e; + int count = 0; - rcu_read_lock(); - list_for_each_entry_rcu(e, &hdev->bpf.prog_list, list) { - hid_put_device(hdev); + mutex_lock(&hdev->bpf.prog_list_lock); + list_for_each_entry(e, &hdev->bpf.prog_list, list) { e->hdev = NULL; + count++; } - rcu_read_unlock(); + mutex_unlock(&hdev->bpf.prog_list_lock); + + while (count--) + hid_put_device(hdev); } static int __init hid_bpf_struct_ops_init(void) diff --git a/drivers/hid/bpf/progs/Huion__Inspiroy-Frego-M.bpf.c b/drivers/hid/bpf/progs/Huion__Inspiroy-Frego-M.bpf.c new file mode 100644 index 000000000000..e6ba2295dc77 --- /dev/null +++ b/drivers/hid/bpf/progs/Huion__Inspiroy-Frego-M.bpf.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "vmlinux.h" +#include "hid_bpf.h" +#include "hid_bpf_helpers.h" +#include <bpf/bpf_tracing.h> + +/* + * Huion Inspiroy Frego M Pen Tablet + * Model L610 + * 256c:8251 (Bluetooth) + * 256c:2012 (USB) + */ +#define VID_HUION 0x256C +#define PID_INSPIROY_FREGO_M 0x8251 +#define PID_L610 0x2012 + +#define PEN_RDESC_SIZE 125 +#define SECONDARY_SWITCH_OFFSET 17 + +HID_BPF_CONFIG( + HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_HUION, PID_INSPIROY_FREGO_M), + HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HUION, PID_L610) +); + +/* + * The pen descriptor reports the second side button as Secondary Tip Switch + * instead of Secondary Barrel Switch. + * + * Relevant part of the original pen report descriptor: + * + * 0x09, 0x42, // Usage (Tip Switch) 12 + * 0x09, 0x44, // Usage (Barrel Switch) 14 + * 0x09, 0x43, // Usage (Secondary Tip Switch) 16 <- change to 0x5a + * 0x09, 0x3c, // Usage (Invert) 18 + * 0x09, 0x45, // Usage (Eraser) 20 + * 0x15, 0x00, // Logical Minimum (0) 22 + * 0x25, 0x01, // Logical Maximum (1) 24 + */ +SEC(HID_BPF_RDESC_FIXUP) +int BPF_PROG(fix_secondary_barrel_rdesc, struct hid_bpf_ctx *hctx) +{ + __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */); + + if (!data) + return 0; /* EPERM check */ + + if (hctx->size != PEN_RDESC_SIZE) + return 0; + + if (data[0] != 0x05 || data[1] != 0x0d || /* Usage Page (Digitizers) */ + data[2] != 0x09 || data[3] != 0x02 || /* Usage (Pen) */ + data[16] != 0x09 || + data[SECONDARY_SWITCH_OFFSET] != 0x43) /* Secondary Tip Switch */ + return 0; + + data[SECONDARY_SWITCH_OFFSET] = 0x5a; + + return 0; +} + +HID_BPF_OPS(fix_secondary_barrel) = { + .hid_rdesc_fixup = (void *)fix_secondary_barrel_rdesc, +}; + +SEC("syscall") +int probe(struct hid_bpf_probe_args *ctx) +{ + ctx->retval = ctx->rdesc_size != PEN_RDESC_SIZE; + if (ctx->retval) { + ctx->retval = -EINVAL; + return 0; + } + + if (ctx->rdesc[0] != 0x05 || ctx->rdesc[1] != 0x0d || /* Usage Page (Digitizers) */ + ctx->rdesc[2] != 0x09 || ctx->rdesc[3] != 0x02 || /* Usage (Pen) */ + ctx->rdesc[16] != 0x09 || + ctx->rdesc[SECONDARY_SWITCH_OFFSET] != 0x43) { /* Secondary Tip Switch */ + ctx->retval = -EINVAL; + return 0; + } + + ctx->retval = 0; + + return 0; +} + +char _license[] SEC("license") = "GPL"; diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index 67179e3fe39b..0556cb5645eb 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -407,6 +407,8 @@ static int u1_raw_event(struct alps_dev *hdata, u8 *data, int size) return 1; case U1_SP_ABSOLUTE_REPORT_ID: + if (!hdata->input2) + return 0; sp_x = get_unaligned_le16(data+2); sp_y = get_unaligned_le16(data+4); @@ -738,7 +740,6 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) goto exit; } - data->input2 = input2; input2->phys = input->phys; input2->name = "DualPoint Stick"; input2->id.bustype = BUS_I2C; @@ -762,11 +763,12 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) __set_bit(INPUT_PROP_POINTER, input2->propbit); __set_bit(INPUT_PROP_POINTING_STICK, input2->propbit); - if (input_register_device(data->input2)) { + if (input_register_device(input2)) { input_free_device(input2); ret = -ENOENT; goto exit; } + data->input2 = input2; } exit: @@ -823,6 +825,24 @@ static int alps_probe(struct hid_device *hdev, const struct hid_device_id *id) return 0; } +static void alps_remove(struct hid_device *hdev) +{ + struct alps_dev *data = hid_get_drvdata(hdev); + + /* + * input2 ("DualPoint Stick") is allocated separately and is not + * tracked in hdev->inputs, so the default remove path + * (hid_hw_stop -> hidinput_disconnect) does not unregister it. + * + * Stop the device first so that no URB callback can touch input2 + * while it is being unregistered, then drop it explicitly. + */ + hid_hw_stop(hdev); + + if (data->input2) + input_unregister_device(data->input2); +} + static const struct hid_device_id alps_id[] = { { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) }, @@ -845,6 +865,7 @@ static struct hid_driver alps_driver = { .input_configured = alps_input_configured, .resume = pm_ptr(alps_post_resume), .reset_resume = pm_ptr(alps_post_reset), + .remove = alps_remove, }; module_hid_driver(alps_driver); 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. */ + + 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, 0, 0); + 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-appleir.c b/drivers/hid/hid-appleir.c index 5e8ced7bc05a..adaa44a858ed 100644 --- a/drivers/hid/hid-appleir.c +++ b/drivers/hid/hid-appleir.c @@ -109,9 +109,10 @@ struct appleir { struct hid_device *hid; unsigned short keymap[ARRAY_SIZE(appleir_key_table)]; struct timer_list key_up_timer; /* timer for key up */ - spinlock_t lock; /* protects .current_key */ + spinlock_t lock; /* protects .current_key, .removing */ int current_key; /* the currently pressed key */ int prev_key_idx; /* key index in a 2 packets message */ + bool removing; /* set during teardown; gates input_dev access */ }; static int get_key(int data) @@ -172,7 +173,7 @@ static void key_up_tick(struct timer_list *t) unsigned long flags; spin_lock_irqsave(&appleir->lock, flags); - if (appleir->current_key) { + if (!appleir->removing && appleir->current_key) { key_up(hid, appleir, appleir->current_key); appleir->current_key = 0; } @@ -195,6 +196,10 @@ static int appleir_raw_event(struct hid_device *hid, struct hid_report *report, int index; spin_lock_irqsave(&appleir->lock, flags); + if (appleir->removing) { + spin_unlock_irqrestore(&appleir->lock, flags); + goto out; + } /* * If we already have a key down, take it up before marking * this one down @@ -229,17 +234,25 @@ static int appleir_raw_event(struct hid_device *hid, struct hid_report *report, appleir->prev_key_idx = 0; if (!memcmp(data, keyrepeat, sizeof(keyrepeat))) { - key_down(hid, appleir, appleir->current_key); - /* - * Remote doesn't do key up, either pull them up, in the test - * above, or here set a timer which pulls them up after 1/8 s - */ - mod_timer(&appleir->key_up_timer, jiffies + HZ / 8); + spin_lock_irqsave(&appleir->lock, flags); + if (!appleir->removing) { + key_down(hid, appleir, appleir->current_key); + /* + * Remote doesn't do key up, either pull them up, in + * the test above, or here set a timer which pulls them + * up after 1/8 s + */ + mod_timer(&appleir->key_up_timer, jiffies + HZ / 8); + } + spin_unlock_irqrestore(&appleir->lock, flags); goto out; } if (!memcmp(data, flatbattery, sizeof(flatbattery))) { - battery_flat(appleir); + spin_lock_irqsave(&appleir->lock, flags); + if (!appleir->removing) + battery_flat(appleir); + spin_unlock_irqrestore(&appleir->lock, flags); /* Fall through */ } @@ -318,8 +331,20 @@ fail: static void appleir_remove(struct hid_device *hid) { struct appleir *appleir = hid_get_drvdata(hid); + unsigned long flags; + + /* + * Mark the driver as tearing down so that any concurrent raw_event + * (e.g. from a USB URB completion that hid_hw_stop() has not yet + * killed) and the key_up_timer softirq stop touching input_dev + * before hid_hw_stop() frees it via hidinput_disconnect(). + */ + spin_lock_irqsave(&appleir->lock, flags); + appleir->removing = true; + spin_unlock_irqrestore(&appleir->lock, flags); + + timer_shutdown_sync(&appleir->key_up_timer); hid_hw_stop(hid); - timer_delete_sync(&appleir->key_up_timer); } static const struct hid_device_id appleir_devices[] = { diff --git a/drivers/hid/hid-appletb-kbd.c b/drivers/hid/hid-appletb-kbd.c index 0fdc0968b9ef..5cc27066f602 100644 --- a/drivers/hid/hid-appletb-kbd.c +++ b/drivers/hid/hid-appletb-kbd.c @@ -17,7 +17,7 @@ #include <linux/module.h> #include <linux/string.h> #include <linux/backlight.h> -#include <linux/timer.h> +#include <linux/workqueue.h> #include <linux/input/sparse-keymap.h> #include "hid-ids.h" @@ -62,7 +62,8 @@ struct appletb_kbd { struct input_handle kbd_handle; struct input_handle tpd_handle; struct backlight_device *backlight_dev; - struct timer_list inactivity_timer; + struct delayed_work inactivity_work; + struct work_struct restore_brightness_work; bool has_dimmed; bool has_turned_off; u8 saved_mode; @@ -164,16 +165,18 @@ static int appletb_tb_key_to_slot(unsigned int code) } } -static void appletb_inactivity_timer(struct timer_list *t) +static void appletb_inactivity_work(struct work_struct *work) { - struct appletb_kbd *kbd = timer_container_of(kbd, t, inactivity_timer); + struct appletb_kbd *kbd = container_of(to_delayed_work(work), + struct appletb_kbd, + inactivity_work); if (kbd->backlight_dev && appletb_tb_autodim) { if (!kbd->has_dimmed) { backlight_device_set_brightness(kbd->backlight_dev, 1); kbd->has_dimmed = true; - mod_timer(&kbd->inactivity_timer, - jiffies + secs_to_jiffies(appletb_tb_idle_timeout)); + 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); kbd->has_turned_off = true; @@ -181,16 +184,25 @@ static void appletb_inactivity_timer(struct timer_list *t) } } +static void appletb_restore_brightness_work(struct work_struct *work) +{ + struct appletb_kbd *kbd = container_of(work, struct appletb_kbd, + restore_brightness_work); + + if (kbd->backlight_dev) + backlight_device_set_brightness(kbd->backlight_dev, 2); +} + static void reset_inactivity_timer(struct appletb_kbd *kbd) { if (kbd->backlight_dev && appletb_tb_autodim) { if (kbd->has_dimmed || kbd->has_turned_off) { - backlight_device_set_brightness(kbd->backlight_dev, 2); kbd->has_dimmed = false; kbd->has_turned_off = false; + schedule_work(&kbd->restore_brightness_work); } - mod_timer(&kbd->inactivity_timer, - jiffies + secs_to_jiffies(appletb_tb_dim_timeout)); + mod_delayed_work(system_dfl_wq, &kbd->inactivity_work, + secs_to_jiffies(appletb_tb_dim_timeout)); } } @@ -408,9 +420,11 @@ static int appletb_kbd_probe(struct hid_device *hdev, const struct hid_device_id dev_err_probe(dev, -ENODEV, "Failed to get backlight device\n"); } else { backlight_device_set_brightness(kbd->backlight_dev, 2); - timer_setup(&kbd->inactivity_timer, appletb_inactivity_timer, 0); - mod_timer(&kbd->inactivity_timer, - jiffies + secs_to_jiffies(appletb_tb_dim_timeout)); + INIT_DELAYED_WORK(&kbd->inactivity_work, appletb_inactivity_work); + INIT_WORK(&kbd->restore_brightness_work, + appletb_restore_brightness_work); + mod_delayed_work(system_dfl_wq, &kbd->inactivity_work, + secs_to_jiffies(appletb_tb_dim_timeout)); } kbd->inp_handler.event = appletb_kbd_inp_event; @@ -440,13 +454,14 @@ static int appletb_kbd_probe(struct hid_device *hdev, const struct hid_device_id unregister_handler: input_unregister_handler(&kbd->inp_handler); close_hw: - if (kbd->backlight_dev) { - put_device(&kbd->backlight_dev->dev); - timer_delete_sync(&kbd->inactivity_timer); - } hid_hw_close(hdev); stop_hw: hid_hw_stop(hdev); + if (kbd->backlight_dev) { + cancel_delayed_work_sync(&kbd->inactivity_work); + cancel_work_sync(&kbd->restore_brightness_work); + put_device(&kbd->backlight_dev->dev); + } return ret; } @@ -457,13 +472,14 @@ static void appletb_kbd_remove(struct hid_device *hdev) appletb_kbd_set_mode(kbd, APPLETB_KBD_MODE_OFF); input_unregister_handler(&kbd->inp_handler); + hid_hw_close(hdev); + hid_hw_stop(hdev); + if (kbd->backlight_dev) { + cancel_delayed_work_sync(&kbd->inactivity_work); + cancel_work_sync(&kbd->restore_brightness_work); put_device(&kbd->backlight_dev->dev); - timer_delete_sync(&kbd->inactivity_timer); } - - hid_hw_close(hdev); - hid_hw_stop(hdev); } static int appletb_kbd_suspend(struct hid_device *hdev, pm_message_t msg) diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 3f5e96900b67..c38a2b3b33d9 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_obj(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_obj(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 }; + const u8 buf[FEATURE_KBD_REPORT_SIZE] = { FEATURE_KBD_REPORT_ID, 0xd0, 0x4e, !!enabled }; + int ret; - return asus_kbd_set_report(hdev, buf, sizeof(buf)); + 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_sync_fn_lock(struct work_struct *work) +static void asus_kbd_set_brightness(struct hid_device *hdev, u8 brightness) { - struct asus_drvdata *drvdata = - container_of(work, struct asus_drvdata, fn_lock_sync_work); + const u8 buf[FEATURE_KBD_REPORT_SIZE] = { + FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, brightness + }; + int ret; - asus_kbd_set_fn_lock(drvdata->hdev, drvdata->fn_lock); + 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_schedule_work(struct asus_kbd_leds *led) +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); - if (!led->removed) - schedule_work(&led->work); - spin_unlock_irqrestore(&led->lock, flags); + drvdata->kbd_backlight_brightness = brightness; + + action = kzalloc_obj(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_kbd_backlight_set(struct asus_hid_listener *listener, - int brightness) +static void asus_work(struct work_struct *work) { - struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds, - listener); + struct asus_worker *worker = container_of(work, struct asus_worker, work); + struct asus_work_action *action = NULL; unsigned long flags; - spin_lock_irqsave(&led->lock, flags); - led->brightness = brightness; - spin_unlock_irqrestore(&led->lock, 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); - asus_schedule_work(led); + /* 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 void asus_kbd_backlight_work(struct work_struct *work) +static int asus_worker_create(struct hid_device *hdev, struct asus_drvdata *drvdata) { - struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work); - u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 }; - int ret; + 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(&led->lock, flags); - buf[4] = led->brightness; - spin_unlock_irqrestore(&led->lock, 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); - 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); + 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 @@ err_stop_hw: 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); } @@ -1478,6 +1671,9 @@ 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, USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1), QUIRK_USE_KBD_BACKLIGHT }, @@ -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 <x0r@dv-life.ru>\n"); @@ -118,7 +112,8 @@ err_free_mem: 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-belkin.c b/drivers/hid/hid-belkin.c index 75aaed35ee9f..84695115d37b 100644 --- a/drivers/hid/hid-belkin.c +++ b/drivers/hid/hid-belkin.c @@ -27,7 +27,8 @@ static int belkin_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { - unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + const struct hid_device_id *id = hid_get_drvdata(hdev); + unsigned long quirks = id->driver_data; if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER || !(quirks & BELKIN_WKBD)) @@ -48,7 +49,7 @@ static int belkin_probe(struct hid_device *hdev, const struct hid_device_id *id) unsigned long quirks = id->driver_data; int ret; - hid_set_drvdata(hdev, (void *)quirks); + hid_set_drvdata(hdev, (void *)id); ret = hid_parse(hdev); if (ret) { 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 <huangbobupt@163.com>\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; - } - - if (list_empty(&hid->inputs)) { - hid_err(hid, "no inputs found\n"); - error = -ENODEV; - goto error_hw_stop; + return -ENODEV; } - 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); + set_bit(FF_RUMBLE, input_dev->ffbit); - 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 61afec5915ec..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); @@ -2033,24 +2040,39 @@ int __hid_request(struct hid_device *hid, struct hid_report *report, } EXPORT_SYMBOL_GPL(__hid_request); -int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size, - int interrupt) +int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data, + size_t bufsize, u32 size, int interrupt) { struct hid_report_enum *report_enum = hid->report_enum + type; struct hid_report *report; struct hid_driver *hdrv; int max_buffer_size = HID_MAX_BUFFER_SIZE; u32 rsize, csize = size; + size_t bsize = bufsize; u8 *cdata = data; int ret = 0; + if (report_enum->numbered && (size < 1 || bufsize < 1)) { + hid_warn_ratelimited(hid, + "Event data for numbered report is too short (%d vs %zu)\n", + size, bufsize); + return -EINVAL; + } + report = hid_get_report(report_enum, data); if (!report) - goto out; + return 0; + + if (unlikely(bsize < csize)) { + hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %zu)\n", + report->id, csize, bsize); + return -EINVAL; + } if (report_enum->numbered) { cdata++; csize--; + bsize--; } rsize = hid_compute_report_size(report); @@ -2063,11 +2085,16 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 * else if (rsize > max_buffer_size) rsize = max_buffer_size; + if (bsize < rsize) { + hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %zu)\n", + report->id, rsize, bsize); + return -EINVAL; + } + if (csize < rsize) { - hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %d)\n", - report->id, rsize, csize); - ret = -EINVAL; - goto out; + dbg_hid("report %d is too short, (%d < %d)\n", report->id, + csize, rsize); + memset(cdata + csize, 0, rsize - csize); } if ((hid->claimed & HID_CLAIMED_HIDDEV) && hid->hiddev_report_event) @@ -2075,7 +2102,7 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 * if (hid->claimed & HID_CLAIMED_HIDRAW) { ret = hidraw_report_event(hid, data, size); if (ret) - goto out; + return ret; } if (hid->claimed != HID_CLAIMED_HIDRAW && report->maxfield) { @@ -2087,15 +2114,15 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 * if (hid->claimed & HID_CLAIMED_INPUT) hidinput_report_event(hid, report); -out: + return ret; } EXPORT_SYMBOL_GPL(hid_report_raw_event); static int __hid_input_report(struct hid_device *hid, enum hid_report_type type, - u8 *data, u32 size, int interrupt, u64 source, bool from_bpf, - bool lock_already_taken) + u8 *data, size_t bufsize, u32 size, int interrupt, u64 source, + bool from_bpf, bool lock_already_taken) { struct hid_report_enum *report_enum; struct hid_driver *hdrv; @@ -2120,7 +2147,8 @@ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type, report_enum = hid->report_enum + type; hdrv = hid->driver; - data = dispatch_hid_bpf_device_event(hid, type, data, &size, interrupt, source, from_bpf); + data = dispatch_hid_bpf_device_event(hid, type, data, &bufsize, &size, interrupt, + source, from_bpf); if (IS_ERR(data)) { ret = PTR_ERR(data); goto unlock; @@ -2149,7 +2177,7 @@ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type, goto unlock; } - ret = hid_report_raw_event(hid, type, data, size, interrupt); + ret = hid_report_raw_event(hid, type, data, bufsize, size, interrupt); unlock: if (!lock_already_taken) @@ -2167,16 +2195,41 @@ unlock: * @interrupt: distinguish between interrupt and control transfers * * This is data entry for lower layers. + * Legacy, please use hid_safe_input_report() instead. */ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size, int interrupt) { - return __hid_input_report(hid, type, data, size, interrupt, 0, + return __hid_input_report(hid, type, data, size, size, interrupt, 0, false, /* from_bpf */ false /* lock_already_taken */); } EXPORT_SYMBOL_GPL(hid_input_report); +/** + * hid_safe_input_report - report data from lower layer (usb, bt...) + * + * @hid: hid device + * @type: HID report type (HID_*_REPORT) + * @data: report contents + * @bufsize: allocated size of the data buffer + * @size: useful size of data parameter + * @interrupt: distinguish between interrupt and control transfers + * + * This is data entry for lower layers. + * Please use this function instead of the non safe version because we provide + * here the size of the buffer, allowing hid-core to make smarter decisions + * regarding the incoming buffer. + */ +int hid_safe_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, + size_t bufsize, u32 size, int interrupt) +{ + return __hid_input_report(hid, type, data, bufsize, size, interrupt, 0, + false, /* from_bpf */ + false /* lock_already_taken */); +} +EXPORT_SYMBOL_GPL(hid_safe_input_report); + bool hid_match_one_id(const struct hid_device *hdev, const struct hid_device_id *id) { @@ -2266,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 && @@ -2289,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"); @@ -2401,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); } @@ -2796,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; @@ -2861,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, @@ -2873,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..7be8c974d11e 100644 --- a/drivers/hid/hid-corsair-void.c +++ b/drivers/hid/hid-corsair-void.c @@ -51,20 +51,23 @@ /* Receiver report information: (ID 102) */ /* -------------------------------------------------------------------------- */ /* - * When queried, the recevier responds with 4 bytes to describe the firmware - * The first 2 bytes are for the receiver, the second 2 are the headset + * When queried, the receiver responds with 5 bytes to describe the firmware + * The first byte is the ID, then 2 bytes for the receiver, then 2 for the headset * The headset firmware version will be 0 if no headset is connected * - * 0: Recevier firmware major version + * 0: Report ID + * 102 for the firmware packet + * + * 1: Receiver firmware major version * Major version of the receiver's firmware * - * 1: Recevier firmware minor version + * 2: Receiver firmware minor version * Minor version of the receiver's firmware * - * 2: Headset firmware major version + * 3: Headset firmware major version * Major version of the headset's firmware * - * 3: Headset firmware minor version + * 4: Headset firmware minor version * Minor version of the headset's firmware */ /* -------------------------------------------------------------------------- */ @@ -92,6 +95,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 +748,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 +763,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-cp2112.c b/drivers/hid/hid-cp2112.c index 803b883ae875..04379db93571 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -29,6 +29,18 @@ #include <linux/usb/ch9.h> #include "hid-ids.h" +/** + * enum cp2112_child_acpi_cell_addrs - Child ACPI addresses for CP2112 sub-functions + * Note that the enum values are explicitly defined, as this defines the interface + * between ACPI and Linux + * @CP2112_I2C_ADR: Address for I2C node + * @CP2112_GPIO_ADR: Address for GPIO node + */ +enum cp2112_child_acpi_cell_addrs { + CP2112_I2C_ADR = 0, + CP2112_GPIO_ADR = 1, +}; + #define CP2112_REPORT_MAX_LENGTH 64 #define CP2112_GPIO_CONFIG_LENGTH 5 #define CP2112_GPIO_GET_LENGTH 2 @@ -1208,7 +1220,11 @@ static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id) struct cp2112_device *dev; u8 buf[3]; struct cp2112_smbus_config_report config; + struct fwnode_handle *cp2112_fwnode; + struct fwnode_handle *child; struct gpio_irq_chip *girq; + struct i2c_timings timings; + u32 addr; int ret; dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL); @@ -1226,6 +1242,28 @@ static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id) return ret; } + cp2112_fwnode = dev_fwnode(&hdev->dev); + if (is_acpi_device_node(cp2112_fwnode)) { + fwnode_for_each_child_node(cp2112_fwnode, child) { + ret = acpi_get_local_address(ACPI_HANDLE_FWNODE(child), &addr); + if (ret) + continue; + + switch (addr) { + case CP2112_I2C_ADR: + device_set_node(&dev->adap.dev, child); + break; + case CP2112_GPIO_ADR: + dev->gc.fwnode = child; + break; + } + } + } else if (is_of_node(cp2112_fwnode)) { + child = fwnode_get_named_child_node(cp2112_fwnode, "i2c"); + device_set_node(&dev->adap.dev, child); + fwnode_handle_put(child); + } + ret = hid_parse(hdev); if (ret) { hid_err(hdev, "parse failed\n"); @@ -1271,6 +1309,9 @@ static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id) goto err_power_normal; } + i2c_parse_fw_timings(&dev->adap.dev, &timings, true); + + config.clock_speed = cpu_to_be32(timings.bus_freq_hz); config.retry_time = cpu_to_be16(1); ret = cp2112_hid_output(hdev, (u8 *)&config, sizeof(config), diff --git a/drivers/hid/hid-cypress.c b/drivers/hid/hid-cypress.c index 98548201feec..f18fddc176d0 100644 --- a/drivers/hid/hid-cypress.c +++ b/drivers/hid/hid-cypress.c @@ -25,6 +25,10 @@ #define VA_INVAL_LOGICAL_BOUNDARY 0x08 +struct cp_device { + unsigned long quirks; +}; + /* * Some USB barcode readers from cypress have usage min and usage max in * the wrong order @@ -70,7 +74,8 @@ static __u8 *va_logical_boundary_fixup(struct hid_device *hdev, __u8 *rdesc, static const __u8 *cp_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { - unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + const struct cp_device *cp_device = hid_get_drvdata(hdev); + unsigned long quirks = cp_device->quirks; if (quirks & CP_RDESC_SWAPPED_MIN_MAX) rdesc = cp_rdesc_fixup(hdev, rdesc, rsize); @@ -84,7 +89,8 @@ static int cp_input_mapped(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { - unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + const struct cp_device *cp_device = hid_get_drvdata(hdev); + unsigned long quirks = cp_device->quirks; if (!(quirks & CP_2WHEEL_MOUSE_HACK)) return 0; @@ -100,22 +106,21 @@ static int cp_input_mapped(struct hid_device *hdev, struct hid_input *hi, static int cp_event(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value) { - unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + struct cp_device *cp_device = hid_get_drvdata(hdev); if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput || - !usage->type || !(quirks & CP_2WHEEL_MOUSE_HACK)) + !usage->type || !(cp_device->quirks & CP_2WHEEL_MOUSE_HACK)) return 0; if (usage->hid == 0x00090005) { if (value) - quirks |= CP_2WHEEL_MOUSE_HACK_ON; + cp_device->quirks |= CP_2WHEEL_MOUSE_HACK_ON; else - quirks &= ~CP_2WHEEL_MOUSE_HACK_ON; - hid_set_drvdata(hdev, (void *)quirks); + cp_device->quirks &= ~CP_2WHEEL_MOUSE_HACK_ON; return 1; } - if (usage->code == REL_WHEEL && (quirks & CP_2WHEEL_MOUSE_HACK_ON)) { + if (usage->code == REL_WHEEL && (cp_device->quirks & CP_2WHEEL_MOUSE_HACK_ON)) { struct input_dev *input = field->hidinput->input; input_event(input, usage->type, REL_HWHEEL, value); @@ -127,10 +132,17 @@ static int cp_event(struct hid_device *hdev, struct hid_field *field, static int cp_probe(struct hid_device *hdev, const struct hid_device_id *id) { - unsigned long quirks = id->driver_data; int ret; + struct cp_device *cp_device; + + cp_device = devm_kzalloc(&hdev->dev, sizeof(*cp_device), GFP_KERNEL); + + if (!cp_device) + return -ENOMEM; + + cp_device->quirks = id->driver_data; - hid_set_drvdata(hdev, (void *)quirks); + hid_set_drvdata(hdev, cp_device); ret = hid_parse(hdev); if (ret) { 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 (hid->product != 0x0006) + 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; @@ -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-elan.c b/drivers/hid/hid-elan.c index 76d93fc48f6a..0190ad567ce4 100644 --- a/drivers/hid/hid-elan.c +++ b/drivers/hid/hid-elan.c @@ -513,6 +513,7 @@ static const struct hid_device_id elan_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_HP_X2_10_COVER), .driver_data = ELAN_HAS_LED }, { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_TOSHIBA_CLICK_L9W) }, + { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_SB974D) }, { } }; MODULE_DEVICE_TABLE(hid, elan_devices); diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c index 37d88ce57f67..d21ead40bb07 100644 --- a/drivers/hid/hid-elecom.c +++ b/drivers/hid/hid-elecom.c @@ -79,7 +79,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, case USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB: case USB_DEVICE_ID_ELECOM_M_XT3URBK_018F: case USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC: - case USB_DEVICE_ID_ELECOM_M_XT4DRBK: + case USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD: /* * Report descriptor format: * 12: button bit count @@ -104,6 +104,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8); break; case USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C: + case USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E: /* * Report descriptor format: * 22: button bit count @@ -148,7 +149,8 @@ static const struct hid_device_id elecom_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, 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 333341e80b0e..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,14 +1112,36 @@ 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); + return -1; + } if (xfer->report >= FT260_I2C_REPORT_MIN && xfer->report <= FT260_I2C_REPORT_MAX) { - ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report, - xfer->length); + bool complete_read; + + ft260_dbg("i2c resp: rep %#02x len %d size %d\n", + xfer->report, xfer->length, size); + + if (xfer->length > size - + offsetof(struct ft260_i2c_input_report, data)) { + hid_err(hdev, "report %#02x: length %d exceeds HID report size\n", + xfer->report, xfer->length); + 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; @@ -1083,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; + + spin_unlock_irqrestore(&dev->read_lock, irqflags); - if (dev->read_idx == dev->read_len) + 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-gfrm.c b/drivers/hid/hid-gfrm.c index 699186ff2349..f7cc754de84e 100644 --- a/drivers/hid/hid-gfrm.c +++ b/drivers/hid/hid-gfrm.c @@ -28,7 +28,8 @@ static int gfrm_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { - unsigned long hdev_type = (unsigned long) hid_get_drvdata(hdev); + const struct hid_device_id *id = hid_get_drvdata(hdev); + unsigned long hdev_type = id->driver_data; if (hdev_type == GFRM100) { if (usage->hid == (HID_UP_CONSUMER | 0x4)) { @@ -50,7 +51,8 @@ static int gfrm_input_mapping(struct hid_device *hdev, struct hid_input *hi, static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { - unsigned long hdev_type = (unsigned long) hid_get_drvdata(hdev); + const struct hid_device_id *id = hid_get_drvdata(hdev); + unsigned long hdev_type = id->driver_data; int ret = 0; if (hdev_type != GFRM100) @@ -66,7 +68,7 @@ static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report, switch (data[1]) { case GFRM100_SEARCH_KEY_DOWN: ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_dn, - sizeof(search_key_dn), 1); + sizeof(search_key_dn), sizeof(search_key_dn), 1); break; case GFRM100_SEARCH_KEY_AUDIO_DATA: @@ -74,7 +76,7 @@ static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report, case GFRM100_SEARCH_KEY_UP: ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_up, - sizeof(search_key_up), 1); + sizeof(search_key_up), sizeof(search_key_up), 1); break; default: @@ -99,7 +101,7 @@ static int gfrm_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret; - hid_set_drvdata(hdev, (void *) id->driver_data); + hid_set_drvdata(hdev, (void *)id); ret = hid_parse(hdev); if (ret) diff --git a/drivers/hid/hid-goodix-spi.c b/drivers/hid/hid-goodix-spi.c index 80c0288a3a38..03d549efbdce 100644 --- a/drivers/hid/hid-goodix-spi.c +++ b/drivers/hid/hid-goodix-spi.c @@ -520,6 +520,9 @@ static int goodix_hid_set_raw_report(struct hid_device *hid, memcpy(tmp_buf + tx_len, args, args_len); tx_len += args_len; + if (tx_len + len > sizeof(tmp_buf)) + return -EINVAL; + memcpy(tmp_buf + tx_len, buf, len); tx_len += len; @@ -719,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-hammer.c b/drivers/hid/hid-google-hammer.c index 1af477e58480..c99c3c0d442e 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -496,7 +496,7 @@ static int hammer_probe(struct hid_device *hdev, if (error) return error; - error = devm_add_action(&hdev->dev, hammer_stop, hdev); + error = devm_add_action_or_reset(&hdev->dev, hammer_stop, hdev); if (error) return error; 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); - - hid_info(hid, "Force Feedback for Google Stadia controller\n"); + disable_work_sync(&stadiaff->work); - return 0; -} + dev->open = stadia_input_open; + dev->close = stadia_input_close; -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; - } + hid_info(hid, "Force Feedback for Google Stadia controller\n"); 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..cfc061dbdd24 100644 --- a/drivers/hid/hid-hyperv.c +++ b/drivers/hid/hid-hyperv.c @@ -13,6 +13,9 @@ #include <linux/hiddev.h> #include <linux/hyperv.h> +#if IS_ENABLED(CONFIG_HID_HYPERV_MOUSE_KUNIT_TEST) +#include <kunit/test.h> +#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 + 1); + 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 + 1); + 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 <benjaminblume@posteo.de> + */ + +#include <linux/hid.h> +#include <linux/input.h> +#include <linux/module.h> + +#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 <benjaminblume@posteo.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 0cf63742315b..9a473f72c8cb 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 @@ -277,6 +281,9 @@ #define USB_VENDOR_ID_BIGBEN 0x146b #define USB_DEVICE_ID_BIGBEN_PS3OFMINIPAD 0x0902 +#define I2C_VENDOR_ID_BLTP 0x36b6 +#define I2C_PRODUCT_ID_BLTP7853 0xc001 + #define USB_VENDOR_ID_BTC 0x046e #define USB_DEVICE_ID_BTC_EMPREX_REMOTE 0x5578 #define USB_DEVICE_ID_BTC_EMPREX_REMOTE_2 0x5577 @@ -331,6 +338,7 @@ #define I2C_VENDOR_ID_CIRQUE 0x0488 #define I2C_PRODUCT_ID_CIRQUE_1063 0x1063 +#define I2C_PRODUCT_ID_CIRQUE_D0C1 0xD0C1 #define USB_VENDOR_ID_CJTOUCH 0x24b8 #define USB_DEVICE_ID_CJTOUCH_MULTI_TOUCH_0020 0x0020 @@ -455,10 +463,12 @@ #define USB_DEVICE_ID_EDIFIER_QR30 0xa101 /* EDIFIER Hal0 2.0 SE */ #define USB_VENDOR_ID_ELAN 0x04f3 +#define USB_DEVICE_ID_SB974D 0x0400 #define USB_DEVICE_ID_TOSHIBA_CLICK_L9W 0x0401 #define USB_DEVICE_ID_HP_X2 0x074d #define USB_DEVICE_ID_HP_X2_10_COVER 0x0755 #define I2C_DEVICE_ID_CHROMEBOOK_TROGDOR_POMPOM 0x2F81 +#define I2C_DEVICE_ID_SURFACE_PRO_12IN 0x4376 #define USB_VENDOR_ID_ELECOM 0x056e #define USB_DEVICE_ID_ELECOM_BM084 0x0061 @@ -467,7 +477,8 @@ #define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC 0x00fc #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C 0x018c -#define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd +#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD 0x00fd +#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E 0x018e #define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe #define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff #define USB_DEVICE_ID_ELECOM_M_DT2DRBK 0x018d @@ -679,7 +690,11 @@ #define USB_DEVICE_ID_HARMONIX_WII_RB3_KEYBOARD 0x3330 #define USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_KEYBOARD_MODE 0x3338 +#define USB_VENDOR_ID_HORI 0x0f0d +#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 @@ -751,6 +766,7 @@ #define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a #define USB_DEVICE_ID_ITE_LENOVO_YOGA900 0x8396 #define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD 0x8987 +#define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD 0x83db #define USB_DEVICE_ID_ITE8595 0x8595 #define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50 @@ -767,6 +783,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 @@ -870,6 +887,7 @@ #define USB_DEVICE_ID_LENOVO_X1_TAB3 0x60b5 #define USB_DEVICE_ID_LENOVO_X12_TAB 0x60fe #define USB_DEVICE_ID_LENOVO_X12_TAB2 0x61ae +#define USB_DEVICE_ID_LENOVO_X13_TAB 0x62af #define USB_DEVICE_ID_LENOVO_YOGABOOK9I 0x6161 #define USB_DEVICE_ID_LENOVO_OPTICAL_USB_MOUSE_600E 0x600e #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_608D 0x608d @@ -958,6 +976,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 @@ -1062,7 +1081,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 @@ -1127,6 +1151,12 @@ #define USB_VENDOR_ID_NVIDIA 0x0955 #define USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER 0x7214 +#define USB_VENDOR_ID_CRSC 0x1a2c +#define USB_DEVICE_ID_ONEXPLAYER_GEN1 0xb001 + +#define USB_VENDOR_ID_WCH 0x1a86 +#define USB_DEVICE_ID_ONEXPLAYER_GEN2 0xfe00 + #define USB_VENDOR_ID_ONTRAK 0x0a07 #define USB_DEVICE_ID_ONTRAK_ADU100 0x0064 @@ -1268,6 +1298,9 @@ #define USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD 0xa006 #define USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD 0xa064 +#define USB_VENDOR_ID_SDINNOVATION 0x36ae +#define USB_DEVICE_ID_SDINNOVATION_GAMING_KBD 0xfeab + #define USB_VENDOR_ID_SEMICO 0x1a2c #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD 0x0023 #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD2 0x0027 @@ -1280,6 +1313,7 @@ #define USB_VENDOR_ID_SIGMA_MICRO 0x1c4f #define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD 0x0002 +#define USB_DEVICE_ID_SIGMA_MICRO_USB_MOUSE 0x0034 #define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD2 0x0059 #define USB_VENDOR_ID_SIGMATEL 0x066F @@ -1358,11 +1392,32 @@ #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_7_2018 0x12ad +#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 @@ -1401,6 +1456,11 @@ #define USB_DEVICE_ID_SYNAPTICS_ACER_SWITCH5_017 0x73f6 #define USB_DEVICE_ID_SYNAPTICS_ACER_SWITCH5 0x81a7 +#define USB_VENDOR_ID_TELINK 0x248a +#define USB_DEVICE_ID_TELINK_RAKK_DASIG_X 0xfb01 +#define USB_DEVICE_ID_TELINK_RAKK_DASIG_X_DONGLE 0xfa02 +#define USB_DEVICE_ID_TELINK_RAKK_DASIG_X_BT 0x8266 + #define USB_VENDOR_ID_TEXAS_INSTRUMENTS 0x2047 #define USB_DEVICE_ID_TEXAS_INSTRUMENTS_LENOVO_YOGA 0x0855 diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index d73cfa2e73d3..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 }, @@ -395,6 +398,8 @@ static const struct hid_device_id hid_battery_quirks[] = { HID_BATTERY_QUIRK_AVOID_QUERY }, { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_CHROMEBOOK_TROGDOR_POMPOM), HID_BATTERY_QUIRK_AVOID_QUERY }, + { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_PRO_12IN), + HID_BATTERY_QUIRK_IGNORE }, /* * Elan HID touchscreens seem to all report a non present battery, * set HID_BATTERY_QUIRK_IGNORE for all Elan I2C and USB HID devices. @@ -430,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, @@ -519,6 +532,13 @@ static struct hid_battery *hidinput_find_battery(struct hid_device *dev, return NULL; } +static void hidinput_cleanup_battery(void *res) +{ + struct hid_battery *bat = res; + + list_del(&bat->list); +} + static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, struct hid_field *field, bool is_percentage) { @@ -584,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; @@ -610,6 +631,12 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, power_supply_powers(bat->ps, &dev->dev); list_add_tail(&bat->list, &dev->batteries); + + error = devm_add_action_or_reset(&dev->dev, + hidinput_cleanup_battery, bat); + if (error) + return error; + return 0; err_free_name: @@ -2302,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; @@ -2315,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 || @@ -2381,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-ite.c b/drivers/hid/hid-ite.c index 8e42780a2663..63908f24b524 100644 --- a/drivers/hid/hid-ite.c +++ b/drivers/hid/hid-ite.c @@ -15,7 +15,9 @@ static const __u8 *ite_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { - unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + + const struct hid_device_id *id = hid_get_drvdata(hdev); + unsigned long quirks = id->driver_data; if (quirks & QUIRK_TOUCHPAD_ON_OFF_REPORT) { /* For Acer Aspire Switch 10 SW5-012 keyboard-dock */ @@ -44,7 +46,8 @@ static int ite_input_mapping(struct hid_device *hdev, int *max) { - unsigned long quirks = (unsigned long)hid_get_drvdata(hdev); + const struct hid_device_id *id = hid_get_drvdata(hdev); + unsigned long quirks = id->driver_data; if ((quirks & QUIRK_TOUCHPAD_ON_OFF_REPORT) && (usage->hid & HID_USAGE_PAGE) == 0x00880000) { @@ -94,7 +97,7 @@ static int ite_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret; - hid_set_drvdata(hdev, (void *)id->driver_data); + hid_set_drvdata(hdev, (void *)id); ret = hid_open_report(hdev); if (ret) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 01c7bdd4fbe0..a72f7f748cb5 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -382,11 +382,9 @@ static int get_endpoint_address(struct hid_device *hdev) struct usb_interface *intf = to_usb_interface(hdev->dev.parent); struct usb_host_endpoint *ep; - if (intf) { - ep = intf->cur_altsetting->endpoint; - if (ep) - return ep->desc.bEndpointAddress; - } + ep = intf->cur_altsetting->endpoint; + if (ep) + return ep->desc.bEndpointAddress; return -ENODEV; } @@ -1369,6 +1367,14 @@ static void cfg_setup(struct work_struct *work) "Failed to retrieve IMU Manufacturer: %i\n", ret); return; } + + ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, FEATURE_OS_MODE, + NULL, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve OS Mode: %i\n", ret); + return; + } } static int hid_gos_cfg_probe(struct hid_device *hdev, @@ -1427,11 +1433,35 @@ static void hid_gos_cfg_remove(struct hid_device *hdev) hid_set_drvdata(hdev, NULL); } +static int hid_gos_cfg_reset_resume(struct hid_device *hdev) +{ + u8 os_mode = drvdata.os_mode; + int ret; + + ret = mcu_property_out(drvdata.hdev, SET_GAMEPAD_CFG, + FEATURE_OS_MODE, &os_mode, 1); + if (ret < 0) + return ret; + + ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, + FEATURE_OS_MODE, NULL, 0); + if (ret < 0) + return ret; + + if (drvdata.os_mode != os_mode) + return -ENODEV; + + return 0; +} + static int hid_gos_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret, ep; + if (!hid_is_usb(hdev)) + return -EINVAL; + ret = hid_parse(hdev); if (ret) { hid_err(hdev, "Parse failed\n"); @@ -1481,6 +1511,20 @@ static void hid_gos_remove(struct hid_device *hdev) } } +static int hid_gos_reset_resume(struct hid_device *hdev) +{ + int ep = get_endpoint_address(hdev); + + switch (ep) { + case GO_S_CFG_INTF_IN: + return hid_gos_cfg_reset_resume(hdev); + default: + break; + } + + return 0; +} + static const struct hid_device_id hid_gos_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_QHE, USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT) }, @@ -1496,6 +1540,7 @@ static struct hid_driver hid_lenovo_go_s = { .probe = hid_gos_probe, .remove = hid_gos_remove, .raw_event = hid_gos_raw_event, + .reset_resume = hid_gos_reset_resume, }; module_hid_driver(hid_lenovo_go_s); diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index d4d26c783356..318b1152ff8b 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -641,9 +641,6 @@ static int get_endpoint_address(struct hid_device *hdev) struct usb_interface *intf = to_usb_interface(hdev->dev.parent); struct usb_host_endpoint *ep; - if (!intf) - return -ENODEV; - ep = intf->cur_altsetting->endpoint; if (!ep) return -ENODEV; @@ -2408,6 +2405,15 @@ static int hid_go_cfg_probe(struct hid_device *hdev, static void hid_go_cfg_remove(struct hid_device *hdev) { + /* + * cfg_setup is scheduled from hid_go_cfg_probe() with a 2 ms delay + * and dereferences drvdata.hdev. Drain it here before tearing + * down so the workqueue cannot run after hid_destroy_device()'s + * put_device() has released the underlying hdev and dereference + * a stale drvdata.hdev pointer. + */ + cancel_delayed_work_sync(&drvdata.go_cfg_setup); + guard(mutex)(&drvdata.cfg_mutex); sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups); hid_hw_close(hdev); @@ -2419,6 +2425,9 @@ static int hid_go_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret, ep; + if (!hid_is_usb(hdev)) + return -EINVAL; + hdev->quirks |= HID_QUIRK_INPUT_PER_APP | HID_QUIRK_MULTI_INPUT; ret = hid_parse(hdev); diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index a6b73e03c16b..3976d7b53b14 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -30,6 +30,7 @@ #include <linux/hid.h> #include <linux/input.h> #include <linux/leds.h> +#include <linux/unaligned.h> #include <linux/workqueue.h> #include "hid-ids.h" @@ -504,6 +505,7 @@ static int lenovo_input_mapping(struct hid_device *hdev, usage, bit, max); case USB_DEVICE_ID_LENOVO_X12_TAB: case USB_DEVICE_ID_LENOVO_X12_TAB2: + case USB_DEVICE_ID_LENOVO_X13_TAB: case USB_DEVICE_ID_LENOVO_X1_TAB: case USB_DEVICE_ID_LENOVO_X1_TAB2: case USB_DEVICE_ID_LENOVO_X1_TAB3: @@ -620,6 +622,7 @@ static ssize_t attr_fn_lock_store(struct device *dev, break; case USB_DEVICE_ID_LENOVO_X12_TAB: case USB_DEVICE_ID_LENOVO_X12_TAB2: + case USB_DEVICE_ID_LENOVO_X13_TAB: case USB_DEVICE_ID_LENOVO_TP10UBKBD: case USB_DEVICE_ID_LENOVO_X1_TAB: case USB_DEVICE_ID_LENOVO_X1_TAB2: @@ -791,10 +794,11 @@ static int lenovo_raw_event(struct hid_device *hdev, * Lenovo TP X12 Tab KBD's Fn+XX is HID raw data defined. Report ID is 0x03 * e.g.: Raw data received for MIC mute is 0x00020003. */ - if (unlikely((hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB - || hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB2) - && size >= 3 && report->id == 0x03)) - return lenovo_raw_event_TP_X12_tab(hdev, le32_to_cpu(*(__le32 *)data)); + if (unlikely((hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB || + hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB2 || + hdev->product == USB_DEVICE_ID_LENOVO_X13_TAB) && + size >= 4 && report->id == 0x03)) + return lenovo_raw_event_TP_X12_tab(hdev, get_unaligned_le32(data)); return 0; } @@ -877,6 +881,7 @@ static int lenovo_event(struct hid_device *hdev, struct hid_field *field, return lenovo_event_cptkbd(hdev, field, usage, value); case USB_DEVICE_ID_LENOVO_X12_TAB: case USB_DEVICE_ID_LENOVO_X12_TAB2: + case USB_DEVICE_ID_LENOVO_X13_TAB: case USB_DEVICE_ID_LENOVO_TP10UBKBD: case USB_DEVICE_ID_LENOVO_X1_TAB: case USB_DEVICE_ID_LENOVO_X1_TAB2: @@ -1161,6 +1166,7 @@ static int lenovo_led_brightness_set(struct led_classdev *led_cdev, break; case USB_DEVICE_ID_LENOVO_X12_TAB: case USB_DEVICE_ID_LENOVO_X12_TAB2: + case USB_DEVICE_ID_LENOVO_X13_TAB: case USB_DEVICE_ID_LENOVO_TP10UBKBD: case USB_DEVICE_ID_LENOVO_X1_TAB: case USB_DEVICE_ID_LENOVO_X1_TAB2: @@ -1355,7 +1361,8 @@ static int lenovo_probe_tp10ubkbd(struct hid_device *hdev) */ data->fn_lock = !(hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB || - hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB2); + hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB2 || + hdev->product == USB_DEVICE_ID_LENOVO_X13_TAB); lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, data->fn_lock); @@ -1402,6 +1409,7 @@ static int lenovo_probe(struct hid_device *hdev, break; case USB_DEVICE_ID_LENOVO_X12_TAB: case USB_DEVICE_ID_LENOVO_X12_TAB2: + case USB_DEVICE_ID_LENOVO_X13_TAB: case USB_DEVICE_ID_LENOVO_TP10UBKBD: case USB_DEVICE_ID_LENOVO_X1_TAB: case USB_DEVICE_ID_LENOVO_X1_TAB2: @@ -1490,6 +1498,7 @@ static void lenovo_remove(struct hid_device *hdev) break; case USB_DEVICE_ID_LENOVO_X12_TAB: case USB_DEVICE_ID_LENOVO_X12_TAB2: + case USB_DEVICE_ID_LENOVO_X13_TAB: case USB_DEVICE_ID_LENOVO_TP10UBKBD: case USB_DEVICE_ID_LENOVO_X1_TAB: case USB_DEVICE_ID_LENOVO_X1_TAB2: @@ -1517,6 +1526,10 @@ static int lenovo_input_configured(struct hid_device *hdev, hi->input->propbit); } break; + case USB_DEVICE_ID_LENOVO_X12_TAB2: + case USB_DEVICE_ID_LENOVO_X13_TAB: + input_set_capability(hi->input, EV_KEY, KEY_PERFORMANCE); + break; } return 0; @@ -1551,6 +1564,8 @@ static const struct hid_device_id lenovo_devices[] = { USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X12_TAB) }, { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X12_TAB2) }, + { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, + USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X13_TAB) }, { HID_DEVICE(BUS_I2C, HID_GROUP_GENERIC, USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD) }, { } diff --git a/drivers/hid/hid-letsketch.c b/drivers/hid/hid-letsketch.c index 11e21f988723..b52e93a91ae5 100644 --- a/drivers/hid/hid-letsketch.c +++ b/drivers/hid/hid-letsketch.c @@ -296,13 +296,42 @@ static int letsketch_probe(struct hid_device *hdev, const struct hid_device_id * ret = letsketch_setup_input_tablet(data); if (ret) - return ret; + goto err_shutdown_timer; ret = letsketch_setup_input_tablet_pad(data); if (ret) - return ret; + goto err_shutdown_timer; + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) + goto err_shutdown_timer; - return hid_hw_start(hdev, HID_CONNECT_HIDRAW); + return 0; + +err_shutdown_timer: + /* + * Drain any pending callback and permanently disable the timer + * before devm releases data: if hid_hw_start() enabled I/O on an + * always-poll-quirk device and then failed, raw_event may have + * armed the timer already. + */ + timer_shutdown_sync(&data->inrange_timer); + return ret; +} + +static void letsketch_remove(struct hid_device *hdev) +{ + struct letsketch_data *data = hid_get_drvdata(hdev); + + /* + * hid_hw_stop() synchronously kills the URBs that deliver + * raw_event(), so once it returns no path can re-arm + * inrange_timer. timer_shutdown_sync() then drains any + * in-flight callback and permanently disables further + * mod_timer() calls before devm releases data. + */ + hid_hw_stop(hdev); + timer_shutdown_sync(&data->inrange_timer); } static const struct hid_device_id letsketch_devices[] = { @@ -315,6 +344,7 @@ static struct hid_driver letsketch_driver = { .name = "letsketch", .id_table = letsketch_devices, .probe = letsketch_probe, + .remove = letsketch_remove, .raw_event = letsketch_raw_event, }; module_hid_driver(letsketch_driver); diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c index 1a88bc44ada4..02ef3e2094b4 100644 --- a/drivers/hid/hid-lg-g15.c +++ b/drivers/hid/hid-lg-g15.c @@ -1374,11 +1374,27 @@ static const struct hid_device_id lg_g15_devices[] = { }; MODULE_DEVICE_TABLE(hid, lg_g15_devices); +static void lg_g15_remove(struct hid_device *hdev) +{ + struct lg_g15_data *g15 = hid_get_drvdata(hdev); + + /* + * g15->work is only initialized for the models that schedule it + * (G15, G15 v2, G510). The G13 and Z-10 leave it zeroed, so only + * cancel it when it was set up. + */ + if (g15 && g15->work.func) + cancel_work_sync(&g15->work); + + hid_hw_stop(hdev); +} + static struct hid_driver lg_g15_driver = { .name = "lg-g15", .id_table = lg_g15_devices, .raw_event = lg_g15_raw_event, .probe = lg_g15_probe, + .remove = lg_g15_remove, }; module_hid_driver(lg_g15_driver); 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 381e4dc5aba7..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) */ @@ -1746,6 +1751,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,18 +1923,33 @@ 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); + } } } output_report_enum = &hdev->report_enum[HID_OUTPUT_REPORT]; rep = output_report_enum->report_id_hash[REPORT_ID_DJ_SHORT]; - if (rep && (rep->maxfield < 1 || - rep->field[0]->report_count != DJREPORT_SHORT_LENGTH - 1)) { + if (rep && rep->maxfield < 1) { + hid_err(hdev, "Expected size of DJ short report is %d, but got 0", + DJREPORT_SHORT_LENGTH - 1); + return -EINVAL; + } + + if (rep && rep->field[0]->report_count != DJREPORT_SHORT_LENGTH - 1) { hid_err(hdev, "Expected size of DJ short report is %d, but got %d", DJREPORT_SHORT_LENGTH - 1, rep->field[0]->report_count); return -EINVAL; @@ -2098,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 b1330d23bd2d..493763a12518 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 @@ -164,7 +165,6 @@ struct hidpp_battery { /** * struct hidpp_scroll_counter - Utility class for processing high-resolution * scroll events. - * @dev: the input device for which events should be reported. * @wheel_multiplier: the scalar multiplier to be applied to each wheel event * @remainder: counts the number of high-resolution units moved since the last * low-resolution event (REL_WHEEL or REL_HWHEEL) was sent. Should @@ -179,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; @@ -206,6 +208,11 @@ 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; bool connected_once; }; @@ -981,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; @@ -2859,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; @@ -2900,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; } /* ************************************************************************** */ @@ -3599,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) { @@ -3673,7 +3895,7 @@ static int hidpp10_consumer_keys_raw_event(struct hidpp_device *hidpp, memcpy(&consumer_report[1], &data[3], 4); /* We are called from atomic context */ hid_report_raw_event(hidpp->hid_dev, HID_INPUT_REPORT, - consumer_report, 5, 1); + consumer_report, sizeof(consumer_report), 5, 1); return 1; } @@ -3710,6 +3932,7 @@ static int hi_res_scroll_enable(struct hidpp_device *hidpp) multiplier = 1; } + hidpp->hires_wheel_multiplier = multiplier; hidpp->vertical_wheel_counter.wheel_multiplier = multiplier; hid_dbg(hidpp->hid_dev, "wheel multiplier = %d\n", multiplier); return 0; @@ -3720,6 +3943,7 @@ static int hidpp_initialize_hires_scroll(struct hidpp_device *hidpp) int ret; unsigned long capabilities; + hidpp->hires_wheel_feature_index = 0xff; capabilities = hidpp->capabilities; if (hidpp->protocol_major >= 2) { @@ -3729,6 +3953,7 @@ static int hidpp_initialize_hires_scroll(struct hidpp_device *hidpp) &feature_index); if (!ret) { hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL; + hidpp->hires_wheel_feature_index = feature_index; hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scroll wheel\n"); return 0; } @@ -3751,6 +3976,31 @@ static int hidpp_initialize_hires_scroll(struct hidpp_device *hidpp) return 0; } +static int hidpp20_hires_wheel_raw_event(struct hidpp_device *hidpp, + u8 *data, int size) +{ + if (hidpp->hires_wheel_feature_index == 0xff) + return 0; + + if (size < 5) + return 0; + + if (data[0] != REPORT_ID_HIDPP_LONG || + data[2] != hidpp->hires_wheel_feature_index) + return 0; + + if ((data[3] & 0xf0) == CMD_HIRES_WHEEL_SET_WHEEL_MODE) { + u8 mode = data[4]; + bool hires = (mode & 0x02) != 0; + int new_multiplier = (hires && hidpp->hires_wheel_multiplier > 0) + ? hidpp->hires_wheel_multiplier : 1; + hidpp->vertical_wheel_counter.wheel_multiplier = new_multiplier; + return 1; + } + + return 0; +} + /* -------------------------------------------------------------------------- */ /* Generic HID++ devices */ /* -------------------------------------------------------------------------- */ @@ -3829,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; @@ -3941,12 +4211,22 @@ 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) return ret; } + if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL) { + ret = hidpp20_hires_wheel_raw_event(hidpp, data, size); + if (ret != 0) + return ret; + } + return 0; } @@ -4125,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; @@ -4228,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) @@ -4295,6 +4619,7 @@ static void hidpp_connect_event(struct work_struct *work) ret = input_register_device(input); if (ret) { + hidpp->input = NULL; input_free_device(input); return; } @@ -4400,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); @@ -4473,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; @@ -4493,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. @@ -4610,6 +4922,10 @@ 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 G502 X Lightspeed Wireless Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC098) }, { /* Logitech G703 Gaming Mouse over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC087) }, { /* Logitech G703 Hero Gaming Mouse over USB */ @@ -4622,8 +4938,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 */ @@ -4670,7 +4984,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 */ @@ -4685,6 +5001,44 @@ static const struct hid_device_id hidpp_devices[] = { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb391) }, { /* MX Master 4 mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb042) }, + { /* Logitech Signature K650 over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb36f) }, + { /* Logitech Signature K650 B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb370) }, + { /* Logitech Pebble Keys 2 K380S over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb377) }, + { /* Logitech Casa Pop-Up Desk over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb371) }, + { /* Logitech Casa Pop-Up Desk B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb374) }, + { /* Logitech Wave Keys over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb383) }, + { /* Logitech Wave Keys B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb384) }, + { /* Logitech Signature Slim K950 over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb386) }, + { /* Logitech Signature Slim K950 B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb388) }, + { /* Logitech MX Keys S over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb378) }, + { /* Logitech MX Keys S B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb380) }, + { /* Logitech Keys-To-Go 2 over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb38c) }, + { /* Logitech Pop Icon Keys over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb38f) }, + { /* Logitech MX Keys Mini over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb369) }, + { /* Logitech MX Keys Mini B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb36e) }, + { /* Logitech Signature Slim Solar+ K980 B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb394) }, + { /* Logitech Bluetooth Keyboard K250/K251 over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb397) }, + { /* Logitech Signature Comfort K880 over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb39c) }, + { /* Logitech Signature Comfort K880 B2B over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb39d) }, {} }; diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index e70bd3dc07ab..d637c0477379 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -383,13 +383,17 @@ 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; int x = 0, y = 0, ii, clicks = 0, npoints; + /* Protect against zero sized recursive calls from DOUBLE_REPORT_ID */ + if (size < 1) + return 0; + switch (data[0]) { case TRACKPAD_REPORT_ID: case TRACKPAD2_BT_REPORT_ID: @@ -490,9 +494,30 @@ static int magicmouse_raw_event(struct hid_device *hdev, /* Sometimes the trackpad sends two touch reports in one * packet. */ - magicmouse_raw_event(hdev, report, data + 2, data[1]); - magicmouse_raw_event(hdev, report, data + 2 + data[1], - size - 2 - data[1]); + + /* + * 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; + + if (size < data[1] + 2) { + hid_warn(hdev, + "received report length (%d) was smaller than specified (%d)", + size, data[1] + 2); + return 0; + } + + __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; @@ -518,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) { @@ -812,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 @@ -822,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]; @@ -884,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); @@ -955,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) || @@ -979,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) { @@ -1042,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 be80970ab48e..d52ce3531ab7 100644 --- a/drivers/hid/hid-mcp2221.c +++ b/drivers/hid/hid-mcp2221.c @@ -128,6 +128,7 @@ struct mcp2221 { u8 *rxbuf; u8 txbuf[64]; int rxbuf_idx; + int rxbuf_size; int status; u8 cur_i2c_clk_div; struct gpio_chip *gc; @@ -330,17 +331,19 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp, mcp->txbuf[3] = (u8)(msg->addr << 1); total_len = msg->len; mcp->rxbuf = msg->buf; + mcp->rxbuf_size = msg->len; } else { mcp->txbuf[1] = smbus_len; mcp->txbuf[2] = 0; mcp->txbuf[3] = (u8)(smbus_addr << 1); total_len = smbus_len; mcp->rxbuf = smbus_buf; + mcp->rxbuf_size = smbus_len; } ret = mcp_send_data_req_status(mcp, mcp->txbuf, 4); if (ret) - return ret; + goto out; mcp->rxbuf_idx = 0; @@ -362,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; @@ -372,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; } @@ -858,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: @@ -919,6 +929,14 @@ static int mcp2221_raw_event(struct hid_device *hdev, mcp->status = -EINVAL; break; } + if (mcp->rxbuf_idx + data[3] > mcp->rxbuf_size) { + 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]; @@ -1042,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,30 +68,15 @@ static int mwctrl_init(struct hid_device *hid) mwctrl->strong = &report->field[2]->value[0]; mwctrl->weak = &report->field[3]->value[0]; - 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; - } + set_bit(FF_RUMBLE, dev->ffbit); - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); - if (ret) { - hid_err(hdev, "hw start failed\n"); - return ret; + error = input_ff_create_memless(dev, mwctrl, mwctrl_play); + if (error) { + kfree(mwctrl); + return error; } - ret = mwctrl_init(hdev); - if (ret) - hid_hw_stop(hdev); - - return ret; + return 0; } static const struct hid_device_id mwctrl_devices[] = { @@ -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); + report = list_first_entry_or_null(report_list, struct hid_report, list); + if (!report) { + hid_err(hid, "no output reports found\n"); + return -ENODEV; + } - mf = kzalloc_obj(struct mf_device); - if (!mf) - return -ENOMEM; + if (report->maxfield < 1 || report->field[0]->report_count < 2) { + hid_err(hid, "Invalid report, this should never happen!\n"); + return -ENODEV; + } - dev = input->input; - set_bit(FF_RUMBLE, dev->ffbit); + mf = kzalloc_obj(struct mf_device); + if (!mf) + return -ENOMEM; - error = input_ff_create_memless(dev, mf, mf_play); - if (error) { - kfree(mf); - return error; - } + mf->report = report; + set_bit(FF_RUMBLE, dev->ffbit); - 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); + error = input_ff_create_memless(dev, mf, mf_play); + if (error) { + kfree(mf); + return error; } - hid_info(hid, "Force feedback for HJZ Mayflash game controller " - "adapters by Marcel Hasler <mahasler@gmail.com>\n"); + 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 <honjow311@gmail.com> + * Copyright (c) 2026 Denis Benato <denis.benato@linux.dev> + * Copyright (c) 2026 Valve Corporation + */ + +#include <linux/array_size.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/container_of.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/hid.h> +#include <linux/kobject.h> +#include <linux/led-class-multicolor.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/spinlock.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/unaligned.h> +#include <linux/usb.h> +#include <linux/workqueue.h> + +#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 <denis.benato@linux.dev>"); +MODULE_AUTHOR("Zhouwang Huang <honjow311@gmail.com>"); +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); +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 e82a3c4e5b44..ab4ac261fe76 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -31,6 +31,7 @@ * [1] https://gitlab.freedesktop.org/libevdev/hid-tools */ +#include <linux/bitmap.h> #include <linux/bits.h> #include <linux/device.h> #include <linux/hid.h> @@ -78,6 +79,7 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) #define MT_QUIRK_YOGABOOK9I BIT(24) #define MT_QUIRK_KEEP_LATENCY_ON_CLOSE BIT(25) +#define MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH BIT(26) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 @@ -97,8 +99,7 @@ enum report_mode { TOUCHPAD_REPORT_ALL = TOUCHPAD_REPORT_BUTTONS | TOUCHPAD_REPORT_CONTACTS, }; -#define MT_IO_SLOTS_MASK GENMASK(7, 0) /* reserve first 8 bits for slot tracking */ -#define MT_IO_FLAGS_RUNNING 32 +#define MT_IO_FLAGS_RUNNING 0 static const bool mtrue = true; /* default for true */ static const bool mfalse; /* default for false */ @@ -174,10 +175,9 @@ struct mt_device { struct timer_list release_timer; /* to release sticky fingers */ struct hid_haptic_device *haptic; /* haptic related configuration */ struct hid_device *hdev; /* hid_device we're attached to */ - unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_RUNNING) - * first 8 bits are reserved for keeping the slot - * states, this is fine because we only support up - * to 250 slots (MT_MAX_MAXCONTACT) + unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_RUNNING) */ + unsigned long *active_slots; /* bitmap of slots with an active + * contact, sized for maxcontacts */ __u8 inputmode_value; /* InputMode HID feature value */ __u8 maxcontacts; @@ -236,6 +236,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_APPLE_TOUCHBAR 0x0114 #define MT_CLS_YOGABOOK9I 0x0115 #define MT_CLS_EGALAX_P80H84 0x0116 +#define MT_CLS_ASUS_ROG_Z13_FOLIO 0x0117 #define MT_CLS_SIS 0x0457 #define MT_DEFAULT_MAXCONTACT 10 @@ -406,6 +407,16 @@ static const struct mt_class mt_classes[] = { .quirks = MT_QUIRK_ALWAYS_VALID | MT_QUIRK_CONTACT_CNT_ACCURATE | MT_QUIRK_ASUS_CUSTOM_UP }, + { .name = MT_CLS_ASUS_ROG_Z13_FOLIO, + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_STICKY_FINGERS | + MT_QUIRK_WIN8_PTP_BUTTONS | + MT_QUIRK_CONFIDENCE | + MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH, + .export_all_inputs = true }, { .name = MT_CLS_VTL, .quirks = MT_QUIRK_ALWAYS_VALID | MT_QUIRK_CONTACT_CNT_ACCURATE | @@ -443,11 +454,14 @@ static const struct mt_class mt_classes[] = { MT_QUIRK_CONTACT_CNT_ACCURATE, }, { .name = MT_CLS_YOGABOOK9I, - .quirks = MT_QUIRK_ALWAYS_VALID | + .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE | MT_QUIRK_FORCE_MULTI_INPUT | MT_QUIRK_SEPARATE_APP_REPORT | MT_QUIRK_HOVERING | MT_QUIRK_YOGABOOK9I, + .maxcontacts = 10, .export_all_inputs = true }, { .name = MT_CLS_EGALAX_P80H84, @@ -505,6 +519,7 @@ static const struct attribute_group mt_attribute_group = { static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) { + struct mt_device *td = hid_get_drvdata(hdev); int ret; u32 size = hid_report_len(report); u8 *buf; @@ -526,14 +541,20 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) dev_warn(&hdev->dev, "failed to fetch feature %d\n", report->id); } else { - /* The report ID in the request and the response should match */ - if (report->id != buf[0]) { + /* + * The report ID in the request and the response should match. + * Some firmware (e.g. the ASUS ROG Z13 Folio + * touchpad) returns a mismatched ID on this specific fetch; + * tolerate it only for devices explicitly flagged as such. + */ + if (report->id != buf[0] && + !(td->mtclass.quirks & MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH)) { hid_err(hdev, "Returned feature report did not match the request\n"); goto free; } ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, buf, - size, 0); + size, size, 0); if (ret) dev_warn(&hdev->dev, "failed to report feature\n"); } @@ -1033,7 +1054,7 @@ static void mt_release_pending_palms(struct mt_device *td, for_each_set_bit(slotnum, app->pending_palm_slots, td->maxcontacts) { clear_bit(slotnum, app->pending_palm_slots); - clear_bit(slotnum, &td->mt_io_flags); + clear_bit(slotnum, td->active_slots); input_mt_slot(input, slotnum); input_mt_report_slot_inactive(input); @@ -1244,9 +1265,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input, input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major); input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor); - set_bit(slotnum, &td->mt_io_flags); + set_bit(slotnum, td->active_slots); } else { - clear_bit(slotnum, &td->mt_io_flags); + clear_bit(slotnum, td->active_slots); } return 0; @@ -1319,21 +1340,18 @@ static void mt_touch_report(struct hid_device *hid, * Includes multi-packet support where subsequent * packets are sent with zero contactcount. */ - if (contact_count >= 0) { + if (contact_count > 0) + app->num_expected = contact_count; + else if (app->num_received == 0 && app->prev_scantime != scantime) { /* + * New multi-report frame: + * * For Win8 PTPs the first packet (td->num_received == 0) may * have a contactcount of 0 if there only is a button event. - * We double check that this is not a continuation packet - * of a possible multi-packet frame be checking that the - * timestamp has changed. + * + * Some other devices use a sentinel frame with 0 to release all contacts */ - if ((app->quirks & MT_QUIRK_WIN8_PTP_BUTTONS) && - app->num_received == 0 && - app->prev_scantime != scantime) - app->num_expected = contact_count; - /* A non 0 contact count always indicates a first packet */ - else if (contact_count) - app->num_expected = contact_count; + app->num_expected = 0; } app->prev_scantime = scantime; @@ -1381,7 +1399,7 @@ static void mt_touch_report(struct hid_device *hid, * defect. */ if (app->quirks & MT_QUIRK_STICKY_FINGERS) { - if (td->mt_io_flags & MT_IO_SLOTS_MASK) + if (!bitmap_empty(td->active_slots, td->maxcontacts)) mod_timer(&td->release_timer, jiffies + msecs_to_jiffies(100)); else @@ -1440,6 +1458,15 @@ static int mt_touch_input_configured(struct hid_device *hdev, if (td->is_pressurepad) __set_bit(INPUT_PROP_PRESSUREPAD, input->propbit); + if (!td->active_slots) { + td->active_slots = devm_kcalloc(&td->hdev->dev, + BITS_TO_LONGS(td->maxcontacts), + sizeof(long), + GFP_KERNEL); + if (!td->active_slots) + return -ENOMEM; + } + app->pending_palm_slots = devm_kcalloc(&hi->input->dev, BITS_TO_LONGS(td->maxcontacts), sizeof(long), @@ -1566,6 +1593,144 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, return 0; } +/* + * Yoga Book 9 14IAH10 descriptor fixup. + * + * The device includes a HID_DG_TOUCHPAD application collection designed for + * the Windows inbox HID driver's Win8 PTP touchpad mode. On Linux we want + * only the HID_DG_TOUCHSCREEN collections. The touchpad collection (and the + * HID_DG_BUTTONTYPE and Win8 compliance blob features it contains) must be + * removed so hid-multitouch does not misclassify the touchscreen nodes as + * indirect buttonpads. + * + * The firmware also resets if any USB control request is received while the + * CDC-ACM interface is initialising (~1.18 s after enumeration). Dropping + * the Win8 blob and Contact Count Max feature reports prevents the + * GET_REPORT calls that hid-multitouch issues at probe. + */ +static void mt_yogabook9_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *size) +{ + /* Usage Page (Digitizer), Usage (Touch Pad), Collection (Application) */ + static const __u8 tp_app_hdr[] = { 0x05, 0x0d, 0x09, 0x05, 0xa1, 0x01 }; + /* Vendor Usage Page 0xff00 (Win8 compliance blob header) */ + static const __u8 win8_page[] = { 0x06, 0x00, 0xff }; + /* Usage (Contact Count Max = 0x55) */ + static const __u8 ccmax_usage[] = { 0x09, 0x55 }; + unsigned int i; + + /* + * Step 1: find and remove the Touch Pad application collection. + * Walk HID short items from the collection header to its matching + * End Collection, then close the gap with memmove. + */ + for (i = 0; i + sizeof(tp_app_hdr) <= *size; i++) { + if (memcmp(rdesc + i, tp_app_hdr, sizeof(tp_app_hdr)) == 0) { + __u8 *start = rdesc + i; + __u8 *coll_end = NULL; + __u8 *p = start; + unsigned int drop; + int depth = 0; + + while (p < rdesc + *size) { + __u8 b = *p; + int ds = b & 3; + int item_len; + + if (b == 0xfe) { /* long item */ + if (p + 2 >= rdesc + *size) + break; + item_len = p[1] + 3; + } else { + item_len = (ds == 3) ? 5 : ds + 1; + } + if (p + item_len > rdesc + *size) + break; + + if ((b & 0xfc) == 0xa0) + depth++; /* Collection */ + else if (b == 0xc0) { + depth--; /* End Collection */ + if (depth == 0) { + coll_end = p; + break; + } + } + p += item_len; + } + + if (!coll_end) { + hid_err(hdev, + "Yoga Book 9: Touch Pad End Collection not found\n"); + break; + } + + drop = coll_end - start + 1; + memmove(start, coll_end + 1, rdesc + *size - coll_end - 1); + *size -= drop; + hid_dbg(hdev, + "Yoga Book 9: dropped Touch Pad collection (%u bytes)\n", + drop); + break; + } + } + + /* + * Step 2: neutralize Win8 compliance blob feature reports remaining + * in the touchscreen collections. Change Usage Page 0xff00 to 0x0f00 + * so the case 0xff0000c5 branch in mt_feature_mapping() is not reached + * and no GET_REPORT is issued. + */ + for (i = 0; i + sizeof(win8_page) <= *size; i++) { + if (memcmp(rdesc + i, win8_page, sizeof(win8_page)) == 0) { + rdesc[i + 2] = 0x0f; /* 0xff00 -> 0x0f00 */ + hid_dbg(hdev, + "Yoga Book 9: neutralized Win8 blob at offset %u\n", + i); + } + } + + /* + * Step 3: neutralize Contact Count Max feature reports. Change usage + * 0x55 (HID_DG_CONTACTMAX) to 0x00 so mt_feature_mapping() does not + * issue GET_REPORT. The class maxcontacts field provides the value. + */ + for (i = 0; i + sizeof(ccmax_usage) <= *size; i++) { + if (memcmp(rdesc + i, ccmax_usage, sizeof(ccmax_usage)) == 0) { + rdesc[i + 1] = 0x00; + hid_dbg(hdev, + "Yoga Book 9: neutralized ContactMax at offset %u\n", + i); + } + } + + /* + * Step 4: neutralize Surface Switch (0x57) and Button Switch (0x58) + * feature report usages in the Device Configuration collection. + * mt_set_modes() issues HID_REQ_SET_REPORT for these on every + * input-device open/close; those repeated control requests hit the + * firmware's CDC-ACM init window and trigger resets. + * + * Input Mode (0x52) is intentionally left intact. mt_set_modes() + * sends it once at probe to set the device into touchscreen mode, + * which flushes the firmware's contact buffer and clears a persistent + * ghost contact (cid 2, fixed coordinates) that otherwise appears on + * every enumeration. By probe time cdc_acm has already satisfied the + * CDC-ACM init watchdog (~130 ms), so the single SET_REPORT for Input + * Mode arrives safely after the reset window has closed. + */ + for (i = 0; i + 2 <= *size; i++) { + if (rdesc[i] == 0x09 && + (rdesc[i + 1] == 0x57 || + rdesc[i + 1] == 0x58)) { + hid_dbg(hdev, + "Yoga Book 9: neutralized set-modes usage 0x%02x at offset %u\n", + rdesc[i + 1], i); + rdesc[i + 1] = 0x00; + } + } +} + static const __u8 *mt_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *size) { @@ -1595,6 +1760,10 @@ got: %x\n", } } + if (hdev->vendor == USB_VENDOR_ID_LENOVO && + hdev->product == USB_DEVICE_ID_LENOVO_YOGABOOK9I) + mt_yogabook9_fixup(hdev, rdesc, size); + return rdesc; } @@ -1917,7 +2086,7 @@ static void mt_release_contacts(struct hid_device *hid) for (i = 0; i < mt->num_slots; i++) { input_mt_slot(input_dev, i); input_mt_report_slot_inactive(input_dev); - clear_bit(i, &td->mt_io_flags); + clear_bit(i, td->active_slots); } input_mt_sync_frame(input_dev); input_sync(input_dev); @@ -1940,7 +2109,7 @@ static void mt_expired_timeout(struct timer_list *t) */ if (test_and_set_bit_lock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags)) return; - if (td->mt_io_flags & MT_IO_SLOTS_MASK) + if (!bitmap_empty(td->active_slots, td->maxcontacts)) mt_release_contacts(hdev); clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); } @@ -2036,16 +2205,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; } @@ -2569,10 +2730,16 @@ 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) }, + /* Asus ROG Flow Z13 (2025) GZ302EA keyboard-cover touchpad */ + { .driver_data = MT_CLS_ASUS_ROG_Z13_FOLIO, + HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, + USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO) }, + /* Generic MT device */ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH, HID_ANY_ID, HID_ANY_ID) }, diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index 29008c2cc530..43e0f2aaea3b 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -316,6 +316,7 @@ enum joycon_ctlr_type { JOYCON_CTLR_TYPE_JCL = 0x01, JOYCON_CTLR_TYPE_JCR = 0x02, JOYCON_CTLR_TYPE_PRO = 0x03, + JOYCON_CTLR_TYPE_LIC_PRO = 0x06, JOYCON_CTLR_TYPE_NESL = 0x09, JOYCON_CTLR_TYPE_NESR = 0x0A, JOYCON_CTLR_TYPE_SNES = 0x0B, @@ -433,6 +434,25 @@ static const struct joycon_ctlr_button_mapping procon_button_mappings[] = { { /* sentinel */ }, }; +/* Licensed Pro Controllers (e.g. HORI) swap X/Y bits in the report */ +static const struct joycon_ctlr_button_mapping lic_procon_button_mappings[] = { + { BTN_EAST, JC_BTN_A, }, + { BTN_SOUTH, JC_BTN_B, }, + { BTN_NORTH, JC_BTN_Y, }, + { BTN_WEST, JC_BTN_X, }, + { BTN_TL, JC_BTN_L, }, + { BTN_TR, JC_BTN_R, }, + { BTN_TL2, JC_BTN_ZL, }, + { BTN_TR2, JC_BTN_ZR, }, + { BTN_SELECT, JC_BTN_MINUS, }, + { BTN_START, JC_BTN_PLUS, }, + { BTN_THUMBL, JC_BTN_LSTICK, }, + { BTN_THUMBR, JC_BTN_RSTICK, }, + { BTN_MODE, JC_BTN_HOME, }, + { BTN_Z, JC_BTN_CAP, }, + { /* sentinel */ }, +}; + static const struct joycon_ctlr_button_mapping nescon_button_mappings[] = { { BTN_SOUTH, JC_BTN_A, }, { BTN_EAST, JC_BTN_B, }, @@ -589,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; @@ -695,7 +717,8 @@ static inline bool joycon_type_is_right_joycon(struct joycon_ctlr *ctlr) static inline bool joycon_type_is_procon(struct joycon_ctlr *ctlr) { - return ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO; + return ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO || + ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO; } static inline bool joycon_type_is_snescon(struct joycon_ctlr *ctlr) @@ -820,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; @@ -851,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; } @@ -865,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) { @@ -1453,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", @@ -1710,7 +1767,10 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, joycon_report_left_stick(ctlr, rep); joycon_report_right_stick(ctlr, rep); joycon_report_dpad(ctlr, rep); - joycon_report_buttons(ctlr, rep, procon_button_mappings); + if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO) + joycon_report_buttons(ctlr, rep, lic_procon_button_mappings); + else + joycon_report_buttons(ctlr, rep, procon_button_mappings); } else if (joycon_type_is_any_nescon(ctlr)) { joycon_report_dpad(ctlr, rep); joycon_report_buttons(ctlr, rep, nescon_button_mappings); @@ -2138,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); @@ -2156,7 +2212,10 @@ static int joycon_input_create(struct joycon_ctlr *ctlr) joycon_config_left_stick(ctlr->input); joycon_config_right_stick(ctlr->input); joycon_config_dpad(ctlr->input); - joycon_config_buttons(ctlr->input, procon_button_mappings); + if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO) + joycon_config_buttons(ctlr->input, lic_procon_button_mappings); + else + joycon_config_buttons(ctlr->input, procon_button_mappings); } else if (joycon_type_is_any_nescon(ctlr)) { joycon_config_dpad(ctlr->input); joycon_config_buttons(ctlr->input, nescon_button_mappings); @@ -2181,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; } @@ -2431,14 +2494,8 @@ static int joycon_read_info(struct joycon_ctlr *ctlr) for (i = 4, j = 0; j < 6; i++, j++) ctlr->mac_addr[j] = report->subcmd_reply.data[i]; - ctlr->mac_addr_str = devm_kasprintf(&ctlr->hdev->dev, GFP_KERNEL, - "%02X:%02X:%02X:%02X:%02X:%02X", - ctlr->mac_addr[0], - ctlr->mac_addr[1], - ctlr->mac_addr[2], - ctlr->mac_addr[3], - ctlr->mac_addr[4], - ctlr->mac_addr[5]); + ctlr->mac_addr_str = devm_kasprintf(&ctlr->hdev->dev, GFP_KERNEL, "%pMU", + ctlr->mac_addr); if (!ctlr->mac_addr_str) return -ENOMEM; hid_info(ctlr->hdev, "controller MAC = %s\n", ctlr->mac_addr_str); @@ -2503,13 +2560,30 @@ static int joycon_init(struct hid_device *hdev) if (joycon_has_joysticks(ctlr)) { /* get controller calibration data, and parse it */ - ret = joycon_request_calibration(ctlr); - if (ret) { + if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO) { /* - * We can function with default calibration, but it may be - * inaccurate. Provide a warning, and continue on. + * Licensed controllers may have incompatible SPI flash + * layouts. Use default calibration values. */ - hid_warn(hdev, "Analog stick positions may be inaccurate\n"); + hid_info(hdev, "using default cal for licensed controller\n"); + joycon_use_default_calibration(hdev, + &ctlr->left_stick_cal_x, + &ctlr->left_stick_cal_y, + "left", 0); + joycon_use_default_calibration(hdev, + &ctlr->right_stick_cal_x, + &ctlr->right_stick_cal_y, + "right", 0); + } else { + ret = joycon_request_calibration(ctlr); + if (ret) { + /* + * We can function with default calibration, but + * it may be inaccurate. Provide a warning, and + * continue on. + */ + hid_warn(hdev, "Analog stick positions may be inaccurate\n"); + } } } @@ -2527,8 +2601,13 @@ static int joycon_init(struct hid_device *hdev) /* Enable the IMU */ ret = joycon_enable_imu(ctlr); if (ret) { - hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret); - goto out_unlock; + if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO) { + hid_dbg(hdev, "IMU enable failed for licensed controller, continuing\n"); + ret = 0; + } else { + hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret); + goto out_unlock; + } } } @@ -2543,8 +2622,13 @@ static int joycon_init(struct hid_device *hdev) /* Enable rumble */ ret = joycon_enable_rumble(ctlr); if (ret) { - hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret); - goto out_unlock; + if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO) { + hid_dbg(hdev, "rumble enable failed for licensed controller, continuing\n"); + ret = 0; + } else { + hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret); + goto out_unlock; + } } } @@ -2559,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); } @@ -2688,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 */ @@ -2718,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); @@ -2813,6 +2903,8 @@ static const struct hid_device_id nintendo_hid_devices[] = { USB_DEVICE_ID_NINTENDO_GENCON) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_N64CON) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_HORI, + USB_DEVICE_ID_HORI_WIRELESS_SWITCH_PAD) }, { } }; MODULE_DEVICE_TABLE(hid, nintendo_hid_devices); diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c new file mode 100644 index 000000000000..1e691ebc1199 --- /dev/null +++ b/drivers/hid/hid-oxp.c @@ -0,0 +1,1580 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for OneXPlayer gamepad configuration devices. + * + * Copyright (c) 2026 Valve Corporation + */ + +#include <linux/array_size.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/device.h> +#include <linux/dmi.h> +#include <linux/hid.h> +#include <linux/jiffies.h> +#include <linux/kstrtox.h> +#include <linux/led-class-multicolor.h> +#include <linux/mutex.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/workqueue.h> + +#include "hid-ids.h" + +#define OXP_PACKET_SIZE 64 + +#define GEN1_MESSAGE_ID 0xff +#define GEN2_MESSAGE_ID 0x3f + +#define GEN1_USAGE_PAGE 0xff01 +#define GEN2_USAGE_PAGE 0xff00 + +enum oxp_function_index { + OXP_FID_GEN1_RGB_SET = 0x07, + OXP_FID_GEN1_RGB_REPLY = 0x0f, + OXP_FID_GEN2_TOGGLE_MODE = 0xb2, + OXP_FID_GEN2_RUMBLE_SET = 0xb3, + OXP_FID_GEN2_KEY_STATE = 0xb4, + OXP_FID_GEN2_STATUS_EVENT = 0xb8, +}; + +#define OXP_MAPPING_GAMEPAD 0x01 +#define OXP_MAPPING_KEYBOARD 0x02 + +struct oxp_button_data { + u8 mode; + u8 index; + u8 key_id; + u8 padding[2]; +} __packed; + +struct oxp_button_entry { + struct oxp_button_data data; + const char *name; +}; + +static const struct oxp_button_entry oxp_button_table[] = { + /* Gamepad Buttons */ + { { OXP_MAPPING_GAMEPAD, 0x01 }, "BTN_A" }, + { { OXP_MAPPING_GAMEPAD, 0x02 }, "BTN_B" }, + { { OXP_MAPPING_GAMEPAD, 0x03 }, "BTN_X" }, + { { OXP_MAPPING_GAMEPAD, 0x04 }, "BTN_Y" }, + { { OXP_MAPPING_GAMEPAD, 0x05 }, "BTN_LB" }, + { { OXP_MAPPING_GAMEPAD, 0x06 }, "BTN_RB" }, + { { OXP_MAPPING_GAMEPAD, 0x07 }, "BTN_LT" }, + { { OXP_MAPPING_GAMEPAD, 0x08 }, "BTN_RT" }, + { { OXP_MAPPING_GAMEPAD, 0x09 }, "BTN_START" }, + { { OXP_MAPPING_GAMEPAD, 0x0a }, "BTN_SELECT" }, + { { OXP_MAPPING_GAMEPAD, 0x0b }, "BTN_L3" }, + { { OXP_MAPPING_GAMEPAD, 0x0c }, "BTN_R3" }, + { { OXP_MAPPING_GAMEPAD, 0x0d }, "DPAD_UP" }, + { { OXP_MAPPING_GAMEPAD, 0x0e }, "DPAD_DOWN" }, + { { OXP_MAPPING_GAMEPAD, 0x0f }, "DPAD_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x10 }, "DPAD_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x11 }, "JOY_L_UP" }, + { { OXP_MAPPING_GAMEPAD, 0x12 }, "JOY_L_UP_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x13 }, "JOY_L_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x14 }, "JOY_L_DOWN_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x15 }, "JOY_L_DOWN" }, + { { OXP_MAPPING_GAMEPAD, 0x16 }, "JOY_L_DOWN_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x17 }, "JOY_L_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x18 }, "JOY_L_UP_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x19 }, "JOY_R_UP" }, + { { OXP_MAPPING_GAMEPAD, 0x1a }, "JOY_R_UP_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x1b }, "JOY_R_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x1c }, "JOY_R_DOWN_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x1d }, "JOY_R_DOWN" }, + { { OXP_MAPPING_GAMEPAD, 0x1e }, "JOY_R_DOWN_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x1f }, "JOY_R_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x20 }, "JOY_R_UP_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x22 }, "BTN_GUIDE" }, + /* Keyboard Keys */ + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5a }, "KEY_F1" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5b }, "KEY_F2" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5c }, "KEY_F3" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5d }, "KEY_F4" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5e }, "KEY_F5" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5f }, "KEY_F6" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x60 }, "KEY_F7" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x61 }, "KEY_F8" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x62 }, "KEY_F9" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x63 }, "KEY_F10" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x64 }, "KEY_F11" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x65 }, "KEY_F12" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x66 }, "KEY_F13" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x67 }, "KEY_F14" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x68 }, "KEY_F15" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x69 }, "KEY_F16" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6a }, "KEY_F17" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6b }, "KEY_F18" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6c }, "KEY_F19" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6d }, "KEY_F20" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6e }, "KEY_F21" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6f }, "KEY_F22" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x70 }, "KEY_F23" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x71 }, "KEY_F24" }, +}; + +enum oxp_joybutton_index { + BUTTON_A = 0x01, + BUTTON_B, + BUTTON_X, + BUTTON_Y, + BUTTON_LB, + BUTTON_RB, + BUTTON_LT, + BUTTON_RT, + BUTTON_START, + BUTTON_SELECT, + BUTTON_L3, + BUTTON_R3, + BUTTON_DUP, + BUTTON_DDOWN, + BUTTON_DLEFT, + BUTTON_DRIGHT, + BUTTON_M1 = 0x22, + BUTTON_M2, + /* These are unused currently, reserved for future devices */ + BUTTON_M3, + BUTTON_M4, + BUTTON_M5, + BUTTON_M6, +}; + +struct oxp_button_idx { + enum oxp_joybutton_index button_idx; + u8 mapping_idx; +} __packed; + +struct oxp_bmap_page_1 { + struct oxp_button_idx btn_a; + struct oxp_button_idx btn_b; + struct oxp_button_idx btn_x; + struct oxp_button_idx btn_y; + struct oxp_button_idx btn_lb; + struct oxp_button_idx btn_rb; + struct oxp_button_idx btn_lt; + struct oxp_button_idx btn_rt; + struct oxp_button_idx btn_start; +} __packed; + +struct oxp_bmap_page_2 { + struct oxp_button_idx btn_select; + struct oxp_button_idx btn_l3; + struct oxp_button_idx btn_r3; + struct oxp_button_idx btn_dup; + struct oxp_button_idx btn_ddown; + struct oxp_button_idx btn_dleft; + struct oxp_button_idx btn_dright; + struct oxp_button_idx btn_m1; + struct oxp_button_idx btn_m2; +} __packed; + +static struct oxp_hid_cfg { + struct delayed_work oxp_rgb_queue; + struct delayed_work oxp_btn_queue; + struct oxp_bmap_page_1 *bmap_1; + struct oxp_bmap_page_2 *bmap_2; + struct delayed_work oxp_mcu_init; + struct led_classdev_mc *led_mc; + struct hid_device *hdev; + struct mutex cfg_mutex; /*ensure single synchronous output report*/ + u8 rgb_brightness; + u8 gamepad_mode; + u8 rumble_intensity; + u8 rgb_effect; + u8 rgb_speed; + u8 rgb_en; +} drvdata; + +#define OXP_FILL_PAGE_SLOT(page, btn) \ + { .button_idx = (page)->btn.button_idx, \ + .mapping_idx = (page)->btn.mapping_idx } + +enum oxp_gamepad_mode_index { + OXP_GP_MODE_XINPUT = 0x00, + OXP_GP_MODE_DEBUG = 0x03, +}; + +static const char *const oxp_gamepad_mode_text[] = { + [OXP_GP_MODE_XINPUT] = "xinput", + [OXP_GP_MODE_DEBUG] = "debug", +}; + +enum oxp_feature_en_index { + OXP_FEAT_DISABLED, + OXP_FEAT_ENABLED, +}; + +static const char *const oxp_feature_en_text[] = { + [OXP_FEAT_DISABLED] = "false", + [OXP_FEAT_ENABLED] = "true", +}; + +enum oxp_rgb_effect_index { + OXP_UNKNOWN, + OXP_EFFECT_AURORA, + OXP_EFFECT_BIRTHDAY, + OXP_EFFECT_FLOWING, + OXP_EFFECT_CHROMA_1, + OXP_EFFECT_NEON, + OXP_EFFECT_CHROMA_2, + OXP_EFFECT_DREAMY, + OXP_EFFECT_WARM, + OXP_EFFECT_CYBERPUNK, + OXP_EFFECT_SEA, + OXP_EFFECT_SUNSET, + OXP_EFFECT_COLORFUL, + OXP_EFFECT_MONSTER, + OXP_EFFECT_GREEN, + OXP_EFFECT_BLUE, + OXP_EFFECT_YELLOW, + OXP_EFFECT_TEAL, + OXP_EFFECT_PURPLE, + OXP_EFFECT_FOGGY, + OXP_EFFECT_MONO_LIST, /* placeholder for effect_index_show */ +}; + +/* These belong to rgb_effect_index, but we want to hide them from + * rgb_effect_text + */ + +#define OXP_GET_PROPERTY 0xfc +#define OXP_SET_PROPERTY 0xfd +#define OXP_EFFECT_MONO_TRUE 0xfe /* actual index for monocolor */ + +static const char *const oxp_rgb_effect_text[] = { + [OXP_UNKNOWN] = "unknown", + [OXP_EFFECT_AURORA] = "aurora", + [OXP_EFFECT_BIRTHDAY] = "birthday_cake", + [OXP_EFFECT_FLOWING] = "flowing_light", + [OXP_EFFECT_CHROMA_1] = "chroma_popping", + [OXP_EFFECT_NEON] = "neon", + [OXP_EFFECT_CHROMA_2] = "chroma_breathing", + [OXP_EFFECT_DREAMY] = "dreamy", + [OXP_EFFECT_WARM] = "warm_sun", + [OXP_EFFECT_CYBERPUNK] = "cyberpunk", + [OXP_EFFECT_SEA] = "sea_foam", + [OXP_EFFECT_SUNSET] = "sunset_afterglow", + [OXP_EFFECT_COLORFUL] = "colorful", + [OXP_EFFECT_MONSTER] = "monster_woke", + [OXP_EFFECT_GREEN] = "green_breathing", + [OXP_EFFECT_BLUE] = "blue_breathing", + [OXP_EFFECT_YELLOW] = "yellow_breathing", + [OXP_EFFECT_TEAL] = "teal_breathing", + [OXP_EFFECT_PURPLE] = "purple_breathing", + [OXP_EFFECT_FOGGY] = "foggy_haze", + [OXP_EFFECT_MONO_LIST] = "monocolor", +}; + +enum oxp_rumble_side_index { + OXP_RUMBLE_LEFT = 0x00, + OXP_RUMBLE_RIGHT, +}; + +struct oxp_gen_1_rgb_report { + u8 report_id; + u8 message_id; + u8 padding_2[2]; + u8 effect; + u8 enabled; + u8 speed; + u8 brightness; + u8 red; + u8 green; + u8 blue; +} __packed; + +struct oxp_gen_2_rgb_report { + u8 report_id; + u8 header_id; + u8 padding_2; + u8 message_id; + u8 padding_4[2]; + u8 enabled; + u8 speed; + u8 brightness; + u8 red; + u8 green; + u8 blue; + u8 padding_12[3]; + u8 effect; +} __packed; + +struct oxp_attr { + u8 index; +}; + +static u16 get_usage_page(struct hid_device *hdev) +{ + return hdev->collection[0].usage >> 16; +} + +static int oxp_hid_raw_event_gen_1(struct hid_device *hdev, + struct hid_report *report, u8 *data, + int size) +{ + struct led_classdev_mc *led_mc = drvdata.led_mc; + struct oxp_gen_1_rgb_report *rgb_rep; + + if (data[1] != OXP_FID_GEN1_RGB_REPLY) + return 0; + + rgb_rep = (struct oxp_gen_1_rgb_report *)data; + /* Ensure we save monocolor as the list value */ + drvdata.rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ? + OXP_EFFECT_MONO_LIST : + rgb_rep->effect; + drvdata.rgb_speed = rgb_rep->speed; + drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED : + OXP_FEAT_ENABLED; + drvdata.rgb_brightness = rgb_rep->brightness; + led_mc->led_cdev.brightness = rgb_rep->brightness / 4 * + led_mc->led_cdev.max_brightness; + /* If monocolor had less than 100% brightness on the previous boot, + * there will be no reliable way to determine the real intensity. + * Since intensity scaling is used with a hardware brightness set at max, + * our brightness will always look like 100%. Use the last set value to + * prevent successive boots from lowering the brightness further. + * Brightness will be "wrong" but the effect will remain the same visually. + */ + led_mc->subled_info[0].intensity = rgb_rep->red; + led_mc->subled_info[1].intensity = rgb_rep->green; + led_mc->subled_info[2].intensity = rgb_rep->blue; + + return 0; +} + +static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, u8 data_size); +static int oxp_set_buttons(void); +static int oxp_rumble_intensity_set(u8 intensity); + +static void oxp_mcu_init_fn(struct work_struct *work) +{ + u8 gp_mode_data[3] = { OXP_GP_MODE_DEBUG, 0x01, 0x02 }; + int ret; + + /* Re-apply the button mapping */ + ret = oxp_set_buttons(); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to set button mapping: %i\n", ret); + + /* Cycle the gamepad mode */ + ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to set gamepad mode: %i\n", ret); + + /* Remainder only applies for xinput mode */ + if (drvdata.gamepad_mode == OXP_GP_MODE_DEBUG) + return; + + gp_mode_data[0] = OXP_GP_MODE_XINPUT; + ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to set gamepad mode: %i\n", ret); + + /* Set vibration level */ + ret = oxp_rumble_intensity_set(drvdata.rumble_intensity); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to set rumble intensity: %i\n", ret); +} + +static int oxp_hid_raw_event_gen_2(struct hid_device *hdev, + struct hid_report *report, u8 *data, + int size) +{ + struct led_classdev_mc *led_mc = drvdata.led_mc; + struct oxp_gen_2_rgb_report *rgb_rep; + + if (data[0] != OXP_FID_GEN2_STATUS_EVENT) + return 0; + + /* Sent ~6s after resume event, indicating the MCU has fully reset. + * Re-apply our settings after this has been received. + */ + if (data[3] == OXP_EFFECT_MONO_TRUE) { + mod_delayed_work(system_dfl_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50)); + return 0; + } + + if (data[3] != OXP_GET_PROPERTY) + return 0; + + rgb_rep = (struct oxp_gen_2_rgb_report *)data; + /* Ensure we save monocolor as the list value */ + drvdata.rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ? + OXP_EFFECT_MONO_LIST : + rgb_rep->effect; + drvdata.rgb_speed = rgb_rep->speed; + drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED : + OXP_FEAT_ENABLED; + drvdata.rgb_brightness = rgb_rep->brightness; + led_mc->led_cdev.brightness = rgb_rep->brightness / 4 * + led_mc->led_cdev.max_brightness; + /* If monocolor had less than 100% brightness on the previous boot, + * there will be no reliable way to determine the real intensity. + * Since intensity scaling is used with a hardware brightness set at max, + * our brightness will always look like 100%. Use the last set value to + * prevent successive boots from lowering the brightness further. + * Brightness will be "wrong" but the effect will remain the same visually. + */ + led_mc->subled_info[0].intensity = rgb_rep->red; + led_mc->subled_info[1].intensity = rgb_rep->green; + led_mc->subled_info[2].intensity = rgb_rep->blue; + + return 0; +} + +static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + u16 up = get_usage_page(hdev); + + dev_dbg(&hdev->dev, "raw event data: [%*ph]\n", OXP_PACKET_SIZE, data); + + switch (up) { + case GEN1_USAGE_PAGE: + return oxp_hid_raw_event_gen_1(hdev, report, data, size); + case GEN2_USAGE_PAGE: + return oxp_hid_raw_event_gen_2(hdev, report, data, size); + default: + break; + } + + return 0; +} + +static int mcu_property_out(u8 *header, size_t header_size, u8 *data, + size_t data_size, u8 *footer, size_t footer_size) +{ + unsigned char *dmabuf __free(kfree) = kzalloc(OXP_PACKET_SIZE, GFP_KERNEL); + int ret; + + if (!dmabuf) + return -ENOMEM; + + if (header_size + data_size + footer_size > OXP_PACKET_SIZE) + return -EINVAL; + + guard(mutex)(&drvdata.cfg_mutex); + memcpy(dmabuf, header, header_size); + memcpy(dmabuf + header_size, data, data_size); + if (footer_size) + memcpy(dmabuf + OXP_PACKET_SIZE - footer_size, footer, footer_size); + + dev_dbg(&drvdata.hdev->dev, "raw data: [%*ph]\n", OXP_PACKET_SIZE, dmabuf); + + ret = hid_hw_output_report(drvdata.hdev, dmabuf, OXP_PACKET_SIZE); + if (ret < 0) + return ret; + + /* MCU takes 200ms to be ready for another command. */ + msleep(200); + return ret == OXP_PACKET_SIZE ? 0 : -EIO; +} + +static int oxp_gen_1_property_out(enum oxp_function_index fid, u8 *data, + u8 data_size) +{ + u8 header[] = { fid, GEN1_MESSAGE_ID }; + size_t header_size = ARRAY_SIZE(header); + + return mcu_property_out(header, header_size, data, data_size, NULL, 0); +} + +static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, + u8 data_size) +{ + u8 header[] = { fid, GEN2_MESSAGE_ID, 0x01 }; + u8 footer[] = { GEN2_MESSAGE_ID, fid }; + size_t header_size = ARRAY_SIZE(header); + size_t footer_size = ARRAY_SIZE(footer); + + return mcu_property_out(header, header_size, data, data_size, footer, + footer_size); +} + +static ssize_t gamepad_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + u16 up = get_usage_page(drvdata.hdev); + u8 data[3] = { 0x00, 0x01, 0x02 }; + int ret = -EINVAL; + int i; + + if (up != GEN2_USAGE_PAGE) + return ret; + + for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) { + if (oxp_gamepad_mode_text[i] && sysfs_streq(buf, oxp_gamepad_mode_text[i])) { + ret = i; + break; + } + } + if (ret < 0) + return ret; + + data[0] = ret; + + ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, data, 3); + if (ret) + return ret; + + drvdata.gamepad_mode = data[0]; + + if (drvdata.gamepad_mode == OXP_GP_MODE_DEBUG) + return count; + + /* Re-apply rumble settings as switching gamepad mode will override */ + ret = oxp_rumble_intensity_set(drvdata.rumble_intensity); + if (ret) + return ret; + + return count; +} + +static ssize_t gamepad_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%s\n", oxp_gamepad_mode_text[drvdata.gamepad_mode]); +} +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; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) { + if (!oxp_gamepad_mode_text[i] || + oxp_gamepad_mode_text[i][0] == '\0') + continue; + + count += sysfs_emit_at(buf, count, "%s ", oxp_gamepad_mode_text[i]); + } + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(gamepad_mode_index); + +static void oxp_set_defaults_bmap_1(struct oxp_bmap_page_1 *bmap) +{ + bmap->btn_a.button_idx = BUTTON_A; + bmap->btn_a.mapping_idx = 0; + bmap->btn_b.button_idx = BUTTON_B; + bmap->btn_b.mapping_idx = 1; + bmap->btn_x.button_idx = BUTTON_X; + bmap->btn_x.mapping_idx = 2; + bmap->btn_y.button_idx = BUTTON_Y; + bmap->btn_y.mapping_idx = 3; + bmap->btn_lb.button_idx = BUTTON_LB; + bmap->btn_lb.mapping_idx = 4; + bmap->btn_rb.button_idx = BUTTON_RB; + bmap->btn_rb.mapping_idx = 5; + bmap->btn_lt.button_idx = BUTTON_LT; + bmap->btn_lt.mapping_idx = 6; + bmap->btn_rt.button_idx = BUTTON_RT; + bmap->btn_rt.mapping_idx = 7; + bmap->btn_start.button_idx = BUTTON_START; + bmap->btn_start.mapping_idx = 8; +} + +static void oxp_set_defaults_bmap_2(struct oxp_bmap_page_2 *bmap) +{ + bmap->btn_select.button_idx = BUTTON_SELECT; + bmap->btn_select.mapping_idx = 9; + bmap->btn_l3.button_idx = BUTTON_L3; + bmap->btn_l3.mapping_idx = 10; + bmap->btn_r3.button_idx = BUTTON_R3; + bmap->btn_r3.mapping_idx = 11; + bmap->btn_dup.button_idx = BUTTON_DUP; + bmap->btn_dup.mapping_idx = 12; + bmap->btn_ddown.button_idx = BUTTON_DDOWN; + bmap->btn_ddown.mapping_idx = 13; + bmap->btn_dleft.button_idx = BUTTON_DLEFT; + bmap->btn_dleft.mapping_idx = 14; + bmap->btn_dright.button_idx = BUTTON_DRIGHT; + bmap->btn_dright.mapping_idx = 15; + bmap->btn_m1.button_idx = BUTTON_M1; + bmap->btn_m1.mapping_idx = 48; /* KEY_F15 */ + bmap->btn_m2.button_idx = BUTTON_M2; + bmap->btn_m2.mapping_idx = 49; /* KEY_F16 */ +} + +static void oxp_page_fill_data(char *buf, const struct oxp_button_idx *buttons, + size_t len) +{ + size_t offset_increment = sizeof(u8) + sizeof(struct oxp_button_idx); + size_t offset = 5; + unsigned int i; + + for (i = 0; i < len; i++, offset += offset_increment) { + buf[offset] = (u8)buttons[i].button_idx; + memcpy(buf + offset + 1, + &oxp_button_table[buttons[i].mapping_idx].data, + sizeof(struct oxp_button_data)); + } +} + +static int oxp_set_buttons(void) +{ + u8 page_1[59] = { 0x02, 0x38, 0x20, 0x01, 0x01 }; + u8 page_2[59] = { 0x02, 0x38, 0x20, 0x02, 0x01 }; + u16 up = get_usage_page(drvdata.hdev); + int ret; + + if (up != GEN2_USAGE_PAGE) + return -EINVAL; + + const struct oxp_button_idx p1[] = { + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_a), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_b), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_x), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_y), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_lb), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_rb), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_lt), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_rt), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_start), + }; + + const struct oxp_button_idx p2[] = { + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_select), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_l3), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_r3), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dup), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_ddown), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dleft), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dright), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_m1), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_m2), + }; + + oxp_page_fill_data(page_1, p1, ARRAY_SIZE(p1)); + oxp_page_fill_data(page_2, p2, ARRAY_SIZE(p2)); + + ret = oxp_gen_2_property_out(OXP_FID_GEN2_KEY_STATE, page_1, ARRAY_SIZE(page_1)); + if (ret) + return ret; + + return oxp_gen_2_property_out(OXP_FID_GEN2_KEY_STATE, page_2, ARRAY_SIZE(page_2)); +} + +static void oxp_reset_buttons(void) +{ + oxp_set_defaults_bmap_1(drvdata.bmap_1); + oxp_set_defaults_bmap_2(drvdata.bmap_2); +} + +static ssize_t reset_buttons_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int val, ret; + + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; + + if (val != 1) + return -EINVAL; + + oxp_reset_buttons(); + ret = oxp_set_buttons(); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_WO(reset_buttons); + +static void oxp_btn_queue_fn(struct work_struct *work) +{ + int ret; + + ret = oxp_set_buttons(); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to write button mapping: %i\n", ret); +} + +static int oxp_button_idx_from_str(const char *buf) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(oxp_button_table); i++) + if (sysfs_streq(buf, oxp_button_table[i].name)) + return i; + + return -EINVAL; +} + +static ssize_t map_button_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count, u8 index) +{ + int idx; + + idx = oxp_button_idx_from_str(buf); + if (idx < 0) + return idx; + + switch (index) { + case BUTTON_A: + drvdata.bmap_1->btn_a.mapping_idx = idx; + break; + case BUTTON_B: + drvdata.bmap_1->btn_b.mapping_idx = idx; + break; + case BUTTON_X: + drvdata.bmap_1->btn_x.mapping_idx = idx; + break; + case BUTTON_Y: + drvdata.bmap_1->btn_y.mapping_idx = idx; + break; + case BUTTON_LB: + drvdata.bmap_1->btn_lb.mapping_idx = idx; + break; + case BUTTON_RB: + drvdata.bmap_1->btn_rb.mapping_idx = idx; + break; + case BUTTON_LT: + drvdata.bmap_1->btn_lt.mapping_idx = idx; + break; + case BUTTON_RT: + drvdata.bmap_1->btn_rt.mapping_idx = idx; + break; + case BUTTON_START: + drvdata.bmap_1->btn_start.mapping_idx = idx; + break; + case BUTTON_SELECT: + drvdata.bmap_2->btn_select.mapping_idx = idx; + break; + case BUTTON_L3: + drvdata.bmap_2->btn_l3.mapping_idx = idx; + break; + case BUTTON_R3: + drvdata.bmap_2->btn_r3.mapping_idx = idx; + break; + case BUTTON_DUP: + drvdata.bmap_2->btn_dup.mapping_idx = idx; + break; + case BUTTON_DDOWN: + drvdata.bmap_2->btn_ddown.mapping_idx = idx; + break; + case BUTTON_DLEFT: + drvdata.bmap_2->btn_dleft.mapping_idx = idx; + break; + case BUTTON_DRIGHT: + drvdata.bmap_2->btn_dright.mapping_idx = idx; + break; + case BUTTON_M1: + drvdata.bmap_2->btn_m1.mapping_idx = idx; + break; + case BUTTON_M2: + drvdata.bmap_2->btn_m2.mapping_idx = idx; + break; + default: + return -EINVAL; + } + mod_delayed_work(system_dfl_wq, &drvdata.oxp_btn_queue, msecs_to_jiffies(50)); + return count; +} + +static ssize_t map_button_show(struct device *dev, + struct device_attribute *attr, char *buf, + u8 index) +{ + u8 i; + + switch (index) { + case BUTTON_A: + i = drvdata.bmap_1->btn_a.mapping_idx; + break; + case BUTTON_B: + i = drvdata.bmap_1->btn_b.mapping_idx; + break; + case BUTTON_X: + i = drvdata.bmap_1->btn_x.mapping_idx; + break; + case BUTTON_Y: + i = drvdata.bmap_1->btn_y.mapping_idx; + break; + case BUTTON_LB: + i = drvdata.bmap_1->btn_lb.mapping_idx; + break; + case BUTTON_RB: + i = drvdata.bmap_1->btn_rb.mapping_idx; + break; + case BUTTON_LT: + i = drvdata.bmap_1->btn_lt.mapping_idx; + break; + case BUTTON_RT: + i = drvdata.bmap_1->btn_rt.mapping_idx; + break; + case BUTTON_START: + i = drvdata.bmap_1->btn_start.mapping_idx; + break; + case BUTTON_SELECT: + i = drvdata.bmap_2->btn_select.mapping_idx; + break; + case BUTTON_L3: + i = drvdata.bmap_2->btn_l3.mapping_idx; + break; + case BUTTON_R3: + i = drvdata.bmap_2->btn_r3.mapping_idx; + break; + case BUTTON_DUP: + i = drvdata.bmap_2->btn_dup.mapping_idx; + break; + case BUTTON_DDOWN: + i = drvdata.bmap_2->btn_ddown.mapping_idx; + break; + case BUTTON_DLEFT: + i = drvdata.bmap_2->btn_dleft.mapping_idx; + break; + case BUTTON_DRIGHT: + i = drvdata.bmap_2->btn_dright.mapping_idx; + break; + case BUTTON_M1: + i = drvdata.bmap_2->btn_m1.mapping_idx; + break; + case BUTTON_M2: + i = drvdata.bmap_2->btn_m2.mapping_idx; + break; + default: + return -EINVAL; + } + + if (i >= ARRAY_SIZE(oxp_button_table)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", oxp_button_table[i].name); +} + +static ssize_t button_mapping_options_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(oxp_button_table); i++) + count += sysfs_emit_at(buf, count, "%s ", oxp_button_table[i].name); + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(button_mapping_options); + +static int oxp_rumble_intensity_set(u8 intensity) +{ + u8 header[15] = { 0x02, 0x38, 0x02, 0xe3, 0x39, 0xe3, 0x39, 0xe3, + 0x39, 0x01, intensity, 0x05, 0xe3, 0x39, 0xe3 }; + u8 footer[9] = { 0x39, 0xe3, 0x39, 0xe3, 0xe3, 0x02, 0x04, 0x39, 0x39 }; + size_t footer_size = ARRAY_SIZE(footer); + size_t header_size = ARRAY_SIZE(header); + u8 data[59] = { 0x0 }; + size_t data_size = ARRAY_SIZE(data); + + memcpy(data, header, header_size); + memcpy(data + data_size - footer_size, footer, footer_size); + + return oxp_gen_2_property_out(OXP_FID_GEN2_RUMBLE_SET, data, data_size); +} + +static ssize_t rumble_intensity_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + + if (val < 0 || val > 5) + return -EINVAL; + + ret = oxp_rumble_intensity_set(val); + if (ret) + return ret; + + drvdata.rumble_intensity = val; + + return count; +} + +static ssize_t rumble_intensity_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%i\n", drvdata.rumble_intensity); +} +static DEVICE_ATTR_RW(rumble_intensity); + +static ssize_t rumble_intensity_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0-5\n"); +} +static DEVICE_ATTR_RO(rumble_intensity_range); + +#define OXP_DEVICE_ATTR_RW(_name, _group) \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return _group##_store(dev, attr, buf, count, _name.index); \ + } \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_show(dev, attr, buf, _name.index); \ + } \ + static DEVICE_ATTR_RW(_name) + +static struct oxp_attr button_a = { BUTTON_A }; +OXP_DEVICE_ATTR_RW(button_a, map_button); + +static struct oxp_attr button_b = { BUTTON_B }; +OXP_DEVICE_ATTR_RW(button_b, map_button); + +static struct oxp_attr button_x = { BUTTON_X }; +OXP_DEVICE_ATTR_RW(button_x, map_button); + +static struct oxp_attr button_y = { BUTTON_Y }; +OXP_DEVICE_ATTR_RW(button_y, map_button); + +static struct oxp_attr button_lb = { BUTTON_LB }; +OXP_DEVICE_ATTR_RW(button_lb, map_button); + +static struct oxp_attr button_rb = { BUTTON_RB }; +OXP_DEVICE_ATTR_RW(button_rb, map_button); + +static struct oxp_attr button_lt = { BUTTON_LT }; +OXP_DEVICE_ATTR_RW(button_lt, map_button); + +static struct oxp_attr button_rt = { BUTTON_RT }; +OXP_DEVICE_ATTR_RW(button_rt, map_button); + +static struct oxp_attr button_start = { BUTTON_START }; +OXP_DEVICE_ATTR_RW(button_start, map_button); + +static struct oxp_attr button_select = { BUTTON_SELECT }; +OXP_DEVICE_ATTR_RW(button_select, map_button); + +static struct oxp_attr button_l3 = { BUTTON_L3 }; +OXP_DEVICE_ATTR_RW(button_l3, map_button); + +static struct oxp_attr button_r3 = { BUTTON_R3 }; +OXP_DEVICE_ATTR_RW(button_r3, map_button); + +static struct oxp_attr button_d_up = { BUTTON_DUP }; +OXP_DEVICE_ATTR_RW(button_d_up, map_button); + +static struct oxp_attr button_d_down = { BUTTON_DDOWN }; +OXP_DEVICE_ATTR_RW(button_d_down, map_button); + +static struct oxp_attr button_d_left = { BUTTON_DLEFT }; +OXP_DEVICE_ATTR_RW(button_d_left, map_button); + +static struct oxp_attr button_d_right = { BUTTON_DRIGHT }; +OXP_DEVICE_ATTR_RW(button_d_right, map_button); + +static struct oxp_attr button_m1 = { BUTTON_M1 }; +OXP_DEVICE_ATTR_RW(button_m1, map_button); + +static struct oxp_attr button_m2 = { BUTTON_M2 }; +OXP_DEVICE_ATTR_RW(button_m2, map_button); + +static struct attribute *oxp_cfg_attrs[] = { + &dev_attr_button_a.attr, + &dev_attr_button_b.attr, + &dev_attr_button_d_down.attr, + &dev_attr_button_d_left.attr, + &dev_attr_button_d_right.attr, + &dev_attr_button_d_up.attr, + &dev_attr_button_l3.attr, + &dev_attr_button_lb.attr, + &dev_attr_button_lt.attr, + &dev_attr_button_m1.attr, + &dev_attr_button_m2.attr, + &dev_attr_button_mapping_options.attr, + &dev_attr_button_r3.attr, + &dev_attr_button_rb.attr, + &dev_attr_button_rt.attr, + &dev_attr_button_select.attr, + &dev_attr_button_start.attr, + &dev_attr_button_x.attr, + &dev_attr_button_y.attr, + &dev_attr_gamepad_mode.attr, + &dev_attr_gamepad_mode_index.attr, + &dev_attr_reset_buttons.attr, + &dev_attr_rumble_intensity.attr, + &dev_attr_rumble_intensity_range.attr, + NULL, +}; + +static const struct attribute_group oxp_cfg_attrs_group = { + .attrs = oxp_cfg_attrs, +}; + +static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness) +{ + u16 up = get_usage_page(drvdata.hdev); + u8 *data; + + /* Always default to max brightness and use intensity scaling when in + * monocolor mode. + */ + switch (up) { + case GEN1_USAGE_PAGE: + data = (u8[4]) { OXP_SET_PROPERTY, enabled, speed, brightness }; + if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST) + data[3] = 0x04; + return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 4); + case GEN2_USAGE_PAGE: + data = (u8[6]) { OXP_SET_PROPERTY, 0x00, 0x02, enabled, speed, brightness }; + if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST) + data[5] = 0x04; + return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 6); + default: + return -ENODEV; + } +} + +static ssize_t oxp_rgb_status_show(void) +{ + u16 up = get_usage_page(drvdata.hdev); + u8 *data; + + switch (up) { + case GEN1_USAGE_PAGE: + data = (u8[1]) { OXP_GET_PROPERTY }; + return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1); + case GEN2_USAGE_PAGE: + data = (u8[3]) { OXP_GET_PROPERTY, 0x00, 0x02 }; + return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3); + default: + return -ENODEV; + } +} + +static int oxp_rgb_color_set(void) +{ + u8 max_br = drvdata.led_mc->led_cdev.max_brightness; + u8 br = drvdata.led_mc->led_cdev.brightness; + u16 up = get_usage_page(drvdata.hdev); + u8 green, red, blue; + size_t size; + u8 *data; + int i; + + red = br * drvdata.led_mc->subled_info[0].intensity / max_br; + green = br * drvdata.led_mc->subled_info[1].intensity / max_br; + blue = br * drvdata.led_mc->subled_info[2].intensity / max_br; + + switch (up) { + case GEN1_USAGE_PAGE: + size = 55; + data = (u8[55]) { OXP_EFFECT_MONO_TRUE }; + + for (i = 0; i < (size - 1) / 3; i++) { + data[3 * i + 1] = red; + data[3 * i + 2] = green; + data[3 * i + 3] = blue; + } + return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, size); + case GEN2_USAGE_PAGE: + size = 57; + data = (u8[57]) { OXP_EFFECT_MONO_TRUE, 0x00, 0x02 }; + + for (i = 1; i < size / 3; i++) { + data[3 * i] = red; + data[3 * i + 1] = green; + data[3 * i + 2] = blue; + } + return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, size); + default: + return -ENODEV; + } +} + +static int oxp_rgb_effect_set(u8 effect) +{ + u16 up = get_usage_page(drvdata.hdev); + u8 *data; + int ret; + + switch (effect) { + case OXP_EFFECT_AURORA: + case OXP_EFFECT_BIRTHDAY: + case OXP_EFFECT_FLOWING: + case OXP_EFFECT_CHROMA_1: + case OXP_EFFECT_NEON: + case OXP_EFFECT_CHROMA_2: + case OXP_EFFECT_DREAMY: + case OXP_EFFECT_WARM: + case OXP_EFFECT_CYBERPUNK: + case OXP_EFFECT_SEA: + case OXP_EFFECT_SUNSET: + case OXP_EFFECT_COLORFUL: + case OXP_EFFECT_MONSTER: + case OXP_EFFECT_GREEN: + case OXP_EFFECT_BLUE: + case OXP_EFFECT_YELLOW: + case OXP_EFFECT_TEAL: + case OXP_EFFECT_PURPLE: + case OXP_EFFECT_FOGGY: + switch (up) { + case GEN1_USAGE_PAGE: + data = (u8[1]) { effect }; + ret = oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1); + break; + case GEN2_USAGE_PAGE: + data = (u8[3]) { effect, 0x00, 0x02 }; + ret = oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3); + break; + default: + ret = -ENODEV; + } + break; + case OXP_EFFECT_MONO_LIST: + ret = oxp_rgb_color_set(); + break; + default: + return -EINVAL; + } + + if (ret) + return ret; + + drvdata.rgb_effect = effect; + + return 0; +} + +static ssize_t enabled_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret = sysfs_match_string(oxp_feature_en_text, buf); + if (ret < 0) + return ret; + val = ret; + + ret = oxp_rgb_status_store(val, drvdata.rgb_speed, + drvdata.rgb_brightness); + if (ret) + return ret; + + drvdata.rgb_en = val; + return count; +} + +static ssize_t enabled_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + + ret = oxp_rgb_status_show(); + if (ret) + return ret; + + if (drvdata.rgb_en >= ARRAY_SIZE(oxp_feature_en_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", oxp_feature_en_text[drvdata.rgb_en]); +} +static DEVICE_ATTR_RW(enabled); + +static ssize_t enabled_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + size_t count = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(oxp_feature_en_text); i++) + count += sysfs_emit_at(buf, count, "%s ", oxp_feature_en_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(enabled_index); + +static ssize_t effect_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret = sysfs_match_string(oxp_rgb_effect_text, buf); + if (ret < 0) + return ret; + + val = ret; + + ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed, + drvdata.rgb_brightness); + if (ret) + return ret; + + ret = oxp_rgb_effect_set(val); + if (ret) + return ret; + + return count; +} + +static ssize_t effect_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + + ret = oxp_rgb_status_show(); + if (ret) + return ret; + + if (drvdata.rgb_effect >= ARRAY_SIZE(oxp_rgb_effect_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", oxp_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) +{ + size_t count = 0; + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(oxp_rgb_effect_text); i++) + count += sysfs_emit_at(buf, count, "%s ", oxp_rgb_effect_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(effect_index); + +static ssize_t speed_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + + if (val > 9) + return -EINVAL; + + ret = oxp_rgb_status_store(drvdata.rgb_en, val, drvdata.rgb_brightness); + if (ret) + return ret; + + drvdata.rgb_speed = val; + return count; +} + +static ssize_t speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + + ret = oxp_rgb_status_show(); + if (ret) + return ret; + + if (drvdata.rgb_speed > 9) + return -EINVAL; + + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_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-9\n"); +} +static DEVICE_ATTR_RO(speed_range); + +static void oxp_rgb_queue_fn(struct work_struct *work) +{ + unsigned int max_brightness = drvdata.led_mc->led_cdev.max_brightness; + unsigned int brightness = drvdata.led_mc->led_cdev.brightness; + u8 val = 4 * brightness / max_brightness; + int ret; + + if (drvdata.rgb_brightness != val) { + ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed, val); + if (ret) + dev_err(drvdata.led_mc->led_cdev.dev, + "Error: Failed to write RGB Status: %i\n", ret); + + drvdata.rgb_brightness = val; + } + + if (drvdata.rgb_effect != OXP_EFFECT_MONO_LIST) + return; + + ret = oxp_rgb_effect_set(drvdata.rgb_effect); + if (ret) + dev_err(drvdata.led_mc->led_cdev.dev, "Error: Failed to write RGB color: %i\n", + ret); +} + +static void oxp_rgb_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + led_cdev->brightness = brightness; + mod_delayed_work(system_dfl_wq, &drvdata.oxp_rgb_queue, msecs_to_jiffies(50)); +} + +static struct attribute *oxp_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 oxp_rgb_attr_group = { + .attrs = oxp_rgb_attrs, +}; + +static struct mc_subled oxp_rgb_subled_info[] = { + { + .color_index = LED_COLOR_ID_RED, + .intensity = 0x24, + .channel = 0x1, + }, + { + .color_index = LED_COLOR_ID_GREEN, + .intensity = 0x22, + .channel = 0x2, + }, + { + .color_index = LED_COLOR_ID_BLUE, + .intensity = 0x99, + .channel = 0x3, + }, +}; + +static struct led_classdev_mc oxp_cdev_rgb = { + .led_cdev = { + .name = "oxp:rgb:joystick_rings", + .color = LED_COLOR_ID_RGB, + .brightness = 0x64, + .max_brightness = 0x64, + .brightness_set = oxp_rgb_brightness_set, + }, + .num_colors = ARRAY_SIZE(oxp_rgb_subled_info), + .subled_info = oxp_rgb_subled_info, +}; + +struct quirk_entry { + bool hybrid_mcu; +}; + +static struct quirk_entry quirk_hybrid_mcu = { + .hybrid_mcu = true, +}; + +static const struct dmi_system_id oxp_hybrid_mcu_list[] = { + { + .ident = "OneXPlayer Apex", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER APEX"), + }, + .driver_data = &quirk_hybrid_mcu, + }, + { + .ident = "OneXPlayer G1 AMD", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 A"), + }, + .driver_data = &quirk_hybrid_mcu, + }, + { + .ident = "OneXPlayer G1 Intel", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 i"), + }, + .driver_data = &quirk_hybrid_mcu, + }, + {}, +}; + +static bool oxp_hybrid_mcu_device(void) +{ + const struct dmi_system_id *dmi_id; + struct quirk_entry *quirks; + + dmi_id = dmi_first_match(oxp_hybrid_mcu_list); + if (!dmi_id) + return false; + + quirks = dmi_id->driver_data; + + return quirks->hybrid_mcu; +} + +static int oxp_cfg_probe(struct hid_device *hdev, u16 up) +{ + struct oxp_bmap_page_1 *bmap_1; + struct oxp_bmap_page_2 *bmap_2; + int ret; + + hid_set_drvdata(hdev, &drvdata); + mutex_init(&drvdata.cfg_mutex); + drvdata.hdev = hdev; + + if (up == GEN2_USAGE_PAGE && oxp_hybrid_mcu_device()) + goto skip_rgb; + + drvdata.led_mc = &oxp_cdev_rgb; + + INIT_DELAYED_WORK(&drvdata.oxp_rgb_queue, oxp_rgb_queue_fn); + ret = devm_led_classdev_multicolor_register(&hdev->dev, &oxp_cdev_rgb); + if (ret) + return dev_err_probe(&hdev->dev, ret, + "Failed to create RGB device\n"); + + ret = devm_device_add_group(drvdata.led_mc->led_cdev.dev, + &oxp_rgb_attr_group); + if (ret) + return dev_err_probe(drvdata.led_mc->led_cdev.dev, ret, + "Failed to create RGB configuration attributes\n"); + + ret = oxp_rgb_status_show(); + if (ret) + dev_warn(drvdata.led_mc->led_cdev.dev, + "Failed to query RGB initial state: %i\n", ret); + + /* Below features are only implemented in gen 2 */ + if (up != GEN2_USAGE_PAGE) + return 0; + +skip_rgb: + bmap_1 = devm_kzalloc(&hdev->dev, sizeof(struct oxp_bmap_page_1), GFP_KERNEL); + if (!bmap_1) + return dev_err_probe(&hdev->dev, -ENOMEM, + "Unable to allocate button map page 1\n"); + + bmap_2 = devm_kzalloc(&hdev->dev, sizeof(struct oxp_bmap_page_2), GFP_KERNEL); + if (!bmap_2) + return dev_err_probe(&hdev->dev, -ENOMEM, + "Unable to allocate button map page 2\n"); + + drvdata.bmap_1 = bmap_1; + drvdata.bmap_2 = bmap_2; + oxp_reset_buttons(); + INIT_DELAYED_WORK(&drvdata.oxp_btn_queue, oxp_btn_queue_fn); + + drvdata.gamepad_mode = OXP_GP_MODE_XINPUT; + drvdata.rumble_intensity = 5; + + INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn); + 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) + return dev_err_probe(&hdev->dev, ret, + "Failed to attach configuration attributes\n"); + + return 0; +} + +static int oxp_hid_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + u16 up; + + ret = hid_parse(hdev); + if (ret) + return dev_err_probe(&hdev->dev, ret, "Failed to parse HID device\n"); + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) + return dev_err_probe(&hdev->dev, ret, "Failed to start HID device\n"); + + ret = hid_hw_open(hdev); + if (ret) { + hid_hw_stop(hdev); + return dev_err_probe(&hdev->dev, ret, "Failed to open HID device\n"); + } + + up = get_usage_page(hdev); + dev_dbg(&hdev->dev, "Got usage page %04x\n", up); + + switch (up) { + case GEN1_USAGE_PAGE: + case GEN2_USAGE_PAGE: + ret = oxp_cfg_probe(hdev, up); + if (ret) { + hid_hw_close(hdev); + hid_hw_stop(hdev); + } + + return ret; + default: + return 0; + } +} + +static void oxp_hid_remove(struct hid_device *hdev) +{ + cancel_delayed_work_sync(&drvdata.oxp_rgb_queue); + cancel_delayed_work_sync(&drvdata.oxp_btn_queue); + cancel_delayed_work_sync(&drvdata.oxp_mcu_init); + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static const struct hid_device_id oxp_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_CRSC, USB_DEVICE_ID_ONEXPLAYER_GEN1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_WCH, USB_DEVICE_ID_ONEXPLAYER_GEN2) }, + {} +}; + +MODULE_DEVICE_TABLE(hid, oxp_devices); +static struct hid_driver hid_oxp = { + .name = "hid-oxp", + .id_table = oxp_devices, + .probe = oxp_hid_probe, + .remove = oxp_hid_remove, + .raw_event = oxp_hid_raw_event, +}; +module_hid_driver(hid_oxp); + +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); +MODULE_DESCRIPTION("Driver for OneXPlayer HID Interfaces"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-picolcd_core.c b/drivers/hid/hid-picolcd_core.c index 2cc01e1bc1a8..d73e97c8b853 100644 --- a/drivers/hid/hid-picolcd_core.c +++ b/drivers/hid/hid-picolcd_core.c @@ -72,7 +72,8 @@ struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev, struct picolcd_pending *work; struct hid_report *report = picolcd_out_report(report_id, hdev); unsigned long flags; - int i, j, k; + int i, j; + unsigned int k; if (!report || !data) return NULL; 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; } + if (report->maxfield < 1) { + hid_err(hid, "no fields in the report\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); + 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; } - hid_info(hid, "Force feedback for PantherLord/GreenAsia devices by Anssi Hannula <anssi.hannula@gmail.com>\n"); + 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-playstation.c b/drivers/hid/hid-playstation.c index c43caac20b61..f9dc9378cf77 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -112,6 +112,12 @@ struct ps_led_info { #define DS_BUTTONS2_TOUCHPAD BIT(1) #define DS_BUTTONS2_MIC_MUTE BIT(2) +/* DualSense Edge extra buttons in buttons[2], bits 4-7. */ +#define DS_EDGE_BUTTONS_FN1 BIT(4) +#define DS_EDGE_BUTTONS_FN2 BIT(5) +#define DS_EDGE_BUTTONS_LEFT_PADDLE BIT(6) +#define DS_EDGE_BUTTONS_RIGHT_PADDLE BIT(7) + /* Status fields of DualSense input report. */ #define DS_STATUS0_BATTERY_CAPACITY GENMASK(3, 0) #define DS_STATUS0_CHARGING GENMASK(7, 4) @@ -178,6 +184,9 @@ struct dualsense { struct input_dev *touchpad; struct input_dev *jack; + /* True if this is a DualSense Edge (product 0x0df2). */ + bool is_edge; + /* Update version is used as a feature/capability version. */ u16 update_version; @@ -1486,6 +1495,18 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r input_report_key(ds->gamepad, BTN_THUMBL, ds_report->buttons[1] & DS_BUTTONS1_L3); input_report_key(ds->gamepad, BTN_THUMBR, ds_report->buttons[1] & DS_BUTTONS1_R3); input_report_key(ds->gamepad, BTN_MODE, ds_report->buttons[2] & DS_BUTTONS2_PS_HOME); + + if (ds->is_edge) { + input_report_key(ds->gamepad, BTN_TRIGGER_HAPPY1, + ds_report->buttons[2] & DS_EDGE_BUTTONS_FN1); + input_report_key(ds->gamepad, BTN_TRIGGER_HAPPY2, + ds_report->buttons[2] & DS_EDGE_BUTTONS_FN2); + input_report_key(ds->gamepad, BTN_TRIGGER_HAPPY3, + ds_report->buttons[2] & DS_EDGE_BUTTONS_LEFT_PADDLE); + input_report_key(ds->gamepad, BTN_TRIGGER_HAPPY4, + ds_report->buttons[2] & DS_EDGE_BUTTONS_RIGHT_PADDLE); + } + input_sync(ds->gamepad); /* @@ -1785,6 +1806,7 @@ static struct ps_device *dualsense_create(struct hid_device *hdev) ds->use_vibration_v2 = ds->update_version >= DS_FEATURE_VERSION(2, 21); } else if (hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER_2) { ds->use_vibration_v2 = true; + ds->is_edge = true; } ret = ps_devices_list_add(ps_dev); @@ -1802,6 +1824,15 @@ static struct ps_device *dualsense_create(struct hid_device *hdev) ret = PTR_ERR(ds->gamepad); goto err; } + + /* Register DualSense Edge back paddle and Fn buttons. */ + if (ds->is_edge) { + input_set_capability(ds->gamepad, EV_KEY, BTN_TRIGGER_HAPPY1); + input_set_capability(ds->gamepad, EV_KEY, BTN_TRIGGER_HAPPY2); + input_set_capability(ds->gamepad, EV_KEY, BTN_TRIGGER_HAPPY3); + input_set_capability(ds->gamepad, EV_KEY, BTN_TRIGGER_HAPPY4); + } + /* Use gamepad input device name as primary device name for e.g. LEDs */ ps_dev->input_dev_name = dev_name(&ds->gamepad->dev); @@ -2384,7 +2415,8 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * } ds4_report = &usb->common; - num_touch_reports = usb->num_touch_reports; + num_touch_reports = min_t(u8, usb->num_touch_reports, + ARRAY_SIZE(usb->touch_reports)); touch_reports = usb->touch_reports; } else if (hdev->bus == BUS_BLUETOOTH && report->id == DS4_INPUT_REPORT_BT && size == DS4_INPUT_REPORT_BT_SIZE) { @@ -2404,7 +2436,8 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * } ds4_report = &bt->common; - num_touch_reports = bt->num_touch_reports; + num_touch_reports = min_t(u8, bt->num_touch_reports, + ARRAY_SIZE(bt->touch_reports)); touch_reports = bt->touch_reports; } else if (hdev->bus == BUS_BLUETOOTH && report->id == DS4_INPUT_REPORT_BT_MINIMAL && diff --git a/drivers/hid/hid-primax.c b/drivers/hid/hid-primax.c index e44d79dff8de..8db054280afb 100644 --- a/drivers/hid/hid-primax.c +++ b/drivers/hid/hid-primax.c @@ -44,7 +44,7 @@ static int px_raw_event(struct hid_device *hid, struct hid_report *report, data[0] |= (1 << (data[idx] - 0xE0)); data[idx] = 0; } - hid_report_raw_event(hid, HID_INPUT_REPORT, data, size, 0); + hid_report_raw_event(hid, HID_INPUT_REPORT, data, size, size, 0); return 1; default: /* unknown report */ diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 9e88c9d6c6dc..96a36c5ba04f 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 }, @@ -183,10 +186,12 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_2), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_PRO), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X65), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, + { HID_USB_DEVICE(USB_VENDOR_ID_SDINNOVATION, USB_DEVICE_ID_SDINNOVATION_GAMING_KBD), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD2), HID_QUIRK_NO_INIT_REPORTS }, { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD), HID_QUIRK_NO_INIT_REPORTS }, { HID_USB_DEVICE(USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB), HID_QUIRK_NOGET }, { HID_USB_DEVICE(USB_VENDOR_ID_SIGMA_MICRO, USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD), HID_QUIRK_NO_INIT_REPORTS }, + { HID_USB_DEVICE(USB_VENDOR_ID_SIGMA_MICRO, USB_DEVICE_ID_SIGMA_MICRO_USB_MOUSE), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_SIGMATEL, USB_DEVICE_ID_SIGMATEL_STMP3780), HID_QUIRK_NOGET }, { HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS1030_TOUCH), HID_QUIRK_NOGET }, { HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS817_TOUCH), HID_QUIRK_NOGET }, @@ -235,7 +240,7 @@ static const struct hid_device_id hid_quirks[] = { * used as a driver. See hid_scan_report(). */ static const struct hid_device_id hid_have_special_driver[] = { -#if IS_ENABLED(CONFIG_APPLEDISPLAY) +#if IS_ENABLED(CONFIG_USB_APPLEDISPLAY) { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9218) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9219) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x921c) }, @@ -421,13 +426,14 @@ static const struct hid_device_id hid_have_special_driver[] = { #endif #if IS_ENABLED(CONFIG_HID_ELECOM) { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, - { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, @@ -746,8 +752,23 @@ 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_7_2018) }, { 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-rakk.c b/drivers/hid/hid-rakk.c new file mode 100644 index 000000000000..1140058a76ca --- /dev/null +++ b/drivers/hid/hid-rakk.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * HID driver for Rakk devices + * + * Copyright (c) 2026 Karl Cayme + * + * The Rakk Dasig X gaming mouse has a faulty HID report descriptor that + * declares USAGE_MAXIMUM = 3 (buttons 1-3) while actually sending 5 button + * bits (REPORT_COUNT = 5). This causes the kernel to ignore side buttons + * (buttons 4 and 5). This driver fixes the descriptor so all 5 buttons + * are properly recognized across 3 modes (wired, dongle, and Bluetooth). + */ + +#include <linux/device.h> +#include <linux/hid.h> +#include <linux/module.h> +#include "hid-ids.h" + +/* + * The faulty byte is at offset 17 in the report descriptor for all three + * connection modes (USB direct, wireless dongle, and Bluetooth). + * + * Bytes 16-17 are: 0x29 0x03 (USAGE_MAXIMUM = 3) + * The fix changes byte 17 to 0x05 (USAGE_MAXIMUM = 5). + * + * Original descriptor bytes 0-17: + * 05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 + * ^^ + * Should be 0x05 to declare 5 buttons instead of 3. + */ +#define RAKK_RDESC_USAGE_MAX_OFFSET 17 +#define RAKK_RDESC_USAGE_MAX_ORIG 0x03 +#define RAKK_RDESC_USAGE_MAX_FIXED 0x05 +#define RAKK_RDESC_USB_SIZE 193 +#define RAKK_RDESC_DONGLE_SIZE 150 +#define RAKK_RDESC_BT_SIZE 89 + +static const __u8 *rakk_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + if (((*rsize == RAKK_RDESC_USB_SIZE && + hdev->product == USB_DEVICE_ID_TELINK_RAKK_DASIG_X) || + (*rsize == RAKK_RDESC_DONGLE_SIZE && + hdev->product == USB_DEVICE_ID_TELINK_RAKK_DASIG_X_DONGLE) || + (*rsize == RAKK_RDESC_BT_SIZE && + hdev->product == USB_DEVICE_ID_TELINK_RAKK_DASIG_X_BT)) && + rdesc[RAKK_RDESC_USAGE_MAX_OFFSET] == RAKK_RDESC_USAGE_MAX_ORIG) { + hid_info(hdev, "fixing Rakk Dasig X button count (3 -> 5)\n"); + rdesc[RAKK_RDESC_USAGE_MAX_OFFSET] = RAKK_RDESC_USAGE_MAX_FIXED; + } + + return rdesc; +} + +static const struct hid_device_id rakk_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_TELINK, + USB_DEVICE_ID_TELINK_RAKK_DASIG_X) }, + { HID_USB_DEVICE(USB_VENDOR_ID_TELINK, + USB_DEVICE_ID_TELINK_RAKK_DASIG_X_DONGLE) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_TELINK, + USB_DEVICE_ID_TELINK_RAKK_DASIG_X_BT) }, + { } +}; +MODULE_DEVICE_TABLE(hid, rakk_devices); + +static struct hid_driver rakk_driver = { + .name = "rakk", + .id_table = rakk_devices, + .report_fixup = rakk_report_fixup, +}; +module_hid_driver(rakk_driver); + +MODULE_DESCRIPTION("HID driver for Rakk Dasig X mouse - fix side button support"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Karl Cayme"); 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..ecc19387f6b0 100644 --- a/drivers/hid/hid-rmi.c +++ b/drivers/hid/hid-rmi.c @@ -235,7 +235,23 @@ static int rmi_hid_read_block(struct rmi_transport_dev *xport, u16 addr, break; } - read_input_count = data->readReport[1]; + read_input_count = min_t(int, data->readReport[1], + data->input_report_size - 2); + if (!read_input_count) { + /* + * A zero length reply advances neither + * bytes_read nor bytes_needed, and because a + * reply did arrive the wait above does not + * time out either, so a device answering 0 + * forever would spin here indefinitely with + * page_mutex held. + */ + hid_warn(hdev, "%s: zero-length read reply\n", + __func__); + clear_bit(RMI_READ_DATA_PENDING, &data->flags); + ret = -EIO; + break; + } memcpy(buf + bytes_read, &data->readReport[2], min(read_input_count, bytes_needed)); @@ -271,6 +287,11 @@ static int rmi_hid_write_block(struct rmi_transport_dev *xport, u16 addr, goto exit; } + if (len + 4 > data->output_report_size) { + ret = -EINVAL; + goto exit; + } + data->writeReport[0] = RMI_WRITE_REPORT_ID; data->writeReport[1] = len; data->writeReport[2] = addr & 0xFF; @@ -365,7 +386,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; @@ -666,8 +687,16 @@ static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id) return ret; } - if (id->driver_data) - data->device_flags = id->driver_data; + /* + * RMI_DEVICE can only mean "this probe validated the RMI reports and + * allocated writeReport": every bail-out to start below skips that + * allocation, and device_flags left carrying RMI_DEVICE from + * driver_data would send rmi_input_configured() into rmi_set_page() + * with writeReport still NULL. A bind through the new_id sysfs + * attribute can supply driver_data with the bit set, so do not let + * driver_data grant it. + */ + data->device_flags = id->driver_data & ~RMI_DEVICE; /* * Check for the RMI specific report ids. If they are misisng @@ -696,6 +725,17 @@ static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id) data->output_report_size = hid_report_len(output_report); + /* + * The write reports built by this driver occupy 6 bytes and the read + * handshake looks at the first 3 bytes of an input report, so refuse + * to drive a device whose reports cannot hold them. + */ + if (data->output_report_size < 6 || data->input_report_size < 3) { + hid_err(hdev, "rmi reports too small (out=%u in=%u)\n", + data->output_report_size, data->input_report_size); + goto start; + } + data->device_flags |= RMI_DEVICE; alloc_size = data->output_report_size + data->input_report_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 <kunit/test.h> + +/* + * Regression test for the out-of-bounds read in + * kone_keep_values_up_to_date(): a malicious USB device sends a + * "switch profile" HID event (event == kone_mouse_event_switch_profile) + * with an attacker-chosen value in 0..255, which is used unbounded as + * profiles[value - 1]. On an unpatched kernel the attack case triggers a + * KASAN slab-out-of-bounds read; the fix must leave actual_dpi unchanged. + */ +static void kone_profile_index_oob_test(struct kunit *test) +{ + struct kone_device *kone; + struct kone_mouse_event ev = {}; + /* + * Allocate only up to the end of profiles[] so that any index past + * the 5-element array is IMMEDIATELY out of bounds and lands in the + * KASAN redzone (a far over-read would hit unrelated valid memory and + * escape KASAN). + */ + size_t sz = offsetof(struct kone_device, profiles) + + sizeof(kone->profiles); + + kone = kunit_kzalloc(test, sz, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, kone); + kone->profiles[0].startup_dpi = 0x42; + + /* benign control: a valid in-range value drives the SAME path and + * must succeed (proves the trigger reaches the real code). + */ + ev.event = kone_mouse_event_switch_profile; + ev.value = 1; + kone_keep_values_up_to_date(kone, &ev); + KUNIT_EXPECT_EQ(test, kone->actual_dpi, 0x42); + + /* attack: value == ARRAY_SIZE(profiles) + 1 reads profiles[5], one + * element past the array end -> KASAN slab-out-of-bounds read on an + * unpatched kernel. The fix must reject it (actual_dpi unchanged). + */ + ev.value = ARRAY_SIZE(kone->profiles) + 1; + kone_keep_values_up_to_date(kone, &ev); + KUNIT_EXPECT_EQ(test, kone->actual_dpi, 0x42); +} + +static struct kunit_case kone_test_cases[] = { + KUNIT_CASE(kone_profile_index_oob_test), + {} +}; + +static struct kunit_suite kone_test_suite = { + .name = "hid-roccat-kone", + .test_cases = kone_test_cases, +}; +kunit_test_suite(kone_test_suite); +#endif /* CONFIG_HID_ROCCAT_KONE_KUNIT_TEST */ diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c index d6fff53d4ee7..5deb6da8d4f7 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); } } @@ -335,8 +344,6 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report return temp; } - mutex_unlock(&devices_lock); - init_waitqueue_head(&device->wait); INIT_LIST_HEAD(&device->readers); mutex_init(&device->readers_lock); @@ -347,6 +354,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report device->cbuf_end = 0; device->report_size = report_size; + mutex_unlock(&devices_lock); return minor; } EXPORT_SYMBOL_GPL(roccat_connect); @@ -360,22 +368,21 @@ void roccat_disconnect(int minor) mutex_lock(&devices_lock); device = devices[minor]; - mutex_unlock(&devices_lock); device->exist = 0; /* TODO exist maybe not needed */ device_destroy(device->dev->class, MKDEV(roccat_major, minor)); - mutex_lock(&devices_lock); devices[minor] = NULL; - mutex_unlock(&devices_lock); if (device->open) { hid_hw_close(device->hid); wake_up_interruptible(&device->wait); } else { - kfree(device); + roccat_free_device(device); } + + mutex_unlock(&devices_lock); } 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); + ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj, + &enable_sensor_attr_group); if (ret) - goto err_remove_group; + goto err_remove_attributes; ret = hid_sensor_custom_dev_if_add(sensor_inst); if (ret) - goto err_remove_attributes; + 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 90666ff629de..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,26 +263,29 @@ 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; + } + if (field->report_count > SIZE_MAX / field_size) { 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; + report_size = field_size * field->report_count; + report_size = min_t(size_t, report_size, buffer_size); - 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); + 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); @@ -286,6 +294,54 @@ done_proc: } EXPORT_SYMBOL_GPL(sensor_hub_get_feature); +int sensor_hub_input_attr_read_values(struct hid_sensor_hub_device *hsdev, + u32 usage_id, u32 attr_usage_id, + u32 report_id, + enum sensor_hub_read_flags flag, + u32 buffer_size, u8 *buffer) +{ + struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev); + struct hid_report *report; + unsigned long flags; + long cycles; + int ret; + + report = sensor_hub_report(report_id, hsdev->hdev, HID_INPUT_REPORT); + if (!report) + return -EINVAL; + + mutex_lock(hsdev->mutex_ptr); + if (flag == SENSOR_HUB_SYNC) { + memset(&hsdev->pending, 0, sizeof(hsdev->pending)); + init_completion(&hsdev->pending.ready); + hsdev->pending.usage_id = usage_id; + hsdev->pending.attr_usage_id = attr_usage_id; + hsdev->pending.max_raw_size = buffer_size; + hsdev->pending.raw_data = buffer; + + spin_lock_irqsave(&data->lock, flags); + hsdev->pending.status = true; + spin_unlock_irqrestore(&data->lock, flags); + } + mutex_lock(&data->mutex); + hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT); + mutex_unlock(&data->mutex); + ret = 0; + if (flag == SENSOR_HUB_SYNC) { + cycles = wait_for_completion_interruptible_timeout(&hsdev->pending.ready, + HZ * 5); + if (cycles == 0) + ret = -ETIMEDOUT; + else if (cycles < 0) + ret = cycles; + + hsdev->pending.status = false; + } + mutex_unlock(hsdev->mutex_ptr); + + return ret; +} +EXPORT_SYMBOL_GPL(sensor_hub_input_attr_read_values); int sensor_hub_input_attr_get_raw_value(struct hid_sensor_hub_device *hsdev, u32 usage_id, @@ -478,6 +534,8 @@ static int sensor_hub_raw_event(struct hid_device *hdev, struct hid_collection *collection = NULL; void *priv = NULL; struct hid_sensor_hub_device *hsdev = NULL; + u32 copy_size; + u32 avail; hid_dbg(hdev, "sensor_hub_raw_event report id:0x%x size:%d type:%d\n", report->id, size, report->type); @@ -518,12 +576,27 @@ static int sensor_hub_raw_event(struct hid_device *hdev, hsdev->pending.attr_usage_id == report->field[i]->logical)) { hid_dbg(hdev, "data was pending ...\n"); - hsdev->pending.raw_data = kmemdup(ptr, sz, GFP_ATOMIC); - if (hsdev->pending.raw_data) - hsdev->pending.raw_size = sz; - else - hsdev->pending.raw_size = 0; - complete(&hsdev->pending.ready); + if (hsdev->pending.max_raw_size) { + if (hsdev->pending.index < hsdev->pending.max_raw_size) { + avail = hsdev->pending.max_raw_size - hsdev->pending.index; + copy_size = clamp(sz, 0U, avail); + + memcpy(hsdev->pending.raw_data + hsdev->pending.index, + ptr, copy_size); + hsdev->pending.index += copy_size; + if (hsdev->pending.index >= hsdev->pending.max_raw_size) { + hsdev->pending.raw_size = hsdev->pending.index; + complete(&hsdev->pending.ready); + } + } + } else { + hsdev->pending.raw_data = kmemdup(ptr, sz, GFP_ATOMIC); + if (hsdev->pending.raw_data) + hsdev->pending.raw_size = sz; + else + hsdev->pending.raw_size = 0; + complete(&hsdev->pending.ready); + } } if (callback->capture_sample) { if (report->field[i]->logical) diff --git a/drivers/hid/hid-sjoy.c b/drivers/hid/hid-sjoy.c index bab93d71b760..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; } + if (report->maxfield < 1) { + hid_err(hid, "no fields in the report\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; + if (report->field[0]->report_count < 3) { + hid_err(hid, "not enough values in the field\n"); + return -ENODEV; + } - dev = hidinput->input; + sjoyff = kzalloc_obj(struct sjoyff_device); + if (!sjoyff) + return -ENOMEM; - set_bit(FF_RUMBLE, dev->ffbit); + set_bit(FF_RUMBLE, dev->ffbit); - error = input_ff_create_memless(dev, sjoyff, hid_sjoyff_play); - if (error) { - kfree(sjoyff); - return error; - } + 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); - 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; } - hid_info(hid, "Force feedback for SmartJoy PLUS PS2/USB adapter\n"); - 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 b5e724676c1d..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 <linux/cleanup.h> #include <linux/device.h> #include <linux/hid.h> #include <linux/module.h> @@ -81,6 +82,7 @@ #define SONY_FF_SUPPORT (SIXAXIS_CONTROLLER | MOTION_CONTROLLER) #define SONY_BT_DEVICE (SIXAXIS_CONTROLLER_BT | MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER_BT) #define NSG_MRXU_REMOTE (NSG_MR5U_REMOTE_BT | NSG_MR7U_REMOTE_BT) +#define RB4_GUITAR_PS4 (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT) #define MAX_LEDS 4 #define NSG_MRXU_MAX_X 1667 @@ -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 int rb3_pro_instrument_raw_event(struct sony_sc *sc, u8 *rd, int size) +{ + /* Rock Band 3 PS3 Pro instruments set rd[24] to 0xE0 when they're + * sending full reports, and 0x02 when only sending navigation. + */ + if (size < 25 || rd[24] != 0x02) + return 0; + + /* Only attempt to enable full report every 8 seconds */ + if (time_after(jiffies, sc->rb3_pro_poke_jiffies)) { + sc->rb3_pro_poke_jiffies = jiffies + secs_to_jiffies(8); + rb3_pro_instrument_enable_full_report(sc); + } + + return 0; } -static void rb4_ps4_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size) +static int rb4_ps4_guitar_raw_event(struct sony_sc *sc, u8 *rd, int size) { + const int expected_size = (sc->quirks & RB4_GUITAR_PS4_BT) ? 78 : 64; + + if (unlikely(size != expected_size || rd[0] != 0x01)) + return 0; + /* * Rock Band 4 PS4 guitars have whammy and * tilt functionality, they're located at @@ -1084,15 +1127,18 @@ static void rb4_ps4_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size) input_report_abs(sc->input_dev, ABS_RZ, rd[45]); input_sync(sc->input_dev); + return 0; } -static void rb4_ps5_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size) +static int rb4_ps5_guitar_raw_event(struct sony_sc *sc, u8 *rd, int size) { u8 charging_status; u8 battery_data; u8 battery_capacity; u8 battery_status; - unsigned long flags; + + if (unlikely(size != 64 || rd[0] != 0x01)) + return 0; /* * Rock Band 4 PS5 guitars have whammy and @@ -1132,74 +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) { - nsg_mrxu_parse_report(sc, rd, size); - return 1; - } else if ((sc->quirks & RB4_GUITAR_PS4_USB) && rd[0] == 0x01 && size == 64) { - rb4_ps4_guitar_parse_report(sc, rd, size); - return 1; - } else if ((sc->quirks & RB4_GUITAR_PS4_BT) && rd[0] == 0x01 && size == 78) { - rb4_ps4_guitar_parse_report(sc, rd, size); - return 1; - } else if ((sc->quirks & RB4_GUITAR_PS5) && rd[0] == 0x01 && size == 64) { - rb4_ps5_guitar_parse_report(sc, rd, size); - return 1; - } - - /* Rock Band 3 PS3 Pro instruments set rd[24] to 0xE0 when they're - * sending full reports, and 0x02 when only sending navigation. - */ - if ((sc->quirks & RB3_PRO_INSTRUMENT) && rd[24] == 0x02) { - /* Only attempt to enable full report every 8 seconds */ - if (time_after(jiffies, sc->rb3_pro_poke_jiffies)) { - sc->rb3_pro_poke_jiffies = jiffies + secs_to_jiffies(8); - rb3_pro_instrument_enable_full_report(sc); - } + if (sc->raw_event) { + ret = sc->raw_event(sc, rd, size); + if (unlikely(ret < 0)) + return ret; } - if (sc->defer_initialization) { + if (unlikely(sc->defer_initialization)) { sc->defer_initialization = 0; - sony_schedule_work(sc, SONY_WORKER_STATE); + sony_schedule_work(sc); } return 0; @@ -1257,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) @@ -1270,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); @@ -1293,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); @@ -1335,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); @@ -1355,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 @@ -1508,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); } @@ -1619,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; @@ -1640,9 +1634,6 @@ static int sony_leds_init(struct sony_sc *sc) u8 max_brightness[MAX_LEDS] = { [0 ... (MAX_LEDS - 1)] = 1 }; u8 use_hw_blink[MAX_LEDS] = { 0 }; - if (WARN_ON(!(sc->quirks & SONY_LED_SUPPORT))) - return -EINVAL; - if (sc->quirks & BUZZ_CONTROLLER) { sc->led_count = 4; use_color_names = 0; @@ -1850,24 +1841,14 @@ 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; } static int sony_init_ff(struct sony_sc *sc) { - struct hid_input *hidinput; - struct input_dev *input_dev; - - if (list_empty(&sc->hdev->inputs)) { - hid_err(sc->hdev, "no inputs found\n"); - return -ENODEV; - } - hidinput = list_entry(sc->hdev->inputs.next, struct hid_input, list); - input_dev = hidinput->input; - - input_set_capability(input_dev, EV_FF, FF_RUMBLE); - return input_ff_create_memless(input_dev, NULL, sony_play_effect); + input_set_capability(sc->input_dev, EV_FF, FF_RUMBLE); + return input_ff_create_memless(sc->input_dev, NULL, sony_play_effect); } #else @@ -1883,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: @@ -1973,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, @@ -1990,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) @@ -2124,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 *)) { @@ -2137,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) { @@ -2154,6 +2140,8 @@ static int sony_input_configured(struct hid_device *hdev, int append_dev_id; int ret; + sc->input_dev = hidinput->input; + ret = sony_set_device_id(sc); if (ret < 0) { hid_err(hdev, "failed to allocate the device id\n"); @@ -2197,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) { /* @@ -2211,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) { /* @@ -2225,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 @@ -2249,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) { /* @@ -2270,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) { /* @@ -2285,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) { @@ -2314,14 +2315,11 @@ static int sony_input_configured(struct hid_device *hdev, goto err_close; } - sc->input_dev = hidinput->input; return 0; 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; } @@ -2345,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); @@ -2373,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; } @@ -2427,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; } @@ -2437,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); } @@ -2456,11 +2453,10 @@ static void sony_remove(struct hid_device *hdev) static int sony_suspend(struct hid_device *hdev, pm_message_t message) { #ifdef CONFIG_SONY_FF + struct sony_sc *sc = hid_get_drvdata(hdev); /* On suspend stop any running force-feedback events */ - if (SONY_FF_SUPPORT) { - struct sony_sc *sc = hid_get_drvdata(hdev); - + if (sc->quirks & SONY_FF_SUPPORT) { sc->left = sc->right = 0; sony_send_output_report(sc); } diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c index 197126d6e081..0f364d43aa4b 100644 --- a/drivers/hid/hid-steam.c +++ b/drivers/hid/hid-steam.c @@ -43,11 +43,14 @@ #include <linux/rcupdate.h> #include <linux/delay.h> #include <linux/power_supply.h> +#include <linux/unaligned.h> +#include <linux/usb.h> #include "hid-ids.h" MODULE_DESCRIPTION("HID driver for Valve Steam Controller"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>"); +MODULE_AUTHOR("Vicki Pfau <vi@endrift.com>"); 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; + + /* + * 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[0]; + 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,53 +461,100 @@ 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)); + if (!(steam->quirks & STEAM_QUIRK_IBEX)) + len += 1; + + /* + * Sometimes the wireless controller fails with EPIPE + * when sending a feature report. + * Doing a HID_REQ_GET_REPORT and waiting for a while + * seems to fix that. + */ + do { + 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); - return ret; + /* + * 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(struct steam_device *steam, - u8 *cmd, int 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[0]; + 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); if (!buf) return -ENOMEM; - /* The report ID is always 0 */ + /* 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_GET_REPORT and waiting for a while + * Doing a HID_REQ_SET_REPORT and waiting for a while * 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, 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)); - if (ret < 0) - goto out; - ret = steam_recv_report(steam, reply, sizeof(reply)); + ret = steam_exchange_report(steam, cmd, sizeof(cmd), 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_obj(struct steam_ibex_output_report); + + 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_obj(struct steam_ibex_output_report); - 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); - - 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); + 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_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)); + if (steam->registered) { + mutex_unlock(&steam->registration_mutex); + return 0; + } - hid_info(steam->hdev, "Steam Controller '%s' connected", - steam->serial_no); + 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)); - /* ignore battery errors, we can live without it */ - if (steam->quirks & STEAM_QUIRK_WIRELESS) - steam_battery_register(steam); + ret = steam_get_attributes(steam); + if (ret < 0) + hid_err(steam->hdev, + "%s:steam_get_attributes failed with error %d\n", + __func__, ret); - mutex_lock(&steam_devices_lock); - if (list_empty(&steam->list)) - list_add(&steam->list, &steam_devices); - mutex_unlock(&steam_devices_lock); - } + hid_info(steam->hdev, "Steam %s '%s' connected", + steam->quirks & STEAM_QUIRK_DECK ? "Deck" : "Controller", + steam->serial_no); - spin_lock_irqsave(&steam->lock, flags); - client_opened = steam->client_opened; - spin_unlock_irqrestore(&steam->lock, flags); + steam_set_lizard_mode(steam, lizard_mode); + mutex_unlock(&steam->report_mutex); - 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; - } + /* 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 @@ err_cancel_work: 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 @@ err_cancel_work: 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..df61fa33d4c6 --- /dev/null +++ b/drivers/hid/hid-steelseries-arctis.c @@ -0,0 +1,762 @@ +// 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 <linux/device.h> +#include <linux/hid.h> +#include <linux/kref.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/power_supply.h> +#include <linux/spinlock.h> +#include <linux/usb.h> +#include <linux/workqueue.h> + +#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_7_2018_request_status(struct hid_device *hdev) +{ + const u8 connection[] = { 0x06, 0x14 }; + const u8 battery[] = { 0x06, 0x18 }; + int ret; + + ret = steelseries_send_output_report(hdev, connection, sizeof(connection)); + if (ret) + return ret; + + return steelseries_send_output_report(hdev, battery, sizeof(battery)); +} + +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_7_2018_parse_status(struct steelseries_device *sd, + u8 *data, int size) +{ + if (size < 3 || data[0] != 0x06) + return; + + switch (data[1]) { + case 0x14: + /* 0x03 means that the headset is connected to the transmitter. */ + sd->headset_connected = data[2] == 0x03; + break; + case 0x18: + if (!sd->headset_connected) + break; + + /* The Arctis 7 sometimes overreports battery. Cap to 100. */ + sd->battery_capacity = steelseries_map_capacity(data[2], 0, 100); + break; + } +} + +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_7_2018_info = { + .sync_interface = 5, + .capabilities = SS_CAP_BATTERY, + .request_status = steelseries_arctis_7_2018_request_status, + .parse_status = steelseries_arctis_7_2018_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); + 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_7_2018), + .driver_data = (unsigned long)&arctis_7_2018_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 <git@mayer-bgk.de>"); +MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>"); +MODULE_AUTHOR("Sriman Achanta <srimanachanta@gmail.com>"); 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 <linux/device.h> +#include <linux/dmi.h> #include <linux/hid.h> #include <linux/module.h> #include <linux/usb.h> #include <linux/leds.h> +#include <linux/led-class-multicolor.h> +#include <linux/slab.h> #include "hid-ids.h" #define STEELSERIES_SRWS1 BIT(0) -#define STEELSERIES_ARCTIS_1 BIT(1) -#define STEELSERIES_ARCTIS_9 BIT(2) +#define STEELSERIES_MSI_RGB BIT(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 @@ err: } #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 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_request_battery(struct hid_device *hdev, - const char *request, size_t len) +static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev) { - 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; - } + if (!hid_is_usb(hdev)) + return NULL; - kfree(write_buf); - return ret; + return to_usb_interface(hdev->dev.parent); } -static void steelseries_headset_fetch_battery(struct hid_device *hdev) +static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev) { - int ret = 0; + struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev); + struct usb_device *udev; - 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 (!intf) + return false; - if (ret < 0) - hid_dbg(hdev, - "Battery query failed (err: %d)\n", ret); -} + udev = interface_to_usbdev(intf); -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; + return intf == usb_ifnum_to_if(udev, 0); } -static void steelseries_headset_battery_timer_tick(struct work_struct *work) +#if STEELSERIES_HAS_LEDS_MULTICOLOR + +static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev) { - struct steelseries_device *sd = container_of(work, - struct steelseries_device, battery_work.work); - struct hid_device *hdev = sd->hdev; + struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev); - steelseries_headset_fetch_battery(hdev); -} + if (!intf) + return NULL; -#define STEELSERIES_PREFIX "SteelSeries " -#define STEELSERIES_PREFIX_LEN strlen(STEELSERIES_PREFIX) + return interface_to_usbdev(intf); +} -static int steelseries_headset_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) +static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev, + enum led_brightness brightness) { - 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; + 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; + } } - return ret; -} -static void -steelseries_headset_set_wireless_status(struct hid_device *hdev, - bool connected) -{ - struct usb_interface *intf; + /* + * 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; - if (!hid_is_usb(hdev)) - return; + 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); - intf = to_usb_interface(hdev->dev.parent); - usb_set_wireless_status(intf, connected ? - USB_WIRELESS_STATUS_CONNECTED : - USB_WIRELESS_STATUS_DISCONNECTED); +out_unlock: + mutex_unlock(&sd->rgb_lock); + return ret < 0 ? ret : 0; } -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 void steelseries_msi_rgb_free_buf(void *data) +{ + kfree(data); +} -static int steelseries_headset_battery_register(struct steelseries_device *sd) +static int steelseries_msi_rgb_register(struct steelseries_device *sd) { - static atomic_t battery_no = ATOMIC_INIT(0); - struct power_supply_config battery_cfg = { .drv_data = sd, }; - unsigned long n; + 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 (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; + } + } - if (steelseries_headset_battery_register(sd) < 0) - hid_err(sd->hdev, - "Failed to register battery for headset\n"); + return 0; +err_stop: + hid_hw_stop(hdev); return ret; } 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 <hadess@hadess.net>"); MODULE_AUTHOR("Simon Wood <simon@mungewell.org>"); -MODULE_AUTHOR("Christian Mayer <git@mayer-bgk.de>"); 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 <linux/hid.h> #include <linux/input.h> +#include <linux/math64.h> #include <linux/slab.h> #include <linux/module.h> @@ -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 @@ fail: 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-u2fzero.c b/drivers/hid/hid-u2fzero.c index 744a91e6e78c..82404b6e2d25 100644 --- a/drivers/hid/hid-u2fzero.c +++ b/drivers/hid/hid-u2fzero.c @@ -341,29 +341,33 @@ static int u2fzero_probe(struct hid_device *hdev, if (ret) return ret; - u2fzero_fill_in_urb(dev); + ret = u2fzero_fill_in_urb(dev); + if (ret) + goto err_hid_hw_stop; dev->present = true; minor = ((struct hidraw *) hdev->hidraw)->minor; ret = u2fzero_init_led(dev, minor); - if (ret) { - hid_hw_stop(hdev); - return ret; - } + if (ret) + goto err_free_urb; hid_info(hdev, "%s LED initialised\n", hw_configs[dev->hw_revision].name); ret = u2fzero_init_hwrng(dev, minor); - if (ret) { - hid_hw_stop(hdev); - return ret; - } + if (ret) + goto err_free_urb; hid_info(hdev, "%s RNG initialised\n", hw_configs[dev->hw_revision].name); return 0; + +err_free_urb: + usb_free_urb(dev->urb); +err_hid_hw_stop: + hid_hw_stop(hdev); + return ret; } static void u2fzero_remove(struct hid_device *hdev) diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index bd7f93e96e4e..0b8a83fa6c5b 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -184,7 +184,9 @@ static int uclogic_input_configured(struct hid_device *hdev, suffix = "System Control"; break; } - } else { + } + + if (suffix) { hi->input->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", hdev->name, suffix); if (!hi->input->name) @@ -546,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-vivaldi-common.c b/drivers/hid/hid-vivaldi-common.c index bf734055d4b6..b12bb5cc091a 100644 --- a/drivers/hid/hid-vivaldi-common.c +++ b/drivers/hid/hid-vivaldi-common.c @@ -85,7 +85,7 @@ void vivaldi_feature_mapping(struct hid_device *hdev, } ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, report_data, - report_len, 0); + report_len, report_len, 0); if (ret) { dev_warn(&hdev->dev, "failed to report feature %d\n", field->report->id); diff --git a/drivers/hid/hid-wiimote-modules.c b/drivers/hid/hid-wiimote-modules.c index dbccdfa63916..dccb78bb3afd 100644 --- a/drivers/hid/hid-wiimote-modules.c +++ b/drivers/hid/hid-wiimote-modules.c @@ -2403,7 +2403,7 @@ static const struct wiimod_ops wiimod_guitar = { .in_ext = wiimod_guitar_in_ext, }; -/* +/* * Turntable * DJ Hero came with a Turntable Controller that was plugged in * as an extension. @@ -2439,15 +2439,15 @@ static const __u16 wiimod_turntable_map[] = { static void wiimod_turntable_in_ext(struct wiimote_data *wdata, const __u8 *ext) { __u8 be, cs, sx, sy, ed, rtt, rbg, rbr, rbb, ltt, lbg, lbr, lbb, bp, bm; - /* + /* * Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | *------+------+-----+-----+-----+-----+------+------+--------+ - * 0 | RTT<4:3> | SX <5:0> | - * 1 | RTT<2:1> | SY <5:0> | + * 0 | RTT<4:3> | SX <5:0> | + * 1 | RTT<2:1> | SY <5:0> | *------+------+-----+-----+-----+-----+------+------+--------+ * 2 |RTT<0>| ED<4:3> | CS<3:0> | RTT<5> | *------+------+-----+-----+-----+-----+------+------+--------+ - * 3 | ED<2:0> | LTT<4:0> | + * 3 | ED<2:0> | LTT<4:0> | *------+------+-----+-----+-----+-----+------+------+--------+ * 4 | 0 | 0 | LBR | B- | 0 | B+ | RBR | LTT<5> | *------+------+-----+-----+-----+-----+------+------+--------+ @@ -2458,20 +2458,20 @@ static void wiimod_turntable_in_ext(struct wiimote_data *wdata, const __u8 *ext) * With Motion+ enabled, it will look like this: * Byte | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | *------+------+-----+-----+-----+-----+------+------+--------+ - * 1 | RTT<4:3> | SX <5:1> | 0 | - * 2 | RTT<2:1> | SY <5:1> | 0 | + * 1 | RTT<4:3> | SX <5:1> | 0 | + * 2 | RTT<2:1> | SY <5:1> | 0 | *------+------+-----+-----+-----+-----+------+------+--------+ * 3 |RTT<0>| ED<4:3> | CS<3:0> | RTT<5> | *------+------+-----+-----+-----+-----+------+------+--------+ - * 4 | ED<2:0> | LTT<4:0> | + * 4 | ED<2:0> | LTT<4:0> | *------+------+-----+-----+-----+-----+------+------+--------+ * 5 | 0 | 0 | LBR | B- | 0 | B+ | RBR | XXXX | *------+------+-----+-----+-----+-----+------+------+--------+ * 6 | LBB | 0 | RBG | BE | LBG | RBB | XXXX | XXXX | *------+------+-----+-----+-----+-----+------+------+--------+ */ - - be = !(ext[5] & 0x10); + + be = !(ext[5] & 0x10); cs = ((ext[2] & 0x1e)); sx = ext[0] & 0x3f; sy = ext[1] & 0x3f; @@ -2499,32 +2499,32 @@ static void wiimod_turntable_in_ext(struct wiimote_data *wdata, const __u8 *ext) input_report_abs(wdata->extension.input, ABS_HAT1X, ltt); input_report_abs(wdata->extension.input, ABS_HAT2X, cs); input_report_abs(wdata->extension.input, ABS_HAT3X, ed); - input_report_key(wdata->extension.input, - wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_G_RIGHT], + input_report_key(wdata->extension.input, + wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_G_RIGHT], rbg); input_report_key(wdata->extension.input, wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_R_RIGHT], rbr); - input_report_key(wdata->extension.input, - wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_B_RIGHT], + input_report_key(wdata->extension.input, + wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_B_RIGHT], rbb); - input_report_key(wdata->extension.input, - wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_G_LEFT], + input_report_key(wdata->extension.input, + wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_G_LEFT], lbg); - input_report_key(wdata->extension.input, - wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_R_LEFT], + input_report_key(wdata->extension.input, + wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_R_LEFT], lbr); - input_report_key(wdata->extension.input, - wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_B_LEFT], + input_report_key(wdata->extension.input, + wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_B_LEFT], lbb); - input_report_key(wdata->extension.input, - wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_EUPHORIA], + input_report_key(wdata->extension.input, + wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_EUPHORIA], be); - input_report_key(wdata->extension.input, - wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_PLUS], + input_report_key(wdata->extension.input, + wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_PLUS], bp); - input_report_key(wdata->extension.input, - wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_MINUS], + input_report_key(wdata->extension.input, + wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_MINUS], bm); input_sync(wdata->extension.input); @@ -2557,7 +2557,7 @@ static void wiimod_turntable_close(struct input_dev *dev) static int wiimod_turntable_probe(const struct wiimod_ops *ops, struct wiimote_data *wdata) { - int ret, i; + int ret, i; wdata->extension.input = input_allocate_device(); if (!wdata->extension.input) @@ -2594,9 +2594,9 @@ static int wiimod_turntable_probe(const struct wiimod_ops *ops, input_set_abs_params(wdata->extension.input, ABS_HAT1X, -8, 8, 0, 0); input_set_abs_params(wdata->extension.input, - ABS_HAT2X, 0, 31, 1, 1); + ABS_HAT2X, 0, 31, 1, 1); input_set_abs_params(wdata->extension.input, - ABS_HAT3X, 0, 7, 0, 0); + ABS_HAT3X, 0, 7, 0, 0); ret = input_register_device(wdata->extension.input); if (ret) goto err_free; diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c index 9cd25a77999e..19b92c2c6579 100644 --- a/drivers/hid/hid-winwing.c +++ b/drivers/hid/hid-winwing.c @@ -315,7 +315,8 @@ static void winwing_haptic_rumble_cb(struct work_struct *work) static int winwing_play_effect(struct input_dev *dev, void *context, struct ff_effect *effect) { - struct winwing_drv_data *data = (struct winwing_drv_data *) context; + struct hid_device *hdev = input_get_drvdata(dev); + struct winwing_drv_data *data = hid_get_drvdata(hdev); if (effect->type != FF_RUMBLE) return 0; @@ -342,7 +343,12 @@ static int winwing_init_ff(struct hid_device *hdev, struct hid_input *hidinput) input_set_capability(hidinput->input, EV_FF, FF_RUMBLE); - return input_ff_create_memless(hidinput->input, data, + /* + * input_ff_create_memless() takes ownership of the context pointer + * and frees it on teardown; do not hand it the devm-managed drvdata. + * winwing_play_effect() fetches it from the input device instead. + */ + return input_ff_create_memless(hidinput->input, NULL, winwing_play_effect); } 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) <Yeking@Red54.com> + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> + +#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) <Yeking@Red54.com>"); +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 <linux/kernel.h> #include <linux/module.h> #include <linux/pm.h> -#include <linux/uuid.h> #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 (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0) + return -ENODEV; + + ret = i2c_hid_acpi_get_descriptor(adev); + if (ret < 0) + return ret; + hid_descriptor_address = ret; + + ihid_acpi = devm_kzalloc(dev, sizeof(*ihid_acpi), GFP_KERNEL); if (!ihid_acpi) return -ENOMEM; - ihid_acpi->adev = ACPI_COMPANION(dev); + ihid_acpi->adev = adev; 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); - if (ret < 0) - return ret; - hid_descriptor_address = ret; - - acpi_device_fix_up_power(ihid_acpi->adev); + 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 <linux/acpi.h> +#include <linux/uuid.h> + +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 5a183af3d5c6..a61fd1de5203 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -123,9 +123,11 @@ static const struct i2c_hid_quirks { __u32 quirks; } i2c_hid_quirks[] = { { I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288, - I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET | I2C_HID_QUIRK_BAD_INPUT_SIZE }, { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_3118, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { USB_VENDOR_ID_ALPS_JP, HID_ANY_ID, @@ -136,6 +138,8 @@ static const struct i2c_hid_quirks { I2C_HID_QUIRK_BAD_INPUT_SIZE }, { I2C_VENDOR_ID_CIRQUE, I2C_PRODUCT_ID_CIRQUE_1063, I2C_HID_QUIRK_NO_SLEEP_ON_SUSPEND }, + { I2C_VENDOR_ID_CIRQUE, I2C_PRODUCT_ID_CIRQUE_D0C1, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, /* * Without additional power on command, at least some QTEC devices send garbage */ @@ -149,6 +153,8 @@ static const struct i2c_hid_quirks { I2C_HID_QUIRK_BOGUS_IRQ }, { I2C_VENDOR_ID_GOODIX, I2C_DEVICE_ID_GOODIX_0D42, I2C_HID_QUIRK_DELAY_WAKEUP_AFTER_RESUME }, + { I2C_VENDOR_ID_BLTP, I2C_PRODUCT_ID_BLTP7853, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { 0, 0 } }; @@ -574,9 +580,10 @@ static void i2c_hid_get_input(struct i2c_hid *ihid) if (ihid->hid->group != HID_GROUP_RMI) pm_wakeup_event(&ihid->client->dev, 0); - hid_input_report(ihid->hid, HID_INPUT_REPORT, - ihid->inbuf + sizeof(__le16), - ret_size - sizeof(__le16), 1); + hid_safe_input_report(ihid->hid, HID_INPUT_REPORT, + ihid->inbuf + sizeof(__le16), + ihid->bufsize - sizeof(__le16), + ret_size - sizeof(__le16), 1); } return; @@ -789,7 +796,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-dmi-quirks.c b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c index 210f17c3a0be..83b9bc4b02e6 100644 --- a/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c +++ b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c @@ -9,7 +9,6 @@ #include <linux/types.h> #include <linux/dmi.h> -#include <linux/mod_devicetable.h> #include <linux/hid.h> #include "i2c-hid.h" 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/i2c-hid/i2c-hid-of.c b/drivers/hid/i2c-hid/i2c-hid-of.c index 57379b77e977..59393d71ddb9 100644 --- a/drivers/hid/i2c-hid/i2c-hid-of.c +++ b/drivers/hid/i2c-hid/i2c-hid-of.c @@ -144,8 +144,8 @@ MODULE_DEVICE_TABLE(of, i2c_hid_of_match); #endif static const struct i2c_device_id i2c_hid_of_id_table[] = { - { "hid" }, - { "hid-over-i2c" }, + { .name = "hid" }, + { .name = "hid-over-i2c" }, { } }; MODULE_DEVICE_TABLE(i2c, i2c_hid_of_id_table); diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c index ed3405c05e73..e7196e429c8b 100644 --- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -110,7 +110,7 @@ void ish_event_tracer(struct ishtp_device *dev, const char *format, ...) vsnprintf(tmp_buf, sizeof(tmp_buf), format, args); va_end(args); - trace_ishtp_dump(tmp_buf); + trace_call__ishtp_dump(tmp_buf); } } @@ -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-ish-hid/ishtp/bus.h b/drivers/hid/intel-ish-hid/ishtp/bus.h index 53645ac89ee8..fa5e2797f1be 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.h +++ b/drivers/hid/intel-ish-hid/ishtp/bus.h @@ -8,7 +8,7 @@ #define _LINUX_ISHTP_CL_BUS_H #include <linux/device.h> -#include <linux/mod_devicetable.h> +#include <linux/device-id/ishtp.h> #include <linux/intel-ish-client-if.h> struct ishtp_cl; 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_dev_deinit(qcdev); + quicki2c_dma_deinit(qcdev); + pm_runtime_dont_use_autosuspend(qcdev->dev); pm_runtime_get_noresume(qcdev->dev); - quicki2c_dev_deinit(qcdev); - 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 <linux/hid-over-i2c.h> #include <linux/workqueue.h> +#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 f669235f1883..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; - thc_dma_unconfigure(qsdev->thc_hw); + if (pm_runtime_resume_and_get(qsdev->dev)) + return; - ret = thc_dma_configure(qsdev->thc_hw); - if (ret) { - dev_err(qsdev->dev, "Re-configure THC DMA failed, ret = %d\n", ret); - return ret; + 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); } - 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_dev_deinit(qsdev); + quickspi_dma_deinit(qsdev); + pm_runtime_dont_use_autosuspend(qsdev->dev); pm_runtime_get_noresume(qsdev->dev); - quickspi_dev_deinit(qsdev); - 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,20 +797,74 @@ 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 + * pending wake touch event and break wake-on-touch. + */ + if (device_may_wakeup(qsdev->dev)) { + thc_interrupt_config(qsdev->thc_hw); + + thc_interrupt_enable(qsdev->thc_hw, true); + + ret = thc_dma_configure(qsdev->thc_hw); + if (ret) + return ret; + + return thc_interrupt_quiesce(qsdev->thc_hw, false); + } + + /* + * Otherwise the touch IC may have lost power across suspend. On + * platforms that suspend through s2idle (for example the Surface Pro + * 10, whose firmware exposes s2idle as the only mem_sleep state) the + * IC loses power the same way it does across hibernation. A plain + * HIDSPI_ON is then not acknowledged and the descriptor read fails, so + * re-enumerate the device through the full reset flow already used by + * quickspi_restore(). + */ + thc_spi_input_output_address_config(qsdev->thc_hw, + qsdev->input_report_hdr_addr, + qsdev->input_report_bdy_addr, + qsdev->output_report_addr); + + ret = thc_spi_read_config(qsdev->thc_hw, qsdev->spi_freq_val, + qsdev->spi_read_io_mode, + qsdev->spi_read_opcode, + qsdev->spi_packet_size); + if (ret) + return ret; + + ret = thc_spi_write_config(qsdev->thc_hw, qsdev->spi_freq_val, + qsdev->spi_write_io_mode, + qsdev->spi_write_opcode, + qsdev->spi_packet_size, + qsdev->performance_limit); + if (ret) + return ret; + thc_interrupt_config(qsdev->thc_hw); thc_interrupt_enable(qsdev->thc_hw, true); - ret = thc_dma_configure(qsdev->thc_hw); + /* The touch IC may have lost power, reset it to recover */ + ret = reset_tic(qsdev); if (ret) return ret; - ret = thc_interrupt_quiesce(qsdev->thc_hw, false); + ret = thc_dma_configure(qsdev->thc_hw); if (ret) return ret; - if (!device_may_wakeup(qsdev->dev)) - return quickspi_set_power(qsdev, HIDSPI_ON); + thc_ltr_config(qsdev->thc_hw, + qsdev->active_ltr_val, + qsdev->low_power_ltr_val); + + thc_change_ltr_mode(qsdev->thc_hw, THC_LTR_MODE_ACTIVE); + + qsdev->state = QUICKSPI_ENABLED; return 0; } @@ -812,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; @@ -843,6 +913,8 @@ static int quickspi_thaw(struct device *device) if (ret) return ret; + WRITE_ONCE(qsdev->recovery_disabled, false); + return 0; } @@ -867,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 <linux/hid-over-spi.h> #include <linux/sizes.h> #include <linux/wait.h> +#include <linux/workqueue.h> #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 16f780bc879b..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; @@ -94,7 +97,7 @@ static int quickspi_get_device_descriptor(struct quickspi_device *qsdev) dev_err_once(qsdev->dev, "Read DEVICE_DESCRIPTOR failed, ret = %d\n", ret); dev_err_once(qsdev->dev, "DEVICE_DESCRIPTOR expected len = %u, actual read = %u\n", input_len, read_len); - return ret; + return ret ?: -EINVAL; } input_rep_type = ((struct input_report_body_header *)read_buf)->input_report_type; @@ -318,7 +321,7 @@ int reset_tic(struct quickspi_device *qsdev) dev_err_once(qsdev->dev, "Read RESET_RESPONSE body failed, ret = %d\n", ret); dev_err_once(qsdev->dev, "RESET_RESPONSE body expected len = %u, actual = %u\n", read_len, actual_read_len); - return ret; + return ret ?: -EINVAL; } input_rep_type = FIELD_GET(HIDSPI_IN_REP_BDY_HDR_REP_TYPE, reset_response); @@ -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; + } + + memcpy(buf, qsdev->report_buf, report_len); - return qsdev->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 @@ end: #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 @@ -50,6 +50,26 @@ enum thc_int_type { }; /** + * 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 * @mmio_addr: MMIO registers address @@ -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/uhid.c b/drivers/hid/uhid.c index 524b53a3c87b..37b60c3aaf66 100644 --- a/drivers/hid/uhid.c +++ b/drivers/hid/uhid.c @@ -595,8 +595,8 @@ static int uhid_dev_input(struct uhid_device *uhid, struct uhid_event *ev) if (!READ_ONCE(uhid->running)) return -EINVAL; - hid_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input.data, - min_t(size_t, ev->u.input.size, UHID_DATA_MAX), 0); + hid_safe_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input.data, UHID_DATA_MAX, + min_t(size_t, ev->u.input.size, UHID_DATA_MAX), 0); return 0; } @@ -606,8 +606,8 @@ static int uhid_dev_input2(struct uhid_device *uhid, struct uhid_event *ev) if (!READ_ONCE(uhid->running)) return -EINVAL; - hid_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input2.data, - min_t(size_t, ev->u.input2.size, UHID_DATA_MAX), 0); + hid_safe_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input2.data, UHID_DATA_MAX, + min_t(size_t, ev->u.input2.size, UHID_DATA_MAX), 0); return 0; } diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index fbbfc0f60829..96b0181cf819 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -27,6 +27,7 @@ #include <linux/wait.h> #include <linux/workqueue.h> #include <linux/string.h> +#include <linux/seq_buf.h> #include <linux/usb.h> @@ -283,9 +284,9 @@ static void hid_irq_in(struct urb *urb) break; usbhid_mark_busy(usbhid); if (!test_bit(HID_RESUME_RUNNING, &usbhid->iofl)) { - hid_input_report(urb->context, HID_INPUT_REPORT, - urb->transfer_buffer, - urb->actual_length, 1); + hid_safe_input_report(urb->context, HID_INPUT_REPORT, + urb->transfer_buffer, urb->transfer_buffer_length, + urb->actual_length, 1); /* * autosuspend refused while keys are pressed * because most keyboards don't wake up when @@ -482,9 +483,10 @@ static void hid_ctrl(struct urb *urb) switch (status) { case 0: /* success */ if (usbhid->ctrl[usbhid->ctrltail].dir == USB_DIR_IN) - hid_input_report(urb->context, + hid_safe_input_report(urb->context, usbhid->ctrl[usbhid->ctrltail].report->type, - urb->transfer_buffer, urb->actual_length, 0); + urb->transfer_buffer, urb->transfer_buffer_length, + urb->actual_length, 0); break; case -ESHUTDOWN: /* unplug */ unplug = 1; @@ -1366,6 +1368,7 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id * struct usb_endpoint_descriptor *ep; struct usbhid_device *usbhid; struct hid_device *hid; + struct seq_buf hid_name; size_t len; int ret; @@ -1397,7 +1400,7 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id * hid->vendor = le16_to_cpu(dev->descriptor.idVendor); hid->product = le16_to_cpu(dev->descriptor.idProduct); hid->version = le16_to_cpu(dev->descriptor.bcdDevice); - hid->name[0] = 0; + seq_buf_init(&hid_name, hid->name, sizeof(hid->name)); if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) hid->type = HID_TYPE_USBMOUSE; @@ -1405,22 +1408,23 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id * hid->type = HID_TYPE_USBNONE; if (dev->manufacturer) - strscpy(hid->name, dev->manufacturer, sizeof(hid->name)); + seq_buf_puts(&hid_name, dev->manufacturer); if (dev->product) { if (dev->manufacturer) - strlcat(hid->name, " ", sizeof(hid->name)); - strlcat(hid->name, dev->product, sizeof(hid->name)); + seq_buf_puts(&hid_name, " "); + seq_buf_puts(&hid_name, dev->product); } - if (!strlen(hid->name)) + if (!seq_buf_used(&hid_name)) snprintf(hid->name, sizeof(hid->name), "HID %04x:%04x", le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct)); usb_make_path(dev, hid->phys, sizeof(hid->phys)); - strlcat(hid->phys, "/input", sizeof(hid->phys)); - len = strlen(hid->phys); + len = strnlen(hid->phys, sizeof(hid->phys)); + strscpy(hid->phys + len, "/input", sizeof(hid->phys) - len); + len = strnlen(hid->phys, sizeof(hid->phys)); if (len < sizeof(hid->phys) - 1) snprintf(hid->phys + len, sizeof(hid->phys) - len, "%d", intf->altsetting[0].desc.bInterfaceNumber); diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index aee8a4443305..22951b7ecd17 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -11,6 +11,7 @@ #include "hid-pidff.h" #include <linux/hid.h> #include <linux/input.h> +#include <linux/math64.h> #include <linux/minmax.h> #include <linux/slab.h> #include <linux/stringify.h> @@ -326,8 +327,10 @@ static s32 pidff_clamp(s32 i, struct hid_field *field) */ static int pidff_rescale(int i, int max, struct hid_field *field) { - return i * (field->logical_maximum - field->logical_minimum) / max + - field->logical_minimum; + /* 64 bits needed for big values during rescale */ + s64 result = field->logical_maximum - field->logical_minimum; + + return div_s64(result * i, max) + field->logical_minimum; } /* @@ -519,7 +522,7 @@ static void pidff_set_effect_report(struct pidff_device *pidff, pidff->set_effect[PID_EFFECT_BLOCK_INDEX].value[0] = pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; pidff->set_effect_type->value[0] = - pidff->create_new_effect_type->value[0]; + pidff_get_effect_type_id(pidff, effect); pidff_set_duration(&pidff->set_effect[PID_DURATION], effect->replay.length); @@ -1536,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/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c index 6b33e6ad0846..83d4df0d7a45 100644 --- a/drivers/hid/usbhid/usbkbd.c +++ b/drivers/hid/usbhid/usbkbd.c @@ -20,6 +20,7 @@ #include <linux/init.h> #include <linux/usb/input.h> #include <linux/hid.h> +#include <linux/seq_buf.h> /* * Version Information @@ -266,8 +267,10 @@ static int usb_kbd_probe(struct usb_interface *iface, struct usb_endpoint_descriptor *endpoint; struct usb_kbd *kbd; struct input_dev *input_dev; + struct seq_buf kbd_name; int i, pipe, maxp; int error = -ENOMEM; + size_t len; interface = iface->cur_altsetting; @@ -292,24 +295,26 @@ static int usb_kbd_probe(struct usb_interface *iface, kbd->usbdev = dev; kbd->dev = input_dev; spin_lock_init(&kbd->leds_lock); + seq_buf_init(&kbd_name, kbd->name, sizeof(kbd->name)); if (dev->manufacturer) - strscpy(kbd->name, dev->manufacturer, sizeof(kbd->name)); + seq_buf_puts(&kbd_name, dev->manufacturer); if (dev->product) { if (dev->manufacturer) - strlcat(kbd->name, " ", sizeof(kbd->name)); - strlcat(kbd->name, dev->product, sizeof(kbd->name)); + seq_buf_puts(&kbd_name, " "); + seq_buf_puts(&kbd_name, dev->product); } - if (!strlen(kbd->name)) + if (!seq_buf_used(&kbd_name)) snprintf(kbd->name, sizeof(kbd->name), "USB HIDBP Keyboard %04x:%04x", le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct)); usb_make_path(dev, kbd->phys, sizeof(kbd->phys)); - strlcat(kbd->phys, "/input0", sizeof(kbd->phys)); + len = strnlen(kbd->phys, sizeof(kbd->phys)); + strscpy(kbd->phys + len, "/input0", sizeof(kbd->phys) - len); input_dev->name = kbd->name; input_dev->phys = kbd->phys; diff --git a/drivers/hid/usbhid/usbmouse.c b/drivers/hid/usbhid/usbmouse.c index 7cc4f9558e5f..b3b2abeee614 100644 --- a/drivers/hid/usbhid/usbmouse.c +++ b/drivers/hid/usbhid/usbmouse.c @@ -18,6 +18,7 @@ #include <linux/init.h> #include <linux/usb/input.h> #include <linux/hid.h> +#include <linux/seq_buf.h> /* for apple IDs */ #ifdef CONFIG_USB_HID_MODULE @@ -110,8 +111,10 @@ static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_i struct usb_endpoint_descriptor *endpoint; struct usb_mouse *mouse; struct input_dev *input_dev; + struct seq_buf mouse_name; int pipe, maxp; int error = -ENOMEM; + size_t len; interface = intf->cur_altsetting; @@ -140,24 +143,26 @@ static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_i mouse->usbdev = dev; mouse->dev = input_dev; + seq_buf_init(&mouse_name, mouse->name, sizeof(mouse->name)); if (dev->manufacturer) - strscpy(mouse->name, dev->manufacturer, sizeof(mouse->name)); + seq_buf_puts(&mouse_name, dev->manufacturer); if (dev->product) { if (dev->manufacturer) - strlcat(mouse->name, " ", sizeof(mouse->name)); - strlcat(mouse->name, dev->product, sizeof(mouse->name)); + seq_buf_puts(&mouse_name, " "); + seq_buf_puts(&mouse_name, dev->product); } - if (!strlen(mouse->name)) + if (!seq_buf_used(&mouse_name)) snprintf(mouse->name, sizeof(mouse->name), "USB HIDBP Mouse %04x:%04x", le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct)); usb_make_path(dev, mouse->phys, sizeof(mouse->phys)); - strlcat(mouse->phys, "/input0", sizeof(mouse->phys)); + len = strnlen(mouse->phys, sizeof(mouse->phys)); + strscpy(mouse->phys + len, "/input0", sizeof(mouse->phys) - len); input_dev->name = mouse->name; input_dev->phys = mouse->phys; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 0d1c6d90fe21..40770affdbde 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -54,7 +54,7 @@ static void wacom_wac_queue_insert(struct hid_device *hdev, { bool warned = false; - while (kfifo_avail(fifo) < size) { + while (kfifo_avail(fifo) < size && !kfifo_is_empty(fifo)) { if (!warned) hid_warn(hdev, "%s: kfifo has filled, starting to drop events\n", __func__); warned = true; @@ -62,7 +62,9 @@ static void wacom_wac_queue_insert(struct hid_device *hdev, kfifo_skip(fifo); } - kfifo_in(fifo, raw_data, size); + if (!kfifo_in(fifo, raw_data, size)) + hid_warn_ratelimited(hdev, "%s: report is too large (%d)\n", + __func__, size); } static void wacom_wac_queue_flush(struct hid_device *hdev, @@ -70,11 +72,10 @@ static void wacom_wac_queue_flush(struct hid_device *hdev, { while (!kfifo_is_empty(fifo)) { int size = kfifo_peek_len(fifo); - u8 *buf; + u8 *buf __free(kfree) = kzalloc(size, GFP_ATOMIC); unsigned int count; int err; - buf = kzalloc(size, GFP_KERNEL); if (!buf) { kfifo_skip(fifo); continue; @@ -87,16 +88,13 @@ static void wacom_wac_queue_flush(struct hid_device *hdev, // to flush seems reasonable enough, however. hid_warn(hdev, "%s: removed fifo entry with unexpected size\n", __func__); - kfree(buf); continue; } - err = hid_report_raw_event(hdev, HID_INPUT_REPORT, buf, size, false); + err = hid_report_raw_event(hdev, HID_INPUT_REPORT, buf, size, size, false); if (err) { hid_warn(hdev, "%s: unable to flush event due to error %d\n", __func__, err); } - - kfree(buf); } } @@ -115,8 +113,9 @@ static int wacom_wac_pen_serial_enforce(struct hid_device *hdev, /* Queue events which have invalid tool type or serial number */ for (i = 0; i < report->maxfield; i++) { - for (j = 0; j < report->field[i]->maxusage; j++) { - struct hid_field *field = report->field[i]; + struct hid_field *field = report->field[i]; + + for (j = 0; j < field->report_count; j++) { struct hid_usage *usage = &field->usage[j]; unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid); unsigned int offset; @@ -334,7 +333,7 @@ static void wacom_feature_mapping(struct hid_device *hdev, data, n, WAC_CMD_RETRIES); if (ret == n && features->type == HID_GENERIC) { ret = hid_report_raw_event(hdev, - HID_FEATURE_REPORT, data, n, 0); + HID_FEATURE_REPORT, data, n, n, 0); } else if (ret == 2 && features->type != HID_GENERIC) { features->touch_max = data[1]; } else { @@ -356,6 +355,7 @@ static void wacom_feature_mapping(struct hid_device *hdev, hid_data->inputmode = field->report->id; hid_data->inputmode_index = usage->usage_index; + hid_data->inputmode_field_index = field->index; break; case HID_UP_DIGITIZER: @@ -395,7 +395,7 @@ static void wacom_feature_mapping(struct hid_device *hdev, data, n, WAC_CMD_RETRIES); if (ret == n) { ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, - data, n, 0); + data, n, n, 0); } else { hid_warn(hdev, "%s: could not retrieve sensor offsets\n", __func__); @@ -571,9 +571,14 @@ static int wacom_hid_set_device_mode(struct hid_device *hdev) re = &(hdev->report_enum[HID_FEATURE_REPORT]); r = re->report_id_hash[hid_data->inputmode]; - if (r) { - r->field[0]->value[hid_data->inputmode_index] = 2; - hid_hw_request(hdev, r, HID_REQ_SET_REPORT); + if (r && hid_data->inputmode_field_index >= 0 && + hid_data->inputmode_field_index < r->maxfield) { + struct hid_field *field = r->field[hid_data->inputmode_field_index]; + + if (field && hid_data->inputmode_index < field->report_count) { + field->value[hid_data->inputmode_index] = 2; + hid_hw_request(hdev, r, HID_REQ_SET_REPORT); + } } return 0; } @@ -2456,16 +2461,16 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) error = wacom_register_inputs(wacom); if (error) - goto fail; + goto fail_hw_stop; if (wacom->wacom_wac.features.device_type & WACOM_DEVICETYPE_PAD) { error = wacom_initialize_leds(wacom); if (error) - goto fail; + goto fail_hw_stop; error = wacom_initialize_remotes(wacom); if (error) - goto fail; + goto fail_hw_stop; } if (!wireless) { @@ -2479,14 +2484,14 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) cancel_delayed_work_sync(&wacom->init_work); _wacom_query_tablet_data(wacom); error = -ENODEV; - goto fail_quirks; + goto fail_hw_stop; } if (features->device_type & WACOM_DEVICETYPE_WL_MONITOR) { error = hid_hw_open(hdev); if (error) { hid_err(hdev, "hw open failed\n"); - goto fail_quirks; + goto fail_hw_stop; } } @@ -2495,7 +2500,7 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) return 0; -fail_quirks: +fail_hw_stop: hid_hw_stop(hdev); fail: wacom_release_resources(wacom); @@ -2846,6 +2851,7 @@ static int wacom_probe(struct hid_device *hdev, return -ENODEV; wacom_wac->hid_data.inputmode = -1; + wacom_wac->hid_data.inputmode_field_index = -1; wacom_wac->mode_report = -1; if (hid_is_usb(hdev)) { diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index da1f0ea85625..8feb8027be95 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; } @@ -1548,6 +1550,19 @@ static int wacom_intuos_pro2_bt_irq(struct wacom_wac *wacom, size_t len) return 0; } + if (wacom->features.type == INTUOSP2_BT || + wacom->features.type == INTUOSP2S_BT) { + if (len < 286) { + dev_warn(wacom->pen_input->dev.parent, + "Pro2 BT report too short: %zu bytes\n", len); + return 0; + } + } else if (len < 46) { + dev_warn(wacom->pen_input->dev.parent, + "Pro2 BT report too short: %zu bytes\n", len); + return 0; + } + wacom_intuos_pro2_bt_pen(wacom); if (wacom->features.type == INTUOSP2_BT || wacom->features.type == INTUOSP2S_BT) { diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index d4f7d8ca1e7e..126bec6e5c0c 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -295,6 +295,7 @@ struct wacom_shared { struct hid_data { __s16 inputmode; /* InputMode HID feature, -1 if non-existent */ __s16 inputmode_index; /* InputMode HID feature index in the report */ + __s16 inputmode_field_index; /* InputMode HID feature field index in the report */ bool sense_state; bool inrange_state; bool eraser; |
