diff options
Diffstat (limited to 'drivers/hid')
163 files changed, 19088 insertions, 2787 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index c1d9f7c6a5f2..c43e824428f0 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -107,7 +107,6 @@ menu "Special HID drivers" config HID_A4TECH tristate "A4TECH mice" - default !EXPERT help Support for some A4TECH mice with two scroll wheels. @@ -140,7 +139,6 @@ config HID_APPLE tristate "Apple {i,Power,Mac}Books" depends on LEDS_CLASS depends on NEW_LEDS - default !EXPERT help Support for some Apple devices which less or more break HID specification. @@ -209,7 +207,6 @@ config HID_AUREAL config HID_BELKIN tristate "Belkin Flip KVM and Wireless keyboard" - default !EXPERT help Support for Belkin Flip KVM and Wireless keyboard. @@ -237,14 +234,12 @@ config HID_BIGBEN_FF config HID_CHERRY tristate "Cherry Cymotion keyboard" - default !EXPERT help Support for Cherry Cymotion keyboard. config HID_CHICONY tristate "Chicony devices" depends on USB_HID - default !EXPERT help Support for Chicony Tactical pad and special keys on Chicony keyboards. @@ -322,7 +317,6 @@ config HID_CREATIVE_SB0540 config HID_CYPRESS tristate "Cypress mouse and barcode readers" - default !EXPERT help Support for cypress mouse and barcode readers. @@ -388,7 +382,6 @@ config HID_EVISION config HID_EZKEY tristate "Ezkey BTC 8193 keyboard" - default !EXPERT help Support for Ezkey BTC 8193 keyboard. @@ -440,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 @@ -492,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 @@ -564,7 +583,6 @@ config HID_ICADE config HID_ITE tristate "ITE devices" - default !EXPERT help Support for ITE devices not fully compliant with HID standard. @@ -585,7 +603,6 @@ config HID_TWINHAN config HID_KENSINGTON tristate "Kensington Slimblade Trackball" - default !EXPERT help Support for Kensington Slimblade Trackball. @@ -610,8 +627,7 @@ config HID_LED config HID_LENOVO tristate "Lenovo / Thinkpad devices" - select NEW_LEDS - select LEDS_CLASS + depends on LEDS_CLASS help Support for IBM/Lenovo devices that are not fully compliant with HID standard. @@ -623,6 +639,28 @@ config HID_LENOVO - ThinkPad Compact Bluetooth Keyboard with TrackPoint (supports Fn keys) - ThinkPad Compact USB Keyboard with TrackPoint (supports Fn keys) +config HID_LENOVO_GO + tristate "HID Driver for Lenovo Legion Go Series Controllers" + depends on USB_HID + depends on LEDS_CLASS_MULTICOLOR + help + Support for Lenovo Legion Go devices with detachable controllers. + + Say Y here to include configuration interface support for the Lenovo Legion Go + and Legion Go 2 Handheld Console Controllers. Say M here to compile this + driver as a module. The module will be called hid-lenovo-go. + +config HID_LENOVO_GO_S + tristate "HID Driver for Lenovo Legion Go S Controller" + depends on USB_HID + depends on LEDS_CLASS_MULTICOLOR + help + Support for Lenovo Legion Go S Handheld Console Controller. + + Say Y here to include configuration interface support for the Lenovo Legion Go + S. Say M here to compile this driver as a module. The module will be called + hid-lenovo-go-s. + config HID_LETSKETCH tristate "Letsketch WP9620N tablets" depends on USB_HID @@ -642,7 +680,6 @@ config HID_LOGITECH depends on USB_HID depends on LEDS_CLASS depends on LEDS_CLASS_MULTICOLOR - default !EXPERT help Support for Logitech devices that are not fully compliant with HID standard. @@ -755,22 +792,28 @@ 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" - default !EXPERT help Support for Redragon keyboards that need fix-ups to work properly. config HID_MICROSOFT tristate "Microsoft non-fully HID-compliant devices" - default !EXPERT select INPUT_FF_MEMLESS help Support for Microsoft devices that are not fully compliant with HID standard. config HID_MONTEREY tristate "Monterey Genius KB29E keyboard" - default !EXPERT help Support for Monterey Genius KB29E. @@ -888,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 @@ -1018,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. @@ -1049,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 @@ -1100,13 +1166,15 @@ config HID_SONY help Support for - * Sony PS3 6-axis controllers + * Sixaxis controllers for PS3 * Buzz controllers - * Sony PS3 Blue-ray Disk Remote Control (Bluetooth) - * Logitech Harmony adapter for Sony Playstation 3 (Bluetooth) - * Guitar Hero Live PS3, Wii U and PS4 guitar dongles - * Guitar Hero PS3 and PC guitar dongles + * Blu-ray Disc Remote Control for PS3 + * Logitech Harmony adapter for PS3 + * Guitar Hero Live PS3, Wii U and PS4 guitars + * Guitar Hero PS3 and PC guitars + * Rock Band 1, 2 and 3 PS3 and Wii instruments * Rock Band 4 PS4 and PS5 guitars + * DJ Hero Turntable for PS3 config SONY_FF bool "Sony PS2/3/4 accessories force feedback support" @@ -1142,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" @@ -1183,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 @@ -1435,6 +1514,13 @@ config HID_KUNIT_TEST If in doubt, say "N". +config HID_HUAWEI + tristate "Huawei HID devices support" + depends on USB_HID + help + Support for huawei cd30 keyboard or other hid devices + that need fix-ups to work properly. + endmenu source "drivers/hid/bpf/Kconfig" diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index e01838239ae6..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 @@ -76,6 +77,8 @@ obj-$(CONFIG_HID_KYE) += hid-kye.o obj-$(CONFIG_HID_KYSONA) += hid-kysona.o obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o obj-$(CONFIG_HID_LENOVO) += hid-lenovo.o +obj-$(CONFIG_HID_LENOVO_GO) += hid-lenovo-go.o +obj-$(CONFIG_HID_LENOVO_GO_S) += hid-lenovo-go-s.o obj-$(CONFIG_HID_LETSKETCH) += hid-letsketch.o obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o obj-$(CONFIG_HID_LOGITECH) += hid-lg-g15.o @@ -90,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 @@ -113,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 @@ -130,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 @@ -152,6 +158,7 @@ obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o obj-$(CONFIG_HID_ZYDACRON) += hid-zydacron.o obj-$(CONFIG_HID_VIEWSONIC) += hid-viewsonic.o obj-$(CONFIG_HID_VRC2) += hid-vrc2.o +obj-$(CONFIG_HID_HUAWEI) += hid-huawei.o wacom-objs := wacom_wac.o wacom_sys.o obj-$(CONFIG_HID_WACOM) += wacom.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 7017bfa59093..ae6add0b9ce3 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_client.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_client.c @@ -47,7 +47,7 @@ int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type) guard(mutex)(&mp2->lock); for (i = 0; i < cli_data->num_hid_devices; i++) { if (cli_data->hid_sensor_hubs[i] == hid) { - struct request_list *new = kzalloc(sizeof(*new), GFP_KERNEL); + struct request_list *new = kzalloc_obj(*new); if (!new) return -ENOMEM; @@ -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 81f3024b7b1b..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) @@ -135,7 +136,7 @@ int amdtp_hid_probe(u32 cur_hid_dev, struct amdtp_cl_data *cli_data) if (IS_ERR(hid)) return PTR_ERR(hid); - hid_data = kzalloc(sizeof(*hid_data), GFP_KERNEL); + hid_data = kzalloc_obj(*hid_data); if (!hid_data) { rc = -ENOMEM; goto err_hid_data; @@ -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 1d9f955573aa..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) @@ -413,12 +455,15 @@ static void sfh_init_work(struct work_struct *work) rc = amd_sfh_hid_client_init(mp2); if (rc) { amd_sfh_clear_intr(mp2); - dev_err(&pdev->dev, "amd_sfh_hid_client_init failed err %d\n", rc); + if (rc != -EOPNOTSUPP) + dev_err(&pdev->dev, "amd_sfh_hid_client_init failed err %d\n", rc); 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) @@ -426,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); } @@ -446,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; @@ -470,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; @@ -539,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 33af17fdc729..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; @@ -320,7 +325,7 @@ hid_bpf_allocate_context(unsigned int hid_id) if (IS_ERR(hdev)) return NULL; - ctx_kern = kzalloc(sizeof(*ctx_kern), GFP_KERNEL); + ctx_kern = kzalloc_obj(*ctx_kern); if (!ctx_kern) { hid_put_device(hdev); return NULL; @@ -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/Generic__touchpad.bpf.c b/drivers/hid/bpf/progs/Generic__touchpad.bpf.c new file mode 100644 index 000000000000..b9f2cac91724 --- /dev/null +++ b/drivers/hid/bpf/progs/Generic__touchpad.bpf.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2025 Benjamin Tissoires + */ + +#include "vmlinux.h" +#include "hid_bpf.h" +#include "hid_bpf_helpers.h" +#include "hid_report_helpers.h" +#include "hid_usages.h" +#include <bpf/bpf_tracing.h> + +HID_BPF_CONFIG( + HID_DEVICE(BUS_ANY, HID_GROUP_MULTITOUCH_WIN_8, HID_VID_ANY, HID_PID_ANY), +); + +EXPORT_UDEV_PROP(HID_DIGITIZER_PAD_TYPE, 32); + +__u8 hw_req_buf[1024]; + +/* to be filled by udev-hid-bpf */ +struct hid_rdesc_descriptor HID_REPORT_DESCRIPTOR; + +SEC("syscall") +int probe(struct hid_bpf_probe_args *ctx) +{ + struct hid_rdesc_report *pad_type_feature = NULL; + struct hid_rdesc_field *pad_type = NULL; + struct hid_rdesc_report *feature; + struct hid_bpf_ctx *hid_ctx; + char *pad_type_str = ""; + int ret; + + hid_bpf_for_each_feature_report(&HID_REPORT_DESCRIPTOR, feature) { + struct hid_rdesc_field *field; + + hid_bpf_for_each_field(feature, field) { + if (field->usage_page == HidUsagePage_Digitizers && + field->usage_id == HidUsage_Dig_PadType) { + pad_type = field; + pad_type_feature = feature; + break; + } + } + if (pad_type) + break; + } + + if (!pad_type || !pad_type_feature) { + ctx->retval = -EINVAL; + return 0; + } + + hid_ctx = hid_bpf_allocate_context(ctx->hid); + + if (!hid_ctx) + return -1; /* EPERM check */ + + hw_req_buf[0] = pad_type_feature->report_id; + + ret = hid_bpf_hw_request(hid_ctx, hw_req_buf, sizeof(hw_req_buf), + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + hid_bpf_release_context(hid_ctx); + + if (ret < 0) { + ctx->retval = ret; + return 0; + } + + ctx->retval = 0; + + switch (EXTRACT_BITS(hw_req_buf, pad_type)) { + case 0: + pad_type_str = "Clickpad"; + break; + case 1: + pad_type_str = "Pressurepad"; + break; + case 2: + pad_type_str = "Discrete"; + break; + default: + pad_type_str = "Unknown"; + } + + UDEV_PROP_SPRINTF(HID_DIGITIZER_PAD_TYPE, "%s", pad_type_str); + + return 0; +} + +char _license[] SEC("license") = "GPL"; 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/bpf/progs/Huion__KeydialK20-Bluetooth.bpf.c b/drivers/hid/bpf/progs/Huion__KeydialK20-Bluetooth.bpf.c new file mode 100644 index 000000000000..d0769e990039 --- /dev/null +++ b/drivers/hid/bpf/progs/Huion__KeydialK20-Bluetooth.bpf.c @@ -0,0 +1,492 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2024 Red Hat, Inc + */ + +#include "vmlinux.h" +#include "hid_bpf.h" +#include "hid_bpf_helpers.h" +#include "hid_report_helpers.h" +#include <bpf/bpf_tracing.h> + +#define VID_HUION 0x256C +#define PID_KEYDIAL_K20_BLUETOOTH 0x8251 + +HID_BPF_CONFIG( + HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_HUION, PID_KEYDIAL_K20_BLUETOOTH), +); + +/* This is the same device as in 0010-Huion__KeydialK20 but connected via Bluetooth. + * It does not need (to support?) switching to a vendor mode so we just modify the + * existing mode. + * + * By default it exports two hidraw nodes, only the second one sends events. + * + * This is the first hidraw node which we disable: + * + * # Keydial mini-050 + * # Report descriptor length: 114 bytes + * # Bytes // Field Name Offset + * # ---------------------------------------------------------------------------------- + * # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 0 + * # 🬠0x09, 0x0e, // Usage (System Multi-Axis Controller) 2 + * # 0xa1, 0x01, // Collection (Application) 4 + * # â”… 0x85, 0x03, // Report ID (3) 6 + * # 🮥 0x05, 0x0d, // Usage Page (Digitizers) 8 + * # 0x75, 0x08, // Report Size (8) 10 + * # 0x95, 0x01, // Report Count (1) 12 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 14 + * # 🬠0x09, 0x21, // Usage (Puck) 16 + * # 0xa1, 0x02, // Collection (Logical) 18 + * # 0x15, 0x00, // Logical Minimum (0) 20 + * # 0x25, 0x01, // Logical Maximum (1) 22 + * # 0x75, 0x01, // Report Size (1) 24 + * # 0x95, 0x01, // Report Count (1) 26 + * # 0xa1, 0x00, // Collection (Physical) 28 + * # 🮥 0x05, 0x09, // Usage Page (Button) 30 + * # 🬠0x09, 0x01, // Usage (Button 1) 32 + * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 34 + * # 🮥 0x05, 0x0d, // Usage Page (Digitizers) 36 + * # 🬠0x09, 0x33, // Usage (Touch) 38 + * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 40 + * # 0x95, 0x06, // Report Count (6) 42 + * # ┇ 0x81, 0x03, // Input (Cnst,Var,Abs) 44 + * # 0xa1, 0x02, // Collection (Logical) 46 + * # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 48 + * # 🬠0x09, 0x37, // Usage (Dial) 50 + * # 0x16, 0x00, 0x80, // Logical Minimum (32768) 52 + * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 55 + * # 0x75, 0x10, // Report Size (16) 58 + * # 0x95, 0x01, // Report Count (1) 60 + * # ┇ 0x81, 0x06, // Input (Data,Var,Rel) 62 + * # 0x35, 0x00, // Physical Minimum (0) 64 + * # 0x46, 0x10, 0x0e, // Physical Maximum (3600) 66 + * # 0x15, 0x00, // Logical Minimum (0) 69 + * # 0x26, 0x10, 0x0e, // Logical Maximum (3600) 71 + * # 🬠0x09, 0x48, // Usage (Resolution Multiplier) 74 + * # â•‘ 0xb1, 0x02, // Feature (Data,Var,Abs) 76 + * # 0x45, 0x00, // Physical Maximum (0) 78 + * # 0xc0, // End Collection 80 + * # 0x75, 0x08, // Report Size (8) 81 + * # 0x95, 0x01, // Report Count (1) 83 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 85 + * # 0x75, 0x08, // Report Size (8) 87 + * # 0x95, 0x01, // Report Count (1) 89 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 91 + * # 0x75, 0x08, // Report Size (8) 93 + * # 0x95, 0x01, // Report Count (1) 95 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 97 + * # 0x75, 0x08, // Report Size (8) 99 + * # 0x95, 0x01, // Report Count (1) 101 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 103 + * # 0x75, 0x08, // Report Size (8) 105 + * # 0x95, 0x01, // Report Count (1) 107 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 109 + * # 0xc0, // End Collection 111 + * # 0xc0, // End Collection 112 + * # 0xc0, // End Collection 113 + * R: 114 05 01 09 0e a1 01 85 03 05 0d 75 08 95 01 81 01 09 21 a1 02 15 00 25 01 75 01 95 01 a1 00 05 09 09 01 81 02 05 0d 09 33 81 02 95 06 81 03 a1 02 05 01 09 37 16 00 80 26 ff 7f 75 10 95 01 81 06 35 00 46 10 0e 15 00 26 10 0e 09 48 b1 02 45 00 c0 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 c0 c0 c0 + * N: Keydial mini-050 + * I: 5 256c 8251 + * + * The second hidraw node is what sends events: + * + * # Keydial mini-050 + * # Report descriptor length: 160 bytes + * # Bytes // Field Name Offset + * # ---------------------------------------------------------------------------------- + * # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 0 + * # 🬠0x09, 0x06, // Usage (Keyboard) 2 + * # 0xa1, 0x01, // Collection (Application) 4 + * # â”… 0x85, 0x01, // Report ID (1) 6 + * # 🮥 0x05, 0x07, // Usage Page (Keyboard/Keypad) 8 + * # 🬠0x19, 0xe0, // Usage Minimum (224) 10 + * # 🬠0x29, 0xe7, // Usage Maximum (231) 12 + * # 0x15, 0x00, // Logical Minimum (0) 14 + * # 0x25, 0x01, // Logical Maximum (1) 16 + * # 0x75, 0x01, // Report Size (1) 18 + * # 0x95, 0x08, // Report Count (8) 20 + * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 22 + * # 0x95, 0x01, // Report Count (1) 24 + * # 0x75, 0x08, // Report Size (8) 26 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 28 + * # 0x95, 0x05, // Report Count (5) 30 + * # 0x75, 0x01, // Report Size (1) 32 + * # 🮥 0x05, 0x08, // Usage Page (LED) 34 + * # 🬠0x19, 0x01, // Usage Minimum (1) 36 + * # 🬠0x29, 0x05, // Usage Maximum (5) 38 + * # ┊ 0x91, 0x02, // Output (Data,Var,Abs) 40 + * # 0x95, 0x01, // Report Count (1) 42 + * # 0x75, 0x03, // Report Size (3) 44 + * # ┊ 0x91, 0x01, // Output (Cnst,Arr,Abs) 46 + * # 0x95, 0x06, // Report Count (6) 48 + * # 0x75, 0x08, // Report Size (8) 50 + * # 0x15, 0x00, // Logical Minimum (0) 52 + * # 0x25, 0xf1, // Logical Maximum (241) 54 + * # 🮥 0x05, 0x07, // Usage Page (Keyboard/Keypad) 56 + * # 🬠0x19, 0x00, // Usage Minimum (0) 58 + * # 🬠0x29, 0xf1, // Usage Maximum (241) 60 + * # ┇ 0x81, 0x00, // Input (Data,Arr,Abs) 62 + * # 0xc0, // End Collection 64 + * # 🮥 0x05, 0x0c, // Usage Page (Consumer) 65 + * # 🬠0x09, 0x01, // Usage (Consumer Control) 67 + * # 0xa1, 0x01, // Collection (Application) 69 + * # â”… 0x85, 0x02, // Report ID (2) 71 + * # 🮥 0x05, 0x0c, // Usage Page (Consumer) 73 + * # 🬠0x19, 0x00, // Usage Minimum (0) 75 + * # 🬠0x2a, 0x80, 0x03, // Usage Maximum (896) 77 + * # 0x15, 0x00, // Logical Minimum (0) 80 + * # 0x26, 0x80, 0x03, // Logical Maximum (896) 82 + * # 0x75, 0x10, // Report Size (16) 85 + * # 0x95, 0x01, // Report Count (1) 87 + * # ┇ 0x81, 0x00, // Input (Data,Arr,Abs) 89 + * # 0xc0, // End Collection 91 + * # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 92 + * # 🬠0x09, 0x02, // Usage (Mouse) 94 + * # 0xa1, 0x01, // Collection (Application) 96 + * # 🬠0x09, 0x01, // Usage (Pointer) 98 + * # â”… 0x85, 0x05, // Report ID (5) 100 + * # 0xa1, 0x00, // Collection (Physical) 102 + * # 🮥 0x05, 0x09, // Usage Page (Button) 104 + * # 🬠0x19, 0x01, // Usage Minimum (1) 106 + * # 🬠0x29, 0x05, // Usage Maximum (5) 108 + * # 0x15, 0x00, // Logical Minimum (0) 110 + * # 0x25, 0x01, // Logical Maximum (1) 112 + * # 0x95, 0x05, // Report Count (5) 114 + * # 0x75, 0x01, // Report Size (1) 116 + * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 118 + * # 0x95, 0x01, // Report Count (1) 120 + * # 0x75, 0x03, // Report Size (3) 122 + * # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 124 + * # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 126 + * # 🬠0x09, 0x30, // Usage (X) 128 + * # 🬠0x09, 0x31, // Usage (Y) 130 + * # 0x16, 0x01, 0x80, // Logical Minimum (32769) 132 + * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 135 + * # 0x75, 0x10, // Report Size (16) 138 + * # 0x95, 0x02, // Report Count (2) 140 + * # ┇ 0x81, 0x06, // Input (Data,Var,Rel) 142 + * # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 144 + * # 🬠0x09, 0x38, // Usage (Wheel) 146 + * # 0x15, 0x81, // Logical Minimum (129) 148 + * # 0x25, 0x7f, // Logical Maximum (127) 150 + * # 0x95, 0x01, // Report Count (1) 152 + * # 0x75, 0x08, // Report Size (8) 154 + * # ┇ 0x81, 0x06, // Input (Data,Var,Rel) 156 + * # 0xc0, // End Collection 158 + * # 0xc0, // End Collection 159 + * R: 160 05 01 09 06 a1 01 85 01 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 95 01 75 08 81 01 95 05 75 01 05 08 19 01 29 05 91 02 95 01 75 03 91 01 95 06 75 08 15 00 25 f1 05 07 19 00 29 f1 81 00 c0 05 0c 09 01 a1 01 85 02 05 0c 19 00 2a 80 03 15 00 26 80 03 75 10 95 01 81 00 c0 05 01 09 02 a1 01 09 01 85 05 a1 00 05 09 19 01 29 05 15 00 25 01 95 05 75 01 81 02 95 01 75 03 81 01 05 01 09 30 09 31 16 01 80 26 ff 7f 75 10 95 02 81 06 05 01 09 38 15 81 25 7f 95 01 75 08 81 06 c0 c0 + * N: Keydial mini-050 + * I: 5 256c 8251 + * # Report descriptor: + * # ------- Input Report ------- + * # â–‘ Report ID: 1 + * # â–‘ | Report size: 72 bits + * # â–‘ Bit: 8 Usage: 0007/00e0: Keyboard/Keypad / Keyboard LeftControl Logical Range: 0..=1 + * # â–‘ Bit: 9 Usage: 0007/00e1: Keyboard/Keypad / Keyboard LeftShift Logical Range: 0..=1 + * # â–‘ Bit: 10 Usage: 0007/00e2: Keyboard/Keypad / Keyboard LeftAlt Logical Range: 0..=1 + * # â–‘ Bit: 11 Usage: 0007/00e3: Keyboard/Keypad / Keyboard Left GUI Logical Range: 0..=1 + * # â–‘ Bit: 12 Usage: 0007/00e4: Keyboard/Keypad / Keyboard RightControl Logical Range: 0..=1 + * # â–‘ Bit: 13 Usage: 0007/00e5: Keyboard/Keypad / Keyboard RightShift Logical Range: 0..=1 + * # â–‘ Bit: 14 Usage: 0007/00e6: Keyboard/Keypad / Keyboard RightAlt Logical Range: 0..=1 + * # â–‘ Bit: 15 Usage: 0007/00e7: Keyboard/Keypad / Keyboard Right GUI Logical Range: 0..=1 + * # â–‘ Bits: 16..=23 ######### Padding + * # â–‘ Bits: 24..=71 Usages: Logical Range: 0..=241 + * # â–‘ 0007/0000: <unknown> + * # â–‘ 0007/0001: Keyboard/Keypad / ErrorRollOver + * # â–‘ 0007/0002: Keyboard/Keypad / POSTFail + * # â–‘ 0007/0003: Keyboard/Keypad / ErrorUndefined + * # â–‘ 0007/0004: Keyboard/Keypad / Keyboard A + * # â–‘ ... use --full to see all usages + * # ------- Input Report ------- + * # â–’ Report ID: 2 + * # â–’ | Report size: 24 bits + * # â–’ Bits: 8..=23 Usages: Logical Range: 0..=896 + * # â–’ 000c/0000: <unknown> + * # â–’ 000c/0001: Consumer / Consumer Control + * # â–’ 000c/0002: Consumer / Numeric Key Pad + * # â–’ 000c/0003: Consumer / Programmable Buttons + * # â–’ 000c/0004: Consumer / Microphone + * # â–’ ... use --full to see all usages + * # ------- Input Report ------- + * # â–ž Report ID: 5 + * # â–ž | Report size: 56 bits + * # â–ž Bit: 8 Usage: 0009/0001: Button / Button 1 Logical Range: 0..=1 + * # â–ž Bit: 9 Usage: 0009/0002: Button / Button 2 Logical Range: 0..=1 + * # â–ž Bit: 10 Usage: 0009/0003: Button / Button 3 Logical Range: 0..=1 + * # â–ž Bit: 11 Usage: 0009/0004: Button / Button 4 Logical Range: 0..=1 + * # â–ž Bit: 12 Usage: 0009/0005: Button / Button 5 Logical Range: 0..=1 + * # â–ž Bits: 13..=15 ######### Padding + * # â–ž Bits: 16..=31 Usage: 0001/0030: Generic Desktop / X Logical Range: 32769..=32767 + * # â–ž Bits: 32..=47 Usage: 0001/0031: Generic Desktop / Y Logical Range: 32769..=32767 + * # â–ž Bits: 48..=55 Usage: 0001/0038: Generic Desktop / Wheel Logical Range: 129..=127 + * # ------- Output Report ------- + * # â–‘ Report ID: 1 + * # â–‘ | Report size: 16 bits + * # â–‘ Bit: 8 Usage: 0008/0001: LED / Num Lock Logical Range: 0..=1 + * # â–‘ Bit: 9 Usage: 0008/0002: LED / Caps Lock Logical Range: 0..=1 + * # â–‘ Bit: 10 Usage: 0008/0003: LED / Scroll Lock Logical Range: 0..=1 + * # â–‘ Bit: 11 Usage: 0008/0004: LED / Compose Logical Range: 0..=1 + * # â–‘ Bit: 12 Usage: 0008/0005: LED / Kana Logical Range: 0..=1 + * # â–‘ Bits: 13..=15 ######### Padding + * ############################################################################## + * # Event nodes: + * # - /dev/input/event12: "Keydial mini-050 Keyboard" + * # - /dev/input/event14: "Keydial mini-050 Mouse" + * ############################################################################## + * # Recorded events below in format: + * # E: <seconds>.<microseconds> <length-in-bytes> [bytes ...] + * # + * + * - Report ID 1 sends keyboard shortcuts when pressing the buttons, e.g. + * + * # â–‘ Report ID: 1 / + * # â–‘ Keyboard LeftControl: 0 |Keyboard LeftShift: 0 |Keyboard LeftAlt: 0 |Keyboard Left GUI: 0 |Keyboard RightControl: 0 |Keyboard RightShift: 0 |Keyboard RightAlt: 0 |Keyboard Right GUI: 0 |<8 bits padding> |0007/0000: 0| Keyboard K: 14| 0007/0000: 0| 0007/0000: 0| 0007/0000: 0| 0007/0000: 0 + * E: 000000.000292 9 01 00 00 00 0e 00 00 00 00 + * + * - Report ID 2 sends the button inside the wheel/dial thing + * # â–’ Report ID: 2 / + * # â–’ Play/Pause: 205 + * E: 000134.347845 3 02 cd 00 + * # â–’ Report ID: 2 / + * # â–’ 000c/0000: 0 + * E: 000134.444965 3 02 00 00 + * + * - Report ID 5 sends the wheel relative events (always a double-event with the second as zero) + * # â–ž Report ID: 5 / + * # â–ž Button 1: 0 |Button 2: 0 |Button 3: 0 |Button 4: 0 |Button 5: 0 |<3 bits padding> |X: 0 |Y: 0 |Wheel: 255 + * E: 000064.859915 7 05 00 00 00 00 00 ff + * # â–ž Report ID: 5 / + * # â–ž Button 1: 0 |Button 2: 0 |Button 3: 0 |Button 4: 0 |Button 5: 0 |<3 bits padding> |X: 0 |Y: 0 |Wheel: 0 + * E: 000064.882009 7 05 00 00 00 00 00 00 + */ + +#define BT_PAD_REPORT_DESCRIPTOR_LENGTH 160 +#define BT_PUCK_REPORT_DESCRIPTOR_LENGTH 114 // This one doesn't send events +#define BT_PAD_KBD_REPORT_ID 1 +#define BT_PAD_CC_REPORT_ID 2 +#define BT_PAD_MOUSE_REPORT_ID 5 +#define BT_PAD_KBD_REPORT_LENGTH 9 +#define BT_PAD_CC_REPORT_LENGTH 3 +#define BT_PAD_MOUSE_REPORT_LENGTH 7 +#define OUR_REPORT_ID 11 /* "randomly" picked report ID for our reports */ + +__u32 last_button_state = 0; + +static const __u8 disabled_rdesc_puck[] = { + FixedSizeVendorReport(BT_PUCK_REPORT_DESCRIPTOR_LENGTH) +}; + +static const __u8 fixed_rdesc_pad[] = { + UsagePage_GenericDesktop + Usage_GD_Keypad + CollectionApplication( + // Byte 0 + ReportId(OUR_REPORT_ID) + UsagePage_Digitizers + Usage_Dig_TabletFunctionKeys + CollectionPhysical( + // Byte 1 is a button so we look like a tablet + Usage_Dig_BarrelSwitch // BTN_STYLUS, needed so we get to be a tablet pad + ReportCount(1) + ReportSize(1) + Input(Var|Abs) + ReportCount(7) // Padding + Input(Const) + // Bytes 2/3 - x/y just exist so we get to be a tablet pad + UsagePage_GenericDesktop + Usage_GD_X + Usage_GD_Y + LogicalMinimum_i8(0x0) + LogicalMaximum_i8(0x1) + ReportCount(2) + ReportSize(8) + Input(Var|Abs) + // Bytes 4-7 are the button state for 19 buttons + pad out to u32 + // We send the first 10 buttons as buttons 1-10 which is BTN_0 -> BTN_9 + UsagePage_Button + UsageMinimum_i8(1) + UsageMaximum_i8(10) + LogicalMinimum_i8(0x0) + LogicalMaximum_i8(0x1) + ReportCount(10) + ReportSize(1) + Input(Var|Abs) + // We send the other 9 buttons as buttons 0x31 and above -> BTN_A - BTN_TL2 + UsageMinimum_i8(0x31) + UsageMaximum_i8(0x3a) + ReportCount(9) + ReportSize(1) + Input(Var|Abs) + ReportCount(13) + ReportSize(1) + Input(Const) // padding + // Byte 8 is the wheel + UsagePage_GenericDesktop + Usage_GD_Wheel + LogicalMinimum_i8(-1) + LogicalMaximum_i8(1) + ReportCount(1) + ReportSize(8) + Input(Var|Rel) + ) + // Make sure we match our original report length + FixedSizeVendorReport(BT_PAD_KBD_REPORT_LENGTH) + ) +}; + +SEC(HID_BPF_RDESC_FIXUP) +int BPF_PROG(k20_bt_fix_rdesc, struct hid_bpf_ctx *hctx) +{ + __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */); + __s32 rdesc_size = hctx->size; + + if (!data) + return 0; /* EPERM check */ + + if (rdesc_size == BT_PAD_REPORT_DESCRIPTOR_LENGTH) { + __builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad)); + return sizeof(fixed_rdesc_pad); + } + if (rdesc_size == BT_PUCK_REPORT_DESCRIPTOR_LENGTH) { + // This hidraw node doesn't send anything and can be ignored + __builtin_memcpy(data, disabled_rdesc_puck, sizeof(disabled_rdesc_puck)); + return sizeof(disabled_rdesc_puck); + } + + return 0; +} + +SEC(HID_BPF_DEVICE_EVENT) +int BPF_PROG(k20_bt_fix_events, struct hid_bpf_ctx *hctx) +{ + __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 12 /* size */); + struct pad_report { + __u8 report_id; + __u8 btn_stylus:1; + __u8 pad:7; + __u8 x; + __u8 y; + __u32 buttons; + __u8 wheel; + } __packed * pad_report = (struct pad_report *)data; + + if (!data) + return 0; /* EPERM check */ + + /* Report ID 1 - Keyboard events (button presses) */ + if (data[0] == BT_PAD_KBD_REPORT_ID) { + const __u8 button_mapping[] = { + 0x0e, /* Button 1: K */ + 0x0a, /* Button 2: G */ + 0x0f, /* Button 3: L */ + 0x4c, /* Button 4: Delete */ + 0x0c, /* Button 5: I */ + 0x07, /* Button 6: D */ + 0x05, /* Button 7: B */ + 0x08, /* Button 8: E */ + 0x16, /* Button 9: S */ + 0x1d, /* Button 10: Z */ + 0x06, /* Button 11: C */ + 0x19, /* Button 12: V */ + 0xff, /* Button 13: LeftControl */ + 0xff, /* Button 14: LeftAlt */ + 0xff, /* Button 15: LeftShift */ + 0x28, /* Button 16: Return Enter */ + 0x2c, /* Button 17: Spacebar */ + 0x11, /* Button 18: N */ + }; + + __u8 modifiers = data[1]; + __u32 buttons = 0; + + if (modifiers & 0x01) { /* Control */ + buttons |= BIT(12); + } + if (modifiers & 0x02) { /* Shift */ + buttons |= BIT(14); + } + if (modifiers & 0x04) { /* Alt */ + buttons |= BIT(13); + } + + for (int i = 4; i < BT_PAD_KBD_REPORT_LENGTH; i++) { + if (!data[i]) + break; + + for (size_t b = 0; b < ARRAY_SIZE(button_mapping); b++) { + if (data[i] != 0xff && data[i] == button_mapping[b]) { + buttons |= BIT(b); + break; + } + } + } + + last_button_state = buttons; + + pad_report->report_id = OUR_REPORT_ID; + pad_report->btn_stylus = 0; + pad_report->x = 0; + pad_report->y = 0; + pad_report->buttons = buttons; + pad_report->wheel = 0; + + return sizeof(struct pad_report); + } + + /* Report ID 2 - Consumer control events (the button inside the wheel) */ + if (data[0] == BT_PAD_CC_REPORT_ID) { + const __u8 PlayPause = 0xcd; + + if (data[1] == PlayPause) + last_button_state |= BIT(18); + else + last_button_state &= ~BIT(18); + + pad_report->report_id = OUR_REPORT_ID; + pad_report->btn_stylus = 0; + pad_report->x = 0; + pad_report->y = 0; + pad_report->buttons = last_button_state; + pad_report->wheel = 0; + + return sizeof(struct pad_report); + } + + /* Report ID 5 - Mouse events (wheel rotation) */ + if (data[0] == BT_PAD_MOUSE_REPORT_ID) { + __u8 wheel_delta = data[6]; + + pad_report->report_id = OUR_REPORT_ID; + pad_report->btn_stylus = 0; + pad_report->x = 0; + pad_report->y = 0; + pad_report->buttons = last_button_state; + pad_report->wheel = wheel_delta; + + return sizeof(struct pad_report); + } + + return 0; +} + +HID_BPF_OPS(keydial_k20_bluetooth) = { + .hid_device_event = (void *)k20_bt_fix_events, + .hid_rdesc_fixup = (void *)k20_bt_fix_rdesc, +}; + +SEC("syscall") +int probe(struct hid_bpf_probe_args *ctx) +{ + switch (ctx->rdesc_size) { + case BT_PAD_REPORT_DESCRIPTOR_LENGTH: + case BT_PUCK_REPORT_DESCRIPTOR_LENGTH: + ctx->retval = 0; + break; + default: + ctx->retval = -EINVAL; + } + + return 0; +} + +char _license[] SEC("license") = "GPL"; diff --git a/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c b/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c index ec360d71130f..c562c2d684fe 100644 --- a/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c +++ b/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c @@ -462,7 +462,8 @@ int BPF_PROG(k20_fix_events, struct hid_bpf_ctx *hctx) __u32 buttons; __u8 wheel; } __attribute__((packed)) *pad_report; - int i, b; + int i; + size_t b; __u8 modifiers = data[1]; __u32 buttons = 0; diff --git a/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c b/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c index 82f1950445dd..253b96458c58 100644 --- a/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c +++ b/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c @@ -34,7 +34,7 @@ int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx) if (data[3] != 0x06) return 0; - for (int idx = 0; idx < ARRAY_SIZE(offsets); idx++) { + for (size_t idx = 0; idx < ARRAY_SIZE(offsets); idx++) { u8 offset = offsets[idx]; /* if Input (Cnst,Var,Abs) , make it Input (Data,Var,Abs) */ diff --git a/drivers/hid/bpf/progs/Trust__Philips-SPK6327.bpf.c b/drivers/hid/bpf/progs/Trust__Philips-SPK6327.bpf.c new file mode 100644 index 000000000000..bc7ff27eac9f --- /dev/null +++ b/drivers/hid/bpf/progs/Trust__Philips-SPK6327.bpf.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Fix for Trust Philips SPK6327 (145f:024b) + * Modifier keys report as Array (0x00) instead of Variable (0x02) + * causing LCtrl, LAlt, Super etc. to all act as LShift + */ +#include "vmlinux.h" +#include "hid_bpf.h" +#include "hid_bpf_helpers.h" +#include <bpf/bpf_tracing.h> + +#define VID_TRUST 0x145F +#define PID_SPK6327 0x024B + +HID_BPF_CONFIG( + HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_TRUST, PID_SPK6327) +); + +SEC(HID_BPF_RDESC_FIXUP) +int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx) +{ + __u8 *data = hid_bpf_get_data(hctx, 0, 4096); + + if (!data) + return 0; + + /* Fix modifier keys: Input Array (0x00) -> Input Variable (0x02) */ + if (data[101] == 0x00) + data[101] = 0x02; + + return 0; +} + +HID_BPF_OPS(trust_spk6327) = { + .hid_rdesc_fixup = (void *)hid_fix_rdesc, +}; + +SEC("syscall") +int probe(struct hid_bpf_probe_args *ctx) +{ + /* Only apply to interface 1 (169 bytes) not interface 0 (62 bytes) */ + if (ctx->rdesc_size == 169) + ctx->retval = 0; + else + ctx->retval = -EINVAL; + + return 0; +} + +char _license[] SEC("license") = "GPL"; diff --git a/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c b/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c index 2da680bc4e11..ed60a660cc1a 100644 --- a/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c +++ b/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c @@ -148,7 +148,7 @@ int probe(struct hid_bpf_probe_args *ctx) { struct hid_bpf_ctx *hid_ctx; __u16 pid; - int i; + size_t i; /* get a struct hid_device to access the actual pid of the device */ hid_ctx = hid_bpf_allocate_context(ctx->hid); diff --git a/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c b/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c index 46d5c459d0c9..ac07216f5b67 100644 --- a/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c +++ b/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c @@ -173,7 +173,7 @@ int BPF_PROG(hid_device_event_xppen_deco_mini_4, struct hid_bpf_ctx *hctx) { __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 8 /* size */); __u8 button_mask = 0; - int d, b; + size_t d, b; if (!data) return 0; /* EPERM check */ diff --git a/drivers/hid/bpf/progs/hid_bpf_async.h b/drivers/hid/bpf/progs/hid_bpf_async.h index 9ab585434239..877bb7e81f03 100644 --- a/drivers/hid/bpf/progs/hid_bpf_async.h +++ b/drivers/hid/bpf/progs/hid_bpf_async.h @@ -116,15 +116,14 @@ static int hid_bpf_async_find_empty_key(void) if (!elem) return -ENOMEM; /* should never happen */ - bpf_spin_lock(&elem->lock); + { + guard(bpf_spin)(&elem->lock); - if (elem->state == HID_BPF_ASYNC_STATE_UNSET) { - elem->state = HID_BPF_ASYNC_STATE_INITIALIZING; - bpf_spin_unlock(&elem->lock); - return i; + if (elem->state == HID_BPF_ASYNC_STATE_UNSET) { + elem->state = HID_BPF_ASYNC_STATE_INITIALIZING; + return i; + } } - - bpf_spin_unlock(&elem->lock); } return -EINVAL; @@ -175,18 +174,19 @@ static int hid_bpf_async_delayed_call(struct hid_bpf_ctx *hctx, u64 milliseconds if (!elem) return -EINVAL; - bpf_spin_lock(&elem->lock); - /* The wq must be: - * - HID_BPF_ASYNC_STATE_INITIALIZED -> it's been initialized and ready to be called - * - HID_BPF_ASYNC_STATE_RUNNING -> possible re-entry from the wq itself - */ - if (elem->state != HID_BPF_ASYNC_STATE_INITIALIZED && - elem->state != HID_BPF_ASYNC_STATE_RUNNING) { - bpf_spin_unlock(&elem->lock); - return -EINVAL; + { + guard(bpf_spin)(&elem->lock); + + /* The wq must be: + * - HID_BPF_ASYNC_STATE_INITIALIZED -> it's been initialized and ready to be called + * - HID_BPF_ASYNC_STATE_RUNNING -> possible re-entry from the wq itself + */ + if (elem->state != HID_BPF_ASYNC_STATE_INITIALIZED && + elem->state != HID_BPF_ASYNC_STATE_RUNNING) + return -EINVAL; + + elem->state = HID_BPF_ASYNC_STATE_STARTING; } - elem->state = HID_BPF_ASYNC_STATE_STARTING; - bpf_spin_unlock(&elem->lock); elem->hid = hctx->hid->id; diff --git a/drivers/hid/bpf/progs/hid_bpf_helpers.h b/drivers/hid/bpf/progs/hid_bpf_helpers.h index 228f8d787567..0fd8e7d90742 100644 --- a/drivers/hid/bpf/progs/hid_bpf_helpers.h +++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h @@ -7,7 +7,18 @@ #include "vmlinux.h" #include <bpf/bpf_helpers.h> +#include <bpf/bpf_endian.h> #include <linux/errno.h> +#include "hid_report_descriptor_helpers.h" + +/* Compiler attributes */ +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +#ifndef __maybe_unused +#define __maybe_unused __attribute__((__unused__)) +#endif extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, @@ -40,6 +51,86 @@ extern int bpf_wq_set_callback(struct bpf_wq *wq, #define HID_MAX_DESCRIPTOR_SIZE 4096 #define HID_IGNORE_EVENT -1 +/** + * Use: _cleanup_(somefunction) struct foo *bar; + */ +#define _cleanup_(_x) __attribute__((cleanup(_x))) + +/** + * Use: _release_(foo) *bar; + * + * This requires foo_releasep() to be present, use DEFINE_RELEASE_CLEANUP_FUNC. + */ +#define _release_(_type) struct _type __attribute__((cleanup(_type##_releasep))) + +/** + * Define a cleanup function for the struct type foo with a matching + * foo_release(). Use: + * DEFINE_RELEASE_CLEANUP_FUNC(foo) + * _unref_(foo) struct foo *bar; + */ +#define DEFINE_RELEASE_CLEANUP_FUNC(_type) \ + static inline void _type##_releasep(struct _type **_p) { \ + if (*_p) \ + _type##_release(*_p); \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +/* for being able to have a cleanup function */ +#define hid_bpf_ctx_release hid_bpf_release_context +DEFINE_RELEASE_CLEANUP_FUNC(hid_bpf_ctx); + +/* + * Kernel-style guard macros adapted for BPF + * Based on include/linux/cleanup.h from the Linux kernel + * + * These provide automatic lock/unlock using __attribute__((cleanup)) + * similar to how _release_() works for contexts. + */ + +/** + * DEFINE_GUARD(name, type, lock, unlock): + * Define a guard for automatic lock/unlock using the same pattern as _release_() + * @name: identifier for the guard (e.g., bpf_spin) + * @type: lock variable type (e.g., struct bpf_spin_lock) + * @lock: lock function name (e.g., bpf_spin_lock) + * @unlock: unlock function name (e.g., bpf_spin_unlock) + * + * guard(name): + * Declare and lock in one statement - lock held until end of scope + * + * Example: + * DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock) + * + * void foo(struct bpf_spin_lock *lock) { + * guard(bpf_spin)(lock); + * // lock held until end of scope + * } + */ + +/* Guard helper struct - stores lock pointer for cleanup */ +#define DEFINE_GUARD(_name, _type, _lock, _unlock) \ +struct _name##_guard { \ + _type *lock; \ +}; \ +static inline void _name##_guard_cleanup(struct _name##_guard *g) { \ + if (g && g->lock) \ + _unlock(g->lock); \ +} \ +static inline struct _name##_guard _name##_guard_init(_type *l) { \ + if (l) \ + _lock(l); \ + return (struct _name##_guard){.lock = l}; \ +} \ +struct __useless_struct_to_allow_trailing_semicolon__ + +#define guard(_name) \ + struct _name##_guard COMBINE(guard, __LINE__) __attribute__((cleanup(_name##_guard_cleanup))) = \ + _name##_guard_init + +/* Define BPF spinlock guard */ +DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock); + /* extracted from <linux/input.h> */ #define BUS_ANY 0x00 #define BUS_PCI 0x01 @@ -183,4 +274,234 @@ extern int bpf_wq_set_callback(struct bpf_wq *wq, _EXPAND(_ARG, __VA_ARGS__) \ } _device_ids SEC(".hid_bpf_config") + +/* Equivalency macros for bpf_htons and friends which are + * Big Endian only - HID needs little endian so these are the + * corresponding macros for that. See bpf/bpf_endian.h + */ +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define __hid_bpf_le16_to_cpu(x) (x) +# define __hid_bpf_le32_to_cpu(x) (x) +# define __hid_bpf_le64_to_cpu(x) (x) +# define __hid_bpf_cpu_to_le16(x) (x) +# define __hid_bpf_cpu_to_le32(x) (x) +# define __hid_bpf_cpu_to_le64(x) (x) +# define __hid_bpf_constant_le16_to_cpu(x) (x) +# define __hid_bpf_constant_le32_to_cpu(x) (x) +# define __hid_bpf_constant_le64_to_cpu(x) (x) +# define __hid_bpf_constant_cpu_to_le16(x) (x) +# define __hid_bpf_constant_cpu_to_le32(x) (x) +# define __hid_bpf_constant_cpu_to_le64(x) (x) +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define __hid_bpf_le16_to_cpu(x) __builtin_bswap16(x) +# define __hid_bpf_le32_to_cpu(x) __builtin_bswap32(x) +# define __hid_bpf_le64_to_cpu(x) __builtin_bswap64(x) +# define __hid_bpf_cpu_to_le16(x) __builtin_bswap16(x) +# define __hid_bpf_cpu_to_le32(x) __builtin_bswap32(x) +# define __hid_bpf_cpu_to_le64(x) __builtin_bswap64(x) +# define __hid_bpf_constant_le16_to_cpu(x) __bpf_swab16(x) +# define __hid_bpf_constant_le32_to_cpu(x) __bpf_swab32(x) +# define __hid_bpf_constant_le64_to_cpu(x) __bpf_swab64(x) +# define __hid_bpf_constant_cpu_to_le16(x) __bpf_swab16(x) +# define __hid_bpf_constant_cpu_to_le32(x) __bpf_swab32(x) +# define __hid_bpf_constant_cpu_to_le64(x) __bpf_swab64(x) +#else +# error "Invalid __BYTE_ORDER__" +#endif + +#define hid_bpf_le16_to_cpu(x) \ + (__builtin_constant_p(x) ? \ + __hid_bpf_constant_le16_to_cpu(x) : __hid_bpf_le16_to_cpu(x)) + +#define hid_bpf_le32_to_cpu(x) \ + (__builtin_constant_p(x) ? \ + __hid_bpf_constant_le32_to_cpu(x) : __hid_bpf_le32_to_cpu(x)) + +#define hid_bpf_le64_to_cpu(x) \ + (__builtin_constant_p(x) ? \ + __hid_bpf_constant_le64_to_cpu(x) : __hid_bpf_le64_to_cpu(x)) + +#define hid_bpf_cpu_to_le16(x) \ + (__builtin_constant_p(x) ? \ + __hid_bpf_constant_cpu_to_le16(x) : __hid_bpf_cpu_to_le16(x)) + +#define hid_bpf_cpu_to_le32(x) \ + (__builtin_constant_p(x) ? \ + __hid_bpf_constant_cpu_to_le32(x) : __hid_bpf_cpu_to_le32(x)) + +#define hid_bpf_cpu_to_le64(x) \ + (__builtin_constant_p(x) ? \ + __hid_bpf_constant_cpu_to_le64(x) : __hid_bpf_cpu_to_le64(x)) + +#define hid_bpf_be16_to_cpu(x) bpf_ntohs(x) +#define hid_bpf_be32_to_cpu(x) bpf_ntohl(x) +#define hid_bpf_be64_to_cpu(x) bpf_be64_to_cpu(x) +#define hid_bpf_cpu_to_be16(x) bpf_htons(x) +#define hid_bpf_cpu_to_be32(x) bpf_htonl(x) +#define hid_bpf_cpu_to_be64(x) bpf_cpu_to_be64(x) + +/* + * The following macros are helpers for exporting udev properties: + * + * EXPORT_UDEV_PROP(name, len) generates: + * - a map with a single element UDEV_PROP_##name, of size len + * - a const global declaration of that len: SIZEOF_##name + * + * udev_prop_ptr(name) retrieves the data pointer behind the map. + * + * UDEV_PROP_SPRINTF(name, fmt, ...) writes data into the udev property. + * + * Can be used as such: + * EXPORT_UDEV_PROP(HID_FOO, 32); + * + * SEC("syscall") + * int probe(struct hid_bpf_probe_args *ctx) + * { + * const char *foo = "foo"; + * UDEV_PROP_SPRINTF(HID_FOO, "%s", foo); + * + * return 0; + * } + */ +#define EXPORT_UDEV_PROP(name, len) \ + const __u32 SIZEOF_##name = len; \ + struct COMBINE(udev_prop, __LINE__) { \ + __uint(type, BPF_MAP_TYPE_ARRAY); \ + __uint(max_entries, 1); \ + __type(key, __u32); \ + __type(value, __u8[len]); \ + } UDEV_PROP_##name SEC(".maps"); + +#define udev_prop_ptr(name) \ + bpf_map_lookup_elem(&UDEV_PROP_##name, &(__u32){0}) + +#define UDEV_PROP_SPRINTF(name, fmt, ...) \ + BPF_SNPRINTF(udev_prop_ptr(name), SIZEOF_##name, fmt, ##__VA_ARGS__) + +static inline __maybe_unused __u16 field_start_byte(struct hid_rdesc_field *field) +{ + return field->bits_start / 8; +} + +static inline __maybe_unused __u16 field_end_byte(struct hid_rdesc_field *field) +{ + if (!field->bits_end) + return 0; + + return (__u16)(field->bits_end - 1) / 8; +} + +static __maybe_unused __u32 extract_bits(__u8 *buffer, const size_t size, struct hid_rdesc_field *field) +{ + __s32 nbits = field->bits_end - field->bits_start; + __u32 start = field_start_byte(field); + __u32 end = field_end_byte(field); + __u8 base_shift = field->bits_start % 8; + + if (nbits <= 0 || nbits > 32 || start >= size || end >= size) + return 0; + + /* Fast path for byte-aligned standard-sized reads */ + if (base_shift == 0) { + /* 8-bit aligned read */ + if (nbits == 8 && start < size) + return buffer[start]; + + /* 16-bit aligned read - use separate variables for verifier */ + if (nbits == 16) { + __u32 off0 = start; + __u32 off1 = start + 1; + + if (off0 < size && off1 < size) { + return buffer[off0] | + ((__u32)buffer[off1] << 8); + } + } + + /* 32-bit aligned read - use separate variables for verifier */ + if (nbits == 32) { + __u32 off0 = start; + __u32 off1 = start + 1; + __u32 off2 = start + 2; + __u32 off3 = start + 3; + + if (off0 < size && off1 < size && + off2 < size && off3 < size) { + return buffer[off0] | + ((__u32)buffer[off1] << 8) | + ((__u32)buffer[off2] << 16) | + ((__u32)buffer[off3] << 24); + } + } + } + + /* General case: bit manipulation for unaligned or non-standard sizes */ + int mask = 0xffffffff >> (32 - nbits); + __u64 value = 0; + __u32 i; + + bpf_for (i, start, end + 1) { + value |= (__u64)buffer[i] << ((i - start) * 8); + } + + return (value >> base_shift) & mask; +} + +#define EXTRACT_BITS(buffer, field) extract_bits(buffer, sizeof(buffer), field) + +/* Base macro for iterating over HID arrays with bounds checking. + * Follows the bpf_for pattern from libbpf. + */ +#define __hid_bpf_for_each_array(array, num_elements, max_elements, var) \ + for ( \ + /* initialize and define destructor */ \ + struct bpf_iter_num ___it __attribute__((aligned(8), \ + cleanup(bpf_iter_num_destroy))), \ + /* ___p pointer is necessary to call bpf_iter_num_new() *once* */ \ + *___p __attribute__((unused)) = ( \ + /* always initialize iterator; if bounds fail, iterate 0 times */ \ + bpf_iter_num_new(&___it, 0, \ + (num_elements) > (max_elements) ? \ + 0 : (num_elements)), \ + /* workaround for Clang bug */ \ + (void)bpf_iter_num_destroy, (void *)0); \ + ({ \ + /* iteration step */ \ + int *___t = bpf_iter_num_next(&___it); \ + int ___i; \ + /* termination and bounds check, assign var */ \ + (___t && (___i = *___t, ___i >= 0 && ___i < (num_elements)) && \ + ((num_elements) <= (max_elements)) && \ + (var = &(array)[___i], 1)); \ + }); \ + ) + +/* Iterate over input reports in a descriptor */ +#define hid_bpf_for_each_input_report(descriptor, report_var) \ + __hid_bpf_for_each_array((descriptor)->input_reports, \ + (descriptor)->num_input_reports, \ + HID_MAX_REPORTS, report_var) + +/* Iterate over feature reports in a descriptor */ +#define hid_bpf_for_each_feature_report(descriptor, report_var) \ + __hid_bpf_for_each_array((descriptor)->feature_reports, \ + (descriptor)->num_feature_reports, \ + HID_MAX_REPORTS, report_var) + +/* Iterate over output reports in a descriptor */ +#define hid_bpf_for_each_output_report(descriptor, report_var) \ + __hid_bpf_for_each_array((descriptor)->output_reports, \ + (descriptor)->num_output_reports, \ + HID_MAX_REPORTS, report_var) + +/* Iterate over fields in a report */ +#define hid_bpf_for_each_field(report, field_var) \ + __hid_bpf_for_each_array((report)->fields, (report)->num_fields, \ + HID_MAX_FIELDS, field_var) + +/* Iterate over collections in a field */ +#define hid_bpf_for_each_collection(field, collection_var) \ + __hid_bpf_for_each_array((field)->collections, (field)->num_collections, \ + HID_MAX_COLLECTIONS, collection_var) + #endif /* __HID_BPF_HELPERS_H */ diff --git a/drivers/hid/bpf/progs/hid_report_descriptor_helpers.h b/drivers/hid/bpf/progs/hid_report_descriptor_helpers.h new file mode 100644 index 000000000000..2aed5c0a6ad4 --- /dev/null +++ b/drivers/hid/bpf/progs/hid_report_descriptor_helpers.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2022 Benjamin Tissoires + */ + +#ifndef __HID_REPORT_DESCRIPTOR_HELPERS_H +#define __HID_REPORT_DESCRIPTOR_HELPERS_H + +#include "vmlinux.h" + +/* Compiler attributes */ +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +#ifndef __maybe_unused +#define __maybe_unused __attribute__((__unused__)) +#endif + +/* Report Descriptor Structures */ +#define HID_MAX_COLLECTIONS 32 +#define HID_MAX_FIELDS 64 +#define HID_MAX_REPORTS 16 + +enum hid_rdesc_field_type { + HID_FIELD_VARIABLE = 0, + HID_FIELD_ARRAY = 1, + HID_FIELD_CONSTANT = 2, +}; + +struct hid_rdesc_collection { + __u16 usage_page; + __u16 usage_id; + __u8 collection_type; +} __packed; + +struct hid_rdesc_field { + __u8 field_type; /* enum hid_rdesc_field_type */ + __u8 num_collections; + __u16 bits_start; + __u16 bits_end; + __u16 usage_page; + union { + __u16 usage_id; /* For Variable fields */ + struct __packed { /* For Array fields */ + __u16 usage_minimum; + __u16 usage_maximum; + }; + }; + __s32 logical_minimum; + __s32 logical_maximum; + struct { + __u8 is_relative:1; /* Data is relative to previous value */ + __u8 wraps:1; /* Value wraps around (e.g., rotary encoder) */ + __u8 is_nonlinear:1; /* Non-linear relationship between logical/physical */ + __u8 has_no_preferred_state:1; /* No rest position (e.g., free-floating joystick) */ + __u8 has_null_state:1; /* Can report null/no-data values */ + __u8 is_volatile:1; /* Volatile (Output/Feature) - NOT POPULATED, always 0 */ + __u8 is_buffered_bytes:1; /* Fixed-size byte stream vs bitfield */ + __u8 reserved:1; /* Reserved for future use */ + } flags; + struct hid_rdesc_collection collections[HID_MAX_COLLECTIONS]; +} __packed; + +struct hid_rdesc_report { + __u8 report_id; /* 0 means no report ID */ + __u16 size_in_bits; + __u8 num_fields; + struct hid_rdesc_field fields[HID_MAX_FIELDS]; +} __packed; + +struct hid_rdesc_descriptor { + __u8 num_input_reports; + __u8 num_output_reports; + __u8 num_feature_reports; + struct hid_rdesc_report input_reports[HID_MAX_REPORTS]; + struct hid_rdesc_report output_reports[HID_MAX_REPORTS]; + struct hid_rdesc_report feature_reports[HID_MAX_REPORTS]; +} __packed; + +#endif /* __HID_REPORT_DESCRIPTOR_HELPERS_H */ diff --git a/drivers/hid/bpf/progs/hid_usages.h b/drivers/hid/bpf/progs/hid_usages.h new file mode 100644 index 000000000000..5469b25c9f79 --- /dev/null +++ b/drivers/hid/bpf/progs/hid_usages.h @@ -0,0 +1,2810 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2025 Red Hat, Inc + */ + +// THIS FILE IS GENERATED, DO NOT EDIT + +#pragma once + +/* ----- Generated Usage Pages and Usages ------ */ +#define HidUsagePage_GenericDesktop 0x01 +#define HidUsagePage_SimulationControls 0x02 +#define HidUsagePage_VRControls 0x03 +#define HidUsagePage_SportControls 0x04 +#define HidUsagePage_GameControls 0x05 +#define HidUsagePage_GenericDeviceControls 0x06 +#define HidUsagePage_KeyboardKeypad 0x07 +#define HidUsagePage_LED 0x08 +#define HidUsagePage_Button 0x09 +#define HidUsagePage_Ordinal 0x0a +#define HidUsagePage_TelephonyDevice 0x0b +#define HidUsagePage_Consumer 0x0c +#define HidUsagePage_Digitizers 0x0d +#define HidUsagePage_Haptics 0x0e +#define HidUsagePage_PhysicalInputDevice 0x0f +#define HidUsagePage_Unicode 0x10 +#define HidUsagePage_SoC 0x11 +#define HidUsagePage_EyeandHeadTrackers 0x12 +#define HidUsagePage_AuxiliaryDisplay 0x14 +#define HidUsagePage_Sensors 0x20 +#define HidUsagePage_MedicalInstrument 0x40 +#define HidUsagePage_BrailleDisplay 0x41 +#define HidUsagePage_LightingAndIllumination 0x59 +#define HidUsagePage_Monitor 0x80 +#define HidUsagePage_MonitorEnumerated 0x81 +#define HidUsagePage_VESAVirtualControls 0x82 +#define HidUsagePage_Power 0x84 +#define HidUsagePage_BatterySystem 0x85 +#define HidUsagePage_BarcodeScanner 0x8c +#define HidUsagePage_Scales 0x8d +#define HidUsagePage_MagneticStripeReader 0x8e +#define HidUsagePage_CameraControl 0x90 +#define HidUsagePage_Arcade 0x91 +#define HidUsagePage_FIDOAlliance 0xf1d0 + +#define HidUsage_GD_Pointer 0x01 +#define HidUsage_GD_Mouse 0x02 +#define HidUsage_GD_Joystick 0x04 +#define HidUsage_GD_Gamepad 0x05 +#define HidUsage_GD_Keyboard 0x06 +#define HidUsage_GD_Keypad 0x07 +#define HidUsage_GD_MultiaxisController 0x08 +#define HidUsage_GD_TabletPCSystemControls 0x09 +#define HidUsage_GD_WaterCoolingDevice 0x0a +#define HidUsage_GD_ComputerChassisDevice 0x0b +#define HidUsage_GD_WirelessRadioControls 0x0c +#define HidUsage_GD_PortableDeviceControl 0x0d +#define HidUsage_GD_SystemMultiAxisController 0x0e +#define HidUsage_GD_SpatialController 0x0f +#define HidUsage_GD_AssistiveControl 0x10 +#define HidUsage_GD_DeviceDock 0x11 +#define HidUsage_GD_DockableDevice 0x12 +#define HidUsage_GD_CallStateManagementControl 0x13 +#define HidUsage_GD_X 0x30 +#define HidUsage_GD_Y 0x31 +#define HidUsage_GD_Z 0x32 +#define HidUsage_GD_Rx 0x33 +#define HidUsage_GD_Ry 0x34 +#define HidUsage_GD_Rz 0x35 +#define HidUsage_GD_Slider 0x36 +#define HidUsage_GD_Dial 0x37 +#define HidUsage_GD_Wheel 0x38 +#define HidUsage_GD_HatSwitch 0x39 +#define HidUsage_GD_CountedBuffer 0x3a +#define HidUsage_GD_ByteCount 0x3b +#define HidUsage_GD_MotionWakeup 0x3c +#define HidUsage_GD_Start 0x3d +#define HidUsage_GD_Select 0x3e +#define HidUsage_GD_Vx 0x40 +#define HidUsage_GD_Vy 0x41 +#define HidUsage_GD_Vz 0x42 +#define HidUsage_GD_Vbrx 0x43 +#define HidUsage_GD_Vbry 0x44 +#define HidUsage_GD_Vbrz 0x45 +#define HidUsage_GD_Vno 0x46 +#define HidUsage_GD_FeatureNotification 0x47 +#define HidUsage_GD_ResolutionMultiplier 0x48 +#define HidUsage_GD_Qx 0x49 +#define HidUsage_GD_Qy 0x4a +#define HidUsage_GD_Qz 0x4b +#define HidUsage_GD_Qw 0x4c +#define HidUsage_GD_SystemControl 0x80 +#define HidUsage_GD_SystemPowerDown 0x81 +#define HidUsage_GD_SystemSleep 0x82 +#define HidUsage_GD_SystemWakeUp 0x83 +#define HidUsage_GD_SystemContextMenu 0x84 +#define HidUsage_GD_SystemMainMenu 0x85 +#define HidUsage_GD_SystemAppMenu 0x86 +#define HidUsage_GD_SystemMenuHelp 0x87 +#define HidUsage_GD_SystemMenuExit 0x88 +#define HidUsage_GD_SystemMenuSelect 0x89 +#define HidUsage_GD_SystemMenuRight 0x8a +#define HidUsage_GD_SystemMenuLeft 0x8b +#define HidUsage_GD_SystemMenuUp 0x8c +#define HidUsage_GD_SystemMenuDown 0x8d +#define HidUsage_GD_SystemColdRestart 0x8e +#define HidUsage_GD_SystemWarmRestart 0x8f +#define HidUsage_GD_DpadUp 0x90 +#define HidUsage_GD_DpadDown 0x91 +#define HidUsage_GD_DpadRight 0x92 +#define HidUsage_GD_DpadLeft 0x93 +#define HidUsage_GD_IndexTrigger 0x94 +#define HidUsage_GD_PalmTrigger 0x95 +#define HidUsage_GD_Thumbstick 0x96 +#define HidUsage_GD_SystemFunctionShift 0x97 +#define HidUsage_GD_SystemFunctionShiftLock 0x98 +#define HidUsage_GD_SystemFunctionShiftLockIndicator 0x99 +#define HidUsage_GD_SystemDismissNotification 0x9a +#define HidUsage_GD_SystemDoNotDisturb 0x9b +#define HidUsage_GD_SystemDock 0xa0 +#define HidUsage_GD_SystemUndock 0xa1 +#define HidUsage_GD_SystemSetup 0xa2 +#define HidUsage_GD_SystemBreak 0xa3 +#define HidUsage_GD_SystemDebuggerBreak 0xa4 +#define HidUsage_GD_ApplicationBreak 0xa5 +#define HidUsage_GD_ApplicationDebuggerBreak 0xa6 +#define HidUsage_GD_SystemSpeakerMute 0xa7 +#define HidUsage_GD_SystemHibernate 0xa8 +#define HidUsage_GD_SystemMicrophoneMute 0xa9 +#define HidUsage_GD_SystemAccessibilityBinding 0xaa +#define HidUsage_GD_SystemDisplayInvert 0xb0 +#define HidUsage_GD_SystemDisplayInternal 0xb1 +#define HidUsage_GD_SystemDisplayExternal 0xb2 +#define HidUsage_GD_SystemDisplayBoth 0xb3 +#define HidUsage_GD_SystemDisplayDual 0xb4 +#define HidUsage_GD_SystemDisplayToggleIntExtMode 0xb5 +#define HidUsage_GD_SystemDisplaySwapPrimarySecondary 0xb6 +#define HidUsage_GD_SystemDisplayToggleLCDAutoscale 0xb7 +#define HidUsage_GD_SensorZone 0xc0 +#define HidUsage_GD_RPM 0xc1 +#define HidUsage_GD_CoolantLevel 0xc2 +#define HidUsage_GD_CoolantCriticalLevel 0xc3 +#define HidUsage_GD_CoolantPump 0xc4 +#define HidUsage_GD_ChassisEnclosure 0xc5 +#define HidUsage_GD_WirelessRadioButton 0xc6 +#define HidUsage_GD_WirelessRadioLED 0xc7 +#define HidUsage_GD_WirelessRadioSliderSwitch 0xc8 +#define HidUsage_GD_SystemDisplayRotationLockButton 0xc9 +#define HidUsage_GD_SystemDisplayRotationLockSliderSwitch 0xca +#define HidUsage_GD_ControlEnable 0xcb +#define HidUsage_GD_DockableDeviceUniqueID 0xd0 +#define HidUsage_GD_DockableDeviceVendorID 0xd1 +#define HidUsage_GD_DockableDevicePrimaryUsagePage 0xd2 +#define HidUsage_GD_DockableDevicePrimaryUsageID 0xd3 +#define HidUsage_GD_DockableDeviceDockingState 0xd4 +#define HidUsage_GD_DockableDeviceDisplayOcclusion 0xd5 +#define HidUsage_GD_DockableDeviceObjectType 0xd6 +#define HidUsage_GD_CallActiveLED 0xe0 +#define HidUsage_GD_CallMuteToggle 0xe1 +#define HidUsage_GD_CallMuteLED 0xe2 +#define HidUsage_SC_FlightSimulationDevice 0x01 +#define HidUsage_SC_AutomobileSimulationDevice 0x02 +#define HidUsage_SC_TankSimulationDevice 0x03 +#define HidUsage_SC_SpaceshipSimulationDevice 0x04 +#define HidUsage_SC_SubmarineSimulationDevice 0x05 +#define HidUsage_SC_SailingSimulationDevice 0x06 +#define HidUsage_SC_MotorcycleSimulationDevice 0x07 +#define HidUsage_SC_SportsSimulationDevice 0x08 +#define HidUsage_SC_AirplaneSimulationDevice 0x09 +#define HidUsage_SC_HelicopterSimulationDevice 0x0a +#define HidUsage_SC_MagicCarpetSimulationDevice 0x0b +#define HidUsage_SC_BicycleSimulationDevice 0x0c +#define HidUsage_SC_FlightControlStick 0x20 +#define HidUsage_SC_FlightStick 0x21 +#define HidUsage_SC_CyclicControl 0x22 +#define HidUsage_SC_CyclicTrim 0x23 +#define HidUsage_SC_FlightYoke 0x24 +#define HidUsage_SC_TrackControl 0x25 +#define HidUsage_SC_Aileron 0xb0 +#define HidUsage_SC_AileronTrim 0xb1 +#define HidUsage_SC_AntiTorqueControl 0xb2 +#define HidUsage_SC_AutopilotEnable 0xb3 +#define HidUsage_SC_ChaffRelease 0xb4 +#define HidUsage_SC_CollectiveControl 0xb5 +#define HidUsage_SC_DiveBrake 0xb6 +#define HidUsage_SC_ElectronicCountermeasures 0xb7 +#define HidUsage_SC_Elevator 0xb8 +#define HidUsage_SC_ElevatorTrim 0xb9 +#define HidUsage_SC_Rudder 0xba +#define HidUsage_SC_Throttle 0xbb +#define HidUsage_SC_FlightCommunications 0xbc +#define HidUsage_SC_FlareRelease 0xbd +#define HidUsage_SC_LandingGear 0xbe +#define HidUsage_SC_ToeBrake 0xbf +#define HidUsage_SC_Trigger 0xc0 +#define HidUsage_SC_WeaponsArm 0xc1 +#define HidUsage_SC_WeaponsSelect 0xc2 +#define HidUsage_SC_WingFlaps 0xc3 +#define HidUsage_SC_Accelerator 0xc4 +#define HidUsage_SC_Brake 0xc5 +#define HidUsage_SC_Clutch 0xc6 +#define HidUsage_SC_Shifter 0xc7 +#define HidUsage_SC_Steering 0xc8 +#define HidUsage_SC_TurretDirection 0xc9 +#define HidUsage_SC_BarrelElevation 0xca +#define HidUsage_SC_DivePlane 0xcb +#define HidUsage_SC_Ballast 0xcc +#define HidUsage_SC_BicycleCrank 0xcd +#define HidUsage_SC_HandleBars 0xce +#define HidUsage_SC_FrontBrake 0xcf +#define HidUsage_SC_RearBrake 0xd0 +#define HidUsage_VRC_Belt 0x01 +#define HidUsage_VRC_BodySuit 0x02 +#define HidUsage_VRC_Flexor 0x03 +#define HidUsage_VRC_Glove 0x04 +#define HidUsage_VRC_HeadTracker 0x05 +#define HidUsage_VRC_HeadMountedDisplay 0x06 +#define HidUsage_VRC_HandTracker 0x07 +#define HidUsage_VRC_Oculometer 0x08 +#define HidUsage_VRC_Vest 0x09 +#define HidUsage_VRC_AnimatronicDevice 0x0a +#define HidUsage_VRC_StereoEnable 0x20 +#define HidUsage_VRC_DisplayEnable 0x21 +#define HidUsage_SC_BaseballBat 0x01 +#define HidUsage_SC_GolfClub 0x02 +#define HidUsage_SC_RowingMachine 0x03 +#define HidUsage_SC_Treadmill 0x04 +#define HidUsage_SC_Oar 0x30 +#define HidUsage_SC_Slope 0x31 +#define HidUsage_SC_Rate 0x32 +#define HidUsage_SC_StickSpeed 0x33 +#define HidUsage_SC_StickFaceAngle 0x34 +#define HidUsage_SC_StickHeelToe 0x35 +#define HidUsage_SC_StickFollowThrough 0x36 +#define HidUsage_SC_StickTempo 0x37 +#define HidUsage_SC_StickType 0x38 +#define HidUsage_SC_StickHeight 0x39 +#define HidUsage_SC_Putter 0x50 +#define HidUsage_SC_OneIron 0x51 +#define HidUsage_SC_TwoIron 0x52 +#define HidUsage_SC_ThreeIron 0x53 +#define HidUsage_SC_FourIron 0x54 +#define HidUsage_SC_FiveIron 0x55 +#define HidUsage_SC_SixIron 0x56 +#define HidUsage_SC_SevenIron 0x57 +#define HidUsage_SC_EightIron 0x58 +#define HidUsage_SC_NineIron 0x59 +#define HidUsage_SC_One0Iron 0x5a +#define HidUsage_SC_One1Iron 0x5b +#define HidUsage_SC_SandWedge 0x5c +#define HidUsage_SC_LoftWedge 0x5d +#define HidUsage_SC_PowerWedge 0x5e +#define HidUsage_SC_OneWood 0x5f +#define HidUsage_SC_ThreeWood 0x60 +#define HidUsage_SC_FiveWood 0x61 +#define HidUsage_SC_SevenWood 0x62 +#define HidUsage_SC_NineWood 0x63 +#define HidUsage_GC_ThreeDGameController 0x01 +#define HidUsage_GC_PinballDevice 0x02 +#define HidUsage_GC_GunDevice 0x03 +#define HidUsage_GC_PointofView 0x20 +#define HidUsage_GC_TurnRightLeft 0x21 +#define HidUsage_GC_PitchForwardBackward 0x22 +#define HidUsage_GC_RollRightLeft 0x23 +#define HidUsage_GC_MoveRightLeft 0x24 +#define HidUsage_GC_MoveForwardBackward 0x25 +#define HidUsage_GC_MoveUpDown 0x26 +#define HidUsage_GC_LeanRightLeft 0x27 +#define HidUsage_GC_LeanForwardBackward 0x28 +#define HidUsage_GC_HeightofPOV 0x29 +#define HidUsage_GC_Flipper 0x2a +#define HidUsage_GC_SecondaryFlipper 0x2b +#define HidUsage_GC_Bump 0x2c +#define HidUsage_GC_NewGame 0x2d +#define HidUsage_GC_ShootBall 0x2e +#define HidUsage_GC_Player 0x2f +#define HidUsage_GC_GunBolt 0x30 +#define HidUsage_GC_GunClip 0x31 +#define HidUsage_GC_GunSelector 0x32 +#define HidUsage_GC_GunSingleShot 0x33 +#define HidUsage_GC_GunBurst 0x34 +#define HidUsage_GC_GunAutomatic 0x35 +#define HidUsage_GC_GunSafety 0x36 +#define HidUsage_GC_GamepadFireJump 0x37 +#define HidUsage_GC_GamepadTrigger 0x39 +#define HidUsage_GC_FormfittingGamepad 0x3a +#define HidUsage_GDC_BackgroundNonuserControls 0x01 +#define HidUsage_GDC_BatteryStrength 0x20 +#define HidUsage_GDC_WirelessChannel 0x21 +#define HidUsage_GDC_WirelessID 0x22 +#define HidUsage_GDC_DiscoverWirelessControl 0x23 +#define HidUsage_GDC_SecurityCodeCharacterEntered 0x24 +#define HidUsage_GDC_SecurityCodeCharacterErased 0x25 +#define HidUsage_GDC_SecurityCodeCleared 0x26 +#define HidUsage_GDC_SequenceID 0x27 +#define HidUsage_GDC_SequenceIDReset 0x28 +#define HidUsage_GDC_RFSignalStrength 0x29 +#define HidUsage_GDC_SoftwareVersion 0x2a +#define HidUsage_GDC_ProtocolVersion 0x2b +#define HidUsage_GDC_HardwareVersion 0x2c +#define HidUsage_GDC_Major 0x2d +#define HidUsage_GDC_Minor 0x2e +#define HidUsage_GDC_Revision 0x2f +#define HidUsage_GDC_Handedness 0x30 +#define HidUsage_GDC_EitherHand 0x31 +#define HidUsage_GDC_LeftHand 0x32 +#define HidUsage_GDC_RightHand 0x33 +#define HidUsage_GDC_BothHands 0x34 +#define HidUsage_GDC_GripPoseOffset 0x40 +#define HidUsage_GDC_PointerPoseOffset 0x41 +#define HidUsage_KK_ErrorRollOver 0x01 +#define HidUsage_KK_POSTFail 0x02 +#define HidUsage_KK_ErrorUndefined 0x03 +#define HidUsage_KK_KeyboardA 0x04 +#define HidUsage_KK_KeyboardB 0x05 +#define HidUsage_KK_KeyboardC 0x06 +#define HidUsage_KK_KeyboardD 0x07 +#define HidUsage_KK_KeyboardE 0x08 +#define HidUsage_KK_KeyboardF 0x09 +#define HidUsage_KK_KeyboardG 0x0a +#define HidUsage_KK_KeyboardH 0x0b +#define HidUsage_KK_KeyboardI 0x0c +#define HidUsage_KK_KeyboardJ 0x0d +#define HidUsage_KK_KeyboardK 0x0e +#define HidUsage_KK_KeyboardL 0x0f +#define HidUsage_KK_KeyboardM 0x10 +#define HidUsage_KK_KeyboardN 0x11 +#define HidUsage_KK_KeyboardO 0x12 +#define HidUsage_KK_KeyboardP 0x13 +#define HidUsage_KK_KeyboardQ 0x14 +#define HidUsage_KK_KeyboardR 0x15 +#define HidUsage_KK_KeyboardS 0x16 +#define HidUsage_KK_KeyboardT 0x17 +#define HidUsage_KK_KeyboardU 0x18 +#define HidUsage_KK_KeyboardV 0x19 +#define HidUsage_KK_KeyboardW 0x1a +#define HidUsage_KK_KeyboardX 0x1b +#define HidUsage_KK_KeyboardY 0x1c +#define HidUsage_KK_KeyboardZ 0x1d +#define HidUsage_KK_Keyboard1andBang 0x1e +#define HidUsage_KK_Keyboard2andAt 0x1f +#define HidUsage_KK_Keyboard3andHash 0x20 +#define HidUsage_KK_Keyboard4andDollar 0x21 +#define HidUsage_KK_Keyboard5andPercent 0x22 +#define HidUsage_KK_Keyboard6andCaret 0x23 +#define HidUsage_KK_Keyboard7andAmpersand 0x24 +#define HidUsage_KK_Keyboard8andStar 0x25 +#define HidUsage_KK_Keyboard9andLeftBracket 0x26 +#define HidUsage_KK_Keyboard0andRightBracket 0x27 +#define HidUsage_KK_KeyboardReturnEnter 0x28 +#define HidUsage_KK_KeyboardEscape 0x29 +#define HidUsage_KK_KeyboardDelete 0x2a +#define HidUsage_KK_KeyboardTab 0x2b +#define HidUsage_KK_KeyboardSpacebar 0x2c +#define HidUsage_KK_KeyboardDashandUnderscore 0x2d +#define HidUsage_KK_KeyboardEqualsandPlus 0x2e +#define HidUsage_KK_KeyboardLeftBrace 0x2f +#define HidUsage_KK_KeyboardRightBrace 0x30 +#define HidUsage_KK_KeyboardBackslashandPipe 0x31 +#define HidUsage_KK_KeyboardNonUSHashandTilde 0x32 +#define HidUsage_KK_KeyboardSemiColonandColon 0x33 +#define HidUsage_KK_KeyboardLeftAposandDouble 0x34 +#define HidUsage_KK_KeyboardGraveAccentandTilde 0x35 +#define HidUsage_KK_KeyboardCommaandLessThan 0x36 +#define HidUsage_KK_KeyboardPeriodandGreaterThan 0x37 +#define HidUsage_KK_KeyboardForwardSlashandQuestionMark 0x38 +#define HidUsage_KK_KeyboardCapsLock 0x39 +#define HidUsage_KK_KeyboardF1 0x3a +#define HidUsage_KK_KeyboardF2 0x3b +#define HidUsage_KK_KeyboardF3 0x3c +#define HidUsage_KK_KeyboardF4 0x3d +#define HidUsage_KK_KeyboardF5 0x3e +#define HidUsage_KK_KeyboardF6 0x3f +#define HidUsage_KK_KeyboardF7 0x40 +#define HidUsage_KK_KeyboardF8 0x41 +#define HidUsage_KK_KeyboardF9 0x42 +#define HidUsage_KK_KeyboardF10 0x43 +#define HidUsage_KK_KeyboardF11 0x44 +#define HidUsage_KK_KeyboardF12 0x45 +#define HidUsage_KK_KeyboardPrintScreen 0x46 +#define HidUsage_KK_KeyboardScrollLock 0x47 +#define HidUsage_KK_KeyboardPause 0x48 +#define HidUsage_KK_KeyboardInsert 0x49 +#define HidUsage_KK_KeyboardHome 0x4a +#define HidUsage_KK_KeyboardPageUp 0x4b +#define HidUsage_KK_KeyboardDeleteForward 0x4c +#define HidUsage_KK_KeyboardEnd 0x4d +#define HidUsage_KK_KeyboardPageDown 0x4e +#define HidUsage_KK_KeyboardRightArrow 0x4f +#define HidUsage_KK_KeyboardLeftArrow 0x50 +#define HidUsage_KK_KeyboardDownArrow 0x51 +#define HidUsage_KK_KeyboardUpArrow 0x52 +#define HidUsage_KK_KeypadNumLockandClear 0x53 +#define HidUsage_KK_KeypadForwardSlash 0x54 +#define HidUsage_KK_KeypadStar 0x55 +#define HidUsage_KK_KeypadDash 0x56 +#define HidUsage_KK_KeypadPlus 0x57 +#define HidUsage_KK_KeypadENTER 0x58 +#define HidUsage_KK_Keypad1andEnd 0x59 +#define HidUsage_KK_Keypad2andDownArrow 0x5a +#define HidUsage_KK_Keypad3andPageDn 0x5b +#define HidUsage_KK_Keypad4andLeftArrow 0x5c +#define HidUsage_KK_Keypad5 0x5d +#define HidUsage_KK_Keypad6andRightArrow 0x5e +#define HidUsage_KK_Keypad7andHome 0x5f +#define HidUsage_KK_Keypad8andUpArrow 0x60 +#define HidUsage_KK_Keypad9andPageUp 0x61 +#define HidUsage_KK_Keypad0andInsert 0x62 +#define HidUsage_KK_KeypadPeriodandDelete 0x63 +#define HidUsage_KK_KeyboardNonUSBackslashandPipe 0x64 +#define HidUsage_KK_KeyboardApplication 0x65 +#define HidUsage_KK_KeyboardPower 0x66 +#define HidUsage_KK_KeypadEquals 0x67 +#define HidUsage_KK_KeyboardF13 0x68 +#define HidUsage_KK_KeyboardF14 0x69 +#define HidUsage_KK_KeyboardF15 0x6a +#define HidUsage_KK_KeyboardF16 0x6b +#define HidUsage_KK_KeyboardF17 0x6c +#define HidUsage_KK_KeyboardF18 0x6d +#define HidUsage_KK_KeyboardF19 0x6e +#define HidUsage_KK_KeyboardF20 0x6f +#define HidUsage_KK_KeyboardF21 0x70 +#define HidUsage_KK_KeyboardF22 0x71 +#define HidUsage_KK_KeyboardF23 0x72 +#define HidUsage_KK_KeyboardF24 0x73 +#define HidUsage_KK_KeyboardExecute 0x74 +#define HidUsage_KK_KeyboardHelp 0x75 +#define HidUsage_KK_KeyboardMenu 0x76 +#define HidUsage_KK_KeyboardSelect 0x77 +#define HidUsage_KK_KeyboardStop 0x78 +#define HidUsage_KK_KeyboardAgain 0x79 +#define HidUsage_KK_KeyboardUndo 0x7a +#define HidUsage_KK_KeyboardCut 0x7b +#define HidUsage_KK_KeyboardCopy 0x7c +#define HidUsage_KK_KeyboardPaste 0x7d +#define HidUsage_KK_KeyboardFind 0x7e +#define HidUsage_KK_KeyboardMute 0x7f +#define HidUsage_KK_KeyboardVolumeUp 0x80 +#define HidUsage_KK_KeyboardVolumeDown 0x81 +#define HidUsage_KK_KeyboardLockingCapsLock 0x82 +#define HidUsage_KK_KeyboardLockingNumLock 0x83 +#define HidUsage_KK_KeyboardLockingScrollLock 0x84 +#define HidUsage_KK_KeypadComma 0x85 +#define HidUsage_KK_KeypadEqualSign 0x86 +#define HidUsage_KK_KeyboardInternational1 0x87 +#define HidUsage_KK_KeyboardInternational2 0x88 +#define HidUsage_KK_KeyboardInternational3 0x89 +#define HidUsage_KK_KeyboardInternational4 0x8a +#define HidUsage_KK_KeyboardInternational5 0x8b +#define HidUsage_KK_KeyboardInternational6 0x8c +#define HidUsage_KK_KeyboardInternational7 0x8d +#define HidUsage_KK_KeyboardInternational8 0x8e +#define HidUsage_KK_KeyboardInternational9 0x8f +#define HidUsage_KK_KeyboardLANG1 0x90 +#define HidUsage_KK_KeyboardLANG2 0x91 +#define HidUsage_KK_KeyboardLANG3 0x92 +#define HidUsage_KK_KeyboardLANG4 0x93 +#define HidUsage_KK_KeyboardLANG5 0x94 +#define HidUsage_KK_KeyboardLANG6 0x95 +#define HidUsage_KK_KeyboardLANG7 0x96 +#define HidUsage_KK_KeyboardLANG8 0x97 +#define HidUsage_KK_KeyboardLANG9 0x98 +#define HidUsage_KK_KeyboardAlternateErase 0x99 +#define HidUsage_KK_KeyboardSysReqAttention 0x9a +#define HidUsage_KK_KeyboardCancel 0x9b +#define HidUsage_KK_KeyboardClear 0x9c +#define HidUsage_KK_KeyboardPrior 0x9d +#define HidUsage_KK_KeyboardReturn 0x9e +#define HidUsage_KK_KeyboardSeparator 0x9f +#define HidUsage_KK_KeyboardOut 0xa0 +#define HidUsage_KK_KeyboardOper 0xa1 +#define HidUsage_KK_KeyboardClearAgain 0xa2 +#define HidUsage_KK_KeyboardCrSelProps 0xa3 +#define HidUsage_KK_KeyboardExSel 0xa4 +#define HidUsage_KK_KeypadDouble0 0xb0 +#define HidUsage_KK_KeypadTriple0 0xb1 +#define HidUsage_KK_ThousandsSeparator 0xb2 +#define HidUsage_KK_DecimalSeparator 0xb3 +#define HidUsage_KK_CurrencyUnit 0xb4 +#define HidUsage_KK_CurrencySubunit 0xb5 +#define HidUsage_KK_KeypadLeftBracket 0xb6 +#define HidUsage_KK_KeypadRightBracket 0xb7 +#define HidUsage_KK_KeypadLeftBrace 0xb8 +#define HidUsage_KK_KeypadRightBrace 0xb9 +#define HidUsage_KK_KeypadTab 0xba +#define HidUsage_KK_KeypadBackspace 0xbb +#define HidUsage_KK_KeypadA 0xbc +#define HidUsage_KK_KeypadB 0xbd +#define HidUsage_KK_KeypadC 0xbe +#define HidUsage_KK_KeypadD 0xbf +#define HidUsage_KK_KeypadE 0xc0 +#define HidUsage_KK_KeypadF 0xc1 +#define HidUsage_KK_KeypadXOR 0xc2 +#define HidUsage_KK_KeypadCaret 0xc3 +#define HidUsage_KK_KeypadPercentage 0xc4 +#define HidUsage_KK_KeypadLess 0xc5 +#define HidUsage_KK_KeypadGreater 0xc6 +#define HidUsage_KK_KeypadAmpersand 0xc7 +#define HidUsage_KK_KeypadDoubleAmpersand 0xc8 +#define HidUsage_KK_KeypadBar 0xc9 +#define HidUsage_KK_KeypadDoubleBar 0xca +#define HidUsage_KK_KeypadColon 0xcb +#define HidUsage_KK_KeypadHash 0xcc +#define HidUsage_KK_KeypadSpace 0xcd +#define HidUsage_KK_KeypadAt 0xce +#define HidUsage_KK_KeypadBang 0xcf +#define HidUsage_KK_KeypadMemoryStore 0xd0 +#define HidUsage_KK_KeypadMemoryRecall 0xd1 +#define HidUsage_KK_KeypadMemoryClear 0xd2 +#define HidUsage_KK_KeypadMemoryAdd 0xd3 +#define HidUsage_KK_KeypadMemorySubtract 0xd4 +#define HidUsage_KK_KeypadMemoryMultiply 0xd5 +#define HidUsage_KK_KeypadMemoryDivide 0xd6 +#define HidUsage_KK_KeypadPlusMinus 0xd7 +#define HidUsage_KK_KeypadClear 0xd8 +#define HidUsage_KK_KeypadClearEntry 0xd9 +#define HidUsage_KK_KeypadBinary 0xda +#define HidUsage_KK_KeypadOctal 0xdb +#define HidUsage_KK_KeypadDecimal 0xdc +#define HidUsage_KK_KeypadHexadecimal 0xdd +#define HidUsage_KK_KeyboardLeftControl 0xe0 +#define HidUsage_KK_KeyboardLeftShift 0xe1 +#define HidUsage_KK_KeyboardLeftAlt 0xe2 +#define HidUsage_KK_KeyboardLeftGUI 0xe3 +#define HidUsage_KK_KeyboardRightControl 0xe4 +#define HidUsage_KK_KeyboardRightShift 0xe5 +#define HidUsage_KK_KeyboardRightAlt 0xe6 +#define HidUsage_KK_KeyboardRightGUI 0xe7 +#define HidUsage_LED_NumLock 0x01 +#define HidUsage_LED_CapsLock 0x02 +#define HidUsage_LED_ScrollLock 0x03 +#define HidUsage_LED_Compose 0x04 +#define HidUsage_LED_Kana 0x05 +#define HidUsage_LED_Power 0x06 +#define HidUsage_LED_Shift 0x07 +#define HidUsage_LED_DoNotDisturb 0x08 +#define HidUsage_LED_Mute 0x09 +#define HidUsage_LED_ToneEnable 0x0a +#define HidUsage_LED_HighCutFilter 0x0b +#define HidUsage_LED_LowCutFilter 0x0c +#define HidUsage_LED_EqualizerEnable 0x0d +#define HidUsage_LED_SoundFieldOn 0x0e +#define HidUsage_LED_SurroundOn 0x0f +#define HidUsage_LED_Repeat 0x10 +#define HidUsage_LED_Stereo 0x11 +#define HidUsage_LED_SamplingRateDetect 0x12 +#define HidUsage_LED_Spinning 0x13 +#define HidUsage_LED_CAV 0x14 +#define HidUsage_LED_CLV 0x15 +#define HidUsage_LED_RecordingFormatDetect 0x16 +#define HidUsage_LED_OffHook 0x17 +#define HidUsage_LED_Ring 0x18 +#define HidUsage_LED_MessageWaiting 0x19 +#define HidUsage_LED_DataMode 0x1a +#define HidUsage_LED_BatteryOperation 0x1b +#define HidUsage_LED_BatteryOK 0x1c +#define HidUsage_LED_BatteryLow 0x1d +#define HidUsage_LED_Speaker 0x1e +#define HidUsage_LED_Headset 0x1f +#define HidUsage_LED_Hold 0x20 +#define HidUsage_LED_Microphone 0x21 +#define HidUsage_LED_Coverage 0x22 +#define HidUsage_LED_NightMode 0x23 +#define HidUsage_LED_SendCalls 0x24 +#define HidUsage_LED_CallPickup 0x25 +#define HidUsage_LED_Conference 0x26 +#define HidUsage_LED_Standby 0x27 +#define HidUsage_LED_CameraOn 0x28 +#define HidUsage_LED_CameraOff 0x29 +#define HidUsage_LED_OnLine 0x2a +#define HidUsage_LED_OffLine 0x2b +#define HidUsage_LED_Busy 0x2c +#define HidUsage_LED_Ready 0x2d +#define HidUsage_LED_PaperOut 0x2e +#define HidUsage_LED_PaperJam 0x2f +#define HidUsage_LED_Remote 0x30 +#define HidUsage_LED_Forward 0x31 +#define HidUsage_LED_Reverse 0x32 +#define HidUsage_LED_Stop 0x33 +#define HidUsage_LED_Rewind 0x34 +#define HidUsage_LED_FastForward 0x35 +#define HidUsage_LED_Play 0x36 +#define HidUsage_LED_Pause 0x37 +#define HidUsage_LED_Record 0x38 +#define HidUsage_LED_Error 0x39 +#define HidUsage_LED_UsageSelectedIndicator 0x3a +#define HidUsage_LED_UsageInUseIndicator 0x3b +#define HidUsage_LED_UsageMultiModeIndicator 0x3c +#define HidUsage_LED_IndicatorOn 0x3d +#define HidUsage_LED_IndicatorFlash 0x3e +#define HidUsage_LED_IndicatorSlowBlink 0x3f +#define HidUsage_LED_IndicatorFastBlink 0x40 +#define HidUsage_LED_IndicatorOff 0x41 +#define HidUsage_LED_FlashOnTime 0x42 +#define HidUsage_LED_SlowBlinkOnTime 0x43 +#define HidUsage_LED_SlowBlinkOffTime 0x44 +#define HidUsage_LED_FastBlinkOnTime 0x45 +#define HidUsage_LED_FastBlinkOffTime 0x46 +#define HidUsage_LED_UsageIndicatorColor 0x47 +#define HidUsage_LED_IndicatorRed 0x48 +#define HidUsage_LED_IndicatorGreen 0x49 +#define HidUsage_LED_IndicatorAmber 0x4a +#define HidUsage_LED_GenericIndicator 0x4b +#define HidUsage_LED_SystemSuspend 0x4c +#define HidUsage_LED_ExternalPowerConnected 0x4d +#define HidUsage_LED_IndicatorBlue 0x4e +#define HidUsage_LED_IndicatorOrange 0x4f +#define HidUsage_LED_GoodStatus 0x50 +#define HidUsage_LED_WarningStatus 0x51 +#define HidUsage_LED_RGBLED 0x52 +#define HidUsage_LED_RedLEDChannel 0x53 +#define HidUsage_LED_BlueLEDChannel 0x54 +#define HidUsage_LED_GreenLEDChannel 0x55 +#define HidUsage_LED_LEDIntensity 0x56 +#define HidUsage_LED_SystemMicrophoneMute 0x57 +#define HidUsage_LED_PlayerIndicator 0x60 +#define HidUsage_LED_Player1 0x61 +#define HidUsage_LED_Player2 0x62 +#define HidUsage_LED_Player3 0x63 +#define HidUsage_LED_Player4 0x64 +#define HidUsage_LED_Player5 0x65 +#define HidUsage_LED_Player6 0x66 +#define HidUsage_LED_Player7 0x67 +#define HidUsage_LED_Player8 0x68 +#define HidUsage_TD_Phone 0x01 +#define HidUsage_TD_AnsweringMachine 0x02 +#define HidUsage_TD_MessageControls 0x03 +#define HidUsage_TD_Handset 0x04 +#define HidUsage_TD_Headset 0x05 +#define HidUsage_TD_TelephonyKeyPad 0x06 +#define HidUsage_TD_ProgrammableButton 0x07 +#define HidUsage_TD_HookSwitch 0x20 +#define HidUsage_TD_Flash 0x21 +#define HidUsage_TD_Feature 0x22 +#define HidUsage_TD_Hold 0x23 +#define HidUsage_TD_Redial 0x24 +#define HidUsage_TD_Transfer 0x25 +#define HidUsage_TD_Drop 0x26 +#define HidUsage_TD_Park 0x27 +#define HidUsage_TD_ForwardCalls 0x28 +#define HidUsage_TD_AlternateFunction 0x29 +#define HidUsage_TD_Line 0x2a +#define HidUsage_TD_SpeakerPhone 0x2b +#define HidUsage_TD_Conference 0x2c +#define HidUsage_TD_RingEnable 0x2d +#define HidUsage_TD_RingSelect 0x2e +#define HidUsage_TD_PhoneMute 0x2f +#define HidUsage_TD_CallerID 0x30 +#define HidUsage_TD_Send 0x31 +#define HidUsage_TD_SpeedDial 0x50 +#define HidUsage_TD_StoreNumber 0x51 +#define HidUsage_TD_RecallNumber 0x52 +#define HidUsage_TD_PhoneDirectory 0x53 +#define HidUsage_TD_VoiceMail 0x70 +#define HidUsage_TD_ScreenCalls 0x71 +#define HidUsage_TD_DoNotDisturb 0x72 +#define HidUsage_TD_Message 0x73 +#define HidUsage_TD_AnswerOnOff 0x74 +#define HidUsage_TD_InsideDialTone 0x90 +#define HidUsage_TD_OutsideDialTone 0x91 +#define HidUsage_TD_InsideRingTone 0x92 +#define HidUsage_TD_OutsideRingTone 0x93 +#define HidUsage_TD_PriorityRingTone 0x94 +#define HidUsage_TD_InsideRingback 0x95 +#define HidUsage_TD_PriorityRingback 0x96 +#define HidUsage_TD_LineBusyTone 0x97 +#define HidUsage_TD_ReorderTone 0x98 +#define HidUsage_TD_CallWaitingTone 0x99 +#define HidUsage_TD_ConfirmationTone1 0x9a +#define HidUsage_TD_ConfirmationTone2 0x9b +#define HidUsage_TD_TonesOff 0x9c +#define HidUsage_TD_OutsideRingback 0x9d +#define HidUsage_TD_Ringer 0x9e +#define HidUsage_TD_PhoneKey0 0xb0 +#define HidUsage_TD_PhoneKey1 0xb1 +#define HidUsage_TD_PhoneKey2 0xb2 +#define HidUsage_TD_PhoneKey3 0xb3 +#define HidUsage_TD_PhoneKey4 0xb4 +#define HidUsage_TD_PhoneKey5 0xb5 +#define HidUsage_TD_PhoneKey6 0xb6 +#define HidUsage_TD_PhoneKey7 0xb7 +#define HidUsage_TD_PhoneKey8 0xb8 +#define HidUsage_TD_PhoneKey9 0xb9 +#define HidUsage_TD_PhoneKeyStar 0xba +#define HidUsage_TD_PhoneKeyPound 0xbb +#define HidUsage_TD_PhoneKeyA 0xbc +#define HidUsage_TD_PhoneKeyB 0xbd +#define HidUsage_TD_PhoneKeyC 0xbe +#define HidUsage_TD_PhoneKeyD 0xbf +#define HidUsage_TD_PhoneCallHistoryKey 0xc0 +#define HidUsage_TD_PhoneCallerIDKey 0xc1 +#define HidUsage_TD_PhoneSettingsKey 0xc2 +#define HidUsage_TD_HostControl 0xf0 +#define HidUsage_TD_HostAvailable 0xf1 +#define HidUsage_TD_HostCallActive 0xf2 +#define HidUsage_TD_ActivateHandsetAudio 0xf3 +#define HidUsage_TD_RingType 0xf4 +#define HidUsage_TD_RedialablePhoneNumber 0xf5 +#define HidUsage_TD_StopRingTone 0xf8 +#define HidUsage_TD_PSTNRingTone 0xf9 +#define HidUsage_TD_HostRingTone 0xfa +#define HidUsage_TD_AlertSoundError 0xfb +#define HidUsage_TD_AlertSoundConfirm 0xfc +#define HidUsage_TD_AlertSoundNotification 0xfd +#define HidUsage_TD_SilentRing 0xfe +#define HidUsage_TD_EmailMessageWaiting 0x108 +#define HidUsage_TD_VoicemailMessageWaiting 0x109 +#define HidUsage_TD_HostHold 0x10a +#define HidUsage_TD_IncomingCallHistoryCount 0x110 +#define HidUsage_TD_OutgoingCallHistoryCount 0x111 +#define HidUsage_TD_IncomingCallHistory 0x112 +#define HidUsage_TD_OutgoingCallHistory 0x113 +#define HidUsage_TD_PhoneLocale 0x114 +#define HidUsage_TD_PhoneTimeSecond 0x140 +#define HidUsage_TD_PhoneTimeMinute 0x141 +#define HidUsage_TD_PhoneTimeHour 0x142 +#define HidUsage_TD_PhoneDateDay 0x143 +#define HidUsage_TD_PhoneDateMonth 0x144 +#define HidUsage_TD_PhoneDateYear 0x145 +#define HidUsage_TD_HandsetNickname 0x146 +#define HidUsage_TD_AddressBookID 0x147 +#define HidUsage_TD_CallDuration 0x14a +#define HidUsage_TD_DualModePhone 0x14b +#define HidUsage_Con_ConsumerControl 0x01 +#define HidUsage_Con_NumericKeyPad 0x02 +#define HidUsage_Con_ProgrammableButtons 0x03 +#define HidUsage_Con_Microphone 0x04 +#define HidUsage_Con_Headphone 0x05 +#define HidUsage_Con_GraphicEqualizer 0x06 +#define HidUsage_Con_Plus10 0x20 +#define HidUsage_Con_Plus100 0x21 +#define HidUsage_Con_AMPM 0x22 +#define HidUsage_Con_Power 0x30 +#define HidUsage_Con_Reset 0x31 +#define HidUsage_Con_Sleep 0x32 +#define HidUsage_Con_SleepAfter 0x33 +#define HidUsage_Con_SleepMode 0x34 +#define HidUsage_Con_Illumination 0x35 +#define HidUsage_Con_FunctionButtons 0x36 +#define HidUsage_Con_Menu 0x40 +#define HidUsage_Con_MenuPick 0x41 +#define HidUsage_Con_MenuUp 0x42 +#define HidUsage_Con_MenuDown 0x43 +#define HidUsage_Con_MenuLeft 0x44 +#define HidUsage_Con_MenuRight 0x45 +#define HidUsage_Con_MenuEscape 0x46 +#define HidUsage_Con_MenuValueIncrease 0x47 +#define HidUsage_Con_MenuValueDecrease 0x48 +#define HidUsage_Con_DataOnScreen 0x60 +#define HidUsage_Con_ClosedCaption 0x61 +#define HidUsage_Con_ClosedCaptionSelect 0x62 +#define HidUsage_Con_VCRTV 0x63 +#define HidUsage_Con_BroadcastMode 0x64 +#define HidUsage_Con_Snapshot 0x65 +#define HidUsage_Con_Still 0x66 +#define HidUsage_Con_PictureinPictureToggle 0x67 +#define HidUsage_Con_PictureinPictureSwap 0x68 +#define HidUsage_Con_RedMenuButton 0x69 +#define HidUsage_Con_GreenMenuButton 0x6a +#define HidUsage_Con_BlueMenuButton 0x6b +#define HidUsage_Con_YellowMenuButton 0x6c +#define HidUsage_Con_Aspect 0x6d +#define HidUsage_Con_ThreeDModeSelect 0x6e +#define HidUsage_Con_DisplayBrightnessIncrement 0x6f +#define HidUsage_Con_DisplayBrightnessDecrement 0x70 +#define HidUsage_Con_DisplayBrightness 0x71 +#define HidUsage_Con_DisplayBacklightToggle 0x72 +#define HidUsage_Con_DisplaySetBrightnesstoMinimum 0x73 +#define HidUsage_Con_DisplaySetBrightnesstoMaximum 0x74 +#define HidUsage_Con_DisplaySetAutoBrightness 0x75 +#define HidUsage_Con_CameraAccessEnabled 0x76 +#define HidUsage_Con_CameraAccessDisabled 0x77 +#define HidUsage_Con_CameraAccessToggle 0x78 +#define HidUsage_Con_KeyboardBrightnessIncrement 0x79 +#define HidUsage_Con_KeyboardBrightnessDecrement 0x7a +#define HidUsage_Con_KeyboardBacklightSetLevel 0x7b +#define HidUsage_Con_KeyboardBacklightOOC 0x7c +#define HidUsage_Con_KeyboardBacklightSetMinimum 0x7d +#define HidUsage_Con_KeyboardBacklightSetMaximum 0x7e +#define HidUsage_Con_KeyboardBacklightAuto 0x7f +#define HidUsage_Con_Selection 0x80 +#define HidUsage_Con_AssignSelection 0x81 +#define HidUsage_Con_ModeStep 0x82 +#define HidUsage_Con_RecallLast 0x83 +#define HidUsage_Con_EnterChannel 0x84 +#define HidUsage_Con_OrderMovie 0x85 +#define HidUsage_Con_Channel 0x86 +#define HidUsage_Con_MediaSelection 0x87 +#define HidUsage_Con_MediaSelectComputer 0x88 +#define HidUsage_Con_MediaSelectTV 0x89 +#define HidUsage_Con_MediaSelectWWW 0x8a +#define HidUsage_Con_MediaSelectDVD 0x8b +#define HidUsage_Con_MediaSelectTelephone 0x8c +#define HidUsage_Con_MediaSelectProgramGuide 0x8d +#define HidUsage_Con_MediaSelectVideoPhone 0x8e +#define HidUsage_Con_MediaSelectGames 0x8f +#define HidUsage_Con_MediaSelectMessages 0x90 +#define HidUsage_Con_MediaSelectCD 0x91 +#define HidUsage_Con_MediaSelectVCR 0x92 +#define HidUsage_Con_MediaSelectTuner 0x93 +#define HidUsage_Con_Quit 0x94 +#define HidUsage_Con_Help 0x95 +#define HidUsage_Con_MediaSelectTape 0x96 +#define HidUsage_Con_MediaSelectCable 0x97 +#define HidUsage_Con_MediaSelectSatellite 0x98 +#define HidUsage_Con_MediaSelectSecurity 0x99 +#define HidUsage_Con_MediaSelectHome 0x9a +#define HidUsage_Con_MediaSelectCall 0x9b +#define HidUsage_Con_ChannelIncrement 0x9c +#define HidUsage_Con_ChannelDecrement 0x9d +#define HidUsage_Con_MediaSelectSAP 0x9e +#define HidUsage_Con_VCRPlus 0xa0 +#define HidUsage_Con_Once 0xa1 +#define HidUsage_Con_Daily 0xa2 +#define HidUsage_Con_Weekly 0xa3 +#define HidUsage_Con_Monthly 0xa4 +#define HidUsage_Con_Play 0xb0 +#define HidUsage_Con_Pause 0xb1 +#define HidUsage_Con_Record 0xb2 +#define HidUsage_Con_FastForward 0xb3 +#define HidUsage_Con_Rewind 0xb4 +#define HidUsage_Con_ScanNextTrack 0xb5 +#define HidUsage_Con_ScanPreviousTrack 0xb6 +#define HidUsage_Con_Stop 0xb7 +#define HidUsage_Con_Eject 0xb8 +#define HidUsage_Con_RandomPlay 0xb9 +#define HidUsage_Con_SelectDisc 0xba +#define HidUsage_Con_EnterDisc 0xbb +#define HidUsage_Con_Repeat 0xbc +#define HidUsage_Con_Tracking 0xbd +#define HidUsage_Con_TrackNormal 0xbe +#define HidUsage_Con_SlowTracking 0xbf +#define HidUsage_Con_FrameForward 0xc0 +#define HidUsage_Con_FrameBack 0xc1 +#define HidUsage_Con_Mark 0xc2 +#define HidUsage_Con_ClearMark 0xc3 +#define HidUsage_Con_RepeatFromMark 0xc4 +#define HidUsage_Con_ReturnToMark 0xc5 +#define HidUsage_Con_SearchMarkForward 0xc6 +#define HidUsage_Con_SearchMarkBackwards 0xc7 +#define HidUsage_Con_CounterReset 0xc8 +#define HidUsage_Con_ShowCounter 0xc9 +#define HidUsage_Con_TrackingIncrement 0xca +#define HidUsage_Con_TrackingDecrement 0xcb +#define HidUsage_Con_StopEject 0xcc +#define HidUsage_Con_PlayPause 0xcd +#define HidUsage_Con_PlaySkip 0xce +#define HidUsage_Con_VoiceCommand 0xcf +#define HidUsage_Con_InvokeCaptureInterface 0xd0 +#define HidUsage_Con_StartorStopGameRecording 0xd1 +#define HidUsage_Con_HistoricalGameCapture 0xd2 +#define HidUsage_Con_CaptureGameScreenshot 0xd3 +#define HidUsage_Con_ShoworHideRecordingIndicator 0xd4 +#define HidUsage_Con_StartorStopMicrophoneCapture 0xd5 +#define HidUsage_Con_StartorStopCameraCapture 0xd6 +#define HidUsage_Con_StartorStopGameBroadcast 0xd7 +#define HidUsage_Con_StartorStopVoiceDictationSession 0xd8 +#define HidUsage_Con_InvokeDismissEmojiPicker 0xd9 +#define HidUsage_Con_Volume 0xe0 +#define HidUsage_Con_Balance 0xe1 +#define HidUsage_Con_Mute 0xe2 +#define HidUsage_Con_Bass 0xe3 +#define HidUsage_Con_Treble 0xe4 +#define HidUsage_Con_BassBoost 0xe5 +#define HidUsage_Con_SurroundMode 0xe6 +#define HidUsage_Con_Loudness 0xe7 +#define HidUsage_Con_MPX 0xe8 +#define HidUsage_Con_VolumeIncrement 0xe9 +#define HidUsage_Con_VolumeDecrement 0xea +#define HidUsage_Con_SpeedSelect 0xf0 +#define HidUsage_Con_PlaybackSpeed 0xf1 +#define HidUsage_Con_StandardPlay 0xf2 +#define HidUsage_Con_LongPlay 0xf3 +#define HidUsage_Con_ExtendedPlay 0xf4 +#define HidUsage_Con_Slow 0xf5 +#define HidUsage_Con_FanEnable 0x100 +#define HidUsage_Con_FanSpeed 0x101 +#define HidUsage_Con_LightEnable 0x102 +#define HidUsage_Con_LightIlluminationLevel 0x103 +#define HidUsage_Con_ClimateControlEnable 0x104 +#define HidUsage_Con_RoomTemperature 0x105 +#define HidUsage_Con_SecurityEnable 0x106 +#define HidUsage_Con_FireAlarm 0x107 +#define HidUsage_Con_PoliceAlarm 0x108 +#define HidUsage_Con_Proximity 0x109 +#define HidUsage_Con_Motion 0x10a +#define HidUsage_Con_DuressAlarm 0x10b +#define HidUsage_Con_HoldupAlarm 0x10c +#define HidUsage_Con_MedicalAlarm 0x10d +#define HidUsage_Con_BalanceRight 0x150 +#define HidUsage_Con_BalanceLeft 0x151 +#define HidUsage_Con_BassIncrement 0x152 +#define HidUsage_Con_BassDecrement 0x153 +#define HidUsage_Con_TrebleIncrement 0x154 +#define HidUsage_Con_TrebleDecrement 0x155 +#define HidUsage_Con_SpeakerSystem 0x160 +#define HidUsage_Con_ChannelLeft 0x161 +#define HidUsage_Con_ChannelRight 0x162 +#define HidUsage_Con_ChannelCenter 0x163 +#define HidUsage_Con_ChannelFront 0x164 +#define HidUsage_Con_ChannelCenterFront 0x165 +#define HidUsage_Con_ChannelSide 0x166 +#define HidUsage_Con_ChannelSurround 0x167 +#define HidUsage_Con_ChannelLowFrequencyEnhancement 0x168 +#define HidUsage_Con_ChannelTop 0x169 +#define HidUsage_Con_ChannelUnknown 0x16a +#define HidUsage_Con_Subchannel 0x170 +#define HidUsage_Con_SubchannelIncrement 0x171 +#define HidUsage_Con_SubchannelDecrement 0x172 +#define HidUsage_Con_AlternateAudioIncrement 0x173 +#define HidUsage_Con_AlternateAudioDecrement 0x174 +#define HidUsage_Con_ApplicationLaunchButtons 0x180 +#define HidUsage_Con_ALLaunchButtonConfigurationTool 0x181 +#define HidUsage_Con_ALProgrammableButtonConfiguration 0x182 +#define HidUsage_Con_ALConsumerControlConfiguration 0x183 +#define HidUsage_Con_ALWordProcessor 0x184 +#define HidUsage_Con_ALTextEditor 0x185 +#define HidUsage_Con_ALSpreadsheet 0x186 +#define HidUsage_Con_ALGraphicsEditor 0x187 +#define HidUsage_Con_ALPresentationApp 0x188 +#define HidUsage_Con_ALDatabaseApp 0x189 +#define HidUsage_Con_ALEmailReader 0x18a +#define HidUsage_Con_ALNewsreader 0x18b +#define HidUsage_Con_ALVoicemail 0x18c +#define HidUsage_Con_ALContactsAddressBook 0x18d +#define HidUsage_Con_ALCalendarSchedule 0x18e +#define HidUsage_Con_ALTaskProjectManager 0x18f +#define HidUsage_Con_ALLogJournalTimecard 0x190 +#define HidUsage_Con_ALCheckbookFinance 0x191 +#define HidUsage_Con_ALCalculator 0x192 +#define HidUsage_Con_ALAVCapturePlayback 0x193 +#define HidUsage_Con_ALLocalMachineBrowser 0x194 +#define HidUsage_Con_ALLANWANBrowser 0x195 +#define HidUsage_Con_ALInternetBrowser 0x196 +#define HidUsage_Con_ALRemoteNetworkingISPConnect 0x197 +#define HidUsage_Con_ALNetworkConference 0x198 +#define HidUsage_Con_ALNetworkChat 0x199 +#define HidUsage_Con_ALTelephonyDialer 0x19a +#define HidUsage_Con_ALLogon 0x19b +#define HidUsage_Con_ALLogoff 0x19c +#define HidUsage_Con_ALLogonLogoff 0x19d +#define HidUsage_Con_ALTerminalLockScreensaver 0x19e +#define HidUsage_Con_ALControlPanel 0x19f +#define HidUsage_Con_ALCommandLineProcessorRun 0x1a0 +#define HidUsage_Con_ALProcessTaskManager 0x1a1 +#define HidUsage_Con_ALSelectTaskApplication 0x1a2 +#define HidUsage_Con_ALNextTaskApplication 0x1a3 +#define HidUsage_Con_ALPreviousTaskApplication 0x1a4 +#define HidUsage_Con_ALPreemptiveHaltTaskApplication 0x1a5 +#define HidUsage_Con_ALIntegratedHelpCenter 0x1a6 +#define HidUsage_Con_ALDocuments 0x1a7 +#define HidUsage_Con_ALThesaurus 0x1a8 +#define HidUsage_Con_ALDictionary 0x1a9 +#define HidUsage_Con_ALDesktop 0x1aa +#define HidUsage_Con_ALSpellCheck 0x1ab +#define HidUsage_Con_ALGrammarCheck 0x1ac +#define HidUsage_Con_ALWirelessStatus 0x1ad +#define HidUsage_Con_ALKeyboardLayout 0x1ae +#define HidUsage_Con_ALVirusProtection 0x1af +#define HidUsage_Con_ALEncryption 0x1b0 +#define HidUsage_Con_ALScreenSaver 0x1b1 +#define HidUsage_Con_ALAlarms 0x1b2 +#define HidUsage_Con_ALClock 0x1b3 +#define HidUsage_Con_ALFileBrowser 0x1b4 +#define HidUsage_Con_ALPowerStatus 0x1b5 +#define HidUsage_Con_ALImageBrowser 0x1b6 +#define HidUsage_Con_ALAudioBrowser 0x1b7 +#define HidUsage_Con_ALMovieBrowser 0x1b8 +#define HidUsage_Con_ALDigitalRightsManager 0x1b9 +#define HidUsage_Con_ALDigitalWallet 0x1ba +#define HidUsage_Con_ALInstantMessaging 0x1bc +#define HidUsage_Con_ALOEMFeaturesTipsTutorialBrowser 0x1bd +#define HidUsage_Con_ALOEMHelp 0x1be +#define HidUsage_Con_ALOnlineCommunity 0x1bf +#define HidUsage_Con_ALEntertainmentContentBrowser 0x1c0 +#define HidUsage_Con_ALOnlineShoppingBrowser 0x1c1 +#define HidUsage_Con_ALSmartCardInformationHelp 0x1c2 +#define HidUsage_Con_ALMarketMonitorFinanceBrowser 0x1c3 +#define HidUsage_Con_ALCustomizedCorporateNewsBrowser 0x1c4 +#define HidUsage_Con_ALOnlineActivityBrowser 0x1c5 +#define HidUsage_Con_ALResearchSearchBrowser 0x1c6 +#define HidUsage_Con_ALAudioPlayer 0x1c7 +#define HidUsage_Con_ALMessageStatus 0x1c8 +#define HidUsage_Con_ALContactSync 0x1c9 +#define HidUsage_Con_ALNavigation 0x1ca +#define HidUsage_Con_ALContextawareDesktopAssistant 0x1cb +#define HidUsage_Con_GenericGUIApplicationControls 0x200 +#define HidUsage_Con_ACNew 0x201 +#define HidUsage_Con_ACOpen 0x202 +#define HidUsage_Con_ACClose 0x203 +#define HidUsage_Con_ACExit 0x204 +#define HidUsage_Con_ACMaximize 0x205 +#define HidUsage_Con_ACMinimize 0x206 +#define HidUsage_Con_ACSave 0x207 +#define HidUsage_Con_ACPrint 0x208 +#define HidUsage_Con_ACProperties 0x209 +#define HidUsage_Con_ACUndo 0x21a +#define HidUsage_Con_ACCopy 0x21b +#define HidUsage_Con_ACCut 0x21c +#define HidUsage_Con_ACPaste 0x21d +#define HidUsage_Con_ACSelectAll 0x21e +#define HidUsage_Con_ACFind 0x21f +#define HidUsage_Con_ACFindandReplace 0x220 +#define HidUsage_Con_ACSearch 0x221 +#define HidUsage_Con_ACGoTo 0x222 +#define HidUsage_Con_ACHome 0x223 +#define HidUsage_Con_ACBack 0x224 +#define HidUsage_Con_ACForward 0x225 +#define HidUsage_Con_ACStop 0x226 +#define HidUsage_Con_ACRefresh 0x227 +#define HidUsage_Con_ACPreviousLink 0x228 +#define HidUsage_Con_ACNextLink 0x229 +#define HidUsage_Con_ACBookmarks 0x22a +#define HidUsage_Con_ACHistory 0x22b +#define HidUsage_Con_ACSubscriptions 0x22c +#define HidUsage_Con_ACZoomIn 0x22d +#define HidUsage_Con_ACZoomOut 0x22e +#define HidUsage_Con_ACZoom 0x22f +#define HidUsage_Con_ACFullScreenView 0x230 +#define HidUsage_Con_ACNormalView 0x231 +#define HidUsage_Con_ACViewToggle 0x232 +#define HidUsage_Con_ACScrollUp 0x233 +#define HidUsage_Con_ACScrollDown 0x234 +#define HidUsage_Con_ACScroll 0x235 +#define HidUsage_Con_ACPanLeft 0x236 +#define HidUsage_Con_ACPanRight 0x237 +#define HidUsage_Con_ACPan 0x238 +#define HidUsage_Con_ACNewWindow 0x239 +#define HidUsage_Con_ACTileHorizontally 0x23a +#define HidUsage_Con_ACTileVertically 0x23b +#define HidUsage_Con_ACFormat 0x23c +#define HidUsage_Con_ACEdit 0x23d +#define HidUsage_Con_ACBold 0x23e +#define HidUsage_Con_ACItalics 0x23f +#define HidUsage_Con_ACUnderline 0x240 +#define HidUsage_Con_ACStrikethrough 0x241 +#define HidUsage_Con_ACSubscript 0x242 +#define HidUsage_Con_ACSuperscript 0x243 +#define HidUsage_Con_ACAllCaps 0x244 +#define HidUsage_Con_ACRotate 0x245 +#define HidUsage_Con_ACResize 0x246 +#define HidUsage_Con_ACFlipHorizontal 0x247 +#define HidUsage_Con_ACFlipVertical 0x248 +#define HidUsage_Con_ACMirrorHorizontal 0x249 +#define HidUsage_Con_ACMirrorVertical 0x24a +#define HidUsage_Con_ACFontSelect 0x24b +#define HidUsage_Con_ACFontColor 0x24c +#define HidUsage_Con_ACFontSize 0x24d +#define HidUsage_Con_ACJustifyLeft 0x24e +#define HidUsage_Con_ACJustifyCenterH 0x24f +#define HidUsage_Con_ACJustifyRight 0x250 +#define HidUsage_Con_ACJustifyBlockH 0x251 +#define HidUsage_Con_ACJustifyTop 0x252 +#define HidUsage_Con_ACJustifyCenterV 0x253 +#define HidUsage_Con_ACJustifyBottom 0x254 +#define HidUsage_Con_ACJustifyBlockV 0x255 +#define HidUsage_Con_ACIndentDecrease 0x256 +#define HidUsage_Con_ACIndentIncrease 0x257 +#define HidUsage_Con_ACNumberedList 0x258 +#define HidUsage_Con_ACRestartNumbering 0x259 +#define HidUsage_Con_ACBulletedList 0x25a +#define HidUsage_Con_ACPromote 0x25b +#define HidUsage_Con_ACDemote 0x25c +#define HidUsage_Con_ACYes 0x25d +#define HidUsage_Con_ACNo 0x25e +#define HidUsage_Con_ACCancel 0x25f +#define HidUsage_Con_ACCatalog 0x260 +#define HidUsage_Con_ACBuyCheckout 0x261 +#define HidUsage_Con_ACAddtoCart 0x262 +#define HidUsage_Con_ACExpand 0x263 +#define HidUsage_Con_ACExpandAll 0x264 +#define HidUsage_Con_ACCollapse 0x265 +#define HidUsage_Con_ACCollapseAll 0x266 +#define HidUsage_Con_ACPrintPreview 0x267 +#define HidUsage_Con_ACPasteSpecial 0x268 +#define HidUsage_Con_ACInsertMode 0x269 +#define HidUsage_Con_ACDelete 0x26a +#define HidUsage_Con_ACLock 0x26b +#define HidUsage_Con_ACUnlock 0x26c +#define HidUsage_Con_ACProtect 0x26d +#define HidUsage_Con_ACUnprotect 0x26e +#define HidUsage_Con_ACAttachComment 0x26f +#define HidUsage_Con_ACDeleteComment 0x270 +#define HidUsage_Con_ACViewComment 0x271 +#define HidUsage_Con_ACSelectWord 0x272 +#define HidUsage_Con_ACSelectSentence 0x273 +#define HidUsage_Con_ACSelectParagraph 0x274 +#define HidUsage_Con_ACSelectColumn 0x275 +#define HidUsage_Con_ACSelectRow 0x276 +#define HidUsage_Con_ACSelectTable 0x277 +#define HidUsage_Con_ACSelectObject 0x278 +#define HidUsage_Con_ACRedoRepeat 0x279 +#define HidUsage_Con_ACSort 0x27a +#define HidUsage_Con_ACSortAscending 0x27b +#define HidUsage_Con_ACSortDescending 0x27c +#define HidUsage_Con_ACFilter 0x27d +#define HidUsage_Con_ACSetClock 0x27e +#define HidUsage_Con_ACViewClock 0x27f +#define HidUsage_Con_ACSelectTimeZone 0x280 +#define HidUsage_Con_ACEditTimeZones 0x281 +#define HidUsage_Con_ACSetAlarm 0x282 +#define HidUsage_Con_ACClearAlarm 0x283 +#define HidUsage_Con_ACSnoozeAlarm 0x284 +#define HidUsage_Con_ACResetAlarm 0x285 +#define HidUsage_Con_ACSynchronize 0x286 +#define HidUsage_Con_ACSendReceive 0x287 +#define HidUsage_Con_ACSendTo 0x288 +#define HidUsage_Con_ACReply 0x289 +#define HidUsage_Con_ACReplyAll 0x28a +#define HidUsage_Con_ACForwardMsg 0x28b +#define HidUsage_Con_ACSend 0x28c +#define HidUsage_Con_ACAttachFile 0x28d +#define HidUsage_Con_ACUpload 0x28e +#define HidUsage_Con_ACDownloadSaveTargetAs 0x28f +#define HidUsage_Con_ACSetBorders 0x290 +#define HidUsage_Con_ACInsertRow 0x291 +#define HidUsage_Con_ACInsertColumn 0x292 +#define HidUsage_Con_ACInsertFile 0x293 +#define HidUsage_Con_ACInsertPicture 0x294 +#define HidUsage_Con_ACInsertObject 0x295 +#define HidUsage_Con_ACInsertSymbol 0x296 +#define HidUsage_Con_ACSaveandClose 0x297 +#define HidUsage_Con_ACRename 0x298 +#define HidUsage_Con_ACMerge 0x299 +#define HidUsage_Con_ACSplit 0x29a +#define HidUsage_Con_ACDisributeHorizontally 0x29b +#define HidUsage_Con_ACDistributeVertically 0x29c +#define HidUsage_Con_ACNextKeyboardLayoutSelect 0x29d +#define HidUsage_Con_ACNavigationGuidance 0x29e +#define HidUsage_Con_ACDesktopShowAllWindows 0x29f +#define HidUsage_Con_ACSoftKeyLeft 0x2a0 +#define HidUsage_Con_ACSoftKeyRight 0x2a1 +#define HidUsage_Con_ACDesktopShowAllApplications 0x2a2 +#define HidUsage_Con_ACIdleKeepAlive 0x2b0 +#define HidUsage_Con_ExtendedKeyboardAttributesCollection 0x2c0 +#define HidUsage_Con_KeyboardFormFactor 0x2c1 +#define HidUsage_Con_KeyboardKeyType 0x2c2 +#define HidUsage_Con_KeyboardPhysicalLayout 0x2c3 +#define HidUsage_Con_VendorSpecificKeyboardPhysicalLayout 0x2c4 +#define HidUsage_Con_KeyboardIETFLanguageTagIndex 0x2c5 +#define HidUsage_Con_ImplementedKeyboardInputAssistControls 0x2c6 +#define HidUsage_Con_KeyboardInputAssistPrevious 0x2c7 +#define HidUsage_Con_KeyboardInputAssistNext 0x2c8 +#define HidUsage_Con_KeyboardInputAssistPreviousGroup 0x2c9 +#define HidUsage_Con_KeyboardInputAssistNextGroup 0x2ca +#define HidUsage_Con_KeyboardInputAssistAccept 0x2cb +#define HidUsage_Con_KeyboardInputAssistCancel 0x2cc +#define HidUsage_Con_PrivacyScreenToggle 0x2d0 +#define HidUsage_Con_PrivacyScreenLevelDecrement 0x2d1 +#define HidUsage_Con_PrivacyScreenLevelIncrement 0x2d2 +#define HidUsage_Con_PrivacyScreenLevelMinimum 0x2d3 +#define HidUsage_Con_PrivacyScreenLevelMaximum 0x2d4 +#define HidUsage_Con_ContactEdited 0x500 +#define HidUsage_Con_ContactAdded 0x501 +#define HidUsage_Con_ContactRecordActive 0x502 +#define HidUsage_Con_ContactIndex 0x503 +#define HidUsage_Con_ContactNickname 0x504 +#define HidUsage_Con_ContactFirstName 0x505 +#define HidUsage_Con_ContactLastName 0x506 +#define HidUsage_Con_ContactFullName 0x507 +#define HidUsage_Con_ContactPhoneNumberPersonal 0x508 +#define HidUsage_Con_ContactPhoneNumberBusiness 0x509 +#define HidUsage_Con_ContactPhoneNumberMobile 0x50a +#define HidUsage_Con_ContactPhoneNumberPager 0x50b +#define HidUsage_Con_ContactPhoneNumberFax 0x50c +#define HidUsage_Con_ContactPhoneNumberOther 0x50d +#define HidUsage_Con_ContactEmailPersonal 0x50e +#define HidUsage_Con_ContactEmailBusiness 0x50f +#define HidUsage_Con_ContactEmailOther 0x510 +#define HidUsage_Con_ContactEmailMain 0x511 +#define HidUsage_Con_ContactSpeedDialNumber 0x512 +#define HidUsage_Con_ContactStatusFlag 0x513 +#define HidUsage_Con_ContactMisc 0x514 +#define HidUsage_Dig_Digitizer 0x01 +#define HidUsage_Dig_Pen 0x02 +#define HidUsage_Dig_LightPen 0x03 +#define HidUsage_Dig_TouchScreen 0x04 +#define HidUsage_Dig_TouchPad 0x05 +#define HidUsage_Dig_Whiteboard 0x06 +#define HidUsage_Dig_CoordinateMeasuringMachine 0x07 +#define HidUsage_Dig_ThreeDDigitizer 0x08 +#define HidUsage_Dig_StereoPlotter 0x09 +#define HidUsage_Dig_ArticulatedArm 0x0a +#define HidUsage_Dig_Armature 0x0b +#define HidUsage_Dig_MultiplePointDigitizer 0x0c +#define HidUsage_Dig_FreeSpaceWand 0x0d +#define HidUsage_Dig_DeviceConfiguration 0x0e +#define HidUsage_Dig_CapacitiveHeatMapDigitizer 0x0f +#define HidUsage_Dig_Stylus 0x20 +#define HidUsage_Dig_Puck 0x21 +#define HidUsage_Dig_Finger 0x22 +#define HidUsage_Dig_Devicesettings 0x23 +#define HidUsage_Dig_CharacterGesture 0x24 +#define HidUsage_Dig_TipPressure 0x30 +#define HidUsage_Dig_BarrelPressure 0x31 +#define HidUsage_Dig_InRange 0x32 +#define HidUsage_Dig_Touch 0x33 +#define HidUsage_Dig_Untouch 0x34 +#define HidUsage_Dig_Tap 0x35 +#define HidUsage_Dig_Quality 0x36 +#define HidUsage_Dig_DataValid 0x37 +#define HidUsage_Dig_TransducerIndex 0x38 +#define HidUsage_Dig_TabletFunctionKeys 0x39 +#define HidUsage_Dig_ProgramChangeKeys 0x3a +#define HidUsage_Dig_BatteryStrength 0x3b +#define HidUsage_Dig_Invert 0x3c +#define HidUsage_Dig_XTilt 0x3d +#define HidUsage_Dig_YTilt 0x3e +#define HidUsage_Dig_Azimuth 0x3f +#define HidUsage_Dig_Altitude 0x40 +#define HidUsage_Dig_Twist 0x41 +#define HidUsage_Dig_TipSwitch 0x42 +#define HidUsage_Dig_SecondaryTipSwitch 0x43 +#define HidUsage_Dig_BarrelSwitch 0x44 +#define HidUsage_Dig_Eraser 0x45 +#define HidUsage_Dig_TabletPick 0x46 +#define HidUsage_Dig_TouchValid 0x47 +#define HidUsage_Dig_Width 0x48 +#define HidUsage_Dig_Height 0x49 +#define HidUsage_Dig_ContactIdentifier 0x51 +#define HidUsage_Dig_DeviceMode 0x52 +#define HidUsage_Dig_DeviceIdentifier 0x53 +#define HidUsage_Dig_ContactCount 0x54 +#define HidUsage_Dig_ContactCountMaximum 0x55 +#define HidUsage_Dig_ScanTime 0x56 +#define HidUsage_Dig_SurfaceSwitch 0x57 +#define HidUsage_Dig_ButtonSwitch 0x58 +#define HidUsage_Dig_PadType 0x59 +#define HidUsage_Dig_SecondaryBarrelSwitch 0x5a +#define HidUsage_Dig_TransducerSerialNumber 0x5b +#define HidUsage_Dig_PreferredColor 0x5c +#define HidUsage_Dig_PreferredColorisLocked 0x5d +#define HidUsage_Dig_PreferredLineWidth 0x5e +#define HidUsage_Dig_PreferredLineWidthisLocked 0x5f +#define HidUsage_Dig_LatencyMode 0x60 +#define HidUsage_Dig_GestureCharacterQuality 0x61 +#define HidUsage_Dig_CharacterGestureDataLength 0x62 +#define HidUsage_Dig_CharacterGestureData 0x63 +#define HidUsage_Dig_GestureCharacterEncoding 0x64 +#define HidUsage_Dig_UTF8CharacterGestureEncoding 0x65 +#define HidUsage_Dig_UTF16LittleEndianCharacterGestureEncoding 0x66 +#define HidUsage_Dig_UTF16BigEndianCharacterGestureEncoding 0x67 +#define HidUsage_Dig_UTF32LittleEndianCharacterGestureEncoding 0x68 +#define HidUsage_Dig_UTF32BigEndianCharacterGestureEncoding 0x69 +#define HidUsage_Dig_CapacitiveHeatMapProtocolVendorID 0x6a +#define HidUsage_Dig_CapacitiveHeatMapProtocolVersion 0x6b +#define HidUsage_Dig_CapacitiveHeatMapFrameData 0x6c +#define HidUsage_Dig_GestureCharacterEnable 0x6d +#define HidUsage_Dig_TransducerSerialNumberPart2 0x6e +#define HidUsage_Dig_NoPreferredColor 0x6f +#define HidUsage_Dig_PreferredLineStyle 0x70 +#define HidUsage_Dig_PreferredLineStyleisLocked 0x71 +#define HidUsage_Dig_Ink 0x72 +#define HidUsage_Dig_Pencil 0x73 +#define HidUsage_Dig_Highlighter 0x74 +#define HidUsage_Dig_ChiselMarker 0x75 +#define HidUsage_Dig_Brush 0x76 +#define HidUsage_Dig_NoPreference 0x77 +#define HidUsage_Dig_DigitizerDiagnostic 0x80 +#define HidUsage_Dig_DigitizerError 0x81 +#define HidUsage_Dig_ErrNormalStatus 0x82 +#define HidUsage_Dig_ErrTransducersExceeded 0x83 +#define HidUsage_Dig_ErrFullTransFeaturesUnavailable 0x84 +#define HidUsage_Dig_ErrChargeLow 0x85 +#define HidUsage_Dig_TransducerSoftwareInfo 0x90 +#define HidUsage_Dig_TransducerVendorId 0x91 +#define HidUsage_Dig_TransducerProductId 0x92 +#define HidUsage_Dig_DeviceSupportedProtocols 0x93 +#define HidUsage_Dig_TransducerSupportedProtocols 0x94 +#define HidUsage_Dig_NoProtocol 0x95 +#define HidUsage_Dig_WacomAESProtocol 0x96 +#define HidUsage_Dig_USIProtocol 0x97 +#define HidUsage_Dig_MicrosoftPenProtocol 0x98 +#define HidUsage_Dig_SupportedReportRates 0xa0 +#define HidUsage_Dig_ReportRate 0xa1 +#define HidUsage_Dig_TransducerConnected 0xa2 +#define HidUsage_Dig_SwitchDisabled 0xa3 +#define HidUsage_Dig_SwitchUnimplemented 0xa4 +#define HidUsage_Dig_TransducerSwitches 0xa5 +#define HidUsage_Dig_TransducerIndexSelector 0xa6 +#define HidUsage_Dig_ButtonPressThreshold 0xb0 +#define HidUsage_Hap_SimpleHapticController 0x01 +#define HidUsage_Hap_WaveformList 0x10 +#define HidUsage_Hap_DurationList 0x11 +#define HidUsage_Hap_AutoTrigger 0x20 +#define HidUsage_Hap_ManualTrigger 0x21 +#define HidUsage_Hap_AutoTriggerAssociatedControl 0x22 +#define HidUsage_Hap_Intensity 0x23 +#define HidUsage_Hap_RepeatCount 0x24 +#define HidUsage_Hap_RetriggerPeriod 0x25 +#define HidUsage_Hap_WaveformVendorPage 0x26 +#define HidUsage_Hap_WaveformVendorID 0x27 +#define HidUsage_Hap_WaveformCutoffTime 0x28 +#define HidUsage_Hap_WaveformNone 0x1001 +#define HidUsage_Hap_WaveformStop 0x1002 +#define HidUsage_Hap_WaveformClick 0x1003 +#define HidUsage_Hap_WaveformBuzzContinuous 0x1004 +#define HidUsage_Hap_WaveformRumbleContinuous 0x1005 +#define HidUsage_Hap_WaveformPress 0x1006 +#define HidUsage_Hap_WaveformRelease 0x1007 +#define HidUsage_Hap_WaveformHover 0x1008 +#define HidUsage_Hap_WaveformSuccess 0x1009 +#define HidUsage_Hap_WaveformError 0x100a +#define HidUsage_Hap_WaveformInkContinuous 0x100b +#define HidUsage_Hap_WaveformPencilContinuous 0x100c +#define HidUsage_Hap_WaveformMarkerContinuous 0x100d +#define HidUsage_Hap_WaveformChiselMarkerContinuous 0x100e +#define HidUsage_Hap_WaveformBrushContinuous 0x100f +#define HidUsage_Hap_WaveformEraserContinuous 0x1010 +#define HidUsage_Hap_WaveformSparkleContinuous 0x1011 +#define HidUsage_PID_PhysicalInputDevice 0x01 +#define HidUsage_PID_Normal 0x20 +#define HidUsage_PID_SetEffectReport 0x21 +#define HidUsage_PID_EffectParameterBlockIndex 0x22 +#define HidUsage_PID_ParameterBlockOffset 0x23 +#define HidUsage_PID_ROMFlag 0x24 +#define HidUsage_PID_EffectType 0x25 +#define HidUsage_PID_ETConstantForce 0x26 +#define HidUsage_PID_ETRamp 0x27 +#define HidUsage_PID_ETCustomForce 0x28 +#define HidUsage_PID_ETSquare 0x30 +#define HidUsage_PID_ETSine 0x31 +#define HidUsage_PID_ETTriangle 0x32 +#define HidUsage_PID_ETSawtoothUp 0x33 +#define HidUsage_PID_ETSawtoothDown 0x34 +#define HidUsage_PID_ETSpring 0x40 +#define HidUsage_PID_ETDamper 0x41 +#define HidUsage_PID_ETInertia 0x42 +#define HidUsage_PID_ETFriction 0x43 +#define HidUsage_PID_Duration 0x50 +#define HidUsage_PID_SamplePeriod 0x51 +#define HidUsage_PID_Gain 0x52 +#define HidUsage_PID_TriggerButton 0x53 +#define HidUsage_PID_TriggerRepeatInterval 0x54 +#define HidUsage_PID_AxesEnable 0x55 +#define HidUsage_PID_DirectionEnable 0x56 +#define HidUsage_PID_Direction 0x57 +#define HidUsage_PID_TypeSpecificBlockOffset 0x58 +#define HidUsage_PID_BlockType 0x59 +#define HidUsage_PID_SetEnvelopeReport 0x5a +#define HidUsage_PID_AttackLevel 0x5b +#define HidUsage_PID_AttackTime 0x5c +#define HidUsage_PID_FadeLevel 0x5d +#define HidUsage_PID_FadeTime 0x5e +#define HidUsage_PID_SetConditionReport 0x5f +#define HidUsage_PID_CenterPointOffset 0x60 +#define HidUsage_PID_PositiveCoefficient 0x61 +#define HidUsage_PID_NegativeCoefficient 0x62 +#define HidUsage_PID_PositiveSaturation 0x63 +#define HidUsage_PID_NegativeSaturation 0x64 +#define HidUsage_PID_DeadBand 0x65 +#define HidUsage_PID_DownloadForceSample 0x66 +#define HidUsage_PID_IsochCustomForceEnable 0x67 +#define HidUsage_PID_CustomForceDataReport 0x68 +#define HidUsage_PID_CustomForceData 0x69 +#define HidUsage_PID_CustomForceVendorDefinedData 0x6a +#define HidUsage_PID_SetCustomForceReport 0x6b +#define HidUsage_PID_CustomForceDataOffset 0x6c +#define HidUsage_PID_SampleCount 0x6d +#define HidUsage_PID_SetPeriodicReport 0x6e +#define HidUsage_PID_Offset 0x6f +#define HidUsage_PID_Magnitude 0x70 +#define HidUsage_PID_Phase 0x71 +#define HidUsage_PID_Period 0x72 +#define HidUsage_PID_SetConstantForceReport 0x73 +#define HidUsage_PID_SetRampForceReport 0x74 +#define HidUsage_PID_RampStart 0x75 +#define HidUsage_PID_RampEnd 0x76 +#define HidUsage_PID_EffectOperationReport 0x77 +#define HidUsage_PID_EffectOperation 0x78 +#define HidUsage_PID_OpEffectStart 0x79 +#define HidUsage_PID_OpEffectStartSolo 0x7a +#define HidUsage_PID_OpEffectStop 0x7b +#define HidUsage_PID_LoopCount 0x7c +#define HidUsage_PID_DeviceGainReport 0x7d +#define HidUsage_PID_DeviceGain 0x7e +#define HidUsage_PID_ParameterBlockPoolsReport 0x7f +#define HidUsage_PID_RAMPoolSize 0x80 +#define HidUsage_PID_ROMPoolSize 0x81 +#define HidUsage_PID_ROMEffectBlockCount 0x82 +#define HidUsage_PID_SimultaneousEffectsMax 0x83 +#define HidUsage_PID_PoolAlignment 0x84 +#define HidUsage_PID_ParameterBlockMoveReport 0x85 +#define HidUsage_PID_MoveSource 0x86 +#define HidUsage_PID_MoveDestination 0x87 +#define HidUsage_PID_MoveLength 0x88 +#define HidUsage_PID_EffectParameterBlockLoadReport 0x89 +#define HidUsage_PID_EffectParameterBlockLoadStatus 0x8b +#define HidUsage_PID_BlockLoadSuccess 0x8c +#define HidUsage_PID_BlockLoadFull 0x8d +#define HidUsage_PID_BlockLoadError 0x8e +#define HidUsage_PID_BlockHandle 0x8f +#define HidUsage_PID_EffectParameterBlockFreeReport 0x90 +#define HidUsage_PID_TypeSpecificBlockHandle 0x91 +#define HidUsage_PID_PIDStateReport 0x92 +#define HidUsage_PID_EffectPlaying 0x94 +#define HidUsage_PID_PIDDeviceControlReport 0x95 +#define HidUsage_PID_PIDDeviceControl 0x96 +#define HidUsage_PID_DCEnableActuators 0x97 +#define HidUsage_PID_DCDisableActuators 0x98 +#define HidUsage_PID_DCStopAllEffects 0x99 +#define HidUsage_PID_DCReset 0x9a +#define HidUsage_PID_DCPause 0x9b +#define HidUsage_PID_DCContinue 0x9c +#define HidUsage_PID_DevicePaused 0x9f +#define HidUsage_PID_ActuatorsEnabled 0xa0 +#define HidUsage_PID_SafetySwitch 0xa4 +#define HidUsage_PID_ActuatorOverrideSwitch 0xa5 +#define HidUsage_PID_ActuatorPower 0xa6 +#define HidUsage_PID_StartDelay 0xa7 +#define HidUsage_PID_ParameterBlockSize 0xa8 +#define HidUsage_PID_DeviceManagedPool 0xa9 +#define HidUsage_PID_SharedParameterBlocks 0xaa +#define HidUsage_PID_CreateNewEffectParameterBlockReport 0xab +#define HidUsage_PID_RAMPoolAvailable 0xac +#define HidUsage_SC_SocControl 0x01 +#define HidUsage_SC_FirmwareTransfer 0x02 +#define HidUsage_SC_FirmwareFileId 0x03 +#define HidUsage_SC_FileOffsetInBytes 0x04 +#define HidUsage_SC_FileTransferSizeMaxInBytes 0x05 +#define HidUsage_SC_FilePayload 0x06 +#define HidUsage_SC_FilePayloadSizeInBytes 0x07 +#define HidUsage_SC_FilePayloadContainsLastBytes 0x08 +#define HidUsage_SC_FileTransferStop 0x09 +#define HidUsage_SC_FileTransferTillEnd 0x0a +#define HidUsage_EHT_EyeTracker 0x01 +#define HidUsage_EHT_HeadTracker 0x02 +#define HidUsage_EHT_TrackingData 0x10 +#define HidUsage_EHT_Capabilities 0x11 +#define HidUsage_EHT_Configuration 0x12 +#define HidUsage_EHT_Status 0x13 +#define HidUsage_EHT_Control 0x14 +#define HidUsage_EHT_SensorTimestamp 0x20 +#define HidUsage_EHT_PositionX 0x21 +#define HidUsage_EHT_PositionY 0x22 +#define HidUsage_EHT_PositionZ 0x23 +#define HidUsage_EHT_GazePoint 0x24 +#define HidUsage_EHT_LeftEyePosition 0x25 +#define HidUsage_EHT_RightEyePosition 0x26 +#define HidUsage_EHT_HeadPosition 0x27 +#define HidUsage_EHT_HeadDirectionPoint 0x28 +#define HidUsage_EHT_RotationaboutXaxis 0x29 +#define HidUsage_EHT_RotationaboutYaxis 0x2a +#define HidUsage_EHT_RotationaboutZaxis 0x2b +#define HidUsage_EHT_TrackerQuality 0x100 +#define HidUsage_EHT_MinimumTrackingDistance 0x101 +#define HidUsage_EHT_OptimumTrackingDistance 0x102 +#define HidUsage_EHT_MaximumTrackingDistance 0x103 +#define HidUsage_EHT_MaximumScreenPlaneWidth 0x104 +#define HidUsage_EHT_MaximumScreenPlaneHeight 0x105 +#define HidUsage_EHT_DisplayManufacturerID 0x200 +#define HidUsage_EHT_DisplayProductID 0x201 +#define HidUsage_EHT_DisplaySerialNumber 0x202 +#define HidUsage_EHT_DisplayManufacturerDate 0x203 +#define HidUsage_EHT_CalibratedScreenWidth 0x204 +#define HidUsage_EHT_CalibratedScreenHeight 0x205 +#define HidUsage_EHT_SamplingFrequency 0x300 +#define HidUsage_EHT_ConfigurationStatus 0x301 +#define HidUsage_EHT_DeviceModeRequest 0x400 +#define HidUsage_AD_AlphanumericDisplay 0x01 +#define HidUsage_AD_AuxiliaryDisplay 0x02 +#define HidUsage_AD_DisplayAttributesReport 0x20 +#define HidUsage_AD_ASCIICharacterSet 0x21 +#define HidUsage_AD_DataReadBack 0x22 +#define HidUsage_AD_FontReadBack 0x23 +#define HidUsage_AD_DisplayControlReport 0x24 +#define HidUsage_AD_ClearDisplay 0x25 +#define HidUsage_AD_DisplayEnable 0x26 +#define HidUsage_AD_ScreenSaverDelay 0x27 +#define HidUsage_AD_ScreenSaverEnable 0x28 +#define HidUsage_AD_VerticalScroll 0x29 +#define HidUsage_AD_HorizontalScroll 0x2a +#define HidUsage_AD_CharacterReport 0x2b +#define HidUsage_AD_DisplayData 0x2c +#define HidUsage_AD_DisplayStatus 0x2d +#define HidUsage_AD_StatNotReady 0x2e +#define HidUsage_AD_StatReady 0x2f +#define HidUsage_AD_ErrNotaloadablecharacter 0x30 +#define HidUsage_AD_ErrFontdatacannotberead 0x31 +#define HidUsage_AD_CursorPositionReport 0x32 +#define HidUsage_AD_Row 0x33 +#define HidUsage_AD_Column 0x34 +#define HidUsage_AD_Rows 0x35 +#define HidUsage_AD_Columns 0x36 +#define HidUsage_AD_CursorPixelPositioning 0x37 +#define HidUsage_AD_CursorMode 0x38 +#define HidUsage_AD_CursorEnable 0x39 +#define HidUsage_AD_CursorBlink 0x3a +#define HidUsage_AD_FontReport 0x3b +#define HidUsage_AD_FontData 0x3c +#define HidUsage_AD_CharacterWidth 0x3d +#define HidUsage_AD_CharacterHeight 0x3e +#define HidUsage_AD_CharacterSpacingHorizontal 0x3f +#define HidUsage_AD_CharacterSpacingVertical 0x40 +#define HidUsage_AD_UnicodeCharacterSet 0x41 +#define HidUsage_AD_Font7Segment 0x42 +#define HidUsage_AD_SevenSegmentDirectMap 0x43 +#define HidUsage_AD_Font14Segment 0x44 +#define HidUsage_AD_One4SegmentDirectMap 0x45 +#define HidUsage_AD_DisplayBrightness 0x46 +#define HidUsage_AD_DisplayContrast 0x47 +#define HidUsage_AD_CharacterAttribute 0x48 +#define HidUsage_AD_AttributeReadback 0x49 +#define HidUsage_AD_AttributeData 0x4a +#define HidUsage_AD_CharAttrEnhance 0x4b +#define HidUsage_AD_CharAttrUnderline 0x4c +#define HidUsage_AD_CharAttrBlink 0x4d +#define HidUsage_AD_BitmapSizeX 0x80 +#define HidUsage_AD_BitmapSizeY 0x81 +#define HidUsage_AD_MaxBlitSize 0x82 +#define HidUsage_AD_BitDepthFormat 0x83 +#define HidUsage_AD_DisplayOrientation 0x84 +#define HidUsage_AD_PaletteReport 0x85 +#define HidUsage_AD_PaletteDataSize 0x86 +#define HidUsage_AD_PaletteDataOffset 0x87 +#define HidUsage_AD_PaletteData 0x88 +#define HidUsage_AD_BlitReport 0x8a +#define HidUsage_AD_BlitRectangleX1 0x8b +#define HidUsage_AD_BlitRectangleY1 0x8c +#define HidUsage_AD_BlitRectangleX2 0x8d +#define HidUsage_AD_BlitRectangleY2 0x8e +#define HidUsage_AD_BlitData 0x8f +#define HidUsage_AD_SoftButton 0x90 +#define HidUsage_AD_SoftButtonID 0x91 +#define HidUsage_AD_SoftButtonSide 0x92 +#define HidUsage_AD_SoftButtonOffset1 0x93 +#define HidUsage_AD_SoftButtonOffset2 0x94 +#define HidUsage_AD_SoftButtonReport 0x95 +#define HidUsage_AD_SoftKeys 0xc2 +#define HidUsage_AD_DisplayDataExtensions 0xcc +#define HidUsage_AD_CharacterMapping 0xcf +#define HidUsage_AD_UnicodeEquivalent 0xdd +#define HidUsage_AD_CharacterPageMapping 0xdf +#define HidUsage_AD_RequestReport 0xff +#define HidUsage_Sen_Sensor 0x01 +#define HidUsage_Sen_Biometric 0x10 +#define HidUsage_Sen_BiometricHumanPresence 0x11 +#define HidUsage_Sen_BiometricHumanProximity 0x12 +#define HidUsage_Sen_BiometricHumanTouch 0x13 +#define HidUsage_Sen_BiometricBloodPressure 0x14 +#define HidUsage_Sen_BiometricBodyTemperature 0x15 +#define HidUsage_Sen_BiometricHeartRate 0x16 +#define HidUsage_Sen_BiometricHeartRateVariability 0x17 +#define HidUsage_Sen_BiometricPeripheralOxygenSaturation 0x18 +#define HidUsage_Sen_BiometricRespiratoryRate 0x19 +#define HidUsage_Sen_Electrical 0x20 +#define HidUsage_Sen_ElectricalCapacitance 0x21 +#define HidUsage_Sen_ElectricalCurrent 0x22 +#define HidUsage_Sen_ElectricalPower 0x23 +#define HidUsage_Sen_ElectricalInductance 0x24 +#define HidUsage_Sen_ElectricalResistance 0x25 +#define HidUsage_Sen_ElectricalVoltage 0x26 +#define HidUsage_Sen_ElectricalPotentiometer 0x27 +#define HidUsage_Sen_ElectricalFrequency 0x28 +#define HidUsage_Sen_ElectricalPeriod 0x29 +#define HidUsage_Sen_Environmental 0x30 +#define HidUsage_Sen_EnvironmentalAtmosphericPressure 0x31 +#define HidUsage_Sen_EnvironmentalHumidity 0x32 +#define HidUsage_Sen_EnvironmentalTemperature 0x33 +#define HidUsage_Sen_EnvironmentalWindDirection 0x34 +#define HidUsage_Sen_EnvironmentalWindSpeed 0x35 +#define HidUsage_Sen_EnvironmentalAirQuality 0x36 +#define HidUsage_Sen_EnvironmentalHeatIndex 0x37 +#define HidUsage_Sen_EnvironmentalSurfaceTemperature 0x38 +#define HidUsage_Sen_EnvironmentalVolatileOrganicCompounds 0x39 +#define HidUsage_Sen_EnvironmentalObjectPresence 0x3a +#define HidUsage_Sen_EnvironmentalObjectProximity 0x3b +#define HidUsage_Sen_Light 0x40 +#define HidUsage_Sen_LightAmbientLight 0x41 +#define HidUsage_Sen_LightConsumerInfrared 0x42 +#define HidUsage_Sen_LightInfraredLight 0x43 +#define HidUsage_Sen_LightVisibleLight 0x44 +#define HidUsage_Sen_LightUltravioletLight 0x45 +#define HidUsage_Sen_Location 0x50 +#define HidUsage_Sen_LocationBroadcast 0x51 +#define HidUsage_Sen_LocationDeadReckoning 0x52 +#define HidUsage_Sen_LocationGPSGlobalPositioningSystem 0x53 +#define HidUsage_Sen_LocationLookup 0x54 +#define HidUsage_Sen_LocationOther 0x55 +#define HidUsage_Sen_LocationStatic 0x56 +#define HidUsage_Sen_LocationTriangulation 0x57 +#define HidUsage_Sen_Mechanical 0x60 +#define HidUsage_Sen_MechanicalBooleanSwitch 0x61 +#define HidUsage_Sen_MechanicalBooleanSwitchArray 0x62 +#define HidUsage_Sen_MechanicalMultivalueSwitch 0x63 +#define HidUsage_Sen_MechanicalForce 0x64 +#define HidUsage_Sen_MechanicalPressure 0x65 +#define HidUsage_Sen_MechanicalStrain 0x66 +#define HidUsage_Sen_MechanicalWeight 0x67 +#define HidUsage_Sen_MechanicalHapticVibrator 0x68 +#define HidUsage_Sen_MechanicalHallEffectSwitch 0x69 +#define HidUsage_Sen_Motion 0x70 +#define HidUsage_Sen_MotionAccelerometer1D 0x71 +#define HidUsage_Sen_MotionAccelerometer2D 0x72 +#define HidUsage_Sen_MotionAccelerometer3D 0x73 +#define HidUsage_Sen_MotionGyrometer1D 0x74 +#define HidUsage_Sen_MotionGyrometer2D 0x75 +#define HidUsage_Sen_MotionGyrometer3D 0x76 +#define HidUsage_Sen_MotionMotionDetector 0x77 +#define HidUsage_Sen_MotionSpeedometer 0x78 +#define HidUsage_Sen_MotionAccelerometer 0x79 +#define HidUsage_Sen_MotionGyrometer 0x7a +#define HidUsage_Sen_MotionGravityVector 0x7b +#define HidUsage_Sen_MotionLinearAccelerometer 0x7c +#define HidUsage_Sen_Orientation 0x80 +#define HidUsage_Sen_OrientationCompass1D 0x81 +#define HidUsage_Sen_OrientationCompass2D 0x82 +#define HidUsage_Sen_OrientationCompass3D 0x83 +#define HidUsage_Sen_OrientationInclinometer1D 0x84 +#define HidUsage_Sen_OrientationInclinometer2D 0x85 +#define HidUsage_Sen_OrientationInclinometer3D 0x86 +#define HidUsage_Sen_OrientationDistance1D 0x87 +#define HidUsage_Sen_OrientationDistance2D 0x88 +#define HidUsage_Sen_OrientationDistance3D 0x89 +#define HidUsage_Sen_OrientationDeviceOrientation 0x8a +#define HidUsage_Sen_OrientationCompass 0x8b +#define HidUsage_Sen_OrientationInclinometer 0x8c +#define HidUsage_Sen_OrientationDistance 0x8d +#define HidUsage_Sen_OrientationRelativeOrientation 0x8e +#define HidUsage_Sen_OrientationSimpleOrientation 0x8f +#define HidUsage_Sen_Scanner 0x90 +#define HidUsage_Sen_ScannerBarcode 0x91 +#define HidUsage_Sen_ScannerRFID 0x92 +#define HidUsage_Sen_ScannerNFC 0x93 +#define HidUsage_Sen_Time 0xa0 +#define HidUsage_Sen_TimeAlarmTimer 0xa1 +#define HidUsage_Sen_TimeRealTimeClock 0xa2 +#define HidUsage_Sen_PersonalActivity 0xb0 +#define HidUsage_Sen_PersonalActivityActivityDetection 0xb1 +#define HidUsage_Sen_PersonalActivityDevicePosition 0xb2 +#define HidUsage_Sen_PersonalActivityFloorTracker 0xb3 +#define HidUsage_Sen_PersonalActivityPedometer 0xb4 +#define HidUsage_Sen_PersonalActivityStepDetection 0xb5 +#define HidUsage_Sen_OrientationExtended 0xc0 +#define HidUsage_Sen_OrientationExtendedGeomagneticOrientation 0xc1 +#define HidUsage_Sen_OrientationExtendedMagnetometer 0xc2 +#define HidUsage_Sen_Gesture 0xd0 +#define HidUsage_Sen_GestureChassisFlipGesture 0xd1 +#define HidUsage_Sen_GestureHingeFoldGesture 0xd2 +#define HidUsage_Sen_Other 0xe0 +#define HidUsage_Sen_OtherCustom 0xe1 +#define HidUsage_Sen_OtherGeneric 0xe2 +#define HidUsage_Sen_OtherGenericEnumerator 0xe3 +#define HidUsage_Sen_OtherHingeAngle 0xe4 +#define HidUsage_Sen_VendorReserved1 0xf0 +#define HidUsage_Sen_VendorReserved2 0xf1 +#define HidUsage_Sen_VendorReserved3 0xf2 +#define HidUsage_Sen_VendorReserved4 0xf3 +#define HidUsage_Sen_VendorReserved5 0xf4 +#define HidUsage_Sen_VendorReserved6 0xf5 +#define HidUsage_Sen_VendorReserved7 0xf6 +#define HidUsage_Sen_VendorReserved8 0xf7 +#define HidUsage_Sen_VendorReserved9 0xf8 +#define HidUsage_Sen_VendorReserved10 0xf9 +#define HidUsage_Sen_VendorReserved11 0xfa +#define HidUsage_Sen_VendorReserved12 0xfb +#define HidUsage_Sen_VendorReserved13 0xfc +#define HidUsage_Sen_VendorReserved14 0xfd +#define HidUsage_Sen_VendorReserved15 0xfe +#define HidUsage_Sen_VendorReserved16 0xff +#define HidUsage_Sen_Event 0x200 +#define HidUsage_Sen_EventSensorState 0x201 +#define HidUsage_Sen_EventSensorEvent 0x202 +#define HidUsage_Sen_Property 0x300 +#define HidUsage_Sen_PropertyFriendlyName 0x301 +#define HidUsage_Sen_PropertyPersistentUniqueID 0x302 +#define HidUsage_Sen_PropertySensorStatus 0x303 +#define HidUsage_Sen_PropertyMinimumReportInterval 0x304 +#define HidUsage_Sen_PropertySensorManufacturer 0x305 +#define HidUsage_Sen_PropertySensorModel 0x306 +#define HidUsage_Sen_PropertySensorSerialNumber 0x307 +#define HidUsage_Sen_PropertySensorDescription 0x308 +#define HidUsage_Sen_PropertySensorConnectionType 0x309 +#define HidUsage_Sen_PropertySensorDevicePath 0x30a +#define HidUsage_Sen_PropertyHardwareRevision 0x30b +#define HidUsage_Sen_PropertyFirmwareVersion 0x30c +#define HidUsage_Sen_PropertyReleaseDate 0x30d +#define HidUsage_Sen_PropertyReportInterval 0x30e +#define HidUsage_Sen_PropertyChangeSensitivityAbsolute 0x30f +#define HidUsage_Sen_PropertyChangeSensitivityPercentofRange 0x310 +#define HidUsage_Sen_PropertyChangeSensitivityPercentRelative 0x311 +#define HidUsage_Sen_PropertyAccuracy 0x312 +#define HidUsage_Sen_PropertyResolution 0x313 +#define HidUsage_Sen_PropertyMaximum 0x314 +#define HidUsage_Sen_PropertyMinimum 0x315 +#define HidUsage_Sen_PropertyReportingState 0x316 +#define HidUsage_Sen_PropertySamplingRate 0x317 +#define HidUsage_Sen_PropertyResponseCurve 0x318 +#define HidUsage_Sen_PropertyPowerState 0x319 +#define HidUsage_Sen_PropertyMaximumFIFOEvents 0x31a +#define HidUsage_Sen_PropertyReportLatency 0x31b +#define HidUsage_Sen_PropertyFlushFIFOEvents 0x31c +#define HidUsage_Sen_PropertyMaximumPowerConsumption 0x31d +#define HidUsage_Sen_PropertyIsPrimary 0x31e +#define HidUsage_Sen_PropertyHumanPresenceDetectionType 0x31f +#define HidUsage_Sen_DataFieldLocation 0x400 +#define HidUsage_Sen_DataFieldAltitudeAntennaSeaLevel 0x402 +#define HidUsage_Sen_DataFieldDifferentialReferenceStationID 0x403 +#define HidUsage_Sen_DataFieldAltitudeEllipsoidError 0x404 +#define HidUsage_Sen_DataFieldAltitudeEllipsoid 0x405 +#define HidUsage_Sen_DataFieldAltitudeSeaLevelError 0x406 +#define HidUsage_Sen_DataFieldAltitudeSeaLevel 0x407 +#define HidUsage_Sen_DataFieldDifferentialGPSDataAge 0x408 +#define HidUsage_Sen_DataFieldErrorRadius 0x409 +#define HidUsage_Sen_DataFieldFixQuality 0x40a +#define HidUsage_Sen_DataFieldFixType 0x40b +#define HidUsage_Sen_DataFieldGeoidalSeparation 0x40c +#define HidUsage_Sen_DataFieldGPSOperationMode 0x40d +#define HidUsage_Sen_DataFieldGPSSelectionMode 0x40e +#define HidUsage_Sen_DataFieldGPSStatus 0x40f +#define HidUsage_Sen_DataFieldPositionDilutionofPrecision 0x410 +#define HidUsage_Sen_DataFieldHorizontalDilutionofPrecision 0x411 +#define HidUsage_Sen_DataFieldVerticalDilutionofPrecision 0x412 +#define HidUsage_Sen_DataFieldLatitude 0x413 +#define HidUsage_Sen_DataFieldLongitude 0x414 +#define HidUsage_Sen_DataFieldTrueHeading 0x415 +#define HidUsage_Sen_DataFieldMagneticHeading 0x416 +#define HidUsage_Sen_DataFieldMagneticVariation 0x417 +#define HidUsage_Sen_DataFieldSpeed 0x418 +#define HidUsage_Sen_DataFieldSatellitesinView 0x419 +#define HidUsage_Sen_DataFieldSatellitesinViewAzimuth 0x41a +#define HidUsage_Sen_DataFieldSatellitesinViewElevation 0x41b +#define HidUsage_Sen_DataFieldSatellitesinViewIDs 0x41c +#define HidUsage_Sen_DataFieldSatellitesinViewPRNs 0x41d +#define HidUsage_Sen_DataFieldSatellitesinViewSNRatios 0x41e +#define HidUsage_Sen_DataFieldSatellitesUsedCount 0x41f +#define HidUsage_Sen_DataFieldSatellitesUsedPRNs 0x420 +#define HidUsage_Sen_DataFieldNMEASentence 0x421 +#define HidUsage_Sen_DataFieldAddressLine1 0x422 +#define HidUsage_Sen_DataFieldAddressLine2 0x423 +#define HidUsage_Sen_DataFieldCity 0x424 +#define HidUsage_Sen_DataFieldStateorProvince 0x425 +#define HidUsage_Sen_DataFieldCountryorRegion 0x426 +#define HidUsage_Sen_DataFieldPostalCode 0x427 +#define HidUsage_Sen_PropertyLocation 0x42a +#define HidUsage_Sen_PropertyLocationDesiredAccuracy 0x42b +#define HidUsage_Sen_DataFieldEnvironmental 0x430 +#define HidUsage_Sen_DataFieldAtmosphericPressure 0x431 +#define HidUsage_Sen_DataFieldRelativeHumidity 0x433 +#define HidUsage_Sen_DataFieldTemperature 0x434 +#define HidUsage_Sen_DataFieldWindDirection 0x435 +#define HidUsage_Sen_DataFieldWindSpeed 0x436 +#define HidUsage_Sen_DataFieldAirQualityIndex 0x437 +#define HidUsage_Sen_DataFieldEquivalentCO2 0x438 +#define HidUsage_Sen_DataFieldVolatileOrganicCompoundConcentration 0x439 +#define HidUsage_Sen_DataFieldObjectPresence 0x43a +#define HidUsage_Sen_DataFieldObjectProximityRange 0x43b +#define HidUsage_Sen_DataFieldObjectProximityOutofRange 0x43c +#define HidUsage_Sen_PropertyEnvironmental 0x440 +#define HidUsage_Sen_PropertyReferencePressure 0x441 +#define HidUsage_Sen_DataFieldMotion 0x450 +#define HidUsage_Sen_DataFieldMotionState 0x451 +#define HidUsage_Sen_DataFieldAcceleration 0x452 +#define HidUsage_Sen_DataFieldAccelerationAxisX 0x453 +#define HidUsage_Sen_DataFieldAccelerationAxisY 0x454 +#define HidUsage_Sen_DataFieldAccelerationAxisZ 0x455 +#define HidUsage_Sen_DataFieldAngularVelocity 0x456 +#define HidUsage_Sen_DataFieldAngularVelocityaboutXAxis 0x457 +#define HidUsage_Sen_DataFieldAngularVelocityaboutYAxis 0x458 +#define HidUsage_Sen_DataFieldAngularVelocityaboutZAxis 0x459 +#define HidUsage_Sen_DataFieldAngularPosition 0x45a +#define HidUsage_Sen_DataFieldAngularPositionaboutXAxis 0x45b +#define HidUsage_Sen_DataFieldAngularPositionaboutYAxis 0x45c +#define HidUsage_Sen_DataFieldAngularPositionaboutZAxis 0x45d +#define HidUsage_Sen_DataFieldMotionSpeed 0x45e +#define HidUsage_Sen_DataFieldMotionIntensity 0x45f +#define HidUsage_Sen_DataFieldOrientation 0x470 +#define HidUsage_Sen_DataFieldHeading 0x471 +#define HidUsage_Sen_DataFieldHeadingXAxis 0x472 +#define HidUsage_Sen_DataFieldHeadingYAxis 0x473 +#define HidUsage_Sen_DataFieldHeadingZAxis 0x474 +#define HidUsage_Sen_DataFieldHeadingCompensatedMagneticNorth 0x475 +#define HidUsage_Sen_DataFieldHeadingCompensatedTrueNorth 0x476 +#define HidUsage_Sen_DataFieldHeadingMagneticNorth 0x477 +#define HidUsage_Sen_DataFieldHeadingTrueNorth 0x478 +#define HidUsage_Sen_DataFieldDistance 0x479 +#define HidUsage_Sen_DataFieldDistanceXAxis 0x47a +#define HidUsage_Sen_DataFieldDistanceYAxis 0x47b +#define HidUsage_Sen_DataFieldDistanceZAxis 0x47c +#define HidUsage_Sen_DataFieldDistanceOutofRange 0x47d +#define HidUsage_Sen_DataFieldTilt 0x47e +#define HidUsage_Sen_DataFieldTiltXAxis 0x47f +#define HidUsage_Sen_DataFieldTiltYAxis 0x480 +#define HidUsage_Sen_DataFieldTiltZAxis 0x481 +#define HidUsage_Sen_DataFieldRotationMatrix 0x482 +#define HidUsage_Sen_DataFieldQuaternion 0x483 +#define HidUsage_Sen_DataFieldMagneticFlux 0x484 +#define HidUsage_Sen_DataFieldMagneticFluxXAxis 0x485 +#define HidUsage_Sen_DataFieldMagneticFluxYAxis 0x486 +#define HidUsage_Sen_DataFieldMagneticFluxZAxis 0x487 +#define HidUsage_Sen_DataFieldMagnetometerAccuracy 0x488 +#define HidUsage_Sen_DataFieldSimpleOrientationDirection 0x489 +#define HidUsage_Sen_DataFieldMechanical 0x490 +#define HidUsage_Sen_DataFieldBooleanSwitchState 0x491 +#define HidUsage_Sen_DataFieldBooleanSwitchArrayStates 0x492 +#define HidUsage_Sen_DataFieldMultivalueSwitchValue 0x493 +#define HidUsage_Sen_DataFieldForce 0x494 +#define HidUsage_Sen_DataFieldAbsolutePressure 0x495 +#define HidUsage_Sen_DataFieldGaugePressure 0x496 +#define HidUsage_Sen_DataFieldStrain 0x497 +#define HidUsage_Sen_DataFieldWeight 0x498 +#define HidUsage_Sen_PropertyMechanical 0x4a0 +#define HidUsage_Sen_PropertyVibrationState 0x4a1 +#define HidUsage_Sen_PropertyForwardVibrationSpeed 0x4a2 +#define HidUsage_Sen_PropertyBackwardVibrationSpeed 0x4a3 +#define HidUsage_Sen_DataFieldBiometric 0x4b0 +#define HidUsage_Sen_DataFieldHumanPresence 0x4b1 +#define HidUsage_Sen_DataFieldHumanProximityRange 0x4b2 +#define HidUsage_Sen_DataFieldHumanProximityOutofRange 0x4b3 +#define HidUsage_Sen_DataFieldHumanTouchState 0x4b4 +#define HidUsage_Sen_DataFieldBloodPressure 0x4b5 +#define HidUsage_Sen_DataFieldBloodPressureDiastolic 0x4b6 +#define HidUsage_Sen_DataFieldBloodPressureSystolic 0x4b7 +#define HidUsage_Sen_DataFieldHeartRate 0x4b8 +#define HidUsage_Sen_DataFieldRestingHeartRate 0x4b9 +#define HidUsage_Sen_DataFieldHeartbeatInterval 0x4ba +#define HidUsage_Sen_DataFieldRespiratoryRate 0x4bb +#define HidUsage_Sen_DataFieldSpO2 0x4bc +#define HidUsage_Sen_DataFieldHumanAttentionDetected 0x4bd +#define HidUsage_Sen_DataFieldHumanHeadAzimuth 0x4be +#define HidUsage_Sen_DataFieldHumanHeadAltitude 0x4bf +#define HidUsage_Sen_DataFieldHumanHeadRoll 0x4c0 +#define HidUsage_Sen_DataFieldHumanHeadPitch 0x4c1 +#define HidUsage_Sen_DataFieldHumanHeadYaw 0x4c2 +#define HidUsage_Sen_DataFieldHumanCorrelationId 0x4c3 +#define HidUsage_Sen_DataFieldLight 0x4d0 +#define HidUsage_Sen_DataFieldIlluminance 0x4d1 +#define HidUsage_Sen_DataFieldColorTemperature 0x4d2 +#define HidUsage_Sen_DataFieldChromaticity 0x4d3 +#define HidUsage_Sen_DataFieldChromaticityX 0x4d4 +#define HidUsage_Sen_DataFieldChromaticityY 0x4d5 +#define HidUsage_Sen_DataFieldConsumerIRSentenceReceive 0x4d6 +#define HidUsage_Sen_DataFieldInfraredLight 0x4d7 +#define HidUsage_Sen_DataFieldRedLight 0x4d8 +#define HidUsage_Sen_DataFieldGreenLight 0x4d9 +#define HidUsage_Sen_DataFieldBlueLight 0x4da +#define HidUsage_Sen_DataFieldUltravioletALight 0x4db +#define HidUsage_Sen_DataFieldUltravioletBLight 0x4dc +#define HidUsage_Sen_DataFieldUltravioletIndex 0x4dd +#define HidUsage_Sen_DataFieldNearInfraredLight 0x4de +#define HidUsage_Sen_PropertyLight 0x4df +#define HidUsage_Sen_PropertyConsumerIRSentenceSend 0x4e0 +#define HidUsage_Sen_PropertyAutoBrightnessPreferred 0x4e2 +#define HidUsage_Sen_PropertyAutoColorPreferred 0x4e3 +#define HidUsage_Sen_DataFieldScanner 0x4f0 +#define HidUsage_Sen_DataFieldRFIDTag40Bit 0x4f1 +#define HidUsage_Sen_DataFieldNFCSentenceReceive 0x4f2 +#define HidUsage_Sen_PropertyScanner 0x4f8 +#define HidUsage_Sen_PropertyNFCSentenceSend 0x4f9 +#define HidUsage_Sen_DataFieldElectrical 0x500 +#define HidUsage_Sen_DataFieldCapacitance 0x501 +#define HidUsage_Sen_DataFieldCurrent 0x502 +#define HidUsage_Sen_DataFieldElectricalPower 0x503 +#define HidUsage_Sen_DataFieldInductance 0x504 +#define HidUsage_Sen_DataFieldResistance 0x505 +#define HidUsage_Sen_DataFieldVoltage 0x506 +#define HidUsage_Sen_DataFieldFrequency 0x507 +#define HidUsage_Sen_DataFieldPeriod 0x508 +#define HidUsage_Sen_DataFieldPercentofRange 0x509 +#define HidUsage_Sen_DataFieldTime 0x520 +#define HidUsage_Sen_DataFieldYear 0x521 +#define HidUsage_Sen_DataFieldMonth 0x522 +#define HidUsage_Sen_DataFieldDay 0x523 +#define HidUsage_Sen_DataFieldDayofWeek 0x524 +#define HidUsage_Sen_DataFieldHour 0x525 +#define HidUsage_Sen_DataFieldMinute 0x526 +#define HidUsage_Sen_DataFieldSecond 0x527 +#define HidUsage_Sen_DataFieldMillisecond 0x528 +#define HidUsage_Sen_DataFieldTimestamp 0x529 +#define HidUsage_Sen_DataFieldJulianDayofYear 0x52a +#define HidUsage_Sen_DataFieldTimeSinceSystemBoot 0x52b +#define HidUsage_Sen_PropertyTime 0x530 +#define HidUsage_Sen_PropertyTimeZoneOffsetfromUTC 0x531 +#define HidUsage_Sen_PropertyTimeZoneName 0x532 +#define HidUsage_Sen_PropertyDaylightSavingsTimeObserved 0x533 +#define HidUsage_Sen_PropertyTimeTrimAdjustment 0x534 +#define HidUsage_Sen_PropertyArmAlarm 0x535 +#define HidUsage_Sen_DataFieldCustom 0x540 +#define HidUsage_Sen_DataFieldCustomUsage 0x541 +#define HidUsage_Sen_DataFieldCustomBooleanArray 0x542 +#define HidUsage_Sen_DataFieldCustomValue 0x543 +#define HidUsage_Sen_DataFieldCustomValue1 0x544 +#define HidUsage_Sen_DataFieldCustomValue2 0x545 +#define HidUsage_Sen_DataFieldCustomValue3 0x546 +#define HidUsage_Sen_DataFieldCustomValue4 0x547 +#define HidUsage_Sen_DataFieldCustomValue5 0x548 +#define HidUsage_Sen_DataFieldCustomValue6 0x549 +#define HidUsage_Sen_DataFieldCustomValue7 0x54a +#define HidUsage_Sen_DataFieldCustomValue8 0x54b +#define HidUsage_Sen_DataFieldCustomValue9 0x54c +#define HidUsage_Sen_DataFieldCustomValue10 0x54d +#define HidUsage_Sen_DataFieldCustomValue11 0x54e +#define HidUsage_Sen_DataFieldCustomValue12 0x54f +#define HidUsage_Sen_DataFieldCustomValue13 0x550 +#define HidUsage_Sen_DataFieldCustomValue14 0x551 +#define HidUsage_Sen_DataFieldCustomValue15 0x552 +#define HidUsage_Sen_DataFieldCustomValue16 0x553 +#define HidUsage_Sen_DataFieldCustomValue17 0x554 +#define HidUsage_Sen_DataFieldCustomValue18 0x555 +#define HidUsage_Sen_DataFieldCustomValue19 0x556 +#define HidUsage_Sen_DataFieldCustomValue20 0x557 +#define HidUsage_Sen_DataFieldCustomValue21 0x558 +#define HidUsage_Sen_DataFieldCustomValue22 0x559 +#define HidUsage_Sen_DataFieldCustomValue23 0x55a +#define HidUsage_Sen_DataFieldCustomValue24 0x55b +#define HidUsage_Sen_DataFieldCustomValue25 0x55c +#define HidUsage_Sen_DataFieldCustomValue26 0x55d +#define HidUsage_Sen_DataFieldCustomValue27 0x55e +#define HidUsage_Sen_DataFieldCustomValue28 0x55f +#define HidUsage_Sen_DataFieldGeneric 0x560 +#define HidUsage_Sen_DataFieldGenericGUIDorPROPERTYKEY 0x561 +#define HidUsage_Sen_DataFieldGenericCategoryGUID 0x562 +#define HidUsage_Sen_DataFieldGenericTypeGUID 0x563 +#define HidUsage_Sen_DataFieldGenericEventPROPERTYKEY 0x564 +#define HidUsage_Sen_DataFieldGenericPropertyPROPERTYKEY 0x565 +#define HidUsage_Sen_DataFieldGenericDataFieldPROPERTYKEY 0x566 +#define HidUsage_Sen_DataFieldGenericEvent 0x567 +#define HidUsage_Sen_DataFieldGenericProperty 0x568 +#define HidUsage_Sen_DataFieldGenericDataField 0x569 +#define HidUsage_Sen_DataFieldEnumeratorTableRowIndex 0x56a +#define HidUsage_Sen_DataFieldEnumeratorTableRowCount 0x56b +#define HidUsage_Sen_DataFieldGenericGUIDorPROPERTYKEYkind 0x56c +#define HidUsage_Sen_DataFieldGenericGUID 0x56d +#define HidUsage_Sen_DataFieldGenericPROPERTYKEY 0x56e +#define HidUsage_Sen_DataFieldGenericTopLevelCollectionID 0x56f +#define HidUsage_Sen_DataFieldGenericReportID 0x570 +#define HidUsage_Sen_DataFieldGenericReportItemPositionIndex 0x571 +#define HidUsage_Sen_DataFieldGenericFirmwareVARTYPE 0x572 +#define HidUsage_Sen_DataFieldGenericUnitofMeasure 0x573 +#define HidUsage_Sen_DataFieldGenericUnitExponent 0x574 +#define HidUsage_Sen_DataFieldGenericReportSize 0x575 +#define HidUsage_Sen_DataFieldGenericReportCount 0x576 +#define HidUsage_Sen_PropertyGeneric 0x580 +#define HidUsage_Sen_PropertyEnumeratorTableRowIndex 0x581 +#define HidUsage_Sen_PropertyEnumeratorTableRowCount 0x582 +#define HidUsage_Sen_DataFieldPersonalActivity 0x590 +#define HidUsage_Sen_DataFieldActivityType 0x591 +#define HidUsage_Sen_DataFieldActivityState 0x592 +#define HidUsage_Sen_DataFieldDevicePosition 0x593 +#define HidUsage_Sen_DataFieldStepCount 0x594 +#define HidUsage_Sen_DataFieldStepCountReset 0x595 +#define HidUsage_Sen_DataFieldStepDuration 0x596 +#define HidUsage_Sen_DataFieldStepType 0x597 +#define HidUsage_Sen_PropertyMinimumActivityDetectionInterval 0x5a0 +#define HidUsage_Sen_PropertySupportedActivityTypes 0x5a1 +#define HidUsage_Sen_PropertySubscribedActivityTypes 0x5a2 +#define HidUsage_Sen_PropertySupportedStepTypes 0x5a3 +#define HidUsage_Sen_PropertySubscribedStepTypes 0x5a4 +#define HidUsage_Sen_PropertyFloorHeight 0x5a5 +#define HidUsage_Sen_DataFieldCustomTypeID 0x5b0 +#define HidUsage_Sen_PropertyCustom 0x5c0 +#define HidUsage_Sen_PropertyCustomValue1 0x5c1 +#define HidUsage_Sen_PropertyCustomValue2 0x5c2 +#define HidUsage_Sen_PropertyCustomValue3 0x5c3 +#define HidUsage_Sen_PropertyCustomValue4 0x5c4 +#define HidUsage_Sen_PropertyCustomValue5 0x5c5 +#define HidUsage_Sen_PropertyCustomValue6 0x5c6 +#define HidUsage_Sen_PropertyCustomValue7 0x5c7 +#define HidUsage_Sen_PropertyCustomValue8 0x5c8 +#define HidUsage_Sen_PropertyCustomValue9 0x5c9 +#define HidUsage_Sen_PropertyCustomValue10 0x5ca +#define HidUsage_Sen_PropertyCustomValue11 0x5cb +#define HidUsage_Sen_PropertyCustomValue12 0x5cc +#define HidUsage_Sen_PropertyCustomValue13 0x5cd +#define HidUsage_Sen_PropertyCustomValue14 0x5ce +#define HidUsage_Sen_PropertyCustomValue15 0x5cf +#define HidUsage_Sen_PropertyCustomValue16 0x5d0 +#define HidUsage_Sen_DataFieldHinge 0x5e0 +#define HidUsage_Sen_DataFieldHingeAngle 0x5e1 +#define HidUsage_Sen_DataFieldGestureSensor 0x5f0 +#define HidUsage_Sen_DataFieldGestureState 0x5f1 +#define HidUsage_Sen_DataFieldHingeFoldInitialAngle 0x5f2 +#define HidUsage_Sen_DataFieldHingeFoldFinalAngle 0x5f3 +#define HidUsage_Sen_DataFieldHingeFoldContributingPanel 0x5f4 +#define HidUsage_Sen_DataFieldHingeFoldType 0x5f5 +#define HidUsage_Sen_SensorStateUndefined 0x800 +#define HidUsage_Sen_SensorStateReady 0x801 +#define HidUsage_Sen_SensorStateNotAvailable 0x802 +#define HidUsage_Sen_SensorStateNoData 0x803 +#define HidUsage_Sen_SensorStateInitializing 0x804 +#define HidUsage_Sen_SensorStateAccessDenied 0x805 +#define HidUsage_Sen_SensorStateError 0x806 +#define HidUsage_Sen_SensorEventUnknown 0x810 +#define HidUsage_Sen_SensorEventStateChanged 0x811 +#define HidUsage_Sen_SensorEventPropertyChanged 0x812 +#define HidUsage_Sen_SensorEventDataUpdated 0x813 +#define HidUsage_Sen_SensorEventPollResponse 0x814 +#define HidUsage_Sen_SensorEventChangeSensitivity 0x815 +#define HidUsage_Sen_SensorEventRangeMaximumReached 0x816 +#define HidUsage_Sen_SensorEventRangeMinimumReached 0x817 +#define HidUsage_Sen_SensorEventHighThresholdCrossUpward 0x818 +#define HidUsage_Sen_SensorEventHighThresholdCrossDownward 0x819 +#define HidUsage_Sen_SensorEventLowThresholdCrossUpward 0x81a +#define HidUsage_Sen_SensorEventLowThresholdCrossDownward 0x81b +#define HidUsage_Sen_SensorEventZeroThresholdCrossUpward 0x81c +#define HidUsage_Sen_SensorEventZeroThresholdCrossDownward 0x81d +#define HidUsage_Sen_SensorEventPeriodExceeded 0x81e +#define HidUsage_Sen_SensorEventFrequencyExceeded 0x81f +#define HidUsage_Sen_SensorEventComplexTrigger 0x820 +#define HidUsage_Sen_ConnectionTypePCIntegrated 0x830 +#define HidUsage_Sen_ConnectionTypePCAttached 0x831 +#define HidUsage_Sen_ConnectionTypePCExternal 0x832 +#define HidUsage_Sen_ReportingStateReportNoEvents 0x840 +#define HidUsage_Sen_ReportingStateReportAllEvents 0x841 +#define HidUsage_Sen_ReportingStateReportThresholdEvents 0x842 +#define HidUsage_Sen_ReportingStateWakeOnNoEvents 0x843 +#define HidUsage_Sen_ReportingStateWakeOnAllEvents 0x844 +#define HidUsage_Sen_ReportingStateWakeOnThresholdEvents 0x845 +#define HidUsage_Sen_ReportingStateAnytime 0x846 +#define HidUsage_Sen_PowerStateUndefined 0x850 +#define HidUsage_Sen_PowerStateD0FullPower 0x851 +#define HidUsage_Sen_PowerStateD1LowPower 0x852 +#define HidUsage_Sen_PowerStateD2StandbyPowerwithWakeup 0x853 +#define HidUsage_Sen_PowerStateD3SleepwithWakeup 0x854 +#define HidUsage_Sen_PowerStateD4PowerOff 0x855 +#define HidUsage_Sen_AccuracyDefault 0x860 +#define HidUsage_Sen_AccuracyHigh 0x861 +#define HidUsage_Sen_AccuracyMedium 0x862 +#define HidUsage_Sen_AccuracyLow 0x863 +#define HidUsage_Sen_FixQualityNoFix 0x870 +#define HidUsage_Sen_FixQualityGPS 0x871 +#define HidUsage_Sen_FixQualityDGPS 0x872 +#define HidUsage_Sen_FixTypeNoFix 0x880 +#define HidUsage_Sen_FixTypeGPSSPSModeFixValid 0x881 +#define HidUsage_Sen_FixTypeDGPSSPSModeFixValid 0x882 +#define HidUsage_Sen_FixTypeGPSPPSModeFixValid 0x883 +#define HidUsage_Sen_FixTypeRealTimeKinematic 0x884 +#define HidUsage_Sen_FixTypeFloatRTK 0x885 +#define HidUsage_Sen_FixTypeEstimateddeadreckoned 0x886 +#define HidUsage_Sen_FixTypeManualInputMode 0x887 +#define HidUsage_Sen_FixTypeSimulatorMode 0x888 +#define HidUsage_Sen_GPSOperationModeManual 0x890 +#define HidUsage_Sen_GPSOperationModeAutomatic 0x891 +#define HidUsage_Sen_GPSSelectionModeAutonomous 0x8a0 +#define HidUsage_Sen_GPSSelectionModeDGPS 0x8a1 +#define HidUsage_Sen_GPSSelectionModeEstimateddeadreckoned 0x8a2 +#define HidUsage_Sen_GPSSelectionModeManualInput 0x8a3 +#define HidUsage_Sen_GPSSelectionModeSimulator 0x8a4 +#define HidUsage_Sen_GPSSelectionModeDataNotValid 0x8a5 +#define HidUsage_Sen_GPSStatusDataValid 0x8b0 +#define HidUsage_Sen_GPSStatusDataNotValid 0x8b1 +#define HidUsage_Sen_DayofWeekSunday 0x8c0 +#define HidUsage_Sen_DayofWeekMonday 0x8c1 +#define HidUsage_Sen_DayofWeekTuesday 0x8c2 +#define HidUsage_Sen_DayofWeekWednesday 0x8c3 +#define HidUsage_Sen_DayofWeekThursday 0x8c4 +#define HidUsage_Sen_DayofWeekFriday 0x8c5 +#define HidUsage_Sen_DayofWeekSaturday 0x8c6 +#define HidUsage_Sen_KindCategory 0x8d0 +#define HidUsage_Sen_KindType 0x8d1 +#define HidUsage_Sen_KindEvent 0x8d2 +#define HidUsage_Sen_KindProperty 0x8d3 +#define HidUsage_Sen_KindDataField 0x8d4 +#define HidUsage_Sen_MagnetometerAccuracyLow 0x8e0 +#define HidUsage_Sen_MagnetometerAccuracyMedium 0x8e1 +#define HidUsage_Sen_MagnetometerAccuracyHigh 0x8e2 +#define HidUsage_Sen_SimpleOrientationDirectionNotRotated 0x8f0 +#define HidUsage_Sen_SimpleOrientationDirectionRotated90DegreesCCW 0x8f1 +#define HidUsage_Sen_SimpleOrientationDirectionRotated180DegreesCCW 0x8f2 +#define HidUsage_Sen_SimpleOrientationDirectionRotated270DegreesCCW 0x8f3 +#define HidUsage_Sen_SimpleOrientationDirectionFaceUp 0x8f4 +#define HidUsage_Sen_SimpleOrientationDirectionFaceDown 0x8f5 +#define HidUsage_Sen_VT_NULL 0x900 +#define HidUsage_Sen_VT_BOOL 0x901 +#define HidUsage_Sen_VT_UI1 0x902 +#define HidUsage_Sen_VT_I1 0x903 +#define HidUsage_Sen_VT_UI2 0x904 +#define HidUsage_Sen_VT_I2 0x905 +#define HidUsage_Sen_VT_UI4 0x906 +#define HidUsage_Sen_VT_I4 0x907 +#define HidUsage_Sen_VT_UI8 0x908 +#define HidUsage_Sen_VT_I8 0x909 +#define HidUsage_Sen_VT_R4 0x90a +#define HidUsage_Sen_VT_R8 0x90b +#define HidUsage_Sen_VT_WSTR 0x90c +#define HidUsage_Sen_VT_STR 0x90d +#define HidUsage_Sen_VT_CLSID 0x90e +#define HidUsage_Sen_VT_VECTORVT_UI1 0x90f +#define HidUsage_Sen_VT_F16E0 0x910 +#define HidUsage_Sen_VT_F16E1 0x911 +#define HidUsage_Sen_VT_F16E2 0x912 +#define HidUsage_Sen_VT_F16E3 0x913 +#define HidUsage_Sen_VT_F16E4 0x914 +#define HidUsage_Sen_VT_F16E5 0x915 +#define HidUsage_Sen_VT_F16E6 0x916 +#define HidUsage_Sen_VT_F16E7 0x917 +#define HidUsage_Sen_VT_F16E8 0x918 +#define HidUsage_Sen_VT_F16E9 0x919 +#define HidUsage_Sen_VT_F16EA 0x91a +#define HidUsage_Sen_VT_F16EB 0x91b +#define HidUsage_Sen_VT_F16EC 0x91c +#define HidUsage_Sen_VT_F16ED 0x91d +#define HidUsage_Sen_VT_F16EE 0x91e +#define HidUsage_Sen_VT_F16EF 0x91f +#define HidUsage_Sen_VT_F32E0 0x920 +#define HidUsage_Sen_VT_F32E1 0x921 +#define HidUsage_Sen_VT_F32E2 0x922 +#define HidUsage_Sen_VT_F32E3 0x923 +#define HidUsage_Sen_VT_F32E4 0x924 +#define HidUsage_Sen_VT_F32E5 0x925 +#define HidUsage_Sen_VT_F32E6 0x926 +#define HidUsage_Sen_VT_F32E7 0x927 +#define HidUsage_Sen_VT_F32E8 0x928 +#define HidUsage_Sen_VT_F32E9 0x929 +#define HidUsage_Sen_VT_F32EA 0x92a +#define HidUsage_Sen_VT_F32EB 0x92b +#define HidUsage_Sen_VT_F32EC 0x92c +#define HidUsage_Sen_VT_F32ED 0x92d +#define HidUsage_Sen_VT_F32EE 0x92e +#define HidUsage_Sen_VT_F32EF 0x92f +#define HidUsage_Sen_ActivityTypeUnknown 0x930 +#define HidUsage_Sen_ActivityTypeStationary 0x931 +#define HidUsage_Sen_ActivityTypeFidgeting 0x932 +#define HidUsage_Sen_ActivityTypeWalking 0x933 +#define HidUsage_Sen_ActivityTypeRunning 0x934 +#define HidUsage_Sen_ActivityTypeInVehicle 0x935 +#define HidUsage_Sen_ActivityTypeBiking 0x936 +#define HidUsage_Sen_ActivityTypeIdle 0x937 +#define HidUsage_Sen_UnitNotSpecified 0x940 +#define HidUsage_Sen_UnitLux 0x941 +#define HidUsage_Sen_UnitDegreesKelvin 0x942 +#define HidUsage_Sen_UnitDegreesCelsius 0x943 +#define HidUsage_Sen_UnitPascal 0x944 +#define HidUsage_Sen_UnitNewton 0x945 +#define HidUsage_Sen_UnitMetersSecond 0x946 +#define HidUsage_Sen_UnitKilogram 0x947 +#define HidUsage_Sen_UnitMeter 0x948 +#define HidUsage_Sen_UnitMetersSecondSecond 0x949 +#define HidUsage_Sen_UnitFarad 0x94a +#define HidUsage_Sen_UnitAmpere 0x94b +#define HidUsage_Sen_UnitWatt 0x94c +#define HidUsage_Sen_UnitHenry 0x94d +#define HidUsage_Sen_UnitOhm 0x94e +#define HidUsage_Sen_UnitVolt 0x94f +#define HidUsage_Sen_UnitHertz 0x950 +#define HidUsage_Sen_UnitBar 0x951 +#define HidUsage_Sen_UnitDegreesAnticlockwise 0x952 +#define HidUsage_Sen_UnitDegreesClockwise 0x953 +#define HidUsage_Sen_UnitDegrees 0x954 +#define HidUsage_Sen_UnitDegreesSecond 0x955 +#define HidUsage_Sen_UnitDegreesSecondSecond 0x956 +#define HidUsage_Sen_UnitKnot 0x957 +#define HidUsage_Sen_UnitPercent 0x958 +#define HidUsage_Sen_UnitSecond 0x959 +#define HidUsage_Sen_UnitMillisecond 0x95a +#define HidUsage_Sen_UnitG 0x95b +#define HidUsage_Sen_UnitBytes 0x95c +#define HidUsage_Sen_UnitMilligauss 0x95d +#define HidUsage_Sen_UnitBits 0x95e +#define HidUsage_Sen_ActivityStateNoStateChange 0x960 +#define HidUsage_Sen_ActivityStateStartActivity 0x961 +#define HidUsage_Sen_ActivityStateEndActivity 0x962 +#define HidUsage_Sen_Exponent0 0x970 +#define HidUsage_Sen_Exponent1 0x971 +#define HidUsage_Sen_Exponent2 0x972 +#define HidUsage_Sen_Exponent3 0x973 +#define HidUsage_Sen_Exponent4 0x974 +#define HidUsage_Sen_Exponent5 0x975 +#define HidUsage_Sen_Exponent6 0x976 +#define HidUsage_Sen_Exponent7 0x977 +#define HidUsage_Sen_Exponent8 0x978 +#define HidUsage_Sen_Exponent9 0x979 +#define HidUsage_Sen_ExponentA 0x97a +#define HidUsage_Sen_ExponentB 0x97b +#define HidUsage_Sen_ExponentC 0x97c +#define HidUsage_Sen_ExponentD 0x97d +#define HidUsage_Sen_ExponentE 0x97e +#define HidUsage_Sen_ExponentF 0x97f +#define HidUsage_Sen_DevicePositionUnknown 0x980 +#define HidUsage_Sen_DevicePositionUnchanged 0x981 +#define HidUsage_Sen_DevicePositionOnDesk 0x982 +#define HidUsage_Sen_DevicePositionInHand 0x983 +#define HidUsage_Sen_DevicePositionMovinginBag 0x984 +#define HidUsage_Sen_DevicePositionStationaryinBag 0x985 +#define HidUsage_Sen_StepTypeUnknown 0x990 +#define HidUsage_Sen_StepTypeWalking 0x991 +#define HidUsage_Sen_StepTypeRunning 0x992 +#define HidUsage_Sen_GestureStateUnknown 0x9a0 +#define HidUsage_Sen_GestureStateStarted 0x9a1 +#define HidUsage_Sen_GestureStateCompleted 0x9a2 +#define HidUsage_Sen_GestureStateCancelled 0x9a3 +#define HidUsage_Sen_HingeFoldContributingPanelUnknown 0x9b0 +#define HidUsage_Sen_HingeFoldContributingPanelPanel1 0x9b1 +#define HidUsage_Sen_HingeFoldContributingPanelPanel2 0x9b2 +#define HidUsage_Sen_HingeFoldContributingPanelBoth 0x9b3 +#define HidUsage_Sen_HingeFoldTypeUnknown 0x9b4 +#define HidUsage_Sen_HingeFoldTypeIncreasing 0x9b5 +#define HidUsage_Sen_HingeFoldTypeDecreasing 0x9b6 +#define HidUsage_Sen_HumanPresenceDetectionTypeVendorDefinedNonBiometric 0x9c0 +#define HidUsage_Sen_HumanPresenceDetectionTypeVendorDefinedBiometric 0x9c1 +#define HidUsage_Sen_HumanPresenceDetectionTypeFacialBiometric 0x9c2 +#define HidUsage_Sen_HumanPresenceDetectionTypeAudioBiometric 0x9c3 +#define HidUsage_Sen_ModifierChangeSensitivityAbsolute 0x1000 +#define HidUsage_Sen_ModifierMaximum 0x2000 +#define HidUsage_Sen_ModifierMinimum 0x3000 +#define HidUsage_Sen_ModifierAccuracy 0x4000 +#define HidUsage_Sen_ModifierResolution 0x5000 +#define HidUsage_Sen_ModifierThresholdHigh 0x6000 +#define HidUsage_Sen_ModifierThresholdLow 0x7000 +#define HidUsage_Sen_ModifierCalibrationOffset 0x8000 +#define HidUsage_Sen_ModifierCalibrationMultiplier 0x9000 +#define HidUsage_Sen_ModifierReportInterval 0xa000 +#define HidUsage_Sen_ModifierFrequencyMax 0xb000 +#define HidUsage_Sen_ModifierPeriodMax 0xc000 +#define HidUsage_Sen_ModifierChangeSensitivityPercentofRange 0xd000 +#define HidUsage_Sen_ModifierChangeSensitivityPercentRelative 0xe000 +#define HidUsage_Sen_ModifierVendorReserved 0xf000 +#define HidUsage_MI_MedicalUltrasound 0x01 +#define HidUsage_MI_VCRAcquisition 0x20 +#define HidUsage_MI_FreezeThaw 0x21 +#define HidUsage_MI_ClipStore 0x22 +#define HidUsage_MI_Update 0x23 +#define HidUsage_MI_Next 0x24 +#define HidUsage_MI_Save 0x25 +#define HidUsage_MI_Print 0x26 +#define HidUsage_MI_MicrophoneEnable 0x27 +#define HidUsage_MI_Cine 0x40 +#define HidUsage_MI_TransmitPower 0x41 +#define HidUsage_MI_Volume 0x42 +#define HidUsage_MI_Focus 0x43 +#define HidUsage_MI_Depth 0x44 +#define HidUsage_MI_SoftStepPrimary 0x60 +#define HidUsage_MI_SoftStepSecondary 0x61 +#define HidUsage_MI_DepthGainCompensation 0x70 +#define HidUsage_MI_ZoomSelect 0x80 +#define HidUsage_MI_ZoomAdjust 0x81 +#define HidUsage_MI_SpectralDopplerModeSelect 0x82 +#define HidUsage_MI_SpectralDopplerAdjust 0x83 +#define HidUsage_MI_ColorDopplerModeSelect 0x84 +#define HidUsage_MI_ColorDopplerAdjust 0x85 +#define HidUsage_MI_MotionModeSelect 0x86 +#define HidUsage_MI_MotionModeAdjust 0x87 +#define HidUsage_MI_TwoDModeSelect 0x88 +#define HidUsage_MI_TwoDModeAdjust 0x89 +#define HidUsage_MI_SoftControlSelect 0xa0 +#define HidUsage_MI_SoftControlAdjust 0xa1 +#define HidUsage_BD_BrailleDisplay 0x01 +#define HidUsage_BD_BrailleRow 0x02 +#define HidUsage_BD_EightDotBrailleCell 0x03 +#define HidUsage_BD_SixDotBrailleCell 0x04 +#define HidUsage_BD_NumberofBrailleCells 0x05 +#define HidUsage_BD_ScreenReaderControl 0x06 +#define HidUsage_BD_ScreenReaderIdentifier 0x07 +#define HidUsage_BD_RouterSet1 0xfa +#define HidUsage_BD_RouterSet2 0xfb +#define HidUsage_BD_RouterSet3 0xfc +#define HidUsage_BD_RouterKey 0x100 +#define HidUsage_BD_RowRouterKey 0x101 +#define HidUsage_BD_BrailleButtons 0x200 +#define HidUsage_BD_BrailleKeyboardDot1 0x201 +#define HidUsage_BD_BrailleKeyboardDot2 0x202 +#define HidUsage_BD_BrailleKeyboardDot3 0x203 +#define HidUsage_BD_BrailleKeyboardDot4 0x204 +#define HidUsage_BD_BrailleKeyboardDot5 0x205 +#define HidUsage_BD_BrailleKeyboardDot6 0x206 +#define HidUsage_BD_BrailleKeyboardDot7 0x207 +#define HidUsage_BD_BrailleKeyboardDot8 0x208 +#define HidUsage_BD_BrailleKeyboardSpace 0x209 +#define HidUsage_BD_BrailleKeyboardLeftSpace 0x20a +#define HidUsage_BD_BrailleKeyboardRightSpace 0x20b +#define HidUsage_BD_BrailleFaceControls 0x20c +#define HidUsage_BD_BrailleLeftControls 0x20d +#define HidUsage_BD_BrailleRightControls 0x20e +#define HidUsage_BD_BrailleTopControls 0x20f +#define HidUsage_BD_BrailleJoystickCenter 0x210 +#define HidUsage_BD_BrailleJoystickUp 0x211 +#define HidUsage_BD_BrailleJoystickDown 0x212 +#define HidUsage_BD_BrailleJoystickLeft 0x213 +#define HidUsage_BD_BrailleJoystickRight 0x214 +#define HidUsage_BD_BrailleDPadCenter 0x215 +#define HidUsage_BD_BrailleDPadUp 0x216 +#define HidUsage_BD_BrailleDPadDown 0x217 +#define HidUsage_BD_BrailleDPadLeft 0x218 +#define HidUsage_BD_BrailleDPadRight 0x219 +#define HidUsage_BD_BraillePanLeft 0x21a +#define HidUsage_BD_BraillePanRight 0x21b +#define HidUsage_BD_BrailleRockerUp 0x21c +#define HidUsage_BD_BrailleRockerDown 0x21d +#define HidUsage_BD_BrailleRockerPress 0x21e +#define HidUsage_LAI_LampArray 0x01 +#define HidUsage_LAI_LampArrayAttributesReport 0x02 +#define HidUsage_LAI_LampCount 0x03 +#define HidUsage_LAI_BoundingBoxWidthInMicrometers 0x04 +#define HidUsage_LAI_BoundingBoxHeightInMicrometers 0x05 +#define HidUsage_LAI_BoundingBoxDepthInMicrometers 0x06 +#define HidUsage_LAI_LampArrayKind 0x07 +#define HidUsage_LAI_MinUpdateIntervalInMicroseconds 0x08 +#define HidUsage_LAI_LampAttributesRequestReport 0x20 +#define HidUsage_LAI_LampId 0x21 +#define HidUsage_LAI_LampAttributesResponseReport 0x22 +#define HidUsage_LAI_PositionXInMicrometers 0x23 +#define HidUsage_LAI_PositionYInMicrometers 0x24 +#define HidUsage_LAI_PositionZInMicrometers 0x25 +#define HidUsage_LAI_LampPurposes 0x26 +#define HidUsage_LAI_UpdateLatencyInMicroseconds 0x27 +#define HidUsage_LAI_RedLevelCount 0x28 +#define HidUsage_LAI_GreenLevelCount 0x29 +#define HidUsage_LAI_BlueLevelCount 0x2a +#define HidUsage_LAI_IntensityLevelCount 0x2b +#define HidUsage_LAI_IsProgrammable 0x2c +#define HidUsage_LAI_InputBinding 0x2d +#define HidUsage_LAI_LampMultiUpdateReport 0x50 +#define HidUsage_LAI_RedUpdateChannel 0x51 +#define HidUsage_LAI_GreenUpdateChannel 0x52 +#define HidUsage_LAI_BlueUpdateChannel 0x53 +#define HidUsage_LAI_IntensityUpdateChannel 0x54 +#define HidUsage_LAI_LampUpdateFlags 0x55 +#define HidUsage_LAI_LampRangeUpdateReport 0x60 +#define HidUsage_LAI_LampIdStart 0x61 +#define HidUsage_LAI_LampIdEnd 0x62 +#define HidUsage_LAI_LampArrayControlReport 0x70 +#define HidUsage_LAI_AutonomousMode 0x71 +#define HidUsage_Mon_MonitorControl 0x01 +#define HidUsage_Mon_EDIDInformation 0x02 +#define HidUsage_Mon_VDIFInformation 0x03 +#define HidUsage_Mon_VESAVersion 0x04 +#define HidUsage_VESAVC_Degauss 0x01 +#define HidUsage_VESAVC_Brightness 0x10 +#define HidUsage_VESAVC_Contrast 0x12 +#define HidUsage_VESAVC_RedVideoGain 0x16 +#define HidUsage_VESAVC_GreenVideoGain 0x18 +#define HidUsage_VESAVC_BlueVideoGain 0x1a +#define HidUsage_VESAVC_Focus 0x1c +#define HidUsage_VESAVC_HorizontalPosition 0x20 +#define HidUsage_VESAVC_HorizontalSize 0x22 +#define HidUsage_VESAVC_HorizontalPincushion 0x24 +#define HidUsage_VESAVC_HorizontalPincushionBalance 0x26 +#define HidUsage_VESAVC_HorizontalMisconvergence 0x28 +#define HidUsage_VESAVC_HorizontalLinearity 0x2a +#define HidUsage_VESAVC_HorizontalLinearityBalance 0x2c +#define HidUsage_VESAVC_VerticalPosition 0x30 +#define HidUsage_VESAVC_VerticalSize 0x32 +#define HidUsage_VESAVC_VerticalPincushion 0x34 +#define HidUsage_VESAVC_VerticalPincushionBalance 0x36 +#define HidUsage_VESAVC_VerticalMisconvergence 0x38 +#define HidUsage_VESAVC_VerticalLinearity 0x3a +#define HidUsage_VESAVC_VerticalLinearityBalance 0x3c +#define HidUsage_VESAVC_ParallelogramDistortionKeyBalance 0x40 +#define HidUsage_VESAVC_TrapezoidalDistortionKey 0x42 +#define HidUsage_VESAVC_TiltRotation 0x44 +#define HidUsage_VESAVC_TopCornerDistortionControl 0x46 +#define HidUsage_VESAVC_TopCornerDistortionBalance 0x48 +#define HidUsage_VESAVC_BottomCornerDistortionControl 0x4a +#define HidUsage_VESAVC_BottomCornerDistortionBalance 0x4c +#define HidUsage_VESAVC_HorizontalMoiré 0x56 +#define HidUsage_VESAVC_VerticalMoiré 0x58 +#define HidUsage_VESAVC_InputLevelSelect 0x5e +#define HidUsage_VESAVC_InputSourceSelect 0x60 +#define HidUsage_VESAVC_RedVideoBlackLevel 0x6c +#define HidUsage_VESAVC_GreenVideoBlackLevel 0x6e +#define HidUsage_VESAVC_BlueVideoBlackLevel 0x70 +#define HidUsage_VESAVC_AutoSizeCenter 0xa2 +#define HidUsage_VESAVC_PolarityHorizontalSynchronization 0xa4 +#define HidUsage_VESAVC_PolarityVerticalSynchronization 0xa6 +#define HidUsage_VESAVC_SynchronizationType 0xa8 +#define HidUsage_VESAVC_ScreenOrientation 0xaa +#define HidUsage_VESAVC_HorizontalFrequency 0xac +#define HidUsage_VESAVC_VerticalFrequency 0xae +#define HidUsage_VESAVC_Settings 0xb0 +#define HidUsage_VESAVC_OnScreenDisplay 0xca +#define HidUsage_VESAVC_StereoMode 0xd4 +#define HidUsage_Pow_iName 0x01 +#define HidUsage_Pow_PresentStatus 0x02 +#define HidUsage_Pow_ChangedStatus 0x03 +#define HidUsage_Pow_UPS 0x04 +#define HidUsage_Pow_PowerSupply 0x05 +#define HidUsage_Pow_BatterySystem 0x10 +#define HidUsage_Pow_BatterySystemId 0x11 +#define HidUsage_Pow_Battery 0x12 +#define HidUsage_Pow_BatteryId 0x13 +#define HidUsage_Pow_Charger 0x14 +#define HidUsage_Pow_ChargerId 0x15 +#define HidUsage_Pow_PowerConverter 0x16 +#define HidUsage_Pow_PowerConverterId 0x17 +#define HidUsage_Pow_OutletSystem 0x18 +#define HidUsage_Pow_OutletSystemId 0x19 +#define HidUsage_Pow_Input 0x1a +#define HidUsage_Pow_InputId 0x1b +#define HidUsage_Pow_Output 0x1c +#define HidUsage_Pow_OutputId 0x1d +#define HidUsage_Pow_Flow 0x1e +#define HidUsage_Pow_FlowId 0x1f +#define HidUsage_Pow_Outlet 0x20 +#define HidUsage_Pow_OutletId 0x21 +#define HidUsage_Pow_Gang 0x22 +#define HidUsage_Pow_GangId 0x23 +#define HidUsage_Pow_PowerSummary 0x24 +#define HidUsage_Pow_PowerSummaryId 0x25 +#define HidUsage_Pow_Voltage 0x30 +#define HidUsage_Pow_Current 0x31 +#define HidUsage_Pow_Frequency 0x32 +#define HidUsage_Pow_ApparentPower 0x33 +#define HidUsage_Pow_ActivePower 0x34 +#define HidUsage_Pow_PercentLoad 0x35 +#define HidUsage_Pow_Temperature 0x36 +#define HidUsage_Pow_Humidity 0x37 +#define HidUsage_Pow_BadCount 0x38 +#define HidUsage_Pow_ConfigVoltage 0x40 +#define HidUsage_Pow_ConfigCurrent 0x41 +#define HidUsage_Pow_ConfigFrequency 0x42 +#define HidUsage_Pow_ConfigApparentPower 0x43 +#define HidUsage_Pow_ConfigActivePower 0x44 +#define HidUsage_Pow_ConfigPercentLoad 0x45 +#define HidUsage_Pow_ConfigTemperature 0x46 +#define HidUsage_Pow_ConfigHumidity 0x47 +#define HidUsage_Pow_SwitchOnControl 0x50 +#define HidUsage_Pow_SwitchOffControl 0x51 +#define HidUsage_Pow_ToggleControl 0x52 +#define HidUsage_Pow_LowVoltageTransfer 0x53 +#define HidUsage_Pow_HighVoltageTransfer 0x54 +#define HidUsage_Pow_DelayBeforeReboot 0x55 +#define HidUsage_Pow_DelayBeforeStartup 0x56 +#define HidUsage_Pow_DelayBeforeShutdown 0x57 +#define HidUsage_Pow_Test 0x58 +#define HidUsage_Pow_ModuleReset 0x59 +#define HidUsage_Pow_AudibleAlarmControl 0x5a +#define HidUsage_Pow_Present 0x60 +#define HidUsage_Pow_Good 0x61 +#define HidUsage_Pow_InternalFailure 0x62 +#define HidUsage_Pow_VoltagOutOfRange 0x63 +#define HidUsage_Pow_FrequencyOutOfRange 0x64 +#define HidUsage_Pow_Overload 0x65 +#define HidUsage_Pow_OverCharged 0x66 +#define HidUsage_Pow_OverTemperature 0x67 +#define HidUsage_Pow_ShutdownRequested 0x68 +#define HidUsage_Pow_ShutdownImminent 0x69 +#define HidUsage_Pow_SwitchOnOff 0x6b +#define HidUsage_Pow_Switchable 0x6c +#define HidUsage_Pow_Used 0x6d +#define HidUsage_Pow_Boost 0x6e +#define HidUsage_Pow_Buck 0x6f +#define HidUsage_Pow_Initialized 0x70 +#define HidUsage_Pow_Tested 0x71 +#define HidUsage_Pow_AwaitingPower 0x72 +#define HidUsage_Pow_CommunicationLost 0x73 +#define HidUsage_Pow_iManufacturer 0xfd +#define HidUsage_Pow_iProduct 0xfe +#define HidUsage_Pow_iSerialNumber 0xff +#define HidUsage_BS_SmartBatteryBatteryMode 0x01 +#define HidUsage_BS_SmartBatteryBatteryStatus 0x02 +#define HidUsage_BS_SmartBatteryAlarmWarning 0x03 +#define HidUsage_BS_SmartBatteryChargerMode 0x04 +#define HidUsage_BS_SmartBatteryChargerStatus 0x05 +#define HidUsage_BS_SmartBatteryChargerSpecInfo 0x06 +#define HidUsage_BS_SmartBatterySelectorState 0x07 +#define HidUsage_BS_SmartBatterySelectorPresets 0x08 +#define HidUsage_BS_SmartBatterySelectorInfo 0x09 +#define HidUsage_BS_OptionalMfgFunction1 0x10 +#define HidUsage_BS_OptionalMfgFunction2 0x11 +#define HidUsage_BS_OptionalMfgFunction3 0x12 +#define HidUsage_BS_OptionalMfgFunction4 0x13 +#define HidUsage_BS_OptionalMfgFunction5 0x14 +#define HidUsage_BS_ConnectionToSMBus 0x15 +#define HidUsage_BS_OutputConnection 0x16 +#define HidUsage_BS_ChargerConnection 0x17 +#define HidUsage_BS_BatteryInsertion 0x18 +#define HidUsage_BS_UseNext 0x19 +#define HidUsage_BS_OKToUse 0x1a +#define HidUsage_BS_BatterySupported 0x1b +#define HidUsage_BS_SelectorRevision 0x1c +#define HidUsage_BS_ChargingIndicator 0x1d +#define HidUsage_BS_ManufacturerAccess 0x28 +#define HidUsage_BS_RemainingCapacityLimit 0x29 +#define HidUsage_BS_RemainingTimeLimit 0x2a +#define HidUsage_BS_AtRate 0x2b +#define HidUsage_BS_CapacityMode 0x2c +#define HidUsage_BS_BroadcastToCharger 0x2d +#define HidUsage_BS_PrimaryBattery 0x2e +#define HidUsage_BS_ChargeController 0x2f +#define HidUsage_BS_TerminateCharge 0x40 +#define HidUsage_BS_TerminateDischarge 0x41 +#define HidUsage_BS_BelowRemainingCapacityLimit 0x42 +#define HidUsage_BS_RemainingTimeLimitExpired 0x43 +#define HidUsage_BS_Charging 0x44 +#define HidUsage_BS_Discharging 0x45 +#define HidUsage_BS_FullyCharged 0x46 +#define HidUsage_BS_FullyDischarged 0x47 +#define HidUsage_BS_ConditioningFlag 0x48 +#define HidUsage_BS_AtRateOK 0x49 +#define HidUsage_BS_SmartBatteryErrorCode 0x4a +#define HidUsage_BS_NeedReplacement 0x4b +#define HidUsage_BS_AtRateTimeToFull 0x60 +#define HidUsage_BS_AtRateTimeToEmpty 0x61 +#define HidUsage_BS_AverageCurrent 0x62 +#define HidUsage_BS_MaxError 0x63 +#define HidUsage_BS_RelativeStateOfCharge 0x64 +#define HidUsage_BS_AbsoluteStateOfCharge 0x65 +#define HidUsage_BS_RemainingCapacity 0x66 +#define HidUsage_BS_FullChargeCapacity 0x67 +#define HidUsage_BS_RunTimeToEmpty 0x68 +#define HidUsage_BS_AverageTimeToEmpty 0x69 +#define HidUsage_BS_AverageTimeToFull 0x6a +#define HidUsage_BS_CycleCount 0x6b +#define HidUsage_BS_BatteryPackModelLevel 0x80 +#define HidUsage_BS_InternalChargeController 0x81 +#define HidUsage_BS_PrimaryBatterySupport 0x82 +#define HidUsage_BS_DesignCapacity 0x83 +#define HidUsage_BS_SpecificationInfo 0x84 +#define HidUsage_BS_ManufactureDate 0x85 +#define HidUsage_BS_SerialNumber 0x86 +#define HidUsage_BS_iManufacturerName 0x87 +#define HidUsage_BS_iDeviceName 0x88 +#define HidUsage_BS_iDeviceChemistry 0x89 +#define HidUsage_BS_ManufacturerData 0x8a +#define HidUsage_BS_Rechargeable 0x8b +#define HidUsage_BS_WarningCapacityLimit 0x8c +#define HidUsage_BS_CapacityGranularity1 0x8d +#define HidUsage_BS_CapacityGranularity2 0x8e +#define HidUsage_BS_iOEMInformation 0x8f +#define HidUsage_BS_InhibitCharge 0xc0 +#define HidUsage_BS_EnablePolling 0xc1 +#define HidUsage_BS_ResetToZero 0xc2 +#define HidUsage_BS_ACPresent 0xd0 +#define HidUsage_BS_BatteryPresent 0xd1 +#define HidUsage_BS_PowerFail 0xd2 +#define HidUsage_BS_AlarmInhibited 0xd3 +#define HidUsage_BS_ThermistorUnderRange 0xd4 +#define HidUsage_BS_ThermistorHot 0xd5 +#define HidUsage_BS_ThermistorCold 0xd6 +#define HidUsage_BS_ThermistorOverRange 0xd7 +#define HidUsage_BS_VoltageOutOfRange 0xd8 +#define HidUsage_BS_CurrentOutOfRange 0xd9 +#define HidUsage_BS_CurrentNotRegulated 0xda +#define HidUsage_BS_VoltageNotRegulated 0xdb +#define HidUsage_BS_MasterMode 0xdc +#define HidUsage_BS_ChargerSelectorSupport 0xf0 +#define HidUsage_BS_ChargerSpec 0xf1 +#define HidUsage_BS_Level2 0xf2 +#define HidUsage_BS_Level3 0xf3 +#define HidUsage_BS_BarcodeBadgeReader 0x01 +#define HidUsage_BS_BarcodeScanner 0x02 +#define HidUsage_BS_DumbBarCodeScanner 0x03 +#define HidUsage_BS_CordlessScannerBase 0x04 +#define HidUsage_BS_BarCodeScannerCradle 0x05 +#define HidUsage_BS_AttributeReport 0x10 +#define HidUsage_BS_SettingsReport 0x11 +#define HidUsage_BS_ScannedDataReport 0x12 +#define HidUsage_BS_RawScannedDataReport 0x13 +#define HidUsage_BS_TriggerReport 0x14 +#define HidUsage_BS_StatusReport 0x15 +#define HidUsage_BS_UPCEANControlReport 0x16 +#define HidUsage_BS_EAN23LabelControlReport 0x17 +#define HidUsage_BS_Code39ControlReport 0x18 +#define HidUsage_BS_Interleaved2of5ControlReport 0x19 +#define HidUsage_BS_Standard2of5ControlReport 0x1a +#define HidUsage_BS_MSIPlesseyControlReport 0x1b +#define HidUsage_BS_CodabarControlReport 0x1c +#define HidUsage_BS_Code128ControlReport 0x1d +#define HidUsage_BS_Misc1DControlReport 0x1e +#define HidUsage_BS_TwoDControlReport 0x1f +#define HidUsage_BS_AimingPointerMode 0x30 +#define HidUsage_BS_BarCodePresentSensor 0x31 +#define HidUsage_BS_Class1ALaser 0x32 +#define HidUsage_BS_Class2Laser 0x33 +#define HidUsage_BS_HeaterPresent 0x34 +#define HidUsage_BS_ContactScanner 0x35 +#define HidUsage_BS_ElectronicArticleSurveillanceNotification 0x36 +#define HidUsage_BS_ConstantElectronicArticleSurveillance 0x37 +#define HidUsage_BS_ErrorIndication 0x38 +#define HidUsage_BS_FixedBeeper 0x39 +#define HidUsage_BS_GoodDecodeIndication 0x3a +#define HidUsage_BS_HandsFreeScanning 0x3b +#define HidUsage_BS_IntrinsicallySafe 0x3c +#define HidUsage_BS_KlasseEinsLaser 0x3d +#define HidUsage_BS_LongRangeScanner 0x3e +#define HidUsage_BS_MirrorSpeedControl 0x3f +#define HidUsage_BS_NotOnFileIndication 0x40 +#define HidUsage_BS_ProgrammableBeeper 0x41 +#define HidUsage_BS_Triggerless 0x42 +#define HidUsage_BS_Wand 0x43 +#define HidUsage_BS_WaterResistant 0x44 +#define HidUsage_BS_MultiRangeScanner 0x45 +#define HidUsage_BS_ProximitySensor 0x46 +#define HidUsage_BS_FragmentDecoding 0x4d +#define HidUsage_BS_ScannerReadConfidence 0x4e +#define HidUsage_BS_DataPrefix 0x4f +#define HidUsage_BS_PrefixAIMI 0x50 +#define HidUsage_BS_PrefixNone 0x51 +#define HidUsage_BS_PrefixProprietary 0x52 +#define HidUsage_BS_ActiveTime 0x55 +#define HidUsage_BS_AimingLaserPattern 0x56 +#define HidUsage_BS_BarCodePresent 0x57 +#define HidUsage_BS_BeeperState 0x58 +#define HidUsage_BS_LaserOnTime 0x59 +#define HidUsage_BS_LaserState 0x5a +#define HidUsage_BS_LockoutTime 0x5b +#define HidUsage_BS_MotorState 0x5c +#define HidUsage_BS_MotorTimeout 0x5d +#define HidUsage_BS_PowerOnResetScanner 0x5e +#define HidUsage_BS_PreventReadofBarcodes 0x5f +#define HidUsage_BS_InitiateBarcodeRead 0x60 +#define HidUsage_BS_TriggerState 0x61 +#define HidUsage_BS_TriggerMode 0x62 +#define HidUsage_BS_TriggerModeBlinkingLaserOn 0x63 +#define HidUsage_BS_TriggerModeContinuousLaserOn 0x64 +#define HidUsage_BS_TriggerModeLaseronwhilePulled 0x65 +#define HidUsage_BS_TriggerModeLaserstaysonafterrelease 0x66 +#define HidUsage_BS_CommitParameterstoNVM 0x6d +#define HidUsage_BS_ParameterScanning 0x6e +#define HidUsage_BS_ParametersChanged 0x6f +#define HidUsage_BS_Setparameterdefaultvalues 0x70 +#define HidUsage_BS_ScannerInCradle 0x75 +#define HidUsage_BS_ScannerInRange 0x76 +#define HidUsage_BS_AimDuration 0x7a +#define HidUsage_BS_GoodReadLampDuration 0x7b +#define HidUsage_BS_GoodReadLampIntensity 0x7c +#define HidUsage_BS_GoodReadLED 0x7d +#define HidUsage_BS_GoodReadToneFrequency 0x7e +#define HidUsage_BS_GoodReadToneLength 0x7f +#define HidUsage_BS_GoodReadToneVolume 0x80 +#define HidUsage_BS_NoReadMessage 0x82 +#define HidUsage_BS_NotonFileVolume 0x83 +#define HidUsage_BS_PowerupBeep 0x84 +#define HidUsage_BS_SoundErrorBeep 0x85 +#define HidUsage_BS_SoundGoodReadBeep 0x86 +#define HidUsage_BS_SoundNotOnFileBeep 0x87 +#define HidUsage_BS_GoodReadWhentoWrite 0x88 +#define HidUsage_BS_GRWTIAfterDecode 0x89 +#define HidUsage_BS_GRWTIBeepLampaftertransmit 0x8a +#define HidUsage_BS_GRWTINoBeepLampuseatall 0x8b +#define HidUsage_BS_BooklandEAN 0x91 +#define HidUsage_BS_ConvertEAN8to13Type 0x92 +#define HidUsage_BS_ConvertUPCAtoEAN13 0x93 +#define HidUsage_BS_ConvertUPCEtoA 0x94 +#define HidUsage_BS_EAN13 0x95 +#define HidUsage_BS_EAN8 0x96 +#define HidUsage_BS_EAN99128Mandatory 0x97 +#define HidUsage_BS_EAN99P5128Optional 0x98 +#define HidUsage_BS_EnableEANTwoLabel 0x99 +#define HidUsage_BS_UPCEAN 0x9a +#define HidUsage_BS_UPCEANCouponCode 0x9b +#define HidUsage_BS_UPCEANPeriodicals 0x9c +#define HidUsage_BS_UPCA 0x9d +#define HidUsage_BS_UPCAwith128Mandatory 0x9e +#define HidUsage_BS_UPCAwith128Optional 0x9f +#define HidUsage_BS_UPCAwithP5Optional 0xa0 +#define HidUsage_BS_UPCE 0xa1 +#define HidUsage_BS_UPCE1 0xa2 +#define HidUsage_BS_Periodical 0xa9 +#define HidUsage_BS_PeriodicalAutoDiscriminatePlus2 0xaa +#define HidUsage_BS_PeriodicalOnlyDecodewithPlus2 0xab +#define HidUsage_BS_PeriodicalIgnorePlus2 0xac +#define HidUsage_BS_PeriodicalAutoDiscriminatePlus5 0xad +#define HidUsage_BS_PeriodicalOnlyDecodewithPlus5 0xae +#define HidUsage_BS_PeriodicalIgnorePlus5 0xaf +#define HidUsage_BS_Check 0xb0 +#define HidUsage_BS_CheckDisablePrice 0xb1 +#define HidUsage_BS_CheckEnable4digitPrice 0xb2 +#define HidUsage_BS_CheckEnable5digitPrice 0xb3 +#define HidUsage_BS_CheckEnableEuropean4digitPrice 0xb4 +#define HidUsage_BS_CheckEnableEuropean5digitPrice 0xb5 +#define HidUsage_BS_EANTwoLabel 0xb7 +#define HidUsage_BS_EANThreeLabel 0xb8 +#define HidUsage_BS_EAN8FlagDigit1 0xb9 +#define HidUsage_BS_EAN8FlagDigit2 0xba +#define HidUsage_BS_EAN8FlagDigit3 0xbb +#define HidUsage_BS_EAN13FlagDigit1 0xbc +#define HidUsage_BS_EAN13FlagDigit2 0xbd +#define HidUsage_BS_EAN13FlagDigit3 0xbe +#define HidUsage_BS_AddEAN23LabelDefinition 0xbf +#define HidUsage_BS_ClearallEAN23LabelDefinitions 0xc0 +#define HidUsage_BS_Codabar 0xc3 +#define HidUsage_BS_Code128 0xc4 +#define HidUsage_BS_Code39 0xc7 +#define HidUsage_BS_Code93 0xc8 +#define HidUsage_BS_FullASCIIConversion 0xc9 +#define HidUsage_BS_Interleaved2of5 0xca +#define HidUsage_BS_ItalianPharmacyCode 0xcb +#define HidUsage_BS_MSIPlessey 0xcc +#define HidUsage_BS_Standard2of5IATA 0xcd +#define HidUsage_BS_Standard2of5 0xce +#define HidUsage_BS_TransmitStartStop 0xd3 +#define HidUsage_BS_TriOptic 0xd4 +#define HidUsage_BS_UCCEAN128 0xd5 +#define HidUsage_BS_CheckDigit 0xd6 +#define HidUsage_BS_CheckDigitDisable 0xd7 +#define HidUsage_BS_CheckDigitEnableInterleaved2of5OPCC 0xd8 +#define HidUsage_BS_CheckDigitEnableInterleaved2of5USS 0xd9 +#define HidUsage_BS_CheckDigitEnableStandard2of5OPCC 0xda +#define HidUsage_BS_CheckDigitEnableStandard2of5USS 0xdb +#define HidUsage_BS_CheckDigitEnableOneMSIPlessey 0xdc +#define HidUsage_BS_CheckDigitEnableTwoMSIPlessey 0xdd +#define HidUsage_BS_CheckDigitCodabarEnable 0xde +#define HidUsage_BS_CheckDigitCode39Enable 0xdf +#define HidUsage_BS_TransmitCheckDigit 0xf0 +#define HidUsage_BS_DisableCheckDigitTransmit 0xf1 +#define HidUsage_BS_EnableCheckDigitTransmit 0xf2 +#define HidUsage_BS_SymbologyIdentifier1 0xfb +#define HidUsage_BS_SymbologyIdentifier2 0xfc +#define HidUsage_BS_SymbologyIdentifier3 0xfd +#define HidUsage_BS_DecodedData 0xfe +#define HidUsage_BS_DecodeDataContinued 0xff +#define HidUsage_BS_BarSpaceData 0x100 +#define HidUsage_BS_ScannerDataAccuracy 0x101 +#define HidUsage_BS_RawDataPolarity 0x102 +#define HidUsage_BS_PolarityInvertedBarCode 0x103 +#define HidUsage_BS_PolarityNormalBarCode 0x104 +#define HidUsage_BS_MinimumLengthtoDecode 0x106 +#define HidUsage_BS_MaximumLengthtoDecode 0x107 +#define HidUsage_BS_DiscreteLengthtoDecode1 0x108 +#define HidUsage_BS_DiscreteLengthtoDecode2 0x109 +#define HidUsage_BS_DataLengthMethod 0x10a +#define HidUsage_BS_DLMethodReadany 0x10b +#define HidUsage_BS_DLMethodCheckinRange 0x10c +#define HidUsage_BS_DLMethodCheckforDiscrete 0x10d +#define HidUsage_BS_AztecCode 0x110 +#define HidUsage_BS_BC412 0x111 +#define HidUsage_BS_ChannelCode 0x112 +#define HidUsage_BS_Code16 0x113 +#define HidUsage_BS_Code32 0x114 +#define HidUsage_BS_Code49 0x115 +#define HidUsage_BS_CodeOne 0x116 +#define HidUsage_BS_Colorcode 0x117 +#define HidUsage_BS_DataMatrix 0x118 +#define HidUsage_BS_MaxiCode 0x119 +#define HidUsage_BS_MicroPDF 0x11a +#define HidUsage_BS_PDF417 0x11b +#define HidUsage_BS_PosiCode 0x11c +#define HidUsage_BS_QRCode 0x11d +#define HidUsage_BS_SuperCode 0x11e +#define HidUsage_BS_UltraCode 0x11f +#define HidUsage_BS_USD5SlugCode 0x120 +#define HidUsage_BS_VeriCode 0x121 +#define HidUsage_Sca_Scales 0x01 +#define HidUsage_Sca_ScaleDevice 0x20 +#define HidUsage_Sca_ScaleClass 0x21 +#define HidUsage_Sca_ScaleClassIMetric 0x22 +#define HidUsage_Sca_ScaleClassIIMetric 0x23 +#define HidUsage_Sca_ScaleClassIIIMetric 0x24 +#define HidUsage_Sca_ScaleClassIIILMetric 0x25 +#define HidUsage_Sca_ScaleClassIVMetric 0x26 +#define HidUsage_Sca_ScaleClassIIIEnglish 0x27 +#define HidUsage_Sca_ScaleClassIIILEnglish 0x28 +#define HidUsage_Sca_ScaleClassIVEnglish 0x29 +#define HidUsage_Sca_ScaleClassGeneric 0x2a +#define HidUsage_Sca_ScaleAttributeReport 0x30 +#define HidUsage_Sca_ScaleControlReport 0x31 +#define HidUsage_Sca_ScaleDataReport 0x32 +#define HidUsage_Sca_ScaleStatusReport 0x33 +#define HidUsage_Sca_ScaleWeightLimitReport 0x34 +#define HidUsage_Sca_ScaleStatisticsReport 0x35 +#define HidUsage_Sca_DataWeight 0x40 +#define HidUsage_Sca_DataScaling 0x41 +#define HidUsage_Sca_WeightUnit 0x50 +#define HidUsage_Sca_WeightUnitMilligram 0x51 +#define HidUsage_Sca_WeightUnitGram 0x52 +#define HidUsage_Sca_WeightUnitKilogram 0x53 +#define HidUsage_Sca_WeightUnitCarats 0x54 +#define HidUsage_Sca_WeightUnitTaels 0x55 +#define HidUsage_Sca_WeightUnitGrains 0x56 +#define HidUsage_Sca_WeightUnitPennyweights 0x57 +#define HidUsage_Sca_WeightUnitMetricTon 0x58 +#define HidUsage_Sca_WeightUnitAvoirTon 0x59 +#define HidUsage_Sca_WeightUnitTroyOunce 0x5a +#define HidUsage_Sca_WeightUnitOunce 0x5b +#define HidUsage_Sca_WeightUnitPound 0x5c +#define HidUsage_Sca_CalibrationCount 0x60 +#define HidUsage_Sca_ReZeroCount 0x61 +#define HidUsage_Sca_ScaleStatus 0x70 +#define HidUsage_Sca_ScaleStatusFault 0x71 +#define HidUsage_Sca_ScaleStatusStableatCenterofZero 0x72 +#define HidUsage_Sca_ScaleStatusInMotion 0x73 +#define HidUsage_Sca_ScaleStatusWeightStable 0x74 +#define HidUsage_Sca_ScaleStatusUnderZero 0x75 +#define HidUsage_Sca_ScaleStatusOverWeightLimit 0x76 +#define HidUsage_Sca_ScaleStatusRequiresCalibration 0x77 +#define HidUsage_Sca_ScaleStatusRequiresRezeroing 0x78 +#define HidUsage_Sca_ZeroScale 0x80 +#define HidUsage_Sca_EnforcedZeroReturn 0x81 +#define HidUsage_MSR_MSRDeviceReadOnly 0x01 +#define HidUsage_MSR_Track1Length 0x11 +#define HidUsage_MSR_Track2Length 0x12 +#define HidUsage_MSR_Track3Length 0x13 +#define HidUsage_MSR_TrackJISLength 0x14 +#define HidUsage_MSR_TrackData 0x20 +#define HidUsage_MSR_Track1Data 0x21 +#define HidUsage_MSR_Track2Data 0x22 +#define HidUsage_MSR_Track3Data 0x23 +#define HidUsage_MSR_TrackJISData 0x24 +#define HidUsage_CC_CameraAutofocus 0x20 +#define HidUsage_CC_CameraShutter 0x21 +#define HidUsage_Arc_GeneralPurposeIOCard 0x01 +#define HidUsage_Arc_CoinDoor 0x02 +#define HidUsage_Arc_WatchdogTimer 0x03 +#define HidUsage_Arc_GeneralPurposeAnalogInputState 0x30 +#define HidUsage_Arc_GeneralPurposeDigitalInputState 0x31 +#define HidUsage_Arc_GeneralPurposeOpticalInputState 0x32 +#define HidUsage_Arc_GeneralPurposeDigitalOutputState 0x33 +#define HidUsage_Arc_NumberofCoinDoors 0x34 +#define HidUsage_Arc_CoinDrawerDropCount 0x35 +#define HidUsage_Arc_CoinDrawerStart 0x36 +#define HidUsage_Arc_CoinDrawerService 0x37 +#define HidUsage_Arc_CoinDrawerTilt 0x38 +#define HidUsage_Arc_CoinDoorTest 0x39 +#define HidUsage_Arc_CoinDoorLockout 0x40 +#define HidUsage_Arc_WatchdogTimeout 0x41 +#define HidUsage_Arc_WatchdogAction 0x42 +#define HidUsage_Arc_WatchdogReboot 0x43 +#define HidUsage_Arc_WatchdogRestart 0x44 +#define HidUsage_Arc_AlarmInput 0x45 +#define HidUsage_Arc_CoinDoorCounter 0x46 +#define HidUsage_Arc_IODirectionMapping 0x47 +#define HidUsage_Arc_SetIODirectionMapping 0x48 +#define HidUsage_Arc_ExtendedOpticalInputState 0x49 +#define HidUsage_Arc_PinPadInputState 0x4a +#define HidUsage_Arc_PinPadStatus 0x4b +#define HidUsage_Arc_PinPadOutput 0x4c +#define HidUsage_Arc_PinPadCommand 0x4d +#define HidUsage_FIDOA_U2FAuthenticatorDevice 0x01 +#define HidUsage_FIDOA_InputReportData 0x20 +#define HidUsage_FIDOA_OutputReportData 0x21 diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index 21e55f3d0d1b..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); @@ -437,6 +439,9 @@ static int alps_raw_event(struct hid_device *hdev, int ret = 0; struct alps_dev *hdata = hid_get_drvdata(hdev); + if (!(hdev->claimed & HID_CLAIMED_INPUT) || !hdata->input) + return 0; + switch (hdev->product) { case HID_PRODUCT_ID_T4_BTNLESS: ret = t4_raw_event(hdata, data, size); @@ -735,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; @@ -759,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: @@ -820,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) }, @@ -842,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 9dcb252c5d6c..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) @@ -623,17 +628,19 @@ static int apple_fetch_battery(struct hid_device *hdev) struct apple_sc *asc = hid_get_drvdata(hdev); struct hid_report_enum *report_enum; struct hid_report *report; + struct hid_battery *bat; - if (!(asc->quirks & APPLE_RDESC_BATTERY) || !hdev->battery) + bat = hid_get_battery(hdev); + if (!(asc->quirks & APPLE_RDESC_BATTERY) || !bat) return -1; - report_enum = &hdev->report_enum[hdev->battery_report_type]; - report = report_enum->report_id_hash[hdev->battery_report_id]; + report_enum = &hdev->report_enum[bat->report_type]; + report = report_enum->report_id_hash[bat->report_id]; if (!report || report->maxfield < 1) return -1; - if (hdev->battery_capacity == hdev->battery_max) + if (bat->capacity == bat->max) return -1; hid_hw_request(hdev, report, HID_REQ_GET_REPORT); @@ -795,7 +802,7 @@ static int apple_backlight_set(struct hid_device *hdev, u16 value, u16 rate) int ret = 0; struct apple_backlight_set_report *rep; - rep = kmalloc(sizeof(*rep), GFP_KERNEL); + rep = kmalloc_obj(*rep); if (rep == NULL) return -ENOMEM; @@ -823,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; @@ -855,15 +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; + /* 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); @@ -926,6 +942,7 @@ static int apple_magic_backlight_init(struct hid_device *hdev) backlight->cdev.name = ":white:" LED_FUNCTION_KBD_BACKLIGHT; backlight->cdev.max_brightness = backlight->brightness->field[0]->logical_maximum; backlight->cdev.brightness_set_blocking = apple_magic_backlight_led_set; + backlight->cdev.flags = LED_CORE_SUSPENDRESUME; apple_magic_backlight_set(backlight, 0, 0); @@ -995,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 bc93b27f9b13..c38a2b3b33d9 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -20,10 +20,8 @@ * Copyright (c) 2016 Frederik Wenigwieser <frederik.wenigwieser@gmail.com> */ -/* - */ - #include <linux/acpi.h> +#include <linux/cleanup.h> #include <linux/dmi.h> #include <linux/hid.h> #include <linux/module.h> @@ -101,7 +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_ROG_NKEY_ID1ID2_INIT BIT(15) +#define QUIRK_FILTER_CAMERA_COMPANION BIT(15) #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ QUIRK_NO_INIT_REPORTS | \ @@ -112,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; }; @@ -136,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; @@ -144,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; }; @@ -208,6 +232,35 @@ static const struct asus_touchpad_info medion_e1239t_tp = { .report_size = 32 /* 2 byte header + 5 * 5 + 5 byte footer */, }; +static const u8 asus_report_id_init[] = { + FEATURE_KBD_REPORT_ID, + FEATURE_KBD_LED_REPORT_ID1, + 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) { @@ -328,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; } @@ -354,7 +453,8 @@ 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 && (usage->hid & HID_USAGE) != 0xff && !usage->type) { @@ -372,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; } @@ -386,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); @@ -411,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; } /* @@ -443,45 +546,46 @@ static int asus_raw_event(struct hid_device *hdev, /* * G713 and G733 send these codes on some keypresses, depending on * the key pressed it can trigger a shutdown event if not caught. - */ - if (data[0] == 0x02 && data[1] == 0x30) { + */ + if (data[0] == 0x02 && data[1] == 0x30) return -1; - } } if (drvdata->quirks & QUIRK_ROG_CLAYMORE_II_KEYBOARD) { /* * CLAYMORE II keyboard sends this packet when it goes to sleep * this causes the whole system to go into suspend. - */ - - if(size == 2 && data[0] == 0x02 && data[1] == 0x00) { + */ + if (size == 2 && data[0] == 0x02 && data[1] == 0x00) 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; } static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t buf_size) { - unsigned char *dmabuf; - int ret; - - dmabuf = kmemdup(buf, buf_size, GFP_KERNEL); + u8 *dmabuf __free(kfree) = kmemdup(buf, buf_size, GFP_KERNEL); if (!dmabuf) return -ENOMEM; /* * The report ID should be set from the incoming buffer due to LED and key * interfaces having different pages - */ - ret = hid_hw_raw_request(hdev, buf[0], dmabuf, - buf_size, HID_FEATURE_REPORT, - HID_REQ_SET_REPORT); - kfree(dmabuf); - - return ret; + */ + return hid_hw_raw_request(hdev, buf[0], dmabuf, buf_size, HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); } static int asus_kbd_init(struct hid_device *hdev, u8 report_id) @@ -576,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; - asus_schedule_work(led); + switch (action->type) { + case BRIGHTNESS_SET: + asus_kbd_set_brightness(worker->hdev, action->data.brightness); + break; + case FN_LOCK_SYNC: + asus_kbd_set_fn_lock(worker->hdev, action->data.fn_lock); + break; + case WMI_FAN: + asus_kbd_wmi_fan(worker->hdev, &action->data.fan_hid_data); + break; + default: + hid_err(worker->hdev, "Invalid action type: %d\n", action->type); + break; + } + + kfree(action); + + /* Re-schedule if there are more pending actions */ + spin_lock_irqsave(&worker->lock, flags); + if (!list_empty(&worker->actions)) + schedule_work(&worker->work); + spin_unlock_irqrestore(&worker->lock, flags); } -static 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); } /* @@ -651,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); @@ -722,6 +924,21 @@ static void validate_mcu_fw_version(struct hid_device *hdev, int idProduct) } } +static bool asus_has_report_id(struct hid_device *hdev, u16 report_id) +{ + struct hid_report *report; + int t; + + for (t = HID_INPUT_REPORT; t <= HID_FEATURE_REPORT; t++) { + list_for_each_entry(report, &hdev->report_enum[t].report_list, list) { + if (report->id == report_id) + return true; + } + } + + return false; +} + static int asus_kbd_register_leds(struct hid_device *hdev) { struct asus_drvdata *drvdata = hid_get_drvdata(hdev); @@ -730,10 +947,6 @@ static int asus_kbd_register_leds(struct hid_device *hdev) unsigned char kbd_func; int ret; - ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID); - if (ret < 0) - return ret; - /* Get keyboard functions */ ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID); if (ret < 0) @@ -743,41 +956,24 @@ static int asus_kbd_register_leds(struct hid_device *hdev) if (!(kbd_func & SUPPORT_KBD_BACKLIGHT)) return -ENODEV; - if (drvdata->quirks & QUIRK_ROG_NKEY_ID1ID2_INIT) { - asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1); - asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2); - } - if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) { ret = asus_kbd_disable_oobe(hdev); if (ret < 0) 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; @@ -999,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; } @@ -1046,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; @@ -1055,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 */ @@ -1163,22 +1360,19 @@ static int asus_start_multitouch(struct hid_device *hdev) return 0; } -static int __maybe_unused asus_resume(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,14 +1480,32 @@ 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; } + for (int r = 0; r < ARRAY_SIZE(asus_report_id_init); r++) { + if (asus_has_report_id(hdev, asus_report_id_init[r])) { + ret = asus_kbd_init(hdev, asus_report_id_init[r]); + if (ret < 0) + hid_warn(hdev, "Failed to initialize 0x%x: %d.\n", + asus_report_id_init[r], ret); + } + } + + /* Laptops keyboard backlight is always at 0x5a */ if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) && - asus_kbd_register_leds(hdev)) + (asus_has_report_id(hdev, FEATURE_KBD_REPORT_ID)) && + (asus_kbd_register_leds(hdev))) hid_warn(hdev, "Failed to initialize backlight.\n"); /* @@ -1311,26 +1521,25 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) * were freed during registration due to no usages being mapped, * leaving drvdata->input pointing to freed memory. */ - if (!drvdata->input || !(hdev->claimed & HID_CLAIMED_INPUT)) { - hid_err(hdev, "Asus input not registered\n"); - ret = -ENOMEM; - goto err_stop_hw; - } - - if (drvdata->tp) { - drvdata->input->name = "Asus TouchPad"; - } else { - drvdata->input->name = "Asus Keyboard"; - } + if (drvdata->input && (hdev->claimed & HID_CLAIMED_INPUT)) { + if (drvdata->tp) + drvdata->input->name = "Asus TouchPad"; + else + drvdata->input->name = "Asus Keyboard"; - if (drvdata->tp) { - ret = asus_start_multitouch(hdev); - if (ret) - goto err_stop_hw; + if (drvdata->tp) { + ret = asus_start_multitouch(hdev); + if (ret) + goto err_stop_hw; + } } 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; } @@ -1338,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); } @@ -1472,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 }, @@ -1484,10 +1686,13 @@ static const struct hid_device_id asus_devices[] = { QUIRK_USE_KBD_BACKLIGHT }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD), - QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_ID1ID2_INIT }, + QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, { 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 | QUIRK_ROG_NKEY_ID1ID2_INIT }, + 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 fbe4e16ab029..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; @@ -96,17 +90,17 @@ static int axff_init(struct hid_device *hid) return -ENODEV; } - axff = kzalloc(sizeof(struct axff_device), GFP_KERNEL); + axff = kzalloc_obj(struct axff_device); 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 a6d5f030d023..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: * ----------------------------------------- @@ -100,10 +93,11 @@ static int betopff_init(struct hid_device *hid) } } - betopff = kzalloc(sizeof(*betopff), GFP_KERNEL); + betopff = kzalloc_obj(*betopff); 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-cmedia.c b/drivers/hid/hid-cmedia.c index 8bf5649b0c79..7b3dd4197875 100644 --- a/drivers/hid/hid-cmedia.c +++ b/drivers/hid/hid-cmedia.c @@ -145,7 +145,7 @@ static int cmhid_probe(struct hid_device *hid, const struct hid_device_id *id) int ret; struct cmhid *cm; - cm = kzalloc(sizeof(struct cmhid), GFP_KERNEL); + cm = kzalloc_obj(struct cmhid); if (!cm) { ret = -ENOMEM; goto allocfail; diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index f5587b786f87..a3ff0514f9cd 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -71,6 +71,9 @@ static u32 s32ton(__s32 value, unsigned int n) if (!value || !n) return 0; + if (n > 32) + n = 32; + a = value >> (n - 1); if (a && a != -1) return value < 0 ? 1 << (n - 1) : (1 << (n - 1)) - 1; @@ -93,7 +96,7 @@ struct hid_report *hid_register_report(struct hid_device *device, if (report_enum->report_id_hash[id]) return report_enum->report_id_hash[id]; - report = kzalloc(sizeof(struct hid_report), GFP_KERNEL); + report = kzalloc_obj(struct hid_report); if (!report) return NULL; @@ -376,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; @@ -386,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; @@ -924,7 +933,6 @@ static int hid_scan_main(struct hid_parser *parser, struct hid_item *item) */ static int hid_scan_report(struct hid_device *hid) { - struct hid_parser *parser; struct hid_item item; const __u8 *start = hid->dev_rdesc; const __u8 *end = start + hid->dev_rsize; @@ -936,7 +944,7 @@ static int hid_scan_report(struct hid_device *hid) hid_parser_reserved }; - parser = vzalloc(sizeof(struct hid_parser)); + struct hid_parser *parser __free(kvfree) = vzalloc(sizeof(*parser)); if (!parser) return -ENOMEM; @@ -987,7 +995,6 @@ static int hid_scan_report(struct hid_device *hid) } kfree(parser->collection_stack); - vfree(parser); return 0; } @@ -1244,6 +1251,90 @@ void hid_setup_resolution_multiplier(struct hid_device *hid) } EXPORT_SYMBOL_GPL(hid_setup_resolution_multiplier); +static int hid_parse_collections(struct hid_device *device) +{ + struct hid_item item; + const u8 *start = device->rdesc; + const u8 *end = start + device->rsize; + const u8 *next; + int ret; + static typeof(hid_parser_main) (* const dispatch_type[]) = { + hid_parser_main, + hid_parser_global, + hid_parser_local, + hid_parser_reserved + }; + + struct hid_parser *parser __free(kvfree) = vzalloc(sizeof(*parser)); + if (!parser) + return -ENOMEM; + + parser->device = device; + + device->collection = kzalloc_objs(*device->collection, + HID_DEFAULT_NUM_COLLECTIONS); + if (!device->collection) + return -ENOMEM; + + device->collection_size = HID_DEFAULT_NUM_COLLECTIONS; + for (unsigned int i = 0; i < HID_DEFAULT_NUM_COLLECTIONS; i++) + device->collection[i].parent_idx = -1; + + ret = -EINVAL; + if (start == end) { + hid_err(device, "rejecting 0-sized report descriptor\n"); + goto out; + } + + while ((next = fetch_item(start, end, &item)) != NULL) { + start = next; + + if (item.format != HID_ITEM_FORMAT_SHORT) { + hid_err(device, "unexpected long global item\n"); + goto out; + } + + if (dispatch_type[item.type](parser, &item)) { + hid_err(device, "item %u %u %u %u parsing failed\n", + item.format, + (unsigned int)item.size, + (unsigned int)item.type, + (unsigned int)item.tag); + goto out; + } + } + + if (start != end) { + hid_err(device, "item fetching failed at offset %u/%u\n", + device->rsize - (unsigned int)(end - start), + device->rsize); + goto out; + } + + if (parser->collection_stack_ptr) { + hid_err(device, "unbalanced collection at end of report description\n"); + goto out; + } + + if (parser->local.delimiter_depth) { + hid_err(device, "unbalanced delimiter at end of report description\n"); + goto out; + } + + /* + * fetch initial values in case the device's + * default multiplier isn't the recommended 1 + */ + hid_setup_resolution_multiplier(device); + + device->status |= HID_STAT_PARSED; + ret = 0; + +out: + kfree(parser->collection_stack); + return ret; +} + /** * hid_open_report - open a driver-specific device report * @@ -1258,21 +1349,9 @@ EXPORT_SYMBOL_GPL(hid_setup_resolution_multiplier); */ int hid_open_report(struct hid_device *device) { - struct hid_parser *parser; - struct hid_item item; unsigned int size; - const __u8 *start; - const __u8 *end; - const __u8 *next; - int ret; - int i; - static int (*dispatch_type[])(struct hid_parser *parser, - struct hid_item *item) = { - hid_parser_main, - hid_parser_global, - hid_parser_local, - hid_parser_reserved - }; + const u8 *start; + int error; if (WARN_ON(device->status & HID_STAT_PARSED)) return -EBUSY; @@ -1288,9 +1367,9 @@ int hid_open_report(struct hid_device *device) * on a copy of our report descriptor so it can * change it. */ - __u8 *buf = kmemdup(start, size, GFP_KERNEL); + u8 *buf __free(kfree) = kmemdup(start, size, GFP_KERNEL); - if (buf == NULL) + if (!buf) return -ENOMEM; start = device->driver->report_fixup(device, buf, &size); @@ -1301,82 +1380,20 @@ int hid_open_report(struct hid_device *device) * needs to be cleaned up or not at the end. */ start = kmemdup(start, size, GFP_KERNEL); - kfree(buf); - if (start == NULL) + if (!start) return -ENOMEM; } device->rdesc = start; device->rsize = size; - parser = vzalloc(sizeof(struct hid_parser)); - if (!parser) { - ret = -ENOMEM; - goto alloc_err; - } - - parser->device = device; - - end = start + size; - - device->collection = kcalloc(HID_DEFAULT_NUM_COLLECTIONS, - sizeof(struct hid_collection), GFP_KERNEL); - if (!device->collection) { - ret = -ENOMEM; - goto err; - } - device->collection_size = HID_DEFAULT_NUM_COLLECTIONS; - for (i = 0; i < HID_DEFAULT_NUM_COLLECTIONS; i++) - device->collection[i].parent_idx = -1; - - ret = -EINVAL; - while ((next = fetch_item(start, end, &item)) != NULL) { - start = next; - - if (item.format != HID_ITEM_FORMAT_SHORT) { - hid_err(device, "unexpected long global item\n"); - goto err; - } - - if (dispatch_type[item.type](parser, &item)) { - hid_err(device, "item %u %u %u %u parsing failed\n", - item.format, (unsigned)item.size, - (unsigned)item.type, (unsigned)item.tag); - goto err; - } - - if (start == end) { - if (parser->collection_stack_ptr) { - hid_err(device, "unbalanced collection at end of report description\n"); - goto err; - } - if (parser->local.delimiter_depth) { - hid_err(device, "unbalanced delimiter at end of report description\n"); - goto err; - } - - /* - * fetch initial values in case the device's - * default multiplier isn't the recommended 1 - */ - hid_setup_resolution_multiplier(device); - - kfree(parser->collection_stack); - vfree(parser); - device->status |= HID_STAT_PARSED; - - return 0; - } + error = hid_parse_collections(device); + if (error) { + hid_close_report(device); + return error; } - hid_err(device, "item fetching failed at offset %u/%u\n", - size - (unsigned int)(end - start), size); -err: - kfree(parser->collection_stack); -alloc_err: - vfree(parser); - hid_close_report(device); - return ret; + return 0; } EXPORT_SYMBOL_GPL(hid_open_report); @@ -1796,7 +1813,7 @@ static void hid_report_process_ordering(struct hid_device *hid, } /* allocate the memory to process the fields */ - entries = kcalloc(count, sizeof(*entries), GFP_KERNEL); + entries = kzalloc_objs(*entries, count); if (!entries) return; @@ -1922,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); @@ -1989,11 +2007,11 @@ static struct hid_report *hid_get_report(struct hid_report_enum *report_enum, int __hid_request(struct hid_device *hid, struct hid_report *report, enum hid_class_request reqtype) { - char *buf, *data_buf; + u8 *data_buf; int ret; u32 len; - buf = hid_alloc_report_buf(report, GFP_KERNEL); + u8 *buf __free(kfree) = hid_alloc_report_buf(report, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -2012,38 +2030,49 @@ int __hid_request(struct hid_device *hid, struct hid_report *report, ret = hid_hw_raw_request(hid, report->id, buf, len, report->type, reqtype); if (ret < 0) { dbg_hid("unable to complete request: %d\n", ret); - goto out; + return ret; } if (reqtype == HID_REQ_GET_REPORT) hid_input_report(hid, report->type, buf, ret, 0); - ret = 0; - -out: - kfree(buf); - return ret; + return 0; } 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); @@ -2056,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) @@ -2068,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) { @@ -2080,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; @@ -2113,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; @@ -2142,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) @@ -2160,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) { @@ -2259,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 && @@ -2282,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"); @@ -2394,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); } @@ -2611,7 +2674,7 @@ static ssize_t new_id_store(struct device_driver *drv, const char *buf, if (ret < 3) return -EINVAL; - dynid = kzalloc(sizeof(*dynid), GFP_KERNEL); + dynid = kzalloc_obj(*dynid); if (!dynid) return -ENOMEM; @@ -2789,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; @@ -2854,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, @@ -2866,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) { @@ -2888,6 +2996,11 @@ static int hid_uevent(const struct device *dev, struct kobj_uevent_env *env) if (add_uevent_var(env, "MODALIAS=hid:b%04Xg%04Xv%08Xp%08X", hdev->bus, hdev->group, hdev->vendor, hdev->product)) return -ENOMEM; + if (hdev->firmware_version) { + if (add_uevent_var(env, "HID_FIRMWARE_VERSION=0x%04llX", + hdev->firmware_version)) + return -ENOMEM; + } return 0; } @@ -2973,7 +3086,7 @@ struct hid_device *hid_allocate_device(void) struct hid_device *hdev; int ret = -ENOMEM; - hdev = kzalloc(sizeof(*hdev), GFP_KERNEL); + hdev = kzalloc_obj(*hdev); if (hdev == NULL) return ERR_PTR(ret); @@ -2991,6 +3104,10 @@ struct hid_device *hid_allocate_device(void) mutex_init(&hdev->ll_open_lock); kref_init(&hdev->ref); +#ifdef CONFIG_HID_BATTERY_STRENGTH + INIT_LIST_HEAD(&hdev->batteries); +#endif + ret = hid_bpf_device_init(hdev); if (ret) goto out_err; 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 62b99f5c3cf8..278c6efb565d 100644 --- a/drivers/hid/hid-corsair.c +++ b/drivers/hid/hid-corsair.c @@ -427,7 +427,7 @@ static int k90_init_backlight(struct hid_device *dev) size_t name_sz; char *name; - drvdata->backlight = kzalloc(sizeof(struct k90_led), GFP_KERNEL); + drvdata->backlight = kzalloc_obj(struct k90_led); if (!drvdata->backlight) { ret = -ENOMEM; goto fail_backlight_alloc; @@ -471,7 +471,7 @@ static int k90_init_macro_functions(struct hid_device *dev) size_t name_sz; char *name; - k90 = kzalloc(sizeof(struct k90_drvdata), GFP_KERNEL); + k90 = kzalloc_obj(struct k90_drvdata); if (!k90) { ret = -ENOMEM; goto fail_drvdata; @@ -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-cougar.c b/drivers/hid/hid-cougar.c index 5596dd940322..ad027c45f162 100644 --- a/drivers/hid/hid-cougar.c +++ b/drivers/hid/hid-cougar.c @@ -166,7 +166,7 @@ static int cougar_bind_shared_data(struct hid_device *hdev, shared = cougar_get_shared_data(hdev); if (!shared) { - shared = kzalloc(sizeof(*shared), GFP_KERNEL); + shared = kzalloc_obj(*shared); if (!shared) { error = -ENOMEM; goto out; 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-debug.c b/drivers/hid/hid-debug.c index c5865b0d2aaa..f44e6e708404 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -990,6 +990,9 @@ static const struct hid_usage_entry hid_usage_table[] = { { 0x0c, 0x01c9, "ALContactSync" }, { 0x0c, 0x01ca, "ALNavigation" }, { 0x0c, 0x01cb, "ALContextawareDesktopAssistant" }, + { 0x0c, 0x01cc, "ALActionOnSelection" }, + { 0x0c, 0x01cd, "ALContextualInsertion" }, + { 0x0c, 0x01ce, "ALContextualQuery" }, { 0x0c, 0x0200, "GenericGUIApplicationControls" }, { 0x0c, 0x0201, "ACNew" }, { 0x0c, 0x0202, "ACOpen" }, @@ -3375,6 +3378,9 @@ static const char *keys[KEY_MAX + 1] = { [KEY_BRIGHTNESS_MIN] = "BrightnessMin", [KEY_BRIGHTNESS_MAX] = "BrightnessMax", [KEY_BRIGHTNESS_AUTO] = "BrightnessAuto", + [KEY_ACTION_ON_SELECTION] = "ActionOnSelection", + [KEY_CONTEXTUAL_INSERT] = "ContextualInsert", + [KEY_CONTEXTUAL_QUERY] = "ContextualQuery", [KEY_KBDINPUTASSIST_PREV] = "KbdInputAssistPrev", [KEY_KBDINPUTASSIST_NEXT] = "KbdInputAssistNext", [KEY_KBDINPUTASSIST_PREVGROUP] = "KbdInputAssistPrevGroup", @@ -3681,7 +3687,7 @@ static int hid_debug_events_open(struct inode *inode, struct file *file) struct hid_debug_list *list; unsigned long flags; - if (!(list = kzalloc(sizeof(struct hid_debug_list), GFP_KERNEL))) { + if (!(list = kzalloc_obj(struct hid_debug_list))) { err = -ENOMEM; goto out; } diff --git a/drivers/hid/hid-dr.c b/drivers/hid/hid-dr.c index 84e1e90a266b..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; @@ -104,10 +101,11 @@ static int drff_init(struct hid_device *hid) return -ENODEV; } - drff = kzalloc(sizeof(struct drff_device), GFP_KERNEL); + drff = kzalloc_obj(struct drff_device); 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-elo.c b/drivers/hid/hid-elo.c index cf17bdd14d9c..b8f5f3eb53a4 100644 --- a/drivers/hid/hid-elo.c +++ b/drivers/hid/hid-elo.c @@ -232,7 +232,7 @@ static int elo_probe(struct hid_device *hdev, const struct hid_device_id *id) if (!hid_is_usb(hdev)) return -EINVAL; - priv = kzalloc(sizeof(*priv), GFP_KERNEL); + priv = kzalloc_obj(*priv); if (!priv) return -ENOMEM; diff --git a/drivers/hid/hid-emsff.c b/drivers/hid/hid-emsff.c index 60bfb6a924d7..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; @@ -76,10 +70,11 @@ static int emsff_init(struct hid_device *hid) return -ENODEV; } - emsff = kzalloc(sizeof(struct emsff_device), GFP_KERNEL); + emsff = kzalloc_obj(struct emsff_device); 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 c6db8b6cc8ee..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; @@ -96,10 +87,11 @@ static int gaff_init(struct hid_device *hid) return -ENODEV; } - gaff = kzalloc(sizeof(struct gaff_device), GFP_KERNEL); + gaff = kzalloc_obj(struct gaff_device); 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 4c1ccf7a267a..c99c3c0d442e 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -59,8 +59,7 @@ static int cbas_ec_query_base(struct cros_ec_device *ec_dev, bool get_state, struct cros_ec_command *msg; int ret; - msg = kzalloc(struct_size(msg, data, max(sizeof(u32), sizeof(*params))), - GFP_KERNEL); + msg = kzalloc_flex(*msg, data, max(sizeof(u32), sizeof(*params))); if (!msg) return -ENOMEM; @@ -497,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 fc8a9997f815..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); @@ -474,8 +467,7 @@ int hid_haptic_init(struct hid_device *hdev, ret = -ENOMEM; goto duration_map; } - haptic->effect = kcalloc(FF_MAX_EFFECTS, - sizeof(struct hid_haptic_effect), GFP_KERNEL); + haptic->effect = kzalloc_objs(struct hid_haptic_effect, FF_MAX_EFFECTS); if (!haptic->effect) { ret = -ENOMEM; goto output_queue; @@ -536,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 8619b80c834c..4834d42b2fa6 100644 --- a/drivers/hid/hid-holtekff.c +++ b/drivers/hid/hid-holtekff.c @@ -120,36 +120,30 @@ 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; } - holtekff = kzalloc(sizeof(*holtekff), GFP_KERNEL); + holtekff = kzalloc_obj(*holtekff); if (!holtekff) return -ENOMEM; @@ -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 new file mode 100644 index 000000000000..ee3fc6f68475 --- /dev/null +++ b/drivers/hid/hid-huawei.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * HID driver for some huawei "special" devices + * + * Copyright (c) 2026 Miao Li <limiao@kylinos.cn> + */ + +#include <linux/device.h> +#include <linux/hid.h> +#include <linux/module.h> +#include <linux/usb.h> + +#include "hid-ids.h" + +static const __u8 huawei_cd30_kbd_rdesc_fixed[] = { + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x80, /* Usage (System Control) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x85, 0x01, /* Report ID (1) */ + 0x19, 0x81, /* Usage Minimum (System Power Down) */ + 0x29, 0x83, /* Usage Maximum (System Wake Up) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x75, 0x01, /* Report Size (1 bit) */ + 0x95, 0x03, /* Report Count (3) */ + 0x81, 0x02, /* Input (Data,Var,Abs) */ + 0x95, 0x05, /* Report Count (5) */ + 0x81, 0x01, /* Input (Cnst,Ary,Abs) */ + 0xc0, /* End Collection */ + 0x05, 0x0c, /* Usage Page (Consumer) */ + 0x09, 0x01, /* Usage (Consumer Control) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x85, 0x02, /* Report ID (2) */ + 0x19, 0x00, /* Usage Minimum (0) */ + 0x2a, 0x3c, 0x02, /* Usage Maximum (0x023C) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0x3c, 0x02, /* Logical Maximum (0x023C) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x10, /* Report Size (16 bits) */ + 0x81, 0x00, /* Input (Data,Ary,Abs) */ + 0xc0 /* End Collection */ +}; + +static const __u8 *huawei_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + 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 || 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) { + hid_info(hdev, "Replacing Huawei cd30 keyboard report descriptor.\n"); + *rsize = sizeof(huawei_cd30_kbd_rdesc_fixed); + return huawei_cd30_kbd_rdesc_fixed; + } + } + break; + } + + return rdesc; +} + +static const struct hid_device_id huawei_devices[] = { + /* HUAWEI cd30 keyboard */ + { HID_USB_DEVICE(USB_VENDOR_ID_HUAWEI, USB_DEVICE_ID_HUAWEI_CD30KBD)}, + { } +}; +MODULE_DEVICE_TABLE(hid, huawei_devices); + +static struct hid_driver huawei_driver = { + .name = "huawei", + .id_table = huawei_devices, + .report_fixup = huawei_report_fixup, +}; +module_hid_driver(huawei_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Miao Li <limiao@kylinos.cn>"); +MODULE_DESCRIPTION("HID driver for some huawei \"special\" devices"); diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c index 9eafff0b6ea4..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; @@ -149,7 +152,7 @@ static struct mousevsc_dev *mousevsc_alloc_device(struct hv_device *device) { struct mousevsc_dev *input_dev; - input_dev = kzalloc(sizeof(struct mousevsc_dev), GFP_KERNEL); + input_dev = kzalloc_obj(struct mousevsc_dev); if (!input_dev) return NULL; @@ -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 afcee13bad61..9a473f72c8cb 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -22,6 +22,9 @@ #define USB_DEVICE_ID_3M2256 0x0502 #define USB_DEVICE_ID_3M3266 0x0506 +#define USB_VENDOR_ID_8BITDO 0x2dc8 +#define USB_DEVICE_ID_8BITDO_PRO_3 0x6009 + #define USB_VENDOR_ID_A4TECH 0x09da #define USB_DEVICE_ID_A4TECH_WCP32PU 0x0006 #define USB_DEVICE_ID_A4TECH_X5_005D 0x000a @@ -217,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 @@ -260,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 @@ -274,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 @@ -328,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 @@ -452,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 @@ -464,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 @@ -664,7 +678,23 @@ #define USB_DEVICE_ID_UGCI_FLYING 0x0020 #define USB_DEVICE_ID_UGCI_FIGHTING 0x0030 +#define USB_VENDOR_ID_HARMONIX 0x1bad +#define USB_DEVICE_ID_HARMONIX_WII_RB1_GUITAR 0x0004 +#define USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR 0x3010 +#define USB_DEVICE_ID_HARMONIX_WII_RB1_DRUMS 0x0005 +#define USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS 0x3110 +#define USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_DRUMS_MODE 0x3138 +#define USB_DEVICE_ID_HARMONIX_WII_RB3_MUSTANG_GUITAR 0x3430 +#define USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_MUSTANG_MODE 0x3438 +#define USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_SQUIER_MODE 0x3538 +#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 @@ -736,9 +766,14 @@ #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 +#define USB_VENDOR_ID_QHE 0x1a86 +#define USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT 0xe310 +#define USB_DEVICE_ID_LENOVO_LEGION_GO_S_DINPUT 0xe311 + #define USB_VENDOR_ID_JABRA 0x0b0e #define USB_DEVICE_ID_JABRA_SPEAK_410 0x0412 #define USB_DEVICE_ID_JABRA_SPEAK_510 0x0420 @@ -748,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 @@ -851,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 @@ -858,7 +895,10 @@ #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_602E 0x602e #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6093 0x6093 #define USB_DEVICE_ID_LENOVO_LEGION_GO_DUAL_DINPUT 0x6184 +#define USB_DEVICE_ID_LENOVO_LEGION_GO2_XINPUT 0x61eb +#define USB_DEVICE_ID_LENOVO_LEGION_GO2_DINPUT 0x61ec #define USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT 0x61ed +#define USB_DEVICE_ID_LENOVO_LEGION_GO2_FPS 0x61ee #define USB_VENDOR_ID_LETSKETCH 0x6161 #define USB_DEVICE_ID_WP9620N 0x4d15 @@ -936,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 @@ -1040,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 @@ -1105,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 @@ -1246,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 @@ -1258,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 @@ -1298,8 +1354,18 @@ #define USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER 0x1000 #define USB_VENDOR_ID_SONY_RHYTHM 0x12ba -#define USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE_DONGLE 0x074b -#define USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE 0x0100 +#define USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE 0x074b +#define USB_DEVICE_ID_SONY_PS3_GH_GUITAR 0x0100 +#define USB_DEVICE_ID_SONY_PS3_GH_DRUMS 0x0120 +#define USB_DEVICE_ID_SONY_PS3_DJH_TURNTABLE 0x0140 +#define USB_DEVICE_ID_SONY_PS3_RB_GUITAR 0x0200 +#define USB_DEVICE_ID_SONY_PS3_RB_DRUMS 0x0210 +#define USB_DEVICE_ID_SONY_PS3_RB3_MPA_DRUMS_MODE 0x0218 +#define USB_DEVICE_ID_SONY_PS3_RB3_MUSTANG_GUITAR 0x2430 +#define USB_DEVICE_ID_SONY_PS3_RB3_MPA_MUSTANG_MODE 0x2438 +#define USB_DEVICE_ID_SONY_PS3_RB3_MPA_SQUIER_MODE 0x2538 +#define USB_DEVICE_ID_SONY_PS3_RB3_KEYBOARD 0x2330 +#define USB_DEVICE_ID_SONY_PS3_RB3_MPA_KEYBOARD_MODE 0x2338 #define USB_VENDOR_ID_SINO_LITE 0x1345 #define USB_DEVICE_ID_SINO_LITE_CONTROLLER 0x3008 @@ -1326,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 @@ -1369,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 @@ -1471,6 +1563,10 @@ #define USB_VENDOR_ID_VTL 0x0306 #define USB_DEVICE_ID_VTL_MULTITOUCH_FF3F 0xff3f +#define USB_VENDOR_ID_VXE 0x3554 +#define USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_DONGLE 0xf58a +#define USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_WIRED 0xf58c + #define USB_VENDOR_ID_WACOM 0x056a #define USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH 0x81 #define USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH 0x00BD @@ -1580,4 +1676,7 @@ #define USB_VENDOR_ID_JIELI_SDK_DEFAULT 0x4c4a #define USB_DEVICE_ID_JIELI_SDK_4155 0x4155 +#define USB_VENDOR_ID_HUAWEI 0x12d1 +#define USB_DEVICE_ID_HUAWEI_CD30KBD 0x109b + #endif diff --git a/drivers/hid/hid-input-test.c b/drivers/hid/hid-input-test.c index 6f5c71660d82..c92008dafddf 100644 --- a/drivers/hid/hid-input-test.c +++ b/drivers/hid/hid-input-test.c @@ -9,54 +9,59 @@ static void hid_test_input_update_battery_charge_status(struct kunit *test) { - struct hid_device *dev; + struct hid_battery *bat; bool handled; - dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + bat = kunit_kzalloc(test, sizeof(*bat), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bat); - handled = hidinput_update_battery_charge_status(dev, HID_DG_HEIGHT, 0); + handled = hidinput_update_battery_charge_status(bat, HID_DG_HEIGHT, 0); KUNIT_EXPECT_FALSE(test, handled); - KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_UNKNOWN); + KUNIT_EXPECT_EQ(test, bat->charge_status, POWER_SUPPLY_STATUS_UNKNOWN); - handled = hidinput_update_battery_charge_status(dev, HID_BAT_CHARGING, 0); + handled = hidinput_update_battery_charge_status(bat, HID_BAT_CHARGING, 0); KUNIT_EXPECT_TRUE(test, handled); - KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_DISCHARGING); + KUNIT_EXPECT_EQ(test, bat->charge_status, POWER_SUPPLY_STATUS_DISCHARGING); - handled = hidinput_update_battery_charge_status(dev, HID_BAT_CHARGING, 1); + handled = hidinput_update_battery_charge_status(bat, HID_BAT_CHARGING, 1); KUNIT_EXPECT_TRUE(test, handled); - KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_CHARGING); + KUNIT_EXPECT_EQ(test, bat->charge_status, POWER_SUPPLY_STATUS_CHARGING); } static void hid_test_input_get_battery_property(struct kunit *test) { struct power_supply *psy; + struct hid_battery *bat; struct hid_device *dev; union power_supply_propval val; int ret; dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); - dev->battery_avoid_query = true; + + bat = kunit_kzalloc(test, sizeof(*bat), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bat); + bat->dev = dev; + bat->avoid_query = true; psy = kunit_kzalloc(test, sizeof(*psy), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, psy); - psy->drv_data = dev; + psy->drv_data = bat; - dev->battery_status = HID_BATTERY_UNKNOWN; - dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING; + bat->status = HID_BATTERY_UNKNOWN; + bat->charge_status = POWER_SUPPLY_STATUS_CHARGING; ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_UNKNOWN); - dev->battery_status = HID_BATTERY_REPORTED; - dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING; + bat->status = HID_BATTERY_REPORTED; + bat->charge_status = POWER_SUPPLY_STATUS_CHARGING; ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_CHARGING); - dev->battery_status = HID_BATTERY_REPORTED; - dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + bat->status = HID_BATTERY_REPORTED; + bat->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_DISCHARGING); diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 8fc20df99b97..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. @@ -416,43 +421,47 @@ static unsigned find_battery_quirk(struct hid_device *hdev) return quirks; } -static int hidinput_scale_battery_capacity(struct hid_device *dev, +static int hidinput_scale_battery_capacity(struct hid_battery *bat, int value) { - if (dev->battery_min < dev->battery_max && - value >= dev->battery_min && value <= dev->battery_max) - value = ((value - dev->battery_min) * 100) / - (dev->battery_max - dev->battery_min); + if (bat->min < bat->max && + value >= bat->min && value <= bat->max) + value = ((value - bat->min) * 100) / + (bat->max - bat->min); return value; } -static int hidinput_query_battery_capacity(struct hid_device *dev) +static int hidinput_query_battery_capacity(struct hid_battery *bat) { - u8 *buf; 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; - buf = kmalloc(4, GFP_KERNEL); + u8 *buf __free(kfree) = kmalloc(max(len, 4), GFP_KERNEL); if (!buf) return -ENOMEM; - ret = hid_hw_raw_request(dev, dev->battery_report_id, buf, 4, - dev->battery_report_type, HID_REQ_GET_REPORT); - if (ret < 2) { - kfree(buf); + ret = hid_hw_raw_request(bat->dev, bat->report_id, buf, max(len, 4), + bat->report_type, HID_REQ_GET_REPORT); + if (ret < len) return -ENODATA; - } - ret = hidinput_scale_battery_capacity(dev, buf[1]); - kfree(buf); - return ret; + return hidinput_scale_battery_capacity(bat, buf[offset]); } static int hidinput_get_battery_property(struct power_supply *psy, enum power_supply_property prop, union power_supply_propval *val) { - struct hid_device *dev = power_supply_get_drvdata(psy); + struct hid_battery *bat = power_supply_get_drvdata(psy); + struct hid_device *dev = bat->dev; int value; int ret = 0; @@ -462,17 +471,17 @@ static int hidinput_get_battery_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_PRESENT: - val->intval = dev->battery_present; + val->intval = bat->present; break; case POWER_SUPPLY_PROP_CAPACITY: - if (dev->battery_status != HID_BATTERY_REPORTED && - !dev->battery_avoid_query) { - value = hidinput_query_battery_capacity(dev); + if (bat->status != HID_BATTERY_REPORTED && + !bat->avoid_query) { + value = hidinput_query_battery_capacity(bat); if (value < 0) return value; } else { - value = dev->battery_capacity; + value = bat->capacity; } val->intval = value; @@ -483,20 +492,20 @@ static int hidinput_get_battery_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_STATUS: - if (dev->battery_status != HID_BATTERY_REPORTED && - !dev->battery_avoid_query) { - value = hidinput_query_battery_capacity(dev); + if (bat->status != HID_BATTERY_REPORTED && + !bat->avoid_query) { + value = hidinput_query_battery_capacity(bat); if (value < 0) return value; - dev->battery_capacity = value; - dev->battery_status = HID_BATTERY_QUERIED; + bat->capacity = value; + bat->status = HID_BATTERY_QUERIED; } - if (dev->battery_status == HID_BATTERY_UNKNOWN) + if (bat->status == HID_BATTERY_UNKNOWN) val->intval = POWER_SUPPLY_STATUS_UNKNOWN; else - val->intval = dev->battery_charge_status; + val->intval = bat->charge_status; break; case POWER_SUPPLY_PROP_SCOPE: @@ -511,36 +520,66 @@ static int hidinput_get_battery_property(struct power_supply *psy, return ret; } +static struct hid_battery *hidinput_find_battery(struct hid_device *dev, + int report_id) +{ + struct hid_battery *bat; + + list_for_each_entry(bat, &dev->batteries, list) { + if (bat->report_id == report_id) + return bat; + } + 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) { + struct hid_battery *bat; struct power_supply_desc *psy_desc; - struct power_supply_config psy_cfg = { .drv_data = dev, }; + struct power_supply_config psy_cfg = { 0 }; unsigned quirks; s32 min, max; int error; - if (dev->battery) - return 0; /* already initialized? */ + /* Check if battery for this report ID already exists */ + if (hidinput_find_battery(dev, field->report->id)) + return 0; quirks = find_battery_quirk(dev); - hid_dbg(dev, "device %x:%x:%x %d quirks %d\n", - dev->bus, dev->vendor, dev->product, dev->version, quirks); + hid_dbg(dev, "device %x:%x:%x %d quirks %d report_id %d\n", + dev->bus, dev->vendor, dev->product, dev->version, quirks, + field->report->id); if (quirks & HID_BATTERY_QUIRK_IGNORE) return 0; - psy_desc = kzalloc(sizeof(*psy_desc), GFP_KERNEL); - if (!psy_desc) + bat = devm_kzalloc(&dev->dev, sizeof(*bat), GFP_KERNEL); + if (!bat) return -ENOMEM; - psy_desc->name = kasprintf(GFP_KERNEL, "hid-%s-battery", - strlen(dev->uniq) ? - dev->uniq : dev_name(&dev->dev)); + psy_desc = devm_kzalloc(&dev->dev, sizeof(*psy_desc), GFP_KERNEL); + if (!psy_desc) { + error = -ENOMEM; + goto err_free_bat; + } + + psy_desc->name = devm_kasprintf(&dev->dev, GFP_KERNEL, + "hid-%s-battery-%d", + strlen(dev->uniq) ? + dev->uniq : dev_name(&dev->dev), + field->report->id); if (!psy_desc->name) { error = -ENOMEM; - goto err_free_mem; + goto err_free_desc; } psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; @@ -560,102 +599,102 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, if (quirks & HID_BATTERY_QUIRK_FEATURE) report_type = HID_FEATURE_REPORT; - dev->battery_min = min; - dev->battery_max = max; - dev->battery_report_type = report_type; - dev->battery_report_id = field->report->id; - dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + bat->dev = dev; + bat->min = min; + 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; /* * Stylus is normally not connected to the device and thus we * can't query the device and get meaningful battery strength. * We have to wait for the device to report it on its own. */ - dev->battery_avoid_query = report_type == HID_INPUT_REPORT && - field->physical == HID_DG_STYLUS; + bat->avoid_query = report_type == HID_INPUT_REPORT && + field->physical == HID_DG_STYLUS; if (quirks & HID_BATTERY_QUIRK_AVOID_QUERY) - dev->battery_avoid_query = true; + bat->avoid_query = true; - dev->battery_present = (quirks & HID_BATTERY_QUIRK_DYNAMIC) ? false : true; + bat->present = (quirks & HID_BATTERY_QUIRK_DYNAMIC) ? false : true; - dev->battery = power_supply_register(&dev->dev, psy_desc, &psy_cfg); - if (IS_ERR(dev->battery)) { - error = PTR_ERR(dev->battery); + psy_cfg.drv_data = bat; + bat->ps = devm_power_supply_register(&dev->dev, psy_desc, &psy_cfg); + if (IS_ERR(bat->ps)) { + error = PTR_ERR(bat->ps); hid_warn(dev, "can't register power supply: %d\n", error); goto err_free_name; } - power_supply_powers(dev->battery, &dev->dev); + 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: - kfree(psy_desc->name); -err_free_mem: - kfree(psy_desc); - dev->battery = NULL; + devm_kfree(&dev->dev, psy_desc->name); +err_free_desc: + devm_kfree(&dev->dev, psy_desc); +err_free_bat: + devm_kfree(&dev->dev, bat); return error; } -static void hidinput_cleanup_battery(struct hid_device *dev) -{ - const struct power_supply_desc *psy_desc; - - if (!dev->battery) - return; - - psy_desc = dev->battery->desc; - power_supply_unregister(dev->battery); - kfree(psy_desc->name); - kfree(psy_desc); - dev->battery = NULL; -} - -static bool hidinput_update_battery_charge_status(struct hid_device *dev, +static bool hidinput_update_battery_charge_status(struct hid_battery *bat, unsigned int usage, int value) { switch (usage) { case HID_BAT_CHARGING: - dev->battery_charge_status = value ? - POWER_SUPPLY_STATUS_CHARGING : - POWER_SUPPLY_STATUS_DISCHARGING; + bat->charge_status = value ? + POWER_SUPPLY_STATUS_CHARGING : + POWER_SUPPLY_STATUS_DISCHARGING; return true; } return false; } -static void hidinput_update_battery(struct hid_device *dev, unsigned int usage, - int value) +static void hidinput_update_battery(struct hid_device *dev, int report_id, + unsigned int usage, int value) { + struct hid_battery *bat; int capacity; - if (!dev->battery) + bat = hidinput_find_battery(dev, report_id); + if (!bat) return; - if (hidinput_update_battery_charge_status(dev, usage, value)) { - dev->battery_present = true; - power_supply_changed(dev->battery); + if (hidinput_update_battery_charge_status(bat, usage, value)) { + bat->present = true; + power_supply_changed(bat->ps); return; } if ((usage & HID_USAGE_PAGE) == HID_UP_DIGITIZER && value == 0) return; - if (value < dev->battery_min || value > dev->battery_max) + if (value < bat->min || value > bat->max) return; - capacity = hidinput_scale_battery_capacity(dev, value); + capacity = hidinput_scale_battery_capacity(bat, value); - if (dev->battery_status != HID_BATTERY_REPORTED || - capacity != dev->battery_capacity || - ktime_after(ktime_get_coarse(), dev->battery_ratelimit_time)) { - dev->battery_present = true; - dev->battery_capacity = capacity; - dev->battery_status = HID_BATTERY_REPORTED; - dev->battery_ratelimit_time = + if (bat->status != HID_BATTERY_REPORTED || + capacity != bat->capacity || + ktime_after(ktime_get_coarse(), bat->ratelimit_time)) { + bat->present = true; + bat->capacity = capacity; + bat->status = HID_BATTERY_REPORTED; + bat->ratelimit_time = ktime_add_ms(ktime_get_coarse(), 30 * 1000); - power_supply_changed(dev->battery); + power_supply_changed(bat->ps); } } #else /* !CONFIG_HID_BATTERY_STRENGTH */ @@ -665,12 +704,8 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, return 0; } -static void hidinput_cleanup_battery(struct hid_device *dev) -{ -} - -static void hidinput_update_battery(struct hid_device *dev, unsigned int usage, - int value) +static void hidinput_update_battery(struct hid_device *dev, int report_id, + unsigned int usage, int value) { } #endif /* CONFIG_HID_BATTERY_STRENGTH */ @@ -1227,6 +1262,9 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel case 0x1bc: map_key_clear(KEY_MESSENGER); break; case 0x1bd: map_key_clear(KEY_INFO); break; case 0x1cb: map_key_clear(KEY_ASSISTANT); break; + case 0x1cc: map_key_clear(KEY_ACTION_ON_SELECTION); break; + case 0x1cd: map_key_clear(KEY_CONTEXTUAL_INSERT); break; + case 0x1ce: map_key_clear(KEY_CONTEXTUAL_QUERY); break; case 0x201: map_key_clear(KEY_NEW); break; case 0x202: map_key_clear(KEY_OPEN); break; case 0x203: map_key_clear(KEY_CLOSE); break; @@ -1554,7 +1592,7 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct return; if (usage->type == EV_PWR) { - hidinput_update_battery(hid, usage->hid, value); + hidinput_update_battery(hid, report->id, usage->hid, value); return; } @@ -1836,7 +1874,6 @@ static void hidinput_led_worker(struct work_struct *work) struct hid_report *report; int ret; u32 len; - __u8 *buf; field = hidinput_get_led_field(hid); if (!field) @@ -1863,7 +1900,7 @@ static void hidinput_led_worker(struct work_struct *work) /* fall back to generic raw-output-report */ len = hid_report_len(report); - buf = hid_alloc_report_buf(report, GFP_KERNEL); + u8 *buf __free(kfree) = hid_alloc_report_buf(report, GFP_KERNEL); if (!buf) return; @@ -1873,7 +1910,6 @@ static void hidinput_led_worker(struct work_struct *work) if (ret == -ENOSYS) hid_hw_raw_request(hid, report->id, buf, len, HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); - kfree(buf); } static int hidinput_input_event(struct input_dev *dev, unsigned int type, @@ -2027,7 +2063,7 @@ static void report_features(struct hid_device *hid) static struct hid_input *hidinput_allocate(struct hid_device *hid, unsigned int application) { - struct hid_input *hidinput = kzalloc(sizeof(*hidinput), GFP_KERNEL); + struct hid_input *hidinput = kzalloc_obj(*hidinput); struct input_dev *input_dev = input_allocate_device(); const char *suffix = NULL; size_t suffix_len, name_len; @@ -2293,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; @@ -2306,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 || @@ -2372,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; @@ -2400,8 +2453,6 @@ void hidinput_disconnect(struct hid_device *hid) { struct hid_input *hidinput, *next; - hidinput_cleanup_battery(hid); - list_for_each_entry_safe(hidinput, next, &hid->inputs, list) { list_del(&hidinput->list); if (hidinput->registered) 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-kysona.c b/drivers/hid/hid-kysona.c index 09bfe30d02cb..ccbd8380064e 100644 --- a/drivers/hid/hid-kysona.c +++ b/drivers/hid/hid-kysona.c @@ -272,6 +272,8 @@ static void kysona_remove(struct hid_device *hdev) static const struct hid_device_id kysona_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_DONGLE) }, { HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_WIRED) }, + { HID_USB_DEVICE(USB_VENDOR_ID_VXE, USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_DONGLE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_VXE, USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_WIRED) }, { } }; MODULE_DEVICE_TABLE(hid, kysona_devices); diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c new file mode 100644 index 000000000000..a72f7f748cb5 --- /dev/null +++ b/drivers/hid/hid-lenovo-go-s.c @@ -0,0 +1,1549 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Lenovo Legion Go S devices. + * + * Copyright (c) 2026 Derek J. Clark <derekjohn.clark@gmail.com> + * Copyright (c) 2026 Valve Corporation + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/array_size.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/device.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/printk.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/unaligned.h> +#include <linux/usb.h> +#include <linux/workqueue.h> +#include <linux/workqueue_types.h> + +#include "hid-ids.h" + +#define GO_S_CFG_INTF_IN 0x84 +#define GO_S_PACKET_SIZE 64 + +static struct hid_gos_cfg { + struct delayed_work gos_cfg_setup; + struct completion send_cmd_complete; + struct led_classdev *led_cdev; + struct hid_device *hdev; + struct mutex cfg_mutex; /*ensure single synchronous output report*/ + u8 gp_auto_sleep_time; + u8 gp_dpad_mode; + u8 gp_mode; + u8 gp_poll_rate; + u8 imu_bypass_en; + u8 imu_manufacturer; + u8 imu_sensor_en; + u8 mcu_id[12]; + u8 mouse_step; + u8 os_mode; + u8 rgb_effect; + u8 rgb_en; + u8 rgb_mode; + u8 rgb_profile; + u8 rgb_speed; + u8 tp_en; + u8 tp_linux_mode; + u8 tp_windows_mode; + u8 tp_version; + u8 tp_manufacturer; +} drvdata; + +struct gos_cfg_attr { + u8 index; +}; + +struct command_report { + u8 cmd; + u8 sub_cmd; + u8 data[63]; +} __packed; + +struct version_report { + u8 cmd; + u32 version; + u8 reserved[59]; +} __packed; + +enum mcu_command_index { + GET_VERSION = 0x01, + GET_MCU_ID, + GET_GAMEPAD_CFG, + SET_GAMEPAD_CFG, + GET_TP_PARAM, + SET_TP_PARAM, + GET_RGB_CFG = 0x0f, + SET_RGB_CFG, + GET_PL_TEST = 0xdf, +}; + +enum feature_enabled_index { + FEATURE_DISABLED, + FEATURE_ENABLED, +}; + +static const char *const feature_enabled_text[] = { + [FEATURE_DISABLED] = "false", + [FEATURE_ENABLED] = "true", +}; + +enum feature_status_index { + FEATURE_NONE = 0x00, + FEATURE_GAMEPAD_MODE = 0x01, + FEATURE_AUTO_SLEEP_TIME = 0x04, + FEATURE_IMU_BYPASS, + FEATURE_RGB_ENABLE, + FEATURE_IMU_ENABLE, + FEATURE_TOUCHPAD_ENABLE, + FEATURE_OS_MODE = 0x0A, + FEATURE_POLL_RATE = 0x10, + FEATURE_DPAD_MODE, + FEATURE_MOUSE_WHEEL_STEP, +}; + +enum gamepad_mode_index { + XINPUT, + DINPUT, +}; + +static const char *const gamepad_mode_text[] = { + [XINPUT] = "xinput", + [DINPUT] = "dinput", +}; + +enum os_type_index { + WINDOWS, + LINUX, +}; + +static const char *const os_type_text[] = { + [WINDOWS] = "windows", + [LINUX] = "linux", +}; + +enum poll_rate_index { + HZ125, + HZ250, + HZ500, + HZ1000, +}; + +static const char *const poll_rate_text[] = { + [HZ125] = "125", + [HZ250] = "250", + [HZ500] = "500", + [HZ1000] = "1000", +}; + +enum dpad_mode_index { + DIR8, + DIR4, +}; + +static const char *const dpad_mode_text[] = { + [DIR8] = "8-way", + [DIR4] = "4-way", +}; + +enum touchpad_mode_index { + TP_REL, + TP_ABS, +}; + +static const char *const touchpad_mode_text[] = { + [TP_REL] = "relative", + [TP_ABS] = "absolute", +}; + +enum touchpad_config_index { + CFG_WINDOWS_MODE = 0x03, + CFG_LINUX_MODE, + +}; + +enum rgb_mode_index { + RGB_MODE_DYNAMIC, + RGB_MODE_CUSTOM, +}; + +static const char *const rgb_mode_text[] = { + [RGB_MODE_DYNAMIC] = "dynamic", + [RGB_MODE_CUSTOM] = "custom", +}; + +enum rgb_effect_index { + RGB_EFFECT_MONO, + RGB_EFFECT_BREATHE, + RGB_EFFECT_CHROMA, + RGB_EFFECT_RAINBOW, +}; + +static const char *const rgb_effect_text[] = { + [RGB_EFFECT_MONO] = "monocolor", + [RGB_EFFECT_BREATHE] = "breathe", + [RGB_EFFECT_CHROMA] = "chroma", + [RGB_EFFECT_RAINBOW] = "rainbow", +}; + +enum rgb_config_index { + LIGHT_MODE_SEL = 0x01, + LIGHT_PROFILE_SEL, + USR_LIGHT_PROFILE_1, + USR_LIGHT_PROFILE_2, + USR_LIGHT_PROFILE_3, +}; + +enum test_command_index { + TEST_TP_MFR = 0x02, + TEST_IMU_MFR, + TEST_TP_VER, +}; + +enum tp_mfr_index { + TP_NONE, + TP_BETTERLIFE, + TP_SIPO, +}; + +static const char *const touchpad_manufacturer_text[] = { + [TP_NONE] = "none", + [TP_BETTERLIFE] = "BetterLife", + [TP_SIPO] = "SIPO", +}; + +enum imu_mfr_index { + IMU_NONE, + IMU_BOSCH, + IMU_ST, +}; + +static const char *const imu_manufacturer_text[] = { + [IMU_NONE] = "none", + [IMU_BOSCH] = "Bosch", + [IMU_ST] = "ST", +}; + +static int hid_gos_version_event(u8 *data) +{ + struct version_report *ver_rep = (struct version_report *)data; + + drvdata.hdev->firmware_version = get_unaligned_le32(&ver_rep->version); + return 0; +} + +static int hid_gos_mcu_id_event(struct command_report *cmd_rep) +{ + drvdata.mcu_id[0] = cmd_rep->sub_cmd; + memcpy(&drvdata.mcu_id[1], cmd_rep->data, 11); + + return 0; +} + +static int hid_gos_gamepad_cfg_event(struct command_report *cmd_rep) +{ + int ret = 0; + + switch (cmd_rep->sub_cmd) { + case FEATURE_GAMEPAD_MODE: + drvdata.gp_mode = cmd_rep->data[0]; + break; + case FEATURE_AUTO_SLEEP_TIME: + drvdata.gp_auto_sleep_time = cmd_rep->data[0]; + break; + case FEATURE_IMU_BYPASS: + drvdata.imu_bypass_en = cmd_rep->data[0]; + break; + case FEATURE_RGB_ENABLE: + drvdata.rgb_en = cmd_rep->data[0]; + break; + case FEATURE_IMU_ENABLE: + drvdata.imu_sensor_en = cmd_rep->data[0]; + break; + case FEATURE_TOUCHPAD_ENABLE: + drvdata.tp_en = cmd_rep->data[0]; + break; + case FEATURE_OS_MODE: + drvdata.os_mode = cmd_rep->data[0]; + break; + case FEATURE_POLL_RATE: + drvdata.gp_poll_rate = cmd_rep->data[0]; + break; + case FEATURE_DPAD_MODE: + drvdata.gp_dpad_mode = cmd_rep->data[0]; + break; + case FEATURE_MOUSE_WHEEL_STEP: + drvdata.mouse_step = cmd_rep->data[0]; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int hid_gos_touchpad_event(struct command_report *cmd_rep) +{ + int ret = 0; + + switch (cmd_rep->sub_cmd) { + case CFG_LINUX_MODE: + drvdata.tp_linux_mode = cmd_rep->data[0]; + break; + case CFG_WINDOWS_MODE: + drvdata.tp_windows_mode = cmd_rep->data[0]; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int hid_gos_pl_test_event(struct command_report *cmd_rep) +{ + int ret = 0; + + switch (cmd_rep->sub_cmd) { + case TEST_TP_MFR: + drvdata.tp_manufacturer = cmd_rep->data[0]; + ret = 0; + break; + case TEST_IMU_MFR: + drvdata.imu_manufacturer = cmd_rep->data[0]; + ret = 0; + break; + case TEST_TP_VER: + drvdata.tp_version = cmd_rep->data[0]; + ret = 0; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int hid_gos_light_event(struct command_report *cmd_rep) +{ + struct led_classdev_mc *mc_cdev; + int ret = 0; + + switch (cmd_rep->sub_cmd) { + case LIGHT_MODE_SEL: + drvdata.rgb_mode = cmd_rep->data[0]; + ret = 0; + break; + case LIGHT_PROFILE_SEL: + drvdata.rgb_profile = cmd_rep->data[0]; + ret = 0; + break; + case USR_LIGHT_PROFILE_1: + case USR_LIGHT_PROFILE_2: + case USR_LIGHT_PROFILE_3: + mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + drvdata.rgb_effect = cmd_rep->data[0]; + mc_cdev->subled_info[0].intensity = cmd_rep->data[1]; + mc_cdev->subled_info[1].intensity = cmd_rep->data[2]; + mc_cdev->subled_info[2].intensity = cmd_rep->data[3]; + drvdata.led_cdev->brightness = cmd_rep->data[4]; + drvdata.rgb_speed = cmd_rep->data[5]; + ret = 0; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int hid_gos_set_event_return(struct command_report *cmd_rep) +{ + if (cmd_rep->data[0] != 0) + return -EIO; + + return 0; +} + +static int get_endpoint_address(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_host_endpoint *ep; + + ep = intf->cur_altsetting->endpoint; + if (ep) + return ep->desc.bEndpointAddress; + + return -ENODEV; +} + +static int hid_gos_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct command_report *cmd_rep; + int ep, ret; + + ep = get_endpoint_address(hdev); + if (ep != GO_S_CFG_INTF_IN) + return 0; + + if (size != GO_S_PACKET_SIZE) + return -EINVAL; + + cmd_rep = (struct command_report *)data; + + switch (cmd_rep->cmd) { + case GET_VERSION: + ret = hid_gos_version_event(data); + break; + case GET_MCU_ID: + ret = hid_gos_mcu_id_event(cmd_rep); + break; + case GET_GAMEPAD_CFG: + ret = hid_gos_gamepad_cfg_event(cmd_rep); + break; + case GET_TP_PARAM: + ret = hid_gos_touchpad_event(cmd_rep); + break; + case GET_PL_TEST: + ret = hid_gos_pl_test_event(cmd_rep); + break; + case GET_RGB_CFG: + ret = hid_gos_light_event(cmd_rep); + break; + case SET_GAMEPAD_CFG: + case SET_RGB_CFG: + case SET_TP_PARAM: + ret = hid_gos_set_event_return(cmd_rep); + break; + default: + ret = -EINVAL; + break; + } + dev_dbg(&hdev->dev, "Rx data as raw input report: [%*ph]\n", + GO_S_PACKET_SIZE, data); + + complete(&drvdata.send_cmd_complete); + return ret; +} + +static int mcu_property_out(struct hid_device *hdev, u8 command, u8 index, + u8 *data, size_t len) +{ + unsigned char *dmabuf __free(kfree) = NULL; + u8 header[] = { command, index }; + size_t header_size = ARRAY_SIZE(header); + int timeout, ret; + + if (header_size + len > GO_S_PACKET_SIZE) + return -EINVAL; + + guard(mutex)(&drvdata.cfg_mutex); + /* We can't use a devm_alloc reusable buffer without side effects during suspend */ + dmabuf = kzalloc(GO_S_PACKET_SIZE, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + memcpy(dmabuf, header, header_size); + memcpy(dmabuf + header_size, data, len); + + dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n", + GO_S_PACKET_SIZE, dmabuf); + + ret = hid_hw_output_report(hdev, dmabuf, GO_S_PACKET_SIZE); + if (ret < 0) + return ret; + + ret = ret == GO_S_PACKET_SIZE ? 0 : -EINVAL; + if (ret) + return ret; + + /* PL_TEST commands can take longer because they go out to another device */ + timeout = (command == GET_PL_TEST) ? 200 : 5; + ret = wait_for_completion_interruptible_timeout(&drvdata.send_cmd_complete, + msecs_to_jiffies(timeout)); + + if (ret == 0) /* timeout occurred */ + ret = -EBUSY; + + reinit_completion(&drvdata.send_cmd_complete); + return 0; +} + +static ssize_t gamepad_property_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + enum feature_status_index index) +{ + size_t size = 1; + u8 val = 0; + int ret; + + switch (index) { + case FEATURE_GAMEPAD_MODE: + ret = sysfs_match_string(gamepad_mode_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_AUTO_SLEEP_TIME: + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + break; + case FEATURE_IMU_ENABLE: + ret = sysfs_match_string(feature_enabled_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_IMU_BYPASS: + ret = sysfs_match_string(feature_enabled_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_RGB_ENABLE: + ret = sysfs_match_string(feature_enabled_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_TOUCHPAD_ENABLE: + ret = sysfs_match_string(feature_enabled_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_OS_MODE: + ret = sysfs_match_string(os_type_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_POLL_RATE: + ret = sysfs_match_string(poll_rate_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_DPAD_MODE: + ret = sysfs_match_string(dpad_mode_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case FEATURE_MOUSE_WHEEL_STEP: + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + if (val < 1 || val > 127) + return -EINVAL; + break; + default: + return -EINVAL; + } + + if (!val) + size = 0; + + ret = mcu_property_out(drvdata.hdev, SET_GAMEPAD_CFG, index, &val, + size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t gamepad_property_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum feature_status_index index) +{ + ssize_t count = 0; + u8 i; + + count = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, index, NULL, 0); + if (count < 0) + return count; + + switch (index) { + case FEATURE_GAMEPAD_MODE: + i = drvdata.gp_mode; + if (i >= ARRAY_SIZE(gamepad_mode_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", gamepad_mode_text[i]); + break; + case FEATURE_AUTO_SLEEP_TIME: + count = sysfs_emit(buf, "%u\n", drvdata.gp_auto_sleep_time); + break; + case FEATURE_IMU_ENABLE: + i = drvdata.imu_sensor_en; + if (i >= ARRAY_SIZE(feature_enabled_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]); + break; + case FEATURE_IMU_BYPASS: + i = drvdata.imu_bypass_en; + if (i >= ARRAY_SIZE(feature_enabled_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]); + break; + case FEATURE_RGB_ENABLE: + i = drvdata.rgb_en; + if (i >= ARRAY_SIZE(feature_enabled_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]); + break; + case FEATURE_TOUCHPAD_ENABLE: + i = drvdata.tp_en; + if (i >= ARRAY_SIZE(feature_enabled_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]); + break; + case FEATURE_OS_MODE: + i = drvdata.os_mode; + if (i >= ARRAY_SIZE(os_type_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", os_type_text[i]); + break; + case FEATURE_POLL_RATE: + i = drvdata.gp_poll_rate; + if (i >= ARRAY_SIZE(poll_rate_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", poll_rate_text[i]); + break; + case FEATURE_DPAD_MODE: + i = drvdata.gp_dpad_mode; + if (i >= ARRAY_SIZE(dpad_mode_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", dpad_mode_text[i]); + break; + case FEATURE_MOUSE_WHEEL_STEP: + i = drvdata.mouse_step; + if (i < 1 || i > 127) + return -EINVAL; + count = sysfs_emit(buf, "%u\n", i); + break; + default: + return -EINVAL; + } + + return count; +} + +static ssize_t gamepad_property_options(struct device *dev, + struct device_attribute *attr, + char *buf, + enum feature_status_index index) +{ + size_t count = 0; + unsigned int i; + + switch (index) { + case FEATURE_GAMEPAD_MODE: + for (i = 0; i < ARRAY_SIZE(gamepad_mode_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + gamepad_mode_text[i]); + } + break; + case FEATURE_AUTO_SLEEP_TIME: + return sysfs_emit(buf, "0-255\n"); + case FEATURE_IMU_ENABLE: + for (i = 0; i < ARRAY_SIZE(feature_enabled_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + feature_enabled_text[i]); + } + break; + case FEATURE_IMU_BYPASS: + case FEATURE_RGB_ENABLE: + case FEATURE_TOUCHPAD_ENABLE: + for (i = 0; i < ARRAY_SIZE(feature_enabled_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + feature_enabled_text[i]); + } + break; + case FEATURE_OS_MODE: + for (i = 0; i < ARRAY_SIZE(os_type_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + os_type_text[i]); + } + break; + case FEATURE_POLL_RATE: + for (i = 0; i < ARRAY_SIZE(poll_rate_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + poll_rate_text[i]); + } + break; + case FEATURE_DPAD_MODE: + for (i = 0; i < ARRAY_SIZE(dpad_mode_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + dpad_mode_text[i]); + } + break; + case FEATURE_MOUSE_WHEEL_STEP: + return sysfs_emit(buf, "1-127\n"); + default: + return count; + } + + if (count) + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t touchpad_property_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + enum touchpad_config_index index) +{ + size_t size = 1; + u8 val = 0; + int ret; + + switch (index) { + case CFG_WINDOWS_MODE: + ret = sysfs_match_string(touchpad_mode_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + case CFG_LINUX_MODE: + ret = sysfs_match_string(touchpad_mode_text, buf); + if (ret < 0) + return ret; + val = ret; + break; + default: + return -EINVAL; + } + if (!val) + size = 0; + + ret = mcu_property_out(drvdata.hdev, SET_TP_PARAM, index, &val, size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t touchpad_property_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum touchpad_config_index index) +{ + int ret = 0; + u8 i; + + ret = mcu_property_out(drvdata.hdev, GET_TP_PARAM, index, NULL, 0); + if (ret < 0) + return ret; + + switch (index) { + case CFG_WINDOWS_MODE: + i = drvdata.tp_windows_mode; + break; + case CFG_LINUX_MODE: + i = drvdata.tp_linux_mode; + break; + default: + return -EINVAL; + } + + if (i >= ARRAY_SIZE(touchpad_mode_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", touchpad_mode_text[i]); +} + +static ssize_t touchpad_property_options(struct device *dev, + struct device_attribute *attr, + char *buf, + enum touchpad_config_index index) +{ + size_t count = 0; + unsigned int i; + + switch (index) { + case CFG_WINDOWS_MODE: + case CFG_LINUX_MODE: + for (i = 0; i < ARRAY_SIZE(touchpad_mode_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + touchpad_mode_text[i]); + } + break; + default: + return count; + } + + if (count) + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t test_property_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum test_command_index index) +{ + size_t count = 0; + u8 i; + + switch (index) { + case TEST_TP_MFR: + i = drvdata.tp_manufacturer; + if (i >= ARRAY_SIZE(touchpad_manufacturer_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", touchpad_manufacturer_text[i]); + break; + case TEST_IMU_MFR: + i = drvdata.imu_manufacturer; + if (i >= ARRAY_SIZE(imu_manufacturer_text)) + return -EINVAL; + count = sysfs_emit(buf, "%s\n", imu_manufacturer_text[i]); + break; + case TEST_TP_VER: + count = sysfs_emit(buf, "%u\n", drvdata.tp_version); + break; + default: + count = -EINVAL; + break; + } + + return count; +} + +static ssize_t mcu_id_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%*phN\n", 12, &drvdata.mcu_id); +} + +static int rgb_cfg_call(struct hid_device *hdev, enum mcu_command_index cmd, + enum rgb_config_index index, u8 *val, size_t size) +{ + if (cmd != SET_RGB_CFG && cmd != GET_RGB_CFG) + return -EINVAL; + + if (index < LIGHT_MODE_SEL || index > USR_LIGHT_PROFILE_3) + return -EINVAL; + + return mcu_property_out(hdev, cmd, index, val, size); +} + +static int rgb_attr_show(void) +{ + enum rgb_config_index index; + + index = drvdata.rgb_profile + 2; + + return rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, index, NULL, 0); +}; + +static ssize_t rgb_effect_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + u8 effect; + int ret; + + ret = sysfs_match_string(rgb_effect_text, buf); + if (ret < 0) + return ret; + + effect = ret; + index = drvdata.rgb_profile + 2; + u8 rgb_profile[6] = { effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + drvdata.led_cdev->brightness, + drvdata.rgb_speed }; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + if (ret) + return ret; + + drvdata.rgb_effect = effect; + return count; +}; + +static ssize_t rgb_effect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret = rgb_attr_show(); + if (ret) + return ret; + + if (drvdata.rgb_effect >= ARRAY_SIZE(rgb_effect_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", rgb_effect_text[drvdata.rgb_effect]); +} + +static ssize_t rgb_effect_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rgb_effect_text); i++) + count += sysfs_emit_at(buf, count, "%s ", rgb_effect_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t rgb_speed_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + int val = 0; + int ret; + + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; + + if (val < 0 || val > 100) + return -EINVAL; + + index = drvdata.rgb_profile + 2; + u8 rgb_profile[6] = { drvdata.rgb_effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + drvdata.led_cdev->brightness, + val }; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + if (ret) + return ret; + + drvdata.rgb_speed = val; + + return count; +}; + +static ssize_t rgb_speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + + ret = rgb_attr_show(); + if (ret) + return ret; + + if (drvdata.rgb_speed > 100) + return -EINVAL; + + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed); +} + +static ssize_t rgb_speed_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0-100\n"); +} + +static ssize_t rgb_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret = sysfs_match_string(rgb_mode_text, buf); + if (ret <= 0) + return ret; + + val = ret; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_MODE_SEL, &val, + 1); + if (ret) + return ret; + + drvdata.rgb_mode = val; + + return count; +}; + +static ssize_t rgb_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + + ret = rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_MODE_SEL, NULL, 0); + if (ret) + return ret; + + if (drvdata.rgb_mode >= ARRAY_SIZE(rgb_mode_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", rgb_mode_text[drvdata.rgb_mode]); +}; + +static ssize_t rgb_mode_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(rgb_mode_text); i++) + count += sysfs_emit_at(buf, count, "%s ", rgb_mode_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t rgb_profile_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + size_t size = 1; + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret < 0) + return ret; + + if (val < 1 || val > 3) + return -EINVAL; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_PROFILE_SEL, &val, size); + if (ret) + return ret; + + drvdata.rgb_profile = val; + + return count; +}; + +static ssize_t rgb_profile_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret = rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_PROFILE_SEL, NULL, 0); + if (ret) + return ret; + + if (drvdata.rgb_profile < 1 || drvdata.rgb_profile > 3) + return -EINVAL; + + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_profile); +}; + +static ssize_t rgb_profile_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "1-3\n"); +} + +static void hid_gos_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + int ret; + + if (brightness > led_cdev->max_brightness) { + dev_err(led_cdev->dev, "Invalid argument\n"); + return; + } + + index = drvdata.rgb_profile + 2; + u8 rgb_profile[6] = { drvdata.rgb_effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + brightness, + drvdata.rgb_speed }; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + switch (ret) { + case 0: + led_cdev->brightness = brightness; + break; + case -ENODEV: /* during switch to IAP -ENODEV is expected */ + case -ENOSYS: /* during rmmod -ENOSYS is expected */ + dev_dbg(led_cdev->dev, "Failed to write RGB profile: %i\n", + ret); + break; + default: + dev_err(led_cdev->dev, "Failed to write RGB profile: %i\n", + ret); + } +} + +#define LEGOS_DEVICE_ATTR_RW(_name, _attrname, _rtype, _group) \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return _group##_property_store(dev, attr, buf, count, \ + _name.index); \ + } \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_property_show(dev, attr, buf, _name.index); \ + } \ + static ssize_t _name##_##_rtype##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return _group##_property_options(dev, attr, buf, _name.index); \ + } \ + static DEVICE_ATTR_RW_NAMED(_name, _attrname) + +#define LEGOS_DEVICE_ATTR_RO(_name, _attrname, _group) \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_property_show(dev, attr, buf, _name.index); \ + } \ + static DEVICE_ATTR_RO_NAMED(_name, _attrname) + +/* Gamepad */ +static struct gos_cfg_attr auto_sleep_time = { FEATURE_AUTO_SLEEP_TIME }; +LEGOS_DEVICE_ATTR_RW(auto_sleep_time, "auto_sleep_time", range, gamepad); +static DEVICE_ATTR_RO(auto_sleep_time_range); + +static struct gos_cfg_attr dpad_mode = { FEATURE_DPAD_MODE }; +LEGOS_DEVICE_ATTR_RW(dpad_mode, "dpad_mode", index, gamepad); +static DEVICE_ATTR_RO(dpad_mode_index); + +static struct gos_cfg_attr gamepad_mode = { FEATURE_GAMEPAD_MODE }; +LEGOS_DEVICE_ATTR_RW(gamepad_mode, "mode", index, gamepad); +static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mode_index"); + +static struct gos_cfg_attr gamepad_poll_rate = { FEATURE_POLL_RATE }; +LEGOS_DEVICE_ATTR_RW(gamepad_poll_rate, "poll_rate", index, gamepad); +static DEVICE_ATTR_RO_NAMED(gamepad_poll_rate_index, "poll_rate_index"); + +static struct attribute *legos_gamepad_attrs[] = { + &dev_attr_auto_sleep_time.attr, + &dev_attr_auto_sleep_time_range.attr, + &dev_attr_dpad_mode.attr, + &dev_attr_dpad_mode_index.attr, + &dev_attr_gamepad_mode.attr, + &dev_attr_gamepad_mode_index.attr, + &dev_attr_gamepad_poll_rate.attr, + &dev_attr_gamepad_poll_rate_index.attr, + NULL, +}; + +static const struct attribute_group gamepad_attr_group = { + .name = "gamepad", + .attrs = legos_gamepad_attrs, +}; + +/* IMU */ +static struct gos_cfg_attr imu_bypass_enabled = { FEATURE_IMU_BYPASS }; +LEGOS_DEVICE_ATTR_RW(imu_bypass_enabled, "bypass_enabled", index, gamepad); +static DEVICE_ATTR_RO_NAMED(imu_bypass_enabled_index, "bypass_enabled_index"); + +static struct gos_cfg_attr imu_manufacturer = { TEST_IMU_MFR }; +LEGOS_DEVICE_ATTR_RO(imu_manufacturer, "manufacturer", test); + +static struct gos_cfg_attr imu_sensor_enabled = { FEATURE_IMU_ENABLE }; +LEGOS_DEVICE_ATTR_RW(imu_sensor_enabled, "sensor_enabled", index, gamepad); +static DEVICE_ATTR_RO_NAMED(imu_sensor_enabled_index, "sensor_enabled_index"); + +static struct attribute *legos_imu_attrs[] = { + &dev_attr_imu_bypass_enabled.attr, + &dev_attr_imu_bypass_enabled_index.attr, + &dev_attr_imu_manufacturer.attr, + &dev_attr_imu_sensor_enabled.attr, + &dev_attr_imu_sensor_enabled_index.attr, + NULL, +}; + +static const struct attribute_group imu_attr_group = { + .name = "imu", + .attrs = legos_imu_attrs, +}; + +/* MCU */ +static DEVICE_ATTR_RO(mcu_id); + +static struct gos_cfg_attr os_mode = { FEATURE_OS_MODE }; +LEGOS_DEVICE_ATTR_RW(os_mode, "os_mode", index, gamepad); +static DEVICE_ATTR_RO(os_mode_index); + +static struct attribute *legos_mcu_attrs[] = { + &dev_attr_mcu_id.attr, + &dev_attr_os_mode.attr, + &dev_attr_os_mode_index.attr, + NULL, +}; + +static const struct attribute_group mcu_attr_group = { + .attrs = legos_mcu_attrs, +}; + +/* Mouse */ +static struct gos_cfg_attr mouse_wheel_step = { FEATURE_MOUSE_WHEEL_STEP }; +LEGOS_DEVICE_ATTR_RW(mouse_wheel_step, "step", range, gamepad); +static DEVICE_ATTR_RO_NAMED(mouse_wheel_step_range, "step_range"); + +static struct attribute *legos_mouse_attrs[] = { + &dev_attr_mouse_wheel_step.attr, + &dev_attr_mouse_wheel_step_range.attr, + NULL, +}; + +static const struct attribute_group mouse_attr_group = { + .name = "mouse", + .attrs = legos_mouse_attrs, +}; + +/* Touchpad */ +static struct gos_cfg_attr touchpad_enabled = { FEATURE_TOUCHPAD_ENABLE }; +LEGOS_DEVICE_ATTR_RW(touchpad_enabled, "enabled", index, gamepad); +static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); + +static struct gos_cfg_attr touchpad_linux_mode = { CFG_LINUX_MODE }; +LEGOS_DEVICE_ATTR_RW(touchpad_linux_mode, "linux_mode", index, touchpad); +static DEVICE_ATTR_RO_NAMED(touchpad_linux_mode_index, "linux_mode_index"); + +static struct gos_cfg_attr touchpad_manufacturer = { TEST_TP_MFR }; +LEGOS_DEVICE_ATTR_RO(touchpad_manufacturer, "manufacturer", test); + +static struct gos_cfg_attr touchpad_version = { TEST_TP_VER }; +LEGOS_DEVICE_ATTR_RO(touchpad_version, "version", test); + +static struct gos_cfg_attr touchpad_windows_mode = { CFG_WINDOWS_MODE }; +LEGOS_DEVICE_ATTR_RW(touchpad_windows_mode, "windows_mode", index, touchpad); +static DEVICE_ATTR_RO_NAMED(touchpad_windows_mode_index, "windows_mode_index"); + +static struct attribute *legos_touchpad_attrs[] = { + &dev_attr_touchpad_enabled.attr, + &dev_attr_touchpad_enabled_index.attr, + &dev_attr_touchpad_linux_mode.attr, + &dev_attr_touchpad_linux_mode_index.attr, + &dev_attr_touchpad_manufacturer.attr, + &dev_attr_touchpad_version.attr, + &dev_attr_touchpad_windows_mode.attr, + &dev_attr_touchpad_windows_mode_index.attr, + NULL, +}; + +static const struct attribute_group touchpad_attr_group = { + .name = "touchpad", + .attrs = legos_touchpad_attrs, +}; + +static const struct attribute_group *top_level_attr_groups[] = { + &gamepad_attr_group, + &imu_attr_group, + &mcu_attr_group, + &mouse_attr_group, + &touchpad_attr_group, + NULL, +}; + +/* RGB */ +static struct gos_cfg_attr rgb_enabled = { FEATURE_RGB_ENABLE }; +LEGOS_DEVICE_ATTR_RW(rgb_enabled, "enabled", index, gamepad); +static DEVICE_ATTR_RO_NAMED(rgb_enabled_index, "enabled_index"); + +static DEVICE_ATTR_RW_NAMED(rgb_effect, "effect"); +static DEVICE_ATTR_RO_NAMED(rgb_effect_index, "effect_index"); +static DEVICE_ATTR_RW_NAMED(rgb_mode, "mode"); +static DEVICE_ATTR_RO_NAMED(rgb_mode_index, "mode_index"); +static DEVICE_ATTR_RW_NAMED(rgb_profile, "profile"); +static DEVICE_ATTR_RO_NAMED(rgb_profile_range, "profile_range"); +static DEVICE_ATTR_RW_NAMED(rgb_speed, "speed"); +static DEVICE_ATTR_RO_NAMED(rgb_speed_range, "speed_range"); + +static struct attribute *gos_rgb_attrs[] = { + &dev_attr_rgb_enabled.attr, + &dev_attr_rgb_enabled_index.attr, + &dev_attr_rgb_effect.attr, + &dev_attr_rgb_effect_index.attr, + &dev_attr_rgb_mode.attr, + &dev_attr_rgb_mode_index.attr, + &dev_attr_rgb_profile.attr, + &dev_attr_rgb_profile_range.attr, + &dev_attr_rgb_speed.attr, + &dev_attr_rgb_speed_range.attr, + NULL, +}; + +static struct attribute_group rgb_attr_group = { + .attrs = gos_rgb_attrs, +}; + +static struct mc_subled gos_rgb_subled_info[] = { + { + .color_index = LED_COLOR_ID_RED, + .brightness = 0x50, + .intensity = 0x24, + .channel = 0x1, + }, + { + .color_index = LED_COLOR_ID_GREEN, + .brightness = 0x50, + .intensity = 0x22, + .channel = 0x2, + }, + { + .color_index = LED_COLOR_ID_BLUE, + .brightness = 0x50, + .intensity = 0x99, + .channel = 0x3, + }, +}; + +static struct led_classdev_mc gos_cdev_rgb = { + .led_cdev = { + .name = "go_s:rgb:joystick_rings", + .brightness = 0x50, + .max_brightness = 0x64, + .brightness_set = hid_gos_brightness_set, + }, + .num_colors = ARRAY_SIZE(gos_rgb_subled_info), + .subled_info = gos_rgb_subled_info, +}; + +static void cfg_setup(struct work_struct *work) +{ + int ret; + + /* MCU */ + ret = mcu_property_out(drvdata.hdev, GET_MCU_ID, FEATURE_NONE, NULL, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, "Failed to retrieve MCU ID: %i\n", + ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, GET_VERSION, FEATURE_NONE, NULL, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, "Failed to retrieve MCU Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_TP_MFR, NULL, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve Touchpad Manufacturer: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_TP_VER, NULL, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve Touchpad Firmware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_IMU_MFR, NULL, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "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, + const struct hid_device_id *_id) +{ + int ret; + + hid_set_drvdata(hdev, &drvdata); + drvdata.hdev = hdev; + mutex_init(&drvdata.cfg_mutex); + + ret = sysfs_create_groups(&hdev->dev.kobj, top_level_attr_groups); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create gamepad configuration attributes\n"); + return ret; + } + + ret = devm_led_classdev_multicolor_register(&hdev->dev, &gos_cdev_rgb); + if (ret) { + dev_err_probe(&hdev->dev, ret, "Failed to create RGB device\n"); + return ret; + } + + ret = devm_device_add_group(gos_cdev_rgb.led_cdev.dev, &rgb_attr_group); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create RGB configuration attributes\n"); + return ret; + } + + drvdata.led_cdev = &gos_cdev_rgb.led_cdev; + + init_completion(&drvdata.send_cmd_complete); + + /* Executing calls prior to returning from probe will lock the MCU. Schedule + * initial data call after probe has completed and MCU can accept calls. + */ + INIT_DELAYED_WORK(&drvdata.gos_cfg_setup, &cfg_setup); + ret = schedule_delayed_work(&drvdata.gos_cfg_setup, msecs_to_jiffies(2)); + if (!ret) { + dev_err(&hdev->dev, "Failed to schedule startup delayed work\n"); + return -ENODEV; + } + + return 0; +} + +static void hid_gos_cfg_remove(struct hid_device *hdev) +{ + guard(mutex)(&drvdata.cfg_mutex); + cancel_delayed_work_sync(&drvdata.gos_cfg_setup); + sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups); + hid_hw_close(hdev); + hid_hw_stop(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"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) { + hid_err(hdev, "Failed to start HID device\n"); + return ret; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "Failed to open HID device\n"); + hid_hw_stop(hdev); + return ret; + } + + ep = get_endpoint_address(hdev); + if (ep != GO_S_CFG_INTF_IN) { + dev_dbg(&hdev->dev, "Started interface %x as generic HID device.\n", ep); + return 0; + } + + ret = hid_gos_cfg_probe(hdev, id); + if (ret) + dev_err_probe(&hdev->dev, ret, "Failed to start configuration interface"); + + dev_dbg(&hdev->dev, "Started interface %x as Go S configuration interface\n", ep); + return ret; +} + +static void hid_gos_remove(struct hid_device *hdev) +{ + int ep = get_endpoint_address(hdev); + + switch (ep) { + case GO_S_CFG_INTF_IN: + hid_gos_cfg_remove(hdev); + break; + default: + hid_hw_close(hdev); + hid_hw_stop(hdev); + + break; + } +} + +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) }, + { HID_USB_DEVICE(USB_VENDOR_ID_QHE, + USB_DEVICE_ID_LENOVO_LEGION_GO_S_DINPUT) }, + {} +}; + +MODULE_DEVICE_TABLE(hid, hid_gos_devices); +static struct hid_driver hid_lenovo_go_s = { + .name = "hid-lenovo-go-s", + .id_table = hid_gos_devices, + .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); + +MODULE_AUTHOR("Derek J. Clark"); +MODULE_DESCRIPTION("HID Driver for Lenovo Legion Go S Series gamepad."); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c new file mode 100644 index 000000000000..318b1152ff8b --- /dev/null +++ b/drivers/hid/hid-lenovo-go.c @@ -0,0 +1,2509 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Lenovo Legion Go series gamepads. + * + * Copyright (c) 2026 Derek J. Clark <derekjohn.clark@gmail.com> + * Copyright (c) 2026 Valve Corporation + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/array_size.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/device.h> +#include <linux/device/devres.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/printk.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/unaligned.h> +#include <linux/usb.h> +#include <linux/workqueue.h> +#include <linux/workqueue_types.h> + +#include "hid-ids.h" + +#define GO_GP_INTF_IN 0x83 +#define GO_OUTPUT_REPORT_ID 0x05 +#define GO_GP_RESET_SUCCESS 0x01 +#define GO_PACKET_SIZE 64 + +static struct hid_go_cfg { + struct delayed_work go_cfg_setup; + struct completion send_cmd_complete; + struct led_classdev *led_cdev; + struct hid_device *hdev; + struct mutex cfg_mutex; /*ensure single synchronous output report*/ + u8 fps_mode; + u8 gp_left_auto_sleep_time; + u8 gp_left_gyro_cal_status; + u8 gp_left_joy_cal_status; + u8 gp_left_notify_en; + u8 gp_left_rumble_mode; + u8 gp_left_trigg_cal_status; + u32 gp_left_version_firmware; + u8 gp_left_version_gen; + u32 gp_left_version_hardware; + u32 gp_left_version_product; + u32 gp_left_version_protocol; + u8 gp_mode; + u8 gp_right_auto_sleep_time; + u8 gp_right_gyro_cal_status; + u8 gp_right_joy_cal_status; + u8 gp_right_notify_en; + u8 gp_right_rumble_mode; + u8 gp_right_trigg_cal_status; + u32 gp_right_version_firmware; + u8 gp_right_version_gen; + u32 gp_right_version_hardware; + u32 gp_right_version_product; + u32 gp_right_version_protocol; + u8 gp_rumble_intensity; + u8 imu_left_bypass_en; + u8 imu_left_sensor_en; + u8 imu_right_bypass_en; + u8 imu_right_sensor_en; + u32 mcu_version_firmware; + u8 mcu_version_gen; + u32 mcu_version_hardware; + u32 mcu_version_product; + u32 mcu_version_protocol; + u32 mouse_dpi; + u8 os_mode; + u8 rgb_effect; + u8 rgb_en; + u8 rgb_mode; + u8 rgb_profile; + u8 rgb_speed; + u8 tp_en; + u8 tp_vibration_en; + u8 tp_vibration_intensity; + u32 tx_dongle_version_firmware; + u8 tx_dongle_version_gen; + u32 tx_dongle_version_hardware; + u32 tx_dongle_version_product; + u32 tx_dongle_version_protocol; +} drvdata; + +struct go_cfg_attr { + u8 index; +}; + +struct command_report { + u8 report_id; + u8 id; + u8 cmd; + u8 sub_cmd; + u8 device_type; + u8 data[59]; +} __packed; + +enum command_id { + MCU_CONFIG_DATA = 0x00, + OS_MODE_DATA = 0x06, + GAMEPAD_DATA = 0x3c, +}; + +enum mcu_command_index { + GET_VERSION_DATA = 0x02, + GET_FEATURE_STATUS, + SET_FEATURE_STATUS, + GET_MOTOR_CFG, + SET_MOTOR_CFG, + GET_DPI_CFG, + SET_DPI_CFG, + SET_TRIGGER_CFG = 0x0a, + SET_JOYSTICK_CFG = 0x0c, + SET_GYRO_CFG = 0x0e, + GET_RGB_CFG, + SET_RGB_CFG, + GET_DEVICE_STATUS = 0xa0, + +}; + +enum dev_type { + UNSPECIFIED, + USB_MCU, + TX_DONGLE, + LEFT_CONTROLLER, + RIGHT_CONTROLLER, +}; + +enum enabled_status_index { + FEATURE_UNKNOWN, + FEATURE_ENABLED, + FEATURE_DISABLED, +}; + +static const char *const enabled_status_text[] = { + [FEATURE_UNKNOWN] = "unknown", + [FEATURE_ENABLED] = "true", + [FEATURE_DISABLED] = "false", +}; + +enum version_data_index { + PRODUCT_VERSION = 0x02, + PROTOCOL_VERSION, + FIRMWARE_VERSION, + HARDWARE_VERSION, + HARDWARE_GENERATION, +}; + +enum feature_status_index { + FEATURE_RESET_GAMEPAD = 0x02, + FEATURE_IMU_BYPASS, + FEATURE_IMU_ENABLE = 0x05, + FEATURE_TOUCHPAD_ENABLE = 0x07, + FEATURE_LIGHT_ENABLE, + FEATURE_AUTO_SLEEP_TIME, + FEATURE_FPS_SWITCH_STATUS = 0x0b, + FEATURE_GAMEPAD_MODE = 0x0e, +}; + +#define FEATURE_OS_MODE 0x69 + +enum fps_switch_status_index { + FPS_STATUS_UNKNOWN, + GAMEPAD, + FPS, +}; + +static const char *const fps_switch_text[] = { + [FPS_STATUS_UNKNOWN] = "unknown", + [GAMEPAD] = "gamepad", + [FPS] = "fps", +}; + +enum gamepad_mode_index { + GAMEPAD_MODE_UNKNOWN, + XINPUT, + DINPUT, +}; + +static const char *const gamepad_mode_text[] = { + [GAMEPAD_MODE_UNKNOWN] = "unknown", + [XINPUT] = "xinput", + [DINPUT] = "dinput", +}; + +enum motor_cfg_index { + MOTOR_CFG_ALL = 0x01, + MOTOR_INTENSITY, + VIBRATION_NOTIFY_ENABLE, + RUMBLE_MODE, + TP_VIBRATION_ENABLE, + TP_VIBRATION_INTENSITY, +}; + +enum intensity_index { + INTENSITY_UNKNOWN, + INTENSITY_OFF, + INTENSITY_LOW, + INTENSITY_MEDIUM, + INTENSITY_HIGH, +}; + +static const char *const intensity_text[] = { + [INTENSITY_UNKNOWN] = "unknown", + [INTENSITY_OFF] = "off", + [INTENSITY_LOW] = "low", + [INTENSITY_MEDIUM] = "medium", + [INTENSITY_HIGH] = "high", +}; + +enum rumble_mode_index { + RUMBLE_MODE_UNKNOWN, + RUMBLE_MODE_FPS, + RUMBLE_MODE_RACE, + RUMBLE_MODE_AVERAGE, + RUMBLE_MODE_SPG, + RUMBLE_MODE_RPG, +}; + +static const char *const rumble_mode_text[] = { + [RUMBLE_MODE_UNKNOWN] = "unknown", + [RUMBLE_MODE_FPS] = "fps", + [RUMBLE_MODE_RACE] = "racing", + [RUMBLE_MODE_AVERAGE] = "standard", + [RUMBLE_MODE_SPG] = "spg", + [RUMBLE_MODE_RPG] = "rpg", +}; + +#define FPS_MODE_DPI 0x02 +#define TRIGGER_CALIBRATE 0x04 +#define JOYSTICK_CALIBRATE 0x04 +#define GYRO_CALIBRATE 0x06 + +enum cal_device_type { + CALDEV_GYROSCOPE = 0x01, + CALDEV_JOYSTICK, + CALDEV_TRIGGER, + CALDEV_JOY_TRIGGER, +}; + +enum cal_enable { + CAL_UNKNOWN, + CAL_START, + CAL_STOP, +}; + +static const char *const cal_enabled_text[] = { + [CAL_UNKNOWN] = "unknown", + [CAL_START] = "start", + [CAL_STOP] = "stop", +}; + +enum cal_status_index { + CAL_STAT_UNKNOWN, + CAL_STAT_SUCCESS, + CAL_STAT_FAILURE, +}; + +static const char *const cal_status_text[] = { + [CAL_STAT_UNKNOWN] = "unknown", + [CAL_STAT_SUCCESS] = "success", + [CAL_STAT_FAILURE] = "failure", +}; + +enum rgb_config_index { + LIGHT_CFG_ALL = 0x01, + LIGHT_MODE_SEL, + LIGHT_PROFILE_SEL, + USR_LIGHT_PROFILE_1, + USR_LIGHT_PROFILE_2, + USR_LIGHT_PROFILE_3, +}; + +enum rgb_mode_index { + RGB_MODE_UNKNOWN, + RGB_MODE_DYNAMIC, + RGB_MODE_CUSTOM, +}; + +static const char *const rgb_mode_text[] = { + [RGB_MODE_UNKNOWN] = "unknown", + [RGB_MODE_DYNAMIC] = "dynamic", + [RGB_MODE_CUSTOM] = "custom", +}; + +enum rgb_effect_index { + RGB_EFFECT_MONO, + RGB_EFFECT_BREATHE, + RGB_EFFECT_CHROMA, + RGB_EFFECT_RAINBOW, +}; + +static const char *const rgb_effect_text[] = { + [RGB_EFFECT_MONO] = "monocolor", + [RGB_EFFECT_BREATHE] = "breathe", + [RGB_EFFECT_CHROMA] = "chroma", + [RGB_EFFECT_RAINBOW] = "rainbow", +}; + +enum device_status_index { + GET_CAL_STATUS = 0x02, + GET_UPGRADE_STATUS, + GET_MACRO_REC_STATUS, + GET_HOTKEY_TRIGG_STATUS, +}; + +enum os_mode_cfg_index { + SET_OS_MODE = 0x09, + GET_OS_MODE, +}; + +enum os_mode_type_index { + OS_UNKNOWN, + WINDOWS, + LINUX, +}; + +static const char *const os_mode_text[] = { + [OS_UNKNOWN] = "unknown", + [WINDOWS] = "windows", + [LINUX] = "linux", +}; + +static int hid_go_version_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->sub_cmd) { + case PRODUCT_VERSION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_product = + get_unaligned_be32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_product = + get_unaligned_be32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_product = + get_unaligned_be32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_product = + get_unaligned_be32(cmd_rep->data); + return 0; + default: + return -EINVAL; + } + case PROTOCOL_VERSION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_protocol = + get_unaligned_be32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_protocol = + get_unaligned_be32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_protocol = + get_unaligned_be32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_protocol = + get_unaligned_be32(cmd_rep->data); + return 0; + default: + return -EINVAL; + } + case FIRMWARE_VERSION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_firmware = + get_unaligned_be32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_firmware = + get_unaligned_be32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_firmware = + get_unaligned_be32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_firmware = + get_unaligned_be32(cmd_rep->data); + return 0; + default: + return -EINVAL; + } + case HARDWARE_VERSION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_hardware = + get_unaligned_be32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_hardware = + get_unaligned_be32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_hardware = + get_unaligned_be32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_hardware = + get_unaligned_be32(cmd_rep->data); + return 0; + default: + return -EINVAL; + } + case HARDWARE_GENERATION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_gen = cmd_rep->data[0]; + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_gen = cmd_rep->data[0]; + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_gen = cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_gen = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int hid_go_feature_status_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->sub_cmd) { + case FEATURE_RESET_GAMEPAD: + return 0; + case FEATURE_IMU_ENABLE: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.imu_left_sensor_en = cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.imu_right_sensor_en = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + } + case FEATURE_IMU_BYPASS: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.imu_left_bypass_en = cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.imu_right_bypass_en = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + } + break; + case FEATURE_LIGHT_ENABLE: + drvdata.rgb_en = cmd_rep->data[0]; + return 0; + case FEATURE_AUTO_SLEEP_TIME: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.gp_left_auto_sleep_time = cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_auto_sleep_time = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + } + break; + case FEATURE_TOUCHPAD_ENABLE: + drvdata.tp_en = cmd_rep->data[0]; + return 0; + case FEATURE_GAMEPAD_MODE: + drvdata.gp_mode = cmd_rep->data[0]; + return 0; + case FEATURE_FPS_SWITCH_STATUS: + drvdata.fps_mode = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + } +} + +static int hid_go_motor_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->sub_cmd) { + case MOTOR_CFG_ALL: + return -EINVAL; + case MOTOR_INTENSITY: + drvdata.gp_rumble_intensity = cmd_rep->data[0]; + return 0; + case VIBRATION_NOTIFY_ENABLE: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.gp_left_notify_en = cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_notify_en = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + } + break; + case RUMBLE_MODE: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.gp_left_rumble_mode = cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_rumble_mode = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + } + case TP_VIBRATION_ENABLE: + drvdata.tp_vibration_en = cmd_rep->data[0]; + return 0; + case TP_VIBRATION_INTENSITY: + drvdata.tp_vibration_intensity = cmd_rep->data[0]; + return 0; + } + return -EINVAL; +} + +static int hid_go_fps_dpi_event(struct command_report *cmd_rep) +{ + if (cmd_rep->sub_cmd != FPS_MODE_DPI) + return -EINVAL; + + drvdata.mouse_dpi = get_unaligned_le32(cmd_rep->data); + + return 0; +} + +static int hid_go_light_event(struct command_report *cmd_rep) +{ + struct led_classdev_mc *mc_cdev; + + switch (cmd_rep->sub_cmd) { + case LIGHT_MODE_SEL: + drvdata.rgb_mode = cmd_rep->data[0]; + return 0; + case LIGHT_PROFILE_SEL: + drvdata.rgb_profile = cmd_rep->data[0]; + return 0; + case USR_LIGHT_PROFILE_1: + case USR_LIGHT_PROFILE_2: + case USR_LIGHT_PROFILE_3: + mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + drvdata.rgb_effect = cmd_rep->data[0]; + mc_cdev->subled_info[0].intensity = cmd_rep->data[1]; + mc_cdev->subled_info[1].intensity = cmd_rep->data[2]; + mc_cdev->subled_info[2].intensity = cmd_rep->data[3]; + drvdata.led_cdev->brightness = cmd_rep->data[4]; + drvdata.rgb_speed = 100 - cmd_rep->data[5]; + return 0; + default: + return -EINVAL; + } +} + +static int hid_go_device_status_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + switch (cmd_rep->data[0]) { + case CALDEV_GYROSCOPE: + drvdata.gp_left_gyro_cal_status = cmd_rep->data[1]; + return 0; + case CALDEV_JOYSTICK: + drvdata.gp_left_joy_cal_status = cmd_rep->data[1]; + return 0; + case CALDEV_TRIGGER: + drvdata.gp_left_trigg_cal_status = cmd_rep->data[1]; + return 0; + default: + return -EINVAL; + } + break; + case RIGHT_CONTROLLER: + switch (cmd_rep->data[0]) { + case CALDEV_GYROSCOPE: + drvdata.gp_right_gyro_cal_status = cmd_rep->data[1]; + return 0; + case CALDEV_JOYSTICK: + drvdata.gp_right_joy_cal_status = cmd_rep->data[1]; + return 0; + case CALDEV_TRIGGER: + drvdata.gp_right_trigg_cal_status = cmd_rep->data[1]; + return 0; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } +} + +static int hid_go_os_mode_cfg_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->sub_cmd) { + case SET_OS_MODE: + if (cmd_rep->data[0] != 1) + return -EIO; + return 0; + case GET_OS_MODE: + drvdata.os_mode = cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + } +} + +static int hid_go_set_event_return(struct command_report *cmd_rep) +{ + if (cmd_rep->data[0] != 0) + return -EIO; + + return 0; +} + +static int get_endpoint_address(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_host_endpoint *ep; + + ep = intf->cur_altsetting->endpoint; + if (!ep) + return -ENODEV; + + return ep->desc.bEndpointAddress; +} + +static int hid_go_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct command_report *cmd_rep; + int ep, ret; + + if (size != GO_PACKET_SIZE) + goto passthrough; + + ep = get_endpoint_address(hdev); + if (ep != GO_GP_INTF_IN) + goto passthrough; + + cmd_rep = (struct command_report *)data; + + switch (cmd_rep->id) { + case MCU_CONFIG_DATA: + switch (cmd_rep->cmd) { + case GET_VERSION_DATA: + ret = hid_go_version_event(cmd_rep); + break; + case GET_FEATURE_STATUS: + ret = hid_go_feature_status_event(cmd_rep); + break; + case GET_MOTOR_CFG: + ret = hid_go_motor_event(cmd_rep); + break; + case GET_DPI_CFG: + ret = hid_go_fps_dpi_event(cmd_rep); + break; + case GET_RGB_CFG: + ret = hid_go_light_event(cmd_rep); + break; + case GET_DEVICE_STATUS: + ret = hid_go_device_status_event(cmd_rep); + break; + case SET_FEATURE_STATUS: + case SET_MOTOR_CFG: + case SET_DPI_CFG: + case SET_RGB_CFG: + case SET_TRIGGER_CFG: + case SET_JOYSTICK_CFG: + case SET_GYRO_CFG: + ret = hid_go_set_event_return(cmd_rep); + break; + default: + ret = -EINVAL; + break; + } + break; + case OS_MODE_DATA: + ret = hid_go_os_mode_cfg_event(cmd_rep); + break; + default: + goto passthrough; + } + dev_dbg(&hdev->dev, "Rx data as raw input report: [%*ph]\n", + GO_PACKET_SIZE, data); + + complete(&drvdata.send_cmd_complete); + return ret; + +passthrough: + /* Forward other HID reports so they generate events */ + hid_input_report(hdev, HID_INPUT_REPORT, data, size, 1); + return 0; +} + +static int mcu_property_out(struct hid_device *hdev, u8 id, u8 command, + u8 index, enum dev_type device, u8 *data, size_t len) +{ + unsigned char *dmabuf __free(kfree) = NULL; + u8 header[] = { GO_OUTPUT_REPORT_ID, id, command, index, device }; + size_t header_size = ARRAY_SIZE(header); + int timeout = 50; + int ret; + + if (header_size + len > GO_PACKET_SIZE) + return -EINVAL; + + guard(mutex)(&drvdata.cfg_mutex); + /* We can't use a devm_alloc reusable buffer without side effects during suspend */ + dmabuf = kzalloc(GO_PACKET_SIZE, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + memcpy(dmabuf, header, header_size); + memcpy(dmabuf + header_size, data, len); + + dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n", + GO_PACKET_SIZE, dmabuf); + + ret = hid_hw_output_report(hdev, dmabuf, GO_PACKET_SIZE); + if (ret < 0) + return ret; + + ret = ret == GO_PACKET_SIZE ? 0 : -EINVAL; + if (ret) + return ret; + + ret = wait_for_completion_interruptible_timeout(&drvdata.send_cmd_complete, + msecs_to_jiffies(timeout)); + + if (ret == 0) /* timeout occurred */ + ret = -EBUSY; + + reinit_completion(&drvdata.send_cmd_complete); + return 0; +} + +static ssize_t version_show(struct device *dev, struct device_attribute *attr, + char *buf, enum version_data_index index, + enum dev_type device_type) +{ + ssize_t count = 0; + int ret; + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + index, device_type, NULL, 0); + if (ret) + return ret; + + switch (index) { + case PRODUCT_VERSION: + switch (device_type) { + case USB_MCU: + count = sysfs_emit(buf, "%x\n", + drvdata.mcu_version_product); + break; + case TX_DONGLE: + count = sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_product); + break; + case LEFT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_product); + break; + case RIGHT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_right_version_product); + break; + default: + return -EINVAL; + } + break; + case PROTOCOL_VERSION: + switch (device_type) { + case USB_MCU: + count = sysfs_emit(buf, "%x\n", + drvdata.mcu_version_protocol); + break; + case TX_DONGLE: + count = sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_protocol); + break; + case LEFT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_protocol); + break; + case RIGHT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_right_version_protocol); + break; + default: + return -EINVAL; + } + break; + case FIRMWARE_VERSION: + switch (device_type) { + case USB_MCU: + count = sysfs_emit(buf, "%x\n", + drvdata.mcu_version_firmware); + break; + case TX_DONGLE: + count = sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_firmware); + break; + case LEFT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_firmware); + break; + case RIGHT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_right_version_firmware); + break; + default: + return -EINVAL; + } + break; + case HARDWARE_VERSION: + switch (device_type) { + case USB_MCU: + count = sysfs_emit(buf, "%x\n", + drvdata.mcu_version_hardware); + break; + case TX_DONGLE: + count = sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_hardware); + break; + case LEFT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_hardware); + break; + case RIGHT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_right_version_hardware); + break; + default: + return -EINVAL; + } + break; + case HARDWARE_GENERATION: + switch (device_type) { + case USB_MCU: + count = sysfs_emit(buf, "%x\n", + drvdata.mcu_version_gen); + break; + case TX_DONGLE: + count = sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_gen); + break; + case LEFT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_gen); + break; + case RIGHT_CONTROLLER: + count = sysfs_emit(buf, "%x\n", + drvdata.gp_right_version_gen); + break; + default: + return -EINVAL; + } + break; + } + + return count; +} + +static ssize_t feature_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + enum feature_status_index index, + enum dev_type device_type) +{ + size_t size = 1; + u8 val = 0; + int ret; + + switch (index) { + case FEATURE_IMU_ENABLE: + case FEATURE_IMU_BYPASS: + case FEATURE_LIGHT_ENABLE: + case FEATURE_TOUCHPAD_ENABLE: + ret = sysfs_match_string(enabled_status_text, buf); + val = ret; + break; + case FEATURE_AUTO_SLEEP_TIME: + ret = kstrtou8(buf, 10, &val); + break; + case FEATURE_RESET_GAMEPAD: + ret = kstrtou8(buf, 10, &val); + if (val != GO_GP_RESET_SUCCESS) + return -EINVAL; + break; + case FEATURE_FPS_SWITCH_STATUS: + ret = sysfs_match_string(fps_switch_text, buf); + val = ret; + break; + case FEATURE_GAMEPAD_MODE: + ret = sysfs_match_string(gamepad_mode_text, buf); + val = ret; + break; + default: + return -EINVAL; + } + + if (ret < 0) + return ret; + + if (!val) + size = 0; + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, + SET_FEATURE_STATUS, index, device_type, &val, + size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t feature_status_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum feature_status_index index, + enum dev_type device_type) +{ + ssize_t count = 0; + int ret; + u8 i; + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, + GET_FEATURE_STATUS, index, device_type, NULL, 0); + if (ret) + return ret; + + switch (index) { + case FEATURE_IMU_ENABLE: + switch (device_type) { + case LEFT_CONTROLLER: + i = drvdata.imu_left_sensor_en; + break; + case RIGHT_CONTROLLER: + i = drvdata.imu_right_sensor_en; + break; + default: + return -EINVAL; + } + if (i >= ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case FEATURE_IMU_BYPASS: + switch (device_type) { + case LEFT_CONTROLLER: + i = drvdata.imu_left_bypass_en; + break; + case RIGHT_CONTROLLER: + i = drvdata.imu_right_bypass_en; + break; + default: + return -EINVAL; + } + if (i >= ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case FEATURE_LIGHT_ENABLE: + i = drvdata.rgb_en; + if (i >= ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case FEATURE_TOUCHPAD_ENABLE: + i = drvdata.tp_en; + if (i >= ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case FEATURE_AUTO_SLEEP_TIME: + switch (device_type) { + case LEFT_CONTROLLER: + i = drvdata.gp_left_auto_sleep_time; + break; + case RIGHT_CONTROLLER: + i = drvdata.gp_right_auto_sleep_time; + break; + default: + return -EINVAL; + } + count = sysfs_emit(buf, "%u\n", i); + break; + case FEATURE_FPS_SWITCH_STATUS: + i = drvdata.fps_mode; + if (i >= ARRAY_SIZE(fps_switch_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", fps_switch_text[i]); + break; + case FEATURE_GAMEPAD_MODE: + i = drvdata.gp_mode; + if (i >= ARRAY_SIZE(gamepad_mode_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", gamepad_mode_text[i]); + break; + default: + return -EINVAL; + } + + return count; +} + +static ssize_t feature_status_options(struct device *dev, + struct device_attribute *attr, char *buf, + enum feature_status_index index) +{ + ssize_t count = 0; + unsigned int i; + + switch (index) { + case FEATURE_IMU_ENABLE: + case FEATURE_IMU_BYPASS: + case FEATURE_LIGHT_ENABLE: + case FEATURE_TOUCHPAD_ENABLE: + for (i = 1; i < ARRAY_SIZE(enabled_status_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + enabled_status_text[i]); + } + break; + case FEATURE_AUTO_SLEEP_TIME: + return sysfs_emit(buf, "0-255\n"); + case FEATURE_FPS_SWITCH_STATUS: + for (i = 1; i < ARRAY_SIZE(fps_switch_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + fps_switch_text[i]); + } + break; + case FEATURE_GAMEPAD_MODE: + for (i = 1; i < ARRAY_SIZE(gamepad_mode_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + gamepad_mode_text[i]); + } + break; + default: + return -EINVAL; + } + + if (count) + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t motor_config_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + enum motor_cfg_index index, + enum dev_type device_type) +{ + size_t size = 1; + u8 val = 0; + int ret; + + switch (index) { + case MOTOR_CFG_ALL: + return -EINVAL; + case MOTOR_INTENSITY: + ret = sysfs_match_string(intensity_text, buf); + val = ret; + break; + case VIBRATION_NOTIFY_ENABLE: + ret = sysfs_match_string(enabled_status_text, buf); + val = ret; + break; + case RUMBLE_MODE: + ret = sysfs_match_string(rumble_mode_text, buf); + val = ret; + break; + case TP_VIBRATION_ENABLE: + ret = sysfs_match_string(enabled_status_text, buf); + val = ret; + break; + case TP_VIBRATION_INTENSITY: + ret = sysfs_match_string(intensity_text, buf); + val = ret; + break; + } + + if (ret < 0) + return ret; + + if (!val) + size = 0; + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, SET_MOTOR_CFG, + index, device_type, &val, size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t motor_config_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum motor_cfg_index index, + enum dev_type device_type) +{ + ssize_t count = 0; + int ret; + u8 i; + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_MOTOR_CFG, + index, device_type, NULL, 0); + if (ret) + return ret; + + switch (index) { + case MOTOR_CFG_ALL: + return -EINVAL; + case MOTOR_INTENSITY: + i = drvdata.gp_rumble_intensity; + if (i >= ARRAY_SIZE(intensity_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", intensity_text[i]); + break; + case VIBRATION_NOTIFY_ENABLE: + switch (device_type) { + case LEFT_CONTROLLER: + i = drvdata.gp_left_notify_en; + break; + case RIGHT_CONTROLLER: + i = drvdata.gp_right_notify_en; + break; + default: + return -EINVAL; + } + if (i >= ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case RUMBLE_MODE: + switch (device_type) { + case LEFT_CONTROLLER: + i = drvdata.gp_left_rumble_mode; + break; + case RIGHT_CONTROLLER: + i = drvdata.gp_right_rumble_mode; + break; + default: + return -EINVAL; + } + if (i >= ARRAY_SIZE(rumble_mode_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", rumble_mode_text[i]); + break; + case TP_VIBRATION_ENABLE: + i = drvdata.tp_vibration_en; + if (i >= ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case TP_VIBRATION_INTENSITY: + i = drvdata.tp_vibration_intensity; + if (i >= ARRAY_SIZE(intensity_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", intensity_text[i]); + break; + } + + return count; +} + +static ssize_t motor_config_options(struct device *dev, + struct device_attribute *attr, char *buf, + enum motor_cfg_index index) +{ + ssize_t count = 0; + unsigned int i; + + switch (index) { + case MOTOR_CFG_ALL: + break; + case RUMBLE_MODE: + for (i = 1; i < ARRAY_SIZE(rumble_mode_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + rumble_mode_text[i]); + } + break; + case MOTOR_INTENSITY: + case TP_VIBRATION_INTENSITY: + for (i = 1; i < ARRAY_SIZE(intensity_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + intensity_text[i]); + } + break; + case VIBRATION_NOTIFY_ENABLE: + case TP_VIBRATION_ENABLE: + for (i = 1; i < ARRAY_SIZE(enabled_status_text); i++) { + count += sysfs_emit_at(buf, count, "%s ", + enabled_status_text[i]); + } + break; + } + + if (count) + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t fps_mode_dpi_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) + +{ + size_t size = 4; + u32 value; + u8 val[4]; + int ret; + + ret = kstrtou32(buf, 10, &value); + if (ret) + return ret; + + if (value != 500 && value != 800 && value != 1200 && value != 1800) + return -EINVAL; + + put_unaligned_le32(value, val); + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, SET_DPI_CFG, + FPS_MODE_DPI, UNSPECIFIED, val, size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t fps_mode_dpi_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_DPI_CFG, + FPS_MODE_DPI, UNSPECIFIED, NULL, 0); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%u\n", drvdata.mouse_dpi); +} + +static ssize_t fps_mode_dpi_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "500 800 1200 1800\n"); +} + +static ssize_t device_status_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum device_status_index index, + enum dev_type device_type, + enum cal_device_type cal_type) +{ + u8 i; + + switch (index) { + case GET_CAL_STATUS: + switch (device_type) { + case LEFT_CONTROLLER: + switch (cal_type) { + case CALDEV_GYROSCOPE: + i = drvdata.gp_left_gyro_cal_status; + break; + case CALDEV_JOYSTICK: + i = drvdata.gp_left_joy_cal_status; + break; + case CALDEV_TRIGGER: + i = drvdata.gp_left_trigg_cal_status; + break; + default: + return -EINVAL; + } + break; + case RIGHT_CONTROLLER: + switch (cal_type) { + case CALDEV_GYROSCOPE: + i = drvdata.gp_right_gyro_cal_status; + break; + case CALDEV_JOYSTICK: + i = drvdata.gp_right_joy_cal_status; + break; + case CALDEV_TRIGGER: + i = drvdata.gp_right_trigg_cal_status; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + if (i >= ARRAY_SIZE(cal_status_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", cal_status_text[i]); +} + +static ssize_t calibrate_config_store(struct device *dev, + struct device_attribute *attr, + const char *buf, u8 cmd, u8 sub_cmd, + size_t count, enum dev_type device_type) +{ + size_t size = 1; + u8 val = 0; + int ret; + + ret = sysfs_match_string(cal_enabled_text, buf); + if (ret < 0) + return ret; + + val = ret; + if (!val) + size = 0; + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, cmd, sub_cmd, + device_type, &val, size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t calibrate_config_options(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(cal_enabled_text); i++) + count += sysfs_emit_at(buf, count, "%s ", cal_enabled_text[i]); + + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t os_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + size_t size = 1; + int ret; + u8 val; + + ret = sysfs_match_string(os_mode_text, buf); + if (ret <= 0) + return ret; + + val = ret; + ret = mcu_property_out(drvdata.hdev, OS_MODE_DATA, FEATURE_OS_MODE, + SET_OS_MODE, USB_MCU, &val, size); + if (ret < 0) + return ret; + + drvdata.os_mode = val; + + return count; +} + +static ssize_t os_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + ssize_t count = 0; + int ret; + u8 i; + + ret = mcu_property_out(drvdata.hdev, OS_MODE_DATA, FEATURE_OS_MODE, + GET_OS_MODE, USB_MCU, NULL, 0); + if (ret) + return ret; + + i = drvdata.os_mode; + if (i >= ARRAY_SIZE(os_mode_text)) + return -EINVAL; + + count = sysfs_emit(buf, "%s\n", os_mode_text[i]); + + return count; +} + +static ssize_t os_mode_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(os_mode_text); i++) + count += sysfs_emit_at(buf, count, "%s ", os_mode_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} + +static int rgb_cfg_call(struct hid_device *hdev, enum mcu_command_index cmd, + enum rgb_config_index index, u8 *val, size_t size) +{ + if (cmd != SET_RGB_CFG && cmd != GET_RGB_CFG) + return -EINVAL; + + if (index < LIGHT_CFG_ALL || index > USR_LIGHT_PROFILE_3) + return -EINVAL; + + return mcu_property_out(hdev, MCU_CONFIG_DATA, cmd, index, UNSPECIFIED, + val, size); +} + +static int rgb_attr_show(void) +{ + enum rgb_config_index index; + + index = drvdata.rgb_profile + 3; + + return rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, index, NULL, 0); +} + +static ssize_t rgb_effect_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + u8 effect; + int ret; + + ret = sysfs_match_string(rgb_effect_text, buf); + if (ret < 0) + return ret; + + effect = ret; + index = drvdata.rgb_profile + 3; + u8 rgb_profile[6] = { effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + drvdata.led_cdev->brightness, + drvdata.rgb_speed }; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + if (ret) + return ret; + + drvdata.rgb_effect = effect; + return count; +} + +static ssize_t rgb_effect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret = rgb_attr_show(); + if (ret) + return ret; + + if (drvdata.rgb_effect >= ARRAY_SIZE(rgb_effect_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", rgb_effect_text[drvdata.rgb_effect]); +} + +static ssize_t rgb_effect_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rgb_effect_text); i++) + count += sysfs_emit_at(buf, count, "%s ", rgb_effect_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t rgb_speed_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + int val = 0; + int ret; + + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; + + if (val < 0 || val > 100) + return -EINVAL; + + /* This is a delay setting, invert logic for consistency with other drivers */ + val = 100 - val; + + index = drvdata.rgb_profile + 3; + u8 rgb_profile[6] = { drvdata.rgb_effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + drvdata.led_cdev->brightness, + val }; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + if (ret) + return ret; + + drvdata.rgb_speed = val; + + return count; +} + +static ssize_t rgb_speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret, val; + + ret = rgb_attr_show(); + if (ret) + return ret; + + if (drvdata.rgb_speed > 100) + return -EINVAL; + + val = drvdata.rgb_speed; + + return sysfs_emit(buf, "%hhu\n", val); +} + +static ssize_t rgb_speed_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0-100\n"); +} + +static ssize_t rgb_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret = sysfs_match_string(rgb_mode_text, buf); + if (ret <= 0) + return ret; + + val = ret; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_MODE_SEL, &val, 1); + if (ret) + return ret; + + drvdata.rgb_mode = val; + + return count; +} + +static ssize_t rgb_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + + ret = rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_MODE_SEL, NULL, 0); + if (ret) + return ret; + + if (drvdata.rgb_mode >= ARRAY_SIZE(rgb_mode_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", rgb_mode_text[drvdata.rgb_mode]); +} + +static ssize_t rgb_mode_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(rgb_mode_text); i++) + count += sysfs_emit_at(buf, count, "%s ", rgb_mode_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t rgb_profile_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + size_t size = 1; + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret < 0) + return ret; + + if (val < 1 || val > 3) + return -EINVAL; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_PROFILE_SEL, &val, size); + if (ret) + return ret; + + drvdata.rgb_profile = val; + + return count; +} + +static ssize_t rgb_profile_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret = rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_PROFILE_SEL, NULL, 0); + if (ret) + return ret; + + if (drvdata.rgb_profile < 1 || drvdata.rgb_profile > 3) + return -EINVAL; + + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_profile); +} + +static ssize_t rgb_profile_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "1-3\n"); +} + +static void hid_go_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + int ret; + + if (brightness > led_cdev->max_brightness) { + dev_err(led_cdev->dev, "Invalid argument\n"); + return; + } + + index = drvdata.rgb_profile + 3; + u8 rgb_profile[6] = { drvdata.rgb_effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + brightness, + drvdata.rgb_speed }; + + ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + switch (ret) { + case 0: + led_cdev->brightness = brightness; + break; + case -ENODEV: /* during switch to IAP -ENODEV is expected */ + case -ENOSYS: /* during rmmod -ENOSYS is expected */ + dev_dbg(led_cdev->dev, "Failed to write RGB profile: %i\n", ret); + break; + default: + dev_err(led_cdev->dev, "Failed to write RGB profile: %i\n", ret); + } +} + +#define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _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, \ + _dtype); \ + } \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_show(dev, attr, buf, _name.index, _dtype); \ + } \ + static ssize_t _name##_##_rtype##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return _group##_options(dev, attr, buf, _name.index); \ + } \ + static DEVICE_ATTR_RW_NAMED(_name, _attrname) + +#define LEGO_DEVICE_ATTR_WO(_name, _attrname, _dtype, _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, \ + _dtype); \ + } \ + static DEVICE_ATTR_WO_NAMED(_name, _attrname) + +#define LEGO_DEVICE_ATTR_RO(_name, _attrname, _dtype, _group) \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_show(dev, attr, buf, _name.index, _dtype); \ + } \ + static DEVICE_ATTR_RO_NAMED(_name, _attrname) + +#define LEGO_CAL_DEVICE_ATTR(_name, _attrname, _scmd, _dtype, _rtype) \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return calibrate_config_store(dev, attr, buf, _name.index, \ + _scmd, count, _dtype); \ + } \ + static ssize_t _name##_##_rtype##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return calibrate_config_options(dev, attr, buf); \ + } \ + static DEVICE_ATTR_WO_NAMED(_name, _attrname) + +#define LEGO_DEVICE_STATUS_ATTR(_name, _attrname, _scmd, _dtype) \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return device_status_show(dev, attr, buf, _name.index, _scmd, \ + _dtype); \ + } \ + static DEVICE_ATTR_RO_NAMED(_name, _attrname) + +/* Gamepad - MCU */ +static struct go_cfg_attr version_product_mcu = { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_mcu, "product_version", USB_MCU, version); + +static struct go_cfg_attr version_protocol_mcu = { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_mcu, "protocol_version", USB_MCU, version); + +static struct go_cfg_attr version_firmware_mcu = { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_mcu, "firmware_version", USB_MCU, version); + +static struct go_cfg_attr version_hardware_mcu = { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_mcu, "hardware_version", USB_MCU, version); + +static struct go_cfg_attr version_gen_mcu = { HARDWARE_GENERATION }; +LEGO_DEVICE_ATTR_RO(version_gen_mcu, "hardware_generation", USB_MCU, version); + +static struct go_cfg_attr fps_switch_status = { FEATURE_FPS_SWITCH_STATUS }; +LEGO_DEVICE_ATTR_RO(fps_switch_status, "fps_switch_status", UNSPECIFIED, + feature_status); + +static struct go_cfg_attr gamepad_mode = { FEATURE_GAMEPAD_MODE }; +LEGO_DEVICE_ATTR_RW(gamepad_mode, "mode", UNSPECIFIED, index, feature_status); +static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mode_index"); + +static struct go_cfg_attr reset_mcu = { FEATURE_RESET_GAMEPAD }; +LEGO_DEVICE_ATTR_WO(reset_mcu, "reset_mcu", USB_MCU, feature_status); + +static struct go_cfg_attr gamepad_rumble_intensity = { MOTOR_INTENSITY }; +LEGO_DEVICE_ATTR_RW(gamepad_rumble_intensity, "rumble_intensity", UNSPECIFIED, + index, motor_config); +static DEVICE_ATTR_RO_NAMED(gamepad_rumble_intensity_index, + "rumble_intensity_index"); + +static DEVICE_ATTR_RW(fps_mode_dpi); +static DEVICE_ATTR_RO(fps_mode_dpi_index); + +static DEVICE_ATTR_RW(os_mode); +static DEVICE_ATTR_RO(os_mode_index); + +static struct attribute *mcu_attrs[] = { + &dev_attr_fps_mode_dpi.attr, + &dev_attr_fps_mode_dpi_index.attr, + &dev_attr_fps_switch_status.attr, + &dev_attr_gamepad_mode.attr, + &dev_attr_gamepad_mode_index.attr, + &dev_attr_gamepad_rumble_intensity.attr, + &dev_attr_gamepad_rumble_intensity_index.attr, + &dev_attr_os_mode.attr, + &dev_attr_os_mode_index.attr, + &dev_attr_reset_mcu.attr, + &dev_attr_version_firmware_mcu.attr, + &dev_attr_version_gen_mcu.attr, + &dev_attr_version_hardware_mcu.attr, + &dev_attr_version_product_mcu.attr, + &dev_attr_version_protocol_mcu.attr, + NULL, +}; + +static const struct attribute_group mcu_attr_group = { + .attrs = mcu_attrs, +}; + +/* Gamepad - TX Dongle */ +static struct go_cfg_attr version_product_tx_dongle = { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_tx_dongle, "product_version", TX_DONGLE, version); + +static struct go_cfg_attr version_protocol_tx_dongle = { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_tx_dongle, "protocol_version", TX_DONGLE, version); + +static struct go_cfg_attr version_firmware_tx_dongle = { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_tx_dongle, "firmware_version", TX_DONGLE, version); + +static struct go_cfg_attr version_hardware_tx_dongle = { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_tx_dongle, "hardware_version", TX_DONGLE, version); + +static struct go_cfg_attr version_gen_tx_dongle = { HARDWARE_GENERATION }; +LEGO_DEVICE_ATTR_RO(version_gen_tx_dongle, "hardware_generation", TX_DONGLE, version); + +static struct go_cfg_attr reset_tx_dongle = { FEATURE_RESET_GAMEPAD }; +LEGO_DEVICE_ATTR_RO(reset_tx_dongle, "reset", TX_DONGLE, feature_status); + +static struct attribute *tx_dongle_attrs[] = { + &dev_attr_reset_tx_dongle.attr, + &dev_attr_version_hardware_tx_dongle.attr, + &dev_attr_version_firmware_tx_dongle.attr, + &dev_attr_version_gen_tx_dongle.attr, + &dev_attr_version_product_tx_dongle.attr, + &dev_attr_version_protocol_tx_dongle.attr, + NULL, +}; + +static const struct attribute_group tx_dongle_attr_group = { + .name = "tx_dongle", + .attrs = tx_dongle_attrs, +}; + +/* Gamepad - Left */ +static struct go_cfg_attr version_product_left = { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_left, "product_version", LEFT_CONTROLLER, version); + +static struct go_cfg_attr version_protocol_left = { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_left, "protocol_version", LEFT_CONTROLLER, version); + +static struct go_cfg_attr version_firmware_left = { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_left, "firmware_version", LEFT_CONTROLLER, version); + +static struct go_cfg_attr version_hardware_left = { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_left, "hardware_version", LEFT_CONTROLLER, version); + +static struct go_cfg_attr version_gen_left = { HARDWARE_GENERATION }; +LEGO_DEVICE_ATTR_RO(version_gen_left, "hardware_generation", LEFT_CONTROLLER, version); + +static struct go_cfg_attr auto_sleep_time_left = { FEATURE_AUTO_SLEEP_TIME }; +LEGO_DEVICE_ATTR_RW(auto_sleep_time_left, "auto_sleep_time", LEFT_CONTROLLER, + range, feature_status); +static DEVICE_ATTR_RO_NAMED(auto_sleep_time_left_range, + "auto_sleep_time_range"); + +static struct go_cfg_attr imu_bypass_left = { FEATURE_IMU_BYPASS }; +LEGO_DEVICE_ATTR_RW(imu_bypass_left, "imu_bypass_enabled", LEFT_CONTROLLER, + index, feature_status); +static DEVICE_ATTR_RO_NAMED(imu_bypass_left_index, "imu_bypass_enabled_index"); + +static struct go_cfg_attr imu_enabled_left = { FEATURE_IMU_ENABLE }; +LEGO_DEVICE_ATTR_RW(imu_enabled_left, "imu_enabled", LEFT_CONTROLLER, index, + feature_status); +static DEVICE_ATTR_RO_NAMED(imu_enabled_left_index, "imu_enabled_index"); + +static struct go_cfg_attr reset_left = { FEATURE_RESET_GAMEPAD }; +LEGO_DEVICE_ATTR_WO(reset_left, "reset", LEFT_CONTROLLER, feature_status); + +static struct go_cfg_attr rumble_mode_left = { RUMBLE_MODE }; +LEGO_DEVICE_ATTR_RW(rumble_mode_left, "rumble_mode", LEFT_CONTROLLER, index, + motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_mode_left_index, "rumble_mode_index"); + +static struct go_cfg_attr rumble_notification_left = { VIBRATION_NOTIFY_ENABLE }; +LEGO_DEVICE_ATTR_RW(rumble_notification_left, "rumble_notification", + LEFT_CONTROLLER, index, motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_notification_left_index, + "rumble_notification_index"); + +static struct go_cfg_attr cal_trigg_left = { TRIGGER_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_trigg_left, "calibrate_trigger", SET_TRIGGER_CFG, + LEFT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_trigg_left_index, "calibrate_trigger_index"); + +static struct go_cfg_attr cal_joy_left = { JOYSTICK_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_joy_left, "calibrate_joystick", SET_JOYSTICK_CFG, + LEFT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_joy_left_index, "calibrate_joystick_index"); + +static struct go_cfg_attr cal_gyro_left = { GYRO_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_gyro_left, "calibrate_gyro", SET_GYRO_CFG, + LEFT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_gyro_left_index, "calibrate_gyro_index"); + +static struct go_cfg_attr cal_trigg_left_status = { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_trigg_left_status, "calibrate_trigger_status", + LEFT_CONTROLLER, CALDEV_TRIGGER); + +static struct go_cfg_attr cal_joy_left_status = { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_joy_left_status, "calibrate_joystick_status", + LEFT_CONTROLLER, CALDEV_JOYSTICK); + +static struct go_cfg_attr cal_gyro_left_status = { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_gyro_left_status, "calibrate_gyro_status", + LEFT_CONTROLLER, CALDEV_GYROSCOPE); + +static struct attribute *left_gamepad_attrs[] = { + &dev_attr_auto_sleep_time_left.attr, + &dev_attr_auto_sleep_time_left_range.attr, + &dev_attr_cal_gyro_left.attr, + &dev_attr_cal_gyro_left_index.attr, + &dev_attr_cal_gyro_left_status.attr, + &dev_attr_cal_joy_left.attr, + &dev_attr_cal_joy_left_index.attr, + &dev_attr_cal_joy_left_status.attr, + &dev_attr_cal_trigg_left.attr, + &dev_attr_cal_trigg_left_index.attr, + &dev_attr_cal_trigg_left_status.attr, + &dev_attr_imu_bypass_left.attr, + &dev_attr_imu_bypass_left_index.attr, + &dev_attr_imu_enabled_left.attr, + &dev_attr_imu_enabled_left_index.attr, + &dev_attr_reset_left.attr, + &dev_attr_rumble_mode_left.attr, + &dev_attr_rumble_mode_left_index.attr, + &dev_attr_rumble_notification_left.attr, + &dev_attr_rumble_notification_left_index.attr, + &dev_attr_version_hardware_left.attr, + &dev_attr_version_firmware_left.attr, + &dev_attr_version_gen_left.attr, + &dev_attr_version_product_left.attr, + &dev_attr_version_protocol_left.attr, + NULL, +}; + +static const struct attribute_group left_gamepad_attr_group = { + .name = "left_handle", + .attrs = left_gamepad_attrs, +}; + +/* Gamepad - Right */ +static struct go_cfg_attr version_product_right = { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_right, "product_version", RIGHT_CONTROLLER, version); + +static struct go_cfg_attr version_protocol_right = { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_right, "protocol_version", RIGHT_CONTROLLER, version); + +static struct go_cfg_attr version_firmware_right = { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_right, "firmware_version", RIGHT_CONTROLLER, version); + +static struct go_cfg_attr version_hardware_right = { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_right, "hardware_version", RIGHT_CONTROLLER, version); + +static struct go_cfg_attr version_gen_right = { HARDWARE_GENERATION }; +LEGO_DEVICE_ATTR_RO(version_gen_right, "hardware_generation", RIGHT_CONTROLLER, version); + +static struct go_cfg_attr auto_sleep_time_right = { FEATURE_AUTO_SLEEP_TIME }; +LEGO_DEVICE_ATTR_RW(auto_sleep_time_right, "auto_sleep_time", RIGHT_CONTROLLER, + range, feature_status); +static DEVICE_ATTR_RO_NAMED(auto_sleep_time_right_range, + "auto_sleep_time_range"); + +static struct go_cfg_attr imu_bypass_right = { FEATURE_IMU_BYPASS }; +LEGO_DEVICE_ATTR_RW(imu_bypass_right, "imu_bypass_enabled", RIGHT_CONTROLLER, + index, feature_status); +static DEVICE_ATTR_RO_NAMED(imu_bypass_right_index, "imu_bypass_enabled_index"); + +static struct go_cfg_attr imu_enabled_right = { FEATURE_IMU_BYPASS }; +LEGO_DEVICE_ATTR_RW(imu_enabled_right, "imu_enabled", RIGHT_CONTROLLER, index, + feature_status); +static DEVICE_ATTR_RO_NAMED(imu_enabled_right_index, "imu_enabled_index"); + +static struct go_cfg_attr reset_right = { FEATURE_RESET_GAMEPAD }; +LEGO_DEVICE_ATTR_WO(reset_right, "reset", LEFT_CONTROLLER, feature_status); + +static struct go_cfg_attr rumble_mode_right = { RUMBLE_MODE }; +LEGO_DEVICE_ATTR_RW(rumble_mode_right, "rumble_mode", RIGHT_CONTROLLER, index, + motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_mode_right_index, "rumble_mode_index"); + +static struct go_cfg_attr rumble_notification_right = { VIBRATION_NOTIFY_ENABLE }; +LEGO_DEVICE_ATTR_RW(rumble_notification_right, "rumble_notification", + RIGHT_CONTROLLER, index, motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_notification_right_index, + "rumble_notification_index"); + +static struct go_cfg_attr cal_trigg_right = { TRIGGER_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_trigg_right, "calibrate_trigger", SET_TRIGGER_CFG, + RIGHT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_trigg_right_index, "calibrate_trigger_index"); + +static struct go_cfg_attr cal_joy_right = { JOYSTICK_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_joy_right, "calibrate_joystick", SET_JOYSTICK_CFG, + RIGHT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_joy_right_index, "calibrate_joystick_index"); + +static struct go_cfg_attr cal_gyro_right = { GYRO_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_gyro_right, "calibrate_gyro", SET_GYRO_CFG, + RIGHT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_gyro_right_index, "calibrate_gyro_index"); + +static struct go_cfg_attr cal_trigg_right_status = { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_trigg_right_status, "calibrate_trigger_status", + RIGHT_CONTROLLER, CALDEV_TRIGGER); + +static struct go_cfg_attr cal_joy_right_status = { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_joy_right_status, "calibrate_joystick_status", + RIGHT_CONTROLLER, CALDEV_JOYSTICK); + +static struct go_cfg_attr cal_gyro_right_status = { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_gyro_right_status, "calibrate_gyro_status", + RIGHT_CONTROLLER, CALDEV_GYROSCOPE); + +static struct attribute *right_gamepad_attrs[] = { + &dev_attr_auto_sleep_time_right.attr, + &dev_attr_auto_sleep_time_right_range.attr, + &dev_attr_cal_gyro_right.attr, + &dev_attr_cal_gyro_right_index.attr, + &dev_attr_cal_gyro_right_status.attr, + &dev_attr_cal_joy_right.attr, + &dev_attr_cal_joy_right_index.attr, + &dev_attr_cal_joy_right_status.attr, + &dev_attr_cal_trigg_right.attr, + &dev_attr_cal_trigg_right_index.attr, + &dev_attr_cal_trigg_right_status.attr, + &dev_attr_imu_bypass_right.attr, + &dev_attr_imu_bypass_right_index.attr, + &dev_attr_imu_enabled_right.attr, + &dev_attr_imu_enabled_right_index.attr, + &dev_attr_reset_right.attr, + &dev_attr_rumble_mode_right.attr, + &dev_attr_rumble_mode_right_index.attr, + &dev_attr_rumble_notification_right.attr, + &dev_attr_rumble_notification_right_index.attr, + &dev_attr_version_hardware_right.attr, + &dev_attr_version_firmware_right.attr, + &dev_attr_version_gen_right.attr, + &dev_attr_version_product_right.attr, + &dev_attr_version_protocol_right.attr, + NULL, +}; + +static const struct attribute_group right_gamepad_attr_group = { + .name = "right_handle", + .attrs = right_gamepad_attrs, +}; + +/* Touchpad */ +static struct go_cfg_attr touchpad_enabled = { FEATURE_TOUCHPAD_ENABLE }; +LEGO_DEVICE_ATTR_RW(touchpad_enabled, "enabled", UNSPECIFIED, index, + feature_status); +static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); + +static struct go_cfg_attr touchpad_vibration_enabled = { TP_VIBRATION_ENABLE }; +LEGO_DEVICE_ATTR_RW(touchpad_vibration_enabled, "vibration_enabled", UNSPECIFIED, + index, motor_config); +static DEVICE_ATTR_RO_NAMED(touchpad_vibration_enabled_index, + "vibration_enabled_index"); + +static struct go_cfg_attr touchpad_vibration_intensity = { TP_VIBRATION_INTENSITY }; +LEGO_DEVICE_ATTR_RW(touchpad_vibration_intensity, "vibration_intensity", + UNSPECIFIED, index, motor_config); +static DEVICE_ATTR_RO_NAMED(touchpad_vibration_intensity_index, + "vibration_intensity_index"); + +static struct attribute *touchpad_attrs[] = { + &dev_attr_touchpad_enabled.attr, + &dev_attr_touchpad_enabled_index.attr, + &dev_attr_touchpad_vibration_enabled.attr, + &dev_attr_touchpad_vibration_enabled_index.attr, + &dev_attr_touchpad_vibration_intensity.attr, + &dev_attr_touchpad_vibration_intensity_index.attr, + NULL, +}; + +static const struct attribute_group touchpad_attr_group = { + .name = "touchpad", + .attrs = touchpad_attrs, +}; + +static const struct attribute_group *top_level_attr_groups[] = { + &mcu_attr_group, &tx_dongle_attr_group, + &left_gamepad_attr_group, &right_gamepad_attr_group, + &touchpad_attr_group, NULL, +}; + +/* RGB */ +static struct go_cfg_attr rgb_enabled = { FEATURE_LIGHT_ENABLE }; + +LEGO_DEVICE_ATTR_RW(rgb_enabled, "enabled", UNSPECIFIED, index, feature_status); +static DEVICE_ATTR_RO_NAMED(rgb_effect_index, "effect_index"); +static DEVICE_ATTR_RO_NAMED(rgb_enabled_index, "enabled_index"); +static DEVICE_ATTR_RO_NAMED(rgb_mode_index, "mode_index"); +static DEVICE_ATTR_RO_NAMED(rgb_profile_range, "profile_range"); +static DEVICE_ATTR_RO_NAMED(rgb_speed_range, "speed_range"); +static DEVICE_ATTR_RW_NAMED(rgb_effect, "effect"); +static DEVICE_ATTR_RW_NAMED(rgb_mode, "mode"); +static DEVICE_ATTR_RW_NAMED(rgb_profile, "profile"); +static DEVICE_ATTR_RW_NAMED(rgb_speed, "speed"); + +static struct attribute *go_rgb_attrs[] = { + &dev_attr_rgb_effect.attr, + &dev_attr_rgb_effect_index.attr, + &dev_attr_rgb_enabled.attr, + &dev_attr_rgb_enabled_index.attr, + &dev_attr_rgb_mode.attr, + &dev_attr_rgb_mode_index.attr, + &dev_attr_rgb_profile.attr, + &dev_attr_rgb_profile_range.attr, + &dev_attr_rgb_speed.attr, + &dev_attr_rgb_speed_range.attr, + NULL, +}; + +static struct attribute_group rgb_attr_group = { + .attrs = go_rgb_attrs, +}; + +static struct mc_subled go_rgb_subled_info[] = { + { + .color_index = LED_COLOR_ID_RED, + .brightness = 0x50, + .intensity = 0x24, + .channel = 0x1, + }, + { + .color_index = LED_COLOR_ID_GREEN, + .brightness = 0x50, + .intensity = 0x22, + .channel = 0x2, + }, + { + .color_index = LED_COLOR_ID_BLUE, + .brightness = 0x50, + .intensity = 0x99, + .channel = 0x3, + }, +}; + +static struct led_classdev_mc go_cdev_rgb = { + .led_cdev = { + .name = "go:rgb:joystick_rings", + .color = LED_COLOR_ID_RGB, + .brightness = 0x50, + .max_brightness = 0x64, + .brightness_set = hid_go_brightness_set, + }, + .num_colors = ARRAY_SIZE(go_rgb_subled_info), + .subled_info = go_rgb_subled_info, +}; + +static void cfg_setup(struct work_struct *work) +{ + int ret; + + /* MCU Version Attrs */ + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PRODUCT_VERSION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Product Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PROTOCOL_VERSION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Protocol Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + FIRMWARE_VERSION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Firmware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_VERSION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Hardware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_GENERATION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Hardware Generation: %i\n", ret); + return; + } + + /* TX Dongle Version Attrs */ + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PRODUCT_VERSION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Product Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PROTOCOL_VERSION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Protocol Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + FIRMWARE_VERSION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Firmware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_VERSION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Hardware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_GENERATION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Hardware Generation: %i\n", ret); + return; + } + + /* Left Handle Version Attrs */ + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PRODUCT_VERSION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Product Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PROTOCOL_VERSION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Protocol Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + FIRMWARE_VERSION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Firmware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_VERSION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Hardware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_GENERATION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Hardware Generation: %i\n", ret); + return; + } + + /* Right Handle Version Attrs */ + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PRODUCT_VERSION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Product Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PROTOCOL_VERSION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Protocol Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + FIRMWARE_VERSION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Firmware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_VERSION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Hardware Version: %i\n", ret); + return; + } + + ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_GENERATION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Hardware Generation: %i\n", ret); + return; + } +} + +static int hid_go_cfg_probe(struct hid_device *hdev, + const struct hid_device_id *_id) +{ + unsigned char *buf; + int ret; + + buf = devm_kzalloc(&hdev->dev, GO_PACKET_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + hid_set_drvdata(hdev, &drvdata); + drvdata.hdev = hdev; + mutex_init(&drvdata.cfg_mutex); + + ret = sysfs_create_groups(&hdev->dev.kobj, top_level_attr_groups); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create gamepad configuration attributes\n"); + return ret; + } + + ret = devm_led_classdev_multicolor_register(&hdev->dev, &go_cdev_rgb); + if (ret) { + dev_err_probe(&hdev->dev, ret, "Failed to create RGB device\n"); + return ret; + } + + ret = devm_device_add_group(go_cdev_rgb.led_cdev.dev, &rgb_attr_group); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create RGB configuration attributes\n"); + return ret; + } + + drvdata.led_cdev = &go_cdev_rgb.led_cdev; + + init_completion(&drvdata.send_cmd_complete); + + /* Executing calls prior to returning from probe will lock the MCU. Schedule + * initial data call after probe has completed and MCU can accept calls. + */ + INIT_DELAYED_WORK(&drvdata.go_cfg_setup, &cfg_setup); + ret = schedule_delayed_work(&drvdata.go_cfg_setup, msecs_to_jiffies(2)); + if (!ret) { + dev_err(&hdev->dev, + "Failed to schedule startup delayed work\n"); + return -ENODEV; + } + return 0; +} + +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); + hid_hw_stop(hdev); + hid_set_drvdata(hdev, NULL); +} + +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); + if (ret) { + hid_err(hdev, "Parse failed\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "Failed to start HID device\n"); + return ret; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "Failed to open HID device\n"); + hid_hw_stop(hdev); + return ret; + } + + ep = get_endpoint_address(hdev); + if (ep != GO_GP_INTF_IN) { + dev_dbg(&hdev->dev, "Started interface %x as generic HID device\n", ep); + return 0; + } + + ret = hid_go_cfg_probe(hdev, id); + if (ret) + dev_err_probe(&hdev->dev, ret, "Failed to start configuration interface\n"); + + dev_dbg(&hdev->dev, "Started Legion Go HID Device: %x\n", ep); + + return ret; +} + +static void hid_go_remove(struct hid_device *hdev) +{ + int ep = get_endpoint_address(hdev); + + if (ep <= 0) + return; + + switch (ep) { + case GO_GP_INTF_IN: + hid_go_cfg_remove(hdev); + break; + default: + hid_hw_close(hdev); + hid_hw_stop(hdev); + break; + } +} + +static const struct hid_device_id hid_go_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_XINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_DINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_FPS) }, + {} +}; +MODULE_DEVICE_TABLE(hid, hid_go_devices); + +static struct hid_driver hid_lenovo_go = { + .name = "hid-lenovo-go", + .id_table = hid_go_devices, + .probe = hid_go_probe, + .remove = hid_go_remove, + .raw_event = hid_go_raw_event, +}; +module_hid_driver(hid_lenovo_go); + +MODULE_AUTHOR("Derek J. Clark"); +MODULE_DESCRIPTION("HID Driver for Lenovo Legion Go Series Gamepads."); +MODULE_LICENSE("GPL"); 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-lg.c b/drivers/hid/hid-lg.c index 9a2cfa018bd3..197c2f3b077f 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -768,7 +768,7 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id) return -ENODEV; } - drv_data = kzalloc(sizeof(struct lg_drv_data), GFP_KERNEL); + drv_data = kzalloc_obj(struct lg_drv_data); if (!drv_data) { hid_err(hdev, "Insufficient memory, cannot allocate driver data\n"); return -ENOMEM; diff --git a/drivers/hid/hid-lg2ff.c b/drivers/hid/hid-lg2ff.c index 73d07e35f12a..c3e85b079d80 100644 --- a/drivers/hid/hid-lg2ff.c +++ b/drivers/hid/hid-lg2ff.c @@ -66,7 +66,7 @@ int lg2ff_init(struct hid_device *hid) if (!report) return -ENODEV; - lg2ff = kmalloc(sizeof(struct lg2ff_device), GFP_KERNEL); + lg2ff = kmalloc_obj(struct lg2ff_device); if (!lg2ff) return -ENOMEM; diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index 32b711723f2a..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; @@ -1288,7 +1296,7 @@ int lg4ff_init(struct hid_device *hid) hid_err(hid, "Cannot add device, private driver data not allocated\n"); return -1; } - entry = kzalloc(sizeof(*entry), GFP_KERNEL); + entry = kzalloc_obj(*entry); if (!entry) return -ENOMEM; spin_lock_init(&entry->report_lock); diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index c41fded63f4b..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 { @@ -733,7 +734,7 @@ static struct dj_receiver_dev *dj_get_receiver_dev(struct hid_device *hdev, djrcv_dev = dj_find_receiver_dev(hdev, type); if (!djrcv_dev) { - djrcv_dev = kzalloc(sizeof(*djrcv_dev), GFP_KERNEL); + djrcv_dev = kzalloc_obj(*djrcv_dev); if (!djrcv_dev) goto out; @@ -851,7 +852,7 @@ static void logi_dj_recv_add_djhid_device(struct dj_receiver_dev *djrcv_dev, snprintf(tmpstr, sizeof(tmpstr), ":%d", device_index); strlcat(dj_hiddev->phys, tmpstr, sizeof(dj_hiddev->phys)); - dj_dev = kzalloc(sizeof(struct dj_device), GFP_KERNEL); + dj_dev = kzalloc_obj(struct dj_device); if (!dj_dev) { hid_err(djrcv_hdev, "%s: failed allocating dj_dev\n", __func__); @@ -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) */ @@ -1332,7 +1337,7 @@ static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev) goto out; } - dj_report = kzalloc(sizeof(struct dj_report), GFP_KERNEL); + dj_report = kzalloc_obj(struct dj_report); if (!dj_report) return -ENOMEM; dj_report->report_id = REPORT_ID_DJ_SHORT; @@ -1356,7 +1361,7 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, u8 *buf; int retval = 0; - dj_report = kzalloc(sizeof(struct dj_report), GFP_KERNEL); + dj_report = kzalloc_obj(struct dj_report); if (!dj_report) return -ENOMEM; @@ -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. */ @@ -1858,7 +1881,8 @@ static int logi_dj_raw_event(struct hid_device *hdev, static int logi_dj_probe(struct hid_device *hdev, const struct hid_device_id *id) { - struct hid_report_enum *rep_enum; + struct hid_report_enum *input_report_enum; + struct hid_report_enum *output_report_enum; struct hid_report *rep; struct dj_receiver_dev *djrcv_dev; struct usb_interface *intf; @@ -1883,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; @@ -1896,17 +1923,42 @@ 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); + } } } - rep_enum = &hdev->report_enum[HID_INPUT_REPORT]; + 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) { + 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; + } + + input_report_enum = &hdev->report_enum[HID_INPUT_REPORT]; /* no input reports, bail out */ - if (list_empty(&rep_enum->report_list)) + if (list_empty(&input_report_enum->report_list)) return -ENODEV; /* @@ -1914,7 +1966,7 @@ static int logi_dj_probe(struct hid_device *hdev, * Note: we should theoretically check for HID++ and DJ * collections, but this will do. */ - list_for_each_entry(rep, &rep_enum->report_list, list) { + list_for_each_entry(rep, &input_report_enum->report_list, list) { if (rep->application == 0xff000001) has_hidpp = true; } @@ -1927,7 +1979,7 @@ static int logi_dj_probe(struct hid_device *hdev, return -ENODEV; /* get the current application attached to the node */ - rep = list_first_entry(&rep_enum->report_list, struct hid_report, list); + rep = list_first_entry(&input_report_enum->report_list, struct hid_report, list); djrcv_dev = dj_get_receiver_dev(hdev, id->driver_data, rep->application, has_hidpp); if (!djrcv_dev) { @@ -1935,7 +1987,7 @@ static int logi_dj_probe(struct hid_device *hdev, return -ENOMEM; } - if (!rep_enum->numbered) + if (!input_report_enum->numbered) djrcv_dev->unnumbered_application = rep->application; /* Starts the usb device and connects to upper interfaces hiddev and @@ -2087,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 65bfad405ac5..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; }; @@ -306,21 +313,22 @@ static int __do_hidpp_send_message_sync(struct hidpp_device *hidpp, if (ret) { dbg_hid("__hidpp_send_report returned err: %d\n", ret); memset(response, 0, sizeof(struct hidpp_report)); - return ret; + goto out; } if (!wait_event_timeout(hidpp->wait, hidpp->answer_available, 5*HZ)) { dbg_hid("%s:timeout waiting for response\n", __func__); memset(response, 0, sizeof(struct hidpp_report)); - return -ETIMEDOUT; + ret = -ETIMEDOUT; + goto out; } if (response->report_id == REPORT_ID_HIDPP_SHORT && response->rap.sub_id == HIDPP_ERROR) { ret = response->rap.params[1]; dbg_hid("%s:got hidpp error %02X\n", __func__, ret); - return ret; + goto out; } if ((response->report_id == REPORT_ID_HIDPP_LONG || @@ -328,10 +336,14 @@ static int __do_hidpp_send_message_sync(struct hidpp_device *hidpp, response->fap.feature_index == HIDPP20_ERROR) { ret = response->fap.params[1]; dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret); - return ret; + goto out; } - return 0; + ret = 0; + +out: + hidpp->send_receive_buf = NULL; + return ret; } /* @@ -390,7 +402,7 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, return -EINVAL; } - message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL); + message = kzalloc_obj(struct hidpp_report); if (!message) return -ENOMEM; @@ -443,7 +455,7 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, if (param_count > max_count) return -EINVAL; - message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL); + message = kzalloc_obj(struct hidpp_report); if (!message) return -ENOMEM; message->report_id = report_id; @@ -976,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; @@ -2502,12 +2515,15 @@ static void hidpp_ff_work_handler(struct work_struct *w) } break; case HIDPP_FF_DESTROY_EFFECT: - if (wd->effect_id >= 0) - /* regular effect destroyed */ - data->effect_ids[wd->params[0]-1] = -1; - else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER) - /* autocenter spring destroyed */ - data->slot_autocenter = 0; + slot = wd->params[0]; + if (slot > 0 && slot <= data->num_effects) { + if (wd->effect_id >= 0) + /* regular effect destroyed */ + data->effect_ids[slot-1] = -1; + else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER) + /* autocenter spring destroyed */ + data->slot_autocenter = 0; + } break; case HIDPP_FF_SET_GLOBAL_GAINS: data->gain = (wd->params[0] << 8) + wd->params[1]; @@ -2527,7 +2543,7 @@ out: static int hidpp_ff_queue_work(struct hidpp_ff_private_data *data, int effect_id, u8 command, u8 *params, u8 size) { - struct hidpp_ff_work_data *wd = kzalloc(sizeof(*wd), GFP_KERNEL); + struct hidpp_ff_work_data *wd = kzalloc_obj(*wd); int s; if (!wd) @@ -2851,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; - data->effect_ids = kcalloc(num_slots, sizeof(int), GFP_KERNEL); + 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; @@ -2892,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; } /* ************************************************************************** */ @@ -3591,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) { @@ -3665,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; } @@ -3702,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; @@ -3712,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) { @@ -3721,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; } @@ -3743,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 */ /* -------------------------------------------------------------------------- */ @@ -3821,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; @@ -3840,8 +4118,7 @@ static int hidpp_input_configured(struct hid_device *hdev, static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, int size) { - struct hidpp_report *question = hidpp->send_receive_buf; - struct hidpp_report *answer = hidpp->send_receive_buf; + struct hidpp_report *question, *answer; struct hidpp_report *report = (struct hidpp_report *)data; int ret; int last_online; @@ -3851,6 +4128,12 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, * previously sent command. */ if (unlikely(mutex_is_locked(&hidpp->send_mutex))) { + question = hidpp->send_receive_buf; + answer = hidpp->send_receive_buf; + + if (!question) + return 0; + /* * Check for a correct hidpp20 answer or the corresponding * error @@ -3928,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; } @@ -4112,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; @@ -4215,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) @@ -4282,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; } @@ -4387,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); @@ -4460,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; @@ -4480,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. @@ -4597,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 */ @@ -4609,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 */ @@ -4657,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 */ @@ -4672,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 9eadf3252d0d..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,24 +843,33 @@ 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 struct hid_report_enum *report_enum; struct hid_report *report; + struct hid_battery *bat; - if (!hdev->battery || + 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[hdev->battery_report_type]; - report = report_enum->report_id_hash[hdev->battery_report_id]; + report_enum = &hdev->report_enum[bat->report_type]; + report = report_enum->report_id_hash[bat->report_id]; if (!report || report->maxfield < 1) return -1; - if (hdev->battery_capacity == hdev->battery_max) + if (bat->capacity == bat->max) return -1; hid_hw_request(hdev, report, HID_REQ_GET_REPORT); @@ -882,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); @@ -953,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) || @@ -977,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) { @@ -1040,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 ef3b5c77c38e..d52ce3531ab7 100644 --- a/drivers/hid/hid-mcp2221.c +++ b/drivers/hid/hid-mcp2221.c @@ -19,8 +19,15 @@ #include <linux/gpio/driver.h> #include <linux/iio/iio.h> #include <linux/minmax.h> +#include <linux/moduleparam.h> #include "hid-ids.h" +static bool gpio_mode_enforce; + +module_param(gpio_mode_enforce, bool, 0644); +MODULE_PARM_DESC(gpio_mode_enforce, + "Enforce GPIO mode for GP0 thru GP3 (default: false, will be used for IIO)"); + /* Commands codes in a raw output report */ enum { MCP2221_I2C_WR_DATA = 0x90, @@ -121,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; @@ -323,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; @@ -355,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; @@ -365,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; } @@ -536,10 +550,10 @@ static int mcp_smbus_xfer(struct i2c_adapter *adapter, u16 addr, if (ret) goto exit; - mcp->rxbuf_idx = 0; - mcp->rxbuf = data->block; - mcp->txbuf[0] = MCP2221_I2C_GET_DATA; - ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1); + ret = mcp_i2c_smbus_read(mcp, NULL, + MCP2221_I2C_RD_RPT_START, + addr, data->block[0] + 1, + data->block); if (ret) goto exit; } else { @@ -555,14 +569,14 @@ static int mcp_smbus_xfer(struct i2c_adapter *adapter, u16 addr, case I2C_SMBUS_I2C_BLOCK_DATA: if (read_write == I2C_SMBUS_READ) { ret = mcp_smbus_write(mcp, addr, command, NULL, - 0, MCP2221_I2C_WR_NO_STOP, 1); + 0, MCP2221_I2C_WR_NO_STOP, 0); if (ret) goto exit; - mcp->rxbuf_idx = 0; - mcp->rxbuf = data->block; - mcp->txbuf[0] = MCP2221_I2C_GET_DATA; - ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1); + ret = mcp_i2c_smbus_read(mcp, NULL, + MCP2221_I2C_RD_RPT_START, + addr, data->block[0], + &data->block[1]); if (ret) goto exit; } else { @@ -650,7 +664,7 @@ static int mcp2221_check_gpio_pinfunc(struct mcp2221 *mcp) int needgpiofix = 0; int ret; - if (IS_ENABLED(CONFIG_IIO)) + if (IS_ENABLED(CONFIG_IIO) && !gpio_mode_enforce) return 0; ret = mcp_gpio_read_sram(mcp); @@ -851,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: @@ -912,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]; @@ -1035,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); } @@ -1045,7 +1072,8 @@ static void mcp2221_remove(struct hid_device *hdev) #if IS_REACHABLE(CONFIG_IIO) struct mcp2221 *mcp = hid_get_drvdata(hdev); - cancel_delayed_work_sync(&mcp->init_work); + if (!gpio_mode_enforce) + cancel_delayed_work_sync(&mcp->init_work); #endif } @@ -1319,8 +1347,10 @@ static int mcp2221_probe(struct hid_device *hdev, #endif #if IS_REACHABLE(CONFIG_IIO) - INIT_DELAYED_WORK(&mcp->init_work, mcp_init_work); - schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100)); + if (!gpio_mode_enforce) { + INIT_DELAYED_WORK(&mcp->init_work, mcp_init_work); + schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100)); + } #endif return 0; diff --git a/drivers/hid/hid-megaworld.c b/drivers/hid/hid-megaworld.c index 0476d7d16e7f..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); @@ -57,20 +52,11 @@ static int mwctrl_init(struct hid_device *hid) return -ENODEV; } - mwctrl = kzalloc(sizeof(struct mwctrl_device), GFP_KERNEL); + mwctrl = kzalloc_obj(struct mwctrl_device); 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 49a4052a1496..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(sizeof(struct mf_device), GFP_KERNEL); - 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-ntrig.c b/drivers/hid/hid-ntrig.c index a7f10c45f62b..2b31756b549e 100644 --- a/drivers/hid/hid-ntrig.c +++ b/drivers/hid/hid-ntrig.c @@ -900,7 +900,7 @@ static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id) hdev->quirks |= HID_QUIRK_MULTI_INPUT | HID_QUIRK_NO_INIT_REPORTS; - nd = kmalloc(sizeof(struct ntrig_data), GFP_KERNEL); + nd = kmalloc_obj(struct ntrig_data); if (!nd) { hid_err(hdev, "cannot allocate N-Trig data\n"); return -ENOMEM; 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_cir.c b/drivers/hid/hid-picolcd_cir.c index d6faa0e00f95..6d4c636e1c9f 100644 --- a/drivers/hid/hid-picolcd_cir.c +++ b/drivers/hid/hid-picolcd_cir.c @@ -134,5 +134,6 @@ void picolcd_exit_cir(struct picolcd_data *data) data->rc_dev = NULL; rc_unregister_device(rdev); + rc_free_device(rdev); } diff --git a/drivers/hid/hid-picolcd_core.c b/drivers/hid/hid-picolcd_core.c index 6a88e6bc70f3..d73e97c8b853 100644 --- a/drivers/hid/hid-picolcd_core.c +++ b/drivers/hid/hid-picolcd_core.c @@ -72,13 +72,14 @@ 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; if (data->status & PICOLCD_FAILED) return NULL; - work = kzalloc(sizeof(*work), GFP_KERNEL); + work = kzalloc_obj(*work); if (!work) return NULL; @@ -528,7 +529,7 @@ static int picolcd_probe(struct hid_device *hdev, * Let's allocate the picolcd data structure, set some reasonable * defaults, and associate it with the device */ - data = kzalloc(sizeof(struct picolcd_data), GFP_KERNEL); + data = kzalloc_obj(struct picolcd_data); if (data == NULL) { hid_err(hdev, "can't allocate space for Minibox PicoLCD device data\n"); return -ENOMEM; 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 dc11d5322fc0..cae56a6c941b 100644 --- a/drivers/hid/hid-pl.c +++ b/drivers/hid/hid-pl.c @@ -24,10 +24,6 @@ */ -/* #define DEBUG */ - -#define debug(format, arg...) pr_debug("hid-plff: " format "\n" , ## arg) - #include <linux/input.h> #include <linux/slab.h> #include <linux/module.h> @@ -53,28 +49,26 @@ static int hid_plff_play(struct input_dev *dev, void *data, left = effect->u.rumble.strong_magnitude; right = effect->u.rumble.weak_magnitude; - debug("called with 0x%04x 0x%04x", left, right); + hid_dbg(dev, "called with 0x%04x 0x%04x", left, right); left = left * plff->maxval / 0xffff; right = right * plff->maxval / 0xffff; *plff->strong = left; *plff->weak = right; - debug("running with 0x%02x 0x%02x", left, right); + hid_dbg(dev, "running with 0x%02x 0x%02x", left, right); hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT); 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; @@ -87,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]; - debug("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; - debug("detected 4-field device"); - } else { - hid_err(hid, "not enough fields or values\n"); - return -ENODEV; - } - - plff = kzalloc(sizeof(struct plff_device), GFP_KERNEL); - 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; } @@ -185,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 */ @@ -221,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 2ec6d4445e84..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); /* @@ -1658,7 +1679,7 @@ static int dualsense_reset_leds(struct dualsense *ds) struct dualsense_output_report report; struct dualsense_output_report_bt *buf; - buf = kzalloc(sizeof(*buf), GFP_KERNEL); + buf = kzalloc_obj(*buf); if (!buf) return -ENOMEM; @@ -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); @@ -2377,8 +2408,15 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * struct dualshock4_input_report_usb *usb = (struct dualshock4_input_report_usb *)data; + if (usb->num_touch_reports > ARRAY_SIZE(usb->touch_reports)) { + hid_err(hdev, "DualShock4 USB input report has invalid num_touch_reports=%d\n", + usb->num_touch_reports); + return -EINVAL; + } + 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) { @@ -2391,8 +2429,15 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * return -EILSEQ; } + if (bt->num_touch_reports > ARRAY_SIZE(bt->touch_reports)) { + hid_err(hdev, "DualShock4 BT input report has invalid num_touch_reports=%d\n", + bt->num_touch_reports); + return -EINVAL; + } + 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-prodikeys.c b/drivers/hid/hid-prodikeys.c index 6e413df38358..fba01e4fcab1 100644 --- a/drivers/hid/hid-prodikeys.c +++ b/drivers/hid/hid-prodikeys.c @@ -797,7 +797,7 @@ static int pk_probe(struct hid_device *hdev, const struct hid_device_id *id) intf = to_usb_interface(hdev->dev.parent); ifnum = intf->cur_altsetting->desc.bInterfaceNumber; - pm = kzalloc(sizeof(*pm), GFP_KERNEL); + pm = kzalloc_obj(*pm); if (pm == NULL) { hid_err(hdev, "can't alloc descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 3217e436c052..96a36c5ba04f 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -25,6 +25,7 @@ */ static const struct hid_device_id hid_quirks[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_8BITDO, USB_DEVICE_ID_8BITDO_PRO_3), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD), HID_QUIRK_BADPAD }, { HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR), HID_QUIRK_BADPAD }, { HID_USB_DEVICE(USB_VENDOR_ID_ADATA_XPG, USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE), HID_QUIRK_ALWAYS_POLL }, @@ -44,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 }, @@ -111,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 }, @@ -133,6 +137,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6019), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_602E), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6093), HID_QUIRK_ALWAYS_POLL }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C007), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C077), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_KEYBOARD_G710_PLUS), HID_QUIRK_NOGET }, @@ -181,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 }, @@ -233,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) }, @@ -419,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) }, @@ -690,30 +698,77 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_JOY_BOX_5_PRO) }, #endif #if IS_ENABLED(CONFIG_HID_SONY) + { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG_DONGLE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS5_GIBSON_SG) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS5_GIBSON_SG_DONGLE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB1_DRUMS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB1_GUITAR) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_KEYBOARD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_DRUMS_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_KEYBOARD_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_MUSTANG_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_SQUIER_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MUSTANG_GUITAR) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_PS3) }, - { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_PS3_BDREMOTE) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_PS4_STRATOCASTER) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_JAGUAR) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS5_RIFFMASTER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_GUITAR_DONGLE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SINO_LITE, USB_DEVICE_ID_SINO_LITE_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_NSG_MR5U_REMOTE) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_NSG_MR7U_REMOTE) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_PS3_BDREMOTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_BUZZ_CONTROLLER) }, - { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER) }, - { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_MOTION_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_MOTION_CONTROLLER) }, - { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_MOTION_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_BDREMOTE) }, - { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, - { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE) }, - { HID_USB_DEVICE(USB_VENDOR_ID_SINO_LITE, USB_DEVICE_ID_SINO_LITE_CONTROLLER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_DJH_TURNTABLE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_DRUMS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_GUITAR) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_KEYBOARD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_DRUMS_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_KEYBOARD_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_MUSTANG_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_SQUIER_MODE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MUSTANG_GUITAR) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB_DRUMS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB_GUITAR) }, #endif #if IS_ENABLED(CONFIG_HID_SPEEDLINK) { HID_USB_DEVICE(USB_VENDOR_ID_X_TENSIONS, USB_DEVICE_ID_SPEEDLINK_VAD_CEZANNE) }, #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) }, @@ -1164,11 +1219,11 @@ static int hid_modify_dquirk(const struct hid_device_id *id, int list_edited = 0; int ret = 0; - hdev = kzalloc(sizeof(*hdev), GFP_KERNEL); + hdev = kzalloc_obj(*hdev); if (!hdev) return -ENOMEM; - q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL); + q_new = kmalloc_obj(struct quirks_list_struct); if (!q_new) { ret = -ENOMEM; goto out; 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-arvo.c b/drivers/hid/hid-roccat-arvo.c index 7b09adfa44a1..0cf2f0008c7f 100644 --- a/drivers/hid/hid-roccat-arvo.c +++ b/drivers/hid/hid-roccat-arvo.c @@ -299,7 +299,7 @@ static int arvo_init_specials(struct hid_device *hdev) return 0; } - arvo = kzalloc(sizeof(*arvo), GFP_KERNEL); + arvo = kzalloc_obj(*arvo); if (!arvo) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-isku.c b/drivers/hid/hid-roccat-isku.c index 339378771ed5..93a49c93ae8c 100644 --- a/drivers/hid/hid-roccat-isku.c +++ b/drivers/hid/hid-roccat-isku.c @@ -279,7 +279,7 @@ static int isku_init_specials(struct hid_device *hdev) return 0; } - isku = kzalloc(sizeof(*isku), GFP_KERNEL); + isku = kzalloc_obj(*isku); if (!isku) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index fabc08efcfd8..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; } @@ -704,7 +706,7 @@ static int kone_init_specials(struct hid_device *hdev) if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { - kone = kzalloc(sizeof(*kone), GFP_KERNEL); + kone = kzalloc_obj(*kone); if (!kone) return -ENOMEM; hid_set_drvdata(hdev, kone); @@ -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-koneplus.c b/drivers/hid/hid-roccat-koneplus.c index 77d45d36421a..f80a60539a96 100644 --- a/drivers/hid/hid-roccat-koneplus.c +++ b/drivers/hid/hid-roccat-koneplus.c @@ -384,7 +384,7 @@ static int koneplus_init_specials(struct hid_device *hdev) if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { - koneplus = kzalloc(sizeof(*koneplus), GFP_KERNEL); + koneplus = kzalloc_obj(*koneplus); if (!koneplus) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-konepure.c b/drivers/hid/hid-roccat-konepure.c index 027bfc55ef9c..7f753dfc2a10 100644 --- a/drivers/hid/hid-roccat-konepure.c +++ b/drivers/hid/hid-roccat-konepure.c @@ -88,7 +88,7 @@ static int konepure_init_specials(struct hid_device *hdev) return 0; } - konepure = kzalloc(sizeof(*konepure), GFP_KERNEL); + konepure = kzalloc_obj(*konepure); if (!konepure) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-kovaplus.c b/drivers/hid/hid-roccat-kovaplus.c index a66f1b4730f3..9ec42c218ef9 100644 --- a/drivers/hid/hid-roccat-kovaplus.c +++ b/drivers/hid/hid-roccat-kovaplus.c @@ -453,7 +453,7 @@ static int kovaplus_init_specials(struct hid_device *hdev) if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { - kovaplus = kzalloc(sizeof(*kovaplus), GFP_KERNEL); + kovaplus = kzalloc_obj(*kovaplus); if (!kovaplus) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-lua.c b/drivers/hid/hid-roccat-lua.c index 45e30549c236..8e35e1a0660b 100644 --- a/drivers/hid/hid-roccat-lua.c +++ b/drivers/hid/hid-roccat-lua.c @@ -119,7 +119,7 @@ static int lua_init_specials(struct hid_device *hdev) struct lua_device *lua; int retval; - lua = kzalloc(sizeof(*lua), GFP_KERNEL); + lua = kzalloc_obj(*lua); if (!lua) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c index de2da6086e0b..0d515995bb9d 100644 --- a/drivers/hid/hid-roccat-pyra.c +++ b/drivers/hid/hid-roccat-pyra.c @@ -403,7 +403,7 @@ static int pyra_init_specials(struct hid_device *hdev) if (intf->cur_altsetting->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) { - pyra = kzalloc(sizeof(*pyra), GFP_KERNEL); + pyra = kzalloc_obj(*pyra); if (!pyra) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-ryos.c b/drivers/hid/hid-roccat-ryos.c index 36911c9da4fe..db83f42457da 100644 --- a/drivers/hid/hid-roccat-ryos.c +++ b/drivers/hid/hid-roccat-ryos.c @@ -96,7 +96,7 @@ static int ryos_init_specials(struct hid_device *hdev) return 0; } - ryos = kzalloc(sizeof(*ryos), GFP_KERNEL); + ryos = kzalloc_obj(*ryos); if (!ryos) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat-savu.c b/drivers/hid/hid-roccat-savu.c index fb2e464c3ada..679136933560 100644 --- a/drivers/hid/hid-roccat-savu.c +++ b/drivers/hid/hid-roccat-savu.c @@ -68,7 +68,7 @@ static int savu_init_specials(struct hid_device *hdev) return 0; } - savu = kzalloc(sizeof(*savu), GFP_KERNEL); + savu = kzalloc_obj(*savu); if (!savu) { hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c index c7f7562e22e5..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) { @@ -152,7 +161,7 @@ static int roccat_open(struct inode *inode, struct file *file) struct roccat_device *device; int error = 0; - reader = kzalloc(sizeof(struct roccat_reader), GFP_KERNEL); + reader = kzalloc_obj(struct roccat_reader); if (!reader) return -ENOMEM; @@ -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); } } @@ -257,6 +266,7 @@ int roccat_report_event(int minor, u8 const *data) if (!new_value) return -ENOMEM; + mutex_lock(&device->readers_lock); mutex_lock(&device->cbuf_lock); report = &device->cbuf[device->cbuf_end]; @@ -279,6 +289,7 @@ int roccat_report_event(int minor, u8 const *data) } mutex_unlock(&device->cbuf_lock); + mutex_unlock(&device->readers_lock); wake_up_interruptible(&device->wait); return 0; @@ -301,7 +312,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report struct roccat_device *device; int temp; - device = kzalloc(sizeof(struct roccat_device), GFP_KERNEL); + device = kzalloc_obj(struct roccat_device); if (!device) return -ENOMEM; @@ -333,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); @@ -345,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); @@ -358,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 761760668f6d..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 * @@ -912,7 +919,7 @@ hid_sensor_custom_get_known(struct hid_sensor_hub_device *hsdev, hid_sensor_custom_known_table; struct hid_sensor_custom_properties *prop; - prop = kmalloc(sizeof(struct hid_sensor_custom_properties), GFP_KERNEL); + prop = kmalloc_obj(struct hid_sensor_custom_properties); if (!prop) return -ENOMEM; @@ -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 0edec902be41..6470a290ebfc 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -139,7 +139,7 @@ int sensor_hub_register_callback(struct hid_sensor_hub_device *hsdev, spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags); return -EINVAL; } - callback = kzalloc(sizeof(*callback), GFP_ATOMIC); + callback = kzalloc_obj(*callback, GFP_ATOMIC); if (!callback) { spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags); return -ENOMEM; @@ -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 d3a777f52a3f..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(sizeof(struct sjoyff_device), GFP_KERNEL); - 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 a89af14e4acc..50f5ad6bbeb4 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * HID driver for Sony / PS2 / PS3 / PS4 BD devices. + * HID driver for Sony / PS2 / PS3 BD / PS4 / PS5 devices. * * Copyright (c) 1999 Andreas Gal * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz> @@ -12,9 +12,10 @@ * Copyright (c) 2014-2016 Frank Praznik <frank.praznik@gmail.com> * Copyright (c) 2018 Todd Kelner * Copyright (c) 2020-2021 Pascal Giard <pascal.giard@etsmtl.ca> - * Copyright (c) 2020 Sanjay Govind <sanjay.govind9@gmail.com> + * Copyright (c) 2020-2026 Sanjay Govind <sanjay.govind9@gmail.com> * Copyright (c) 2021 Daniel Nguyen <daniel.nguyen.1@ens.etsmtl.ca> * Copyright (c) 2026 Rosalie Wanders <rosalie@mailbox.org> + * Copyright (c) 2026 Brenton Simpson <appsforartists@google.com> */ /* @@ -28,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> @@ -59,12 +61,15 @@ #define NSG_MR5U_REMOTE_BT BIT(11) #define NSG_MR7U_REMOTE_BT BIT(12) #define SHANWAN_GAMEPAD BIT(13) -#define GH_GUITAR_CONTROLLER BIT(14) -#define GHL_GUITAR_PS3WIIU BIT(15) -#define GHL_GUITAR_PS4 BIT(16) -#define RB4_GUITAR_PS4_USB BIT(17) -#define RB4_GUITAR_PS4_BT BIT(18) -#define RB4_GUITAR_PS5 BIT(19) +#define INSTRUMENT BIT(14) +#define GH_GUITAR_TILT BIT(15) +#define GHL_GUITAR_PS3WIIU BIT(16) +#define GHL_GUITAR_PS4 BIT(17) +#define RB4_GUITAR_PS4_USB BIT(18) +#define RB4_GUITAR_PS4_BT BIT(19) +#define RB4_GUITAR_PS5 BIT(20) +#define RB3_PRO_INSTRUMENT BIT(21) +#define DJH_TURNTABLE BIT(22) #define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT) #define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT) @@ -72,10 +77,12 @@ NAVIGATION_CONTROLLER_BT) #define SONY_LED_SUPPORT (SIXAXIS_CONTROLLER | BUZZ_CONTROLLER |\ MOTION_CONTROLLER | NAVIGATION_CONTROLLER) -#define SONY_BATTERY_SUPPORT (SIXAXIS_CONTROLLER | MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER) +#define SONY_BATTERY_SUPPORT (SIXAXIS_CONTROLLER | MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER |\ + RB4_GUITAR_PS5) #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 @@ -87,6 +94,10 @@ #define GHL_GUITAR_POKE_INTERVAL 8 /* In seconds */ #define GUITAR_TILT_USAGE 44 +#define TURNTABLE_EFFECTS_KNOB_USAGE 44 +#define TURNTABLE_PLATTER_BUTTONS_USAGE 45 +#define TURNTABLE_CROSS_FADER_USAGE 46 + /* Magic data taken from GHLtarUtility: * https://github.com/ghlre/GHLtarUtility/blob/master/PS3Guitar.cs * Note: The Wii U and PS3 dongles happen to share the same! @@ -427,20 +438,25 @@ static const unsigned int rb4_absmap[] = { [0x31] = ABS_Y, }; -static const unsigned int rb4_keymap[] = { - [0x1] = BTN_WEST, /* Square */ - [0x2] = BTN_SOUTH, /* Cross */ - [0x3] = BTN_EAST, /* Circle */ - [0x4] = BTN_NORTH, /* Triangle */ - [0x5] = BTN_TL, /* L1 */ - [0x6] = BTN_TR, /* R1 */ - [0x7] = BTN_TL2, /* L2 */ - [0x8] = BTN_TR2, /* R2 */ - [0x9] = BTN_SELECT, /* Share */ - [0xa] = BTN_START, /* Options */ - [0xb] = BTN_THUMBL, /* L3 */ - [0xc] = BTN_THUMBR, /* R3 */ - [0xd] = BTN_MODE, /* PS */ +static const unsigned int ps3_turntable_absmap[] = { + [0x32] = ABS_X, + [0x35] = ABS_Y, +}; + +static const unsigned int instrument_keymap[] = { + [0x1] = BTN_WEST, + [0x2] = BTN_SOUTH, + [0x3] = BTN_EAST, + [0x4] = BTN_NORTH, + [0x5] = BTN_TL, + [0x6] = BTN_TR, + [0x7] = BTN_TL2, + [0x8] = BTN_TR2, + [0x9] = BTN_SELECT, + [0xa] = BTN_START, + [0xb] = BTN_THUMBL, + [0xc] = BTN_THUMBR, + [0xd] = BTN_MODE, }; static enum power_supply_property sony_battery_props[] = { @@ -457,6 +473,7 @@ struct sixaxis_led { u8 duty_off; /* % of duty_length the led is off (0xff means 100%) */ u8 duty_on; /* % of duty_length the led is on (0xff mean 100%) */ } __packed; +static_assert(sizeof(struct sixaxis_led) == 5); struct sixaxis_rumble { u8 padding; @@ -465,6 +482,7 @@ struct sixaxis_rumble { u8 left_duration; /* Left motor duration (0xff means forever) */ u8 left_motor_force; /* left (large) motor, supports force values from 0 to 255 */ } __packed; +static_assert(sizeof(struct sixaxis_rumble) == 5); struct sixaxis_output_report { u8 report_id; @@ -474,22 +492,26 @@ struct sixaxis_output_report { struct sixaxis_led led[4]; /* LEDx at (4 - x) */ struct sixaxis_led _reserved; /* LED5, not actually soldered */ } __packed; +static_assert(sizeof(struct sixaxis_output_report) == 36); union sixaxis_output_report_01 { struct sixaxis_output_report data; u8 buf[36]; }; +static_assert(sizeof(union sixaxis_output_report_01) == 36); struct motion_output_report_02 { u8 type, zero; u8 r, g, b; u8 zero2; u8 rumble; -}; +} __packed; +static_assert(sizeof(struct motion_output_report_02) == 7); #define SIXAXIS_REPORT_0xF2_SIZE 17 #define SIXAXIS_REPORT_0xF5_SIZE 8 #define MOTION_REPORT_0x02_SIZE 49 +#define PRO_INSTRUMENT_0x00_SIZE 8 #define SENSOR_SUFFIX " Motion Sensors" #define TOUCHPAD_SUFFIX " Touchpad" @@ -501,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; @@ -514,8 +532,9 @@ 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 *); + void (*send_output_report)(struct sony_sc *sc); struct power_supply *battery; struct power_supply_desc battery_desc; int device_id; @@ -539,23 +558,18 @@ struct sony_sc { /* GH Live */ struct urb *ghl_urb; struct timer_list ghl_poke_timer; + + /* Rock Band 3 Pro Instruments */ + unsigned long rb3_pro_poke_jiffies; }; 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) @@ -589,11 +603,11 @@ static int ghl_init_urb(struct sony_sc *sc, struct usb_device *usbdev, pipe = usb_sndctrlpipe(usbdev, 0); cr = devm_kzalloc(&sc->hdev->dev, sizeof(*cr), GFP_ATOMIC); - if (cr == NULL) + if (!cr) return -ENOMEM; databuf = devm_kzalloc(&sc->hdev->dev, poke_size, GFP_ATOMIC); - if (databuf == NULL) + if (!databuf) return -ENOMEM; cr->bRequestType = @@ -610,35 +624,108 @@ static int ghl_init_urb(struct sony_sc *sc, struct usb_device *usbdev, return 0; } -static int gh_guitar_mapping(struct hid_device *hdev, struct hid_input *hi, + + +/* + * Sending HID_REQ_SET_REPORT enables the full report. Without this + * Rock Band 3 Pro instruments only report navigation events + */ +static int rb3_pro_instrument_enable_full_report(struct sony_sc *sc) +{ + struct hid_device *hdev = sc->hdev; + static const u8 report[] = { 0x00, 0xE9, 0x00, 0x89, 0x1B, + 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x89, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xE9, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00 }; + u8 *buf; + int ret; + + buf = kmemdup(report, sizeof(report), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(report), + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + + kfree(buf); + + return ret; +} + +static int djh_turntable_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR) { unsigned int abs = usage->hid & HID_USAGE; - if (abs == GUITAR_TILT_USAGE) { + if (abs == TURNTABLE_CROSS_FADER_USAGE) { + hid_map_usage_clear(hi, usage, bit, max, EV_ABS, ABS_RX); + return 1; + } else if (abs == TURNTABLE_EFFECTS_KNOB_USAGE) { hid_map_usage_clear(hi, usage, bit, max, EV_ABS, ABS_RY); return 1; + } else if (abs == TURNTABLE_PLATTER_BUTTONS_USAGE) { + hid_map_usage_clear(hi, usage, bit, max, EV_ABS, ABS_RZ); + return 1; } + } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) { + unsigned int abs = usage->hid & HID_USAGE; + + if (abs >= ARRAY_SIZE(ps3_turntable_absmap)) + return -1; + + abs = ps3_turntable_absmap[abs]; + + hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs); + return 1; } return 0; } -static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi, +static int instrument_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) { unsigned int key = usage->hid & HID_USAGE; - if (key >= ARRAY_SIZE(rb4_keymap)) + if (key >= ARRAY_SIZE(instrument_keymap)) return 0; - key = rb4_keymap[key]; + key = instrument_keymap[key]; hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key); return 1; - } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) { + } + + return 0; +} + +static int gh_guitar_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR) { + unsigned int abs = usage->hid & HID_USAGE; + + if (abs == GUITAR_TILT_USAGE) { + hid_map_usage_clear(hi, usage, bit, max, EV_ABS, ABS_RY); + return 1; + } + } + return 0; +} + +static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) { unsigned int abs = usage->hid & HID_USAGE; /* Let the HID parser deal with the HAT. */ @@ -850,14 +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. @@ -870,15 +982,15 @@ static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size) battery_capacity = 100; battery_status = (rd[offset] & 0x01) ? POWER_SUPPLY_STATUS_FULL : POWER_SUPPLY_STATUS_CHARGING; } else { - u8 index = rd[offset] <= 5 ? rd[offset] : 5; + index = rd[offset] <= 5 ? rd[offset] : 5; battery_capacity = sixaxis_battery_capacity[index]; 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; @@ -896,19 +1008,24 @@ 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. * For the first byte, bit 0 is set when touchpad button is pressed. * Bit 2 is set when a touch is active and the drag (Fn) key is pressed. - * This drag key is mapped to BTN_LEFT. It is operational only when a + * This drag key is mapped to BTN_LEFT. It is operational only when a * touch point is active. * Bit 4 is set when only the first touch point is active. * Bit 6 is set when only the second touch point is active. @@ -970,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 @@ -987,10 +1127,19 @@ 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; + + if (unlikely(size != 64 || rd[0] != 0x01)) + return 0; + /* * Rock Band 4 PS5 guitars have whammy and * tilt functionality, they're located at @@ -1003,58 +1152,56 @@ static void rb4_ps5_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size) input_report_abs(sc->input_dev, ABS_Z, rd[41]); input_report_abs(sc->input_dev, ABS_RZ, rd[42]); + /* + * Rock Band 4 PS5 guitars also report the + * battery status and level at byte 30. + */ + charging_status = (rd[30] >> 4) & 0x0F; + battery_data = rd[30] & 0x0F; + + switch (charging_status) { + case 0x0: + battery_capacity = min(battery_data * 10 + 5, 100); + battery_status = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case 0x1: + battery_capacity = min(battery_data * 10 + 5, 100); + battery_status = POWER_SUPPLY_STATUS_CHARGING; + break; + case 0x2: + battery_capacity = 100; + battery_status = POWER_SUPPLY_STATUS_FULL; + break; + default: + battery_capacity = 0; + battery_status = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + + 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; + 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; @@ -1065,6 +1212,7 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi, unsigned long **bit, int *max) { struct sony_sc *sc = hid_get_drvdata(hdev); + int ret; if (sc->quirks & BUZZ_CONTROLLER) { unsigned int key = usage->hid & HID_USAGE; @@ -1098,10 +1246,20 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi, if (sc->quirks & SIXAXIS_CONTROLLER) return sixaxis_mapping(hdev, hi, field, usage, bit, max); - if (sc->quirks & GH_GUITAR_CONTROLLER) + /* INSTRUMENT quirk is used as a base mapping for instruments */ + if (sc->quirks & INSTRUMENT) { + ret = instrument_mapping(hdev, hi, field, usage, bit, max); + if (ret != 0) + return ret; + } + + if (sc->quirks & GH_GUITAR_TILT) return gh_guitar_mapping(hdev, hi, field, usage, bit, max); - if (sc->quirks & (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT)) + if (sc->quirks & DJH_TURNTABLE) + return djh_turntable_mapping(hdev, hi, field, usage, bit, max); + + if (sc->quirks & RB4_GUITAR_PS4) return rb4_guitar_mapping(hdev, hi, field, usage, bit, max); if (sc->quirks & RB4_GUITAR_PS5) @@ -1114,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); @@ -1137,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); @@ -1153,19 +1307,18 @@ static int sony_register_touchpad(struct sony_sc *sc, int touch_count, input_set_abs_params(sc->touchpad, ABS_MT_POSITION_Y, 0, h, 0, 0); if (touch_major > 0) { - input_set_abs_params(sc->touchpad, ABS_MT_TOUCH_MAJOR, + input_set_abs_params(sc->touchpad, ABS_MT_TOUCH_MAJOR, 0, touch_major, 0, 0); if (touch_minor > 0) - input_set_abs_params(sc->touchpad, ABS_MT_TOUCH_MINOR, + input_set_abs_params(sc->touchpad, ABS_MT_TOUCH_MINOR, 0, touch_minor, 0, 0); if (orientation > 0) - input_set_abs_params(sc->touchpad, ABS_MT_ORIENTATION, + input_set_abs_params(sc->touchpad, ABS_MT_ORIENTATION, 0, orientation, 0, 0); } - if (sc->quirks & NSG_MRXU_REMOTE) { + if (sc->quirks & NSG_MRXU_REMOTE) __set_bit(EV_REL, sc->touchpad->evbit); - } ret = input_mt_init_slots(sc->touchpad, touch_count, INPUT_MT_POINTER); if (ret < 0) @@ -1180,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); @@ -1200,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 @@ -1320,7 +1469,7 @@ static void sixaxis_set_leds_from_id(struct sony_sc *sc) int id = sc->device_id; - BUILD_BUG_ON(MAX_LEDS < ARRAY_SIZE(sixaxis_leds[0])); + BUILD_BUG_ON(ARRAY_SIZE(sixaxis_leds[0]) > MAX_LEDS); if (id < 0) return; @@ -1338,7 +1487,7 @@ static void buzz_set_leds(struct sony_sc *sc) struct hid_report, list); s32 *value = report->field[0]->value; - BUILD_BUG_ON(MAX_LEDS < 4); + BUILD_BUG_ON(4 > MAX_LEDS); value[0] = 0x00; value[1] = sc->led_state[0] ? 0xff : 0x00; @@ -1353,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); } @@ -1464,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; @@ -1485,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; @@ -1535,15 +1681,12 @@ static int sony_leds_init(struct sony_sc *sc) name_sz = strlen(dev_name(&hdev->dev)) + strlen(color_name_str[n]) + 2; led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev) + name_sz, GFP_KERNEL); - if (!led) { - hid_err(hdev, "Couldn't allocate memory for LED %d\n", n); + if (!led) return -ENOMEM; - } name = (void *)(&led[1]); if (use_color_names) - snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), - color_name_str[n]); + snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), color_name_str[n]); else snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), n + 1); led->name = name; @@ -1698,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 @@ -1731,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: @@ -1821,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, @@ -1838,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) @@ -1972,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 *)) { @@ -1985,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) { @@ -2002,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"); @@ -2045,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) { /* @@ -2059,7 +2200,23 @@ 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) { + /* + * Rock Band 3 PS3 Pro Instruments also do not handle HID Output + * Reports on the interrupt EP like they should, so we need to force + * HID output reports to use HID_REQ_SET_REPORT on the Control EP. + * + * There is also another issue about HID Output Reports via USB, + * these instruments do not want the report_id as part of the data + * packet, so we have to discard buf[0] when sending the actual + * control message, even for numbered reports. + */ + 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 @@ -2084,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) { /* @@ -2105,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) { /* @@ -2120,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) { @@ -2149,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; } @@ -2176,12 +2339,12 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) quirks |= SHANWAN_GAMEPAD; sc = devm_kzalloc(&hdev->dev, sizeof(*sc), GFP_KERNEL); - if (sc == NULL) { - hid_err(hdev, "can't alloc sony descriptor\n"); + if (!sc) 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); @@ -2210,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; } @@ -2227,6 +2391,9 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) goto err; } + if (sc->quirks & RB3_PRO_INSTRUMENT) + sc->rb3_pro_poke_jiffies = 0; + if (sc->quirks & (GHL_GUITAR_PS3WIIU | GHL_GUITAR_PS4)) { if (!hid_is_usb(hdev)) { ret = -EINVAL; @@ -2261,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; } @@ -2271,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); } @@ -2290,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); } @@ -2364,35 +2526,82 @@ static const struct hid_device_id sony_devices[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_NSG_MR7U_REMOTE), .driver_data = NSG_MR7U_REMOTE_BT }, /* Guitar Hero Live PS3 and Wii U guitar dongles */ - { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE_DONGLE), - .driver_data = GHL_GUITAR_PS3WIIU | GH_GUITAR_CONTROLLER }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE), + .driver_data = GHL_GUITAR_PS3WIIU | GH_GUITAR_TILT | INSTRUMENT }, /* Guitar Hero PC Guitar Dongle */ { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_GUITAR_DONGLE), - .driver_data = GH_GUITAR_CONTROLLER }, - /* Guitar Hero PS3 World Tour Guitar Dongle */ - { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE), - .driver_data = GH_GUITAR_CONTROLLER }, + .driver_data = GH_GUITAR_TILT | INSTRUMENT }, + /* Guitar Hero PS3 Guitar Dongle */ + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_GUITAR), + .driver_data = GH_GUITAR_TILT | INSTRUMENT }, + /* Guitar Hero PS3 Drum Dongle */ + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_DRUMS), + .driver_data = INSTRUMENT }, + /* DJ Hero PS3 Guitar Dongle */ + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_DJH_TURNTABLE), + .driver_data = DJH_TURNTABLE | INSTRUMENT }, /* Guitar Hero Live PS4 guitar dongles */ { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE), - .driver_data = GHL_GUITAR_PS4 | GH_GUITAR_CONTROLLER }, + .driver_data = GHL_GUITAR_PS4 | GH_GUITAR_TILT | INSTRUMENT }, + /* Rock Band 1 Wii instruments */ + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB1_GUITAR), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB1_DRUMS), + .driver_data = INSTRUMENT }, + /* Rock Band 2 Wii instruments */ + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS), + .driver_data = INSTRUMENT }, + /* Rock Band 3 Wii instruments */ + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_DRUMS_MODE), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MUSTANG_GUITAR), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_MUSTANG_MODE), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_SQUIER_MODE), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_KEYBOARD), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_KEYBOARD_MODE), + .driver_data = INSTRUMENT }, + /* Rock Band 3 PS3 instruments */ + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB_GUITAR), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB_DRUMS), + .driver_data = INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_DRUMS_MODE), + .driver_data = INSTRUMENT }, + /* Rock Band 3 PS3 Pro instruments */ + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MUSTANG_GUITAR), + .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_MUSTANG_MODE), + .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_SQUIER_MODE), + .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_KEYBOARD), + .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_KEYBOARD_MODE), + .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT }, /* Rock Band 4 PS4 guitars */ { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER), - .driver_data = RB4_GUITAR_PS4_USB }, + .driver_data = RB4_GUITAR_PS4_USB | INSTRUMENT }, { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG), - .driver_data = RB4_GUITAR_PS4_USB }, + .driver_data = RB4_GUITAR_PS4_USB | INSTRUMENT }, { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG_DONGLE), - .driver_data = RB4_GUITAR_PS4_USB }, + .driver_data = RB4_GUITAR_PS4_USB | INSTRUMENT }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_JAGUAR), - .driver_data = RB4_GUITAR_PS4_BT }, + .driver_data = RB4_GUITAR_PS4_BT | INSTRUMENT }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_PS4_STRATOCASTER), - .driver_data = RB4_GUITAR_PS4_BT }, + .driver_data = RB4_GUITAR_PS4_BT | INSTRUMENT }, /* Rock Band 4 PS5 guitars */ { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS5_RIFFMASTER), - .driver_data = RB4_GUITAR_PS5 }, + .driver_data = RB4_GUITAR_PS5 | INSTRUMENT }, { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS5_GIBSON_SG), - .driver_data = RB4_GUITAR_PS5 }, + .driver_data = RB4_GUITAR_PS5 | INSTRUMENT }, { HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS5_GIBSON_SG_DONGLE), - .driver_data = RB4_GUITAR_PS5 }, + .driver_data = RB4_GUITAR_PS5 | INSTRUMENT }, { } }; MODULE_DEVICE_TABLE(hid, sony_devices); @@ -2428,5 +2637,5 @@ static void __exit sony_exit(void) module_init(sony_init); module_exit(sony_exit); -MODULE_DESCRIPTION("HID driver for Sony / PS2 / PS3 / PS4 BD devices"); +MODULE_DESCRIPTION("HID driver for Sony / PS2 / PS3 BD / PS4 / PS5 devices"); MODULE_LICENSE("GPL"); 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-thrustmaster.c b/drivers/hid/hid-thrustmaster.c index 0bf70664c35e..48a79c97b7d0 100644 --- a/drivers/hid/hid-thrustmaster.c +++ b/drivers/hid/hid-thrustmaster.c @@ -308,7 +308,7 @@ static int thrustmaster_probe(struct hid_device *hdev, const struct hid_device_i } // Now we allocate the tm_wheel - tm_wheel = kzalloc(sizeof(struct tm_wheel), GFP_KERNEL); + tm_wheel = kzalloc_obj(struct tm_wheel); if (!tm_wheel) { ret = -ENOMEM; goto error1; @@ -328,7 +328,7 @@ static int thrustmaster_probe(struct hid_device *hdev, const struct hid_device_i goto error3; } - tm_wheel->response = kzalloc(sizeof(struct tm_wheel_response), GFP_KERNEL); + tm_wheel->response = kzalloc_obj(struct tm_wheel_response); if (!tm_wheel->response) { ret = -ENOMEM; goto error4; diff --git a/drivers/hid/hid-tmff.c b/drivers/hid/hid-tmff.c index fcd859aa3a8c..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,24 +116,27 @@ 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; - tmff = kzalloc(sizeof(struct tmff_device), GFP_KERNEL); + ff_bits = (void *)id->driver_data; + + tmff = kzalloc_obj(struct tmff_device); if (!tmff) return -ENOMEM; @@ -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-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index e28176d9d9c9..ed16f50f21ed 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -1358,13 +1358,13 @@ static int uclogic_params_ugee_v2_init_event_hooks(struct hid_device *hdev, if (!uclogic_params_ugee_v2_has_battery(hdev)) return 0; - p->event_hooks = kzalloc(sizeof(*p->event_hooks), GFP_KERNEL); + p->event_hooks = kzalloc_obj(*p->event_hooks); if (!p->event_hooks) return -ENOMEM; INIT_LIST_HEAD(&p->event_hooks->list); - event_hook = kzalloc(sizeof(*event_hook), GFP_KERNEL); + event_hook = kzalloc_obj(*event_hook); if (!event_hook) return -ENOMEM; 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-core.c b/drivers/hid/hid-wiimote-core.c index 5b5fc460a4c5..63c4fa8fbb9b 100644 --- a/drivers/hid/hid-wiimote-core.c +++ b/drivers/hid/hid-wiimote-core.c @@ -1737,7 +1737,7 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev) { struct wiimote_data *wdata; - wdata = kzalloc(sizeof(*wdata), GFP_KERNEL); + wdata = kzalloc_obj(*wdata); if (!wdata) return NULL; diff --git a/drivers/hid/hid-wiimote-debug.c b/drivers/hid/hid-wiimote-debug.c index 00f9be55f148..5f74917781f2 100644 --- a/drivers/hid/hid-wiimote-debug.c +++ b/drivers/hid/hid-wiimote-debug.c @@ -174,7 +174,7 @@ int wiidebug_init(struct wiimote_data *wdata) struct wiimote_debug *dbg; unsigned long flags; - dbg = kzalloc(sizeof(*dbg), GFP_KERNEL); + dbg = kzalloc_obj(*dbg); if (!dbg) return -ENOMEM; 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 ab65dc12d1e0..19b92c2c6579 100644 --- a/drivers/hid/hid-winwing.c +++ b/drivers/hid/hid-winwing.c @@ -12,6 +12,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/workqueue.h> #define MAX_REPORT 16 @@ -35,10 +36,14 @@ static const struct winwing_led_info led_info[3] = { struct winwing_drv_data { struct hid_device *hdev; - __u8 *report_buf; - struct mutex lock; - int map_more_buttons; - unsigned int num_leds; + struct mutex lights_lock; + __u8 *report_lights; + __u8 *report_rumble; + struct work_struct rumble_work; + struct ff_rumble_effect rumble; + int rumble_left; + int rumble_right; + int has_grip15; struct winwing_led leds[]; }; @@ -47,11 +52,15 @@ static int winwing_led_write(struct led_classdev *cdev, { struct winwing_led *led = (struct winwing_led *) cdev; struct winwing_drv_data *data = hid_get_drvdata(led->hdev); - __u8 *buf = data->report_buf; + __u8 *buf = data->report_lights; int ret; - mutex_lock(&data->lock); + mutex_lock(&data->lights_lock); + /* + * Mimicking requests captured by usbmon when LEDs + * are controlled by the vendor's app in a VM. + */ buf[0] = 0x02; buf[1] = 0x60; buf[2] = 0xbe; @@ -69,7 +78,7 @@ static int winwing_led_write(struct led_classdev *cdev, ret = hid_hw_output_report(led->hdev, buf, 14); - mutex_unlock(&data->lock); + mutex_unlock(&data->lights_lock); return ret; } @@ -87,9 +96,9 @@ static int winwing_init_led(struct hid_device *hdev, if (!data) return -EINVAL; - data->report_buf = devm_kmalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL); + data->report_lights = devm_kzalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL); - if (!data->report_buf) + if (!data->report_lights) return -ENOMEM; for (i = 0; i < 3; i += 1) { @@ -117,7 +126,7 @@ static int winwing_init_led(struct hid_device *hdev, return ret; } -static int winwing_map_button(int button, int map_more_buttons) +static int winwing_map_button(int button, int has_grip15) { if (button < 1) return KEY_RESERVED; @@ -141,7 +150,7 @@ static int winwing_map_button(int button, int map_more_buttons) return (button - 65) + BTN_TRIGGER_HAPPY17; } - if (!map_more_buttons) { + if (!has_grip15) { /* * Not mapping numbers [33 .. 64] which * are not assigned to any real buttons @@ -194,13 +203,155 @@ static int winwing_input_mapping(struct hid_device *hdev, /* Button numbers start with 1 */ button = usage->hid & HID_USAGE; - code = winwing_map_button(button, data->map_more_buttons); + code = winwing_map_button(button, data->has_grip15); hid_map_usage(hi, usage, bit, max, EV_KEY, code); return 1; } +/* + * If x ≤ 0, return 0; + * if x is in [1 .. 65535], return a value in [1 .. 255] + */ +static inline int convert_magnitude(int x) +{ + if (x < 1) + return 0; + + return ((x * 255) >> 16) + 1; +} + +static int winwing_haptic_rumble(struct winwing_drv_data *data) +{ + __u8 *buf; + __u8 m; + + if (!data) + return -EINVAL; + + if (!data->hdev) + return -EINVAL; + + buf = data->report_rumble; + + if (!buf) + return -EINVAL; + + m = convert_magnitude(data->rumble.strong_magnitude); + if (m != data->rumble_left) { + int ret; + + /* + * Mimicking requests captured by usbmon when rumble + * is activated by the vendor's app in a VM. + */ + buf[0] = 0x02; + buf[1] = 0x01; + buf[2] = 0xbf; + buf[3] = 0x00; + buf[4] = 0x00; + buf[5] = 0x03; + buf[6] = 0x49; + buf[7] = 0x00; + buf[8] = m; + buf[9] = 0x00; + buf[10] = 0; + buf[11] = 0; + buf[12] = 0; + buf[13] = 0; + + ret = hid_hw_output_report(data->hdev, buf, 14); + if (ret < 0) { + hid_err(data->hdev, "error %d (%*ph)\n", ret, 14, buf); + return ret; + } + data->rumble_left = m; + } + + m = convert_magnitude(data->rumble.weak_magnitude); + if (m != data->rumble_right) { + int ret; + + /* + * Mimicking requests captured by usbmon when rumble + * is activated by the vendor's app in a VM. + */ + buf[0] = 0x02; + buf[1] = 0x03; + buf[2] = 0xbf; + buf[3] = 0x00; + buf[4] = 0x00; + buf[5] = 0x03; + buf[6] = 0x49; + buf[7] = 0x00; + buf[8] = m; + buf[9] = 0x00; + buf[10] = 0; + buf[11] = 0; + buf[12] = 0; + buf[13] = 0; + + ret = hid_hw_output_report(data->hdev, buf, 14); + if (ret < 0) { + hid_err(data->hdev, "error %d (%*ph)\n", ret, 14, buf); + return ret; + } + data->rumble_right = m; + } + + return 0; +} + + +static void winwing_haptic_rumble_cb(struct work_struct *work) +{ + struct winwing_drv_data *data; + + data = container_of(work, struct winwing_drv_data, rumble_work); + winwing_haptic_rumble(data); +} + +static int winwing_play_effect(struct input_dev *dev, void *context, + struct ff_effect *effect) +{ + struct hid_device *hdev = input_get_drvdata(dev); + struct winwing_drv_data *data = hid_get_drvdata(hdev); + + if (effect->type != FF_RUMBLE) + return 0; + + if (!data) + return -EINVAL; + + data->rumble = effect->u.rumble; + + return schedule_work(&data->rumble_work); +} + +static int winwing_init_ff(struct hid_device *hdev, struct hid_input *hidinput) +{ + struct winwing_drv_data *data; + + data = (struct winwing_drv_data *) hid_get_drvdata(hdev); + if (!data) + return -EINVAL; + + data->report_rumble = devm_kzalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL); + data->rumble_left = -1; + data->rumble_right = -1; + + input_set_capability(hidinput->input, EV_FF, FF_RUMBLE); + + /* + * 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); +} + static int winwing_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -219,10 +370,12 @@ static int winwing_probe(struct hid_device *hdev, if (!data) return -ENOMEM; - data->map_more_buttons = id->driver_data; - + data->hdev = hdev; + data->has_grip15 = id->driver_data; hid_set_drvdata(hdev, data); + INIT_WORK(&data->rumble_work, winwing_haptic_rumble_cb); + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { hid_err(hdev, "hw start failed\n"); @@ -232,19 +385,39 @@ static int winwing_probe(struct hid_device *hdev, return 0; } +static void winwing_remove(struct hid_device *hdev) +{ + struct winwing_drv_data *data; + + data = (struct winwing_drv_data *) hid_get_drvdata(hdev); + + if (data) + cancel_work_sync(&data->rumble_work); + + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + static int winwing_input_configured(struct hid_device *hdev, struct hid_input *hidinput) { + struct winwing_drv_data *data; int ret; + data = (struct winwing_drv_data *) hid_get_drvdata(hdev); + ret = winwing_init_led(hdev, hidinput->input); if (ret) hid_err(hdev, "led init failed\n"); + if (data->has_grip15) + winwing_init_ff(hdev, hidinput); + return ret; } +/* Set driver_data to 1 for grips with rumble motor and more than 32 buttons */ static const struct hid_device_id winwing_devices[] = { { HID_USB_DEVICE(0x4098, 0xbd65), .driver_data = 1 }, /* TGRIP-15E */ { HID_USB_DEVICE(0x4098, 0xbd64), .driver_data = 1 }, /* TGRIP-15EX */ @@ -261,6 +434,7 @@ static struct hid_driver winwing_driver = { .input_configured = winwing_input_configured, .input_mapping = winwing_input_mapping, .probe = winwing_probe, + .remove = winwing_remove, }; module_hid_driver(winwing_driver); diff --git a/drivers/hid/hid-zpff.c b/drivers/hid/hid-zpff.c index aacf7f137b18..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); @@ -71,10 +66,11 @@ static int zpff_init(struct hid_device *hid) return -ENODEV; } - zpff = kzalloc(sizeof(struct zpff_device), GFP_KERNEL); + zpff = kzalloc_obj(struct zpff_device); 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/hidraw.c b/drivers/hid/hidraw.c index bbd6f23bce78..9129fabed181 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -281,7 +281,7 @@ static int hidraw_open(struct inode *inode, struct file *file) unsigned long flags; int err = 0; - if (!(list = kzalloc(sizeof(struct hidraw_list), GFP_KERNEL))) { + if (!(list = kzalloc_obj(struct hidraw_list))) { err = -ENOMEM; goto out; } @@ -603,7 +603,7 @@ int hidraw_connect(struct hid_device *hid) /* we accept any HID device, all applications */ - dev = kzalloc(sizeof(struct hidraw), GFP_KERNEL); + dev = kzalloc_obj(struct hidraw); if (!dev) return -ENOMEM; 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-elan.c b/drivers/hid/i2c-hid/i2c-hid-of-elan.c index 0215f217f6d8..919e32c47e12 100644 --- a/drivers/hid/i2c-hid/i2c-hid-of-elan.c +++ b/drivers/hid/i2c-hid/i2c-hid-of-elan.c @@ -168,6 +168,13 @@ static const struct elan_i2c_hid_chip_data elan_ekth6a12nay_chip_data = { .power_after_backlight = true, }; +static const struct elan_i2c_hid_chip_data focaltech_ft8112_chip_data = { + .post_power_delay_ms = 10, + .post_gpio_reset_on_delay_ms = 150, + .hid_descriptor_address = 0x0001, + .main_supply_name = "vcc33", +}; + static const struct elan_i2c_hid_chip_data ilitek_ili9882t_chip_data = { .post_power_delay_ms = 1, .post_gpio_reset_on_delay_ms = 200, @@ -188,11 +195,20 @@ static const struct elan_i2c_hid_chip_data ilitek_ili2901_chip_data = { .main_supply_name = "vcc33", }; +static const struct elan_i2c_hid_chip_data parade_tc3408_chip_data = { + .post_power_delay_ms = 10, + .post_gpio_reset_on_delay_ms = 300, + .hid_descriptor_address = 0x0001, + .main_supply_name = "vcc33", +}; + static const struct of_device_id elan_i2c_hid_of_match[] = { { .compatible = "elan,ekth6915", .data = &elan_ekth6915_chip_data }, { .compatible = "elan,ekth6a12nay", .data = &elan_ekth6a12nay_chip_data }, + { .compatible = "focaltech,ft8112", .data = &focaltech_ft8112_chip_data }, { .compatible = "ilitek,ili9882t", .data = &ilitek_ili9882t_chip_data }, { .compatible = "ilitek,ili2901", .data = &ilitek_ili2901_chip_data }, + { .compatible = "parade,tc3408", .data = ¶de_tc3408_chip_data }, { } }; MODULE_DEVICE_TABLE(of, elan_i2c_hid_of_match); 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-hid.c b/drivers/hid/intel-ish-hid/ishtp-hid.c index be2c62fc8251..6e255b1bc721 100644 --- a/drivers/hid/intel-ish-hid/ishtp-hid.c +++ b/drivers/hid/intel-ish-hid/ishtp-hid.c @@ -214,7 +214,7 @@ int ishtp_hid_probe(unsigned int cur_hid_dev, if (IS_ERR(hid)) return PTR_ERR(hid); - hid_data = kzalloc(sizeof(*hid_data), GFP_KERNEL); + hid_data = kzalloc_obj(*hid_data); if (!hid_data) { rv = -ENOMEM; goto err_hid_data; diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c index b890fbf97a75..44db90ad5495 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.c +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -435,7 +435,7 @@ static struct ishtp_cl_device *ishtp_bus_add_device(struct ishtp_device *dev, } spin_unlock_irqrestore(&dev->device_list_lock, flags); - device = kzalloc(sizeof(struct ishtp_cl_device), GFP_KERNEL); + device = kzalloc_obj(struct ishtp_cl_device); if (!device) return NULL; 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-ish-hid/ishtp/client-buffers.c b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c index 97f4026b1627..45ddc9a047bc 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c +++ b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c @@ -66,7 +66,7 @@ int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl) for (j = 0; j < cl->tx_ring_size; ++j) { struct ishtp_cl_tx_ring *tx_buf; - tx_buf = kzalloc(sizeof(struct ishtp_cl_tx_ring), GFP_KERNEL); + tx_buf = kzalloc_obj(struct ishtp_cl_tx_ring); if (!tx_buf) goto out; @@ -183,7 +183,7 @@ struct ishtp_cl_rb *ishtp_io_rb_init(struct ishtp_cl *cl) { struct ishtp_cl_rb *rb; - rb = kzalloc(sizeof(struct ishtp_cl_rb), GFP_KERNEL); + rb = kzalloc_obj(struct ishtp_cl_rb); if (!rb) return NULL; diff --git a/drivers/hid/intel-ish-hid/ishtp/client.c b/drivers/hid/intel-ish-hid/ishtp/client.c index 40f510b1c072..4824f3302927 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client.c +++ b/drivers/hid/intel-ish-hid/ishtp/client.c @@ -105,7 +105,7 @@ struct ishtp_cl *ishtp_cl_allocate(struct ishtp_cl_device *cl_device) { struct ishtp_cl *cl; - cl = kmalloc(sizeof(struct ishtp_cl), GFP_KERNEL); + cl = kmalloc_obj(struct ishtp_cl); if (!cl) return NULL; diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.c b/drivers/hid/intel-ish-hid/ishtp/hbm.c index 97c4fcd9e3c6..d4296dbf7eb0 100644 --- a/drivers/hid/intel-ish-hid/ishtp/hbm.c +++ b/drivers/hid/intel-ish-hid/ishtp/hbm.c @@ -34,8 +34,7 @@ static void ishtp_hbm_fw_cl_allocate(struct ishtp_device *dev) return; /* allocate storage for fw clients representation */ - clients = kcalloc(dev->fw_clients_num, sizeof(struct ishtp_fw_client), - GFP_KERNEL); + clients = kzalloc_objs(struct ishtp_fw_client, dev->fw_clients_num); if (!clients) { dev->dev_state = ISHTP_DEV_RESETTING; ish_hw_reset(dev); 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 f178017352ba..0d2ad7bc3648 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c @@ -26,6 +26,11 @@ static struct quicki2c_ddata ptl_ddata = { .max_interrupt_delay = MAX_RX_INTERRUPT_DELAY, }; +static struct quicki2c_ddata nvl_ddata = { + .max_detect_size = MAX_RX_DETECT_SIZE_NVL, + .max_interrupt_delay = MAX_RX_INTERRUPT_DELAY, +}; + /* THC QuickI2C ACPI method to get device properties */ /* HIDI2C device method */ static guid_t i2c_hid_guid = @@ -170,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); @@ -179,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; } @@ -240,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) @@ -338,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); @@ -381,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); @@ -406,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); @@ -434,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); @@ -677,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) { @@ -767,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); } @@ -791,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) @@ -821,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; @@ -862,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); @@ -878,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; @@ -909,6 +937,8 @@ static int quicki2c_thaw(struct device *device) if (ret) return ret; + WRITE_ONCE(qcdev->recovery_disabled, false); + return 0; } @@ -933,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; @@ -953,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; @@ -1032,6 +1061,8 @@ static const struct pci_device_id quicki2c_pci_tbl[] = { { PCI_DEVICE_DATA(INTEL, THC_PTL_U_DEVICE_ID_I2C_PORT2, &ptl_ddata) }, { PCI_DEVICE_DATA(INTEL, THC_WCL_DEVICE_ID_I2C_PORT1, &ptl_ddata) }, { PCI_DEVICE_DATA(INTEL, THC_WCL_DEVICE_ID_I2C_PORT2, &ptl_ddata) }, + { PCI_DEVICE_DATA(INTEL, THC_NVL_H_DEVICE_ID_I2C_PORT1, &nvl_ddata) }, + { PCI_DEVICE_DATA(INTEL, THC_NVL_H_DEVICE_ID_I2C_PORT2, &nvl_ddata) }, { } }; MODULE_DEVICE_TABLE(pci, quicki2c_pci_tbl); 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 33a1e3db1cb2..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 @@ -15,6 +17,8 @@ #define PCI_DEVICE_ID_INTEL_THC_PTL_U_DEVICE_ID_I2C_PORT2 0xE44A #define PCI_DEVICE_ID_INTEL_THC_WCL_DEVICE_ID_I2C_PORT1 0x4D48 #define PCI_DEVICE_ID_INTEL_THC_WCL_DEVICE_ID_I2C_PORT2 0x4D4A +#define PCI_DEVICE_ID_INTEL_THC_NVL_H_DEVICE_ID_I2C_PORT1 0xD348 +#define PCI_DEVICE_ID_INTEL_THC_NVL_H_DEVICE_ID_I2C_PORT2 0xD34A /* Packet size value, the unit is 16 bytes */ #define MAX_PACKET_SIZE_VALUE_LNL 256 @@ -40,6 +44,8 @@ /* PTL Max packet size detection capability is 255 Bytes */ #define MAX_RX_DETECT_SIZE_PTL 255 +/* NVL Max packet size detection capability is 64K Bytes */ +#define MAX_RX_DETECT_SIZE_NVL 65535 /* Max interrupt delay capability is 2.56ms */ #define MAX_RX_INTERRUPT_DELAY 256 @@ -155,7 +161,6 @@ struct quicki2c_ddata { struct device; struct pci_dev; -struct thc_device; struct hid_device; struct acpi_device; @@ -170,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 @@ -187,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; @@ -200,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; @@ -222,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 ad6bd59963b2..89226f5ce45e 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c @@ -37,6 +37,10 @@ struct quickspi_driver_data arl = { .max_packet_size_value = MAX_PACKET_SIZE_VALUE_MTL, }; +struct quickspi_driver_data nvl = { + .max_packet_size_value = MAX_PACKET_SIZE_VALUE_LNL, +}; + /* THC QuickSPI ACPI method to get device properties */ /* HIDSPI Method: {6e2ac436-0fcf-41af-a265-b32a220dcfab} */ static guid_t hidspi_guid = @@ -248,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); } /** @@ -333,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); @@ -381,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); @@ -457,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); @@ -551,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; @@ -632,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) { @@ -707,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); } @@ -733,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) @@ -749,9 +763,14 @@ static int quickspi_suspend(struct device *device) if (!qsdev) return -ENODEV; - ret = quickspi_set_power(qsdev, HIDSPI_SLEEP); - if (ret) - return ret; + if (!device_may_wakeup(qsdev->dev)) { + ret = quickspi_set_power(qsdev, HIDSPI_SLEEP); + if (ret) + return ret; + } + + WRITE_ONCE(qsdev->recovery_disabled, true); + cancel_work_sync(&qsdev->recover_work); ret = thc_interrupt_quiesce(qsdev->thc_hw, true); if (ret) @@ -778,22 +797,75 @@ static int quickspi_resume(struct device *device) if (ret) return ret; - thc_interrupt_config(qsdev->thc_hw); + WRITE_ONCE(qsdev->recovery_disabled, false); - thc_interrupt_enable(qsdev->thc_hw, true); + /* + * 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); - ret = thc_dma_configure(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_interrupt_quiesce(qsdev->thc_hw, false); + 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; - ret = quickspi_set_power(qsdev, HIDSPI_ON); + thc_interrupt_config(qsdev->thc_hw); + + thc_interrupt_enable(qsdev->thc_hw, true); + + /* The touch IC may have lost power, reset it to recover */ + ret = reset_tic(qsdev); if (ret) return ret; + ret = thc_dma_configure(qsdev->thc_hw); + if (ret) + return ret; + + 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; } @@ -807,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; @@ -838,6 +913,8 @@ static int quickspi_thaw(struct device *device) if (ret) return ret; + WRITE_ONCE(qsdev->recovery_disabled, false); + return 0; } @@ -851,6 +928,9 @@ static int quickspi_poweroff(struct device *device) if (!qsdev) return -ENODEV; + /* Ignore the return value as platform will be poweroff soon */ + quickspi_set_power(qsdev, HIDSPI_OFF); + ret = thc_interrupt_quiesce(qsdev->thc_hw, true); if (ret) return ret; @@ -859,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; @@ -982,6 +1064,8 @@ static const struct pci_device_id quickspi_pci_tbl[] = { {PCI_DEVICE_DATA(INTEL, THC_WCL_DEVICE_ID_SPI_PORT2, &ptl), }, {PCI_DEVICE_DATA(INTEL, THC_ARL_DEVICE_ID_SPI_PORT1, &arl), }, {PCI_DEVICE_DATA(INTEL, THC_ARL_DEVICE_ID_SPI_PORT2, &arl), }, + {PCI_DEVICE_DATA(INTEL, THC_NVL_H_DEVICE_ID_SPI_PORT1, &nvl), }, + {PCI_DEVICE_DATA(INTEL, THC_NVL_H_DEVICE_ID_SPI_PORT2, &nvl), }, {} }; MODULE_DEVICE_TABLE(pci, quickspi_pci_tbl); 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 c30e1a42eb09..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" @@ -23,6 +24,8 @@ #define PCI_DEVICE_ID_INTEL_THC_WCL_DEVICE_ID_SPI_PORT2 0x4D4B #define PCI_DEVICE_ID_INTEL_THC_ARL_DEVICE_ID_SPI_PORT1 0x7749 #define PCI_DEVICE_ID_INTEL_THC_ARL_DEVICE_ID_SPI_PORT2 0x774B +#define PCI_DEVICE_ID_INTEL_THC_NVL_H_DEVICE_ID_SPI_PORT1 0xD349 +#define PCI_DEVICE_ID_INTEL_THC_NVL_H_DEVICE_ID_SPI_PORT2 0xD34B /* HIDSPI special ACPI parameters DSM methods */ #define ACPI_QUICKSPI_REVISION_NUM 2 @@ -124,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; @@ -155,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; @@ -171,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 d8e195189e4b..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 @@ -1112,12 +1112,15 @@ int thc_port_select(struct thc_device *dev, enum thc_port_type port_type) EXPORT_SYMBOL_NS_GPL(thc_port_select, "INTEL_THC"); #define THC_SPI_FREQUENCY_7M 7812500 +#define THC_SPI_FREQUENCY_10M 10416700 #define THC_SPI_FREQUENCY_15M 15625000 #define THC_SPI_FREQUENCY_17M 17857100 #define THC_SPI_FREQUENCY_20M 20833000 #define THC_SPI_FREQUENCY_25M 25000000 #define THC_SPI_FREQUENCY_31M 31250000 +#define THC_SPI_FREQUENCY_35M 35714200 #define THC_SPI_FREQUENCY_41M 41666700 +#define THC_SPI_FREQUENCY_50M 50000000 #define THC_SPI_LOW_FREQUENCY THC_SPI_FREQUENCY_17M @@ -1125,21 +1128,27 @@ static u8 thc_get_spi_freq_div_val(struct thc_device *dev, u32 spi_freq_val) { static const int frequency[] = { THC_SPI_FREQUENCY_7M, + THC_SPI_FREQUENCY_10M, THC_SPI_FREQUENCY_15M, THC_SPI_FREQUENCY_17M, THC_SPI_FREQUENCY_20M, THC_SPI_FREQUENCY_25M, THC_SPI_FREQUENCY_31M, + THC_SPI_FREQUENCY_35M, THC_SPI_FREQUENCY_41M, + THC_SPI_FREQUENCY_50M, }; static const u8 frequency_div[] = { THC_SPI_FRQ_DIV_2, THC_SPI_FRQ_DIV_1, + THC_SPI_FRQ_DIV_1, THC_SPI_FRQ_DIV_7, THC_SPI_FRQ_DIV_6, THC_SPI_FRQ_DIV_5, THC_SPI_FRQ_DIV_4, THC_SPI_FRQ_DIV_3, + THC_SPI_FRQ_DIV_3, + THC_SPI_FRQ_DIV_2, }; int size = ARRAY_SIZE(frequency); u32 closest_freq; @@ -1190,6 +1199,25 @@ int thc_spi_read_config(struct thc_device *dev, u32 spi_freq_val, if (spi_freq_val < THC_SPI_LOW_FREQUENCY) is_low_freq = true; + /* 10M, 35M and 50M CLK need 1.5, 3.5 and 2.5 half divider */ + if ((freq_div == THC_SPI_FRQ_DIV_2 && spi_freq_val >= THC_SPI_FREQUENCY_50M) || + (freq_div == THC_SPI_FRQ_DIV_3 && spi_freq_val < THC_SPI_FREQUENCY_41M) || + (freq_div == THC_SPI_FRQ_DIV_1 && spi_freq_val < THC_SPI_FREQUENCY_15M)) { + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCRF_HALF_DIV_EN, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCRF_HALF_DIV_EN); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPARE_REG_OFFSET, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE); + } else { + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCRF_HALF_DIV_EN, 0); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPARE_REG_OFFSET, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE, 0); + } + cfg = FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TCRF, freq_div) | FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TRMODE, io_mode) | (is_low_freq ? THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN : 0) | @@ -1243,6 +1271,25 @@ int thc_spi_write_config(struct thc_device *dev, u32 spi_freq_val, if (spi_freq_val < THC_SPI_LOW_FREQUENCY) is_low_freq = true; + /* 10M, 35M and 50M CLK need 1.5, 3.5 and 2.5 half divider */ + if ((freq_div == THC_SPI_FRQ_DIV_2 && spi_freq_val >= THC_SPI_FREQUENCY_50M) || + (freq_div == THC_SPI_FRQ_DIV_3 && spi_freq_val < THC_SPI_FREQUENCY_41M) || + (freq_div == THC_SPI_FRQ_DIV_1 && spi_freq_val < THC_SPI_FREQUENCY_15M)) { + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCWF_HALF_DIV_EN, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCWF_HALF_DIV_EN); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPARE_REG_OFFSET, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE); + } else { + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET, + THC_M_PRT_SPI_DUTYC_CFG_SPI_TCWF_HALF_DIV_EN, 0); + + regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPARE_REG_OFFSET, + THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE, 0); + } + cfg = FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TCWF, freq_div) | FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TWMODE, io_mode) | (is_low_freq ? THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN : 0) | @@ -1375,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; @@ -1399,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; } @@ -1427,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, @@ -1443,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; @@ -1466,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 413730f8e3f7..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 @@ -643,6 +643,10 @@ #define THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_VAL GENMASK(3, 0) #define THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_EN BIT(25) +#define THC_M_PRT_SPI_DUTYC_CFG_SPI_TCRF_HALF_DIV_EN BIT(30) +#define THC_M_PRT_SPI_DUTYC_CFG_SPI_TCWF_HALF_DIV_EN BIT(31) + +#define THC_M_PRT_SPARE_REG_SPI_CLK_INV_ENABLE BIT(2) /* CS Assertion delay default value */ #define THC_CSA_CK_DELAY_VAL_DEFAULT 4 @@ -883,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 21a70420151e..37b60c3aaf66 100644 --- a/drivers/hid/uhid.c +++ b/drivers/hid/uhid.c @@ -110,7 +110,7 @@ static int uhid_queue_event(struct uhid_device *uhid, __u32 event) unsigned long flags; struct uhid_event *ev; - ev = kzalloc(sizeof(*ev), GFP_KERNEL); + ev = kzalloc_obj(*ev); if (!ev) return -ENOMEM; @@ -129,7 +129,7 @@ static int uhid_hid_start(struct hid_device *hid) struct uhid_event *ev; unsigned long flags; - ev = kzalloc(sizeof(*ev), GFP_KERNEL); + ev = kzalloc_obj(*ev); if (!ev) return -ENOMEM; @@ -240,7 +240,7 @@ static int uhid_hid_get_report(struct hid_device *hid, unsigned char rnum, if (!READ_ONCE(uhid->running)) return -EIO; - ev = kzalloc(sizeof(*ev), GFP_KERNEL); + ev = kzalloc_obj(*ev); if (!ev) return -ENOMEM; @@ -282,7 +282,7 @@ static int uhid_hid_set_report(struct hid_device *hid, unsigned char rnum, if (!READ_ONCE(uhid->running) || count > UHID_DATA_MAX) return -EIO; - ev = kzalloc(sizeof(*ev), GFP_KERNEL); + ev = kzalloc_obj(*ev); if (!ev) return -ENOMEM; @@ -365,7 +365,7 @@ static int uhid_hid_output_raw(struct hid_device *hid, __u8 *buf, size_t count, if (count < 1 || count > UHID_DATA_MAX) return -EINVAL; - ev = kzalloc(sizeof(*ev), GFP_KERNEL); + ev = kzalloc_obj(*ev); if (!ev) return -ENOMEM; @@ -433,7 +433,7 @@ static int uhid_event_from_user(const char __user *buffer, size_t len, */ struct uhid_create_req_compat *compat; - compat = kzalloc(sizeof(*compat), GFP_KERNEL); + compat = kzalloc_obj(*compat); if (!compat) return -ENOMEM; @@ -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; } @@ -636,7 +636,7 @@ static int uhid_char_open(struct inode *inode, struct file *file) { struct uhid_device *uhid; - uhid = kzalloc(sizeof(*uhid), GFP_KERNEL); + uhid = kzalloc_obj(*uhid); if (!uhid) return -ENOMEM; diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 758eb21430cd..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; @@ -858,7 +860,7 @@ static int hid_alloc_buffers(struct usb_device *dev, struct hid_device *hid) &usbhid->inbuf_dma); usbhid->outbuf = usb_alloc_coherent(dev, usbhid->bufsize, GFP_KERNEL, &usbhid->outbuf_dma); - usbhid->cr = kmalloc(sizeof(*usbhid->cr), GFP_KERNEL); + usbhid->cr = kmalloc_obj(*usbhid->cr); usbhid->ctrlbuf = usb_alloc_coherent(dev, usbhid->bufsize, GFP_KERNEL, &usbhid->ctrlbuf_dma); if (!usbhid->inbuf || !usbhid->outbuf || !usbhid->cr || @@ -1363,19 +1365,18 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id * { struct usb_host_interface *interface = intf->cur_altsetting; struct usb_device *dev = interface_to_usbdev(intf); + struct usb_endpoint_descriptor *ep; struct usbhid_device *usbhid; struct hid_device *hid; - unsigned int n, has_in = 0; + struct seq_buf hid_name; size_t len; int ret; dbg_hid("HID probe called for ifnum %d\n", intf->altsetting->desc.bInterfaceNumber); - for (n = 0; n < interface->desc.bNumEndpoints; n++) - if (usb_endpoint_is_int_in(&interface->endpoint[n].desc)) - has_in++; - if (!has_in) { + ret = usb_find_int_in_endpoint(interface, &ep); + if (ret) { hid_err(intf, "couldn't find an input interrupt endpoint\n"); return -ENODEV; } @@ -1399,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; @@ -1407,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); @@ -1430,7 +1432,7 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id * if (usb_string(dev, dev->descriptor.iSerialNumber, hid->uniq, 64) <= 0) hid->uniq[0] = 0; - usbhid = kzalloc(sizeof(*usbhid), GFP_KERNEL); + usbhid = kzalloc_obj(*usbhid); if (usbhid == NULL) { ret = -ENOMEM; goto err; @@ -1552,7 +1554,7 @@ static int hid_post_reset(struct usb_interface *intf) * configuration descriptors passed, we already know that * the size of the HID report descriptor has not changed. */ - rdesc = kmalloc(hid->dev_rsize, GFP_KERNEL); + rdesc = kmalloc(hid->dev_rsize, GFP_NOIO); if (!rdesc) return -ENOMEM; diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 56d6af39ba81..22951b7ecd17 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -11,8 +11,10 @@ #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> #include <linux/usb.h> #define PID_EFFECTS_MAX 64 @@ -81,7 +83,7 @@ static const u8 pidff_set_envelope[] = { 0x22, 0x5b, 0x5c, 0x5d, 0x5e }; #define PID_NEG_COEFFICIENT 4 #define PID_POS_SATURATION 5 #define PID_NEG_SATURATION 6 -#define PID_DEAD_BAND 7 +#define PID_DEADBAND 7 static const u8 pidff_set_condition[] = { 0x22, 0x23, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65 }; @@ -325,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; } /* @@ -518,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); @@ -618,14 +622,24 @@ static void pidff_set_condition_report(struct pidff_device *pidff, effect->u.condition[i].center); pidff_set_signed(&pidff->set_condition[PID_POS_COEFFICIENT], effect->u.condition[i].right_coeff); - pidff_set_signed(&pidff->set_condition[PID_NEG_COEFFICIENT], - effect->u.condition[i].left_coeff); pidff_set(&pidff->set_condition[PID_POS_SATURATION], effect->u.condition[i].right_saturation); - pidff_set(&pidff->set_condition[PID_NEG_SATURATION], - effect->u.condition[i].left_saturation); - pidff_set(&pidff->set_condition[PID_DEAD_BAND], - effect->u.condition[i].deadband); + + /* Omit Negative Coefficient if missing */ + if (!(pidff->quirks & HID_PIDFF_QUIRK_MISSING_NEG_COEFFICIENT)) + pidff_set_signed(&pidff->set_condition[PID_NEG_COEFFICIENT], + effect->u.condition[i].left_coeff); + + /* Omit Negative Saturation if missing */ + if (!(pidff->quirks & HID_PIDFF_QUIRK_MISSING_NEG_SATURATION)) + pidff_set_signed(&pidff->set_condition[PID_NEG_SATURATION], + effect->u.condition[i].left_saturation); + + /* Omit Deadband field if missing */ + if (!(pidff->quirks & HID_PIDFF_QUIRK_MISSING_DEADBAND)) + pidff_set(&pidff->set_condition[PID_DEADBAND], + effect->u.condition[i].deadband); + hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONDITION], HID_REQ_SET_REPORT); } @@ -1053,6 +1067,11 @@ static int pidff_find_field_with_usage(int *usage_index, return -1; } +#define PIDFF_MISSING_FIELD(name, quirks) \ + ({ pr_debug("%s field not found, but that's OK\n", __stringify(name)); \ + pr_debug("Setting MISSING_%s quirk\n", __stringify(name)); \ + *quirks |= HID_PIDFF_QUIRK_MISSING_ ## name; }) + /* * Find fields from a report and fill a pidff_usage */ @@ -1060,9 +1079,6 @@ static int pidff_find_fields(struct pidff_usage *usage, const u8 *table, struct hid_report *report, int count, int strict, u32 *quirks) { - const u8 block_offset = pidff_set_condition[PID_PARAM_BLOCK_OFFSET]; - const u8 delay = pidff_set_effect[PID_START_DELAY]; - if (!report) { pr_debug("%s, null report\n", __func__); return -1; @@ -1080,17 +1096,23 @@ static int pidff_find_fields(struct pidff_usage *usage, const u8 *table, continue; } - if (table[i] == delay) { - pr_debug("Delay field not found, but that's OK\n"); - pr_debug("Setting MISSING_DELAY quirk\n"); - *quirks |= HID_PIDFF_QUIRK_MISSING_DELAY; + /* Field quirks auto-detection */ + if (table[i] == pidff_set_effect[PID_START_DELAY]) + PIDFF_MISSING_FIELD(DELAY, quirks); - } else if (table[i] == block_offset) { - pr_debug("PBO field not found, but that's OK\n"); - pr_debug("Setting MISSING_PBO quirk\n"); - *quirks |= HID_PIDFF_QUIRK_MISSING_PBO; + else if (table[i] == pidff_set_condition[PID_PARAM_BLOCK_OFFSET]) + PIDFF_MISSING_FIELD(PBO, quirks); - } else if (strict) { + else if (table[i] == pidff_set_condition[PID_NEG_COEFFICIENT]) + PIDFF_MISSING_FIELD(NEG_COEFFICIENT, quirks); + + else if (table[i] == pidff_set_condition[PID_NEG_SATURATION]) + PIDFF_MISSING_FIELD(NEG_SATURATION, quirks); + + else if (table[i] == pidff_set_condition[PID_DEADBAND]) + PIDFF_MISSING_FIELD(DEADBAND, quirks); + + else if (strict) { pr_debug("failed to locate %d\n", i); return -1; } @@ -1517,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)) { @@ -1531,7 +1560,7 @@ int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks) return -ENODEV; } - pidff = kzalloc(sizeof(*pidff), GFP_KERNEL); + pidff = kzalloc_obj(*pidff); if (!pidff) return -ENOMEM; diff --git a/drivers/hid/usbhid/hid-pidff.h b/drivers/hid/usbhid/hid-pidff.h index f321f675e131..c413aa732842 100644 --- a/drivers/hid/usbhid/hid-pidff.h +++ b/drivers/hid/usbhid/hid-pidff.h @@ -21,6 +21,15 @@ /* Force all periodic effects to be uploaded as SINE */ #define HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY BIT(4) +/* Allow devices with missing negative coefficient in the set condition usage */ +#define HID_PIDFF_QUIRK_MISSING_NEG_COEFFICIENT BIT(5) + +/* Allow devices with missing negative saturation in the set condition usage */ +#define HID_PIDFF_QUIRK_MISSING_NEG_SATURATION BIT(6) + +/* Allow devices with missing deadband in the set condition usage */ +#define HID_PIDFF_QUIRK_MISSING_DEADBAND BIT(7) + #ifdef CONFIG_HID_PID int hid_pidff_init(struct hid_device *hid); int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks); diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 59cf3ddfdf78..6378801b22c6 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c @@ -434,7 +434,7 @@ static noinline int hiddev_ioctl_usage(struct hiddev *hiddev, unsigned int cmd, struct hid_field *field; int i; - uref_multi = kmalloc(sizeof(struct hiddev_usage_ref_multi), GFP_KERNEL); + uref_multi = kmalloc_obj(struct hiddev_usage_ref_multi); if (!uref_multi) return -ENOMEM; uref = &uref_multi->uref; @@ -890,7 +890,7 @@ int hiddev_connect(struct hid_device *hid, unsigned int force) return -EINVAL; } - if (!(hiddev = kzalloc(sizeof(struct hiddev), GFP_KERNEL))) + if (!(hiddev = kzalloc_obj(struct hiddev))) return -ENOMEM; init_waitqueue_head(&hiddev->wait); diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c index af6bc76dbf64..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 @@ -241,7 +242,7 @@ static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd) return -1; if (!(kbd->new = usb_alloc_coherent(dev, 8, GFP_KERNEL, &kbd->new_dma))) return -1; - if (!(kbd->cr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL))) + if (!(kbd->cr = kmalloc_obj(struct usb_ctrlrequest))) return -1; if (!(kbd->leds = usb_alloc_coherent(dev, 1, GFP_KERNEL, &kbd->leds_dma))) return -1; @@ -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; @@ -281,7 +284,7 @@ static int usb_kbd_probe(struct usb_interface *iface, pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); maxp = usb_maxpacket(dev, pipe); - kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL); + kbd = kzalloc_obj(struct usb_kbd); input_dev = input_allocate_device(); if (!kbd || !input_dev) goto fail1; @@ -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 3fd93c2e4f4a..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; @@ -125,7 +128,7 @@ static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_i pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); maxp = usb_maxpacket(dev, pipe); - mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL); + mouse = kzalloc_obj(struct usb_mouse); input_dev = input_allocate_device(); if (!mouse || !input_dev) goto fail1; @@ -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 afc900560706..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; } @@ -892,7 +897,7 @@ static int wacom_add_shared_data(struct hid_device *hdev) data = wacom_get_hdev_data(hdev); if (!data) { - data = kzalloc(sizeof(struct wacom_hdev_data), GFP_KERNEL); + data = kzalloc_obj(struct wacom_hdev_data); if (!data) { mutex_unlock(&wacom_udev_list_lock); return -ENOMEM; @@ -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; |
