Linux Input/HID development
 help / color / mirror / Atom feed
* Re: [PATCH V3 RESEND 0/2] Add tc3408 bindings and timing
From: Dmitry Torokhov @ 2026-02-26 16:06 UTC (permalink / raw)
  To: Langyan Ye
  Cc: robh, krzk+dt, conor+dt, jikos, bentiss, dianders, linux-input,
	devicetree, linux-kernel
In-Reply-To: <20260108063524.742464-1-yelangyan@huaqin.corp-partner.google.com>

On Thu, Jan 08, 2026 at 02:35:22PM +0800, Langyan Ye wrote:
> The tc3408 touch screen chip same as Elan eKTH6915 controller
> has a reset gpio. The difference is that they have different
> post_power_delay_ms.
> 
> According to the Parade TC3408 datasheet, the reset pin requires a
> pull-down duration longer than 10 ms, therefore post_power_delay_ms
> is set to 10. In addition, the chipset requires an initialization
> time greater than 300 ms after reset, so post_gpio_reset_on_delay_ms
> is configured as 300.
> 
> Changes in v3:
> - PATCH 2/2: Corrected post_gpio_reset_on_delay_ms: 100 -> 300
> - Link to v2: https://lore.kernel.org/all/20250820122520.3356738-1-yelangyan@huaqin.corp-partner.google.com/
> 
> Changes in v2:
> - PATCH 1/2: Drop redundant "bindings for" from subject
> - PATCH 1/2: Improve description (describe hardware instead of bindings)
> - PATCH 1/2: Drop "panel: true" property
> - PATCH 1/2: Drop redundant description for reset-gpios
> - PATCH 1/2: Use unevaluatedProperties: false instead of additionalProperties
> - Link to v1: https://lore.kernel.org/all/20250819034852.1230264-1-yelangyan@huaqin.corp-partner.google.com/
> 
> Langyan Ye (2):
>   dt-bindings: input: Add Parade TC3408 touchscreen controller
>   HID: i2c-hid: elan: Add parade-tc3408 timing
> 
>  .../bindings/input/parade,tc3408.yaml         | 68 +++++++++++++++++++
>  drivers/hid/i2c-hid/i2c-hid-of-elan.c         |  8 +++
>  2 files changed, 76 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/input/parade,tc3408.yaml

Applied the lot, thank you.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH 1/1] HID: uhid: Fix out-of-bounds write caused by raw events mismanagement
From: Lee Jones @ 2026-02-26 16:23 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Jiri Kosina, David Rheinsberg, linux-input, linux-kernel, stable
In-Reply-To: <aaBqruhB0a7m0SBG@plouf>

On Thu, 26 Feb 2026, Benjamin Tissoires wrote:

> On Feb 26 2026, Lee Jones wrote:
> > On Thu, 26 Feb 2026, Benjamin Tissoires wrote:
> > 
> > > On Feb 26 2026, Lee Jones wrote:
> > > > On Tue, 24 Feb 2026, Jiri Kosina wrote:
> > > > 
> > > > > On Tue, 24 Feb 2026, Benjamin Tissoires wrote:
> > > > > 
> > > > > > Long story short: that patch is too intrusive as it makes assumption on
> > > > > > the behavior of the device. We need to understand where/if the bug was
> > > > > > spotted and fix the caller of hid_hw_raw_request, not the uhid
> > > > > > implementation.
> > > > > 
> > > > > Thanks a lot for the analysis, Benjamin!
> > > > > 
> > > > > I asked about that here:
> > > > > 
> > > > > 	https://lore.kernel.org/all/172q4775-616s-p7s4-7n80-p8579n0r3516@xreary.bet/
> > > > > 
> > > > > So let's wait for Lee to clarify. Until that, the patch stays out of the 
> > > > > branch.
> > > > 
> > > > Thanks to both of you for looking into this.  I appreciate your efforts.
> > > > 
> > > > This is very much real world.
> > > > 
> > > > Is there a way to add an errata for the PS3 controller?
> > > > 
> > > 
> > > Unfortunatelly no. uhid merely emulates what a device can do, and HID is
> > > a convention. So if we were to have a special case to PS3 controllers,
> > > we would then start having to maintain an endless list of quirks when
> > > the issue is *not* in uhid, but in the processing of the device after
> > > (maybe in hid-core?).
> > 
> > Actually I think the issue is in UHID.  At least the way I read it.
> 
> And I disagree :)
> 
> > 
> > Are there legitimate use-cases for devices overwriting the Report ID
> > contained in the first index of the data buffer?  From my very limited
> > knowledge of the subsystem, this sounds like an oversight.
> > 
> 
> Legitimate, probably no, but we are talking about physical devices
> here. uhid is a mere replacement of a transport layer, and there is
> nothing that prevents a device to reply with a buffer starting with 1
> when requested about feature 2 (because it's firmware and they just
> don't care).
> 
> This happens a lot with proprietary features on devices, when there is
> no spec, so ODM provide their own driver and they can do whatever they
> want.
> 
> If uhid or any transport layer solely takes the decision that a reply to
> a request is wrong, we have no chance of fixing it after the fact. This
> is what happens with the PS3 controller: an undocumented feature is
> used, but that's what the Playstation does, so we need to tag along.
> 
> I hope it makes more sense now.
> 
> FTR, Lee shared the logs of the issue privately, and I already told him
> where we should fix the issue.

Thanks for all the help and the insight.

I will follow-up with the suggested fixes soon.

-- 
Lee Jones [李琼斯]

^ permalink raw reply

* Re: [PATCH v5 01/16] include: device.h: Add named device attributes
From: Jiri Kosina @ 2026-02-26 17:05 UTC (permalink / raw)
  To: Derek J. Clark
  Cc: Benjamin Tissoires, Richard Hughes, Mario Limonciello,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	linux-input, linux-doc, linux-kernel, Greg Kroah-Hartman,
	Rafael J. Wysocki
In-Reply-To: <20260224013217.1363996-2-derekjohn.clark@gmail.com>

You would need and Ack from driver core maintainers (CCing Greg and Rafael 
here) on this one so that I can take it together with the rest of the 
series.

Thanks,

-- 
Jiri Kosina
SUSE Labs

On Tue, 24 Feb 2026, Derek J. Clark wrote:

> Adds DEVICE_ATTR_[RW|RO|WO]_NAMED macros for adding attributes that
> reuse the same sysfs name in a driver under separate subdirectories.
> 
> When dealing with some devices it can be useful to be able to reuse
> the same name for similar attributes under a different subdirectory.
> For example, a single logical HID endpoint may provide a configuration
> interface for multiple physical devices. In such a case it is useful to
> provide symmetrical attribute names under different subdirectories on
> the configuration device. The Lenovo Legion Go is one such device,
> providing configuration to a detachable left controller, detachable
> right controller, the wireless transmission dongle, and the MCU. It is
> therefore beneficial to treat each of these as individual devices in
> the driver, providing a subdirectory for each physical device in the
> sysfs. As some attributes are reused by each physical device, it
> provides a much cleaner interface if the same driver can reuse the same
> attribute name in sysfs while uniquely distinguishing the store/show
> functions in the driver, rather than repeat string portions.
> 
> Example new WO attrs:
> ATTRS{left_handle/reset}=="(not readable)"
> ATTRS{right_handle/reset}=="(not readable)"
> ATTRS{tx_dongle/reset}=="(not readable)"
> 
> vs old WO attrs in a subdir:
> ATTRS{left_handle/left_handle_reset}=="(not readable)"
> ATTRS{right_handle/right_handle_reset}=="(not readable)"
> ATTRS{tx_dongle/tx_dongle_reset}=="(not readable)"
> 
> or old WO attrs with no subdir:
> ATTRS{left_handle_reset}=="(not readable)"
> ATTRS{right_handle_reset}=="(not readable)"
> ATTRS{tx_dongle_reset}=="(not readable)"
> 
> While the third option is usable, it doesn't logically break up the
> physical devices and creates a device directory with over 80 attributes
> once all attrs are defined.
> 
> Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
>   - Use dmabuf per request instead of devm allocated static buffer.
>     Resolves bug with side effects during suspend.
> ---
>  include/linux/device.h | 46 ++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 46 insertions(+)
> 
> diff --git a/include/linux/device.h b/include/linux/device.h
> index 0be95294b6e61..381463baed6d3 100644
> --- a/include/linux/device.h
> +++ b/include/linux/device.h
> @@ -189,6 +189,22 @@ ssize_t device_show_string(struct device *dev, struct device_attribute *attr,
>  #define DEVICE_ATTR_ADMIN_RW(_name) \
>  	struct device_attribute dev_attr_##_name = __ATTR_RW_MODE(_name, 0600)
>  
> +/**
> + * DEVICE_ATTR_RW_NAMED - Define a read-write device attribute with a sysfs name
> + * that differs from the function name.
> + * @_name: Attribute function preface
> + * @_attrname: Attribute name as it wil be exposed in the sysfs.
> + *
> + * Like DEVICE_ATTR_RW(), but allows for reusing names under separate paths in
> + * the same driver.
> + */
> +#define DEVICE_ATTR_RW_NAMED(_name, _attrname)                            \
> +	struct device_attribute dev_attr_##_name = {                      \
> +		.attr = { .name = _attrname, .mode = 0644 }, \
> +		.show = _name##_show,                                     \
> +		.store = _name##_store,                                   \
> +	}
> +
>  /**
>   * DEVICE_ATTR_RO - Define a readable device attribute.
>   * @_name: Attribute name.
> @@ -207,6 +223,21 @@ ssize_t device_show_string(struct device *dev, struct device_attribute *attr,
>  #define DEVICE_ATTR_ADMIN_RO(_name) \
>  	struct device_attribute dev_attr_##_name = __ATTR_RO_MODE(_name, 0400)
>  
> +/**
> + * DEVICE_ATTR_RO_NAMED - Define a read-only device attribute with a sysfs name
> + * that differs from the function name.
> + * @_name: Attribute function preface
> + * @_attrname: Attribute name as it wil be exposed in the sysfs.
> + *
> + * Like DEVICE_ATTR_RO(), but allows for reusing names under separate paths in
> + * the same driver.
> + */
> +#define DEVICE_ATTR_RO_NAMED(_name, _attrname)                            \
> +	struct device_attribute dev_attr_##_name = {                      \
> +		.attr = { .name = _attrname, .mode = 0444 }, \
> +		.show = _name##_show,                                     \
> +	}
> +
>  /**
>   * DEVICE_ATTR_WO - Define an admin-only writable device attribute.
>   * @_name: Attribute name.
> @@ -216,6 +247,21 @@ ssize_t device_show_string(struct device *dev, struct device_attribute *attr,
>  #define DEVICE_ATTR_WO(_name) \
>  	struct device_attribute dev_attr_##_name = __ATTR_WO(_name)
>  
> +/**
> + * DEVICE_ATTR_WO_NAMED - Define a read-only device attribute with a sysfs name
> + * that differs from the function name.
> + * @_name: Attribute function preface
> + * @_attrname: Attribute name as it wil be exposed in the sysfs.
> + *
> + * Like DEVICE_ATTR_WO(), but allows for reusing names under separate paths in
> + * the same driver.
> + */
> +#define DEVICE_ATTR_WO_NAMED(_name, _attrname)                            \
> +	struct device_attribute dev_attr_##_name = {                      \
> +		.attr = { .name = _attrname, .mode = 0200 }, \
> +		.store = _name##_store,                                   \
> +	}
> +
>  /**
>   * DEVICE_ULONG_ATTR - Define a device attribute backed by an unsigned long.
>   * @_name: Attribute name.
> -- 
> 2.52.0
> 

^ permalink raw reply

* Re: [PATCH v3 1/9] dt-bindings: regulator: cpcap-regulator: convert to DT schema
From: Mark Brown @ 2026-02-26 18:55 UTC (permalink / raw)
  To: Svyatoslav Ryhel
  Cc: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Pavel Machek, Liam Girdwood, David Lechner,
	Tony Lindgren, linux-input, devicetree, linux-kernel, linux-leds
In-Reply-To: <20260223063858.12208-2-clamor95@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 529 bytes --]

On Mon, Feb 23, 2026 at 08:38:50AM +0200, Svyatoslav Ryhel wrote:
> Convert devicetree bindings for the Motorola CPCAP MFD regulator subnode
> from TXT to YAML format. Main functionality preserved.

Please submit patches using subject lines reflecting the style for the
subsystem, this makes it easier for people to identify relevant patches.
Look at what existing commits in the area you're changing are doing and
make sure your subject lines visually resemble what they're doing.
There's no need to resubmit to fix this alone.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply

* Re: (subset) [PATCH v3 0/9] mfd: cpcap: convert documentation to schema and add Mot board support
From: Mark Brown @ 2026-02-26 22:13 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Pavel Machek, Liam Girdwood, David Lechner,
	Tony Lindgren, Svyatoslav Ryhel
  Cc: linux-input, devicetree, linux-kernel, linux-leds
In-Reply-To: <20260223063858.12208-1-clamor95@gmail.com>

On Mon, 23 Feb 2026 08:38:49 +0200, Svyatoslav Ryhel wrote:
> The initial goal was only to add support for the CPCAP used in the Mot
> Tegra20 board; however, since the documentation was already partially
> converted, I decided to complete the conversion to schema too.
> 
> The CPCAP regulator, leds, rtc, pwrbutton and core files were converted
> from TXT to YAML while preserving the original structure. Mot board
> compatibility was added to the regulator and core schema. Since these
> were one-line patches, they were not separated into dedicated commits;
> however, the commit message notes this for both cases.
> 
> [...]

Applied to

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regulator.git for-next

Thanks!

[1/9] dt-bindings: regulator: cpcap-regulator: convert to DT schema
      commit: 5a8ffc5dca9c096fe9c8879fa3a2faff723fbb8a
[2/9] dt-bindings: regulator: cpcap-regulator: document Mot regulator
      commit: 0e5646030212856a7f022387a647694ba65e75a7
[3/9] regulator: cpcap-regulator: add support for Mot regulators
      commit: d90c0f78379454d51a428e312ac6db573060185c

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark


^ permalink raw reply

* Re: [PATCH v5 01/16] include: device.h: Add named device attributes
From: Greg Kroah-Hartman @ 2026-02-26 22:38 UTC (permalink / raw)
  To: Jiri Kosina
  Cc: Derek J. Clark, Benjamin Tissoires, Richard Hughes,
	Mario Limonciello, Zhixin Zhang, Mia Shao, Mark Pearson,
	Pierre-Loup A . Griffais, linux-input, linux-doc, linux-kernel,
	Rafael J. Wysocki
In-Reply-To: <478589q4-1rp6-87q8-02o8-n50071039639@xreary.bet>

On Thu, Feb 26, 2026 at 06:05:10PM +0100, Jiri Kosina wrote:
> You would need and Ack from driver core maintainers (CCing Greg and Rafael 
> here) on this one so that I can take it together with the rest of the 
> series.

The comments are a bit odd in the descriptions of these new macros, but
hey, it's good enough to understand, so no objection from me.

Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

^ permalink raw reply

* Re: [PATCH v5 00/16] HID: Add Legion Go and Go S Drivers
From: Jiri Kosina @ 2026-02-26 22:53 UTC (permalink / raw)
  To: Derek J. Clark
  Cc: Benjamin Tissoires, Richard Hughes, Mario Limonciello,
	Zhixin Zhang, Mia Shao, Mark Pearson, Pierre-Loup A . Griffais,
	linux-input, linux-doc, linux-kernel
In-Reply-To: <20260224013217.1363996-1-derekjohn.clark@gmail.com>

On Tue, 24 Feb 2026, Derek J. Clark wrote:

> This series adds configuration driver support for the Legion Go S,
> Legion Go, and Legion Go 2 built-in controller HID interfaces. This
> allows for configuring hardware specific attributes such as the auso
> sleep timeout, rumble intensity, etc. non-configuration reports are
> forwarded to the HID subsystem to ensure no loss of functionality in
> userspace. Basic gamepad functionality is provided through xpad, while
> advanced features are currently only implemented in userspace daemons
> such as InputPlumber[1]. I plan to move this functionality into the
> kernel in a later patch series.
> 
> Three new device.h macros are added that solve a fairly specific
> problem. Many of the attributes need to have the same name as other
> attributes when they are in separate attribute subdirectories. The
> previous version of this series, along with the upcoming his-asus-ally
> driver[2] use this macro to simplify the sysfs by removing redundancy.
> An upcoming out of tree driver for the Zotac Zone [3] also found this
> macro to be useful. This greatly reduces the path length and term
> redundancy of file paths in the sysfs, while also allowing for cleaner
> subdirectories that are grouped by functionality. Rather than carry the
> same macro in four drivers, it seems beneficial to me that we include the
> macro with the other device macros.
> 
> A new HID uevent property is also added, HID_FIRMWARE_VERSION, so as to
> permit fwupd to read the firmware version of the Go S HID interface without
> detaching the kernel driver.
> 
> Finally, there are some checkpatch warnings that will need to be supressed:
> WARNING: ENOSYS means 'invalid syscall nr' and nothing else
> 1292: FILE: drivers/hid/lenovo-legos-hid/lenovo-legos-hid-config.c:1085:
> +       case -ENOSYS: /* during rmmod -ENOSYS is expected */
> 
> This error handling case was added as it is experienced in the real world
> when the driver is rmmod. The LED subsystem produces this error code in
> its legacy code and this is not a new novel use of -ENOSYS, we are simply
> catching the case to avoid spurious errors in dmesg when the drivers are
> removed.
> 
> [1]: https://github.com/ShadowBlip/InputPlumber/tree/main/src/drivers/lego
> [2]: https://lore.kernel.org/all/20240806081212.56860-1-luke@ljones.dev/
> [3]: https://github.com/flukejones/linux/tree/wip/zotac-zone-6.15/drivers/hid/zotac-zone-hid
> 
> Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> Change Log
> v5:
>   - Make all RO attributes cache the data during probe using delayed
>     work for both drivers. All RW attributes are read in realtime to
>     ensure they match the device current state in the event of firmware
>     reset or a userspace application.
>   - Fix endianness of version strings and print as hex for Go driver.
>   - Remove reset__esume function. It was not being hit as the MCU of
>     both devices disconnects of suspend, forcing a reinit of the driver.
>     Udev or userpsace will need to set the OS Mode upon resume.
> v4: https://lore.kernel.org/linux-input/20260220070533.4083667-1-derekjohn.clark@gmail.com/
>   - Use dmabuf allocated per request for both drivers instead of a devm
>     preallocated buffer that is reused. This solves a bug where some
>     attributes couldn't be restored without manual writing after resume.
>   - Reduce the number of quirks and flags in the Go S init to only those
>     necessary. Previously they were duplicated from the Go driver but
>     everything except HID_CONNECT_HIDRAW was found to be unnessary
>     during operational testing.
>   - Clean up formatting for debug prints in Go S driver.
>   - Fix bugs in RGB driver for Go that caused the effect to switch to
>     solid when the speed or brightness was changed.
>   - Remove extraneous setting of os_mode member of drvdata when setting
>     os_mode. It will be read from the hardware in _show.
> v3: https://lore.kernel.org/linux-input/20260124014907.991265-1-derekjohn.clark@gmail.com/
>   - Fix Documentation formatting by removing extra + characters.
>   - Fix bugs in hid-lenovo-go-s IMU & TP RO attributes being tied to the
>     wrong _show function.
>   - Rename enume os_mode_index to os_mode_types_index to fix collision
>     with os_mode_index attribute.
>   - Remove accidental rename for enabled->enable attributes in patch 4
>   - Add SOB for Mario in patch 10 as Co-Developer.
> v2: https://lore.kernel.org/linux-input/20251229031753.581664-1-derekjohn.clark@gmail.com/
>   - Break up adding the Go S driver into feature specific patches.
>   - Rename Go S driver from lenovo-legos-hid to hid-lenovo-go-s and move
>     it out of a subdirectory.
>   - Drop the arbitrary uevent properties patch.
>   - Add Go series driver.
>   - Move DEVICE_ATTR_NAMED macros to device.h.
> v1: https://lore.kernel.org/linux-input/20250703004943.515919-1-derekjohn.clark@gmail.com/

This is now queued in hid.git#for-7.1/lenovo.

Thanks,

-- 
Jiri Kosina
SUSE Labs


^ permalink raw reply

* [PATCH 1/1] HID: logitech-hidpp: Prevent use-after-free on force feedback initialisation failure
From: Lee Jones @ 2026-02-27 10:09 UTC (permalink / raw)
  To: lee, Filipe Laíns, Bastien Nocera, Jiri Kosina,
	Benjamin Tissoires, linux-input, linux-kernel

Presently, if the force feedback initialisation fails when probing the
Logitech G920 Driving Force Racing Wheel for Xbox One, an error number
will be returned and propagated before the userspace infrastructure
(sysfs and /dev/input) has been torn down.  If userspace ignores the
errors and continues to use its references to these dangling entities, a
UAF will promptly follow.

We have 2 options; continue to return the error, but ensure that all of
the infrastructure is torn down accordingly or continue to treat this
condition as a warning by emitting the message but returning success.
It is thought that the original author's intention was to emit the
warning but keep the device functional, less the force feedback feature,
so let's go with that.

Signed-off-by: Lee Jones <lee@kernel.org>
---
 drivers/hid/hid-logitech-hidpp.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index e871f1729d4b..eee9ab6a2fc4 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -4487,10 +4487,12 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 		if (!ret)
 			ret = hidpp_ff_init(hidpp, &data);
 
-		if (ret)
+		if (ret) {
 			hid_warn(hidpp->hid_dev,
 		     "Unable to initialize force feedback support, errno %d\n",
 				 ret);
+			ret = 0;
+		}
 	}
 
 	/*
-- 
2.53.0.473.g4a7958ca14-goog


^ permalink raw reply related

* Re: [PATCH 1/2] hid: asus: add xg mobile 2023 external hardware support
From: Jiri Kosina @ 2026-02-27 13:11 UTC (permalink / raw)
  To: Denis Benato
  Cc: linux-kernel, linux-input, Benjamin Tissoires, Luke D . Jones,
	Mateusz Schyboll, Denis Benato, Ilpo Järvinen
In-Reply-To: <20260216175539.12415-1-denis.benato@linux.dev>

On Mon, 16 Feb 2026, Denis Benato wrote:

> XG mobile stations have the 0x5a endpoint and has to be initialized:
> add them to hid-asus.

Applied to hid.git#for-7.0/upstream-fixes.

-- 
Jiri Kosina
SUSE Labs


^ permalink raw reply

* Re: [PATCH 2/2] hid: asus: always fully initialize devices
From: Jiri Kosina @ 2026-02-27 13:11 UTC (permalink / raw)
  To: Denis Benato
  Cc: linux-kernel, linux-input, Benjamin Tissoires, Luke D . Jones,
	Mateusz Schyboll, Denis Benato, Ilpo Järvinen
In-Reply-To: <20260216175539.12415-2-denis.benato@linux.dev>

On Mon, 16 Feb 2026, Denis Benato wrote:

> ASUS has more devices other than laptop keyboards which needs
> to be initializated: the init sequence is the same as keyboards.
> 
> ID1 and ID2 are USB report IDs that are correctly enumerated:
> send init commands to all devices that supports those reports IDs.
> 
> Signed-off-by: Denis Benato <denis.benato@linux.dev>

Applied to hid.git#for-7.1/asus.

-- 
Jiri Kosina
SUSE Labs


^ permalink raw reply

* Re: [PATCH v6 1/2] rust: core abstractions for HID drivers
From: Gary Guo @ 2026-02-27 15:27 UTC (permalink / raw)
  To: Rahul Rameshbabu, linux-input, linux-kernel, rust-for-linux
  Cc: a.hindborg, alex.gaynor, aliceryhl, benjamin.tissoires,
	benno.lossin, bjorn3_gh, boqun.feng, dakr, db48x, gary, jikos,
	ojeda, peter.hutterer, tmgross
In-Reply-To: <20260222215611.79760-2-sergeantsagara@protonmail.com>

On Sun Feb 22, 2026 at 9:56 PM GMT, Rahul Rameshbabu wrote:
> These abstractions enable the development of HID drivers in Rust by binding
> with the HID core C API. They provide Rust types that map to the
> equivalents in C. In this initial draft, only hid_device and hid_device_id
> are provided direct Rust type equivalents. hid_driver is specially wrapped
> with a custom Driver type. The module_hid_driver! macro provides analogous
> functionality to its C equivalent. Only the .report_fixup callback is
> binded to Rust so far.
>
> Future work for these abstractions would include more bindings for common
> HID-related types, such as hid_field, hid_report_enum, and hid_report as
> well as more bus callbacks. Providing Rust equivalents to useful core HID
> functions will also be necessary for HID driver development in Rust.
>
> Signed-off-by: Rahul Rameshbabu <sergeantsagara@protonmail.com>
> ---
>
> Notes:
>     Changelog:
>     
>         v5->v6:
>           * Converted From<u16> for Group to TryFrom<u16> to properly handle
>             error case
>           * Renamed into method for Group to into_u16 to not conflate with the
>             From trait
>           * Refactored new upstream changes to RegistrationOps
>           * Implemented DriverLayout trait for hid::Adapter<T>
>         v4->v5:
>           * Add rust/ to drivers/hid/Makefile
>           * Implement RawDeviceIdIndex trait
>         v3->v4:
>           * Removed specifying tree in MAINTAINERS file since that is up for
>             debate
>           * Minor rebase cleanup
>           * Moved driver logic under drivers/hid/rust
>         v2->v3:
>           * Implemented AlwaysRefCounted trait using embedded struct device's
>             reference counts instead of the separate reference counter in struct
>             hid_device
>           * Used &raw mut as appropriate
>           * Binded include/linux/device.h for get_device and put_device
>           * Cleaned up various comment related formatting
>           * Minified dev_err! format string
>           * Updated Group enum to be repr(u16)
>           * Implemented From<u16> trait for Group
>           * Added TODO comment when const_trait_impl stabilizes
>           * Made group getter functions return a Group variant instead of a raw
>             number
>           * Made sure example code builds
>         v1->v2:
>           * Binded drivers/hid/hid-ids.h for use in Rust drivers
>           * Remove pre-emptive referencing of a C HID driver instance before
>             it is fully initialized in the driver registration path
>           * Moved static getters to generic Device trait implementation, so
>             they can be used by all device::DeviceContext
>           * Use core macros for supporting DeviceContext transitions
>           * Implemented the AlwaysRefCounted and AsRef traits
>           * Make use for dev_err! as appropriate
>         RFC->v1:
>           * Use Danilo's core infrastructure
>           * Account for HID device groups
>           * Remove probe and remove callbacks
>           * Implement report_fixup support
>           * Properly comment code including SAFETY comments
>
>  MAINTAINERS                     |   8 +
>  drivers/hid/Kconfig             |   2 +
>  drivers/hid/Makefile            |   2 +
>  drivers/hid/rust/Kconfig        |  12 +
>  rust/bindings/bindings_helper.h |   3 +
>  rust/kernel/hid.rs              | 530 ++++++++++++++++++++++++++++++++
>  rust/kernel/lib.rs              |   2 +
>  7 files changed, 559 insertions(+)
>  create mode 100644 drivers/hid/rust/Kconfig
>  create mode 100644 rust/kernel/hid.rs
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b8d8a5c41597..1fee14024fa2 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -11319,6 +11319,14 @@ F:	include/uapi/linux/hid*
>  F:	samples/hid/
>  F:	tools/testing/selftests/hid/
>  
> +HID CORE LAYER [RUST]
> +M:	Rahul Rameshbabu <sergeantsagara@protonmail.com>
> +R:	Benjamin Tissoires <bentiss@kernel.org>
> +L:	linux-input@vger.kernel.org
> +S:	Maintained
> +F:	drivers/hid/rust/*.rs
> +F:	rust/kernel/hid.rs
> +
>  HID LOGITECH DRIVERS
>  R:	Filipe Laíns <lains@riseup.net>
>  L:	linux-input@vger.kernel.org
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index c1d9f7c6a5f2..750c2d49a806 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -1439,6 +1439,8 @@ endmenu
>  
>  source "drivers/hid/bpf/Kconfig"
>  
> +source "drivers/hid/rust/Kconfig"
> +
>  source "drivers/hid/i2c-hid/Kconfig"
>  
>  source "drivers/hid/intel-ish-hid/Kconfig"
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index e01838239ae6..b78ab84c47b4 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -8,6 +8,8 @@ hid-$(CONFIG_HID_HAPTIC)	+= hid-haptic.o
>  
>  obj-$(CONFIG_HID_BPF)		+= bpf/
>  
> +obj-$(CONFIG_RUST_HID_ABSTRACTIONS)		+= rust/
> +
>  obj-$(CONFIG_HID)		+= hid.o
>  obj-$(CONFIG_UHID)		+= uhid.o
>  
> diff --git a/drivers/hid/rust/Kconfig b/drivers/hid/rust/Kconfig
> new file mode 100644
> index 000000000000..d3247651829e
> --- /dev/null
> +++ b/drivers/hid/rust/Kconfig
> @@ -0,0 +1,12 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +menu "Rust HID support"
> +
> +config RUST_HID_ABSTRACTIONS
> +	bool "Rust HID abstractions support"
> +	depends on RUST
> +	depends on HID=y
> +	help
> +	  Adds support needed for HID drivers written in Rust. It provides a
> +	  wrapper around the C hid core.
> +
> +endmenu # Rust HID support
> diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
> index 083cc44aa952..200e58af27a3 100644
> --- a/rust/bindings/bindings_helper.h
> +++ b/rust/bindings/bindings_helper.h
> @@ -48,6 +48,7 @@
>  #include <linux/cpumask.h>
>  #include <linux/cred.h>
>  #include <linux/debugfs.h>
> +#include <linux/device.h>
>  #include <linux/device/faux.h>
>  #include <linux/dma-direction.h>
>  #include <linux/dma-mapping.h>
> @@ -60,6 +61,8 @@
>  #include <linux/i2c.h>
>  #include <linux/interrupt.h>
>  #include <linux/io-pgtable.h>
> +#include <linux/hid.h>
> +#include "../../drivers/hid/hid-ids.h"
>  #include <linux/ioport.h>
>  #include <linux/jiffies.h>
>  #include <linux/jump_label.h>
> diff --git a/rust/kernel/hid.rs b/rust/kernel/hid.rs
> new file mode 100644
> index 000000000000..b9db542d923a
> --- /dev/null
> +++ b/rust/kernel/hid.rs
> @@ -0,0 +1,530 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +// Copyright (C) 2025 Rahul Rameshbabu <sergeantsagara@protonmail.com>
> +
> +//! Abstractions for the HID interface.
> +//!
> +//! C header: [`include/linux/hid.h`](srctree/include/linux/hid.h)
> +
> +use crate::{
> +    device,
> +    device_id::{
> +        RawDeviceId,
> +        RawDeviceIdIndex, //
> +    },
> +    driver,
> +    error::*,
> +    prelude::*,
> +    types::Opaque, //
> +};
> +use core::{
> +    marker::PhantomData,
> +    ptr::{
> +        addr_of_mut,
> +        NonNull, //
> +    } //
> +};
> +
> +/// Indicates the item is static read-only.
> +///
> +/// Refer to [Device Class Definition for HID 1.11]
> +/// Section 6.2.2.5 Input, Output, and Feature Items.
> +///
> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
> +pub const MAIN_ITEM_CONSTANT: u8 = bindings::HID_MAIN_ITEM_CONSTANT as u8;

These can be bitflags with `impl_flags!`?

> +
> +/// Indicates the item represents data from a physical control.
> +///
> +/// Refer to [Device Class Definition for HID 1.11]
> +/// Section 6.2.2.5 Input, Output, and Feature Items.
> +///
> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
> +pub const MAIN_ITEM_VARIABLE: u8 = bindings::HID_MAIN_ITEM_VARIABLE as u8;
> +
> +/// Indicates the item should be treated as a relative change from the previous
> +/// report.
> +///
> +/// Refer to [Device Class Definition for HID 1.11]
> +/// Section 6.2.2.5 Input, Output, and Feature Items.
> +///
> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
> +pub const MAIN_ITEM_RELATIVE: u8 = bindings::HID_MAIN_ITEM_RELATIVE as u8;
> +
> +/// Indicates the item should wrap around when reaching the extreme high or
> +/// extreme low values.
> +///
> +/// Refer to [Device Class Definition for HID 1.11]
> +/// Section 6.2.2.5 Input, Output, and Feature Items.
> +///
> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
> +pub const MAIN_ITEM_WRAP: u8 = bindings::HID_MAIN_ITEM_WRAP as u8;
> +
> +/// Indicates the item should wrap around when reaching the extreme high or
> +/// extreme low values.
> +///
> +/// Refer to [Device Class Definition for HID 1.11]
> +/// Section 6.2.2.5 Input, Output, and Feature Items.
> +///
> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
> +pub const MAIN_ITEM_NONLINEAR: u8 = bindings::HID_MAIN_ITEM_NONLINEAR as u8;
> +
> +/// Indicates whether the control has a preferred state it will physically
> +/// return to without user intervention.
> +///
> +/// Refer to [Device Class Definition for HID 1.11]
> +/// Section 6.2.2.5 Input, Output, and Feature Items.
> +///
> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
> +pub const MAIN_ITEM_NO_PREFERRED: u8 = bindings::HID_MAIN_ITEM_NO_PREFERRED as u8;
> +
> +/// Indicates whether the control has a physical state where it will not send
> +/// any reports.
> +///
> +/// Refer to [Device Class Definition for HID 1.11]
> +/// Section 6.2.2.5 Input, Output, and Feature Items.
> +///
> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
> +pub const MAIN_ITEM_NULL_STATE: u8 = bindings::HID_MAIN_ITEM_NULL_STATE as u8;
> +
> +/// Indicates whether the control requires host system logic to change state.
> +///
> +/// Refer to [Device Class Definition for HID 1.11]
> +/// Section 6.2.2.5 Input, Output, and Feature Items.
> +///
> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
> +pub const MAIN_ITEM_VOLATILE: u8 = bindings::HID_MAIN_ITEM_VOLATILE as u8;
> +
> +/// Indicates whether the item is fixed size or a variable buffer of bytes.
> +///
> +/// Refer to [Device Class Definition for HID 1.11]
> +/// Section 6.2.2.5 Input, Output, and Feature Items.
> +///
> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
> +pub const MAIN_ITEM_BUFFERED_BYTE: u8 = bindings::HID_MAIN_ITEM_BUFFERED_BYTE as u8;
> +
> +/// HID device groups are intended to help categories HID devices based on a set
> +/// of common quirks and logic that they will require to function correctly.
> +#[repr(u16)]
> +pub enum Group {
> +    /// Used to match a device against any group when probing.
> +    Any = bindings::HID_GROUP_ANY as u16,
> +
> +    /// Indicates a generic device that should need no custom logic from the
> +    /// core HID stack.
> +    Generic = bindings::HID_GROUP_GENERIC as u16,
> +
> +    /// Maps multitouch devices to hid-multitouch instead of hid-generic.
> +    Multitouch = bindings::HID_GROUP_MULTITOUCH as u16,
> +
> +    /// Used for autodetecing and mapping of HID sensor hubs to
> +    /// hid-sensor-hub.
> +    SensorHub = bindings::HID_GROUP_SENSOR_HUB as u16,
> +
> +    /// Used for autodetecing and mapping Win 8 multitouch devices to set the
> +    /// needed quirks.
> +    MultitouchWin8 = bindings::HID_GROUP_MULTITOUCH_WIN_8 as u16,
> +
> +    // Vendor-specific device groups.
> +    /// Used to distinguish Synpatics touchscreens from other products. The
> +    /// touchscreens will be handled by hid-multitouch instead, while everything
> +    /// else will be managed by hid-rmi.
> +    RMI = bindings::HID_GROUP_RMI as u16,
> +
> +    /// Used for hid-core handling to automatically identify Wacom devices and
> +    /// have them probed by hid-wacom.
> +    Wacom = bindings::HID_GROUP_WACOM as u16,
> +
> +    /// Used by logitech-djreceiver and logitech-djdevice to autodetect if
> +    /// devices paied to the DJ receivers are DJ devices and handle them with
> +    /// the device driver.
> +    LogitechDJDevice = bindings::HID_GROUP_LOGITECH_DJ_DEVICE as u16,
> +
> +    /// Since the Valve Steam Controller only has vendor-specific usages,
> +    /// prevent hid-generic from parsing its reports since there would be
> +    /// nothing hid-generic could do for the device.
> +    Steam = bindings::HID_GROUP_STEAM as u16,
> +
> +    /// Used to differentiate 27 Mhz frequency Logitech DJ devices from other
> +    /// Logitech DJ devices.
> +    Logitech27MHzDevice = bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE as u16,
> +
> +    /// Used for autodetecting and mapping Vivaldi devices to hid-vivaldi.
> +    Vivaldi = bindings::HID_GROUP_VIVALDI as u16,
> +}
> +
> +// TODO: use `const_trait_impl` once stabilized:
> +//
> +// ```
> +// impl const From<Group> for u16 {
> +//     /// [`Group`] variants are represented by [`u16`] values.
> +//     fn from(value: Group) -> Self {
> +//         value as Self
> +//     }
> +// }
> +// ```
> +impl Group {
> +    /// Internal function used to convert [`Group`] variants into [`u16`].
> +    const fn into_u16(self) -> u16 {

This and many functions in the abstraction can be `#[inline]`.

> +        self as u16
> +    }
> +}
> +
> +impl TryFrom<u16> for Group {
> +    type Error = &'static str;
> +
> +    /// [`u16`] values can be safely converted to [`Group`] variants.
> +    fn try_from(value: u16) -> Result<Group, Self::Error> {
> +        match value.into() {
> +            bindings::HID_GROUP_GENERIC => Ok(Group::Generic),
> +            bindings::HID_GROUP_MULTITOUCH => Ok(Group::Multitouch),
> +            bindings::HID_GROUP_SENSOR_HUB => Ok(Group::SensorHub),
> +            bindings::HID_GROUP_MULTITOUCH_WIN_8 => Ok(Group::MultitouchWin8),
> +            bindings::HID_GROUP_RMI => Ok(Group::RMI),
> +            bindings::HID_GROUP_WACOM => Ok(Group::Wacom),
> +            bindings::HID_GROUP_LOGITECH_DJ_DEVICE => Ok(Group::LogitechDJDevice),
> +            bindings::HID_GROUP_STEAM => Ok(Group::Steam),
> +            bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE => Ok(Group::Logitech27MHzDevice),
> +            bindings::HID_GROUP_VIVALDI => Ok(Group::Vivaldi),
> +            _ => Err("Unknown HID group encountered!"),

Please define a custom error type or just use a kernel error code.

> +        }
> +    }
> +}
> +
> +/// The HID device representation.
> +///
> +/// This structure represents the Rust abstraction for a C `struct hid_device`.
> +/// The implementation abstracts the usage of an already existing C `struct
> +/// hid_device` within Rust code that we get passed from the C side.
> +///
> +/// # Invariants
> +///
> +/// A [`Device`] instance represents a valid `struct hid_device` created by the
> +/// C portion of the kernel.
> +#[repr(transparent)]
> +pub struct Device<Ctx: device::DeviceContext = device::Normal>(
> +    Opaque<bindings::hid_device>,
> +    PhantomData<Ctx>,
> +);
> +
> +impl<Ctx: device::DeviceContext> Device<Ctx> {
> +    fn as_raw(&self) -> *mut bindings::hid_device {
> +        self.0.get()
> +    }
> +
> +    /// Returns the HID transport bus ID.
> +    pub fn bus(&self) -> u16 {
> +        // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device`
> +        unsafe { *self.as_raw() }.bus
> +    }
> +
> +    /// Returns the HID report group.
> +    pub fn group(&self) -> Result<Group, &'static str> {
> +        // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device`
> +        unsafe { *self.as_raw() }.group.try_into()
> +    }
> +
> +    /// Returns the HID vendor ID.
> +    pub fn vendor(&self) -> u32 {
> +        // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device`
> +        unsafe { *self.as_raw() }.vendor
> +    }
> +
> +    /// Returns the HID product ID.
> +    pub fn product(&self) -> u32 {
> +        // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device`
> +        unsafe { *self.as_raw() }.product
> +    }
> +}
> +
> +// SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic
> +// argument.
> +kernel::impl_device_context_deref!(unsafe { Device });
> +kernel::impl_device_context_into_aref!(Device);
> +
> +// SAFETY: Instances of `Device` are always reference-counted.
> +unsafe impl crate::types::AlwaysRefCounted for Device {
> +    fn inc_ref(&self) {
> +        // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero.
> +        unsafe { bindings::get_device(&raw mut (*self.as_raw()).dev) };
> +    }
> +
> +    unsafe fn dec_ref(obj: NonNull<Self>) {
> +        // SAFETY: The safety requirements guarantee that the refcount is non-zero.
> +        unsafe { bindings::put_device(&raw mut (*obj.cast::<bindings::hid_device>().as_ptr()).dev) }
> +    }
> +}
> +
> +impl<Ctx: device::DeviceContext> AsRef<device::Device<Ctx>> for Device<Ctx> {
> +    fn as_ref(&self) -> &device::Device<Ctx> {
> +        // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid
> +        // `struct hid_device`.
> +        let dev = unsafe { addr_of_mut!((*self.as_raw()).dev) };
> +
> +        // SAFETY: `dev` points to a valid `struct device`.
> +        unsafe { device::Device::from_raw(dev) }
> +    }
> +}
> +
> +/// Abstraction for the HID device ID structure `struct hid_device_id`.
> +#[repr(transparent)]
> +#[derive(Clone, Copy)]
> +pub struct DeviceId(bindings::hid_device_id);
> +
> +impl DeviceId {
> +    /// Equivalent to C's `HID_USB_DEVICE` macro.
> +    ///
> +    /// Create a new `hid::DeviceId` from a group, vendor ID, and device ID
> +    /// number.

The main description should be the first sentence, and C equivalent sentence
follows.

> +    pub const fn new_usb(group: Group, vendor: u32, product: u32) -> Self {
> +        Self(bindings::hid_device_id {
> +            bus: 0x3, // BUS_USB
> +            group: group.into_u16(),
> +            vendor,
> +            product,
> +            driver_data: 0,
> +        })
> +    }
> +
> +    /// Returns the HID transport bus ID.
> +    pub fn bus(&self) -> u16 {
> +        self.0.bus
> +    }
> +
> +    /// Returns the HID report group.
> +    pub fn group(&self) -> Result<Group, &'static str> {
> +        self.0.group.try_into()
> +    }
> +
> +    /// Returns the HID vendor ID.
> +    pub fn vendor(&self) -> u32 {
> +        self.0.vendor
> +    }
> +
> +    /// Returns the HID product ID.
> +    pub fn product(&self) -> u32 {
> +        self.0.product
> +    }
> +}
> +
> +// SAFETY:
> +// * `DeviceId` is a `#[repr(transparent)` wrapper of `hid_device_id` and does not add
> +//   additional invariants, so it's safe to transmute to `RawType`.
> +// * `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field.
> +unsafe impl RawDeviceId for DeviceId {
> +    type RawType = bindings::hid_device_id;
> +}
> +
> +// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field.
> +unsafe impl RawDeviceIdIndex for DeviceId {
> +    const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::hid_device_id, driver_data);
> +
> +    fn index(&self) -> usize {
> +        self.0.driver_data
> +    }
> +}
> +
> +/// [`IdTable`] type for HID.
> +pub type IdTable<T> = &'static dyn kernel::device_id::IdTable<DeviceId, T>;
> +
> +/// Create a HID [`IdTable`] with its alias for modpost.
> +#[macro_export]
> +macro_rules! hid_device_table {
> +    ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => {
> +        const $table_name: $crate::device_id::IdArray<
> +            $crate::hid::DeviceId,
> +            $id_info_type,
> +            { $table_data.len() },
> +        > = $crate::device_id::IdArray::new($table_data);
> +
> +        $crate::module_device_table!("hid", $module_table_name, $table_name);
> +    };
> +}
> +
> +/// The HID driver trait.
> +///
> +/// # Examples
> +///
> +/// ```
> +/// use kernel::{bindings, device, hid};
> +///
> +/// struct MyDriver;
> +///
> +/// kernel::hid_device_table!(
> +///     HID_TABLE,
> +///     MODULE_HID_TABLE,
> +///     <MyDriver as hid::Driver>::IdInfo,
> +///     [(
> +///         hid::DeviceId::new_usb(
> +///             hid::Group::Steam,
> +///             bindings::USB_VENDOR_ID_VALVE,
> +///             bindings::USB_DEVICE_ID_STEAM_DECK,
> +///         ),
> +///         (),
> +///     )]
> +/// );
> +///
> +/// #[vtable]
> +/// impl hid::Driver for MyDriver {
> +///     type IdInfo = ();
> +///     const ID_TABLE: hid::IdTable<Self::IdInfo> = &HID_TABLE;
> +///
> +///     /// This function is optional to implement.
> +///     fn report_fixup<'a, 'b: 'a>(_hdev: &hid::Device<device::Core>, rdesc: &'b mut [u8]) -> &'a [u8] {
> +///         // Perform some report descriptor fixup.
> +///         rdesc
> +///     }
> +/// }
> +/// ```
> +/// Drivers must implement this trait in order to get a HID driver registered.
> +/// Please refer to the `Adapter` documentation for an example.
> +#[vtable]
> +pub trait Driver: Send {
> +    /// The type holding information about each device id supported by the driver.
> +    // TODO: Use `associated_type_defaults` once stabilized:
> +    //
> +    // ```
> +    // type IdInfo: 'static = ();
> +    // ```
> +    type IdInfo: 'static;
> +
> +    /// The table of device ids supported by the driver.
> +    const ID_TABLE: IdTable<Self::IdInfo>;
> +
> +    /// Called before report descriptor parsing. Can be used to mutate the
> +    /// report descriptor before the core HID logic processes the descriptor.
> +    /// Useful for problematic report descriptors that prevent HID devices from
> +    /// functioning correctly.
> +    ///
> +    /// Optional to implement.
> +    fn report_fixup<'a, 'b: 'a>(_hdev: &Device<device::Core>, _rdesc: &'b mut [u8]) -> &'a [u8] {

I think this can just use a single lifetime?

> +        build_error!(VTABLE_DEFAULT_ERROR)
> +    }
> +}
> +
> +/// An adapter for the registration of HID drivers.
> +pub struct Adapter<T: Driver>(T);
> +
> +// SAFETY:
> +// - `bindings::hid_driver` is a C type declared as `repr(C)`.
> +// - `T` is the type of the driver's device private data.
> +// - `struct hid_driver` embeds a `struct device_driver`.
> +// - `DEVICE_DRIVER_OFFSET` is the correct byte offset to the embedded `struct device_driver`.
> +unsafe impl<T: Driver + 'static> driver::DriverLayout for Adapter<T> {
> +    type DriverType = bindings::hid_driver;
> +    type DriverData = T;
> +    const DEVICE_DRIVER_OFFSET: usize = core::mem::offset_of!(Self::DriverType, driver);
> +}
> +
> +// SAFETY: A call to `unregister` for a given instance of `DriverType` is guaranteed to be valid if
> +// a preceding call to `register` has been successful.
> +unsafe impl<T: Driver + 'static> driver::RegistrationOps for Adapter<T> {
> +    unsafe fn register(
> +        hdrv: &Opaque<Self::DriverType>,
> +        name: &'static CStr,
> +        module: &'static ThisModule,
> +    ) -> Result {
> +        // SAFETY: It's safe to set the fields of `struct hid_driver` on initialization.
> +        unsafe {
> +            (*hdrv.get()).name = name.as_char_ptr();
> +            (*hdrv.get()).id_table = T::ID_TABLE.as_ptr();
> +            (*hdrv.get()).report_fixup = if T::HAS_REPORT_FIXUP {
> +                Some(Self::report_fixup_callback)
> +            } else {
> +                None
> +            };
> +        }
> +
> +        // SAFETY: `hdrv` is guaranteed to be a valid `DriverType`
> +        to_result(unsafe {
> +            bindings::__hid_register_driver(hdrv.get(), module.0, name.as_char_ptr())
> +        })
> +    }
> +
> +    unsafe fn unregister(hdrv: &Opaque<Self::DriverType>) {
> +        // SAFETY: `hdrv` is guaranteed to be a valid `DriverType`
> +        unsafe { bindings::hid_unregister_driver(hdrv.get()) }
> +    }
> +}
> +
> +impl<T: Driver + 'static> Adapter<T> {
> +    extern "C" fn report_fixup_callback(
> +        hdev: *mut bindings::hid_device,
> +        buf: *mut u8,
> +        size: *mut kernel::ffi::c_uint,
> +    ) -> *const u8 {
> +        // SAFETY: The HID subsystem only ever calls the report_fixup callback
> +        // with a valid pointer to a `struct hid_device`.
> +        //
> +        // INVARIANT: `hdev` is valid for the duration of
> +        // `report_fixup_callback()`.
> +        let hdev = unsafe { &*hdev.cast::<Device<device::Core>>() };
> +
> +        // SAFETY: The HID subsystem only ever calls the report_fixup callback
> +        // with a valid pointer to a `kernel::ffi::c_uint`.
> +        //
> +        // INVARIANT: `size` is valid for the duration of
> +        // `report_fixup_callback()`.
> +        let buf_len: usize = match unsafe { *size }.try_into() {

This can just be a cast. In all kernel envs `usize` is at least as large as `u32`.

> +            Ok(len) => len,
> +            Err(e) => {
> +                dev_err!(
> +                    hdev.as_ref(),
> +                    "Cannot fix report description due to {:?}!\n",
> +                    e
> +                );
> +
> +                return buf;
> +            }
> +        };
> +
> +        // Build a mutable Rust slice from `buf` and `size`.
> +        //
> +        // SAFETY: The HID subsystem only ever calls the `report_fixup callback`
> +        // with a valid pointer to a `u8` buffer.
> +        //
> +        // INVARIANT: `buf` is valid for the duration of
> +        // `report_fixup_callback()`.
> +        let rdesc_slice = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) };
> +        let rdesc_slice = T::report_fixup(hdev, rdesc_slice);
> +
> +        match rdesc_slice.len().try_into() {

I'm somewhat inclined to just `BUG()` (i.e. `.expect()`) in this case. A driver
that hits this error condition would need to leak >= 4G of memory to satisfy the
lifetime bound.

> +            // SAFETY: The HID subsystem only ever calls the report_fixup
> +            // callback with a valid pointer to a `kernel::ffi::c_uint`.
> +            //
> +            // INVARIANT: `size` is valid for the duration of
> +            // `report_fixup_callback()`.
> +            Ok(len) => unsafe { *size = len },
> +            Err(e) => {
> +                dev_err!(
> +                    hdev.as_ref(),

as_ref() shouldn't be needed now.

Best,
Gary

> +                    "Fixed report description will not be used due to {:?}!\n",
> +                    e
> +                );
> +
> +                return buf;
> +            }
> +        }
> +
> +        rdesc_slice.as_ptr()
> +    }
> +}
> +
> +/// Declares a kernel module that exposes a single HID driver.
> +///
> +/// # Examples
> +///
> +/// ```ignore
> +/// kernel::module_hid_driver! {
> +///     type: MyDriver,
> +///     name: "Module name",
> +///     authors: ["Author name"],
> +///     description: "Description",
> +///     license: "GPL",
> +/// }
> +/// ```
> +#[macro_export]
> +macro_rules! module_hid_driver {
> +    ($($f:tt)*) => {
> +        $crate::module_driver!(<T>, $crate::hid::Adapter<T>, { $($f)* });
> +    };
> +}
> diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
> index 3da92f18f4ee..e2dcacd9369e 100644
> --- a/rust/kernel/lib.rs
> +++ b/rust/kernel/lib.rs
> @@ -102,6 +102,8 @@
>  pub mod id_pool;
>  #[doc(hidden)]
>  pub mod impl_flags;
> +#[cfg(CONFIG_RUST_HID_ABSTRACTIONS)]
> +pub mod hid;
>  pub mod init;
>  pub mod io;
>  pub mod ioctl;


^ permalink raw reply

* [PATCH 1/2] HID: core: Mitigate potential OOB by removing bogus memset()
From: Lee Jones @ 2026-02-27 16:30 UTC (permalink / raw)
  To: lee, Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel

The memset() in hid_report_raw_event() has the good intention of
clearing out bogus data by zeroing the area from the end of the incoming
data string to the assumed end of the buffer.  However, as we have
recently seen, the size of the report buffer isn't always correct which
can culminate in OOB writes.

The current suggestion from one of the HID maintainers is to remove the
attempt completely.  The subsequent handling should be able to simply
use the data size provided to prevent any potential overruns.

Suggested-by Benjamin Tissoires <bentiss@kernel.org>
Signed-off-by: Lee Jones <lee@kernel.org>
---
 drivers/hid/hid-core.c | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index a5b3a8ca2fcb..1d51042e4b1f 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2056,12 +2056,6 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *
 	else if (rsize > max_buffer_size)
 		rsize = max_buffer_size;
 
-	if (csize < rsize) {
-		dbg_hid("report %d is too short, (%d < %d)\n", report->id,
-				csize, rsize);
-		memset(cdata + csize, 0, rsize - csize);
-	}
-
 	if ((hid->claimed & HID_CLAIMED_HIDDEV) && hid->hiddev_report_event)
 		hid->hiddev_report_event(hid, report);
 	if (hid->claimed & HID_CLAIMED_HIDRAW) {
-- 
2.53.0.473.g4a7958ca14-goog


^ permalink raw reply related

* [PATCH 2/2] HID: multitouch: Check to ensure report responses match the request
From: Lee Jones @ 2026-02-27 16:30 UTC (permalink / raw)
  To: lee, Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel
In-Reply-To: <20260227163031.1166560-1-lee@kernel.org>

It is possible for a malicious (or clumsy) device to respond to a
specific report's feature request using a completely different report
ID.  This can cause confusion in the HID core resulting in nasty
side-effects such as OOB writes.

Add a check to ensure that the report ID in the response, matches the
one that was requested.  If it doesn't, omit reporting the raw event and
return early.

Signed-off-by: Lee Jones <lee@kernel.org>
---
 drivers/hid/hid-multitouch.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index b1c3ef129058..834c1a334887 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -499,12 +499,19 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report)
 		dev_warn(&hdev->dev, "failed to fetch feature %d\n",
 			 report->id);
 	} else {
+		/* The report ID in the request and the response should match */
+		if (report->id != buf[0]) {
+			hid_err(hdev, "Returned feature report did not match the request\n");
+			goto free;
+		}
+
 		ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, buf,
 					   size, 0);
 		if (ret)
 			dev_warn(&hdev->dev, "failed to report feature\n");
 	}
 
+free:
 	kfree(buf);
 }
 
-- 
2.53.0.473.g4a7958ca14-goog


^ permalink raw reply related

* [PATCH 0/3] HID: hid-lenovo-go-s: Multiple bug fixes
From: Ethan Tidmore @ 2026-02-27 20:54 UTC (permalink / raw)
  To: Derek J . Clark, Mark Pearson, Jiri Kosina
  Cc: Benjamin Tissoires, Mario Limonciello, linux-input, linux-kernel,
	Ethan Tidmore

Here are three simple bug fixes found with Smatch.

Ethan Tidmore (3):
  HID: hid-lenovo-go-s: Fix signedness bug
  HID: hid-lenovo-go-s: Remove impossible condition
  HID: hid-lenovo-go-s: Fix positive promotion bug

 drivers/hid/hid-lenovo-go-s.c | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

-- 
2.53.0


^ permalink raw reply

* [PATCH 1/3] HID: hid-lenovo-go-s: Fix signedness bug
From: Ethan Tidmore @ 2026-02-27 20:54 UTC (permalink / raw)
  To: Derek J . Clark, Mark Pearson, Jiri Kosina
  Cc: Benjamin Tissoires, Mario Limonciello, linux-input, linux-kernel,
	Ethan Tidmore
In-Reply-To: <20260227205444.1083103-1-ethantidmore06@gmail.com>

The function get_endpoint_address() returns type u8 but in its error
path returns -ENODEV. Also, every variable that is assigned from this
function is type int.

Change return type to int from u8.

Detected by Smatch:
drivers/hid/hid-lenovo-go-s.c:391 get_endpoint_address() warn:
signedness bug returning '(-19)'

Fixes: 4325fdab5dbbf ("HID: hid-lenovo-go-s: Add Lenovo Legion Go S Series HID Driver")
Signed-off-by: Ethan Tidmore <ethantidmore06@gmail.com>
---
 drivers/hid/hid-lenovo-go-s.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c
index cacc5bd5ed2b..0ef98ba68d86 100644
--- a/drivers/hid/hid-lenovo-go-s.c
+++ b/drivers/hid/hid-lenovo-go-s.c
@@ -377,7 +377,7 @@ static int hid_gos_set_event_return(struct command_report *cmd_rep)
 	return 0;
 }
 
-static u8 get_endpoint_address(struct hid_device *hdev)
+static int get_endpoint_address(struct hid_device *hdev)
 {
 	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
 	struct usb_host_endpoint *ep;
-- 
2.53.0


^ permalink raw reply related

* [PATCH 2/3] HID: hid-lenovo-go-s: Remove impossible condition
From: Ethan Tidmore @ 2026-02-27 20:54 UTC (permalink / raw)
  To: Derek J . Clark, Mark Pearson, Jiri Kosina
  Cc: Benjamin Tissoires, Mario Limonciello, linux-input, linux-kernel,
	Ethan Tidmore
In-Reply-To: <20260227205444.1083103-1-ethantidmore06@gmail.com>

The variable val is of type u8, so it can only be 0-255.

Remove this condition.

Detected by Smatch:
drivers/hid/hid-lenovo-go-s.c:508 gamepad_property_store() warn:
impossible condition '(val > 255) => (0-255 > 255)'

Signed-off-by: Ethan Tidmore <ethantidmore06@gmail.com>
---
 drivers/hid/hid-lenovo-go-s.c | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c
index 0ef98ba68d86..a24737170f83 100644
--- a/drivers/hid/hid-lenovo-go-s.c
+++ b/drivers/hid/hid-lenovo-go-s.c
@@ -504,9 +504,6 @@ static ssize_t gamepad_property_store(struct device *dev,
 		ret = kstrtou8(buf, 10, &val);
 		if (ret)
 			return ret;
-
-		if (val < 0 || val > 255)
-			return -EINVAL;
 		break;
 	case FEATURE_IMU_ENABLE:
 		ret = sysfs_match_string(feature_enabled_text, buf);
-- 
2.53.0


^ permalink raw reply related

* [PATCH 3/3] HID: hid-lenovo-go-s: Fix positive promotion bug
From: Ethan Tidmore @ 2026-02-27 20:54 UTC (permalink / raw)
  To: Derek J . Clark, Mark Pearson, Jiri Kosina
  Cc: Benjamin Tissoires, Mario Limonciello, linux-input, linux-kernel,
	Ethan Tidmore
In-Reply-To: <20260227205444.1083103-1-ethantidmore06@gmail.com>

The function mcu_property_out() returns type int and returns negative
error codes. The variable count is assigned from it and checked with
(count < 0) but this check would always be false because count can
never be less than zero as it is size_t.

Change count to ssize_t.

Detected by Smatch:
drivers/hid/hid-lenovo-go-s.c:583 gamepad_property_show() warn:
unsigned 'count' is never less than zero.

drivers/hid/hid-lenovo-go-s.c:583 gamepad_property_show() warn:
error code type promoted to positive: 'count'

Fixes: 14651777fd675 ("HID: hid-lenovo-go-s: Add Feature Status Attributes")
Signed-off-by: Ethan Tidmore <ethantidmore06@gmail.com>
---
 drivers/hid/hid-lenovo-go-s.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c
index a24737170f83..4596c18037a9 100644
--- a/drivers/hid/hid-lenovo-go-s.c
+++ b/drivers/hid/hid-lenovo-go-s.c
@@ -573,7 +573,7 @@ static ssize_t gamepad_property_show(struct device *dev,
 				     struct device_attribute *attr, char *buf,
 				     enum feature_status_index index)
 {
-	size_t count = 0;
+	ssize_t count = 0;
 	u8 i;
 
 	count = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, index, 0, 0);
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH v5 1/4] firmware_loader: expand firmware error codes with up-to-date error
From: Russ Weight @ 2026-02-27 22:05 UTC (permalink / raw)
  To: Marco Felsch
  Cc: Luis Chamberlain, Greg Kroah-Hartman, Rafael J. Wysocki,
	Andrew Morton, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Dmitry Torokhov, Kamel Bouhara, Marco Felsch, Henrik Rydberg,
	Danilo Krummrich, linux-kernel, devicetree, linux-input
In-Reply-To: <3ohould4vufzfqau4e7vg2ztks3gflmfosyaizggwzufbwvx2f@yqsgim7ash3x>

On Mon, Feb 23, 2026 at 11:39:02AM +0100, Marco Felsch wrote:
> Hi Russ,
> 
> On 26-02-19, Russ Weight wrote:
> > On Sun, Jan 11, 2026 at 04:05:44PM +0100, Marco Felsch wrote:
> > > Add FW_UPLOAD_ERR_DUPLICATE to allow drivers to inform the firmware_loader
> > > framework that the update is not required. This can be the case if the
> > > user provided firmware matches the current running firmware.
> > > 
> > > Sync lib/test_firmware.c accordingly.
> > > 
> > > Reviewed-by: Russ Weight <russ.weight@linux.dev>
> > > Reviewed-by: Luis Chamberlain <mcgrof@kernel.org>
> > > Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
> > > ---
> > >  drivers/base/firmware_loader/sysfs_upload.c | 1 +
> > >  include/linux/firmware.h                    | 2 ++
> > >  lib/test_firmware.c                         | 1 +
> > >  3 files changed, 4 insertions(+)
> > > 
> > > diff --git a/drivers/base/firmware_loader/sysfs_upload.c b/drivers/base/firmware_loader/sysfs_upload.c
> > > index c3797b93c5f5a2ecf2ae34707893c89eb7773154..9e93070b2c24179986b868a24b09cf051776c644 100644
> > > --- a/drivers/base/firmware_loader/sysfs_upload.c
> > > +++ b/drivers/base/firmware_loader/sysfs_upload.c
> > > @@ -28,6 +28,7 @@ static const char * const fw_upload_err_str[] = {
> > >  	[FW_UPLOAD_ERR_RW_ERROR]     = "read-write-error",
> > >  	[FW_UPLOAD_ERR_WEAROUT]	     = "flash-wearout",
> > >  	[FW_UPLOAD_ERR_FW_INVALID]   = "firmware-invalid",
> > > +	[FW_UPLOAD_ERR_DUPLICATE]    = "firmware-duplicate",
> > >  };
> > 
> > Hi Marco,
> > 
> > There is a corresponding change that should be made to
> > lib/test_firmware.c. You can look at the recent change for
> > FW_UPLOAD_ERR_FW_INVALID as an example.
> 
> Can you elaborate a bit more please? I've added the
> FW_UPLOAD_ERR_DUPLICATE to lib/test_firmware.c with this patchset and I
> don't know what you want me todo.

Hi Marco,

Please disregard. I didn't remember that test_firmware.c was updated
in the same patch as sysfs_upload.c. It looks good as is.

- Russ

> 
> Regards,
>   Marco
> 
> 
> > 
> > - Russ
> > 
> > >  
> > >  static const char *fw_upload_progress(struct device *dev,
> > > diff --git a/include/linux/firmware.h b/include/linux/firmware.h
> > > index aae1b85ffc10e20e9c3c9b6009d26b83efd8cb24..fe7797be4c08cd62cdad9617b8f70095d5e0af2f 100644
> > > --- a/include/linux/firmware.h
> > > +++ b/include/linux/firmware.h
> > > @@ -29,6 +29,7 @@ struct firmware {
> > >   * @FW_UPLOAD_ERR_RW_ERROR: read or write to HW failed, see kernel log
> > >   * @FW_UPLOAD_ERR_WEAROUT: FLASH device is approaching wear-out, wait & retry
> > >   * @FW_UPLOAD_ERR_FW_INVALID: invalid firmware file
> > > + * @FW_UPLOAD_ERR_DUPLICATE: firmware is already up to date (duplicate)
> > >   * @FW_UPLOAD_ERR_MAX: Maximum error code marker
> > >   */
> > >  enum fw_upload_err {
> > > @@ -41,6 +42,7 @@ enum fw_upload_err {
> > >  	FW_UPLOAD_ERR_RW_ERROR,
> > >  	FW_UPLOAD_ERR_WEAROUT,
> > >  	FW_UPLOAD_ERR_FW_INVALID,
> > > +	FW_UPLOAD_ERR_DUPLICATE,
> > >  	FW_UPLOAD_ERR_MAX
> > >  };
> > >  
> > > diff --git a/lib/test_firmware.c b/lib/test_firmware.c
> > > index be4f93124901e5faac41f48a66dabe6da56be0ca..952ec1cb03102911dbea9abd648ab9d9e0112a46 100644
> > > --- a/lib/test_firmware.c
> > > +++ b/lib/test_firmware.c
> > > @@ -1134,6 +1134,7 @@ static const char * const fw_upload_err_str[] = {
> > >  	[FW_UPLOAD_ERR_RW_ERROR]     = "read-write-error",
> > >  	[FW_UPLOAD_ERR_WEAROUT]	     = "flash-wearout",
> > >  	[FW_UPLOAD_ERR_FW_INVALID]   = "firmware-invalid",
> > > +	[FW_UPLOAD_ERR_DUPLICATE]    = "firmware-duplicate",
> > >  };
> > >  
> > >  static void upload_err_inject_error(struct test_firmware_upload *tst,
> > > 
> > > -- 
> > > 2.47.3
> > > 
> > 
> 
> -- 
> #gernperDu 
> #CallMeByMyFirstName
> 
> Pengutronix e.K.                           |                             |
> Steuerwalder Str. 21                       | https://www.pengutronix.de/ |
> 31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
> Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-9    |

^ permalink raw reply

* Re: [PATCH RESEND v2] HID: winwing: Enable rumble effects
From: Ivan Gorinov @ 2026-02-27 23:10 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: linux-input, linux-kernel
In-Reply-To: <s44p8pqp-6934-0r6q-n712-o5p5n2qs0429@xreary.bet>

On 2026-02-26 06:58, Jiri Kosina wrote:

> On Thu, 26 Feb 2026, Ivan Gorinov wrote:
> 
>> Enable rumble motor control on TGRIP-15E and TGRIP-15EX throttle grips
>> by sending haptic feedback commands (EV_FF events) to the input 
>> device.
>> 
>> Signed-off-by: Ivan Gorinov <linux-kernel@altimeter.info>
> [ ... snip ... ]
> 
>> +
>> +        buf[0] = 0x02;
>> +        buf[1] = 0x03;
>> +        buf[2] = 0xbf;
>> +        buf[3] = 0x00;
>> +        buf[4] = 0x00;
>> +        buf[5] = 0x03;
>> +        buf[6] = 0x49;
>> +        buf[7] = 0x00;
>> +        buf[8] = m;
>> +        buf[9] = 0x00;
>> +        buf[10] = 0;
>> +        buf[11] = 0;
>> +        buf[12] = 0;
>> +        buf[13] = 0;
> 
> Do these magic numbers have any real meaning, or is it just mimicking
> observed binary stream?
> It'd be nice to have at least short comment explaining it.

Mimicking USB requests captured by usbmon when the vendor's app is 
running in a Win10 VM (Qemu).
I will add some comments.

^ permalink raw reply

* [PATCH][next] HID: hid-lenovo-go-s: Fix spelling mistake "configuratiion" -> "configuration"
From: Colin Ian King @ 2026-02-27 23:16 UTC (permalink / raw)
  To: Derek J . Clark, Mark Pearson, Jiri Kosina, Benjamin Tissoires,
	linux-input
  Cc: kernel-janitors, linux-kernel

There is a spelling mistake in a dev_err_probe message. Fix it.

Signed-off-by: Colin Ian King <colin.i.king@gmail.com>
---
 drivers/hid/hid-lenovo-go-s.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c
index cacc5bd5ed2b..dbb88492fbba 100644
--- a/drivers/hid/hid-lenovo-go-s.c
+++ b/drivers/hid/hid-lenovo-go-s.c
@@ -1401,7 +1401,7 @@ static int hid_gos_cfg_probe(struct hid_device *hdev,
 	ret = devm_device_add_group(gos_cdev_rgb.led_cdev.dev, &rgb_attr_group);
 	if (ret) {
 		dev_err_probe(&hdev->dev, ret,
-			      "Failed to create RGB configuratiion attributes\n");
+			      "Failed to create RGB configuration attributes\n");
 		return ret;
 	}
 
-- 
2.51.0


^ permalink raw reply related

* [PATCH v3 00/18] HID: steelseries: Add support for Arctis headset lineup
From: Sriman Achanta @ 2026-02-27 23:50 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: linux-input, linux-kernel, Bastien Nocera, Simon Wood,
	Christian Mayer, Sriman Achanta

This patch series adds comprehensive support for the SteelSeries Arctis
wireless gaming headset lineup to the hid-steelseries driver.

The current driver provides only basic battery monitoring for Arctis 1
and Arctis 9. This series extends support to 25+ Arctis models with
full feature control including sidetone, auto-sleep, microphone
controls, volume limiting, and Bluetooth settings.

The driver restructure uses a capability-based device info system to
cleanly handle the varying feature sets across different Arctis
generations while maintaining support for the legacy SRW-S1 racing
wheel.

The driver also sets up future support for async device control which
is currently implemented for the Arctis Nova 7 Gen 2 and Post-January update
Gen 1 devices as implemented.

Tested on Arctis Nova 7 (0x2202) and Arctis Nova 7 (0x22a1). All other
implementation details are based on the reverse engineering done in the
HeadsetControl library (902e9bc).

Changes since v2:
* Expose audio related controls via ALSA mixers
* Implement async inputs from supported devices with known protocols
* Overall code cleanup and improvements to initalization logic
* Fixed several logical and protocol issues for Arctis 7 and 9

Sriman Achanta (18):
  HID: steelseries: Fix ARCTIS_1_X device mislabeling
  HID: hid-ids: Add SteelSeries Arctis headset device IDs
  HID: quirks: Add additional Arctis headset device IDs
  HID: steelseries: Add async support and unify device definitions
  HID: steelseries: Update Kconfig help text for expanded headset
    support
  HID: steelseries: Add ALSA sound card infrastructure
  HID: steelseries: Add ChatMix ALSA mixer controls
  HID: steelseries: Add mic mute ALSA mixer control
  HID: steelseries: Add Bluetooth state sysfs attributes
  HID: steelseries: Add settings poll infrastructure
  HID: steelseries: Add sidetone ALSA mixer control
  HID: steelseries: Add mic volume ALSA mixer control
  HID: steelseries: Add volume limiter ALSA mixer control
  HID: steelseries: Add Bluetooth call audio ducking control
  HID: steelseries: Add inactive time sysfs attribute
  HID: steelseries: Add Bluetooth auto-enable sysfs attribute
  HID: steelseries: Add mic mute LED brightness control
  HID: steelseries: Document sysfs ABI

 .../ABI/testing/sysfs-driver-hid-steelseries  |   87 +
 drivers/hid/Kconfig                           |    5 +-
 drivers/hid/hid-ids.h                         |   35 +-
 drivers/hid/hid-quirks.c                      |   27 +
 drivers/hid/hid-steelseries.c                 | 2329 ++++++++++++++---
 5 files changed, 2184 insertions(+), 299 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-steelseries

-- 
2.53.0


^ permalink raw reply

* [PATCH v3 01/18] HID: steelseries: Fix ARCTIS_1_X device mislabeling
From: Sriman Achanta @ 2026-02-27 23:50 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: linux-input, linux-kernel, Bastien Nocera, Simon Wood,
	Christian Mayer, Sriman Achanta
In-Reply-To: <20260227235042.410062-1-srimanachanta@gmail.com>

The SteelSeries Arctis 1 Wireless for Xbox (device ID 0x12b6) was
previously mislabeled as the regular Arctis 1 Wireless (device ID
0x12b3). This commit corrects the labeling by introducing a new
STEELSERIES_ARCTIS_1_X quirk flag and device table entry.

Both the Arctis 1 and Arctis 1 Wireless for Xbox share the same battery
reporting protocol, so they are handled identically in the battery fetch
and raw event processing functions.

Changes:
- Add STEELSERIES_ARCTIS_1_X quirk flag definition
- Shift STEELSERIES_ARCTIS_9 quirk flag bit accordingly
- Add device table entry for USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X
- Update steelseries_headset_fetch_battery() to handle both Arctis 1
  variants
- Update steelseries_headset_raw_event() to handle both Arctis 1
  variants
- Update device comment to clarify Arctis 1 vs Arctis 1 Wireless for
  Xbox

Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
 drivers/hid/hid-ids.h         |  5 +++--
 drivers/hid/hid-quirks.c      |  1 +
 drivers/hid/hid-steelseries.c | 19 +++++++++++++------
 3 files changed, 17 insertions(+), 8 deletions(-)

diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 3e299a30dcde..b01704f37142 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1329,8 +1329,9 @@
 
 #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	0x12b3
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X	0x12b6
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9	0x12c2
 
 #define USB_VENDOR_ID_SUN		0x0430
 #define USB_DEVICE_ID_RARITAN_KVM_DONGLE	0xcdab
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index edc4339adb50..17349eac5c3e 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -713,6 +713,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
 #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) },
 #endif
 #if IS_ENABLED(CONFIG_HID_SUNPLUS)
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f98435631aa1..d3711022bf86 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -19,7 +19,8 @@
 
 #define STEELSERIES_SRWS1		BIT(0)
 #define STEELSERIES_ARCTIS_1		BIT(1)
-#define STEELSERIES_ARCTIS_9		BIT(2)
+#define STEELSERIES_ARCTIS_1_X		BIT(2)
+#define STEELSERIES_ARCTIS_9		BIT(3)
 
 struct steelseries_device {
 	struct hid_device *hdev;
@@ -97,7 +98,7 @@ static const __u8 steelseries_srws1_rdesc_fixed[] = {
 0x29, 0x11,         /*          Usage Maximum (11h),        */
 0x95, 0x11,         /*          Report Count (17),          */
 0x81, 0x02,         /*          Input (Variable),           */
-                    /*   ---- Dial patch starts here ----   */
+		    /*   ---- Dial patch starts here ----   */
 0x05, 0x01,         /*          Usage Page (Desktop),       */
 0x09, 0x33,         /*          Usage (RX),                 */
 0x75, 0x04,         /*          Report Size (4),            */
@@ -110,7 +111,7 @@ static const __u8 steelseries_srws1_rdesc_fixed[] = {
 0x95, 0x01,         /*          Report Count (1),           */
 0x25, 0x03,         /*          Logical Maximum (3),        */
 0x81, 0x02,         /*          Input (Variable),           */
-                    /*    ---- Dial patch ends here ----    */
+		    /*    ---- Dial patch ends here ----    */
 0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),         */
 0x09, 0x01,         /*          Usage (01h),                */
 0x75, 0x04,         /* Changed  Report Size (4),            */
@@ -374,7 +375,8 @@ static void steelseries_headset_fetch_battery(struct hid_device *hdev)
 {
 	int ret = 0;
 
-	if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1)
+	if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+	    hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X)
 		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)
@@ -638,7 +640,8 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
 	if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
 		return 0;
 
-	if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
+	if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+	    hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) {
 		hid_dbg(sd->hdev,
 			"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
 		if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
@@ -724,10 +727,14 @@ 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 */
+	{ /* SteelSeries Arctis 1 Wireless */
 	  HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1),
 	  .driver_data = STEELSERIES_ARCTIS_1 },
 
+	{ /* SteelSeries Arctis 1 Wireless for XBox */
+	  HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X),
+	  .driver_data = STEELSERIES_ARCTIS_1_X },
+
 	{ /* SteelSeries Arctis 9 Wireless for XBox */
 	  HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
 	  .driver_data = STEELSERIES_ARCTIS_9 },
-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 02/18] HID: hid-ids: Add SteelSeries Arctis headset device IDs
From: Sriman Achanta @ 2026-02-27 23:50 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: linux-input, linux-kernel, Bastien Nocera, Simon Wood,
	Christian Mayer, Sriman Achanta
In-Reply-To: <20260227235042.410062-1-srimanachanta@gmail.com>

Add USB device IDs for the complete SteelSeries Arctis headset lineup,
including:
- Arctis 1, 1 Wireless, 7, 7P, 7X variants
- Arctis 7+ series (PS5, Xbox, Destiny editions)
- Arctis 9 Wireless
- Arctis Pro Wireless
- Arctis Nova 3, 3P, 3X
- Arctis Nova 5, 5X
- Arctis Nova 7 series (multiple variants and special editions)
- Arctis Nova Pro Wireless and Pro X

These IDs will be used by the updated hid-steelseries driver to provide
battery monitoring, sidetone control, and other device-specific features
for these wireless gaming headsets.

Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
 drivers/hid/hid-ids.h | 36 +++++++++++++++++++++++++++++++-----
 1 file changed, 31 insertions(+), 5 deletions(-)

diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index b01704f37142..e16b8dc5edd0 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1327,11 +1327,37 @@
 #define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS	0x1142
 #define USB_DEVICE_ID_STEAM_DECK	0x1205
 
-#define USB_VENDOR_ID_STEELSERIES	0x1038
-#define USB_DEVICE_ID_STEELSERIES_SRWS1	0x1410
-#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1	0x12b3
-#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X	0x12b6
-#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9	0x12c2
+#define USB_VENDOR_ID_STEELSERIES				0x1038
+#define USB_DEVICE_ID_STEELSERIES_SRWS1				0x1410
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1			0x12b3
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X			0x12b6
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7			0x1260
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_P			0x12d5
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_X			0x12d7
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_GEN2			0x12ad
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS			0x220e
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_P		0x2212
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_X		0x2216
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_DESTINY		0x2236
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9			0x12c2
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_P		0x2269
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X		0x226d
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5			0x2232
+#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_2		0x22a1
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P		0x220a
+#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_X_3		0x22a5
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO		0x223a
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2	0x22a9
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW		0x227a
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2		0x227e
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2		0x229e
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2	0x2258
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO		0x12e0
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO_X		0x12e5
 
 #define USB_VENDOR_ID_SUN		0x0430
 #define USB_DEVICE_ID_RARITAN_KVM_DONGLE	0xcdab
-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 03/18] HID: quirks: Add additional Arctis headset device IDs
From: Sriman Achanta @ 2026-02-27 23:50 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: linux-input, linux-kernel, Bastien Nocera, Simon Wood,
	Christian Mayer, Sriman Achanta
In-Reply-To: <20260227235042.410062-1-srimanachanta@gmail.com>

Add support for additional SteelSeries Arctis headset models to the HID
quirks table. This enables proper device recognition and handling for:
- Arctis 7 series (7, 7P, 7X, 7 Gen2)
- Arctis 7 Plus series (7 Plus, 7 Plus P, 7 Plus X, 7 Plus Destiny)
- Arctis Pro
- Arctis Nova 3 series (3, 3P, 3X)
- Arctis Nova 5 series (5, 5X)
- Arctis Nova 7 series (7, 7X, 7P, 7X Rev2, 7 Diablo, 7 WoW, 7 Gen2, 7X
  Gen2)
- Arctis Nova Pro series (Pro, Pro X)

Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
 drivers/hid/hid-quirks.c | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 17349eac5c3e..65a6f6ab30b9 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -714,7 +714,33 @@ static const struct hid_device_id hid_have_special_driver[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_P) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_X) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_GEN2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_P) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_X) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_DESTINY) },
 	{ 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_3_P) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5) },
+	{ 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_2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P) },
+	{ 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_X_3) },
+	{ 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_DIABLO_2) },
+	{ 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_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_PRO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO_X) },
 #endif
 #if IS_ENABLED(CONFIG_HID_SUNPLUS)
 	{ HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 04/18] HID: steelseries: Add async support and unify device definitions
From: Sriman Achanta @ 2026-02-27 23:50 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: linux-input, linux-kernel, Bastien Nocera, Simon Wood,
	Christian Mayer, Sriman Achanta
In-Reply-To: <20260227235042.410062-1-srimanachanta@gmail.com>

Refactor the SteelSeries driver to improve scalability and support the
modern Arctis Nova headset lineup along with legacy models.

- Replace the bitmap-based quirk system with `struct
  steelseries_device_info` to encapsulate device-specific traits
  (product ID, name, capabilities, interfaces).
- Implement asynchronous battery monitoring. Devices that support async
  updates (like the Nova series) now rely on interrupt events rather
  than periodic polling, reducing overhead.
- Add support for complex multi-interface devices (e.g., Nova 7) where
  battery events arrive on a separate asynchronous interface.
- Consolidate battery request and report parsing logic. New helpers
  `steelseries_send_feature_report` and `steelseries_send_output_report`
  simplify command dispatch.
- Add support for over 20 new devices including the entire Arctis Nova
  series (3, 5, 7, Pro) and various Arctis 7/9/Pro variants.
- Clean up naming conventions (e.g., removing `_headset_` prefix from
  general functions) and improve locking in the battery timer.

Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
 drivers/hid/hid-steelseries.c | 894 +++++++++++++++++++++++++---------
 1 file changed, 653 insertions(+), 241 deletions(-)

diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index d3711022bf86..d8ece8449255 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -4,37 +4,54 @@
  *
  *  Copyright (c) 2013 Simon Wood
  *  Copyright (c) 2023 Bastien Nocera
+ *  Copyright (c) 2025 Sriman Achanta
  */
 
-/*
- */
-
+#include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/hid.h>
 #include <linux/module.h>
 #include <linux/usb.h>
 #include <linux/leds.h>
+#include <linux/power_supply.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
 
 #include "hid-ids.h"
 
-#define STEELSERIES_SRWS1		BIT(0)
-#define STEELSERIES_ARCTIS_1		BIT(1)
-#define STEELSERIES_ARCTIS_1_X		BIT(2)
-#define STEELSERIES_ARCTIS_9		BIT(3)
+#define SS_CAP_BATTERY			BIT(0)
+
+#define SS_QUIRK_STATUS_SYNC_POLL	BIT(0)
+
+struct steelseries_device;
+
+struct steelseries_device_info {
+	unsigned long capabilities;
+	unsigned long quirks;
+
+	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 hid_device *hdev;
-	unsigned long quirks;
+	const struct steelseries_device_info *info;
 
-	struct delayed_work battery_work;
-	spinlock_t lock;
-	bool removed;
+	bool use_async_protocol;
+
+	struct delayed_work status_work;
 
 	struct power_supply_desc battery_desc;
 	struct power_supply *battery;
-	uint8_t battery_capacity;
 	bool headset_connected;
+	u8 battery_capacity;
 	bool battery_charging;
+
+	spinlock_t lock;
+	bool removed;
 };
 
 #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -341,53 +358,118 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
 }
 #endif
 
-#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS	3000
+static const __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev,
+		__u8 *rdesc, unsigned int *rsize)
+{
+	if (hdev->vendor != USB_VENDOR_ID_STEELSERIES ||
+	    hdev->product != USB_DEVICE_ID_STEELSERIES_SRWS1)
+		return rdesc;
 
-#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 };
+	if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8
+			&& rdesc[29] == 0xbb && rdesc[40] == 0xc5) {
+		hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n");
+		*rsize = sizeof(steelseries_srws1_rdesc_fixed);
+		return steelseries_srws1_rdesc_fixed;
+	}
+	return rdesc;
+}
 
-static int steelseries_headset_request_battery(struct hid_device *hdev,
-	const char *request, size_t len)
+/*
+ * Headset report helpers
+ */
+
+static int steelseries_send_report(struct hid_device *hdev, const u8 *data,
+				    int len, enum hid_report_type type)
 {
-	u8 *write_buf;
+	u8 *buf;
 	int ret;
 
-	/* Request battery information */
-	write_buf = kmemdup(request, len, GFP_KERNEL);
-	if (!write_buf)
+	buf = kmemdup(data, len, GFP_KERNEL);
+	if (!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;
-	}
+	ret = hid_hw_raw_request(hdev, data[0], buf, len, type,
+				 HID_REQ_SET_REPORT);
+	kfree(buf);
 
-	kfree(write_buf);
-	return ret;
+	if (ret < 0)
+		return ret;
+	if (ret < len)
+		return -EIO;
+
+	return 0;
 }
 
-static void steelseries_headset_fetch_battery(struct hid_device *hdev)
+static inline int steelseries_send_feature_report(struct hid_device *hdev,
+						   const u8 *data, int len)
 {
-	int ret = 0;
+	return steelseries_send_report(hdev, data, len, HID_FEATURE_REPORT);
+}
 
-	if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
-	    hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X)
-		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));
+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);
+}
 
-	if (ret < 0)
-		hid_dbg(hdev,
-			"Battery query failed (err: %d)\n", ret);
+/*
+ * Headset status request functions
+ */
+
+static int steelseries_arctis_1_request_status(struct hid_device *hdev)
+{
+	const u8 data[] = { 0x06, 0x12 };
+
+	return steelseries_send_feature_report(hdev, data, sizeof(data));
+}
+
+static int steelseries_arctis_7_request_status(struct hid_device *hdev)
+{
+	int ret;
+	const u8 connection_data[] = { 0x06, 0x14 };
+	const u8 battery_data[] = { 0x06, 0x18 };
+
+	ret = steelseries_send_feature_report(hdev, connection_data, sizeof(connection_data));
+	if (ret)
+		return ret;
+
+	msleep(10);
+
+	return steelseries_send_feature_report(hdev, battery_data, sizeof(battery_data));
+}
+
+static int steelseries_arctis_9_request_status(struct hid_device *hdev)
+{
+	const u8 data[] = { 0x00, 0x20 };
+
+	return steelseries_send_feature_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));
+}
+
+static int steelseries_arctis_nova_3p_request_status(struct hid_device *hdev)
+{
+	const u8 data[] = { 0xb0 };
+
+	return steelseries_send_output_report(hdev, data, sizeof(data));
+}
+
+static int steelseries_arctis_nova_pro_request_status(struct hid_device *hdev)
+{
+	const u8 data[] = { 0x06, 0xb0 };
+
+	return steelseries_send_output_report(hdev, data, sizeof(data));
+}
+
+/*
+ * Headset battery helpers
+ */
+
 static int battery_capacity_to_level(int capacity)
 {
 	if (capacity >= 50)
@@ -397,19 +479,247 @@ static int battery_capacity_to_level(int capacity)
 	return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
 }
 
-static void steelseries_headset_battery_timer_tick(struct work_struct *work)
+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)
+{
+	if (size < 4)
+		return;
+
+	sd->headset_connected = (data[2] != 0x01);
+	sd->battery_capacity = data[3];
+}
+
+static void steelseries_arctis_7_parse_status(struct steelseries_device *sd,
+					      u8 *data, int size)
+{
+	if (size < 3)
+		return;
+
+	if (data[0] == 0x06) {
+		if (data[1] == 0x14)
+			sd->headset_connected = (data[2] == 0x03);
+		else if (data[1] == 0x18)
+			sd->battery_capacity = data[2];
+	}
+}
+
+static void steelseries_arctis_7_plus_parse_status(struct steelseries_device *sd,
+						   u8 *data, int size)
+{
+	if (size < 4)
+		return;
+
+	if (data[0] == 0xb0) {
+		sd->headset_connected = !(data[1] == 0x01);
+		sd->battery_capacity = steelseries_map_capacity(data[2], 0x00, 0x04);
+		sd->battery_charging = (data[3] == 0x01);
+	}
+}
+
+static void steelseries_arctis_9_parse_status(struct steelseries_device *sd,
+					      u8 *data, int size)
+{
+	if (size < 5)
+		return;
+
+	if (data[0] == 0xaa) {
+		sd->headset_connected = (data[1] == 0x01);
+		sd->battery_charging = (data[4] == 0x01);
+		sd->battery_capacity = steelseries_map_capacity(data[3], 0x64, 0x9A);
+	}
+}
+
+static void steelseries_arctis_nova_3p_parse_status(struct steelseries_device *sd,
+						   u8 *data, int size)
+{
+	if (size < 4)
+		return;
+
+	if (data[0] == 0xb0) {
+		sd->headset_connected = !(data[1] == 0x02);
+		sd->battery_capacity = steelseries_map_capacity(data[3], 0x00, 0x64);
+	}
+}
+
+static void steelseries_arctis_nova_5_parse_status(struct steelseries_device *sd,
+						   u8 *data, int size)
+{
+	if (size < 5)
+		return;
+
+	if (data[0] == 0xb0) {
+		sd->headset_connected = !(data[1] == 0x02);
+		sd->battery_capacity = data[3];
+		sd->battery_charging = (data[4] == 0x01);
+	}
+}
+
+static void steelseries_arctis_nova_7_parse_status(struct steelseries_device *sd,
+						   u8 *data, int size)
+{
+	if (size < 4)
+		return;
+
+	if (data[0] == 0xb0) {
+		sd->headset_connected = (data[1] == 0x03);
+		sd->battery_capacity = steelseries_map_capacity(data[2], 0x00, 0x04);
+		sd->battery_charging = (data[3] == 0x01);
+	}
+}
+
+static void steelseries_arctis_nova_7_gen2_parse_status(struct steelseries_device *sd,
+							u8 *data, int size)
+{
+	if (size < 4)
+		return;
+
+	switch (data[0]) {
+	case 0xb0:
+		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_pro_parse_status(struct steelseries_device *sd,
+						     u8 *data, int size)
+{
+	if (size < 16)
+		return;
+
+	if (data[0] == 0x06 && data[1] == 0xb0) {
+		sd->headset_connected = (data[15] == 0x08 || data[15] == 0x02);
+		sd->battery_capacity = steelseries_map_capacity(data[6], 0x00, 0x08);
+		sd->battery_charging = (data[15] == 0x02);
+	}
+}
+
+/*
+ * Device info definitions
+ */
+
+static const struct steelseries_device_info srws1_info = { };
+
+static const struct steelseries_device_info arctis_1_info = {
+	.sync_interface = 3,
+	.capabilities = SS_CAP_BATTERY,
+	.quirks = SS_QUIRK_STATUS_SYNC_POLL,
+	.request_status = steelseries_arctis_1_request_status,
+	.parse_status = steelseries_arctis_1_parse_status,
+};
+
+static const struct steelseries_device_info arctis_7_info = {
+	.sync_interface = 5,
+	.capabilities = SS_CAP_BATTERY,
+	.quirks = SS_QUIRK_STATUS_SYNC_POLL,
+	.request_status = steelseries_arctis_7_request_status,
+	.parse_status = steelseries_arctis_7_parse_status,
+};
+
+static const struct steelseries_device_info arctis_7_plus_info = {
+	.sync_interface = 3,
+	.capabilities = SS_CAP_BATTERY,
+	.quirks = SS_QUIRK_STATUS_SYNC_POLL,
+	.request_status = steelseries_arctis_nova_request_status,
+	.parse_status = steelseries_arctis_7_plus_parse_status,
+};
+
+static const struct steelseries_device_info arctis_9_info = {
+	.sync_interface = 0,
+	.capabilities = SS_CAP_BATTERY,
+	.quirks = SS_QUIRK_STATUS_SYNC_POLL,
+	.request_status = steelseries_arctis_9_request_status,
+	.parse_status = steelseries_arctis_9_parse_status,
+};
+
+static const struct steelseries_device_info arctis_nova_3p_info = {
+	.sync_interface = 4,
+	.capabilities = SS_CAP_BATTERY,
+	.quirks = SS_QUIRK_STATUS_SYNC_POLL,
+	.request_status = steelseries_arctis_nova_3p_request_status,
+	.parse_status = steelseries_arctis_nova_3p_parse_status,
+};
+
+static const struct steelseries_device_info arctis_nova_5_info = {
+	.sync_interface = 3,
+	.capabilities = SS_CAP_BATTERY,
+	.quirks = SS_QUIRK_STATUS_SYNC_POLL,
+	.request_status = steelseries_arctis_nova_request_status,
+	.parse_status = steelseries_arctis_nova_5_parse_status,
+};
+
+static const struct steelseries_device_info arctis_nova_7_info = {
+	.sync_interface = 3,
+	.capabilities = SS_CAP_BATTERY,
+	.quirks = SS_QUIRK_STATUS_SYNC_POLL,
+	.request_status = steelseries_arctis_nova_request_status,
+	.parse_status = steelseries_arctis_nova_7_parse_status,
+};
+
+static const struct steelseries_device_info arctis_nova_7_gen2_info = {
+	.sync_interface = 3,
+	.async_interface = 5,
+	.capabilities = SS_CAP_BATTERY,
+	.request_status = steelseries_arctis_nova_request_status,
+	.parse_status = steelseries_arctis_nova_7_gen2_parse_status,
+};
+
+static const struct steelseries_device_info arctis_nova_pro_info = {
+	.sync_interface = 4,
+	.capabilities = SS_CAP_BATTERY,
+	.quirks = SS_QUIRK_STATUS_SYNC_POLL,
+	.request_status = steelseries_arctis_nova_pro_request_status,
+	.parse_status = steelseries_arctis_nova_pro_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 steelseries_device *sd = container_of(work,
-		struct steelseries_device, battery_work.work);
-	struct hid_device *hdev = sd->hdev;
+	struct usb_interface *intf;
+
+	if (!hid_is_usb(hdev))
+		return;
 
-	steelseries_headset_fetch_battery(hdev);
+	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 "
 #define STEELSERIES_PREFIX_LEN strlen(STEELSERIES_PREFIX)
 
-static int steelseries_headset_battery_get_property(struct power_supply *psy,
+static int steelseries_battery_get_property(struct power_supply *psy,
 				enum power_supply_property psp,
 				union power_supply_propval *val)
 {
@@ -452,22 +762,7 @@ static int steelseries_headset_battery_get_property(struct power_supply *psy,
 	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[] = {
+static enum power_supply_property steelseries_battery_props[] = {
 	POWER_SUPPLY_PROP_MODEL_NAME,
 	POWER_SUPPLY_PROP_MANUFACTURER,
 	POWER_SUPPLY_PROP_PRESENT,
@@ -477,7 +772,26 @@ static enum power_supply_property steelseries_headset_battery_props[] = {
 	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
 };
 
-static int steelseries_headset_battery_register(struct steelseries_device *sd)
+/*
+ * Delayed work handlers for status polling and settings requests
+ */
+
+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);
+	if (!sd->removed && !sd->use_async_protocol)
+		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)
 {
 	static atomic_t battery_no = ATOMIC_INIT(0);
 	struct power_supply_config battery_cfg = { .drv_data = sd, };
@@ -485,9 +799,9 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
 	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.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;
 	n = atomic_inc_return(&battery_no) - 1;
 	sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
@@ -496,14 +810,16 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
 		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->headset_connected = false;
+	steelseries_headset_set_wireless_status(sd->hdev, 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);
+		sd->battery = NULL;
 		hid_err(sd->hdev,
 				"%s:power_supply_register failed with error %d\n",
 				__func__, ret);
@@ -511,68 +827,185 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
 	}
 	power_supply_powers(sd->battery, &sd->hdev->dev);
 
-	INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick);
-	steelseries_headset_fetch_battery(sd->hdev);
+	return 0;
+}
+
+static int steelseries_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 = false;
+
+	if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+		return 0;
+
+	if (!sd)
+		return 0;
+
+	old_capacity = sd->battery_capacity;
+	old_connected = sd->headset_connected;
+	old_charging = sd->battery_charging;
+
+	if (hid_is_usb(hdev)) {
+		struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+
+		is_async_interface = (intf->cur_altsetting->desc.bInterfaceNumber ==
+				      sd->info->async_interface);
+	}
+
+	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->use_async_protocol && is_async_interface) {
+			schedule_delayed_work(&sd->status_work, 0);
+		}
 
-	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));
+		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);
 	}
 
 	return 0;
 }
 
-static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t usage_page)
+static struct hid_device *steelseries_get_sibling_hdev(struct hid_device *hdev,
+						       int interface_num)
 {
-	return hdev->rdesc[0] == 0x06 &&
-		hdev->rdesc[1] == usage_page &&
-		hdev->rdesc[2] == 0xff;
+	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;
+
+	sibling_intf = usb_ifnum_to_if(usb_dev, interface_num);
+	if (!sibling_intf)
+		return NULL;
+
+	sibling_hdev = usb_get_intfdata(sibling_intf);
+
+	return sibling_hdev;
 }
 
-static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
+static int steelseries_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;
+	struct hid_device *master_hdev;
+	u8 interface_num;
 	int ret;
 
 	if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
 #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
-    (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
+	(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
 		return steelseries_srws1_probe(hdev, id);
 #else
 		return -ENODEV;
 #endif
 	}
 
-	sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
-	if (!sd)
-		return -ENOMEM;
-	hid_set_drvdata(hdev, sd);
-	sd->hdev = hdev;
-	sd->quirks = id->driver_data;
+	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;
 
-	if (sd->quirks & STEELSERIES_ARCTIS_9 &&
-			!steelseries_is_vendor_usage_page(hdev, 0xc0))
-		return -ENODEV;
+	/* 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);
 
-	spin_lock_init(&sd->lock);
+	if (interface_num == info->sync_interface) {
+		sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
+		if (!sd)
+			return -ENOMEM;
 
-	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
-	if (ret)
-		return ret;
+		sd->hdev = hdev;
+		sd->info = info;
+		spin_lock_init(&sd->lock);
 
-	ret = hid_hw_open(hdev);
-	if (ret)
-		return ret;
+		hid_set_drvdata(hdev, sd);
 
-	if (steelseries_headset_battery_register(sd) < 0)
-		hid_err(sd->hdev,
-			"Failed to register battery for headset\n");
+		ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+		if (ret)
+			return ret;
+
+		ret = hid_hw_open(hdev);
+		if (ret)
+			goto err_stop;
+
+		sd->use_async_protocol = !(info->quirks & SS_QUIRK_STATUS_SYNC_POLL);
+
+		if (info->capabilities & SS_CAP_BATTERY) {
+			ret = steelseries_battery_register(sd);
+			if (ret < 0)
+				hid_warn(hdev, "Failed to register battery: %d\n", ret);
+		}
+
+		INIT_DELAYED_WORK(&sd->status_work, steelseries_status_timer_work_handler);
+		schedule_delayed_work(&sd->status_work, msecs_to_jiffies(100));
+
+		return 0;
+	}
+
+	if (info->async_interface && interface_num == info->async_interface) {
+		master_hdev = steelseries_get_sibling_hdev(hdev, info->sync_interface);
 
+		if (!master_hdev || !hid_get_drvdata(master_hdev))
+			return -EPROBE_DEFER;
+
+		sd = hid_get_drvdata(master_hdev);
+		hid_set_drvdata(hdev, sd);
+
+		ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+		if (ret)
+			return ret;
+
+		ret = hid_hw_open(hdev);
+		if (ret) {
+			hid_hw_stop(hdev);
+			return ret;
+		}
+		return 0;
+	}
+
+	return -ENODEV;
+
+err_stop:
+	hid_hw_stop(hdev);
 	return ret;
 }
 
@@ -580,166 +1013,144 @@ static void steelseries_remove(struct hid_device *hdev)
 {
 	struct steelseries_device *sd;
 	unsigned long flags;
+	struct usb_interface *intf;
+	u8 interface_num;
 
 	if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
 #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
-    (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
+	(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
 		hid_hw_stop(hdev);
 #endif
 		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);
-}
-
-static const __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev,
-		__u8 *rdesc, unsigned int *rsize)
-{
-	if (hdev->vendor != USB_VENDOR_ID_STEELSERIES ||
-	    hdev->product != USB_DEVICE_ID_STEELSERIES_SRWS1)
-		return rdesc;
-
-	if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8
-			&& rdesc[29] == 0xbb && rdesc[40] == 0xc5) {
-		hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n");
-		*rsize = sizeof(steelseries_srws1_rdesc_fixed);
-		return steelseries_srws1_rdesc_fixed;
+	if (hid_is_usb(hdev)) {
+		intf = to_usb_interface(hdev->dev.parent);
+		interface_num = intf->cur_altsetting->desc.bInterfaceNumber;
+	} else {
+		return;
 	}
-	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;
+	sd = hid_get_drvdata(hdev);
 
-	if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
-	    hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) {
-		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 (!sd) {
+		hid_hw_stop(hdev);
+		return;
 	}
 
-	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 (interface_num == sd->info->sync_interface) {
+		if (sd->info->async_interface) {
+			struct hid_device *sibling;
 
-		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;
+			sibling = steelseries_get_sibling_hdev(hdev,
+							       sd->info->async_interface);
+			if (sibling)
+				hid_set_drvdata(sibling, NULL);
 		}
-	}
 
-	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);
-	}
+		spin_lock_irqsave(&sd->lock, flags);
+		sd->removed = true;
+		spin_unlock_irqrestore(&sd->lock, flags);
 
-	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);
+		cancel_delayed_work_sync(&sd->status_work);
 	}
 
-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;
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
 }
 
 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 */
-	  HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1),
-	  .driver_data = STEELSERIES_ARCTIS_1 },
-
-	{ /* SteelSeries Arctis 1 Wireless for XBox */
-	  HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X),
-	  .driver_data = STEELSERIES_ARCTIS_1_X },
-
-	{ /* SteelSeries Arctis 9 Wireless for XBox */
-	  HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
-	  .driver_data = STEELSERIES_ARCTIS_9 },
-
-	{ }
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_SRWS1),
+	  .driver_data = (unsigned long)&srws1_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_1),
+	  .driver_data = (unsigned long)&arctis_1_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X),
+	  .driver_data = (unsigned long)&arctis_1_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_7),
+	  .driver_data = (unsigned long)&arctis_7_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_7_P),
+	  .driver_data = (unsigned long)&arctis_1_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_7_X),
+	  .driver_data = (unsigned long)&arctis_1_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_7_GEN2),
+	  .driver_data = (unsigned long)&arctis_7_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS),
+	  .driver_data = (unsigned long)&arctis_7_plus_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_P),
+	  .driver_data = (unsigned long)&arctis_7_plus_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_X),
+	  .driver_data = (unsigned long)&arctis_7_plus_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_DESTINY),
+	  .driver_data = (unsigned long)&arctis_7_plus_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_3_P),
+	  .driver_data = (unsigned long)&arctis_nova_3p_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X),
+	  .driver_data = (unsigned long)&arctis_nova_3p_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5),
+	  .driver_data = (unsigned long)&arctis_nova_5_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X),
+	  .driver_data = (unsigned long)&arctis_nova_5_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_2),
+	  .driver_data = (unsigned long)&arctis_nova_7_gen2_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P),
+	  .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_X_3),
+	  .driver_data = (unsigned long)&arctis_nova_7_gen2_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_DIABLO_2),
+	  .driver_data = (unsigned long)&arctis_nova_7_gen2_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_GEN2),
+	  .driver_data = (unsigned long)&arctis_nova_7_gen2_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2),
+	  .driver_data = (unsigned long)&arctis_nova_7_gen2_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2),
+	  .driver_data = (unsigned long)&arctis_nova_7_gen2_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO),
+	  .driver_data = (unsigned long)&arctis_nova_pro_info },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO_X),
+	  .driver_data = (unsigned long)&arctis_nova_pro_info },
+	{}
 };
 MODULE_DEVICE_TABLE(hid, steelseries_devices);
 
@@ -749,7 +1160,7 @@ static struct hid_driver steelseries_driver = {
 	.probe = steelseries_probe,
 	.remove = steelseries_remove,
 	.report_fixup = steelseries_srws1_report_fixup,
-	.raw_event = steelseries_headset_raw_event,
+	.raw_event = steelseries_raw_event,
 };
 
 module_hid_driver(steelseries_driver);
@@ -758,3 +1169,4 @@ 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>");
+MODULE_AUTHOR("Sriman Achanta <srimanachanta@gmail.com>");
-- 
2.53.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox