hid-for-linus-2026081901

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEL65usyKPHcrRDEicpmLzj2vtYEkFAmqFXCEACgkQpmLzj2vt
 YEkTbw//QD2eA9n18g/iDkUk6SAZ6h3YZxhOTQIlHe4sv6auuW64epL/Nz0XKnD7
 5odcrnukMIGXRtUmcu0bO8FP85+bvsPUERz82IWtKSaP1vdYtkTNxeBin6HlAHOR
 A0TfOOMJjfWuVGUVZ1G3AbBq3GGR80Q8c8YyhPnAnTzdh0cjw6+++1iP4NiGfalX
 yAgHmQh++Fo/Ar4c2QyvyEdSqMiAeocVU3qqeNUm7tRImtcsbmFa4cBOMupLSiKR
 YMkQD8A2QqS3M3b9BbXo4HdtB8JLiskkClm8ytUadIsUYSUaRah7aj8C3RaDSc4C
 mHCLZWjNGKEW+My1Q+lNjELRGLWKPkjw0tv+ZK3Ci0rsCTo7/lnopyufBe9XCptQ
 acLvnaCt2RYNRNFPPFhnIBOAuYrIwnWFnhYnNlyGVl9mcgeZfiiRjtk1Lp80YgTo
 WCD7i6/zIm2OobrIpRoEbk3ffNXkZ6wcMkp42p0oNLnI191Gspt6SOmLCnQ2GqHd
 efJAIFPPfbdWbydZW8qPBdRG/lzlaPHoyOL94fTIwqvpznX/Mp5FPQE9RD8APU2Z
 iAhrD6Jxk+5Z27BHCcVUU2jrdeBUGisD6zNdJ+st9Xdp6qLYbU8pl3NLdhCwc6NA
 Avt0IwhrboQZSGu8nZkFcYMc/pGe4I8dcf3+MRqIIjWwXLvUHUY=
 =7+OR
 -----END PGP SIGNATURE-----

Merge tag 'hid-for-linus-2026081901' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid

Pull HID updates from Jiri Kosina:
 "Core:
   - fix long-standing force-feedback initialization race across the
     subsystem (Dmitry Torokhov)
   - switch to system_dfl_wq (Marco Crivellari)

  AMD-SFH:
   - support for tablet-mode switch for AMD SFH-based systems (Basavaraj
     Natikar)

  HyperX:
   - support for HyperX QuadCast 2 (Benjamin Blume)

  I2C-HID:
   - support for devices that provide HID descriptor solely through
     the ACPI _DSM method (XIE Zhibang)

  Intel-THC-HID:
   - support for full I2C bus config parameters (Even Xu)

  Logitech:
   - HID++ 2.0 repogrammable button support (Elliot Douglas)
   - Bolt receiver support for HID++ devices (Erik Håkansson)

  MSI:
   - support for MSI Claw (Derek J. Clark)

  Steam:
   - initial support for 2026 Steam Controller (Vicki Pfau)
   - support for sensor events on the 2025 Steam Controller (Vicki Pfau)

  And many, many other fixes for various long standing issues that were
  found by new modern tools, and quite a few device ID additions"

* tag 'hid-for-linus-2026081901' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (146 commits)
  HID: tmff: Use 64-bit arithmetic for force feedback scaling
  HID: multitouch: reclassify HTIX5288 to WIN_8_FORCE_MULTI_INPUT_NSMU
  HID: sensor: custom: Fix field sysfs group cleanup on failure
  HID: sensor: custom: Fix use-after-free in enable_sensor
  HID: intel-thc-hid: intel-quickspi: bound GET_REPORT response to the caller buffer
  HID: haptic: don't write an uninitialized value to unhandled usages
  HID: intel-thc-hid: intel-quickspi: fix autosuspend cleanup during teardown
  HID: intel-thc-hid: intel-quicki2c: fix autosuspend cleanup during teardown
  HID: steam: Zero out inputs when disabling gamepad mode
  HID: steam: Clean up locking
  HID: steam: Don't set feature reports when disconnecting
  HID: steam: Fix wording of connect/disconnect logs
  HID: steam: Initial 2026 Steam Controller support
  HID: steam: Refactor registration
  HID: logitech: add Bolt receiver support for Logitech HID++ devices
  HID: sensor-hub: Fix out-of-bounds write in sensor_hub_get_feature
  HID: universal-pidff: stop the device when force-feedback init fails
  HID: haptic: move FF initialization into .input_configured()
  HID: logitech-hidpp: move FF initialization to .input_configured()
  HID: megaworld: move FF initialization to .input_configured()
  ...
This commit is contained in:
Linus Torvalds 2026-08-19 10:00:31 -07:00
commit a93f3bf4e1
92 changed files with 7172 additions and 2028 deletions

View File

@ -522,3 +522,53 @@ This should really be your last resort.
vendor: 0x093a
product: 0x2510
...
Input Device Registration and Lifecycle
========================================
HID drivers that rely on the HID core to register input devices (by using the
``HID_CONNECT_HIDINPUT`` flag, which is part of ``HID_CONNECT_DEFAULT``)
must be aware of the registration timing.
When ``hid_hw_start(hdev, flags)`` is called with ``HID_CONNECT_HIDINPUT``,
the HID core immediately parses the report descriptor, allocates ``input_dev``
structures, and calls ``input_register_device()`` for each of them.
This means the input device becomes **live and visible to userspace** before
``hid_hw_start()`` returns.
If a driver needs to perform additional configuration on the input device (such
as adding force-feedback support, setting extra bits in ``evbit``, or
assigning custom event handlers), doing so in the ``probe`` function after
``hid_hw_start()`` is **incorrect and racy**. Userspace may trigger
callbacks (like ``play_effect``) via ioctls immediately after registration,
leading to potential NULL pointer dereferences if the driver hasn't finished
initializing its private data.
The correct way to augment an input device before it is registered is to use the
``.input_configured`` callback in ``struct hid_driver``. This hook is
called by the HID core after the ``input_dev`` is fully formed but **before**
``input_register_device()`` is invoked.
Example:
.. code-block:: c
static int my_input_configured(struct hid_device *hdev, struct hid_input *hidinput)
{
struct input_dev *input = hidinput->input;
/* Initialize private data and capabilities here */
set_bit(EV_FF, input->evbit);
return input_ff_create_memless(input, NULL, my_play_effect);
}
static struct hid_driver my_driver = {
.name = "my_driver",
.probe = my_probe,
.input_configured = my_input_configured,
};
Drivers that require even more control over the lifecycle should mask out
``HID_CONNECT_HIDINPUT`` and call ``input_register_device()`` manually
when they are ready.

View File

@ -1301,6 +1301,7 @@ L: linux-input@vger.kernel.org
S: Maintained
F: Documentation/hid/amd-sfh*
F: drivers/hid/amd-sfh-hid/
F: drivers/input/misc/amd_sfh_tabletmode.c
AMD SPI DRIVER
M: Raju Rangoju <Raju.Rangoju@amd.com>
@ -11514,6 +11515,12 @@ F: include/uapi/linux/hid*
F: samples/hid/
F: tools/testing/selftests/hid/
HID HYPERX DRIVER
M: Benjamin Blume <benjaminblume@posteo.de>
L: linux-input@vger.kernel.org
S: Maintained
F: drivers/hid/hid-hyperx.c
HID LOGITECH DRIVERS
R: Filipe Laíns <lains@riseup.net>
L: linux-input@vger.kernel.org
@ -11550,6 +11557,12 @@ F: drivers/hid/hid-sensor-*
F: drivers/iio/*/hid-*
F: include/linux/hid-sensor-*
HID STEAM CONTROLLER
M: Vicki Pfau <vi@endrift.com>
L: linux-input@vger.kernel.org
S: Maintained
F: drivers/hid/hid-steam.c
HID VRC-2 CAR CONTROLLER DRIVER
M: Marcus Folkesson <marcus.folkesson@gmail.com>
L: linux-input@vger.kernel.org
@ -18350,6 +18363,12 @@ S: Odd Fixes
F: Documentation/devicetree/bindings/net/ieee802154/mrf24j40.txt
F: drivers/net/ieee802154/mrf24j40.c
MSI HID DRIVER
M: Derek J. Clark <derekjohn.clark@gmail.com>
L: linux-input@vger.kernel.org
S: Maintained
F: drivers/hid/hid-msi.c
MSI EC DRIVER
M: Nikita Kravets <teackot@gmail.com>
L: platform-driver-x86@vger.kernel.org

View File

@ -433,6 +433,19 @@ config HOLTEK_FF
Say Y here if you have a Holtek On Line Grip based game controller
and want to have force feedback support for it.
config HID_HYPERX
tristate "HyperX microphones"
depends on USB_HID
help
Support for the mute button of HyperX microphones.
The button is handled in the device firmware and only reports the
resulting mute state through a vendor-defined collection, so without
this driver userspace never learns that the microphone was muted.
Supported devices:
- HyperX QuadCast 2
config HID_VIVALDI_COMMON
tristate
help
@ -485,6 +498,19 @@ config HID_GT683R
Currently the following devices are know to be supported:
- MSI GT683R
config HID_MSI
tristate "MSI Claw Gamepad Support"
depends on USB_HID
select NEW_LEDS
select LEDS_CLASS
select LEDS_CLASS_MULTICOLOR
help
Support for the MSI Claw RGB and controller configuration
Say Y here to include configuration interface support for the MSI Claw Line
of Handheld Console Controllers. Say M here to compile this driver as a
module. The module will be called hid-msi.
config HID_KEYTOUCH
tristate "Keytouch HID devices"
help
@ -1048,6 +1074,7 @@ config HID_PXRC
config HID_RAPOO
tristate "Rapoo non-fully HID-compliant devices"
depends on USB_HID
help
Support for Rapoo devices that are not fully compliant with the
HID standard.
@ -1079,6 +1106,15 @@ config HID_ROCCAT
Say Y here if you have a Roccat mouse or keyboard and want
support for its special functionalities.
config HID_ROCCAT_KONE_KUNIT_TEST
bool "KUnit tests for the Roccat Kone driver" if !KUNIT_ALL_TESTS
depends on HID_ROCCAT=y && KUNIT=y
default KUNIT_ALL_TESTS
help
Enable the KUnit regression tests for the Roccat Kone driver,
covering bounds checking of device-supplied profile indices.
If unsure, say N.
config HID_SAITEK
tristate "Saitek (Mad Catz) non-fully HID-compliant devices"
help
@ -1215,6 +1251,16 @@ config HID_HYPERV_MOUSE
help
Select this option to enable the Hyper-V mouse driver.
config HID_HYPERV_MOUSE_KUNIT_TEST
bool "KUnit tests for Hyper-V mouse driver" if !KUNIT_ALL_TESTS
depends on KUNIT && HID_HYPERV_MOUSE
default KUNIT_ALL_TESTS
help
Builds unit tests for the Hyper-V synthetic HID driver.
These tests exercise the initial device-info parser with
malformed host-provided HID descriptors and are only useful
for kernel developers running KUnit.
config HID_SMARTJOYPLUS
tristate "SmartJoy PLUS PS2/USB adapter support"
help

View File

@ -67,6 +67,7 @@ obj-$(CONFIG_HID_HOLTEK) += hid-holtek-kbd.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtek-mouse.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtekff.o
obj-$(CONFIG_HID_HYPERV_MOUSE) += hid-hyperv.o
obj-$(CONFIG_HID_HYPERX) += hid-hyperx.o
obj-$(CONFIG_HID_ICADE) += hid-icade.o
obj-$(CONFIG_HID_ITE) += hid-ite.o
obj-$(CONFIG_HID_JABRA) += hid-jabra.o
@ -92,6 +93,7 @@ obj-$(CONFIG_HID_MAYFLASH) += hid-mf.o
obj-$(CONFIG_HID_MEGAWORLD_FF) += hid-megaworld.o
obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o
obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o
obj-$(CONFIG_HID_MSI) += hid-msi.o
obj-$(CONFIG_HID_MULTITOUCH) += hid-multitouch.o
obj-$(CONFIG_HID_NINTENDO) += hid-nintendo.o
obj-$(CONFIG_HID_NTI) += hid-nti.o
@ -134,7 +136,7 @@ obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
obj-$(CONFIG_HID_SONY) += hid-sony.o
obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o
obj-$(CONFIG_HID_STEAM) += hid-steam.o
obj-$(CONFIG_HID_STEELSERIES) += hid-steelseries.o
obj-$(CONFIG_HID_STEELSERIES) += hid-steelseries.o hid-steelseries-arctis.o
obj-$(CONFIG_HID_SUNPLUS) += hid-sunplus.o
obj-$(CONFIG_HID_GREENASIA) += hid-gaff.o
obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o hid-thrustmaster.o

View File

@ -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.

View File

@ -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;
}

View File

@ -10,6 +10,7 @@
#ifndef AMD_SFH_COMMON_H
#define AMD_SFH_COMMON_H
#include <linux/auxiliary_bus.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include "amd_sfh_hid.h"
@ -35,6 +36,11 @@ enum cmd_id {
STOP_ALL_SENSORS = 8,
};
enum amd_mp2_version {
MP2_VER_V2 = 1,
MP2_VER_1_1 = 2,
};
struct amd_mp2_sensor_info {
u8 sensor_idx;
u32 period;
@ -64,6 +70,8 @@ struct amd_mp2_dev {
struct mutex lock;
u8 init_done;
u8 rver;
u8 mp2_ver;
struct auxiliary_device *tm_auxdev;
};
struct amd_mp2_ops {
@ -100,4 +108,9 @@ static inline u64 amd_get_p2c_val(struct amd_mp2_dev *mp2, u32 idx)
{
return mp2->rver == 1 ? AMD_P2C_MSG_V1(idx) : AMD_P2C_MSG(idx);
}
bool amd_sfh_op_idx_enabled(struct amd_mp2_dev *mp2);
void sfh_set_emp2(struct amd_mp2_dev *mp2);
void sfh_deinit_emp2(void);
#endif

View File

@ -87,16 +87,17 @@ static int amdtp_wait_for_response(struct hid_device *hid)
break;
}
if (!cli_data->request_done[i])
if (!cli_data->request_done[i]) {
ret = wait_event_interruptible_timeout(hid_data->hid_wait,
cli_data->request_done[i],
msecs_to_jiffies(AMD_SFH_RESPONSE_TIMEOUT));
if (ret == -ERESTARTSYS)
return -ERESTARTSYS;
else if (ret < 0)
return -ETIMEDOUT;
else
return 0;
if (ret == -ERESTARTSYS)
return -ERESTARTSYS;
if (ret <= 0)
return -ETIMEDOUT;
}
return 0;
}
void amdtp_hid_wakeup(struct hid_device *hid)
@ -162,6 +163,7 @@ int amdtp_hid_probe(u32 cur_hid_dev, struct amdtp_cl_data *cli_data)
return 0;
err_hid_device:
cli_data->hid_sensor_hubs[cur_hid_dev] = NULL;
kfree(hid_data);
err_hid_data:
hid_destroy_device(hid);

View File

@ -8,11 +8,13 @@
* Basavaraj Natikar <Basavaraj.Natikar@amd.com>
*/
#include <linux/auxiliary_bus.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/devm-helpers.h>
#include <linux/dma-mapping.h>
#include <linux/dmi.h>
#include <linux/idr.h>
#include <linux/interrupt.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/iopoll.h>
@ -122,19 +124,10 @@ static irqreturn_t amd_sfh_irq_handler(int irq, void *data)
int amd_sfh_irq_init_v2(struct amd_mp2_dev *privdata)
{
int rc;
pcim_intx(privdata->pdev, true);
rc = devm_request_irq(&privdata->pdev->dev, privdata->pdev->irq,
amd_sfh_irq_handler, 0, DRIVER_NAME, privdata);
if (rc) {
dev_err(&privdata->pdev->dev, "failed to request irq %d err=%d\n",
privdata->pdev->irq, rc);
return rc;
}
return 0;
return devm_request_irq(&privdata->pdev->dev, privdata->pdev->irq,
amd_sfh_irq_handler, 0, DRIVER_NAME, privdata);
}
static int amd_sfh_dis_sts_v2(struct amd_mp2_dev *privdata)
@ -251,6 +244,8 @@ int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id)
static void amd_mp2_pci_remove(void *privdata)
{
struct amd_mp2_dev *mp2 = privdata;
sfh_deinit_emp2();
amd_sfh_hid_client_deinit(privdata);
mp2->mp2_ops->stop_all(mp2);
pcim_intx(mp2->pdev, false);
@ -285,6 +280,7 @@ static void mp2_select_ops(struct amd_mp2_dev *privdata)
switch (acs) {
case V2_STATUS:
privdata->mp2_ops = &amd_sfh_ops_v2;
privdata->mp2_ver = MP2_VER_V2;
break;
default:
privdata->mp2_ops = &amd_sfh_ops;
@ -386,6 +382,51 @@ static const struct attribute_group *amd_sfh_groups[] = {
NULL,
};
static DEFINE_IDA(sfh_tm_ida);
static void amd_sfh_maybe_register_tm(struct amd_mp2_dev *mp2)
{
struct auxiliary_device *adev;
bool present;
int id;
if (mp2->tm_auxdev)
return;
present = mp2->sfh1_1_ops ? mp2->dev_en.is_sra_present
: (mp2->mp2_ver == MP2_VER_V2 &&
amd_sfh_op_idx_enabled(mp2));
if (!present)
return;
id = ida_alloc(&sfh_tm_ida, GFP_KERNEL);
if (id < 0)
return;
adev = auxiliary_device_create(&mp2->pdev->dev, KBUILD_MODNAME,
"tabletmode", NULL, id);
if (!adev) {
ida_free(&sfh_tm_ida, id);
dev_warn(&mp2->pdev->dev, "tabletmode auxdev create failed\n");
return;
}
mp2->tm_auxdev = adev;
}
static void amd_sfh_unregister_tm(struct amd_mp2_dev *mp2)
{
int id;
if (!mp2->tm_auxdev)
return;
id = mp2->tm_auxdev->id;
auxiliary_device_destroy(mp2->tm_auxdev);
mp2->tm_auxdev = NULL;
ida_free(&sfh_tm_ida, id);
}
static void sfh1_1_init_work(struct work_struct *work)
{
struct amd_mp2_dev *mp2 = container_of(work, struct amd_mp2_dev, work);
@ -402,6 +443,7 @@ static void sfh1_1_init_work(struct work_struct *work)
if (rc)
dev_warn(&mp2->pdev->dev, "failed to update sysfs group\n");
amd_sfh_maybe_register_tm(mp2);
}
static void sfh_init_work(struct work_struct *work)
@ -418,8 +460,10 @@ static void sfh_init_work(struct work_struct *work)
return;
}
sfh_set_emp2(mp2);
amd_sfh_clear_intr(mp2);
mp2->init_done = 1;
amd_sfh_maybe_register_tm(mp2);
}
static void amd_sfh_remove(struct pci_dev *pdev)
@ -427,6 +471,7 @@ static void amd_sfh_remove(struct pci_dev *pdev)
struct amd_mp2_dev *mp2 = pci_get_drvdata(pdev);
flush_work(&mp2->work);
amd_sfh_unregister_tm(mp2);
if (mp2->init_done)
mp2->mp2_ops->remove(mp2);
}
@ -447,6 +492,7 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i
privdata->pdev = pdev;
dev_set_drvdata(&pdev->dev, privdata);
rc = pcim_enable_device(pdev);
if (rc)
return rc;
@ -471,8 +517,9 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i
if (rc)
return rc;
privdata->sfh1_1_ops = (const struct amd_sfh1_1_ops *)id->driver_data;
if (privdata->sfh1_1_ops) {
privdata->mp2_ver = (enum amd_mp2_version)id->driver_data;
if (privdata->mp2_ver >= MP2_VER_1_1) {
privdata->sfh1_1_ops = &sfh1_1_ops;
if (boot_cpu_data.x86 >= 0x1A)
privdata->rver = 1;
@ -540,8 +587,7 @@ static SIMPLE_DEV_PM_OPS(amd_mp2_pm_ops, amd_mp2_pci_suspend,
static const struct pci_device_id amd_mp2_pci_tbl[] = {
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2_1_1),
.driver_data = (kernel_ulong_t)&sfh1_1_ops },
{ PCI_DEVICE_DATA(AMD, MP2_1_1, MP2_VER_1_1) },
{ }
};
MODULE_DEVICE_TABLE(pci, amd_mp2_pci_tbl);

View File

@ -8,12 +8,14 @@
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
*/
#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 +80,48 @@ static struct amd_mp2_ops amd_sfh_ops = {
void sfh_deinit_emp2(void)
{
guard(mutex)(&emp2_lock);
emp2 = NULL;
}
void sfh_set_emp2(struct amd_mp2_dev *mp2)
{
guard(mutex)(&emp2_lock);
emp2 = mp2;
}
void sfh_interface_init(struct amd_mp2_dev *mp2)
{
mp2->mp2_ops = &amd_sfh_ops;
guard(mutex)(&emp2_lock);
emp2 = mp2;
}
static int amd_sfh_op_mode_info(u32 *op_mode)
{
struct sfh_op_mode mode;
bool present;
if (!op_mode)
return -EINVAL;
if (!emp2)
return -ENODEV;
present = emp2->sfh1_1_ops ? emp2->dev_en.is_sra_present
: (emp2->mp2_ver == MP2_VER_V2 &&
amd_sfh_op_idx_enabled(emp2));
if (!present)
return -ENODEV;
mode.val = readl(emp2->mmio + amd_get_c2p_val(emp2, 3));
dev_dbg(&emp2->pdev->dev, "op-mode: %s (mode=%u)\n",
mode.op_mode.mode == SFH_MODE_TABLET ? "tablet" : "laptop",
mode.op_mode.mode);
*op_mode = mode.op_mode.mode;
return 0;
}
static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement)
{
struct sfh_op_mode mode;
@ -160,6 +195,8 @@ static int amd_sfh_als_info(u32 *ambient_light)
int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op)
{
guard(mutex)(&emp2_lock);
if (sfh_info) {
switch (op) {
case MT_HPD:
@ -169,6 +206,8 @@ int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op)
case MT_SRA:
return amd_sfh_mode_info(&sfh_info->platform_type,
&sfh_info->laptop_placement);
case MT_OP_MODE:
return amd_sfh_op_mode_info(&sfh_info->op_mode);
}
}
return -EINVAL;

View File

@ -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

View File

@ -91,6 +91,9 @@ struct apple_sc_backlight {
struct hid_device *hdev;
};
/* T2 VHCI re-enumerates the internal keyboard across system resume. */
static int apple_backlight_resume_brightness = -1;
struct apple_backlight_config_report {
u8 report_id;
u8 version;
@ -368,6 +371,8 @@ static const struct apple_non_apple_keyboard non_apple_keyboards[] = {
{ "TH87" }, /* EPOMAKER TH87 BT mode */
{ "HFD Epomaker TH87" }, /* EPOMAKER TH87 USB mode */
{ "2.4G Wireless Receiver" }, /* EPOMAKER TH87 dongle */
{ "Thock TKL Wireless" }, /* ENDORFY THOCK TKL BT mode */
{ "USB Dongle" }, /* ENDORFY THOCK TKL dongle */
};
static bool apple_is_non_apple_keyboard(struct hid_device *hdev)
@ -825,6 +830,7 @@ static int apple_backlight_led_set(struct led_classdev *led_cdev,
static int apple_backlight_init(struct hid_device *hdev)
{
int ret;
int brightness;
struct apple_sc *asc = hid_get_drvdata(hdev);
struct apple_backlight_config_report *rep;
@ -857,16 +863,23 @@ static int apple_backlight_init(struct hid_device *hdev)
}
asc->backlight->hdev = hdev;
asc->backlight->cdev.name = "apple::kbd_backlight";
asc->backlight->cdev.name = ":white:" LED_FUNCTION_KBD_BACKLIGHT;
asc->backlight->cdev.max_brightness = rep->backlight_on_max;
asc->backlight->cdev.brightness_set_blocking = apple_backlight_led_set;
asc->backlight->cdev.flags = LED_CORE_SUSPENDRESUME;
/* VHCI re-enumeration restores the cached brightness in the next probe. */
ret = apple_backlight_set(hdev, 0, 0);
brightness = READ_ONCE(apple_backlight_resume_brightness);
if (brightness < 0)
brightness = LED_OFF;
else
brightness = min_t(int, brightness, rep->backlight_on_max);
ret = apple_backlight_set(hdev, brightness, 0);
if (ret < 0) {
hid_err(hdev, "backlight set request failed: %d\n", ret);
goto cleanup_and_exit;
}
asc->backlight->cdev.brightness = brightness;
ret = devm_led_classdev_register(&hdev->dev, &asc->backlight->cdev);
@ -999,6 +1012,9 @@ static void apple_remove(struct hid_device *hdev)
if (asc->quirks & APPLE_RDESC_BATTERY)
timer_delete_sync(&asc->battery_timer);
if (asc->backlight)
WRITE_ONCE(apple_backlight_resume_brightness,
asc->backlight->cdev.brightness);
hid_hw_stop(hdev);
}

View File

@ -175,7 +175,7 @@ static void appletb_inactivity_work(struct work_struct *work)
if (!kbd->has_dimmed) {
backlight_device_set_brightness(kbd->backlight_dev, 1);
kbd->has_dimmed = true;
mod_delayed_work(system_wq, &kbd->inactivity_work,
mod_delayed_work(system_dfl_wq, &kbd->inactivity_work,
secs_to_jiffies(appletb_tb_idle_timeout));
} else if (!kbd->has_turned_off) {
backlight_device_set_brightness(kbd->backlight_dev, 0);
@ -201,7 +201,7 @@ static void reset_inactivity_timer(struct appletb_kbd *kbd)
kbd->has_turned_off = false;
schedule_work(&kbd->restore_brightness_work);
}
mod_delayed_work(system_wq, &kbd->inactivity_work,
mod_delayed_work(system_dfl_wq, &kbd->inactivity_work,
secs_to_jiffies(appletb_tb_dim_timeout));
}
}
@ -423,7 +423,7 @@ static int appletb_kbd_probe(struct hid_device *hdev, const struct hid_device_id
INIT_DELAYED_WORK(&kbd->inactivity_work, appletb_inactivity_work);
INIT_WORK(&kbd->restore_brightness_work,
appletb_restore_brightness_work);
mod_delayed_work(system_wq, &kbd->inactivity_work,
mod_delayed_work(system_dfl_wq, &kbd->inactivity_work,
secs_to_jiffies(appletb_tb_dim_timeout));
}

View File

@ -99,6 +99,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
#define QUIRK_ROG_ALLY_XPAD BIT(13)
#define QUIRK_HID_FN_LOCK BIT(14)
#define QUIRK_FILTER_CAMERA_COMPANION BIT(15)
#define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \
QUIRK_NO_INIT_REPORTS | \
@ -109,11 +110,36 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define TRKID_SGN ((TRKID_MAX + 1) >> 1)
struct asus_kbd_leds {
struct asus_hid_listener listener;
enum asus_work_action_type {
FN_LOCK_SYNC,
BRIGHTNESS_SET,
WMI_FAN,
};
struct hid_raw_event_data {
u8 report_data[FEATURE_KBD_REPORT_SIZE];
size_t report_size;
};
struct asus_work_action {
struct list_head node;
enum asus_work_action_type type;
union {
/* Data for BRIGHTNESS_SET */
unsigned int brightness;
/* Data for FN_LOCK_SYNC */
bool fn_lock;
/* Data for WMI_FAN */
struct hid_raw_event_data fan_hid_data;
} data;
};
struct asus_worker {
struct hid_device *hdev;
struct work_struct work;
unsigned int brightness;
struct list_head actions;
spinlock_t lock;
bool removed;
};
@ -133,7 +159,8 @@ struct asus_drvdata {
struct hid_device *hdev;
struct input_dev *input;
struct input_dev *tp_kbd_input;
struct asus_kbd_leds *kbd_backlight;
struct asus_worker *worker;
unsigned int kbd_backlight_brightness;
const struct asus_touchpad_info *tp;
struct power_supply *battery;
struct power_supply_desc battery_desc;
@ -141,7 +168,7 @@ struct asus_drvdata {
int battery_stat;
bool battery_in_query;
unsigned long battery_next_query;
struct work_struct fn_lock_sync_work;
struct asus_hid_listener listener;
bool fn_lock;
};
@ -211,6 +238,29 @@ static const u8 asus_report_id_init[] = {
FEATURE_KBD_LED_REPORT_ID2
};
/*
* Send events to asus-wmi driver for handling special keys
*/
static int asus_wmi_send_event(struct asus_drvdata *drvdata, u8 code)
{
int err;
u32 retval;
err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS,
ASUS_WMI_METHODID_NOTIF, code, &retval);
if (err) {
pr_warn("Failed to notify asus-wmi: %d\n", err);
return err;
}
if (retval != 0) {
pr_warn("Failed to notify asus-wmi (retval): 0x%x\n", retval);
return -EIO;
}
return 0;
}
static void asus_report_contact_down(struct asus_drvdata *drvdat,
int toolType, u8 *data)
{
@ -331,25 +381,71 @@ static int asus_e1239t_event(struct asus_drvdata *drvdat, u8 *data, int size)
}
/*
* Send events to asus-wmi driver for handling special keys
* Used in atomic contexts to schedule work involving sleeps operations or
* asus-wmi interactions.
*
* Caller is responsible to store relevant data in the structure to carry out
* the required action.
*
* This function must be called while the spin lock protecting the workqueue
* is already being held.
*/
static int asus_wmi_send_event(struct asus_drvdata *drvdata, u8 code)
static void asus_worker_schedule(struct asus_worker *worker, struct asus_work_action *action)
{
int err;
u32 retval;
err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS,
ASUS_WMI_METHODID_NOTIF, code, &retval);
if (err) {
pr_warn("Failed to notify asus-wmi: %d\n", err);
return err;
if (worker->removed) {
kfree(action);
return;
}
if (retval != 0) {
pr_warn("Failed to notify asus-wmi (retval): 0x%x\n", retval);
return -EIO;
list_add_tail(&action->node, &worker->actions);
schedule_work(&worker->work);
}
static int asus_kbd_fn_lock_set(struct asus_drvdata *drvdata, bool enabled)
{
struct asus_work_action *action;
unsigned long flags;
action = kzalloc(sizeof(struct asus_work_action), GFP_ATOMIC);
if (!action)
return -ENOMEM;
drvdata->fn_lock = enabled;
action->type = FN_LOCK_SYNC;
action->data.fn_lock = drvdata->fn_lock;
INIT_LIST_HEAD(&action->node);
spin_lock_irqsave(&drvdata->worker->lock, flags);
asus_worker_schedule(drvdata->worker, action);
spin_unlock_irqrestore(&drvdata->worker->lock, flags);
return 0;
}
static int asus_kbd_wmi_fan_send(struct asus_drvdata *drvdata, u8 *report_data,
size_t report_size)
{
struct asus_work_action *action;
unsigned long flags;
if (report_size > FEATURE_KBD_REPORT_SIZE) {
hid_err(drvdata->hdev, "Invalid report size for fan event: %zu\n", report_size);
return -EINVAL;
}
action = kzalloc(sizeof(struct asus_work_action), GFP_NOWAIT);
if (!action)
return -ENOMEM;
action->type = WMI_FAN;
action->data.fan_hid_data.report_size = report_size;
memcpy(action->data.fan_hid_data.report_data, report_data, report_size);
INIT_LIST_HEAD(&action->node);
spin_lock_irqsave(&drvdata->worker->lock, flags);
asus_worker_schedule(drvdata->worker, action);
spin_unlock_irqrestore(&drvdata->worker->lock, flags);
return 0;
}
@ -357,6 +453,7 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
int ret;
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR &&
(usage->hid & HID_USAGE) != 0x00 &&
@ -375,8 +472,11 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
return !asus_hid_event(ASUS_EV_BRTTOGGLE);
case KEY_FN_ESC:
if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
drvdata->fn_lock = !drvdata->fn_lock;
schedule_work(&drvdata->fn_lock_sync_work);
ret = asus_kbd_fn_lock_set(drvdata, !drvdata->fn_lock);
if (ret) {
hid_err(hdev, "Error while toggling FN lock: %d\n", ret);
return ret;
}
}
break;
}
@ -389,6 +489,12 @@ static int asus_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
int ret;
if (size < 2) {
hid_dbg(hdev, "Unexpected keyboard report size %d\n", size);
return 0;
}
if (drvdata->battery && data[0] == BATTERY_REPORT_ID)
return asus_report_battery(drvdata, data, size);
@ -414,19 +520,13 @@ static int asus_raw_event(struct hid_device *hdev,
* pass to userspace so it can implement its own fan control.
*/
if (data[1] == ASUS_FAN_CTRL_KEY_CODE) {
int ret = asus_wmi_send_event(drvdata, ASUS_FAN_CTRL_KEY_CODE);
ret = asus_kbd_wmi_fan_send(drvdata, data, size);
if (ret == 0) {
/* Successfully handled by asus-wmi, block event */
/* if execution deferred successfully block event */
if (ret == 0)
return -1;
}
/*
* Warn if asus-wmi failed (but not if it's unavailable).
* Let the event reach userspace in all failure cases.
*/
if (ret != -ENODEV)
hid_warn(hdev, "Failed to notify asus-wmi: %d\n", ret);
return ret;
}
/*
@ -460,6 +560,17 @@ static int asus_raw_event(struct hid_device *hdev,
return -1;
}
/*
* The camera-toggle key reports its vendor usage (0x85) together with a
* companion state byte in the same array report, e.g. "5a 85 01" and
* "5a 85 10" for the two toggle positions. The 0x10 companion aliases the
* brightness-down vendor usage and would spuriously dim the panel, so drop
* the companion slots and leave only the camera usage for input mapping.
*/
if (drvdata->quirks & QUIRK_FILTER_CAMERA_COMPANION &&
report->id == FEATURE_KBD_REPORT_ID && size >= 3 && data[1] == 0x85)
memset(&data[2], 0, size - 2);
return 0;
}
@ -569,59 +680,157 @@ static int asus_kbd_disable_oobe(struct hid_device *hdev)
return 0;
}
static int asus_kbd_set_fn_lock(struct hid_device *hdev, bool enabled)
static void asus_kbd_set_fn_lock(struct hid_device *hdev, bool enabled)
{
u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xd0, 0x4e, !!enabled };
return asus_kbd_set_report(hdev, buf, sizeof(buf));
}
static void asus_sync_fn_lock(struct work_struct *work)
{
struct asus_drvdata *drvdata =
container_of(work, struct asus_drvdata, fn_lock_sync_work);
asus_kbd_set_fn_lock(drvdata->hdev, drvdata->fn_lock);
}
static void asus_schedule_work(struct asus_kbd_leds *led)
{
unsigned long flags;
spin_lock_irqsave(&led->lock, flags);
if (!led->removed)
schedule_work(&led->work);
spin_unlock_irqrestore(&led->lock, flags);
}
static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
int brightness)
{
struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
listener);
unsigned long flags;
spin_lock_irqsave(&led->lock, flags);
led->brightness = brightness;
spin_unlock_irqrestore(&led->lock, flags);
asus_schedule_work(led);
}
static void asus_kbd_backlight_work(struct work_struct *work)
{
struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
const u8 buf[FEATURE_KBD_REPORT_SIZE] = { FEATURE_KBD_REPORT_ID, 0xd0, 0x4e, !!enabled };
int ret;
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
if (ret < 0)
hid_err(hdev, "Asus failed to set fn lock: %d\n", ret);
}
static void asus_kbd_set_brightness(struct hid_device *hdev, u8 brightness)
{
const u8 buf[FEATURE_KBD_REPORT_SIZE] = {
FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, brightness
};
int ret;
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
if (ret < 0)
hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret);
}
static void asus_kbd_wmi_fan(struct hid_device *hdev, struct hid_raw_event_data *data)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
int ret;
ret = asus_wmi_send_event(drvdata, ASUS_FAN_CTRL_KEY_CODE);
/*
* Warn if asus-wmi failed (but not if it's unavailable).
* Let the event reach userspace in all failure cases.
*/
switch (ret) {
case -ENODEV:
break;
case 0:
return;
default:
hid_warn(hdev, "Failed to notify asus-wmi: %d\n", ret);
break;
}
/*
* Fallback: pass the raw event to the HID core; to avoid
* racing against the hid_report_raw_event() that generated
* this event use the same locking mechanism and wait for
* that function to terminate and signal the deferred execution
* before raising the stored event.
*/
down(&hdev->driver_input_lock);
hid_report_raw_event(hdev, HID_INPUT_REPORT,
data->report_data, data->report_size,
data->report_size, 1);
up(&hdev->driver_input_lock);
}
static void asus_kbd_backlight_set(struct asus_hid_listener *listener, int brightness)
{
struct asus_drvdata *drvdata = container_of(listener, struct asus_drvdata, listener);
struct asus_worker *worker = drvdata->worker;
struct asus_work_action *action;
unsigned long flags;
spin_lock_irqsave(&led->lock, flags);
buf[4] = led->brightness;
spin_unlock_irqrestore(&led->lock, flags);
drvdata->kbd_backlight_brightness = brightness;
ret = asus_kbd_set_report(led->hdev, buf, sizeof(buf));
if (ret < 0)
hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
action = kzalloc(sizeof(struct asus_work_action), GFP_NOWAIT);
if (!action)
return;
action->type = BRIGHTNESS_SET;
action->data.brightness = brightness;
INIT_LIST_HEAD(&action->node);
spin_lock_irqsave(&worker->lock, flags);
asus_worker_schedule(worker, action);
spin_unlock_irqrestore(&worker->lock, flags);
}
static void asus_work(struct work_struct *work)
{
struct asus_worker *worker = container_of(work, struct asus_worker, work);
struct asus_work_action *action = NULL;
unsigned long flags;
/* Save the action to be performed and clear the flag */
spin_lock_irqsave(&worker->lock, flags);
if (!list_empty(&worker->actions)) {
action = list_first_entry(&worker->actions,
struct asus_work_action, node);
list_del(&action->node);
}
spin_unlock_irqrestore(&worker->lock, flags);
if (!action)
return;
switch (action->type) {
case BRIGHTNESS_SET:
asus_kbd_set_brightness(worker->hdev, action->data.brightness);
break;
case FN_LOCK_SYNC:
asus_kbd_set_fn_lock(worker->hdev, action->data.fn_lock);
break;
case WMI_FAN:
asus_kbd_wmi_fan(worker->hdev, &action->data.fan_hid_data);
break;
default:
hid_err(worker->hdev, "Invalid action type: %d\n", action->type);
break;
}
kfree(action);
/* Re-schedule if there are more pending actions */
spin_lock_irqsave(&worker->lock, flags);
if (!list_empty(&worker->actions))
schedule_work(&worker->work);
spin_unlock_irqrestore(&worker->lock, flags);
}
static int asus_worker_create(struct hid_device *hdev, struct asus_drvdata *drvdata)
{
drvdata->worker = devm_kzalloc(&hdev->dev, sizeof(struct asus_worker), GFP_KERNEL);
if (!drvdata->worker)
return -ENOMEM;
drvdata->worker->removed = false;
drvdata->worker->hdev = hdev;
INIT_LIST_HEAD(&drvdata->worker->actions);
INIT_WORK(&drvdata->worker->work, asus_work);
spin_lock_init(&drvdata->worker->lock);
return 0;
}
static void asus_worker_stop(struct asus_worker *worker)
{
struct asus_work_action *action, *tmp;
unsigned long flags;
spin_lock_irqsave(&worker->lock, flags);
worker->removed = true;
list_for_each_entry_safe(action, tmp, &worker->actions, node) {
list_del(&action->node);
kfree(action);
}
spin_unlock_irqrestore(&worker->lock, flags);
cancel_work_sync(&worker->work);
}
/*
@ -644,7 +853,7 @@ static int mcu_parse_version_string(const u8 *response, size_t response_size)
dots++;
}
if (dots != 2 || p >= end || (p + 3) >= end)
if (dots != 2 || end - p < 3)
return -EINVAL;
memcpy(buf, p, 3);
@ -753,30 +962,18 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
return ret;
}
if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
if ((drvdata->quirks & QUIRK_ROG_ALLY_XPAD) && hid_is_usb(hdev)) {
intf = to_usb_interface(hdev->dev.parent);
udev = interface_to_usbdev(intf);
validate_mcu_fw_version(hdev,
le16_to_cpu(udev->descriptor.idProduct));
}
drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
sizeof(struct asus_kbd_leds),
GFP_KERNEL);
if (!drvdata->kbd_backlight)
return -ENOMEM;
drvdata->kbd_backlight->removed = false;
drvdata->kbd_backlight->brightness = 0;
drvdata->kbd_backlight->hdev = hdev;
drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
spin_lock_init(&drvdata->kbd_backlight->lock);
ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
drvdata->listener.brightness_set = asus_kbd_backlight_set;
ret = asus_hid_register_listener(&drvdata->listener);
if (ret < 0) {
/* No need to have this still around */
devm_kfree(&hdev->dev, drvdata->kbd_backlight);
hid_err(hdev, "Unable to register kbd brightness listener: %d\n", ret);
drvdata->listener.brightness_set = NULL;
}
return ret;
@ -998,11 +1195,9 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
drvdata->input = input;
if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
drvdata->fn_lock = true;
INIT_WORK(&drvdata->fn_lock_sync_work, asus_sync_fn_lock);
asus_kbd_set_fn_lock(hdev, true);
}
if ((drvdata->quirks & QUIRK_HID_FN_LOCK) &&
(asus_kbd_fn_lock_set(drvdata, true)))
hid_warn(hdev, "Error while setting FN lock to ON\n");
return 0;
}
@ -1045,6 +1240,8 @@ static int asus_input_mapping(struct hid_device *hdev,
case 0x6c: asus_map_key_clear(KEY_SLEEP); break;
case 0x7c: asus_map_key_clear(KEY_MICMUTE); break;
case 0x82: asus_map_key_clear(KEY_CAMERA); break;
case 0x85: asus_map_key_clear(KEY_CAMERA); break;
case 0x86: asus_map_key_clear(KEY_PROG1); break; /* MyASUS key */
case 0x88: asus_map_key_clear(KEY_RFKILL); break;
case 0xb5: asus_map_key_clear(KEY_CALC); break;
case 0xc4: asus_map_key_clear(KEY_KBDILLUMUP); break;
@ -1054,6 +1251,7 @@ static int asus_input_mapping(struct hid_device *hdev,
case 0x7e: asus_map_key_clear(KEY_EMOJI_PICKER); break;
case 0x8b: asus_map_key_clear(KEY_PROG1); break; /* ProArt Creator Hub key */
case 0x5f: asus_map_key_clear(KEY_PROG2); break; /* S-shaped programmable key */
case 0x6b: asus_map_key_clear(KEY_F21); break; /* ASUS touchpad toggle */
case 0x38: asus_map_key_clear(KEY_PROG1); break; /* ROG key */
case 0xba: asus_map_key_clear(KEY_PROG2); break; /* Fn+C ASUS Splendid */
@ -1165,20 +1363,16 @@ static int asus_start_multitouch(struct hid_device *hdev)
static int __maybe_unused asus_resume(struct hid_device *hdev)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
int ret = 0;
if (drvdata->kbd_backlight) {
const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4,
drvdata->kbd_backlight->brightness };
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
if (ret < 0) {
hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret);
goto asus_resume_err;
}
}
/*
* If we have a backlight listener registered, restore the previous state,
* in case of error do not fail: most models restore the backlight
* automatically, and the error is non-fatal.
*/
if (drvdata->listener.brightness_set)
asus_kbd_backlight_set(&drvdata->listener, drvdata->kbd_backlight_brightness);
asus_resume_err:
return ret;
return 0;
}
static int __maybe_unused asus_reset_resume(struct hid_device *hdev)
@ -1200,10 +1394,8 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
int ret;
drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
if (drvdata == NULL) {
hid_err(hdev, "Can't alloc Asus descriptor\n");
if (drvdata == NULL)
return -ENOMEM;
}
hid_set_drvdata(hdev, drvdata);
@ -1288,8 +1480,15 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
is_vendor = true;
}
ret = asus_worker_create(hdev, drvdata);
if (ret) {
hid_warn(hdev, "Failed to initialize worker: %d\n", ret);
return ret;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
asus_worker_stop(drvdata->worker);
hid_err(hdev, "Asus hw start failed: %d\n", ret);
return ret;
}
@ -1337,6 +1536,10 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
return 0;
err_stop_hw:
if (drvdata->listener.brightness_set)
asus_hid_unregister_listener(&drvdata->listener);
asus_worker_stop(drvdata->worker);
hid_hw_stop(hdev);
return ret;
}
@ -1344,21 +1547,11 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
static void asus_remove(struct hid_device *hdev)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
unsigned long flags;
if (drvdata->kbd_backlight) {
asus_hid_unregister_listener(&drvdata->kbd_backlight->listener);
spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags);
drvdata->kbd_backlight->removed = true;
spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags);
cancel_work_sync(&drvdata->kbd_backlight->work);
}
if (drvdata->quirks & QUIRK_HID_FN_LOCK)
cancel_work_sync(&drvdata->fn_lock_sync_work);
if (drvdata->listener.brightness_set)
asus_hid_unregister_listener(&drvdata->listener);
asus_worker_stop(drvdata->worker);
hid_hw_stop(hdev);
}
@ -1477,6 +1670,9 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
static const struct hid_device_id asus_devices[] = {
{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD), I2C_KEYBOARD_QUIRKS},
{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_I2C_ZENBOOK_KEYBOARD),
I2C_KEYBOARD_QUIRKS | QUIRK_FILTER_CAMERA_COMPANION },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD), I2C_TOUCHPAD_QUIRKS },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
@ -1494,6 +1690,9 @@ static const struct hid_device_id asus_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },

View File

@ -59,30 +59,24 @@ static int axff_play(struct input_dev *dev, void *data, struct ff_effect *effect
return 0;
}
static int axff_init(struct hid_device *hid)
static int ax_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct axff_device *axff;
struct hid_report *report;
struct hid_input *hidinput;
struct list_head *report_list =&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct input_dev *dev;
struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct input_dev *dev = hidinput->input;
int field_count = 0;
int i, j;
int error;
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
return -ENODEV;
}
hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
dev = hidinput->input;
if (!list_is_first(&hidinput->list, &hid->inputs))
return 0;
if (list_empty(report_list)) {
report = list_first_entry_or_null(report_list, struct hid_report, list);
if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
report = list_first_entry(report_list, struct hid_report, list);
for (i = 0; i < report->maxfield; i++) {
for (j = 0; j < report->field[i]->report_count; j++) {
report->field[i]->value[j] = 0x00;
@ -100,13 +94,13 @@ static int axff_init(struct hid_device *hid)
if (!axff)
return -ENOMEM;
axff->report = report;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, axff, axff_play);
if (error)
goto err_free_mem;
axff->report = report;
hid_hw_request(hid, axff->report, HID_REQ_SET_REPORT);
hid_info(hid, "Force Feedback for ACRUX game controllers by Sergei Kolzun <x0r@dv-life.ru>\n");
@ -118,7 +112,8 @@ static int axff_init(struct hid_device *hid)
return error;
}
#else
static inline int axff_init(struct hid_device *hid)
static inline int ax_input_configured(struct hid_device *hid,
struct hid_input *hidinput)
{
return 0;
}
@ -136,23 +131,11 @@ static int ax_probe(struct hid_device *hdev, const struct hid_device_id *id)
return error;
}
error = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
error = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (error) {
hid_err(hdev, "hw start failed\n");
return error;
}
error = axff_init(hdev);
if (error) {
/*
* Do not fail device initialization completely as device
* may still be partially operable, just warn.
*/
hid_warn(hdev,
"Failed to enable force feedback support, error: %d\n",
error);
}
/*
* We need to start polling device right away, otherwise
* it will go into a coma.
@ -185,6 +168,7 @@ static struct hid_driver ax_driver = {
.id_table = ax_devices,
.probe = ax_probe,
.remove = ax_remove,
.input_configured = ax_input_configured,
};
module_hid_driver(ax_driver);

View File

@ -52,31 +52,24 @@ static int hid_betopff_play(struct input_dev *dev, void *data,
return 0;
}
static int betopff_init(struct hid_device *hid)
static int betop_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct betopff_device *betopff;
struct hid_report *report;
struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct input_dev *dev;
struct input_dev *dev = hidinput->input;
int error;
int i, j;
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
return -ENODEV;
}
if (!list_is_first(&hidinput->list, &hid->inputs))
return 0;
hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
dev = hidinput->input;
if (list_empty(report_list)) {
report = list_first_entry_or_null(report_list, struct hid_report, list);
if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
report = list_first_entry(report_list, struct hid_report, list);
/*
* Actually there are 4 fields for 4 Bytes as below:
* -----------------------------------------
@ -104,6 +97,7 @@ static int betopff_init(struct hid_device *hid)
if (!betopff)
return -ENOMEM;
betopff->report = report;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, betopff, hid_betopff_play);
@ -112,7 +106,6 @@ static int betopff_init(struct hid_device *hid)
return error;
}
betopff->report = report;
hid_hw_request(hid, betopff->report, HID_REQ_SET_REPORT);
hid_info(hid, "Force feedback for betop devices by huangbo <huangbobupt@163.com>\n");
@ -130,20 +123,15 @@ static int betop_probe(struct hid_device *hdev, const struct hid_device_id *id)
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
return ret;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
return ret;
}
betopff_init(hdev);
return 0;
err:
return ret;
}
static const struct hid_device_id betop_devices[] = {
@ -159,6 +147,7 @@ static struct hid_driver betop_driver = {
.name = "betop",
.id_table = betop_devices,
.probe = betop_probe,
.input_configured = betop_input_configured,
};
module_hid_driver(betop_driver);

View File

@ -366,58 +366,29 @@ static void bigben_remove(struct hid_device *hid)
hid_hw_stop(hid);
}
static int bigben_probe(struct hid_device *hid,
const struct hid_device_id *id)
static int bigben_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct bigben_device *bigben;
struct hid_input *hidinput;
struct bigben_device *bigben = hid_get_drvdata(hid);
struct input_dev *input_dev = hidinput->input;
struct led_classdev *led;
char *name;
size_t name_sz;
int n, error;
bigben = devm_kzalloc(&hid->dev, sizeof(*bigben), GFP_KERNEL);
if (!bigben)
return -ENOMEM;
hid_set_drvdata(hid, bigben);
bigben->hid = hid;
bigben->removed = false;
error = hid_parse(hid);
if (error) {
hid_err(hid, "parse failed\n");
return error;
}
error = hid_hw_start(hid, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (error) {
hid_err(hid, "hw start failed\n");
return error;
}
if (!list_is_first(&hidinput->list, &hid->inputs))
return 0;
bigben->report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 8);
if (!bigben->report) {
hid_err(hid, "no output report found\n");
error = -ENODEV;
goto error_hw_stop;
return -ENODEV;
}
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
error = -ENODEV;
goto error_hw_stop;
}
set_bit(FF_RUMBLE, input_dev->ffbit);
hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
set_bit(FF_RUMBLE, hidinput->input->ffbit);
INIT_WORK(&bigben->worker, bigben_worker);
spin_lock_init(&bigben->lock);
error = input_ff_create_memless(hidinput->input, NULL,
hid_bigben_play_effect);
error = input_ff_create_memless(input_dev, NULL, hid_bigben_play_effect);
if (error)
goto error_hw_stop;
return error;
name_sz = strlen(dev_name(&hid->dev)) + strlen(":red:bigben#") + 1;
@ -427,10 +398,9 @@ static int bigben_probe(struct hid_device *hid,
sizeof(struct led_classdev) + name_sz,
GFP_KERNEL
);
if (!led) {
error = -ENOMEM;
goto error_hw_stop;
}
if (!led)
return -ENOMEM;
name = (void *)(&led[1]);
snprintf(name, name_sz,
"%s:red:bigben%d",
@ -444,7 +414,7 @@ static int bigben_probe(struct hid_device *hid,
bigben->leds[n] = led;
error = devm_led_classdev_register(&hid->dev, led);
if (error)
goto error_hw_stop;
return error;
}
/* initial state: LED1 is on, no rumble effect */
@ -458,10 +428,36 @@ static int bigben_probe(struct hid_device *hid,
hid_info(hid, "LED and force feedback support for BigBen gamepad\n");
return 0;
}
error_hw_stop:
hid_hw_stop(hid);
return error;
static int bigben_probe(struct hid_device *hid, const struct hid_device_id *id)
{
struct bigben_device *bigben;
int error;
bigben = devm_kzalloc(&hid->dev, sizeof(*bigben), GFP_KERNEL);
if (!bigben)
return -ENOMEM;
hid_set_drvdata(hid, bigben);
bigben->hid = hid;
bigben->removed = false;
INIT_WORK(&bigben->worker, bigben_worker);
spin_lock_init(&bigben->lock);
error = hid_parse(hid);
if (error) {
hid_err(hid, "parse failed\n");
return error;
}
error = hid_hw_start(hid, HID_CONNECT_DEFAULT);
if (error) {
hid_err(hid, "hw start failed\n");
return error;
}
return 0;
}
static const __u8 *bigben_report_fixup(struct hid_device *hid, __u8 *rdesc,
@ -487,6 +483,7 @@ static struct hid_driver bigben_driver = {
.probe = bigben_probe,
.report_fixup = bigben_report_fixup,
.remove = bigben_remove,
.input_configured = bigben_input_configured,
};
module_hid_driver(bigben_driver);

View File

@ -379,6 +379,9 @@ static int hid_add_field(struct hid_parser *parser, unsigned report_type, unsign
static u32 item_udata(struct hid_item *item)
{
if (item->format != HID_ITEM_FORMAT_SHORT)
return 0;
switch (item->size) {
case 1: return item->data.u8;
case 2: return item->data.u16;
@ -389,6 +392,9 @@ static u32 item_udata(struct hid_item *item)
static s32 item_sdata(struct hid_item *item)
{
if (item->format != HID_ITEM_FORMAT_SHORT)
return 0;
switch (item->size) {
case 1: return item->data.s8;
case 2: return item->data.s16;
@ -1933,13 +1939,14 @@ int hid_set_field(struct hid_field *field, unsigned offset, __s32 value)
size = field->report_size;
hid_dump_input(field->report->device, field->usage + offset, value);
if (offset >= field->report_count) {
hid_err(field->report->device, "offset (%d) exceeds report_count (%d)\n",
offset, field->report_count);
return -1;
}
hid_dump_input(field->report->device, field->usage + offset, value);
if (field->logical_minimum < 0) {
if (value != snto32(s32ton(value, size), size)) {
hid_err(field->report->device, "value %d is out of range\n", value);
@ -2312,8 +2319,8 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
if (hid_hiddev(hdev))
connect_mask |= HID_CONNECT_HIDDEV_FORCE;
if ((connect_mask & HID_CONNECT_HIDINPUT) && !hidinput_connect(hdev,
connect_mask & HID_CONNECT_HIDINPUT_FORCE))
if ((connect_mask & HID_CONNECT_HIDINPUT) &&
!hidinput_connect(hdev, connect_mask))
hdev->claimed |= HID_CLAIMED_INPUT;
if ((connect_mask & HID_CONNECT_HIDDEV) && hdev->hiddev_connect &&
@ -2335,10 +2342,6 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
hid_process_ordering(hdev);
if ((hdev->claimed & HID_CLAIMED_INPUT) &&
(connect_mask & HID_CONNECT_FF) && hdev->ff_init)
hdev->ff_init(hdev);
len = 0;
if (hdev->claimed & HID_CLAIMED_INPUT)
len += sprintf(buf + len, "input");
@ -2447,9 +2450,16 @@ EXPORT_SYMBOL_GPL(hid_hw_start);
*
* This is usually called from remove function or from probe when something
* failed and hid_hw_start was called already.
*
* If the caller enabled HID input via hid_device_io_start() and is unwinding
* without an explicit hid_device_io_stop(), quiesce input first so that
* in-flight reports cannot reach handlers (e.g. hidraw_report_event) whose
* backing objects hid_disconnect() is about to free.
*/
void hid_hw_stop(struct hid_device *hdev)
{
if (hdev->io_started)
hid_device_io_stop(hdev);
hid_disconnect(hdev);
hdev->ll_driver->stop(hdev);
}
@ -2842,6 +2852,8 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
*/
if (ret) {
if (hdev->io_started)
hid_device_io_stop(hdev);
devres_release_group(&hdev->dev, hdev->devres_group_id);
hid_close_report(hdev);
hdev->driver = NULL;
@ -2907,6 +2919,45 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *a,
}
static DEVICE_ATTR_RO(modalias);
/*
* Expose this as bustype instead of bus as
* that's the name the input subsystem uses
*/
static ssize_t bustype_show(struct device *dev, struct device_attribute *a,
char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
return sysfs_emit(buf, "%04x\n", hdev->bus);
}
static DEVICE_ATTR_RO(bustype);
#define HID_DEV_ID_ATTR(name) \
static ssize_t name##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct hid_device *hdev = to_hid_device(dev); \
\
return sysfs_emit(buf, "%04x\n", hdev->name); \
} \
static DEVICE_ATTR_RO(name)
HID_DEV_ID_ATTR(vendor);
HID_DEV_ID_ATTR(product);
HID_DEV_ID_ATTR(version);
static struct attribute *hid_dev_id_attrs[] = {
&dev_attr_bustype.attr,
&dev_attr_vendor.attr,
&dev_attr_product.attr,
&dev_attr_version.attr,
NULL
};
static const struct attribute_group hid_dev_id_attr_group = {
.name = "id",
.attrs = hid_dev_id_attrs,
};
static struct attribute *hid_dev_attrs[] = {
&dev_attr_modalias.attr,
NULL,
@ -2919,7 +2970,11 @@ static const struct attribute_group hid_dev_group = {
.attrs = hid_dev_attrs,
.bin_attrs = hid_dev_bin_attrs,
};
__ATTRIBUTE_GROUPS(hid_dev);
static const struct attribute_group *hid_dev_groups[] = {
&hid_dev_group,
&hid_dev_id_attr_group,
NULL
};
static int hid_uevent(const struct device *dev, struct kobj_uevent_env *env)
{

View File

@ -92,6 +92,9 @@
#define CORSAIR_VOID_STATUS_REPORT_ID 0x64
#define CORSAIR_VOID_FIRMWARE_REPORT_ID 0x66
#define CORSAIR_VOID_STATUS_REPORT_SIZE 5
#define CORSAIR_VOID_FIRMWARE_REPORT_SIZE 5
#define CORSAIR_VOID_USB_SIDETONE_REQUEST 0x1
#define CORSAIR_VOID_USB_SIDETONE_REQUEST_TYPE 0x21
#define CORSAIR_VOID_USB_SIDETONE_VALUE 0x200
@ -742,6 +745,13 @@ static int corsair_void_raw_event(struct hid_device *hid_dev,
/* Description of packets are documented at the top of this file */
if (hid_report->id == CORSAIR_VOID_STATUS_REPORT_ID) {
if (size < CORSAIR_VOID_STATUS_REPORT_SIZE) {
hid_warn_ratelimited(hid_dev,
"unexpected status report of size %d",
size);
return 1;
}
drvdata->mic_up = FIELD_GET(CORSAIR_VOID_MIC_MASK, data[2]);
drvdata->connected = (data[3] == CORSAIR_VOID_WIRELESS_CONNECTED) ||
drvdata->is_wired;
@ -750,6 +760,13 @@ static int corsair_void_raw_event(struct hid_device *hid_dev,
FIELD_GET(CORSAIR_VOID_CAPACITY_MASK, data[2]),
data[3], data[4]);
} else if (hid_report->id == CORSAIR_VOID_FIRMWARE_REPORT_ID) {
if (size < CORSAIR_VOID_FIRMWARE_REPORT_SIZE) {
hid_warn_ratelimited(hid_dev,
"unexpected firmware report of size %d",
size);
return 1;
}
drvdata->fw_receiver_major = data[1];
drvdata->fw_receiver_minor = data[2];
drvdata->fw_headset_major = data[3];

View File

@ -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,

View File

@ -71,29 +71,26 @@ static int drff_play(struct input_dev *dev, void *data,
return 0;
}
static int drff_init(struct hid_device *hid)
static int dr_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct drff_device *drff;
struct hid_report *report;
struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct input_dev *dev;
struct input_dev *dev = hidinput->input;
int error;
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
return -ENODEV;
}
hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
dev = hidinput->input;
if (!list_is_first(&hidinput->list, &hid->inputs))
return 0;
if (list_empty(report_list)) {
if (hid->product != 0x0006)
return 0;
report = list_first_entry_or_null(report_list, struct hid_report, list);
if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
report = list_first_entry(report_list, struct hid_report, list);
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
@ -108,6 +105,7 @@ static int drff_init(struct hid_device *hid)
if (!drff)
return -ENOMEM;
drff->report = report;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, drff, drff_play);
@ -116,7 +114,6 @@ static int drff_init(struct hid_device *hid)
return error;
}
drff->report = report;
drff->report->field[0]->value[0] = 0xf3;
drff->report->field[0]->value[1] = 0x00;
drff->report->field[0]->value[2] = 0x00;
@ -132,7 +129,8 @@ static int drff_init(struct hid_device *hid)
return 0;
}
#else
static inline int drff_init(struct hid_device *hid)
static inline int dr_input_configured(struct hid_device *hid,
struct hid_input *hidinput)
{
return 0;
}
@ -266,43 +264,9 @@ static int dr_input_mapping(struct hid_device *hdev, struct hid_input *hi,
return 0;
}
static int dr_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
dev_dbg(&hdev->dev, "DragonRise Inc. HID hardware probe...");
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
}
switch (hdev->product) {
case 0x0006:
ret = drff_init(hdev);
if (ret) {
dev_err(&hdev->dev, "force feedback init failed\n");
hid_hw_stop(hdev);
goto err;
}
break;
}
return 0;
err:
return ret;
}
static const struct hid_device_id dr_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006), },
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011), },
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006), },
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011), },
{ }
};
MODULE_DEVICE_TABLE(hid, dr_devices);
@ -311,8 +275,8 @@ static struct hid_driver dr_driver = {
.name = "dragonrise",
.id_table = dr_devices,
.report_fixup = dr_report_fixup,
.probe = dr_probe,
.input_mapping = dr_input_mapping,
.input_configured = dr_input_configured,
};
module_hid_driver(dr_driver);

View File

@ -43,29 +43,23 @@ static int emsff_play(struct input_dev *dev, void *data,
return 0;
}
static int emsff_init(struct hid_device *hid)
static int ems_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct emsff_device *emsff;
struct hid_report *report;
struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct input_dev *dev;
struct input_dev *dev = hidinput->input;
int error;
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
return -ENODEV;
}
hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
dev = hidinput->input;
if (!list_is_first(&hidinput->list, &hid->inputs))
return 0;
if (list_empty(report_list)) {
report = list_first_entry_or_null(report_list, struct hid_report, list);
if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
report = list_first_entry(report_list, struct hid_report, list);
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
@ -80,6 +74,7 @@ static int emsff_init(struct hid_device *hid)
if (!emsff)
return -ENOMEM;
emsff->report = report;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, emsff, emsff_play);
@ -88,7 +83,6 @@ static int emsff_init(struct hid_device *hid)
return error;
}
emsff->report = report;
emsff->report->field[0]->value[0] = 0x01;
emsff->report->field[0]->value[1] = 0x00;
emsff->report->field[0]->value[2] = 0x00;
@ -103,34 +97,6 @@ static int emsff_init(struct hid_device *hid)
return 0;
}
static int ems_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
}
ret = emsff_init(hdev);
if (ret) {
dev_err(&hdev->dev, "force feedback init failed\n");
hid_hw_stop(hdev);
goto err;
}
return 0;
err:
return ret;
}
static const struct hid_device_id ems_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II) },
{ }
@ -140,7 +106,7 @@ MODULE_DEVICE_TABLE(hid, ems_devices);
static struct hid_driver ems_driver = {
.name = "hkems",
.id_table = ems_devices,
.probe = ems_probe,
.input_configured = ems_input_configured,
};
module_hid_driver(ems_driver);

View File

@ -240,6 +240,8 @@ struct ft260_device {
struct mutex lock;
u8 write_buf[FT260_REPORT_MAX_LENGTH];
unsigned long need_wakeup_at;
/* Protects read_buf, read_idx and read_len against ft260_raw_event() */
spinlock_t read_lock;
u8 *read_buf;
u16 read_idx;
u16 read_len;
@ -501,16 +503,25 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
int timeout, ret = 0;
struct ft260_i2c_read_request_report rep;
struct hid_device *hdev = dev->hdev;
unsigned long irqflags;
u8 bus_busy = 0;
/*
* STOP terminates the last chunk; clear means hold the bus so a
* follow-up call continues the same I2C transaction.
*/
bool want_stop = !!(flag & FT260_FLAG_STOP);
if ((flag & FT260_FLAG_START_REPEATED) == FT260_FLAG_START_REPEATED)
flag = FT260_FLAG_START_REPEATED;
else
else if (flag & FT260_FLAG_START)
flag = FT260_FLAG_START;
else
flag = 0; /* no fresh START - continue current transaction */
do {
if (len <= rd_data_max) {
rd_len = len;
flag |= FT260_FLAG_STOP;
if (want_stop)
flag |= FT260_FLAG_STOP;
} else {
rd_len = rd_data_max;
}
@ -526,9 +537,11 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
reinit_completion(&dev->wait);
spin_lock_irqsave(&dev->read_lock, irqflags);
dev->read_idx = 0;
dev->read_buf = data;
dev->read_len = rd_len;
spin_unlock_irqrestore(&dev->read_lock, irqflags);
ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
if (ret < 0) {
@ -543,7 +556,9 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
goto ft260_i2c_read_exit;
}
spin_lock_irqsave(&dev->read_lock, irqflags);
dev->read_buf = NULL;
spin_unlock_irqrestore(&dev->read_lock, irqflags);
if (flag & FT260_FLAG_STOP)
bus_busy = FT260_I2C_STATUS_BUS_BUSY;
@ -562,7 +577,9 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
} while (len > 0);
ft260_i2c_read_exit:
spin_lock_irqsave(&dev->read_lock, irqflags);
dev->read_buf = NULL;
spin_unlock_irqrestore(&dev->read_lock, irqflags);
return ret;
}
@ -708,14 +725,41 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
break;
case I2C_SMBUS_BLOCK_DATA:
if (read_write == I2C_SMBUS_READ) {
u8 count = 0;
/*
* SMBus 2.0 section 6.5.7 block read in one I2C
* transaction:
*
* S Addr+Wr A Reg A Sr Addr+Rd A Count A Data... P
*
* The count is read separately and validated
* before sizing the data read so a misbehaving
* slave cannot drive a write past data->block[].
*/
ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
FT260_FLAG_START);
if (ret)
goto smbus_exit;
ret = ft260_i2c_read(dev, addr, data->block,
data->block[0] + 1,
FT260_FLAG_START_STOP_REPEATED);
ret = ft260_i2c_read(dev, addr, &count, 1,
FT260_FLAG_START_REPEATED);
if (ret)
goto smbus_exit;
if (count == 0 || count > I2C_SMBUS_BLOCK_MAX) {
hid_warn(hdev,
"smbus block read: invalid count %u from slave 0x%02x\n",
count, addr);
ft260_i2c_reset(hdev);
ret = -EPROTO;
goto smbus_exit;
}
data->block[0] = count;
ret = ft260_i2c_read(dev, addr, data->block + 1,
count, FT260_FLAG_STOP);
} else {
ret = ft260_smbus_write(dev, addr, cmd, data->block,
data->block[0] + 1,
@ -1018,6 +1062,7 @@ static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
"FT260 usb-i2c bridge");
mutex_init(&dev->lock);
spin_lock_init(&dev->read_lock);
init_completion(&dev->wait);
ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY);
@ -1067,6 +1112,7 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
{
struct ft260_device *dev = hid_get_drvdata(hdev);
struct ft260_i2c_input_report *xfer = (void *)data;
unsigned long irqflags;
if (size < offsetof(struct ft260_i2c_input_report, data)) {
hid_err(hdev, "short report %d\n", size);
@ -1075,6 +1121,8 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
if (xfer->report >= FT260_I2C_REPORT_MIN &&
xfer->report <= FT260_I2C_REPORT_MAX) {
bool complete_read;
ft260_dbg("i2c resp: rep %#02x len %d size %d\n",
xfer->report, xfer->length, size);
@ -1085,8 +1133,15 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
return -1;
}
/*
* Hold read_lock so a timed-out ft260_i2c_read() cannot
* clear read_buf between the NULL check and the memcpy.
*/
spin_lock_irqsave(&dev->read_lock, irqflags);
if ((dev->read_buf == NULL) ||
(xfer->length > dev->read_len - dev->read_idx)) {
spin_unlock_irqrestore(&dev->read_lock, irqflags);
hid_err(hdev, "unexpected report %#02x, length %d\n",
xfer->report, xfer->length);
return -1;
@ -1095,8 +1150,11 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
memcpy(&dev->read_buf[dev->read_idx], &xfer->data,
xfer->length);
dev->read_idx += xfer->length;
complete_read = dev->read_idx == dev->read_len;
if (dev->read_idx == dev->read_len)
spin_unlock_irqrestore(&dev->read_lock, irqflags);
if (complete_read)
complete(&dev->wait);
} else {

View File

@ -60,32 +60,23 @@ static int hid_gaff_play(struct input_dev *dev, void *data,
return 0;
}
static int gaff_init(struct hid_device *hid)
static int gaff_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct gaff_device *gaff;
struct hid_report *report;
struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct list_head *report_ptr = report_list;
struct input_dev *dev;
struct input_dev *dev = hidinput->input;
int error;
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
return -ENODEV;
}
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
dev = hidinput->input;
if (!list_is_first(&hidinput->list, &hid->inputs))
return 0;
if (list_empty(report_list)) {
report = list_first_entry_or_null(report_list, struct hid_report, list);
if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
report_ptr = report_ptr->next;
report = list_entry(report_ptr, struct hid_report, list);
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
@ -100,6 +91,7 @@ static int gaff_init(struct hid_device *hid)
if (!gaff)
return -ENOMEM;
gaff->report = report;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, gaff, hid_gaff_play);
@ -108,7 +100,6 @@ static int gaff_init(struct hid_device *hid)
return error;
}
gaff->report = report;
gaff->report->field[0]->value[0] = 0x51;
gaff->report->field[0]->value[1] = 0x00;
gaff->report->field[0]->value[2] = 0x00;
@ -125,37 +116,13 @@ static int gaff_init(struct hid_device *hid)
return 0;
}
#else
static inline int gaff_init(struct hid_device *hdev)
static inline int gaff_input_configured(struct hid_device *hdev,
struct hid_input *hidinput)
{
return 0;
}
#endif
static int ga_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
dev_dbg(&hdev->dev, "Greenasia HID hardware probe...");
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
}
gaff_init(hdev);
return 0;
err:
return ret;
}
static const struct hid_device_id ga_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, 0x0012), },
{ }
@ -165,7 +132,7 @@ MODULE_DEVICE_TABLE(hid, ga_devices);
static struct hid_driver ga_driver = {
.name = "greenasia",
.id_table = ga_devices,
.probe = ga_probe,
.input_configured = gaff_input_configured,
};
module_hid_driver(ga_driver);

View File

@ -722,11 +722,8 @@ static int goodix_spi_probe(struct spi_device *spi)
error = devm_request_threaded_irq(&ts->spi->dev, ts->spi->irq,
NULL, goodix_hid_irq, IRQF_ONESHOT,
"goodix_spi_hid", ts);
if (error) {
dev_err(ts->dev, "could not register interrupt, irq = %d, %d",
ts->spi->irq, error);
if (error)
goto err_destroy_hid;
}
return 0;

View File

@ -17,10 +17,7 @@
struct stadiaff_device {
struct hid_device *hid;
struct hid_report *report;
spinlock_t lock;
bool removed;
uint16_t strong_magnitude;
uint16_t weak_magnitude;
u32 magnitudes;
struct work_struct work;
};
@ -29,12 +26,10 @@ static void stadiaff_work(struct work_struct *work)
struct stadiaff_device *stadiaff =
container_of(work, struct stadiaff_device, work);
struct hid_field *rumble_field = stadiaff->report->field[0];
unsigned long flags;
u32 mags = READ_ONCE(stadiaff->magnitudes);
spin_lock_irqsave(&stadiaff->lock, flags);
rumble_field->value[0] = stadiaff->strong_magnitude;
rumble_field->value[1] = stadiaff->weak_magnitude;
spin_unlock_irqrestore(&stadiaff->lock, flags);
rumble_field->value[0] = mags & 0xffff;
rumble_field->value[1] = (mags >> 16) & 0xffff;
hid_hw_request(stadiaff->hid, stadiaff->report, HID_REQ_SET_REPORT);
}
@ -44,33 +39,50 @@ static int stadiaff_play(struct input_dev *dev, void *data,
{
struct hid_device *hid = input_get_drvdata(dev);
struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
unsigned long flags;
u32 mags = (u32)effect->u.rumble.strong_magnitude |
((u32)effect->u.rumble.weak_magnitude << 16);
spin_lock_irqsave(&stadiaff->lock, flags);
if (!stadiaff->removed) {
stadiaff->strong_magnitude = effect->u.rumble.strong_magnitude;
stadiaff->weak_magnitude = effect->u.rumble.weak_magnitude;
schedule_work(&stadiaff->work);
}
spin_unlock_irqrestore(&stadiaff->lock, flags);
WRITE_ONCE(stadiaff->magnitudes, mags);
schedule_work(&stadiaff->work);
return 0;
}
static int stadiaff_init(struct hid_device *hid)
static int stadia_input_open(struct input_dev *dev)
{
struct hid_device *hid = input_get_drvdata(dev);
struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
int error;
error = hid_hw_open(hid);
if (error)
return error;
enable_work(&stadiaff->work);
return 0;
}
static void stadia_input_close(struct input_dev *dev)
{
struct hid_device *hid = input_get_drvdata(dev);
struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
WRITE_ONCE(stadiaff->magnitudes, 0);
stadiaff_work(&stadiaff->work);
disable_work_sync(&stadiaff->work);
hid_hw_close(hid);
}
static int stadia_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct stadiaff_device *stadiaff;
struct hid_report *report;
struct hid_input *hidinput;
struct input_dev *dev;
struct input_dev *dev = hidinput->input;
int error;
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
return -ENODEV;
}
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
dev = hidinput->input;
if (!list_is_first(&hidinput->list, &hid->inputs))
return 0;
report = hid_validate_values(hid, HID_OUTPUT_REPORT,
STADIA_FF_REPORT_ID, 0, 2);
@ -90,56 +102,19 @@ static int stadiaff_init(struct hid_device *hid)
if (error)
return error;
stadiaff->removed = false;
stadiaff->hid = hid;
stadiaff->report = report;
INIT_WORK(&stadiaff->work, stadiaff_work);
spin_lock_init(&stadiaff->lock);
disable_work_sync(&stadiaff->work);
dev->open = stadia_input_open;
dev->close = stadia_input_close;
hid_info(hid, "Force Feedback for Google Stadia controller\n");
return 0;
}
static int stadia_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
return ret;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
return ret;
}
ret = stadiaff_init(hdev);
if (ret) {
hid_err(hdev, "force feedback init failed\n");
hid_hw_stop(hdev);
return ret;
}
return 0;
}
static void stadia_remove(struct hid_device *hid)
{
struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
unsigned long flags;
spin_lock_irqsave(&stadiaff->lock, flags);
stadiaff->removed = true;
spin_unlock_irqrestore(&stadiaff->lock, flags);
cancel_work_sync(&stadiaff->work);
hid_hw_stop(hid);
}
static const struct hid_device_id stadia_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) },
@ -150,8 +125,7 @@ MODULE_DEVICE_TABLE(hid, stadia_devices);
static struct hid_driver stadia_driver = {
.name = "stadia",
.id_table = stadia_devices,
.probe = stadia_probe,
.remove = stadia_remove,
.input_configured = stadia_input_configured,
};
module_hid_driver(stadia_driver);

View File

@ -82,16 +82,24 @@ int hid_haptic_input_configured(struct hid_device *hdev,
struct hid_haptic_device *haptic,
struct hid_input *hi)
{
int error;
if (hi->application == HID_DG_TOUCHPAD) {
if (haptic->auto_trigger_report &&
haptic->manual_trigger_report) {
__set_bit(INPUT_PROP_PRESSUREPAD, hi->input->propbit);
return 1;
}
if (hi->application != HID_DG_TOUCHPAD)
return -1;
if (!haptic->auto_trigger_report || !haptic->manual_trigger_report)
return 0;
__set_bit(INPUT_PROP_PRESSUREPAD, hi->input->propbit);
error = hid_haptic_init(hdev, haptic, hi->input);
if (error) {
dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
hdev->name);
return 0;
}
return -1;
return 1;
}
EXPORT_SYMBOL_GPL(hid_haptic_input_configured);
@ -187,7 +195,7 @@ static void fill_effect_buf(struct hid_haptic_device *haptic,
value = waveform_ordinal;
break;
default:
break;
continue;
}
field->value[j] = value;
@ -401,11 +409,9 @@ static void hid_haptic_destroy(struct ff_device *ff)
}
int hid_haptic_init(struct hid_device *hdev,
struct hid_haptic_device **haptic_ptr)
struct hid_haptic_device *haptic,
struct input_dev *dev)
{
struct hid_haptic_device *haptic = *haptic_ptr;
struct input_dev *dev = NULL;
struct hid_input *hidinput;
struct ff_device *ff;
int ret = 0, r;
struct ff_haptic_effect stop_effect = {
@ -447,19 +453,6 @@ int hid_haptic_init(struct hid_device *hdev,
for (r = 0; r < haptic->auto_trigger_report->maxfield; r++)
parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]);
list_for_each_entry(hidinput, &hdev->inputs, list) {
if (hidinput->application == HID_DG_TOUCHPAD) {
dev = hidinput->input;
break;
}
}
if (!dev) {
dev_err(&hdev->dev, "Failed to find the input device\n");
ret = -ENODEV;
goto duration_map;
}
haptic->input_dev = dev;
haptic->manual_trigger_report_len =
hid_report_len(haptic->manual_trigger_report);
@ -535,10 +528,6 @@ int hid_haptic_init(struct hid_device *hdev,
input_free:
input_ff_destroy(dev);
/* Do not let double free happen, input_ff_destroy will call
* hid_haptic_destroy.
*/
*haptic_ptr = NULL;
/* Restore dev flush and event */
dev->flush = flush;
dev->event = event;

View File

@ -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;
}

View File

@ -120,30 +120,24 @@ static int holtekff_play(struct input_dev *dev, void *data,
return 0;
}
static int holtekff_init(struct hid_device *hid)
static int holtek_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct holtekff_device *holtekff;
struct hid_report *report;
struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct input_dev *dev;
struct input_dev *dev = hidinput->input;
int error;
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
return -ENODEV;
}
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
dev = hidinput->input;
if (!list_is_first(&hidinput->list, &hid->inputs))
return 0;
if (list_empty(report_list)) {
report = list_first_entry_or_null(report_list, struct hid_report, list);
if (!report) {
hid_err(hid, "no output report found\n");
return -ENODEV;
}
report = list_entry(report_list->next, struct hid_report, list);
if (report->maxfield < 1 || report->field[0]->report_count != 7) {
hid_err(hid, "unexpected output report layout\n");
return -ENODEV;
@ -172,35 +166,13 @@ static int holtekff_init(struct hid_device *hid)
return 0;
}
#else
static inline int holtekff_init(struct hid_device *hid)
static inline int holtek_input_configured(struct hid_device *hid,
struct hid_input *hidinput)
{
return 0;
}
#endif
static int holtek_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
}
holtekff_init(hdev);
return 0;
err:
return ret;
}
static const struct hid_device_id holtek_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) },
{ }
@ -210,7 +182,7 @@ MODULE_DEVICE_TABLE(hid, holtek_devices);
static struct hid_driver holtek_driver = {
.name = "holtek",
.id_table = holtek_devices,
.probe = holtek_probe,
.input_configured = holtek_input_configured,
};
module_hid_driver(holtek_driver);

View File

@ -44,11 +44,12 @@ static const __u8 huawei_cd30_kbd_rdesc_fixed[] = {
static const __u8 *huawei_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct usb_interface *intf = hid_is_usb(hdev) ?
to_usb_interface(hdev->dev.parent) : NULL;
switch (hdev->product) {
case USB_DEVICE_ID_HUAWEI_CD30KBD:
if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
if (!intf || intf->cur_altsetting->desc.bInterfaceNumber == 1) {
if (*rsize != sizeof(huawei_cd30_kbd_rdesc_fixed) ||
memcmp(huawei_cd30_kbd_rdesc_fixed, rdesc,
sizeof(huawei_cd30_kbd_rdesc_fixed)) != 0) {

View File

@ -13,6 +13,9 @@
#include <linux/hiddev.h>
#include <linux/hyperv.h>
#if IS_ENABLED(CONFIG_HID_HYPERV_MOUSE_KUNIT_TEST)
#include <kunit/test.h>
#endif
struct hv_input_dev_info {
unsigned int size;
@ -171,18 +174,32 @@ static void mousevsc_free_device(struct mousevsc_dev *device)
}
static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device,
struct synthhid_device_info *device_info)
struct synthhid_device_info *device_info,
u32 device_info_size)
{
int ret = 0;
struct hid_descriptor *desc;
struct mousevsc_prt_msg ack;
size_t desc_offset;
size_t desc_size;
input_device->dev_info_status = -ENOMEM;
if (device_info_size < sizeof(*device_info)) {
input_device->dev_info_status = -EINVAL;
goto cleanup;
}
input_device->hid_dev_info = device_info->hid_dev_info;
desc = &device_info->hid_descriptor;
desc_offset = offsetof(struct synthhid_device_info, hid_descriptor);
desc_size = device_info_size - desc_offset;
if (desc->bLength == 0)
goto cleanup;
if (desc->bLength < sizeof(*desc) || desc->bLength > desc_size) {
input_device->dev_info_status = -EINVAL;
goto cleanup;
}
/* The pointer is not NULL when we resume from hibernation */
kfree(input_device->hid_desc);
@ -197,6 +214,10 @@ static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device,
input_device->dev_info_status = -EINVAL;
goto cleanup;
}
if (input_device->report_desc_size > desc_size - desc->bLength) {
input_device->dev_info_status = -EINVAL;
goto cleanup;
}
/* The pointer is not NULL when we resume from hibernation */
kfree(input_device->report_desc);
@ -222,13 +243,18 @@ static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device,
ack.ack.header.size = 1;
ack.ack.reserved = 0;
ret = vmbus_sendpacket(input_device->device->channel,
&ack,
sizeof(struct pipe_prt_msg) +
sizeof(struct synthhid_device_info_ack),
(unsigned long)&ack,
VM_PKT_DATA_INBAND,
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
if (IS_ENABLED(CONFIG_HID_HYPERV_MOUSE_KUNIT_TEST) &&
!input_device->device) {
ret = 0;
} else {
ret = vmbus_sendpacket(input_device->device->channel,
&ack,
sizeof(struct pipe_prt_msg) +
sizeof(struct synthhid_device_info_ack),
(unsigned long)&ack,
VM_PKT_DATA_INBAND,
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
}
if (!ret)
input_device->dev_info_status = 0;
@ -273,14 +299,17 @@ static void mousevsc_on_receive(struct hv_device *device,
break;
case SYNTH_HID_INITIAL_DEVICE_INFO:
WARN_ON(pipe_msg->size < sizeof(struct hv_input_dev_info));
if (WARN_ON_ONCE(pipe_msg->size <
sizeof(struct synthhid_device_info)))
break;
/*
* Parse out the device info into device attr,
* hid desc and report desc
*/
mousevsc_on_receive_device_info(input_dev,
(struct synthhid_device_info *)pipe_msg->data);
(struct synthhid_device_info *)pipe_msg->data,
pipe_msg->size);
break;
case SYNTH_HID_INPUT_REPORT:
input_report =
@ -614,5 +643,100 @@ static void __exit mousevsc_exit(void)
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Microsoft Hyper-V Synthetic HID Driver");
#if IS_ENABLED(CONFIG_HID_HYPERV_MOUSE_KUNIT_TEST)
static struct mousevsc_dev *mousevsc_kunit_alloc_dev(struct kunit *test)
{
struct mousevsc_dev *input_dev;
input_dev = kunit_kzalloc(test, sizeof(*input_dev), GFP_KERNEL);
if (!input_dev)
return NULL;
init_completion(&input_dev->wait_event);
return input_dev;
}
static void mousevsc_device_info_zero_blength(struct kunit *test)
{
struct synthhid_device_info *info;
struct mousevsc_dev *input_dev;
input_dev = mousevsc_kunit_alloc_dev(test);
KUNIT_ASSERT_NOT_NULL(test, input_dev);
info = kunit_kzalloc(test, sizeof(*info), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, info);
info->hid_descriptor.bLength = 0;
mousevsc_on_receive_device_info(input_dev, info, sizeof(*info));
KUNIT_EXPECT_EQ(test, input_dev->dev_info_status, -ENOMEM);
}
static void mousevsc_device_info_valid_descriptor(struct kunit *test)
{
struct synthhid_device_info *info;
struct mousevsc_dev *input_dev;
u8 *report;
input_dev = mousevsc_kunit_alloc_dev(test);
KUNIT_ASSERT_NOT_NULL(test, input_dev);
info = kunit_kzalloc(test, sizeof(*info) + 4, GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, info);
info->hid_descriptor.bLength = sizeof(struct hid_descriptor);
info->hid_descriptor.rpt_desc.wDescriptorLength = cpu_to_le16(4);
report = ((u8 *)&info->hid_descriptor) + info->hid_descriptor.bLength;
memset(report, 0x42, 4);
mousevsc_on_receive_device_info(input_dev, info, sizeof(*info) + 4);
KUNIT_EXPECT_EQ(test, input_dev->dev_info_status, 0);
KUNIT_EXPECT_EQ(test, input_dev->report_desc_size, 4);
KUNIT_EXPECT_MEMEQ(test, input_dev->report_desc, report, 4);
kfree(input_dev->hid_desc);
kfree(input_dev->report_desc);
}
static void mousevsc_device_info_report_desc_oob(struct kunit *test)
{
struct synthhid_device_info *info;
struct mousevsc_dev *input_dev;
u8 *report;
input_dev = mousevsc_kunit_alloc_dev(test);
KUNIT_ASSERT_NOT_NULL(test, input_dev);
info = kunit_kzalloc(test, sizeof(*info) + 8, GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, info);
info->hid_descriptor.bLength = sizeof(struct hid_descriptor);
info->hid_descriptor.rpt_desc.wDescriptorLength = cpu_to_le16(64);
report = ((u8 *)&info->hid_descriptor) + info->hid_descriptor.bLength;
memset(report, 0x42, 8);
mousevsc_on_receive_device_info(input_dev, info, sizeof(*info) + 8);
KUNIT_EXPECT_EQ(test, input_dev->dev_info_status, -EINVAL);
kfree(input_dev->hid_desc);
}
static struct kunit_case mousevsc_test_cases[] = {
KUNIT_CASE(mousevsc_device_info_zero_blength),
KUNIT_CASE(mousevsc_device_info_valid_descriptor),
KUNIT_CASE(mousevsc_device_info_report_desc_oob),
{}
};
static struct kunit_suite mousevsc_test_suite = {
.name = "hid_hyperv_mouse",
.test_cases = mousevsc_test_cases,
};
kunit_test_suite(mousevsc_test_suite);
#endif
module_init(mousevsc_init);
module_exit(mousevsc_exit);

117
drivers/hid/hid-hyperx.c Normal file
View File

@ -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");

View File

@ -220,6 +220,7 @@
#define USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD 0x183d
#define USB_DEVICE_ID_ASUSTEK_T304_KEYBOARD 0x184a
#define USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD 0x8585
#define USB_DEVICE_ID_ASUSTEK_I2C_ZENBOOK_KEYBOARD 0x4b42
#define USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD 0x0101
#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854
#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837
@ -263,6 +264,9 @@
#define USB_VENDOR_ID_BAANTO 0x2453
#define USB_DEVICE_ID_BAANTO_MT_190W2 0x0100
#define USB_VENDOR_ID_BEITONG 0x20dd
#define USB_DEVICE_ID_BEITONG_KP20D 0x5159
#define USB_VENDOR_ID_BELKIN 0x050d
#define USB_DEVICE_ID_FLIP_KVM 0x3201
@ -688,6 +692,7 @@
#define USB_DEVICE_ID_HORI_WIRELESS_SWITCH_PAD 0x00f6
#define USB_VENDOR_ID_HP 0x03f0
#define USB_PRODUCT_ID_HP_HYPERX_QUADCAST_2 0x07b4
#define USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A 0x464a
#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a
#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A 0x0b4a
@ -775,6 +780,7 @@
#define USB_DEVICE_ID_JESS_YUREX 0x1010
#define USB_DEVICE_ID_ASUS_MD_5112 0x5112
#define USB_DEVICE_ID_REDRAGON_ASURA 0x760b
#define USB_DEVICE_ID_AULA_MINI_60_HE_PRO 0xfefe
#define USB_VENDOR_ID_JESS2 0x0f30
#define USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD 0x0111
@ -967,6 +973,7 @@
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2 0xc543
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3 0xc547
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4 0xc54d
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_5 0xc545
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY 0xc53a
#define USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER 0xc548
#define USB_DEVICE_ID_SPACETRAVELLER 0xc623
@ -1071,7 +1078,12 @@
#define USB_DEVICE_ID_MOZA_R16_R21_2 0x0010
#define USB_VENDOR_ID_MSI 0x1770
#define USB_VENDOR_ID_MSI_2 0x0db0
#define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00
#define USB_DEVICE_ID_MSI_CLAW_XINPUT 0x1901
#define USB_DEVICE_ID_MSI_CLAW_DINPUT 0x1902
#define USB_DEVICE_ID_MSI_CLAW_DESKTOP 0x1903
#define USB_DEVICE_ID_MSI_CLAW_BIOS 0x1904
#define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400
#define USB_DEVICE_ID_N_S_HARMONY 0xc359
@ -1374,11 +1386,31 @@
#define USB_DEVICE_ID_STEAM_CONTROLLER 0x1102
#define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS 0x1142
#define USB_DEVICE_ID_STEAM_DECK 0x1205
#define USB_DEVICE_ID_STEAM_CONTROLLER_IBEX 0x1302
#define USB_DEVICE_ID_STEAM_CONTROLLER_IBEX_BLE 0x1303
#define USB_DEVICE_ID_STEAM_CONTROLLER_PROTEUS 0x1304
#define USB_DEVICE_ID_STEAM_CONTROLLER_NEREID 0x1305
#define USB_VENDOR_ID_STEELSERIES 0x1038
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X 0x12b6
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X 0x2253
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7 0x2202
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X 0x2206
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2 0x22a4
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO 0x223a
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW 0x227a
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026 0x22a1
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026 0x22a7
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026 0x22a5
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026 0x22a9
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2 0x227e
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2 0x2258
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2 0x229e
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3 0x22ad
#define USB_DEVICE_ID_STEELSERIES_MSI_KLC 0x1122
#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab

View File

@ -375,6 +375,9 @@ static const struct hid_device_id hid_battery_quirks[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_MAGICTRACKPAD),
HID_BATTERY_QUIRK_IGNORE },
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC),
HID_BATTERY_QUIRK_AVOID_QUERY },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM,
USB_DEVICE_ID_ELECOM_BM084),
HID_BATTERY_QUIRK_IGNORE },
@ -432,17 +435,25 @@ static int hidinput_scale_battery_capacity(struct hid_battery *bat,
static int hidinput_query_battery_capacity(struct hid_battery *bat)
{
int ret;
/*
* The capacity field may not be the first field in the report: some
* devices (e.g. the Apple Magic Trackpad 2 over Bluetooth) precede it
* with status flags. Read it from its actual byte offset in the report
* (report_offset is in bits; the leading byte is the report id).
*/
int offset = 1 + bat->report_offset / 8;
int len = offset + 1;
u8 *buf __free(kfree) = kmalloc(4, GFP_KERNEL);
u8 *buf __free(kfree) = kmalloc(max(len, 4), GFP_KERNEL);
if (!buf)
return -ENOMEM;
ret = hid_hw_raw_request(bat->dev, bat->report_id, buf, 4,
ret = hid_hw_raw_request(bat->dev, bat->report_id, buf, max(len, 4),
bat->report_type, HID_REQ_GET_REPORT);
if (ret < 2)
if (ret < len)
return -ENODATA;
return hidinput_scale_battery_capacity(bat, buf[1]);
return hidinput_scale_battery_capacity(bat, buf[offset]);
}
static int hidinput_get_battery_property(struct power_supply *psy,
@ -593,6 +604,7 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
bat->max = max;
bat->report_type = report_type;
bat->report_id = field->report->id;
bat->report_offset = field->report_offset;
bat->charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
bat->status = HID_BATTERY_UNKNOWN;
@ -2317,7 +2329,19 @@ static inline void hidinput_configure_usages(struct hid_input *hidinput,
* Read all reports and initialize the absolute field values.
*/
int hidinput_connect(struct hid_device *hid, unsigned int force)
static bool hid_has_ff_input(struct hid_device *hdev)
{
struct hid_input *hidinput;
list_for_each_entry(hidinput, &hdev->inputs, list) {
if (test_bit(EV_FF, hidinput->input->evbit))
return true;
}
return false;
}
int hidinput_connect(struct hid_device *hid, unsigned int connect_mask)
{
struct hid_driver *drv = hid->driver;
struct hid_report *report;
@ -2330,7 +2354,7 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
hid->status &= ~HID_STAT_DUP_DETECTED;
if (!force) {
if (!(connect_mask & HID_CONNECT_HIDINPUT_FORCE)) {
for (i = 0; i < hid->maxcollection; i++) {
struct hid_collection *col = &hid->collection[i];
if (col->type == HID_COLLECTION_APPLICATION ||
@ -2396,6 +2420,11 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
continue;
}
if (list_is_first(&hidinput->list, &hid->inputs) &&
(connect_mask & HID_CONNECT_FF) && hid->ff_init &&
!hid_has_ff_input(hid))
hid->ff_init(hid);
if (input_register_device(hidinput->input))
goto out_unwind;
hidinput->registered = true;

View File

@ -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;

View File

@ -121,6 +121,7 @@ enum recvr_type {
recvr_type_27mhz,
recvr_type_bluetooth,
recvr_type_dinovo,
recvr_type_bolt,
};
struct dj_report {
@ -1156,6 +1157,10 @@ static void logi_hidpp_recv_queue_notif(struct hid_device *hdev,
logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem);
workitem.reports_supported |= STD_KEYBOARD;
break;
case 0x10:
device_type = "Bolt";
logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem);
break;
}
/* custom receiver device (eg. powerplay) */
@ -1745,6 +1750,24 @@ static int logi_dj_hidpp_event(struct hid_device *hdev,
dj_dev = djrcv_dev->paired_dj_devices[device_index];
/*
* Bolt receivers send explicit unpair notifications as HID++ events;
* queue device removal when we receive one.
*/
if (djrcv_dev->type == recvr_type_bolt &&
hidpp_report->report_id == REPORT_ID_HIDPP_SHORT &&
hidpp_report->sub_id == REPORT_TYPE_NOTIF_DEVICE_UNPAIRED) {
struct dj_workitem workitem = {
.device_index = device_index,
.type = WORKITEM_TYPE_UNPAIRED,
};
kfifo_in(&djrcv_dev->notif_fifo, &workitem, sizeof(workitem));
schedule_work(&djrcv_dev->work);
spin_unlock_irqrestore(&djrcv_dev->lock, flags);
return false;
}
/*
* With 27 MHz receivers, we do not get an explicit unpair event,
* remove the old device if the user has paired a *different* device.
@ -1884,6 +1907,9 @@ static int logi_dj_probe(struct hid_device *hdev,
* treat these as logitech-dj interfaces then this causes input events
* reported through this extra interface to not be reported correctly.
* To avoid this, we treat these as generic-hid devices.
*
* Bolt receivers only use LOGITECH_DJ_INTERFACE_NUMBER for receiver
* reporting. Treat all other Bolt interfaces as generic-hid devices.
*/
switch (id->driver_data) {
case recvr_type_dj: no_dj_interfaces = 3; break;
@ -1897,10 +1923,20 @@ static int logi_dj_probe(struct hid_device *hdev,
}
if (hid_is_usb(hdev)) {
intf = to_usb_interface(hdev->dev.parent);
if (intf && intf->altsetting->desc.bInterfaceNumber >=
no_dj_interfaces) {
hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (intf) {
bool generic_hid_interface;
if (id->driver_data == recvr_type_bolt)
generic_hid_interface =
intf->altsetting->desc.bInterfaceNumber !=
LOGITECH_DJ_INTERFACE_NUMBER;
else
generic_hid_interface =
intf->altsetting->desc.bInterfaceNumber >= no_dj_interfaces;
if (generic_hid_interface) {
hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
}
}
}
@ -2103,10 +2139,18 @@ static const struct hid_device_id logi_dj_receivers[] = {
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3),
.driver_data = recvr_type_gaming_hidpp_ls_1_3},
{ /* Logitech Bolt receiver (0xc548) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER),
.driver_data = recvr_type_bolt},
{ /* Logitech lightspeed receiver (0xc54d) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4),
.driver_data = recvr_type_gaming_hidpp_ls_1_3},
{ /* Logitech lightspeed receiver (0xc545) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_5),
.driver_data = recvr_type_gaming_hidpp_ls_1_3},
{ /* Logitech 27 MHz HID++ 1.0 receiver (0xc513) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),

View File

@ -76,6 +76,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(28)
#define HIDPP_QUIRK_WIRELESS_STATUS BIT(29)
#define HIDPP_QUIRK_RESET_HI_RES_SCROLL BIT(30)
#define HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS BIT(31)
/* These are just aliases for now */
#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@ -178,6 +179,8 @@ struct hidpp_scroll_counter {
unsigned long long last_time;
};
struct hidpp_reprog_control_mapping;
struct hidpp_device {
struct hid_device *hid_dev;
struct input_dev *input;
@ -205,6 +208,8 @@ struct hidpp_device {
struct hidpp_scroll_counter vertical_wheel_counter;
u8 wireless_feature_index;
u8 reprog_controls_feature_index;
const struct hidpp_reprog_control_mapping *reprog_controls;
int hires_wheel_multiplier;
u8 hires_wheel_feature_index;
@ -983,7 +988,8 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
}
/* the device might not be connected */
if (ret == HIDPP_ERROR_RESOURCE_ERROR ||
if (ret == HIDPP_ERROR_CONNECT_FAIL ||
ret == HIDPP_ERROR_RESOURCE_ERROR ||
ret == HIDPP_ERROR_UNKNOWN_DEVICE)
return -EIO;
@ -2861,18 +2867,19 @@ static int hidpp_ff_init(struct hidpp_device *hidpp,
* ownership to FF core
*/
data = kmemdup(data, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
if (!data) {
error = -ENOMEM;
goto err_destroy_ff;
}
data->effect_ids = kzalloc_objs(int, num_slots);
if (!data->effect_ids) {
kfree(data);
return -ENOMEM;
error = -ENOMEM;
goto err_free_data;
}
data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue");
if (!data->wq) {
kfree(data->effect_ids);
kfree(data);
return -ENOMEM;
error = -ENOMEM;
goto err_free_effect_ids;
}
data->hidpp = hidpp;
@ -2902,6 +2909,14 @@ static int hidpp_ff_init(struct hidpp_device *hidpp,
version);
return 0;
err_free_effect_ids:
kfree(data->effect_ids);
err_free_data:
kfree(data);
err_destroy_ff:
input_ff_destroy(dev);
return error;
}
/* ************************************************************************** */
@ -3601,6 +3616,211 @@ static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
return 1;
}
/* -------------------------------------------------------------------------- */
/* HID++2.0 reprogrammable controls */
/* -------------------------------------------------------------------------- */
#define HIDPP_PAGE_REPROG_CONTROLS_V4 0x1b04
#define HIDPP_REPROG_CONTROLS_GET_COUNT 0x00
#define HIDPP_REPROG_CONTROLS_GET_CID_INFO 0x10
#define HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING 0x30
#define HIDPP_REPROG_CONTROLS_FLAG_MOUSE BIT(0)
#define HIDPP_REPROG_CONTROLS_FLAG_DIVERT BIT(5)
#define HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED BIT(0)
#define HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT BIT(1)
#define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED 0x00
#define HIDPP_REPROG_CONTROL_BACK 0x0053
#define HIDPP_REPROG_CONTROL_FORWARD 0x0056
#define HIDPP_PRODUCT_SIGNATURE_M650 0xb02a
struct hidpp_reprog_control_mapping {
u16 control;
u16 code;
};
static const struct hidpp_reprog_control_mapping m650_reprog_control_mappings[] = {
{ HIDPP_REPROG_CONTROL_BACK, BTN_BACK },
{ HIDPP_REPROG_CONTROL_FORWARD, BTN_FORWARD },
{ }
};
static const struct hidpp_reprog_control_mapping *
hidpp20_reprog_controls_get_mappings(struct hidpp_device *hidpp)
{
switch (hidpp->hid_dev->product) {
case HIDPP_PRODUCT_SIGNATURE_M650:
return m650_reprog_control_mappings;
}
return NULL;
}
static int hidpp20_reprog_controls_get_count(struct hidpp_device *hidpp)
{
struct hidpp_report response;
u8 feature_index = hidpp->reprog_controls_feature_index;
u8 cmd = HIDPP_REPROG_CONTROLS_GET_COUNT;
int ret;
ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, NULL, 0,
&response);
if (ret > 0)
return -EPROTO;
if (ret)
return ret;
return response.fap.params[0];
}
static int hidpp20_reprog_controls_get_cid_info(struct hidpp_device *hidpp,
u8 index, u16 *control,
u8 *flags)
{
struct hidpp_report response;
u8 feature_index = hidpp->reprog_controls_feature_index;
u8 cmd = HIDPP_REPROG_CONTROLS_GET_CID_INFO;
int ret;
ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, &index,
sizeof(index), &response);
if (ret > 0)
return -EPROTO;
if (ret)
return ret;
*control = get_unaligned_be16(&response.fap.params[0]);
*flags = response.fap.params[4];
return 0;
}
static bool hidpp20_reprog_controls_find_control(struct hidpp_device *hidpp,
u16 control)
{
int count, ret;
u16 cid;
u8 flags;
int i;
count = hidpp20_reprog_controls_get_count(hidpp);
if (count < 0)
return false;
for (i = 0; i < count; i++) {
ret = hidpp20_reprog_controls_get_cid_info(hidpp, i, &cid,
&flags);
if (ret)
return false;
if (cid == control)
return (flags & HIDPP_REPROG_CONTROLS_FLAG_MOUSE) &&
(flags & HIDPP_REPROG_CONTROLS_FLAG_DIVERT);
}
return false;
}
static int hidpp20_reprog_controls_set_control_reporting(struct hidpp_device *hidpp,
u16 control, u8 flags)
{
struct hidpp_report response;
u8 params[5];
put_unaligned_be16(control, &params[0]);
params[2] = flags;
put_unaligned_be16(control, &params[3]);
return hidpp_send_fap_command_sync(hidpp,
hidpp->reprog_controls_feature_index,
HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING,
params, sizeof(params), &response);
}
static void hidpp20_reprog_controls_connect(struct hidpp_device *hidpp)
{
const struct hidpp_reprog_control_mapping *mapping;
u8 flags = HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED |
HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT;
if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS))
return;
if (!hidpp->reprog_controls)
return;
if (hidpp_root_get_feature(hidpp, HIDPP_PAGE_REPROG_CONTROLS_V4,
&hidpp->reprog_controls_feature_index))
return;
for (mapping = hidpp->reprog_controls; mapping->control; mapping++) {
if (!hidpp20_reprog_controls_find_control(hidpp, mapping->control))
continue;
hidpp20_reprog_controls_set_control_reporting(hidpp,
mapping->control,
flags);
}
}
static int hidpp20_reprog_controls_raw_event(struct hidpp_device *hidpp,
u8 *data, int size)
{
const struct hidpp_reprog_control_mapping *mapping;
struct hidpp_report *report = (struct hidpp_report *)data;
u16 controls[4];
bool pressed;
unsigned int i, j;
if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS) ||
!hidpp->input ||
!hidpp->reprog_controls ||
hidpp->reprog_controls_feature_index == 0xff)
return 0;
if (size < HIDPP_REPORT_LONG_LENGTH ||
report->fap.feature_index != hidpp->reprog_controls_feature_index ||
report->fap.funcindex_clientid != HIDPP_REPROG_CONTROLS_EVENT_DIVERTED)
return 0;
for (i = 0; i < ARRAY_SIZE(controls); i++)
controls[i] = get_unaligned_be16(&report->fap.params[i * 2]);
for (mapping = hidpp->reprog_controls; mapping->control; mapping++) {
pressed = false;
for (j = 0; j < ARRAY_SIZE(controls); j++) {
if (controls[j] == mapping->control) {
pressed = true;
break;
}
}
input_report_key(hidpp->input, mapping->code, pressed);
}
input_sync(hidpp->input);
return 1;
}
static void hidpp20_reprog_controls_populate_input(struct hidpp_device *hidpp,
struct input_dev *input_dev)
{
const struct hidpp_reprog_control_mapping *mapping;
if (!hidpp->reprog_controls)
return;
for (mapping = hidpp->reprog_controls; mapping->control; mapping++)
input_set_capability(input_dev, EV_KEY, mapping->code);
}
static void hidpp10_extra_mouse_buttons_populate_input(
struct hidpp_device *hidpp, struct input_dev *input_dev)
{
@ -3859,17 +4079,37 @@ static void hidpp_populate_input(struct hidpp_device *hidpp,
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS)
hidpp10_extra_mouse_buttons_populate_input(hidpp, input);
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS)
hidpp20_reprog_controls_populate_input(hidpp, input);
}
static int hidpp_input_configured(struct hid_device *hdev,
struct hid_input *hidinput)
static int hidpp_input_configured(struct hid_device *hdev, struct hid_input *hidinput)
{
struct hidpp_device *hidpp = hid_get_drvdata(hdev);
struct input_dev *input = hidinput->input;
int ret;
if (!hidpp)
return 0;
if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
struct hidpp_ff_private_data data;
if (!list_is_first(&hidinput->list, &hdev->inputs))
return 0;
ret = g920_get_config(hidpp, &data);
if (!ret)
ret = hidpp_ff_init(hidpp, &data);
if (ret) {
hid_warn(hidpp->hid_dev,
"Unable to initialize force feedback support, errno %d\n",
ret);
}
}
hidpp_populate_input(hidpp, input);
return 0;
@ -3971,6 +4211,10 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
return ret;
}
ret = hidpp20_reprog_controls_raw_event(hidpp, data, size);
if (ret != 0)
return ret;
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
ret = hidpp10_consumer_keys_raw_event(hidpp, data, size);
if (ret != 0)
@ -4161,8 +4405,50 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
return ret;
}
static bool hidpp_is_bolt_child(struct hid_device *hdev)
{
struct device *parent = hdev->dev.parent;
struct hid_device *receiver_hdev;
if (!parent)
return false;
receiver_hdev = to_hid_device(parent);
return receiver_hdev->vendor == USB_VENDOR_ID_LOGITECH &&
receiver_hdev->product == USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER;
}
static int hidpp_bolt_init(struct hidpp_device *hidpp)
{
struct hid_device *hdev = hidpp->hid_dev;
char *name;
int ret;
ret = hidpp_serial_init(hidpp);
if (ret)
return ret;
name = hidpp_get_device_name(hidpp);
if (!name)
return -EIO;
snprintf(hdev->name, sizeof(hdev->name), "%s", name);
dbg_hid("HID++ Bolt: Got name: %s\n", name);
kfree(name);
return 0;
}
static int hidpp_receiver_init(struct hidpp_device *hidpp)
{
if (hidpp_is_bolt_child(hidpp->hid_dev))
return hidpp_bolt_init(hidpp);
return hidpp_unifying_init(hidpp);
}
/* Get name + serial for USB and Bluetooth HID++ devices */
static void hidpp_non_unifying_init(struct hidpp_device *hidpp)
static void hidpp_non_receiver_init(struct hidpp_device *hidpp)
{
struct hid_device *hdev = hidpp->hid_dev;
char *name;
@ -4264,6 +4550,8 @@ static void hidpp_connect_event(struct work_struct *work)
return;
}
hidpp20_reprog_controls_connect(hidpp);
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
ret = hidpp10_consumer_keys_connect(hidpp);
if (ret)
@ -4437,6 +4725,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hidpp->hid_dev = hdev;
hidpp->name = hdev->name;
hidpp->quirks = id->driver_data;
hidpp->reprog_controls_feature_index = 0xff;
hidpp->reprog_controls = hidpp20_reprog_controls_get_mappings(hidpp);
hid_set_drvdata(hdev, hidpp);
ret = hid_parse(hdev);
@ -4510,9 +4800,9 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
/* Get name + serial, store in hdev->name + hdev->uniq */
if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE)
hidpp_unifying_init(hidpp);
hidpp_receiver_init(hidpp);
else
hidpp_non_unifying_init(hidpp);
hidpp_non_receiver_init(hidpp);
if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
connect_mask &= ~HID_CONNECT_HIDINPUT;
@ -4530,21 +4820,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
schedule_work(&hidpp->work);
flush_work(&hidpp->work);
if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
struct hidpp_ff_private_data data;
ret = g920_get_config(hidpp, &data);
if (!ret)
ret = hidpp_ff_init(hidpp, &data);
if (ret) {
hid_warn(hidpp->hid_dev,
"Unable to initialize force feedback support, errno %d\n",
ret);
ret = 0;
}
}
/*
* This relies on logi_dj_ll_close() being a no-op so that DJ connection
* events will still be received.
@ -4647,6 +4922,8 @@ static const struct hid_device_id hidpp_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC082) },
{ /* Logitech G502 Lightspeed Wireless Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08D) },
{ /* Logitech G502 X Plus Wireless Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC095) },
{ /* Logitech G703 Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC087) },
{ /* Logitech G703 Hero Gaming Mouse over USB */
@ -4659,8 +4936,6 @@ static const struct hid_device_id hidpp_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },
{ /* MX Vertical over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08A) },
{ /* Logitech G703 Hero Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC090) },
{ /* Logitech G903 Hero Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC091) },
{ /* Logitech G915 TKL Keyboard over USB */
@ -4707,7 +4982,9 @@ static const struct hid_device_id hidpp_devices[] = {
{ /* MX Vertical mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) },
{ /* Signature M650 over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
HIDPP_PRODUCT_SIGNATURE_M650),
.driver_data = HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS },
{ /* MX Master 3 mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) },
{ /* MX Anywhere 3 mouse over Bluetooth */

View File

@ -383,8 +383,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
}
}
static int magicmouse_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
static int __magicmouse_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size, bool nested)
{
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
struct input_dev *input = msc->input;
@ -495,6 +495,15 @@ static int magicmouse_raw_event(struct hid_device *hdev,
* packet.
*/
/*
* A double report only ever wraps two normal reports, so it is
* never nested. Refuse to recurse a second time; otherwise a
* malicious device could chain DOUBLE_REPORT_ID packets to drive
* unbounded recursion and overflow the kernel stack.
*/
if (nested)
return 0;
/* Ensure that we have at least 2 elements (report type and size) */
if (size < 2)
return 0;
@ -506,9 +515,9 @@ static int magicmouse_raw_event(struct hid_device *hdev,
return 0;
}
magicmouse_raw_event(hdev, report, data + 2, data[1]);
magicmouse_raw_event(hdev, report, data + 2 + data[1],
size - 2 - data[1]);
__magicmouse_raw_event(hdev, report, data + 2, data[1], true);
__magicmouse_raw_event(hdev, report, data + 2 + data[1],
size - 2 - data[1], true);
return 0;
default:
return 0;
@ -534,6 +543,12 @@ static int magicmouse_raw_event(struct hid_device *hdev,
return 1;
}
static int magicmouse_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
return __magicmouse_raw_event(hdev, report, data, size, false);
}
static int magicmouse_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
@ -828,6 +843,12 @@ static bool is_usb_magictrackpad2(__u32 vendor, __u32 product)
product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC;
}
static bool is_bt_magictrackpad2(__u32 vendor, __u32 product)
{
return vendor == BT_VENDOR_ID_APPLE &&
product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC;
}
static int magicmouse_fetch_battery(struct hid_device *hdev)
{
#ifdef CONFIG_HID_BATTERY_STRENGTH
@ -838,7 +859,8 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
bat = hid_get_battery(hdev);
if (!bat ||
(!is_usb_magicmouse2(hdev->vendor, hdev->product) &&
!is_usb_magictrackpad2(hdev->vendor, hdev->product)))
!is_usb_magictrackpad2(hdev->vendor, hdev->product) &&
!is_bt_magictrackpad2(hdev->vendor, hdev->product)))
return -1;
report_enum = &hdev->report_enum[bat->report_type];
@ -900,6 +922,16 @@ static int magicmouse_probe(struct hid_device *hdev,
return ret;
}
/*
* When hidinput_connect() fails it frees every input device it
* created, but that does not fail hid_hw_start(): the core simply
* does not claim an input. msc->input, cached in ->input_mapping
* while the report descriptor was parsed, would then be a dangling
* pointer that passes every NULL check. Trust the core's claim.
*/
if (!(hdev->claimed & HID_CLAIMED_INPUT))
msc->input = NULL;
if (is_usb_magicmouse2(id->vendor, id->product) ||
is_usb_magictrackpad2(id->vendor, id->product)) {
timer_setup(&msc->battery_timer, magicmouse_battery_timer_tick, 0);
@ -971,6 +1003,16 @@ static int magicmouse_probe(struct hid_device *hdev,
schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
}
/*
* Query the Bluetooth Magic Trackpad USB-C battery as done for USB.
* Start io first: probe holds driver_input_lock and the synchronous
* GET_REPORT reply would otherwise be dropped.
*/
if (is_bt_magictrackpad2(id->vendor, id->product)) {
hid_device_io_start(hdev);
magicmouse_fetch_battery(hdev);
}
return 0;
err_stop_hw:
if (is_usb_magicmouse2(id->vendor, id->product) ||
@ -995,6 +1037,22 @@ static void magicmouse_remove(struct hid_device *hdev)
hid_hw_stop(hdev);
}
#ifdef CONFIG_PM
static int magicmouse_reset_resume(struct hid_device *hdev)
{
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
/* The device drops out of multitouch mode on resume; re-send the
* enable report. Only the HID_TYPE_USBMOUSE interface accepts it, and
* it must be deferred. Sending it inline here is too early.
*/
if (msc && hdev->type == HID_TYPE_USBMOUSE)
schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
return 0;
}
#endif
static const __u8 *magicmouse_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
@ -1058,6 +1116,9 @@ static struct hid_driver magicmouse_driver = {
.event = magicmouse_event,
.input_mapping = magicmouse_input_mapping,
.input_configured = magicmouse_input_configured,
#ifdef CONFIG_PM
.reset_resume = magicmouse_reset_resume,
#endif
};
module_hid_driver(magicmouse_driver);

View File

@ -343,7 +343,7 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 4);
if (ret)
return ret;
goto out;
mcp->rxbuf_idx = 0;
@ -365,7 +365,7 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
} else {
usleep_range(980, 1000);
mcp_cancel_last_cmd(mcp);
return ret;
goto out;
}
} else {
retries = 0;
@ -375,6 +375,10 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
usleep_range(980, 1000);
ret = mcp_chk_last_cmd_status_free_bus(mcp);
out:
mcp->rxbuf = NULL;
mcp->rxbuf_size = 0;
return ret;
}
@ -861,6 +865,9 @@ static int mcp2221_raw_event(struct hid_device *hdev,
u8 *buf;
struct mcp2221 *mcp = hid_get_drvdata(hdev);
if (size < 4)
return 0;
switch (data[0]) {
case MCP2221_I2C_WR_DATA:
@ -926,6 +933,10 @@ static int mcp2221_raw_event(struct hid_device *hdev,
mcp->status = -EINVAL;
break;
}
if (4 + data[3] > size) {
mcp->status = -EINVAL;
break;
}
buf = mcp->rxbuf;
memcpy(&buf[mcp->rxbuf_idx], &data[4], data[3]);
mcp->rxbuf_idx = mcp->rxbuf_idx + data[3];
@ -1049,6 +1060,8 @@ static void mcp2221_hid_unregister(void *ptr)
{
struct hid_device *hdev = ptr;
if (hdev->io_started)
hid_device_io_stop(hdev);
hid_hw_close(hdev);
hid_hw_stop(hdev);
}

View File

@ -35,21 +35,16 @@ static int mwctrl_play(struct input_dev *dev, void *data,
return 0;
}
static int mwctrl_init(struct hid_device *hid)
static int mwctrl_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct mwctrl_device *mwctrl;
struct hid_report *report;
struct hid_input *hidinput;
struct input_dev *dev;
struct input_dev *dev = hidinput->input;
int error;
int i;
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
return -ENODEV;
}
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
dev = hidinput->input;
if (!list_is_first(&hidinput->list, &hid->inputs))
return 0;
for (i = 0; i < 4; i++) {
report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, i, 1);
@ -61,16 +56,7 @@ static int mwctrl_init(struct hid_device *hid)
if (!mwctrl)
return -ENOMEM;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, mwctrl, mwctrl_play);
if (error) {
kfree(mwctrl);
return error;
}
mwctrl->report = report;
/* Field 0 is always 2, and field 1 is always 0. The original
* windows driver has a 5 bytes command, where the 5th byte is
* a repeat of the 3rd byte, however the device has only 4
@ -82,32 +68,17 @@ static int mwctrl_init(struct hid_device *hid)
mwctrl->strong = &report->field[2]->value[0];
mwctrl->weak = &report->field[3]->value[0];
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, mwctrl, mwctrl_play);
if (error) {
kfree(mwctrl);
return error;
}
return 0;
}
static int mwctrl_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
return ret;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
return ret;
}
ret = mwctrl_init(hdev);
if (ret)
hid_hw_stop(hdev);
return ret;
}
static const struct hid_device_id mwctrl_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_MEGAWORLD,
USB_DEVICE_ID_MEGAWORLD_GAMEPAD) },
@ -118,7 +89,7 @@ MODULE_DEVICE_TABLE(hid, mwctrl_devices);
static struct hid_driver mwctrl_driver = {
.name = "megaworld",
.id_table = mwctrl_devices,
.probe = mwctrl_probe,
.input_configured = mwctrl_input_configured,
};
module_hid_driver(mwctrl_driver);

View File

@ -54,61 +54,45 @@ static int mf_play(struct input_dev *dev, void *data, struct ff_effect *effect)
return 0;
}
static int mf_init(struct hid_device *hid)
static int mf_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct mf_device *mf;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct list_head *report_ptr;
struct hid_report *report;
struct list_head *input_ptr = &hid->inputs;
struct hid_input *input;
struct input_dev *dev;
struct input_dev *dev = hidinput->input;
int error;
/* Setup each of the four inputs */
list_for_each(report_ptr, report_list) {
report = list_entry(report_ptr, struct hid_report, list);
if (!list_is_first(&hidinput->list, &hid->inputs))
return 0;
if (report->maxfield < 1 || report->field[0]->report_count < 2) {
hid_err(hid, "Invalid report, this should never happen!\n");
return -ENODEV;
}
if (list_is_last(input_ptr, &hid->inputs)) {
hid_err(hid, "Missing input, this should never happen!\n");
return -ENODEV;
}
input_ptr = input_ptr->next;
input = list_entry(input_ptr, struct hid_input, list);
mf = kzalloc_obj(struct mf_device);
if (!mf)
return -ENOMEM;
dev = input->input;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, mf, mf_play);
if (error) {
kfree(mf);
return error;
}
mf->report = report;
mf->report->field[0]->value[0] = 0x00;
mf->report->field[0]->value[1] = 0x00;
hid_hw_request(hid, mf->report, HID_REQ_SET_REPORT);
report = list_first_entry_or_null(report_list, struct hid_report, list);
if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
hid_info(hid, "Force feedback for HJZ Mayflash game controller "
"adapters by Marcel Hasler <mahasler@gmail.com>\n");
if (report->maxfield < 1 || report->field[0]->report_count < 2) {
hid_err(hid, "Invalid report, this should never happen!\n");
return -ENODEV;
}
mf = kzalloc_obj(struct mf_device);
if (!mf)
return -ENOMEM;
mf->report = report;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, mf, mf_play);
if (error) {
kfree(mf);
return error;
}
mf->report->field[0]->value[0] = 0x00;
mf->report->field[0]->value[1] = 0x00;
hid_hw_request(hid, mf->report, HID_REQ_SET_REPORT);
return 0;
}
@ -128,22 +112,14 @@ static int mf_probe(struct hid_device *hid, const struct hid_device_id *id)
return error;
}
error = hid_hw_start(hid, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
error = hid_hw_start(hid, HID_CONNECT_DEFAULT);
if (error) {
hid_err(hid, "HID hw start failed\n");
return error;
}
error = mf_init(hid);
if (error) {
hid_err(hid, "Force feedback init failed.\n");
hid_hw_stop(hid);
return error;
}
return 0;
}
static const struct hid_device_id mf_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_PS3),
.driver_data = HID_QUIRK_MULTI_INPUT },
@ -163,6 +139,7 @@ static struct hid_driver mf_driver = {
.name = "hid_mf",
.id_table = mf_devices,
.probe = mf_probe,
.input_configured = mf_input_configured,
};
module_hid_driver(mf_driver);

View File

@ -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,

2063
drivers/hid/hid-msi.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2189,16 +2189,8 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
if (td->is_haptic_touchpad) {
if (hid_haptic_init(hdev, &td->haptic)) {
dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
hdev->name);
td->is_haptic_touchpad = false;
devm_kfree(&hdev->dev, td->haptic);
}
} else {
if (!td->is_haptic_touchpad)
devm_kfree(&hdev->dev, td->haptic);
}
return 0;
}
@ -2722,7 +2714,7 @@ static const struct hid_device_id mt_devices[] = {
HID_ANY_ID) },
/* Hantick */
{ .driver_data = MT_CLS_NSMU,
{ .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU,
HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288) },

View File

@ -609,6 +609,8 @@ struct joycon_ctlr {
unsigned int last_input_report_msecs;
unsigned int last_subcmd_sent_msecs;
unsigned int consecutive_valid_report_deltas;
unsigned int subcmd_rate_exhaustions;
bool subcmd_rate_relaxed;
/* factory calibration data */
struct joycon_stick_cal left_stick_cal_x;
@ -841,10 +843,11 @@ static void joycon_wait_for_input_report(struct joycon_ctlr *ctlr)
#define JC_SUBCMD_TX_OFFSET_MS 4
#define JC_SUBCMD_VALID_DELTA_REQ 3
#define JC_SUBCMD_RATE_MAX_ATTEMPTS 25
#define JC_SUBCMD_RATE_MAX_FAILURES 4
#define JC_SUBCMD_RATE_LIMITER_USB_MS 20
#define JC_SUBCMD_RATE_LIMITER_BT_MS 60
#define JC_SUBCMD_RATE_LIMITER_MS(ctlr) ((ctlr)->hdev->bus == BUS_USB ? JC_SUBCMD_RATE_LIMITER_USB_MS : JC_SUBCMD_RATE_LIMITER_BT_MS)
static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr)
static void joycon_enforce_subcmd_rate_strict(struct joycon_ctlr *ctlr)
{
unsigned int current_ms;
unsigned long subcmd_delta;
@ -872,6 +875,14 @@ static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr)
if (attempts >= JC_SUBCMD_RATE_MAX_ATTEMPTS) {
hid_warn(ctlr->hdev, "%s: exceeded max attempts", __func__);
if (++ctlr->subcmd_rate_exhaustions == JC_SUBCMD_RATE_MAX_FAILURES) {
ctlr->subcmd_rate_relaxed = true;
hid_info(ctlr->hdev,
"input report cadence does not fit the %d-%dms window; using the legacy subcommand throttle\n",
JC_INPUT_REPORT_MIN_DELTA,
JC_INPUT_REPORT_MAX_DELTA);
}
return;
}
@ -886,6 +897,32 @@ static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr)
msleep(JC_SUBCMD_TX_OFFSET_MS);
}
/* The rate limiter as it was before commit d750d1480362, without the report
* cadence requirement.
*/
static void joycon_enforce_subcmd_rate_legacy(struct joycon_ctlr *ctlr)
{
static const unsigned int max_subcmd_rate_ms = 25;
unsigned int current_ms = jiffies_to_msecs(jiffies);
unsigned int delta_ms = current_ms - ctlr->last_subcmd_sent_msecs;
while (delta_ms < max_subcmd_rate_ms &&
ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) {
joycon_wait_for_input_report(ctlr);
current_ms = jiffies_to_msecs(jiffies);
delta_ms = current_ms - ctlr->last_subcmd_sent_msecs;
}
ctlr->last_subcmd_sent_msecs = current_ms;
}
static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr)
{
if (ctlr->subcmd_rate_relaxed)
joycon_enforce_subcmd_rate_legacy(ctlr);
else
joycon_enforce_subcmd_rate_strict(ctlr);
}
static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len,
u32 timeout)
{
@ -1474,7 +1511,6 @@ static void joycon_parse_imu_report(struct joycon_ctlr *ctlr,
dropped_threshold = ctlr->imu_avg_delta_ms * 3 / 2;
dropped_pkts = (delta - min(delta, dropped_threshold)) /
ctlr->imu_avg_delta_ms;
ctlr->imu_timestamp_us += 1000 * ctlr->imu_avg_delta_ms;
if (dropped_pkts > JC_IMU_DROPPED_PKT_WARNING) {
hid_warn_ratelimited(ctlr->hdev,
"compensating for %u dropped IMU reports\n",
@ -2162,10 +2198,6 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
ctlr->input->phys = hdev->phys;
input_set_drvdata(ctlr->input, ctlr);
ret = input_register_device(ctlr->input);
if (ret)
return ret;
if (joycon_type_is_right_joycon(ctlr)) {
joycon_config_right_stick(ctlr->input);
joycon_config_buttons(ctlr->input, right_joycon_button_mappings);
@ -2208,6 +2240,10 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
if (joycon_has_rumble(ctlr))
joycon_config_rumble(ctlr);
ret = input_register_device(ctlr->input);
if (ret)
return ret;
return 0;
}
@ -2607,7 +2643,12 @@ static int joycon_ctlr_read_handler(struct joycon_ctlr *ctlr, u8 *data,
{
if (data[0] == JC_INPUT_SUBCMD_REPLY || data[0] == JC_INPUT_IMU_DATA ||
data[0] == JC_INPUT_MCU_DATA) {
if (size >= 12) /* make sure it contains the input report */
/*
* The whole struct is cast and parsed below, including the
* IMU/subcmd union, not just the 12-byte partial header this
* used to check for.
*/
if (size >= sizeof(struct joycon_input_report))
joycon_parse_report(ctlr,
(struct joycon_input_report *)data);
}
@ -2736,14 +2777,14 @@ static int nintendo_hid_probe(struct hid_device *hdev,
ret = joycon_init(hdev);
if (ret) {
hid_err(hdev, "Failed to initialize controller; ret=%d\n", ret);
goto err_close;
goto err_io_stop;
}
/* Initialize the leds */
ret = joycon_leds_create(ctlr);
if (ret) {
hid_err(hdev, "Failed to create leds; ret=%d\n", ret);
goto err_close;
goto err_io_stop;
}
/* Initialize the battery power supply */
@ -2766,7 +2807,8 @@ static int nintendo_hid_probe(struct hid_device *hdev,
err_ida:
ida_free(&nintendo_player_id_allocator, ctlr->player_id);
err_close:
err_io_stop:
hid_device_io_stop(hdev);
hid_hw_close(hdev);
err_stop:
hid_hw_stop(hdev);

View File

@ -398,7 +398,7 @@ static int oxp_hid_raw_event_gen_2(struct hid_device *hdev,
* Re-apply our settings after this has been received.
*/
if (data[3] == OXP_EFFECT_MONO_TRUE) {
mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
mod_delayed_work(system_dfl_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
return 0;
}
@ -788,7 +788,7 @@ static ssize_t map_button_store(struct device *dev,
default:
return -EINVAL;
}
mod_delayed_work(system_wq, &drvdata.oxp_btn_queue, msecs_to_jiffies(50));
mod_delayed_work(system_dfl_wq, &drvdata.oxp_btn_queue, msecs_to_jiffies(50));
return count;
}
@ -1349,7 +1349,7 @@ static void oxp_rgb_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
led_cdev->brightness = brightness;
mod_delayed_work(system_wq, &drvdata.oxp_rgb_queue, msecs_to_jiffies(50));
mod_delayed_work(system_dfl_wq, &drvdata.oxp_rgb_queue, msecs_to_jiffies(50));
}
static struct attribute *oxp_rgb_attrs[] = {
@ -1502,7 +1502,7 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
drvdata.rumble_intensity = 5;
INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn);
mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
mod_delayed_work(system_dfl_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
ret = devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group);
if (ret)

View File

@ -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

View File

@ -62,15 +62,13 @@ static int hid_plff_play(struct input_dev *dev, void *data,
return 0;
}
static int plff_init(struct hid_device *hid)
static int pl_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct plff_device *plff;
struct hid_report *report;
struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct list_head *report_ptr = report_list;
struct input_dev *dev;
struct input_dev *dev = hidinput->input;
int error;
s32 maxval;
s32 *strong;
@ -83,89 +81,80 @@ static int plff_init(struct hid_device *hid)
The input reports also contain a field which contains
8 ff00.0001 usages and 8 boolean values. Their meaning is
currently unknown.
A version of the 0e8f:0003 exists that has all the values in
separate fields and misses the extra input field, thus resembling
Zeroplus (hid-zpff) devices.
*/
if (list_empty(report_list)) {
if (!list_is_first(&hidinput->list, &hid->inputs))
return 0;
report = list_first_entry_or_null(report_list, struct hid_report, list);
if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
list_for_each_entry(hidinput, &hid->inputs, list) {
report_ptr = report_ptr->next;
if (report_ptr == report_list) {
hid_err(hid, "required output report is missing\n");
return -ENODEV;
}
report = list_entry(report_ptr, struct hid_report, list);
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
}
maxval = 0x7f;
if (report->field[0]->report_count >= 4) {
report->field[0]->value[0] = 0x00;
report->field[0]->value[1] = 0x00;
strong = &report->field[0]->value[2];
weak = &report->field[0]->value[3];
hid_dbg(hid, "detected single-field device");
} else if (report->field[0]->maxusage == 1 &&
report->field[0]->usage[0].hid ==
(HID_UP_LED | 0x43) &&
report->maxfield >= 4 &&
report->field[0]->report_count >= 1 &&
report->field[1]->report_count >= 1 &&
report->field[2]->report_count >= 1 &&
report->field[3]->report_count >= 1) {
report->field[0]->value[0] = 0x00;
report->field[1]->value[0] = 0x00;
strong = &report->field[2]->value[0];
weak = &report->field[3]->value[0];
if (hid->vendor == USB_VENDOR_ID_JESS2)
maxval = 0xff;
hid_dbg(hid, "detected 4-field device");
} else {
hid_err(hid, "not enough fields or values\n");
return -ENODEV;
}
plff = kzalloc_obj(struct plff_device);
if (!plff)
return -ENOMEM;
dev = hidinput->input;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, plff, hid_plff_play);
if (error) {
kfree(plff);
return error;
}
plff->report = report;
plff->strong = strong;
plff->weak = weak;
plff->maxval = maxval;
*strong = 0x00;
*weak = 0x00;
hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
}
hid_info(hid, "Force feedback for PantherLord/GreenAsia devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
maxval = 0x7f;
if (report->field[0]->report_count >= 4) {
report->field[0]->value[0] = 0x00;
report->field[0]->value[1] = 0x00;
strong = &report->field[0]->value[2];
weak = &report->field[0]->value[3];
hid_dbg(hid, "detected single-field device");
} else if (report->field[0]->maxusage == 1 &&
report->field[0]->usage[0].hid ==
(HID_UP_LED | 0x43) &&
report->maxfield >= 4 &&
report->field[0]->report_count >= 1 &&
report->field[1]->report_count >= 1 &&
report->field[2]->report_count >= 1 &&
report->field[3]->report_count >= 1) {
report->field[0]->value[0] = 0x00;
report->field[1]->value[0] = 0x00;
strong = &report->field[2]->value[0];
weak = &report->field[3]->value[0];
if (hid->vendor == USB_VENDOR_ID_JESS2)
maxval = 0xff;
hid_dbg(hid, "detected 4-field device");
} else {
hid_err(hid, "not enough fields or values\n");
return -ENODEV;
}
plff = kzalloc_obj(struct plff_device);
if (!plff)
return -ENOMEM;
dev = hidinput->input;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, plff, hid_plff_play);
if (error) {
kfree(plff);
return error;
}
plff->report = report;
plff->strong = strong;
plff->weak = weak;
plff->maxval = maxval;
*strong = 0x00;
*weak = 0x00;
hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
return 0;
}
#else
static inline int plff_init(struct hid_device *hid)
static inline int pl_input_configured(struct hid_device *hid,
struct hid_input *hidinput)
{
return 0;
}
@ -181,27 +170,17 @@ static int pl_probe(struct hid_device *hdev, const struct hid_device_id *id)
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
return ret;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
return ret;
}
ret = plff_init(hdev);
if (ret)
goto stop;
return 0;
stop:
hid_hw_stop(hdev);
err:
return ret;
}
static const struct hid_device_id pl_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR),
.driver_data = 1 }, /* Twin USB Joystick */
@ -217,6 +196,7 @@ static struct hid_driver pl_driver = {
.name = "pantherlord",
.id_table = pl_devices,
.probe = pl_probe,
.input_configured = pl_input_configured,
};
module_hid_driver(pl_driver);

View File

@ -45,6 +45,7 @@ static const struct hid_device_id hid_quirks[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS682), HID_QUIRK_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS692), HID_QUIRK_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_UC100KM), HID_QUIRK_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_BEITONG, USB_DEVICE_ID_BEITONG_KP20D), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_MULTI_TOUCH), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE2), HID_QUIRK_ALWAYS_POLL },
@ -112,6 +113,8 @@ static const struct hid_device_id hid_quirks[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, USB_DEVICE_ID_IDEACOM_IDC6680), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_INNOMEDIA, USB_DEVICE_ID_INNEX_GENESIS_ATARI), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_JESS, USB_DEVICE_ID_AULA_MINI_60_HE_PRO),
HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE_ID2), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M406), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M506), HID_QUIRK_MULTI_INPUT },
@ -747,8 +750,22 @@ static const struct hid_device_id hid_have_special_driver[] = {
#endif
#if IS_ENABLED(CONFIG_HID_STEELSERIES)
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3) },
#endif
#if IS_ENABLED(CONFIG_HID_SUNPLUS)
{ HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },

View File

@ -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)

View File

@ -365,7 +365,7 @@ static int rmi_check_sanity(struct hid_device *hdev, u8 *data, int size)
* such reports here.
*/
while ((data[valid_size - 1] == 0xff) && valid_size > 0)
while (valid_size > 0 && data[valid_size - 1] == 0xff)
valid_size--;
return valid_size;

View File

@ -36,6 +36,8 @@ static uint profile_numbers[5] = {0, 1, 2, 3, 4};
static void kone_profile_activated(struct kone_device *kone, uint new_profile)
{
if (new_profile < 1 || new_profile > ARRAY_SIZE(kone->profiles))
new_profile = 1;
kone->actual_profile = new_profile;
kone->actual_dpi = kone->profiles[new_profile - 1].startup_dpi;
}
@ -793,8 +795,10 @@ static void kone_keep_values_up_to_date(struct kone_device *kone,
{
switch (event->event) {
case kone_mouse_event_switch_profile:
kone->actual_dpi = kone->profiles[event->value - 1].
startup_dpi;
if (event->value >= 1 &&
event->value <= ARRAY_SIZE(kone->profiles))
kone->actual_dpi =
kone->profiles[event->value - 1].startup_dpi;
fallthrough;
case kone_mouse_event_osd_profile:
kone->actual_profile = event->value;
@ -915,3 +919,60 @@ module_exit(kone_exit);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat Kone driver");
MODULE_LICENSE("GPL v2");
#if IS_ENABLED(CONFIG_HID_ROCCAT_KONE_KUNIT_TEST)
#include <kunit/test.h>
/*
* Regression test for the out-of-bounds read in
* kone_keep_values_up_to_date(): a malicious USB device sends a
* "switch profile" HID event (event == kone_mouse_event_switch_profile)
* with an attacker-chosen value in 0..255, which is used unbounded as
* profiles[value - 1]. On an unpatched kernel the attack case triggers a
* KASAN slab-out-of-bounds read; the fix must leave actual_dpi unchanged.
*/
static void kone_profile_index_oob_test(struct kunit *test)
{
struct kone_device *kone;
struct kone_mouse_event ev = {};
/*
* Allocate only up to the end of profiles[] so that any index past
* the 5-element array is IMMEDIATELY out of bounds and lands in the
* KASAN redzone (a far over-read would hit unrelated valid memory and
* escape KASAN).
*/
size_t sz = offsetof(struct kone_device, profiles) +
sizeof(kone->profiles);
kone = kunit_kzalloc(test, sz, GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, kone);
kone->profiles[0].startup_dpi = 0x42;
/* benign control: a valid in-range value drives the SAME path and
* must succeed (proves the trigger reaches the real code).
*/
ev.event = kone_mouse_event_switch_profile;
ev.value = 1;
kone_keep_values_up_to_date(kone, &ev);
KUNIT_EXPECT_EQ(test, kone->actual_dpi, 0x42);
/* attack: value == ARRAY_SIZE(profiles) + 1 reads profiles[5], one
* element past the array end -> KASAN slab-out-of-bounds read on an
* unpatched kernel. The fix must reject it (actual_dpi unchanged).
*/
ev.value = ARRAY_SIZE(kone->profiles) + 1;
kone_keep_values_up_to_date(kone, &ev);
KUNIT_EXPECT_EQ(test, kone->actual_dpi, 0x42);
}
static struct kunit_case kone_test_cases[] = {
KUNIT_CASE(kone_profile_index_oob_test),
{}
};
static struct kunit_suite kone_test_suite = {
.name = "hid-roccat-kone",
.test_cases = kone_test_cases,
};
kunit_test_suite(kone_test_suite);
#endif /* CONFIG_HID_ROCCAT_KONE_KUNIT_TEST */

View File

@ -70,6 +70,15 @@ static struct roccat_device *devices[ROCCAT_MAX_DEVICES];
/* protects modifications of devices array */
static DEFINE_MUTEX(devices_lock);
static void roccat_free_device(struct roccat_device *device)
{
int i;
for (i = 0; i < ROCCAT_CBUF_SIZE; i++)
kfree(device->cbuf[i].value);
kfree(device);
}
static ssize_t roccat_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
@ -226,7 +235,7 @@ static int roccat_release(struct inode *inode, struct file *file)
hid_hw_power(device->hid, PM_HINT_NORMAL);
hid_hw_close(device->hid);
} else {
kfree(device);
roccat_free_device(device);
}
}
@ -374,7 +383,7 @@ void roccat_disconnect(int minor)
hid_hw_close(device->hid);
wake_up_interruptible(&device->wait);
} else {
kfree(device);
roccat_free_device(device);
}
}
EXPORT_SYMBOL_GPL(roccat_disconnect);

View File

@ -609,7 +609,7 @@ static int hid_sensor_custom_add_attributes(struct hid_sensor_custom
&sensor_inst->fields[i].
hid_custom_attribute_group);
if (ret)
break;
goto err_remove_groups;
/* For power or report field store indexes */
if (sensor_inst->fields[i].attribute.attrib_id ==
@ -621,6 +621,13 @@ static int hid_sensor_custom_add_attributes(struct hid_sensor_custom
}
return ret;
err_remove_groups:
while (--i >= 0)
sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
&sensor_inst->fields[i].hid_custom_attribute_group);
kfree(sensor_inst->fields);
return ret;
}
static void hid_sensor_custom_remove_attributes(struct hid_sensor_custom *
@ -1005,26 +1012,26 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
return ret;
}
ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj,
&enable_sensor_attr_group);
ret = hid_sensor_custom_add_attributes(sensor_inst);
if (ret)
goto err_remove_callback;
ret = hid_sensor_custom_add_attributes(sensor_inst);
if (ret)
goto err_remove_group;
ret = hid_sensor_custom_dev_if_add(sensor_inst);
ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj,
&enable_sensor_attr_group);
if (ret)
goto err_remove_attributes;
ret = hid_sensor_custom_dev_if_add(sensor_inst);
if (ret)
goto err_remove_group;
return 0;
err_remove_attributes:
hid_sensor_custom_remove_attributes(sensor_inst);
err_remove_group:
sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
&enable_sensor_attr_group);
err_remove_attributes:
hid_sensor_custom_remove_attributes(sensor_inst);
err_remove_callback:
sensor_hub_remove_callback(hsdev, hsdev->usage);
@ -1042,9 +1049,10 @@ static void hid_sensor_custom_remove(struct platform_device *pdev)
}
hid_sensor_custom_dev_if_remove(sensor_inst);
hid_sensor_custom_remove_attributes(sensor_inst);
/* Remove enable_sensor first as it uses fields via power_state/report_state. */
sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
&enable_sensor_attr_group);
hid_sensor_custom_remove_attributes(sensor_inst);
sensor_hub_remove_callback(hsdev, hsdev->usage);
}

View File

@ -239,12 +239,17 @@ int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id,
u32 field_index, int buffer_size, void *buffer)
{
struct hid_report *report;
struct hid_field *field;
struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev);
int report_size;
size_t field_size;
size_t report_size;
size_t copied = 0;
size_t to_copy;
int ret = 0;
u8 *val_ptr;
int buffer_index = 0;
int i;
unsigned int i;
if (!buffer || buffer_size <= 0)
return -EINVAL;
memset(buffer, 0, buffer_size);
@ -258,27 +263,30 @@ int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id,
hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT);
hid_hw_wait(hsdev->hdev);
field = report->field[field_index];
/* calculate number of bytes required to read this field */
report_size = DIV_ROUND_UP(report->field[field_index]->report_size,
8) *
report->field[field_index]->report_count;
if (!report_size) {
field_size = DIV_ROUND_UP(field->report_size, 8);
/* HID core stores each parsed report value in a __s32 slot. */
if (!field_size || field_size > sizeof(field->value[0])) {
ret = -EINVAL;
goto done_proc;
}
ret = min(report_size, buffer_size);
val_ptr = (u8 *)report->field[field_index]->value;
for (i = 0; i < report->field[field_index]->report_count; ++i) {
if (buffer_index >= ret)
break;
memcpy(&((u8 *)buffer)[buffer_index], val_ptr,
report->field[field_index]->report_size / 8);
val_ptr += sizeof(__s32);
buffer_index += (report->field[field_index]->report_size / 8);
if (field->report_count > SIZE_MAX / field_size) {
ret = -EINVAL;
goto done_proc;
}
report_size = field_size * field->report_count;
report_size = min_t(size_t, report_size, buffer_size);
for (i = 0; i < field->report_count && copied < report_size; ++i) {
to_copy = min(field_size, report_size - copied);
memcpy(&((u8 *)buffer)[copied], &field->value[i], to_copy);
copied += to_copy;
}
ret = copied;
done_proc:
mutex_unlock(&data->mutex);

View File

@ -48,68 +48,56 @@ static int hid_sjoyff_play(struct input_dev *dev, void *data,
return 0;
}
static int sjoyff_init(struct hid_device *hid)
static int sjoy_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct sjoyff_device *sjoyff;
struct hid_report *report;
struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct list_head *report_ptr = report_list;
struct input_dev *dev;
struct input_dev *dev = hidinput->input;
int error;
if (list_empty(report_list)) {
if (!list_is_first(&hidinput->list, &hid->inputs))
return 0;
report = list_first_entry_or_null(report_list, struct hid_report, list);
if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
list_for_each_entry(hidinput, &hid->inputs, list) {
report_ptr = report_ptr->next;
if (report_ptr == report_list) {
hid_err(hid, "required output report is missing\n");
return -ENODEV;
}
report = list_entry(report_ptr, struct hid_report, list);
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
}
if (report->field[0]->report_count < 3) {
hid_err(hid, "not enough values in the field\n");
return -ENODEV;
}
sjoyff = kzalloc_obj(struct sjoyff_device);
if (!sjoyff)
return -ENOMEM;
dev = hidinput->input;
set_bit(FF_RUMBLE, dev->ffbit);
sjoyff->report = report;
sjoyff->report->field[0]->value[0] = 0x01;
sjoyff->report->field[0]->value[1] = 0x00;
sjoyff->report->field[0]->value[2] = 0x00;
hid_hw_request(hid, sjoyff->report, HID_REQ_SET_REPORT);
error = input_ff_create_memless(dev, sjoyff, hid_sjoyff_play);
if (error) {
kfree(sjoyff);
return error;
}
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
}
hid_info(hid, "Force feedback for SmartJoy PLUS PS2/USB adapter\n");
if (report->field[0]->report_count < 3) {
hid_err(hid, "not enough values in the field\n");
return -ENODEV;
}
sjoyff = kzalloc_obj(struct sjoyff_device);
if (!sjoyff)
return -ENOMEM;
set_bit(FF_RUMBLE, dev->ffbit);
sjoyff->report = report;
sjoyff->report->field[0]->value[0] = 0x01;
sjoyff->report->field[0]->value[1] = 0x00;
sjoyff->report->field[0]->value[2] = 0x00;
hid_hw_request(hid, sjoyff->report, HID_REQ_SET_REPORT);
error = input_ff_create_memless(dev, sjoyff, hid_sjoyff_play);
if (error) {
kfree(sjoyff);
return error;
}
return 0;
}
#else
static inline int sjoyff_init(struct hid_device *hid)
static inline int sjoy_input_configured(struct hid_device *hid,
struct hid_input *hidinput)
{
return 0;
}
@ -124,20 +112,16 @@ static int sjoy_probe(struct hid_device *hdev, const struct hid_device_id *id)
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
return ret;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
return ret;
}
sjoyff_init(hdev);
return 0;
err:
return ret;
}
static const struct hid_device_id sjoy_devices[] = {
@ -165,6 +149,7 @@ static struct hid_driver sjoy_driver = {
.name = "smartjoyplus",
.id_table = sjoy_devices,
.probe = sjoy_probe,
.input_configured = sjoy_input_configured,
};
module_hid_driver(sjoy_driver);

View File

@ -29,6 +29,7 @@
* There will be no PIN request from the device.
*/
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
@ -81,6 +82,7 @@
#define SONY_FF_SUPPORT (SIXAXIS_CONTROLLER | MOTION_CONTROLLER)
#define SONY_BT_DEVICE (SIXAXIS_CONTROLLER_BT | MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER_BT)
#define NSG_MRXU_REMOTE (NSG_MR5U_REMOTE_BT | NSG_MR7U_REMOTE_BT)
#define RB4_GUITAR_PS4 (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT)
#define MAX_LEDS 4
#define NSG_MRXU_MAX_X 1667
@ -503,7 +505,7 @@ struct motion_output_report_02 {
u8 r, g, b;
u8 zero2;
u8 rumble;
};
} __packed;
static_assert(sizeof(struct motion_output_report_02) == 7);
#define SIXAXIS_REPORT_0xF2_SIZE 17
@ -521,10 +523,6 @@ static DEFINE_SPINLOCK(sony_dev_list_lock);
static LIST_HEAD(sony_device_list);
static DEFINE_IDA(sony_device_id_allocator);
enum sony_worker {
SONY_WORKER_STATE
};
struct sony_sc {
spinlock_t lock;
struct list_head list_node;
@ -534,6 +532,7 @@ struct sony_sc {
struct input_dev *sensor_dev;
struct led_classdev *leds[MAX_LEDS];
unsigned long quirks;
int (*raw_event)(struct sony_sc *sc, u8 *rd, int size);
struct work_struct state_worker;
void (*send_output_report)(struct sony_sc *sc);
struct power_supply *battery;
@ -566,19 +565,11 @@ struct sony_sc {
static void sony_set_leds(struct sony_sc *sc);
static inline void sony_schedule_work(struct sony_sc *sc,
enum sony_worker which)
static inline void sony_schedule_work(struct sony_sc *sc)
{
unsigned long flags;
switch (which) {
case SONY_WORKER_STATE:
spin_lock_irqsave(&sc->lock, flags);
if (!sc->defer_initialization && sc->state_worker_initialized)
schedule_work(&sc->state_worker);
spin_unlock_irqrestore(&sc->lock, flags);
break;
}
guard(spinlock_irqsave)(&sc->lock);
if (!sc->defer_initialization && sc->state_worker_initialized)
schedule_work(&sc->state_worker);
}
static void ghl_magic_poke_cb(struct urb *urb)
@ -946,15 +937,39 @@ static const u8 *sony_report_fixup(struct hid_device *hdev, u8 *rdesc,
return rdesc;
}
static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size)
static int sixaxis_raw_event(struct sony_sc *sc, u8 *rd, int size)
{
static const u8 sixaxis_battery_capacity[] = { 0, 1, 25, 50, 75, 100 };
unsigned long flags;
int offset;
u8 index;
u8 battery_capacity;
int battery_status;
if (unlikely(size != 49 || rd[0] != 0x01))
return 0;
if (sc->quirks & SIXAXIS_CONTROLLER) {
/*
* When connected via Bluetooth the Sixaxis occasionally sends
* a report with the second byte 0xff and the rest zeroed.
*
* This report does not reflect the actual state of the
* controller must be ignored to avoid generating false input
* events.
*/
if (rd[1] == 0xff)
return -EINVAL;
/*
* Sixaxis HID report has acclerometers/gyro with MSByte first, this
* has to be BYTE_SWAPPED before passing up to joystick interface
*/
swap(rd[41], rd[42]);
swap(rd[43], rd[44]);
swap(rd[45], rd[46]);
swap(rd[47], rd[48]);
}
/*
* The sixaxis is charging if the battery value is 0xee
* and it is fully charged if the value is 0xef.
@ -972,10 +987,10 @@ static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size)
battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
}
spin_lock_irqsave(&sc->lock, flags);
sc->battery_capacity = battery_capacity;
sc->battery_status = battery_status;
spin_unlock_irqrestore(&sc->lock, flags);
scoped_guard(spinlock_irqsave, &sc->lock) {
sc->battery_capacity = battery_capacity;
sc->battery_status = battery_status;
}
if (sc->quirks & SIXAXIS_CONTROLLER) {
int val;
@ -993,13 +1008,18 @@ static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size)
input_sync(sc->sensor_dev);
}
return 0;
}
static void nsg_mrxu_parse_report(struct sony_sc *sc, u8 *rd, int size)
static int nsg_mrxu_raw_event(struct sony_sc *sc, u8 *rd, int size)
{
int n, offset, relx, rely;
u8 active;
if (unlikely(size < 12 || rd[0] != 0x02))
return 0;
/*
* The NSG-MRxU multi-touch trackpad data starts at offset 1 and
* the touch-related data starts at offset 2.
@ -1067,10 +1087,33 @@ static void nsg_mrxu_parse_report(struct sony_sc *sc, u8 *rd, int size)
input_mt_sync_frame(sc->touchpad);
input_sync(sc->touchpad);
return 0;
}
static void rb4_ps4_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
static int rb3_pro_instrument_raw_event(struct sony_sc *sc, u8 *rd, int size)
{
/* Rock Band 3 PS3 Pro instruments set rd[24] to 0xE0 when they're
* sending full reports, and 0x02 when only sending navigation.
*/
if (size < 25 || rd[24] != 0x02)
return 0;
/* Only attempt to enable full report every 8 seconds */
if (time_after(jiffies, sc->rb3_pro_poke_jiffies)) {
sc->rb3_pro_poke_jiffies = jiffies + secs_to_jiffies(8);
rb3_pro_instrument_enable_full_report(sc);
}
return 0;
}
static int rb4_ps4_guitar_raw_event(struct sony_sc *sc, u8 *rd, int size)
{
const int expected_size = (sc->quirks & RB4_GUITAR_PS4_BT) ? 78 : 64;
if (unlikely(size != expected_size || rd[0] != 0x01))
return 0;
/*
* Rock Band 4 PS4 guitars have whammy and
* tilt functionality, they're located at
@ -1084,15 +1127,18 @@ static void rb4_ps4_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
input_report_abs(sc->input_dev, ABS_RZ, rd[45]);
input_sync(sc->input_dev);
return 0;
}
static void rb4_ps5_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
static int rb4_ps5_guitar_raw_event(struct sony_sc *sc, u8 *rd, int size)
{
u8 charging_status;
u8 battery_data;
u8 battery_capacity;
u8 battery_status;
unsigned long flags;
if (unlikely(size != 64 || rd[0] != 0x01))
return 0;
/*
* Rock Band 4 PS5 guitars have whammy and
@ -1132,73 +1178,30 @@ static void rb4_ps5_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
break;
}
spin_lock_irqsave(&sc->lock, flags);
sc->battery_capacity = battery_capacity;
sc->battery_status = battery_status;
spin_unlock_irqrestore(&sc->lock, flags);
scoped_guard(spinlock_irqsave, &sc->lock) {
sc->battery_capacity = battery_capacity;
sc->battery_status = battery_status;
}
input_sync(sc->input_dev);
return 0;
}
static int sony_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *rd, int size)
{
struct sony_sc *sc = hid_get_drvdata(hdev);
int ret;
/*
* Sixaxis HID report has acclerometers/gyro with MSByte first, this
* has to be BYTE_SWAPPED before passing up to joystick interface
*/
if ((sc->quirks & SIXAXIS_CONTROLLER) && rd[0] == 0x01 && size == 49) {
/*
* When connected via Bluetooth the Sixaxis occasionally sends
* a report with the second byte 0xff and the rest zeroed.
*
* This report does not reflect the actual state of the
* controller must be ignored to avoid generating false input
* events.
*/
if (rd[1] == 0xff)
return -EINVAL;
swap(rd[41], rd[42]);
swap(rd[43], rd[44]);
swap(rd[45], rd[46]);
swap(rd[47], rd[48]);
sixaxis_parse_report(sc, rd, size);
} else if ((sc->quirks & MOTION_CONTROLLER_BT) && rd[0] == 0x01 && size == 49) {
sixaxis_parse_report(sc, rd, size);
} else if ((sc->quirks & NAVIGATION_CONTROLLER) && rd[0] == 0x01 && size == 49) {
sixaxis_parse_report(sc, rd, size);
} else if ((sc->quirks & NSG_MRXU_REMOTE) && rd[0] == 0x02 && size >= 12) {
nsg_mrxu_parse_report(sc, rd, size);
return 1;
} else if ((sc->quirks & RB4_GUITAR_PS4_USB) && rd[0] == 0x01 && size == 64) {
rb4_ps4_guitar_parse_report(sc, rd, size);
return 1;
} else if ((sc->quirks & RB4_GUITAR_PS4_BT) && rd[0] == 0x01 && size == 78) {
rb4_ps4_guitar_parse_report(sc, rd, size);
return 1;
} else if ((sc->quirks & RB4_GUITAR_PS5) && rd[0] == 0x01 && size == 64) {
rb4_ps5_guitar_parse_report(sc, rd, size);
return 1;
if (sc->raw_event) {
ret = sc->raw_event(sc, rd, size);
if (unlikely(ret < 0))
return ret;
}
/* Rock Band 3 PS3 Pro instruments set rd[24] to 0xE0 when they're
* sending full reports, and 0x02 when only sending navigation.
*/
if ((sc->quirks & RB3_PRO_INSTRUMENT) && size >= 25 && rd[24] == 0x02) {
/* Only attempt to enable full report every 8 seconds */
if (time_after(jiffies, sc->rb3_pro_poke_jiffies)) {
sc->rb3_pro_poke_jiffies = jiffies + secs_to_jiffies(8);
rb3_pro_instrument_enable_full_report(sc);
}
}
if (sc->defer_initialization) {
if (unlikely(sc->defer_initialization)) {
sc->defer_initialization = 0;
sony_schedule_work(sc, SONY_WORKER_STATE);
sony_schedule_work(sc);
}
return 0;
@ -1256,7 +1259,7 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
if (sc->quirks & DJH_TURNTABLE)
return djh_turntable_mapping(hdev, hi, field, usage, bit, max);
if (sc->quirks & (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT))
if (sc->quirks & RB4_GUITAR_PS4)
return rb4_guitar_mapping(hdev, hi, field, usage, bit, max);
if (sc->quirks & RB4_GUITAR_PS5)
@ -1269,8 +1272,6 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
static int sony_register_touchpad(struct sony_sc *sc, int touch_count,
int w, int h, int touch_major, int touch_minor, int orientation)
{
size_t name_sz;
char *name;
int ret;
sc->touchpad = devm_input_allocate_device(&sc->hdev->dev);
@ -1292,12 +1293,10 @@ static int sony_register_touchpad(struct sony_sc *sc, int touch_count,
* a suffix. Other devices which were added later like Sony TV remotes
* inhirited this suffix.
*/
name_sz = strlen(sc->hdev->name) + sizeof(TOUCHPAD_SUFFIX);
name = devm_kzalloc(&sc->hdev->dev, name_sz, GFP_KERNEL);
if (!name)
sc->touchpad->name = devm_kasprintf(&sc->hdev->dev, GFP_KERNEL, "%s" TOUCHPAD_SUFFIX,
sc->hdev->name);
if (!sc->touchpad->name)
return -ENOMEM;
snprintf(name, name_sz, "%s" TOUCHPAD_SUFFIX, sc->hdev->name);
sc->touchpad->name = name;
/* We map the button underneath the touchpad to BTN_LEFT. */
__set_bit(EV_KEY, sc->touchpad->evbit);
@ -1334,8 +1333,6 @@ static int sony_register_touchpad(struct sony_sc *sc, int touch_count,
static int sony_register_sensors(struct sony_sc *sc)
{
size_t name_sz;
char *name;
int ret;
sc->sensor_dev = devm_input_allocate_device(&sc->hdev->dev);
@ -1354,12 +1351,10 @@ static int sony_register_sensors(struct sony_sc *sc)
/* Append a suffix to the controller name as there are various
* DS4 compatible non-Sony devices with different names.
*/
name_sz = strlen(sc->hdev->name) + sizeof(SENSOR_SUFFIX);
name = devm_kzalloc(&sc->hdev->dev, name_sz, GFP_KERNEL);
if (!name)
sc->sensor_dev->name = devm_kasprintf(&sc->hdev->dev, GFP_KERNEL, "%s" SENSOR_SUFFIX,
sc->hdev->name);
if (!sc->sensor_dev->name)
return -ENOMEM;
snprintf(name, name_sz, "%s" SENSOR_SUFFIX, sc->hdev->name);
sc->sensor_dev->name = name;
if (sc->quirks & SIXAXIS_CONTROLLER) {
/* For the DS3 we only support the accelerometer, which works
@ -1507,7 +1502,7 @@ static void buzz_set_leds(struct sony_sc *sc)
static void sony_set_leds(struct sony_sc *sc)
{
if (!(sc->quirks & BUZZ_CONTROLLER))
sony_schedule_work(sc, SONY_WORKER_STATE);
sony_schedule_work(sc);
else
buzz_set_leds(sc);
}
@ -1618,7 +1613,7 @@ static int sony_led_blink_set(struct led_classdev *led, unsigned long *delay_on,
new_off != drv_data->led_delay_off[n]) {
drv_data->led_delay_on[n] = new_on;
drv_data->led_delay_off[n] = new_off;
sony_schedule_work(drv_data, SONY_WORKER_STATE);
sony_schedule_work(drv_data);
}
return 0;
@ -1846,7 +1841,7 @@ static int sony_play_effect(struct input_dev *dev, void *data,
sc->left = effect->u.rumble.strong_magnitude / 256;
sc->right = effect->u.rumble.weak_magnitude / 256;
sony_schedule_work(sc, SONY_WORKER_STATE);
sony_schedule_work(sc);
return 0;
}
@ -1869,15 +1864,14 @@ static int sony_battery_get_property(struct power_supply *psy,
union power_supply_propval *val)
{
struct sony_sc *sc = power_supply_get_drvdata(psy);
unsigned long flags;
int ret = 0;
u8 battery_capacity;
int battery_status;
spin_lock_irqsave(&sc->lock, flags);
battery_capacity = sc->battery_capacity;
battery_status = sc->battery_status;
spin_unlock_irqrestore(&sc->lock, flags);
scoped_guard(spinlock_irqsave, &sc->lock) {
battery_capacity = sc->battery_capacity;
battery_status = sc->battery_status;
}
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
@ -1959,10 +1953,9 @@ static inline int sony_compare_connection_type(struct sony_sc *sc0,
static int sony_check_add_dev_list(struct sony_sc *sc)
{
struct sony_sc *entry;
unsigned long flags;
int ret;
spin_lock_irqsave(&sony_dev_list_lock, flags);
guard(spinlock_irqsave)(&sony_dev_list_lock);
list_for_each_entry(entry, &sony_device_list, list_node) {
ret = memcmp(sc->mac_address, entry->mac_address,
@ -1976,27 +1969,23 @@ static int sony_check_add_dev_list(struct sony_sc *sc)
"controller with MAC address %pMR already connected\n",
sc->mac_address);
}
goto unlock;
goto out;
}
}
ret = 0;
list_add(&(sc->list_node), &sony_device_list);
unlock:
spin_unlock_irqrestore(&sony_dev_list_lock, flags);
out:
return ret;
}
static void sony_remove_dev_list(struct sony_sc *sc)
{
unsigned long flags;
guard(spinlock_irqsave)(&sony_dev_list_lock);
if (sc->list_node.next) {
spin_lock_irqsave(&sony_dev_list_lock, flags);
list_del(&(sc->list_node));
spin_unlock_irqrestore(&sony_dev_list_lock, flags);
}
if (!list_empty(&sc->list_node))
list_del_init(&sc->list_node);
}
static int sony_get_bt_devaddr(struct sony_sc *sc)
@ -2110,6 +2099,12 @@ static void sony_release_device_id(struct sony_sc *sc)
}
}
static inline void sony_init_raw_event_handler(struct sony_sc *sc,
int (*raw_event)(struct sony_sc *, u8 *, int))
{
sc->raw_event = raw_event;
}
static inline void sony_init_output_report(struct sony_sc *sc,
void (*send_output_report)(struct sony_sc *))
{
@ -2123,16 +2118,21 @@ static inline void sony_init_output_report(struct sony_sc *sc,
static inline void sony_cancel_work_sync(struct sony_sc *sc)
{
unsigned long flags;
if (sc->state_worker_initialized) {
spin_lock_irqsave(&sc->lock, flags);
sc->state_worker_initialized = 0;
spin_unlock_irqrestore(&sc->lock, flags);
scoped_guard(spinlock_irqsave, &sc->lock) {
sc->state_worker_initialized = 0;
}
cancel_work_sync(&sc->state_worker);
}
}
static void sony_cleanup(struct sony_sc *sc)
{
sony_cancel_work_sync(sc);
sony_remove_dev_list(sc);
sony_release_device_id(sc);
}
static int sony_input_configured(struct hid_device *hdev,
struct hid_input *hidinput)
{
@ -2185,6 +2185,7 @@ static int sony_input_configured(struct hid_device *hdev,
goto err_stop;
}
sony_init_raw_event_handler(sc, sixaxis_raw_event);
sony_init_output_report(sc, sixaxis_send_output_report);
} else if (sc->quirks & NAVIGATION_CONTROLLER_BT) {
/*
@ -2199,6 +2200,7 @@ static int sony_input_configured(struct hid_device *hdev,
goto err_stop;
}
sony_init_raw_event_handler(sc, sixaxis_raw_event);
sony_init_output_report(sc, sixaxis_send_output_report);
} else if (sc->quirks & RB3_PRO_INSTRUMENT) {
/*
@ -2213,6 +2215,8 @@ static int sony_input_configured(struct hid_device *hdev,
*/
hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP;
hdev->quirks |= HID_QUIRK_SKIP_OUTPUT_REPORT_ID;
sony_init_raw_event_handler(sc, rb3_pro_instrument_raw_event);
} else if (sc->quirks & SIXAXIS_CONTROLLER_USB) {
/*
* The Sony Sixaxis does not handle HID Output Reports on the
@ -2237,6 +2241,7 @@ static int sony_input_configured(struct hid_device *hdev,
goto err_stop;
}
sony_init_raw_event_handler(sc, sixaxis_raw_event);
sony_init_output_report(sc, sixaxis_send_output_report);
} else if (sc->quirks & SIXAXIS_CONTROLLER_BT) {
/*
@ -2258,6 +2263,7 @@ static int sony_input_configured(struct hid_device *hdev,
goto err_stop;
}
sony_init_raw_event_handler(sc, sixaxis_raw_event);
sony_init_output_report(sc, sixaxis_send_output_report);
} else if (sc->quirks & NSG_MRXU_REMOTE) {
/*
@ -2273,8 +2279,15 @@ static int sony_input_configured(struct hid_device *hdev,
goto err_stop;
}
sony_init_raw_event_handler(sc, nsg_mrxu_raw_event);
} else if (sc->quirks & MOTION_CONTROLLER) {
if (sc->quirks & MOTION_CONTROLLER_BT)
sony_init_raw_event_handler(sc, sixaxis_raw_event);
sony_init_output_report(sc, motion_send_output_report);
} else if (sc->quirks & RB4_GUITAR_PS4) {
sony_init_raw_event_handler(sc, rb4_ps4_guitar_raw_event);
} else if (sc->quirks & RB4_GUITAR_PS5) {
sony_init_raw_event_handler(sc, rb4_ps5_guitar_raw_event);
}
if (sc->quirks & SONY_LED_SUPPORT) {
@ -2306,9 +2319,7 @@ static int sony_input_configured(struct hid_device *hdev,
err_close:
hid_hw_close(hdev);
err_stop:
sony_cancel_work_sync(sc);
sony_remove_dev_list(sc);
sony_release_device_id(sc);
sony_cleanup(sc);
return ret;
}
@ -2332,6 +2343,8 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
return -ENOMEM;
spin_lock_init(&sc->lock);
INIT_LIST_HEAD(&sc->list_node);
sc->device_id = -1;
sc->quirks = quirks;
hid_set_drvdata(hdev, sc);
@ -2360,6 +2373,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
ret = hid_hw_start(hdev, connect_mask);
if (ret) {
hid_err(hdev, "hw start failed\n");
sony_cleanup(sc);
return ret;
}
@ -2414,7 +2428,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
err:
usb_free_urb(sc->ghl_urb);
sony_cleanup(sc);
hid_hw_stop(hdev);
return ret;
}
@ -2424,18 +2438,14 @@ static void sony_remove(struct hid_device *hdev)
struct sony_sc *sc = hid_get_drvdata(hdev);
if (sc->quirks & (GHL_GUITAR_PS3WIIU | GHL_GUITAR_PS4)) {
timer_delete_sync(&sc->ghl_poke_timer);
/* poison, not kill: a pending timer must not re-submit during teardown */
usb_poison_urb(sc->ghl_urb);
timer_shutdown_sync(&sc->ghl_poke_timer);
usb_free_urb(sc->ghl_urb);
}
hid_hw_close(hdev);
sony_cancel_work_sync(sc);
sony_remove_dev_list(sc);
sony_release_device_id(sc);
sony_cleanup(sc);
hid_hw_stop(hdev);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,718 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* HID driver for Steelseries arctis headsets
*
* Copyright (c) 2023 Bastien Nocera
* Copyright (c) 2026 Sriman Achanta
*/
#include <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_9_request_status(struct hid_device *hdev)
{
const u8 data[] = { 0x00, 0x20 };
return steelseries_send_output_report(hdev, data, sizeof(data));
}
static int steelseries_arctis_nova_request_status(struct hid_device *hdev)
{
const u8 data[] = { 0x00, 0xb0 };
return steelseries_send_output_report(hdev, data, sizeof(data));
}
/*
* Headset battery helpers
*/
static int battery_capacity_to_level(int capacity)
{
if (capacity >= 50)
return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
if (capacity >= 20)
return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
}
static u8 steelseries_map_capacity(u8 capacity, u8 min_in, u8 max_in)
{
if (capacity >= max_in)
return 100;
if (capacity <= min_in)
return 0;
return (capacity - min_in) * 100 / (max_in - min_in);
}
/*
* Headset status parse functions
*/
static void steelseries_arctis_1_parse_status(struct steelseries_device *sd,
u8 *data, int size)
{
/* Only the battery status report echoes the request header. */
if (size < 8 || data[0] != 0x06 || data[1] != 0x12)
return;
sd->headset_connected = (data[2] != 0x01);
sd->battery_capacity = data[3];
}
static void steelseries_arctis_9_parse_status(struct steelseries_device *sd,
u8 *data, int size)
{
if (size < 5)
return;
if (data[0] == 0xaa && data[1] == 0x01) {
sd->headset_connected = true;
sd->battery_charging = (data[4] == 0x01);
sd->battery_capacity = steelseries_map_capacity(data[3], 0x64, 0x9a);
} else {
/* Device off: 0x55 (no status) or 0x03 (stale status). */
sd->headset_connected = false;
sd->battery_charging = false;
}
}
static void steelseries_arctis_nova_parse_status(struct steelseries_device *sd,
u8 *data, int size)
{
if (size < 2)
return;
switch (data[0]) {
case 0xb0:
if (size < 4)
return;
sd->headset_connected = (data[1] == 0x03);
sd->battery_capacity = data[2];
sd->battery_charging = (data[3] == 0x01);
break;
case 0xb7:
sd->battery_capacity = data[1];
break;
case 0xb9:
sd->headset_connected = (data[1] == 0x03);
break;
case 0xbb:
sd->battery_charging = (data[1] == 0x01);
break;
}
}
static void steelseries_arctis_nova_7_parse_status(struct steelseries_device *sd,
u8 *data, int size)
{
if (size < 2)
return;
switch (data[0]) {
case 0xb0:
if (size < 4)
return;
sd->headset_connected = (data[1] == 0x03);
sd->battery_capacity = steelseries_map_capacity(data[2], 0, 4);
sd->battery_charging = (data[3] == 0x01);
break;
case 0xb7:
sd->battery_capacity = steelseries_map_capacity(data[1], 0, 4);
break;
case 0xb9:
sd->headset_connected = (data[1] == 0x03);
break;
case 0xbb:
sd->battery_charging = (data[1] == 0x01);
break;
}
}
/*
* Device info definitions
*/
static const struct steelseries_device_info arctis_1_info = {
.sync_interface = 3,
.capabilities = SS_CAP_BATTERY,
.request_status = steelseries_arctis_1_request_status,
.parse_status = steelseries_arctis_1_parse_status,
};
static const struct steelseries_device_info arctis_9_info = {
.sync_interface = 0,
.capabilities = SS_CAP_BATTERY,
.request_status = steelseries_arctis_9_request_status,
.parse_status = steelseries_arctis_9_parse_status,
};
static const struct steelseries_device_info arctis_nova_info = {
.sync_interface = 3,
.async_interface = 5,
.capabilities = SS_CAP_BATTERY,
.request_status = steelseries_arctis_nova_request_status,
.parse_status = steelseries_arctis_nova_parse_status,
};
static const struct steelseries_device_info arctis_nova_7_info = {
.sync_interface = 3,
.async_interface = 5,
.capabilities = SS_CAP_BATTERY,
.request_status = steelseries_arctis_nova_request_status,
.parse_status = steelseries_arctis_nova_7_parse_status,
};
/*
* Headset wireless status and battery infrastructure
*/
#define STEELSERIES_HEADSET_STATUS_TIMEOUT_MS 3000
static void
steelseries_headset_set_wireless_status(struct hid_device *hdev,
bool connected)
{
struct usb_interface *intf;
if (!hid_is_usb(hdev))
return;
intf = to_usb_interface(hdev->dev.parent);
usb_set_wireless_status(intf, connected ?
USB_WIRELESS_STATUS_CONNECTED :
USB_WIRELESS_STATUS_DISCONNECTED);
}
#define STEELSERIES_PREFIX "SteelSeries "
static int steelseries_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct steelseries_device *sd = power_supply_get_drvdata(psy);
size_t prefix_len;
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = sd->hdev->name;
while ((prefix_len = str_has_prefix(val->strval, STEELSERIES_PREFIX)))
val->strval += prefix_len;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = "SteelSeries";
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_STATUS:
if (!sd->headset_connected)
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
else if (sd->battery_charging)
val->intval = sd->battery_capacity >= 100 ?
POWER_SUPPLY_STATUS_FULL :
POWER_SUPPLY_STATUS_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = sd->battery_capacity;
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
val->intval = battery_capacity_to_level(sd->battery_capacity);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static enum power_supply_property steelseries_battery_props[] = {
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
};
/*
* Delayed work handlers for status polling
*/
static void steelseries_status_timer_work_handler(struct work_struct *work)
{
struct steelseries_device *sd = container_of(
work, struct steelseries_device, status_work.work);
unsigned long flags;
sd->info->request_status(sd->hdev);
spin_lock_irqsave(&sd->lock, flags);
/* Async devices push status events themselves; only poll once. */
if (!sd->removed && !sd->info->async_interface)
schedule_delayed_work(&sd->status_work,
msecs_to_jiffies(STEELSERIES_HEADSET_STATUS_TIMEOUT_MS));
spin_unlock_irqrestore(&sd->lock, flags);
}
static int steelseries_battery_register(struct steelseries_device *sd)
{
struct power_supply_config battery_cfg = { .drv_data = sd, };
struct power_supply *battery;
int ret;
sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
sd->battery_desc.properties = steelseries_battery_props;
sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_battery_props);
sd->battery_desc.get_property = steelseries_battery_get_property;
sd->battery_desc.use_for_apm = 0;
sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
"steelseries_headset_battery_%s",
sd->hdev->uniq[0] ? sd->hdev->uniq :
dev_name(&sd->hdev->dev));
if (!sd->battery_desc.name)
return -ENOMEM;
/* avoid the warning of 0% battery while waiting for the first info */
sd->battery_capacity = 100;
sd->battery_charging = false;
sd->headset_connected = false;
steelseries_headset_set_wireless_status(sd->hdev, false);
battery = power_supply_register(&sd->hdev->dev,
&sd->battery_desc, &battery_cfg);
if (IS_ERR(battery)) {
ret = PTR_ERR(battery);
hid_err(sd->hdev,
"%s:power_supply_register failed with error %d\n",
__func__, ret);
return ret;
}
power_supply_powers(battery, &sd->hdev->dev);
/* Assign on success only, so a concurrent raw_event never sees an ERR_PTR. */
sd->battery = battery;
return 0;
}
static struct hid_driver steelseries_arctis_driver;
static struct steelseries_device *
steelseries_get_sibling_sd(struct hid_device *hdev, int interface_num)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
struct usb_device *usb_dev = interface_to_usbdev(intf);
struct usb_interface *sibling_intf;
struct hid_device *sibling_hdev;
struct steelseries_device *sd = NULL;
sibling_intf = usb_ifnum_to_if(usb_dev, interface_num);
if (!sibling_intf)
return NULL;
/*
* usb_get_intfdata() only yields a hid_device when usbhid is bound;
* gate on the descriptor class so a non-HID sibling (e.g. a crafted
* device exposing storage or audio here) is never treated as one.
*/
if (sibling_intf->cur_altsetting->desc.bInterfaceClass != USB_INTERFACE_CLASS_HID)
return NULL;
/*
* Take the sibling's device lock across the intfdata read and the
* kref_get so a concurrent unbind cannot free the hid_device underneath
* us; usbhid leaves intfdata dangling on disconnect, so dev.driver is
* the reliable "still bound" test under this lock. Use device_trylock()
* to stay off the lockdep chain of the interface being probed and let
* the caller retry via -EPROBE_DEFER if the sibling is momentarily busy.
*/
if (!device_trylock(&sibling_intf->dev))
return NULL;
if (sibling_intf->dev.driver) {
sibling_hdev = usb_get_intfdata(sibling_intf);
if (sibling_hdev &&
sibling_hdev->driver == &steelseries_arctis_driver) {
sd = hid_get_drvdata(sibling_hdev);
if (sd)
kref_get(&sd->refcnt);
}
}
device_unlock(&sibling_intf->dev);
return sd;
}
static int steelseries_arctis_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
const struct steelseries_device_info *info =
(const struct steelseries_device_info *)id->driver_data;
struct steelseries_device *sd;
struct usb_interface *intf;
u8 interface_num;
int ret;
if (hid_is_usb(hdev)) {
intf = to_usb_interface(hdev->dev.parent);
interface_num = intf->cur_altsetting->desc.bInterfaceNumber;
} else {
return -ENODEV;
}
ret = hid_parse(hdev);
if (ret)
return ret;
/* Let hid-generic handle non-vendor or unknown interfaces */
if (interface_num != info->sync_interface &&
(!info->async_interface || interface_num != info->async_interface))
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (interface_num == info->sync_interface) {
sd = kzalloc_obj(*sd, GFP_KERNEL);
if (!sd)
return -ENOMEM;
kref_init(&sd->refcnt);
sd->hdev = hdev;
sd->info = info;
spin_lock_init(&sd->lock);
INIT_DELAYED_WORK(&sd->status_work, steelseries_status_timer_work_handler);
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret)
goto err_free;
ret = hid_hw_open(hdev);
if (ret)
goto err_stop;
if (info->capabilities & SS_CAP_BATTERY) {
ret = steelseries_battery_register(sd);
if (ret < 0)
hid_warn(hdev, "Failed to register battery: %d\n", ret);
}
/*
* Publish drvdata only once fully initialised: the async sibling
* attaches by reading it, so it must never observe a half-built or
* failed instance. A failed probe never gets here, so the error
* path below has nothing to unpublish.
*/
hid_set_drvdata(hdev, sd);
schedule_delayed_work(&sd->status_work, msecs_to_jiffies(100));
return 0;
}
/*
* The async interface shares the steelseries_device created by the
* sync interface. Defer until the sync interface has probed and
* published its drvdata.
*/
if (info->async_interface && interface_num == info->async_interface) {
sd = steelseries_get_sibling_sd(hdev, info->sync_interface);
if (!sd)
return -EPROBE_DEFER;
hid_set_drvdata(hdev, sd);
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
kref_put(&sd->refcnt, steelseries_device_release);
return ret;
}
ret = hid_hw_open(hdev);
if (ret) {
hid_hw_stop(hdev);
kref_put(&sd->refcnt, steelseries_device_release);
return ret;
}
return 0;
}
return -ENODEV;
err_stop:
hid_hw_stop(hdev);
err_free:
/* drvdata is unpublished until full success, so no sibling can hold sd. */
kref_put(&sd->refcnt, steelseries_device_release);
return ret;
}
static void steelseries_arctis_remove(struct hid_device *hdev)
{
struct steelseries_device *sd;
struct power_supply *battery;
unsigned long flags;
struct usb_interface *intf;
u8 interface_num;
if (hid_is_usb(hdev)) {
intf = to_usb_interface(hdev->dev.parent);
interface_num = intf->cur_altsetting->desc.bInterfaceNumber;
} else {
return;
}
sd = hid_get_drvdata(hdev);
if (!sd) {
hid_hw_stop(hdev);
return;
}
if (interface_num == sd->info->sync_interface) {
spin_lock_irqsave(&sd->lock, flags);
sd->removed = true;
battery = sd->battery;
sd->battery = NULL;
spin_unlock_irqrestore(&sd->lock, flags);
cancel_delayed_work_sync(&sd->status_work);
if (battery)
power_supply_unregister(battery);
}
hid_hw_close(hdev);
hid_hw_stop(hdev);
kref_put(&sd->refcnt, steelseries_device_release);
}
static int steelseries_arctis_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
struct steelseries_device *sd = hid_get_drvdata(hdev);
u8 old_capacity;
bool old_connected;
bool old_charging;
bool is_async_interface;
unsigned long flags;
if (!sd)
return 0;
is_async_interface = (hdev != sd->hdev);
spin_lock_irqsave(&sd->lock, flags);
if (sd->removed) {
spin_unlock_irqrestore(&sd->lock, flags);
return 0;
}
old_capacity = sd->battery_capacity;
old_connected = sd->headset_connected;
old_charging = sd->battery_charging;
sd->info->parse_status(sd, data, size);
if (sd->headset_connected != old_connected) {
hid_dbg(hdev,
"Connected status changed from %sconnected to %sconnected\n",
old_connected ? "" : "not ",
sd->headset_connected ? "" : "not ");
if (sd->headset_connected && !old_connected &&
sd->info->async_interface && is_async_interface)
schedule_delayed_work(&sd->status_work, 0);
if (sd->battery) {
steelseries_headset_set_wireless_status(sd->hdev,
sd->headset_connected);
power_supply_changed(sd->battery);
}
}
if (sd->battery_capacity != old_capacity) {
hid_dbg(hdev, "Battery capacity changed from %d%% to %d%%\n",
old_capacity, sd->battery_capacity);
if (sd->battery)
power_supply_changed(sd->battery);
}
if (sd->battery_charging != old_charging) {
hid_dbg(hdev,
"Battery charging status changed from %scharging to %scharging\n",
old_charging ? "" : "not ",
sd->battery_charging ? "" : "not ");
if (sd->battery)
power_supply_changed(sd->battery);
}
spin_unlock_irqrestore(&sd->lock, flags);
return 0;
}
static const struct hid_device_id steelseries_arctis_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X),
.driver_data = (unsigned long)&arctis_1_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = (unsigned long)&arctis_9_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X),
.driver_data = (unsigned long)&arctis_nova_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7),
.driver_data = (unsigned long)&arctis_nova_7_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X),
.driver_data = (unsigned long)&arctis_nova_7_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2),
.driver_data = (unsigned long)&arctis_nova_7_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO),
.driver_data = (unsigned long)&arctis_nova_7_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW),
.driver_data = (unsigned long)&arctis_nova_7_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026),
.driver_data = (unsigned long)&arctis_nova_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026),
.driver_data = (unsigned long)&arctis_nova_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026),
.driver_data = (unsigned long)&arctis_nova_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026),
.driver_data = (unsigned long)&arctis_nova_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2),
.driver_data = (unsigned long)&arctis_nova_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2),
.driver_data = (unsigned long)&arctis_nova_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2),
.driver_data = (unsigned long)&arctis_nova_info },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3),
.driver_data = (unsigned long)&arctis_nova_info },
{}
};
MODULE_DEVICE_TABLE(hid, steelseries_arctis_devices);
static struct hid_driver steelseries_arctis_driver = {
.name = "hid-steelseries-arctis",
.id_table = steelseries_arctis_devices,
.probe = steelseries_arctis_probe,
.remove = steelseries_arctis_remove,
.raw_event = steelseries_arctis_raw_event,
};
module_hid_driver(steelseries_arctis_driver);
MODULE_DESCRIPTION("HID driver for Steelseries arctis headsets");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Christian Mayer <git@mayer-bgk.de>");
MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
MODULE_AUTHOR("Sriman Achanta <srimanachanta@gmail.com>");

View File

@ -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 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
}
#endif
#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
#define ARCTIS_1_BATTERY_RESPONSE_LEN 8
#define ARCTIS_9_BATTERY_RESPONSE_LEN 64
static const char arctis_1_battery_request[] = { 0x06, 0x12 };
static const char arctis_9_battery_request[] = { 0x00, 0x20 };
static int steelseries_headset_request_battery(struct hid_device *hdev,
const char *request, size_t len)
{
u8 *write_buf;
int ret;
/* Request battery information */
write_buf = kmemdup(request, len, GFP_KERNEL);
if (!write_buf)
return -ENOMEM;
hid_dbg(hdev, "Sending battery request report");
ret = hid_hw_raw_request(hdev, request[0], write_buf, len,
HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
if (ret < (int)len) {
hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
ret = -ENODATA;
}
kfree(write_buf);
return ret;
}
static void steelseries_headset_fetch_battery(struct hid_device *hdev)
{
int ret = 0;
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1)
ret = steelseries_headset_request_battery(hdev,
arctis_1_battery_request, sizeof(arctis_1_battery_request));
else if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9)
ret = steelseries_headset_request_battery(hdev,
arctis_9_battery_request, sizeof(arctis_9_battery_request));
if (ret < 0)
hid_dbg(hdev,
"Battery query failed (err: %d)\n", ret);
}
static int battery_capacity_to_level(int capacity)
{
if (capacity >= 50)
return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
if (capacity >= 20)
return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
}
static void steelseries_headset_battery_timer_tick(struct work_struct *work)
{
struct steelseries_device *sd = container_of(work,
struct steelseries_device, battery_work.work);
struct hid_device *hdev = sd->hdev;
steelseries_headset_fetch_battery(hdev);
}
#define STEELSERIES_PREFIX "SteelSeries "
#define STEELSERIES_PREFIX_LEN strlen(STEELSERIES_PREFIX)
static int steelseries_headset_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct steelseries_device *sd = power_supply_get_drvdata(psy);
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = sd->hdev->name;
while (!strncmp(val->strval, STEELSERIES_PREFIX, STEELSERIES_PREFIX_LEN))
val->strval += STEELSERIES_PREFIX_LEN;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = "SteelSeries";
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_STATUS:
if (sd->headset_connected) {
val->intval = sd->battery_charging ?
POWER_SUPPLY_STATUS_CHARGING :
POWER_SUPPLY_STATUS_DISCHARGING;
} else
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = sd->battery_capacity;
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
val->intval = battery_capacity_to_level(sd->battery_capacity);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static void
steelseries_headset_set_wireless_status(struct hid_device *hdev,
bool connected)
{
struct usb_interface *intf;
if (!hid_is_usb(hdev))
return;
intf = to_usb_interface(hdev->dev.parent);
usb_set_wireless_status(intf, connected ?
USB_WIRELESS_STATUS_CONNECTED :
USB_WIRELESS_STATUS_DISCONNECTED);
}
static enum power_supply_property steelseries_headset_battery_props[] = {
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
},
},
{ }
};
static int steelseries_headset_battery_register(struct steelseries_device *sd)
static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev)
{
static atomic_t battery_no = ATOMIC_INIT(0);
struct power_supply_config battery_cfg = { .drv_data = sd, };
unsigned long n;
if (!hid_is_usb(hdev))
return NULL;
return to_usb_interface(hdev->dev.parent);
}
static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
{
struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
struct usb_device *udev;
if (!intf)
return false;
udev = interface_to_usbdev(intf);
return intf == usb_ifnum_to_if(udev, 0);
}
#if STEELSERIES_HAS_LEDS_MULTICOLOR
static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
{
struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
if (!intf)
return NULL;
return interface_to_usbdev(intf);
}
static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
struct steelseries_device *sd = container_of(mc_cdev,
struct steelseries_device,
mc_cdev);
struct hid_device *hdev = sd->hdev;
struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
int i, ret;
u8 r, g, b;
static const u8 keys[] = {
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
};
static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03 };
if (!udev)
return -ENODEV;
mutex_lock(&sd->rgb_lock);
led_mc_calc_color_components(mc_cdev, brightness);
r = mc_cdev->subled_info[0].brightness;
g = mc_cdev->subled_info[1].brightness;
b = mc_cdev->subled_info[2].brightness;
/*
* Report layout (524 bytes):
* Byte 0: Opcode (0x0c)
* Byte 1: 0x00
* Byte 2: Mode (0x66 for Keyboard, 0x06 for Lightbar)
* Byte 3: 0x00
* Bytes 4+: 4-byte chunks per LED (Index, R, G, B)
*/
memset(sd->rgb_buf, 0, STEELSERIES_MSI_RGB_REPORT_LEN);
sd->rgb_buf[0] = STEELSERIES_MSI_RGB_OPCODE;
sd->rgb_buf[1] = 0x00;
sd->rgb_buf[3] = 0x00;
for (i = 0; i < (STEELSERIES_MSI_RGB_REPORT_LEN - 4) / 4; i++)
sd->rgb_buf[4 + i * 4] = 0xff;
if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
sd->rgb_buf[2] = STEELSERIES_MSI_RGB_KLC_MODE;
for (i = 0; i < ARRAY_SIZE(keys); i++) {
sd->rgb_buf[4 + i * 4] = keys[i];
sd->rgb_buf[5 + i * 4] = r;
sd->rgb_buf[6 + i * 4] = g;
sd->rgb_buf[7 + i * 4] = b;
}
} else {
sd->rgb_buf[2] = STEELSERIES_MSI_RGB_ALC_MODE;
for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
sd->rgb_buf[4 + i * 4] = alc_zones[i];
sd->rgb_buf[5 + i * 4] = r;
sd->rgb_buf[6 + i * 4] = g;
sd->rgb_buf[7 + i * 4] = b;
}
}
/*
* Send the vendor report verbatim with usb_control_msg(): byte 0 is a
* protocol opcode (0x0c), not a HID report ID, and the controller
* expects it under report ID 0 (wValue 0x0300). hid_hw_raw_request()
* would write the report number into byte 0, so the direct control
* transfer is used to keep the payload byte-identical to the tested
* userspace implementation.
*/
ret = hid_hw_power(hdev, PM_HINT_FULLON);
if (ret < 0)
goto out_unlock;
ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
HID_REQ_SET_REPORT,
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
STEELSERIES_MSI_RGB_WVALUE, 0,
sd->rgb_buf, STEELSERIES_MSI_RGB_REPORT_LEN,
USB_CTRL_SET_TIMEOUT);
hid_hw_power(hdev, PM_HINT_NORMAL);
out_unlock:
mutex_unlock(&sd->rgb_lock);
return ret < 0 ? ret : 0;
}
static void steelseries_msi_rgb_free_buf(void *data)
{
kfree(data);
}
static int steelseries_msi_rgb_register(struct steelseries_device *sd)
{
struct hid_device *hdev = sd->hdev;
struct led_classdev *led_cdev;
int ret;
sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
sd->battery_desc.properties = steelseries_headset_battery_props;
sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_headset_battery_props);
sd->battery_desc.get_property = steelseries_headset_battery_get_property;
sd->battery_desc.use_for_apm = 0;
n = atomic_inc_return(&battery_no) - 1;
sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
"steelseries_headset_battery_%ld", n);
if (!sd->battery_desc.name)
sd->rgb_buf = kzalloc(STEELSERIES_MSI_RGB_REPORT_LEN, GFP_KERNEL);
if (!sd->rgb_buf)
return -ENOMEM;
/* avoid the warning of 0% battery while waiting for the first info */
steelseries_headset_set_wireless_status(sd->hdev, false);
sd->battery_capacity = 100;
sd->battery_charging = false;
sd->battery = devm_power_supply_register(&sd->hdev->dev,
&sd->battery_desc, &battery_cfg);
if (IS_ERR(sd->battery)) {
ret = PTR_ERR(sd->battery);
hid_err(sd->hdev,
"%s:power_supply_register failed with error %d\n",
__func__, ret);
ret = devm_add_action_or_reset(&hdev->dev,
steelseries_msi_rgb_free_buf,
sd->rgb_buf);
if (ret) {
sd->rgb_buf = NULL;
return ret;
}
power_supply_powers(sd->battery, &sd->hdev->dev);
INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick);
steelseries_headset_fetch_battery(sd->hdev);
ret = devm_mutex_init(&hdev->dev, &sd->rgb_lock);
if (ret) {
devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
sd->rgb_buf);
kfree(sd->rgb_buf);
sd->rgb_buf = NULL;
return ret;
}
if (sd->quirks & STEELSERIES_ARCTIS_9) {
/* The first fetch_battery request can remain unanswered in some cases */
schedule_delayed_work(&sd->battery_work,
msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
sd->subled_info[0].color_index = LED_COLOR_ID_RED;
sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
sd->subled_info[0].intensity = 255;
sd->subled_info[1].intensity = 255;
sd->subled_info[2].intensity = 255;
sd->subled_info[0].channel = 0;
sd->subled_info[1].channel = 1;
sd->subled_info[2].channel = 2;
sd->mc_cdev.subled_info = sd->subled_info;
sd->mc_cdev.num_colors = 3;
led_cdev = &sd->mc_cdev.led_cdev;
if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
led_cdev->name = "steelseries::kbd_backlight";
else
led_cdev->name = "steelseries::lightbar";
led_cdev->max_brightness = 255;
led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
ret = devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
if (ret) {
devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
sd->rgb_buf);
kfree(sd->rgb_buf);
sd->rgb_buf = NULL;
return ret;
}
return 0;
}
static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t usage_page)
#else
static int steelseries_msi_rgb_register(struct steelseries_device *sd)
{
return hdev->rdesc[0] == 0x06 &&
hdev->rdesc[1] == usage_page &&
hdev->rdesc[2] == 0xff;
return -ENODEV;
}
#endif
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
@ -549,36 +585,45 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
sd->hdev = hdev;
sd->quirks = id->driver_data;
if (sd->quirks & STEELSERIES_MSI_RGB) {
if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
!steelseries_msi_rgb_is_interface0(hdev)) {
hid_dbg(hdev, "MSI RGB quirk not applicable, using generic HID path\n");
sd->quirks &= ~STEELSERIES_MSI_RGB;
}
}
ret = hid_parse(hdev);
if (ret)
return ret;
if (sd->quirks & STEELSERIES_ARCTIS_9 &&
!steelseries_is_vendor_usage_page(hdev, 0xc0))
return -ENODEV;
spin_lock_init(&sd->lock);
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret)
return ret;
ret = hid_hw_open(hdev);
if (ret)
return ret;
goto err_stop;
if (steelseries_headset_battery_register(sd) < 0)
hid_err(sd->hdev,
"Failed to register battery for headset\n");
if (sd->quirks & STEELSERIES_MSI_RGB) {
ret = steelseries_msi_rgb_register(sd);
if (ret) {
hid_warn(hdev,
"Failed to register MSI RGB LEDs: %d, continuing without RGB support\n",
ret);
sd->quirks &= ~STEELSERIES_MSI_RGB;
}
}
return 0;
err_stop:
hid_hw_stop(hdev);
return ret;
}
static void steelseries_remove(struct hid_device *hdev)
{
struct steelseries_device *sd;
unsigned long flags;
if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
@ -587,14 +632,6 @@ static void steelseries_remove(struct hid_device *hdev)
return;
}
sd = hid_get_drvdata(hdev);
spin_lock_irqsave(&sd->lock, flags);
sd->removed = true;
spin_unlock_irqrestore(&sd->lock, flags);
cancel_delayed_work_sync(&sd->battery_work);
hid_hw_close(hdev);
hid_hw_stop(hdev);
}
@ -615,122 +652,19 @@ static const __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev,
return rdesc;
}
static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in, uint8_t max_in)
{
if (capacity >= max_in)
return 100;
if (capacity <= min_in)
return 0;
return (capacity - min_in) * 100 / (max_in - min_in);
}
static int steelseries_headset_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *read_buf,
int size)
{
struct steelseries_device *sd = hid_get_drvdata(hdev);
int capacity = sd->battery_capacity;
bool connected = sd->headset_connected;
bool charging = sd->battery_charging;
unsigned long flags;
/* Not a headset */
if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
return 0;
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
memcmp(read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_request))) {
if (!delayed_work_pending(&sd->battery_work))
goto request_battery;
return 0;
}
if (read_buf[2] == 0x01) {
connected = false;
capacity = 100;
} else {
connected = true;
capacity = read_buf[3];
}
}
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 9 headset (%*ph)\n", size, read_buf);
if (size < ARCTIS_9_BATTERY_RESPONSE_LEN) {
if (!delayed_work_pending(&sd->battery_work))
goto request_battery;
return 0;
}
if (read_buf[0] == 0xaa && read_buf[1] == 0x01) {
connected = true;
charging = read_buf[4] == 0x01;
/*
* Found no official documentation about min and max.
* Values defined by testing.
*/
capacity = steelseries_headset_map_capacity(read_buf[3], 0x68, 0x9d);
} else {
/*
* Device is off and sends the last known status read_buf[1] == 0x03 or
* there is no known status of the device read_buf[0] == 0x55
*/
connected = false;
charging = false;
}
}
if (connected != sd->headset_connected) {
hid_dbg(sd->hdev,
"Connected status changed from %sconnected to %sconnected\n",
sd->headset_connected ? "" : "not ",
connected ? "" : "not ");
sd->headset_connected = connected;
steelseries_headset_set_wireless_status(hdev, connected);
}
if (capacity != sd->battery_capacity) {
hid_dbg(sd->hdev,
"Battery capacity changed from %d%% to %d%%\n",
sd->battery_capacity, capacity);
sd->battery_capacity = capacity;
power_supply_changed(sd->battery);
}
if (charging != sd->battery_charging) {
hid_dbg(sd->hdev,
"Battery charging status changed from %scharging to %scharging\n",
sd->battery_charging ? "" : "not ",
charging ? "" : "not ");
sd->battery_charging = charging;
power_supply_changed(sd->battery);
}
request_battery:
spin_lock_irqsave(&sd->lock, flags);
if (!sd->removed)
schedule_delayed_work(&sd->battery_work,
msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
spin_unlock_irqrestore(&sd->lock, flags);
return 0;
}
static const struct hid_device_id steelseries_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1),
.driver_data = STEELSERIES_SRWS1 },
{ /* SteelSeries Arctis 1 Wireless for XBox */
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1),
.driver_data = STEELSERIES_ARCTIS_1 },
#if STEELSERIES_HAS_LEDS_MULTICOLOR
{ /* MSI Raider A18 KLC */
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_KLC),
.driver_data = STEELSERIES_MSI_RGB },
{ /* SteelSeries Arctis 9 Wireless for XBox */
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = STEELSERIES_ARCTIS_9 },
{ /* MSI Raider A18 ALC */
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
.driver_data = STEELSERIES_MSI_RGB },
#endif
{ }
};
@ -742,12 +676,9 @@ static struct hid_driver steelseries_driver = {
.probe = steelseries_probe,
.remove = steelseries_remove,
.report_fixup = steelseries_srws1_report_fixup,
.raw_event = steelseries_headset_raw_event,
};
module_hid_driver(steelseries_driver);
MODULE_DESCRIPTION("HID driver for Steelseries devices");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
MODULE_AUTHOR("Simon Wood <simon@mungewell.org>");
MODULE_AUTHOR("Christian Mayer <git@mayer-bgk.de>");

View File

@ -17,6 +17,7 @@
#include <linux/hid.h>
#include <linux/input.h>
#include <linux/math64.h>
#include <linux/slab.h>
#include <linux/module.h>
@ -47,9 +48,9 @@ struct tmff_device {
/* Changes values from 0 to 0xffff into values from minimum to maximum */
static inline int tmff_scale_u16(unsigned int in, int minimum, int maximum)
{
int ret;
s64 ret;
ret = (in * (maximum - minimum) / 0xffff) + minimum;
ret = div_s64((s64)in * ((s64)maximum - minimum), 0xffff) + minimum;
if (ret < minimum)
return minimum;
if (ret > maximum)
@ -60,9 +61,9 @@ static inline int tmff_scale_u16(unsigned int in, int minimum, int maximum)
/* Changes values from -0x80 to 0x7f into values from minimum to maximum */
static inline int tmff_scale_s8(int in, int minimum, int maximum)
{
int ret;
s64 ret;
ret = (((in + 0x80) * (maximum - minimum)) / 0xff) + minimum;
ret = div_s64((s64)(in + 0x80) * ((s64)maximum - minimum), 0xff) + minimum;
if (ret < minimum)
return minimum;
if (ret > maximum)
@ -115,22 +116,25 @@ static int tmff_play(struct input_dev *dev, void *data,
return 0;
}
static int tmff_init(struct hid_device *hid, const signed short *ff_bits)
static int tm_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct tmff_device *tmff;
struct hid_report *report;
struct list_head *report_list;
struct hid_input *hidinput;
struct input_dev *input_dev;
struct input_dev *input_dev = hidinput->input;
const struct hid_device_id *id;
const signed short *ff_bits;
int error;
int i;
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
if (!list_is_first(&hidinput->list, &hid->inputs))
return 0;
id = hid_match_device(hid, hid->driver);
if (!id)
return -ENODEV;
}
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
input_dev = hidinput->input;
ff_bits = (void *)id->driver_data;
tmff = kzalloc_obj(struct tmff_device);
if (!tmff)
@ -204,35 +208,13 @@ static int tmff_init(struct hid_device *hid, const signed short *ff_bits)
return error;
}
#else
static inline int tmff_init(struct hid_device *hid, const signed short *ff_bits)
static inline int tm_input_configured(struct hid_device *hid,
struct hid_input *hidinput)
{
return 0;
}
#endif
static int tm_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
}
tmff_init(hdev, (void *)id->driver_data);
return 0;
err:
return ret;
}
static const struct hid_device_id tm_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300),
.driver_data = (unsigned long)ff_rumble },
@ -261,7 +243,7 @@ MODULE_DEVICE_TABLE(hid, tm_devices);
static struct hid_driver tm_driver = {
.name = "thrustmaster",
.id_table = tm_devices,
.probe = tm_probe,
.input_configured = tm_input_configured,
};
module_hid_driver(tm_driver);

View File

@ -548,7 +548,17 @@ static void uclogic_remove(struct hid_device *hdev)
{
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
timer_delete_sync(&drvdata->inrange_timer);
/*
* Shut the in-range timer down before stopping the device.
* uclogic_raw_event_pen() re-arms inrange_timer on every pen report
* and keeps running until hid_hw_stop() stops the transport, so a
* plain timer_delete_sync() here can be undone by a report landing in
* the window before hid_hw_stop(). timer_shutdown_sync() cancels the
* timer and makes any later re-arm a no-op, so it is provably dead
* before hid_hw_stop() frees the input device drvdata->pen_input
* points at.
*/
timer_shutdown_sync(&drvdata->inrange_timer);
hid_hw_stop(hdev);
kfree(drvdata->desc_ptr);
uclogic_params_cleanup(&drvdata->params);

View File

@ -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;
}

View File

@ -50,20 +50,15 @@ static int zpff_play(struct input_dev *dev, void *data,
return 0;
}
static int zpff_init(struct hid_device *hid)
static int zp_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct zpff_device *zpff;
struct hid_report *report;
struct hid_input *hidinput;
struct input_dev *dev;
struct input_dev *dev = hidinput->input;
int i, error;
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
return -ENODEV;
}
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
dev = hidinput->input;
if (!list_is_first(&hidinput->list, &hid->inputs))
return 0;
for (i = 0; i < 4; i++) {
report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, i, 1);
@ -75,6 +70,7 @@ static int zpff_init(struct hid_device *hid)
if (!zpff)
return -ENOMEM;
zpff->report = report;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, zpff, zpff_play);
@ -83,7 +79,6 @@ static int zpff_init(struct hid_device *hid)
return error;
}
zpff->report = report;
zpff->report->field[0]->value[0] = 0x00;
zpff->report->field[1]->value[0] = 0x02;
zpff->report->field[2]->value[0] = 0x00;
@ -95,35 +90,13 @@ static int zpff_init(struct hid_device *hid)
return 0;
}
#else
static inline int zpff_init(struct hid_device *hid)
static inline int zp_input_configured(struct hid_device *hid,
struct hid_input *hidinput)
{
return 0;
}
#endif
static int zp_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err;
}
zpff_init(hdev);
return 0;
err:
return ret;
}
static const struct hid_device_id zp_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0005) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0030) },
@ -134,7 +107,7 @@ MODULE_DEVICE_TABLE(hid, zp_devices);
static struct hid_driver zp_driver = {
.name = "zeroplus",
.id_table = zp_devices,
.probe = zp_probe,
.input_configured = zp_input_configured,
};
module_hid_driver(zp_driver);

View File

@ -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

View File

@ -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");

View File

@ -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 (!ihid_acpi)
return -ENOMEM;
if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0)
return -ENODEV;
ihid_acpi->adev = ACPI_COMPANION(dev);
ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
ihid_acpi->ops.restore_sequence = i2c_hid_acpi_restore_sequence;
ret = i2c_hid_acpi_get_descriptor(ihid_acpi);
ret = i2c_hid_acpi_get_descriptor(adev);
if (ret < 0)
return ret;
hid_descriptor_address = ret;
acpi_device_fix_up_power(ihid_acpi->adev);
ihid_acpi = devm_kzalloc(dev, sizeof(*ihid_acpi), GFP_KERNEL);
if (!ihid_acpi)
return -ENOMEM;
ihid_acpi->adev = adev;
ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
ihid_acpi->ops.restore_sequence = i2c_hid_acpi_restore_sequence;
acpi_device_fix_up_power(adev);
return i2c_hid_core_probe(client, &ihid_acpi->ops,
hid_descriptor_address, 0);

View File

@ -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

View File

@ -792,7 +792,7 @@ static int i2c_hid_parse(struct hid_device *hid)
ihid->hdesc.wReportDescRegister,
rdesc, rsize);
if (ret) {
hid_err(hid, "reading report descriptor failed\n");
dev_err(&client->dev, "reading report descriptor failed\n");
goto out;
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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(

View File

@ -175,7 +175,9 @@ static int quicki2c_get_acpi_resources(struct quicki2c_device *qcdev)
if (i2c_param.addressing_mode != HIDI2C_ADDRESSING_MODE_7BIT)
return -EOPNOTSUPP;
qcdev->i2c_slave_addr = i2c_param.device_address;
qcdev->i2c_config.addr_mode = HIDI2C_ADDRESSING_MODE_7BIT;
qcdev->i2c_config.target_addr = i2c_param.device_address;
ret = quicki2c_acpi_get_dsd_property(adev, QUICKI2C_ACPI_METHOD_NAME_ISUB,
ACPI_TYPE_BUFFER, &i2c_config);
@ -184,24 +186,32 @@ static int quicki2c_get_acpi_resources(struct quicki2c_device *qcdev)
if (i2c_param.connection_speed > 0 &&
i2c_param.connection_speed <= QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED) {
qcdev->i2c_speed_mode = THC_I2C_STANDARD;
qcdev->i2c_clock_hcnt = i2c_config.SMHX;
qcdev->i2c_clock_lcnt = i2c_config.SMLX;
qcdev->i2c_config.speed = THC_I2C_STANDARD;
qcdev->i2c_config.scl_hcnt = (u32)i2c_config.SMHX;
qcdev->i2c_config.scl_lcnt = (u32)i2c_config.SMLX;
qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.SMTD;
qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.SMRD;
} else if (i2c_param.connection_speed > QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED &&
i2c_param.connection_speed <= QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED) {
qcdev->i2c_speed_mode = THC_I2C_FAST_AND_PLUS;
qcdev->i2c_clock_hcnt = i2c_config.FMHX;
qcdev->i2c_clock_lcnt = i2c_config.FMLX;
qcdev->i2c_config.speed = THC_I2C_FAST_AND_PLUS;
qcdev->i2c_config.scl_hcnt = (u32)i2c_config.FMHX;
qcdev->i2c_config.scl_lcnt = (u32)i2c_config.FMLX;
qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.FMTD;
qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.FMRD;
} else if (i2c_param.connection_speed > QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED &&
i2c_param.connection_speed <= QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED) {
qcdev->i2c_speed_mode = THC_I2C_FAST_AND_PLUS;
qcdev->i2c_clock_hcnt = i2c_config.FPHX;
qcdev->i2c_clock_lcnt = i2c_config.FPLX;
qcdev->i2c_config.speed = THC_I2C_FAST_AND_PLUS;
qcdev->i2c_config.scl_hcnt = (u32)i2c_config.FPHX;
qcdev->i2c_config.scl_lcnt = (u32)i2c_config.FPLX;
qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.FPTD;
qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.FPRD;
} else if (i2c_param.connection_speed > QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED &&
i2c_param.connection_speed <= QUICKI2C_SUBIP_HIGH_SPEED_MODE_MAX_SPEED) {
qcdev->i2c_speed_mode = THC_I2C_HIGH_SPEED;
qcdev->i2c_clock_hcnt = i2c_config.HMHX;
qcdev->i2c_clock_lcnt = i2c_config.HMLX;
qcdev->i2c_config.speed = THC_I2C_HIGH_SPEED;
qcdev->i2c_config.scl_hcnt = (u32)i2c_config.HMHX;
qcdev->i2c_config.scl_lcnt = (u32)i2c_config.HMLX;
qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.HMTD;
qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.HMRD;
} else {
return -EOPNOTSUPP;
}
@ -245,28 +255,33 @@ static irqreturn_t quicki2c_irq_quick_handler(int irq, void *dev_id)
}
/**
* try_recover - Try to recovery THC and Device
* @qcdev: Pointer to quicki2c_device structure
* try_recover - Recover callback to recover THC
* @work: pointer to work_struct
*
* This function is an error handler, called when fatal error happens.
* It try to reset touch device and re-configure THC to recovery
* communication between touch device and THC.
*
* Return: 0 if successful or error code on failure
* It try to reset Touch Device and re-configure THC to recover
* transferring between Device and THC.
*/
static int try_recover(struct quicki2c_device *qcdev)
static void try_recover(struct work_struct *work)
{
int ret;
struct quicki2c_device *qcdev = container_of(work, struct quicki2c_device, recover_work);
thc_dma_unconfigure(qcdev->thc_hw);
if (READ_ONCE(qcdev->recovery_disabled))
return;
ret = thc_dma_configure(qcdev->thc_hw);
if (ret) {
dev_err(qcdev->dev, "Reconfig DMA failed\n");
return ret;
if (pm_runtime_resume_and_get(qcdev->dev))
return;
thc_interrupt_enable(qcdev->thc_hw, false);
if (thc_rxdma_reset(qcdev->thc_hw)) {
qcdev->state = QUICKI2C_DISABLED;
dev_err(qcdev->dev, "RxDMA reset failed during recover, disable QuickI2C\n");
} else {
thc_interrupt_enable(qcdev->thc_hw, true);
}
return 0;
pm_runtime_put_autosuspend(qcdev->dev);
}
static int handle_input_report(struct quicki2c_device *qcdev)
@ -343,11 +358,10 @@ static irqreturn_t quicki2c_irq_thread_handler(int irq, void *dev_id)
}
exit:
thc_interrupt_enable(qcdev->thc_hw, true);
if (err_recover)
if (try_recover(qcdev))
qcdev->state = QUICKI2C_DISABLED;
schedule_work(&qcdev->recover_work);
else
thc_interrupt_enable(qcdev->thc_hw, true);
pm_runtime_put_autosuspend(qcdev->dev);
@ -386,6 +400,8 @@ static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __io
qcdev->ddata = ddata;
init_waitqueue_head(&qcdev->reset_ack_wq);
WRITE_ONCE(qcdev->recovery_disabled, false);
INIT_WORK(&qcdev->recover_work, try_recover);
/* THC hardware init */
qcdev->thc_hw = thc_dev_init(qcdev->dev, qcdev->mem_addr);
@ -411,10 +427,7 @@ static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __io
return ERR_PTR(ret);
}
ret = thc_i2c_subip_init(qcdev->thc_hw, qcdev->i2c_slave_addr,
qcdev->i2c_speed_mode,
qcdev->i2c_clock_hcnt,
qcdev->i2c_clock_lcnt);
ret = thc_i2c_subip_init(qcdev->thc_hw, &qcdev->i2c_config);
if (ret)
return ERR_PTR(ret);
@ -439,6 +452,9 @@ static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __io
*/
static void quicki2c_dev_deinit(struct quicki2c_device *qcdev)
{
WRITE_ONCE(qcdev->recovery_disabled, true);
cancel_work_sync(&qcdev->recover_work);
thc_interrupt_quiesce(qcdev->thc_hw, true);
thc_interrupt_enable(qcdev->thc_hw, false);
thc_ltr_unconfig(qcdev->thc_hw);
@ -682,11 +698,8 @@ static int quicki2c_probe(struct pci_dev *pdev, const struct pci_device_id *id)
quicki2c_irq_thread_handler,
IRQF_ONESHOT, KBUILD_MODNAME,
qcdev);
if (ret) {
dev_err_once(&pdev->dev,
"Failed to request threaded IRQ, irq = %d.\n", pdev->irq);
if (ret)
goto dev_deinit;
}
ret = quicki2c_get_device_descriptor(qcdev);
if (ret) {
@ -772,12 +785,14 @@ static void quicki2c_remove(struct pci_dev *pdev)
return;
quicki2c_hid_remove(qcdev);
quicki2c_dma_deinit(qcdev);
pm_runtime_get_noresume(qcdev->dev);
quicki2c_dev_deinit(qcdev);
quicki2c_dma_deinit(qcdev);
pm_runtime_dont_use_autosuspend(qcdev->dev);
pm_runtime_get_noresume(qcdev->dev);
pci_clear_master(pdev);
}
@ -796,10 +811,10 @@ static void quicki2c_shutdown(struct pci_dev *pdev)
if (!qcdev)
return;
quicki2c_dev_deinit(qcdev);
/* Must stop DMA before reboot to avoid DMA entering into unknown state */
quicki2c_dma_deinit(qcdev);
quicki2c_dev_deinit(qcdev);
}
static int quicki2c_suspend(struct device *device)
@ -826,6 +841,9 @@ static int quicki2c_suspend(struct device *device)
if (ret)
return ret;
WRITE_ONCE(qcdev->recovery_disabled, true);
cancel_work_sync(&qcdev->recover_work);
ret = thc_interrupt_quiesce(qcdev->thc_hw, true);
if (ret)
return ret;
@ -867,6 +885,8 @@ static int quicki2c_resume(struct device *device)
if (ret)
return ret;
WRITE_ONCE(qcdev->recovery_disabled, false);
if (!device_may_wakeup(qcdev->dev))
return quicki2c_set_power(qcdev, HIDI2C_ON);
@ -883,6 +903,9 @@ static int quicki2c_freeze(struct device *device)
if (!qcdev)
return -ENODEV;
WRITE_ONCE(qcdev->recovery_disabled, true);
cancel_work_sync(&qcdev->recover_work);
ret = thc_interrupt_quiesce(qcdev->thc_hw, true);
if (ret)
return ret;
@ -914,6 +937,8 @@ static int quicki2c_thaw(struct device *device)
if (ret)
return ret;
WRITE_ONCE(qcdev->recovery_disabled, false);
return 0;
}
@ -938,6 +963,8 @@ static int quicki2c_poweroff(struct device *device)
thc_ltr_unconfig(qcdev->thc_hw);
quicki2c_dev_deinit(qcdev);
quicki2c_dma_deinit(qcdev);
return 0;
@ -958,10 +985,7 @@ static int quicki2c_restore(struct device *device)
if (ret)
return ret;
ret = thc_i2c_subip_init(qcdev->thc_hw, qcdev->i2c_slave_addr,
qcdev->i2c_speed_mode,
qcdev->i2c_clock_hcnt,
qcdev->i2c_clock_lcnt);
ret = thc_i2c_subip_init(qcdev->thc_hw, &qcdev->i2c_config);
if (ret)
return ret;

View File

@ -7,6 +7,8 @@
#include <linux/hid-over-i2c.h>
#include <linux/workqueue.h>
#include "intel-thc-dev.h"
#define PCI_DEVICE_ID_INTEL_THC_LNL_DEVICE_ID_I2C_PORT1 0xA848
#define PCI_DEVICE_ID_INTEL_THC_LNL_DEVICE_ID_I2C_PORT2 0xA84A
#define PCI_DEVICE_ID_INTEL_THC_PTL_H_DEVICE_ID_I2C_PORT1 0xE348
@ -159,7 +161,6 @@ struct quicki2c_ddata {
struct device;
struct pci_dev;
struct thc_device;
struct hid_device;
struct acpi_device;
@ -174,13 +175,10 @@ struct acpi_device;
* @state: THC I2C device state
* @mem_addr: MMIO memory address
* @dev_desc: Device descriptor for HIDI2C protocol
* @i2c_slave_addr: HIDI2C device slave address
* @i2c_config: I2C bus configuration
* @hid_desc_addr: Register address for retrieve HID device descriptor
* @active_ltr_val: THC active LTR value
* @low_power_ltr_val: THC low power LTR value
* @i2c_speed_mode: 0 - standard mode, 1 - fast mode, 2 - fast mode plus
* @i2c_clock_hcnt: I2C CLK high period time (unit in cycle count)
* @i2c_clock_lcnt: I2C CLK low period time (unit in cycle count)
* @report_descriptor: Store a copy of device report descriptor
* @input_buf: Store a copy of latest input report data
* @report_buf: Store a copy of latest input/output report packet from set/get feature
@ -191,6 +189,8 @@ struct acpi_device;
* @i2c_max_frame_size: Max RX frame size (unit in Bytes)
* @i2c_int_delay_enable: Indicate interrupt delay feature enabled or not
* @i2c_int_delay: Interrupt detection delay value (unit in 10 us)
* @recover_work: Work structure for recovery
* @recovery_disabled: Whether recovery work is blocked during teardown
*/
struct quicki2c_device {
struct device *dev;
@ -204,16 +204,12 @@ struct quicki2c_device {
void __iomem *mem_addr;
struct hidi2c_dev_descriptor dev_desc;
u8 i2c_slave_addr;
struct thc_i2c_config i2c_config;
u16 hid_desc_addr;
u32 active_ltr_val;
u32 low_power_ltr_val;
u32 i2c_speed_mode;
u32 i2c_clock_hcnt;
u32 i2c_clock_lcnt;
u8 *report_descriptor;
u8 *input_buf;
u8 *report_buf;
@ -226,6 +222,9 @@ struct quicki2c_device {
u32 i2c_max_frame_size;
u32 i2c_int_delay_enable;
u32 i2c_int_delay;
struct work_struct recover_work;
bool recovery_disabled;
};
#endif /* _QUICKI2C_DEV_H_ */

View File

@ -252,34 +252,33 @@ static irqreturn_t quickspi_irq_quick_handler(int irq, void *dev_id)
}
/**
* try_recover - Try to recovery THC and Device
* @qsdev: pointer to quickspi device
* try_recover - Recover callback to recover THC
* @work: pointer to work_struct
*
* This function is a error handler, called when fatal error happens.
* It try to reset Touch Device and re-configure THC to recovery
* This function is an error handler, called when fatal error happens.
* It try to reset Touch Device and re-configure THC to recover
* transferring between Device and THC.
*
* Return: 0 if successful or error code on failed.
*/
static int try_recover(struct quickspi_device *qsdev)
static void try_recover(struct work_struct *work)
{
int ret;
struct quickspi_device *qsdev = container_of(work, struct quickspi_device, recover_work);
ret = reset_tic(qsdev);
if (ret) {
dev_err(qsdev->dev, "Reset touch device failed, ret = %d\n", ret);
return ret;
if (READ_ONCE(qsdev->recovery_disabled))
return;
if (pm_runtime_resume_and_get(qsdev->dev))
return;
thc_interrupt_enable(qsdev->thc_hw, false);
if (thc_rxdma_reset(qsdev->thc_hw)) {
qsdev->state = QUICKSPI_DISABLED;
dev_err(qsdev->dev, "RxDMA reset failed during recover, disable QuickSPI\n");
} else {
thc_interrupt_enable(qsdev->thc_hw, true);
}
thc_dma_unconfigure(qsdev->thc_hw);
ret = thc_dma_configure(qsdev->thc_hw);
if (ret) {
dev_err(qsdev->dev, "Re-configure THC DMA failed, ret = %d\n", ret);
return ret;
}
return 0;
pm_runtime_put_autosuspend(qsdev->dev);
}
/**
@ -337,11 +336,10 @@ static irqreturn_t quickspi_irq_thread_handler(int irq, void *dev_id)
}
end:
thc_interrupt_enable(qsdev->thc_hw, true);
if (err_recover)
if (try_recover(qsdev))
qsdev->state = QUICKSPI_DISABLED;
schedule_work(&qsdev->recover_work);
else
thc_interrupt_enable(qsdev->thc_hw, true);
pm_runtime_put_autosuspend(qsdev->dev);
@ -385,6 +383,8 @@ static struct quickspi_device *quickspi_dev_init(struct pci_dev *pdev, void __io
init_waitqueue_head(&qsdev->report_desc_got_wq);
init_waitqueue_head(&qsdev->get_report_cmpl_wq);
init_waitqueue_head(&qsdev->set_report_cmpl_wq);
WRITE_ONCE(qsdev->recovery_disabled, false);
INIT_WORK(&qsdev->recover_work, try_recover);
/* thc hw init */
qsdev->thc_hw = thc_dev_init(qsdev->dev, qsdev->mem_addr);
@ -461,6 +461,10 @@ static struct quickspi_device *quickspi_dev_init(struct pci_dev *pdev, void __io
*/
static void quickspi_dev_deinit(struct quickspi_device *qsdev)
{
WRITE_ONCE(qsdev->recovery_disabled, true);
cancel_work_sync(&qsdev->recover_work);
thc_interrupt_quiesce(qsdev->thc_hw, true);
thc_interrupt_enable(qsdev->thc_hw, false);
thc_ltr_unconfig(qsdev->thc_hw);
thc_wot_unconfig(qsdev->thc_hw);
@ -555,7 +559,14 @@ static int quickspi_alloc_report_buf(struct quickspi_device *qsdev)
max_report_len = max(le16_to_cpu(qsdev->dev_desc.max_output_len),
le16_to_cpu(qsdev->dev_desc.max_input_len));
qsdev->report_buf = devm_kzalloc(qsdev->dev, max_report_len, GFP_KERNEL);
/*
* write_cmd_to_txdma() writes the output report header ahead of the
* content in this buffer, so it has to hold both.
*/
qsdev->report_buf_size = HIDSPI_OUTPUT_REPORT_SIZE(max_report_len);
qsdev->report_buf = devm_kzalloc(qsdev->dev, qsdev->report_buf_size,
GFP_KERNEL);
if (!qsdev->report_buf)
return -ENOMEM;
@ -636,11 +647,8 @@ static int quickspi_probe(struct pci_dev *pdev,
quickspi_irq_thread_handler,
IRQF_ONESHOT, KBUILD_MODNAME,
qsdev);
if (ret) {
dev_err(&pdev->dev,
"Failed to request threaded IRQ, irq = %d.\n", pdev->irq);
if (ret)
goto dev_deinit;
}
ret = reset_tic(qsdev);
if (ret) {
@ -711,12 +719,14 @@ static void quickspi_remove(struct pci_dev *pdev)
return;
quickspi_hid_remove(qsdev);
quickspi_dma_deinit(qsdev);
pm_runtime_get_noresume(qsdev->dev);
quickspi_dev_deinit(qsdev);
quickspi_dma_deinit(qsdev);
pm_runtime_dont_use_autosuspend(qsdev->dev);
pm_runtime_get_noresume(qsdev->dev);
pci_clear_master(pdev);
}
@ -737,10 +747,10 @@ static void quickspi_shutdown(struct pci_dev *pdev)
if (!qsdev)
return;
quickspi_dev_deinit(qsdev);
/* Must stop DMA before reboot to avoid DMA entering into unknown state */
quickspi_dma_deinit(qsdev);
quickspi_dev_deinit(qsdev);
}
static int quickspi_suspend(struct device *device)
@ -759,6 +769,9 @@ static int quickspi_suspend(struct device *device)
return ret;
}
WRITE_ONCE(qsdev->recovery_disabled, true);
cancel_work_sync(&qsdev->recover_work);
ret = thc_interrupt_quiesce(qsdev->thc_hw, true);
if (ret)
return ret;
@ -784,6 +797,8 @@ static int quickspi_resume(struct device *device)
if (ret)
return ret;
WRITE_ONCE(qsdev->recovery_disabled, false);
/*
* A wake-enabled device keeps its power and state across suspend, so
* only restore the THC context. Resetting it here would discard a
@ -864,6 +879,9 @@ static int quickspi_freeze(struct device *device)
if (!qsdev)
return -ENODEV;
WRITE_ONCE(qsdev->recovery_disabled, true);
cancel_work_sync(&qsdev->recover_work);
ret = thc_interrupt_quiesce(qsdev->thc_hw, true);
if (ret)
return ret;
@ -895,6 +913,8 @@ static int quickspi_thaw(struct device *device)
if (ret)
return ret;
WRITE_ONCE(qsdev->recovery_disabled, false);
return 0;
}
@ -919,6 +939,8 @@ static int quickspi_poweroff(struct device *device)
thc_ltr_unconfig(qsdev->thc_hw);
quickspi_dev_deinit(qsdev);
quickspi_dma_deinit(qsdev);
return 0;

View File

@ -8,6 +8,7 @@
#include <linux/hid-over-spi.h>
#include <linux/sizes.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include "quickspi-protocol.h"
@ -126,6 +127,8 @@ struct acpi_device;
* @get_feature_cmpl: indicate get feature received or not
* @set_feature_cmpl_wq: workqueue for waiting set feature to device
* @set_feature_cmpl: indicate set feature send complete or not
* @recover_work: Work structure for recovery
* @recovery_disabled: Whether recovery work is blocked during teardown
*/
struct quickspi_device {
struct device *dev;
@ -157,6 +160,7 @@ struct quickspi_device {
u8 *report_descriptor;
u8 *input_buf;
u8 *report_buf;
u32 report_buf_size;
u32 report_len;
wait_queue_head_t reset_ack_wq;
@ -173,6 +177,9 @@ struct quickspi_device {
wait_queue_head_t set_report_cmpl_wq;
bool set_report_cmpl;
struct work_struct recover_work;
bool recovery_disabled;
};
#endif /* _QUICKSPI_DEV_H_ */

View File

@ -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);

View File

@ -30,6 +30,9 @@ static int write_cmd_to_txdma(struct quickspi_device *qsdev,
write_buf = (struct output_report *)qsdev->report_buf;
if (HIDSPI_OUTPUT_REPORT_SIZE(report_buf_len) > qsdev->report_buf_size)
return -EINVAL;
write_buf->output_hdr.report_type = report_type;
write_buf->output_hdr.content_len = cpu_to_le16(report_buf_len);
write_buf->output_hdr.content_id = report_id;
@ -342,10 +345,12 @@ int reset_tic(struct quickspi_device *qsdev)
}
int quickspi_get_report(struct quickspi_device *qsdev,
u8 report_type, unsigned int report_id, void *buf)
u8 report_type, unsigned int report_id, void *buf,
u32 buf_len)
{
int rep_type;
int ret;
u32 report_len;
if (report_type == HID_INPUT_REPORT) {
rep_type = GET_INPUT_REPORT;
@ -372,9 +377,17 @@ int quickspi_get_report(struct quickspi_device *qsdev,
}
qsdev->get_report_cmpl = false;
memcpy(buf, qsdev->report_buf, qsdev->report_len);
/* quickspi_handle_input_data() updates this from IRQ context. */
report_len = READ_ONCE(qsdev->report_len);
if (report_len > buf_len) {
dev_err_once(qsdev->dev, "Get report response too big, %u vs %u\n",
report_len, buf_len);
return -EINVAL;
}
return qsdev->report_len;
memcpy(buf, qsdev->report_buf, report_len);
return report_len;
}
int quickspi_set_report(struct quickspi_device *qsdev,

View File

@ -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);

View File

@ -1422,14 +1422,25 @@ static int thc_i2c_subip_pio_write(struct thc_device *dev, const u32 address,
#define I2C_SUBIP_DMA_TDLR_DEFAULT 7
#define I2C_SUBIP_DMA_RDLR_DEFAULT 7
static int thc_i2c_subip_set_speed(struct thc_device *dev, const u32 speed,
const u32 hcnt, const u32 lcnt)
static int thc_i2c_subip_bus_config(struct thc_device *dev, const struct thc_i2c_config *i2c_config)
{
u32 hcnt_offset, lcnt_offset;
u32 val;
u32 read_size = sizeof(u32);
u32 val = 0;
int ret;
switch (speed) {
ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_TAR_OFFSET, &read_size, &val);
if (ret < 0)
return ret;
val &= ~(THC_I2C_IC_TAR_IC_TAR | THC_I2C_IC_TAR_IC_10BITADDR_MASTER);
val |= FIELD_PREP(THC_I2C_IC_TAR_IC_10BITADDR_MASTER, i2c_config->addr_mode);
val |= FIELD_PREP(THC_I2C_IC_TAR_IC_TAR, i2c_config->target_addr);
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_TAR_OFFSET, sizeof(u32), &val);
if (ret < 0)
return ret;
switch (i2c_config->speed) {
case THC_I2C_STANDARD:
hcnt_offset = THC_I2C_IC_SS_SCL_HCNT_OFFSET;
lcnt_offset = THC_I2C_IC_SS_SCL_LCNT_OFFSET;
@ -1446,25 +1457,43 @@ static int thc_i2c_subip_set_speed(struct thc_device *dev, const u32 speed,
break;
default:
dev_err_once(dev->dev, "Unsupported i2c speed %d\n", speed);
dev_err_once(dev->dev, "Unsupported i2c speed %d\n", i2c_config->speed);
ret = -EINVAL;
return ret;
}
ret = thc_i2c_subip_pio_write(dev, hcnt_offset, sizeof(u32), &hcnt);
ret = thc_i2c_subip_pio_write(dev, hcnt_offset, sizeof(u32), &i2c_config->scl_hcnt);
if (ret < 0)
return ret;
ret = thc_i2c_subip_pio_write(dev, lcnt_offset, sizeof(u32), &lcnt);
ret = thc_i2c_subip_pio_write(dev, lcnt_offset, sizeof(u32), &i2c_config->scl_lcnt);
if (ret < 0)
return ret;
val = I2C_SUBIP_CON_DEFAULT & ~THC_I2C_IC_CON_SPEED;
val |= FIELD_PREP(THC_I2C_IC_CON_SPEED, speed);
val |= FIELD_PREP(THC_I2C_IC_CON_SPEED, i2c_config->speed);
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_CON_OFFSET, sizeof(u32), &val);
if (ret < 0)
return ret;
ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_SDA_HOLD_OFFSET, &read_size, &val);
if (ret < 0)
return ret;
if (i2c_config->sda_tx_hold) {
val &= ~THC_I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD;
val |= FIELD_PREP(THC_I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD, i2c_config->sda_tx_hold);
}
if (i2c_config->sda_rx_hold) {
val &= ~THC_I2C_IC_SDA_HOLD_IC_SDA_RX_HOLD;
val |= FIELD_PREP(THC_I2C_IC_SDA_HOLD_IC_SDA_RX_HOLD, i2c_config->sda_rx_hold);
}
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_SDA_HOLD_OFFSET, sizeof(u32), &val);
if (ret < 0)
return ret;
return 0;
}
@ -1474,6 +1503,7 @@ static u32 i2c_subip_regs[] = {
THC_I2C_IC_INTR_MASK_OFFSET,
THC_I2C_IC_RX_TL_OFFSET,
THC_I2C_IC_TX_TL_OFFSET,
THC_I2C_IC_SDA_HOLD_OFFSET,
THC_I2C_IC_DMA_CR_OFFSET,
THC_I2C_IC_DMA_TDLR_OFFSET,
THC_I2C_IC_DMA_RDLR_OFFSET,
@ -1490,20 +1520,19 @@ static u32 i2c_subip_regs[] = {
* thc_i2c_subip_init - Initialize and configure THC I2C subsystem
*
* @dev: The pointer of THC private device context
* @target_address: Slave address of touch device (TIC)
* @speed: I2C bus frequency speed mode
* @hcnt: I2C clock SCL high count
* @lcnt: I2C clock SCL low count
* @i2c_config: The pointer of THC I2C bus configure structure
*
* Return: 0 on success, other error codes on failed.
*/
int thc_i2c_subip_init(struct thc_device *dev, const u32 target_address,
const u32 speed, const u32 hcnt, const u32 lcnt)
int thc_i2c_subip_init(struct thc_device *dev, const struct thc_i2c_config *i2c_config)
{
u32 read_size = sizeof(u32);
u32 val;
int ret;
if (!dev || !i2c_config)
return -EINVAL;
ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_ENABLE_OFFSET, &read_size, &val);
if (ret < 0)
return ret;
@ -1513,17 +1542,7 @@ int thc_i2c_subip_init(struct thc_device *dev, const u32 target_address,
if (ret < 0)
return ret;
ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_TAR_OFFSET, &read_size, &val);
if (ret < 0)
return ret;
val &= ~THC_I2C_IC_TAR_IC_TAR;
val |= FIELD_PREP(THC_I2C_IC_TAR_IC_TAR, target_address);
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_TAR_OFFSET, sizeof(u32), &val);
if (ret < 0)
return ret;
ret = thc_i2c_subip_set_speed(dev, speed, hcnt, lcnt);
ret = thc_i2c_subip_bus_config(dev, i2c_config);
if (ret < 0)
return ret;

View File

@ -49,6 +49,26 @@ enum thc_int_type {
THC_UNKNOWN_INT
};
/**
* struct thc_i2c_config - THC I2C bus configuration
* @target_addr: Slave address of touch device (TIC)
* @addr_mode: Slave address mode of touch device (TIC), 7bit or 10bit
* @speed: I2C bus frequency speed mode
* @scl_hcnt: I2C clock SCL high count
* @scl_lcnt: I2C clock SCL low count
* @sda_tx_hold: I2C Data SDA transmit hold period
* @sda_rx_hold: I2C Data SDA receive hold period
*/
struct thc_i2c_config {
u16 target_addr;
u8 addr_mode;
u32 speed;
u32 scl_hcnt;
u32 scl_lcnt;
u32 sda_tx_hold;
u32 sda_rx_hold;
};
/**
* struct thc_device - THC private device struct
* @thc_regmap: MMIO regmap structure for accessing THC registers
@ -121,8 +141,7 @@ int thc_spi_write_config(struct thc_device *dev, u32 spi_freq_val,
u32 io_mode, u32 opcode, u32 spi_wr_mps, u32 perf_limit);
void thc_spi_input_output_address_config(struct thc_device *dev, u32 input_hdr_addr,
u32 input_bdy_addr, u32 output_addr);
int thc_i2c_subip_init(struct thc_device *dev, const u32 target_address,
const u32 speed, const u32 hcnt, const u32 lcnt);
int thc_i2c_subip_init(struct thc_device *dev, const struct thc_i2c_config *i2c_config);
int thc_i2c_subip_regs_save(struct thc_device *dev);
int thc_i2c_subip_regs_restore(struct thc_device *dev);
int thc_i2c_set_rx_max_size(struct thc_device *dev, u32 max_rx_size);

View File

@ -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)

View File

@ -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,

View File

@ -887,4 +887,7 @@ enum THC_I2C_SPEED_MODE {
#define THC_I2C_IC_DMA_CR_RDMAE BIT(0)
#define THC_I2C_IC_DMA_CR_TDMAE BIT(1)
#define THC_I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD GENMASK(15, 0)
#define THC_I2C_IC_SDA_HOLD_IC_SDA_RX_HOLD GENMASK(23, 16)
#endif /* _INTEL_THC_HW_H_ */

View File

@ -1539,13 +1539,20 @@ static int pidff_check_autocenter(struct pidff_device *pidff,
int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks)
{
struct pidff_device *pidff;
struct hid_input *hidinput =
list_entry(hid->inputs.next, struct hid_input, list);
struct input_dev *dev = hidinput->input;
struct hid_input *hidinput;
struct input_dev *dev;
struct ff_device *ff;
int max_effects;
int error;
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
return -ENODEV;
}
hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
dev = hidinput->input;
hid_dbg(hid, "starting pid init\n");
if (list_empty(&hid->report_enum[HID_OUTPUT_REPORT].report_list)) {

View File

@ -1192,8 +1192,11 @@ static int int_dist(int x1, int y1, int x2, int y2)
static void wacom_intuos_bt_process_data(struct wacom_wac *wacom,
unsigned char *data)
{
memcpy(wacom->data, data, 10);
u8 *saved_data = wacom->data;
wacom->data = data;
wacom_intuos_irq(wacom);
wacom->data = saved_data;
input_sync(wacom->pen_input);
if (wacom->pad_input)
@ -1202,7 +1205,7 @@ static void wacom_intuos_bt_process_data(struct wacom_wac *wacom,
static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
{
u8 *data = kmemdup(wacom->data, len, GFP_KERNEL);
u8 *data = wacom->data;
int i = 1;
unsigned power_raw, battery_capacity, bat_charging, ps_connected;
@ -1242,7 +1245,6 @@ static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
break;
}
kfree(data);
return 0;
}

View File

@ -914,6 +914,21 @@ config INPUT_SOC_BUTTON_ARRAY
To compile this driver as a module, choose M here: the
module will be called soc_button_array.
config INPUT_AMD_SFH_TABLETMODE
tristate "AMD SFH tablet-mode switch"
depends on AMD_SFH_HID
select AUXILIARY_BUS
help
Expose SW_TABLET_MODE for AMD convertible laptops whose
operation-mode sensor is provided by the AMD Sensor Fusion Hub.
Userspace components such as systemd-logind and modern desktop
environments react to SW_TABLET_MODE when the device is folded
into tablet posture.
To compile this driver as a module, choose M here: the module
will be called amd_sfh_tabletmode.
config INPUT_DRV260X_HAPTICS
tristate "TI DRV260X haptics support"
depends on INPUT && I2C

View File

@ -15,6 +15,7 @@ obj-$(CONFIG_INPUT_AD714X_SPI) += ad714x-spi.o
obj-$(CONFIG_INPUT_ADXL34X) += adxl34x.o
obj-$(CONFIG_INPUT_ADXL34X_I2C) += adxl34x-i2c.o
obj-$(CONFIG_INPUT_ADXL34X_SPI) += adxl34x-spi.o
obj-$(CONFIG_INPUT_AMD_SFH_TABLETMODE) += amd_sfh_tabletmode.o
obj-$(CONFIG_INPUT_APANEL) += apanel.o
obj-$(CONFIG_INPUT_ARIEL_PWRBUTTON) += ariel-pwrbutton.o
obj-$(CONFIG_INPUT_ARIZONA_HAPTICS) += arizona-haptics.o

View File

@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD SFH tablet-mode switch driver
*
* Copyright (c) 2026, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
*/
#include <linux/amd-pmf-io.h>
#include <linux/auxiliary_bus.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/pci_ids.h>
#define POLL_INTERVAL_MS 200
static void sfh_tm_poll(struct input_dev *input)
{
struct amd_sfh_info info = {};
if (amd_get_sfh_info(&info, MT_OP_MODE))
return;
input_report_switch(input, SW_TABLET_MODE,
info.op_mode == SFH_MODE_TABLET);
input_sync(input);
}
static int sfh_tm_probe(struct auxiliary_device *auxdev,
const struct auxiliary_device_id *id)
{
struct device *dev = &auxdev->dev;
struct amd_sfh_info info = {};
struct input_dev *input;
int error;
error = amd_get_sfh_info(&info, MT_OP_MODE);
if (error)
return error == -EINVAL ? -ENODEV : error;
input = devm_input_allocate_device(dev);
if (!input)
return -ENOMEM;
input->name = "AMD SFH tablet mode switch";
input->phys = "amd-sfh/tabletmode";
input->id.bustype = BUS_HOST;
input->id.vendor = PCI_VENDOR_ID_AMD;
input_set_capability(input, EV_SW, SW_TABLET_MODE);
error = input_setup_polling(input, sfh_tm_poll);
if (error)
return error;
input_set_poll_interval(input, POLL_INTERVAL_MS);
sfh_tm_poll(input);
error = input_register_device(input);
if (error)
return error;
return 0;
}
static const struct auxiliary_device_id sfh_tm_id_table[] = {
{ .name = "amd_sfh.tabletmode" },
{}
};
MODULE_DEVICE_TABLE(auxiliary, sfh_tm_id_table);
static struct auxiliary_driver sfh_tm_driver = {
.name = "tabletmode",
.id_table = sfh_tm_id_table,
.probe = sfh_tm_probe,
};
module_auxiliary_driver(sfh_tm_driver);
MODULE_DESCRIPTION("AMD SFH tablet mode switch");
MODULE_LICENSE("GPL");

View File

@ -19,11 +19,13 @@
* @MT_HPD: Message ID to know the Human presence info from MP2 FW
* @MT_ALS: Message ID to know the Ambient light info from MP2 FW
* @MT_SRA: Message ID to know the SRA data from MP2 FW
* @MT_OP_MODE: Message ID to know the operating-mode (tablet/laptop) info
*/
enum sfh_message_type {
MT_HPD,
MT_ALS,
MT_SRA,
MT_OP_MODE,
};
/**
@ -44,12 +46,24 @@ enum sfh_hpd_info {
* @user_present: Populates the user presence information
* @platform_type: Operating modes (clamshell, flat, tent, etc.)
* @laptop_placement: Device states (ontable, onlap, outbag)
* @op_mode: Operating-mode field (see enum sfh_dev_mode); used for tablet detection
*/
struct amd_sfh_info {
u32 ambient_light;
u8 user_present;
u32 platform_type;
u32 laptop_placement;
u32 op_mode;
};
/**
* enum sfh_dev_mode - SFH operating-mode field (sfh_op_mode.mode, bits 0-2)
* @SFH_MODE_LAPTOP: Device is in laptop/clamshell posture
* @SFH_MODE_TABLET: Device is in tablet posture
*/
enum sfh_dev_mode {
SFH_MODE_LAPTOP = 1,
SFH_MODE_TABLET = 3,
};
enum laptop_placement {

View File

@ -642,6 +642,7 @@ enum hid_battery_status {
* @max: maximum battery value from HID descriptor
* @report_type: HID report type (input/feature)
* @report_id: HID report ID for this battery
* @report_offset: bit offset of the capacity field within its report
* @charge_status: current charging status
* @status: battery reporting status
* @capacity: current battery capacity (0-100)
@ -657,6 +658,7 @@ struct hid_battery {
__s32 max;
__s32 report_type;
__s32 report_id;
__s32 report_offset;
__s32 charge_status;
enum hid_battery_status status;
__s32 capacity;
@ -1021,7 +1023,7 @@ extern void hid_unregister_driver(struct hid_driver *);
extern void hidinput_hid_event(struct hid_device *, struct hid_field *, struct hid_usage *, __s32);
extern void hidinput_report_event(struct hid_device *hid, struct hid_report *report);
extern int hidinput_connect(struct hid_device *hid, unsigned int force);
extern int hidinput_connect(struct hid_device *hid, unsigned int connect_mask);
extern void hidinput_disconnect(struct hid_device *);
void hidinput_reset_resume(struct hid_device *hid);

View File

@ -0,0 +1,34 @@
/// Detect HID drivers that initialize force-feedback after hid_hw_start()
/// when HID_CONNECT_HIDINPUT is used. This is a lifecycle violation as
/// the input device is already registered.
//
// Confidence: High
// Copyright: (C) 2026 Gemini. GPLv2.
virtual report
@r@
identifier probe_fn;
expression hdev, flags;
position p1, p2;
@@
probe_fn(struct hid_device *hdev, ...) {
<...
hid_hw_start@p1(hdev, flags)
...
\(input_ff_create\|input_ff_create_memless\)@p2(...)
...>
}
@script:python depends on report@
p1 << r.p1;
p2 << r.p2;
flags << r.flags;
@@
# Check if flags include HID_CONNECT_HIDINPUT (0x01) or HID_CONNECT_DEFAULT (0x0f)
# Note: HID_CONNECT_DEFAULT is 0x0f, HID_CONNECT_HIDINPUT is 0x01
if "HID_CONNECT_HIDINPUT" in flags or "HID_CONNECT_DEFAULT" in flags:
msg = "WARNING: force-feedback initialized after hid_hw_start() with HID_CONNECT_HIDINPUT. Input device is already registered at this point. Use .input_configured() instead."
coccilib.report.print_report(p2[0], msg)