mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups
@ 2026-11-09  6:10 Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 01/18] HID: logitech-hidpp: fix G920 force-feedback initialization timeout Dmitry Torokhov
                   ` (17 more replies)
  0 siblings, 18 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel

This series starts from Gideon Heinz's report of Logitech G920
force-feedback initialization timing out in v7.3 and addresses several
additional pre-existing bugs, lifetime races, and cleanup opportunities
found while auditing hid-logitech-hidpp.

The first seven patches carry Fixes and stable tags. They fix the v7.3
G920 force-feedback initialization timeout by temporarily enabling
device I/O around g920_get_config() in hidpp_input_configured(), switch
hidpp_ff_queue_work() to GFP_ATOMIC since playback, gain, and autocenter
callbacks run under the input event spinlock, drain the G920 FF
workqueue before closing the HID transport on input close so queued
commands still receive their USB interrupt IN replies, synchronously
disable driver work items before stopping the transport on removal and
probe failure, avoid storing an ERR_PTR in battery->ps on registration
failure, avoid NULL pointer subtraction in the HID++ 1.0 consumer keys
report fixup, and fix an off-by-one in G920 slot tracking that made the
63rd user effect in hardware slot 64 unusable and leaked it across
reopen.

The remaining patches rework G920 force-feedback initialization for next
and clean up surrounding driver state management. G920 capability
discovery moves back into hidpp_probe() while device I/O is already
active (keeping force feedback optional on failure and excluding generic
USB PID force feedback), device-level wheel settings are separated from
the input FF queue lifetime, the G920 range sysfs attribute moves to
driver dev_groups, and FF workqueue teardown moves into ff->stop so open
evdev handles across USB disconnect or driver unbind no longer cause
use-after-free. Follow-up cleanups switch multi-byte handling to
unaligned accessors, store device serials as byte arrays, and adopt
guard(mutex), __free(kfree), alloc_ordered_workqueue(),
devm_mutex_init(), and devm_device_add_group().

Please note that not everything in the driver is fixed by this series,
especially around stalls on close and teardown. Draining the FF
workqueue before hid_hw_close() keeps the USB interrupt IN endpoint
alive during a normal last-user close, but close still blocks flushing
asynchronous FF commands, and paths where device or transport I/O is
already stopped (such as driver unbind or module unload while effects
are loaded, inhibited input devices, or writing the sysfs range
attribute while the input device is closed) can still stall for 3 x 5
seconds per command because hidpp_send_message_sync() retries non-busy
timeouts and transport errors as if the device reported busy. Fixing the
synchronous send retry logic, converting sleepable FF operations
(upload, erase, and range) to synchronous execution with proper
transport lifetime handling, and fixing a few remaining battery
reporting bugs will come in a follow-up series.

Gideon, could you please give this full series a spin on your G920
(checking initialization, force feedback in fftest including exiting
back to the shell, range sysfs access, and general wheel/pedal input)
and, if anything breaks, ideally bisect within the series to pinpoint
the offending patch?

Thanks.

Dmitry Torokhov (18):
  HID: logitech-hidpp: fix G920 force-feedback initialization timeout
  HID: logitech-hidpp: use GFP_ATOMIC in hidpp_ff_queue_work()
  HID: logitech-hidpp: drain G920 FF workqueue before closing transport
  HID: logitech-hidpp: disable works before stopping transport
  HID: logitech-hidpp: do not store ERR_PTR in battery->ps
  HID: logitech-hidpp: avoid NULL subtraction in consumer key report
    fixup
  HID: logitech-hidpp: fix off-by-one in G920 force-feedback slot
    tracking
  HID: logitech-hidpp: rework G920 force-feedback initialization
  HID: logitech-hidpp: use sysfs_emit() in hidpp_ff_range_show()
  HID: logitech-hidpp: manage G920 range sysfs attribute via dev_groups
  HID: logitech-hidpp: shut down G920 force feedback on input unregister
  HID: logitech-hidpp: use unaligned accessors for multi-byte values
  HID: logitech-hidpp: store device serial as a byte array
  HID: logitech-hidpp: use guard() for send_mutex
  HID: logitech-hidpp: use __free(kfree) for temporary allocations
  HID: logitech-hidpp: use alloc_ordered_workqueue() for FF queue
  HID: logitech-hidpp: manage send_mutex with devm_mutex_init()
  HID: logitech-hidpp: manage ps_attribute_group with
    devm_device_add_group()

 drivers/hid/hid-logitech-hidpp.c | 509 ++++++++++++++++---------------
 1 file changed, 264 insertions(+), 245 deletions(-)

-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 01/18] HID: logitech-hidpp: fix G920 force-feedback initialization timeout
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 02/18] HID: logitech-hidpp: use GFP_ATOMIC in hidpp_ff_queue_work() Dmitry Torokhov
                   ` (16 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel,
	stable

Commit 544315e40172 ("HID: logitech-hidpp: move FF initialization to
.input_configured()") moved Logitech G920 force-feedback querying and
initialization into hidpp_input_configured(), which runs during
hid_connect(). Because hidpp_probe() calls hid_device_io_stop() across
hid_connect(), incoming HID++ responses from the device are blocked
and g920_get_config() times out with -ETIMEDOUT.

Temporarily re-enable HID device I/O around g920_get_config() in
hidpp_input_configured() so the hardware configuration queries receive
their replies during input setup.

Fixes: 544315e40172 ("HID: logitech-hidpp: move FF initialization to .input_configured()")
Reported-by: Gideon Heinz <gideonheinz02@gmail.com>
Closes: https://lore.kernel.org/r/CAPb1+K1-pirdLGZ1wTUw26G6pvhg7rGP3QZ172ys5DB7rNCf5w@mail.gmail.com
Tested-by: Gideon Heinz <gideonheinz02@gmail.com>
Cc: stable@vger.kernel.org
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 9646380febac..6b1f55483385 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -4098,7 +4098,9 @@ static int hidpp_input_configured(struct hid_device *hdev, struct hid_input *hid
 		if (!list_is_first(&hidinput->list, &hdev->inputs))
 			return 0;
 
+		hid_device_io_start(hdev);
 		ret = g920_get_config(hidpp, &data);
+		hid_device_io_stop(hdev);
 		if (!ret)
 			ret = hidpp_ff_init(hidpp, &data);
 
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 02/18] HID: logitech-hidpp: use GFP_ATOMIC in hidpp_ff_queue_work()
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 01/18] HID: logitech-hidpp: fix G920 force-feedback initialization timeout Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 03/18] HID: logitech-hidpp: drain G920 FF workqueue before closing transport Dmitry Torokhov
                   ` (15 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel,
	stable

Force-feedback playback, gain, and autocenter updates are invoked by the
input force-feedback core while holding the input device event spinlock
with interrupts disabled. Allocating work items with GFP_KERNEL in
hidpp_ff_queue_work() can therefore sleep in atomic context.

Allocate work items with GFP_ATOMIC instead so force-feedback commands
can be queued safely while event locks are held.

Fixes: ff21a635dd1a ("HID: logitech-hidpp: Force feedback support for the Logitech G920")
Cc: stable@vger.kernel.org
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 6b1f55483385..40ebab5241b7 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -2542,9 +2542,10 @@ static void hidpp_ff_work_handler(struct work_struct *w)
 
 static int hidpp_ff_queue_work(struct hidpp_ff_private_data *data, int effect_id, u8 command, u8 *params, u8 size)
 {
-	struct hidpp_ff_work_data *wd = kzalloc_obj(*wd);
+	struct hidpp_ff_work_data *wd;
 	int s;
 
+	wd = kzalloc_obj(*wd, GFP_ATOMIC);
 	if (!wd)
 		return -ENOMEM;
 
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 03/18] HID: logitech-hidpp: drain G920 FF workqueue before closing transport
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 01/18] HID: logitech-hidpp: fix G920 force-feedback initialization timeout Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 02/18] HID: logitech-hidpp: use GFP_ATOMIC in hidpp_ff_queue_work() Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 04/18] HID: logitech-hidpp: disable works before stopping transport Dmitry Torokhov
                   ` (14 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel,
	stable

Probe no longer keeps the HID transport open indefinitely, so the
underlying USB interrupt IN endpoint only stays active while the HID
transport is held open. When the last input user closes the device
after input core flushes active effects, or when the device is
inhibited with commands still queued, releasing the HID transport
shuts down transport I/O unless another client holds it open. Queued
force-feedback commands then fail to receive their HID++ replies,
causing command timeouts and leaving stale slot mappings behind.

Override the input close callback once G920 force feedback is
initialized so the force-feedback send queue drains completely before
closing the HID transport.

Fixes: 91cf9a98ae41 ("HID: logitech-hidpp: make .probe usbhid capable")
Cc: stable@vger.kernel.org
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 40ebab5241b7..476c0f0eb086 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -2815,6 +2815,15 @@ static void hidpp_ff_destroy(struct ff_device *ff)
 	kfree(data->effect_ids);
 }
 
+static void hidpp_ff_close(struct input_dev *dev)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct hidpp_ff_private_data *data = dev->ff->private;
+
+	flush_workqueue(data->wq);
+	hid_hw_close(hid);
+}
+
 static int hidpp_ff_init(struct hidpp_device *hidpp,
 			 struct hidpp_ff_private_data *data)
 {
@@ -2897,6 +2906,8 @@ static int hidpp_ff_init(struct hidpp_device *hidpp,
 	ff->set_autocenter = hidpp_ff_set_autocenter;
 	ff->destroy = hidpp_ff_destroy;
 
+	dev->close = hidpp_ff_close;
+
 	/* Create sysfs interface */
 	error = device_create_file(&(hidpp->hid_dev->dev), &dev_attr_range);
 	if (error)
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 04/18] HID: logitech-hidpp: disable works before stopping transport
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (2 preceding siblings ...)
  2026-11-09  6:10 ` [PATCH 03/18] HID: logitech-hidpp: drain G920 FF workqueue before closing transport Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 05/18] HID: logitech-hidpp: do not store ERR_PTR in battery->ps Dmitry Torokhov
                   ` (13 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel,
	stable

Device removal cancels the connect-event and hi-res-reset work items
after stopping the HID transport, leaving in-flight handlers able to
issue HID++ transfers or register power-supply and input devices during
teardown. The connect failure unwind path in probe likewise cancels work
after closing and stopping the transport and omits the hi-res-reset
work item even though incoming packets are already enabled before
connection setup.

Disable both work items synchronously before closing or stopping the HID
transport on device removal and on the probe post-I/O failure path, and
drop the unreachable cancellation from the early transport start failure
path.

Fixes: ed80cc4667ac ("HID: logitech-hidpp: Add HIDPP_QUIRK_RESET_HI_RES_SCROLL")
Cc: stable@vger.kernel.org
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 476c0f0eb086..4e0758cdae64 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -4799,12 +4799,13 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	return ret;
 
 hid_hw_init_fail:
+	disable_work_sync(&hidpp->work);
+	disable_work_sync(&hidpp->reset_hi_res_work);
 	hid_hw_close(hdev);
 hid_hw_open_fail:
 	hid_hw_stop(hdev);
 hid_hw_start_fail:
 	sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
-	cancel_work_sync(&hidpp->work);
 	mutex_destroy(&hidpp->send_mutex);
 	return ret;
 }
@@ -4818,9 +4819,10 @@ static void hidpp_remove(struct hid_device *hdev)
 
 	sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
 
+	disable_work_sync(&hidpp->work);
+	disable_work_sync(&hidpp->reset_hi_res_work);
+
 	hid_hw_stop(hdev);
-	cancel_work_sync(&hidpp->work);
-	cancel_work_sync(&hidpp->reset_hi_res_work);
 	mutex_destroy(&hidpp->send_mutex);
 }
 
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 05/18] HID: logitech-hidpp: do not store ERR_PTR in battery->ps
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (3 preceding siblings ...)
  2026-11-09  6:10 ` [PATCH 04/18] HID: logitech-hidpp: disable works before stopping transport Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 06/18] HID: logitech-hidpp: avoid NULL subtraction in consumer key report fixup Dmitry Torokhov
                   ` (12 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel,
	stable

When power supply registration fails, storing the error pointer directly
in the battery supply field leaves it non-NULL. Callers such as the
connect handler and battery event handlers only check for non-NULL
before notifying the power supply core, causing an error-pointer
dereference oops and blocking retries on subsequent reconnect events.

Capture the registered power supply pointer in a local variable and
publish it to device state only after registration and sysfs linking
succeed.

Fixes: a52ec107fa81 ("HID: logitech-hidpp: create the battery for all types of HID++ devices")
Cc: stable@vger.kernel.org
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 4e0758cdae64..9f3ae8724dd2 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -4327,6 +4327,7 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
 	struct power_supply_desc *desc = &hidpp->battery.desc;
 	enum power_supply_property *battery_props;
 	struct hidpp_battery *battery;
+	struct power_supply *ps;
 	unsigned int num_battery_props;
 	unsigned long n;
 	int ret;
@@ -4407,15 +4408,16 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
 	desc->type = POWER_SUPPLY_TYPE_BATTERY;
 	desc->use_for_apm = 0;
 
-	battery->ps = devm_power_supply_register(&hidpp->hid_dev->dev,
-						 &battery->desc,
-						 &cfg);
-	if (IS_ERR(battery->ps))
-		return PTR_ERR(battery->ps);
+	ps = devm_power_supply_register(&hidpp->hid_dev->dev,
+					&battery->desc,
+					&cfg);
+	if (IS_ERR(ps))
+		return PTR_ERR(ps);
 
-	power_supply_powers(battery->ps, &hidpp->hid_dev->dev);
+	power_supply_powers(ps, &hidpp->hid_dev->dev);
+	battery->ps = ps;
 
-	return ret;
+	return 0;
 }
 
 /* Get name + serial for USB and Bluetooth HID++ devices */
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 06/18] HID: logitech-hidpp: avoid NULL subtraction in consumer key report fixup
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (4 preceding siblings ...)
  2026-11-09  6:10 ` [PATCH 05/18] HID: logitech-hidpp: do not store ERR_PTR in battery->ps Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 07/18] HID: logitech-hidpp: fix off-by-one in G920 force-feedback slot tracking Dmitry Torokhov
                   ` (11 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel,
	stable

Report fixup runs for all 27 MHz devices because the keyboard quirk
is only determined later after parsing the report descriptor. For mice
and other devices without the consumer collection, searching for the
descriptor fragment returns NULL, yet the remaining length calculation
subtracts the base descriptor pointer from NULL before checking the
search result, triggering pointer-subtraction undefined behavior.

Ensure the search result is non-NULL before computing the remaining
descriptor length.

Fixes: 42bc4f3129e6 ("HID: logitech-hidpp: add support for HID++ 1.0 consumer keys reports")
Cc: stable@vger.kernel.org
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 9f3ae8724dd2..a1a69705338f 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -3866,11 +3866,9 @@ static u8 *hidpp10_consumer_keys_report_fixup(struct hidpp_device *hidpp,
 		0x26, 0x00	/* LOGICAL_MAX (...                    */
 	};
 	char *consumer_rdesc, *rdesc = (char *)_rdesc;
-	unsigned int size;
 
 	consumer_rdesc = strnstr(rdesc, consumer_rdesc_start, *rsize);
-	size = *rsize - (consumer_rdesc - rdesc);
-	if (consumer_rdesc && size >= 25) {
+	if (consumer_rdesc && *rsize - (consumer_rdesc - rdesc) >= 25) {
 		consumer_rdesc[15] = 0x7f;
 		consumer_rdesc[16] = 0x10;
 		consumer_rdesc[20] = 0x7f;
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 07/18] HID: logitech-hidpp: fix off-by-one in G920 force-feedback slot tracking
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (5 preceding siblings ...)
  2026-11-09  6:10 ` [PATCH 06/18] HID: logitech-hidpp: avoid NULL subtraction in consumer key report fixup Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 08/18] HID: logitech-hidpp: rework G920 force-feedback initialization Dmitry Torokhov
                   ` (10 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel,
	stable

Logitech G920 firmware reports 64 total 1-based hardware effect slots,
reserving one slot for the built-in autocenter spring while allocating
the remaining 63 user effects across 1-based slots 2 through 64. Because
the driver subtracts the reserved slot count upfront before sizing and
bounds-checking its slot mapping table, the 63rd concurrent user effect
is assigned hardware slot 64 and exceeds the stored limit, so its slot
mapping is never recorded or released and remains leaked in wheel memory
across device close and reopen.

Keep the full hardware slot count for table sizing, lookups, and slot
bounds checks, and withhold the reserved slot count only when creating
the input force-feedback device.

Fixes: ff21a635dd1a ("HID: logitech-hidpp: Force feedback support for the Logitech G920")
Cc: stable@vger.kernel.org
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index a1a69705338f..c4c878c9061b 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -2865,7 +2865,7 @@ static int hidpp_ff_init(struct hidpp_device *hidpp,
 		for (j = 0; hidpp_ff_effects_v2[j] >= 0; j++)
 			set_bit(hidpp_ff_effects_v2[j], dev->ffbit);
 
-	error = input_ff_create(dev, num_slots);
+	error = input_ff_create(dev, num_slots - HIDPP_FF_RESERVED_SLOTS);
 
 	if (error) {
 		hid_err(dev, "Failed to create FF device!\n");
@@ -3480,7 +3480,7 @@ static int g920_get_config(struct hidpp_device *hidpp,
 		return -EPROTO;
 	}
 
-	data->num_effects = response.fap.params[0] - HIDPP_FF_RESERVED_SLOTS;
+	data->num_effects = response.fap.params[0];
 
 	/* reset all forces */
 	ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 08/18] HID: logitech-hidpp: rework G920 force-feedback initialization
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (6 preceding siblings ...)
  2026-11-09  6:10 ` [PATCH 07/18] HID: logitech-hidpp: fix off-by-one in G920 force-feedback slot tracking Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 09/18] HID: logitech-hidpp: use sysfs_emit() in hidpp_ff_range_show() Dmitry Torokhov
                   ` (9 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel

Querying G920 force-feedback capabilities inside .input_configured()
requires toggling device I/O mid-registration and conflates device
configuration with input setup. At the same time, the single allocated
state structure mixes per-device wheel settings with the lifetime of the
input force-feedback queue, complicating error handling and cleanup.

Exclude generic USB PID force feedback for validated HID++ devices and
query the G920 force-feedback configuration during probe while device
I/O is already active. Keep both probe query failures and force-feedback
initialization errors non-fatal so basic wheel and pedal inputs remain
usable without force feedback, while ensuring failed initialization
leaves input capabilities clean.

Split the overloaded force-feedback state by lifetime so device-level
G920 wheel settings are devm-managed for the bound device lifetime,
while the input force-feedback queue and slot tracking are allocated
with a flexible array and owned strictly by the force-feedback device.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 223 +++++++++++++++----------------
 1 file changed, 111 insertions(+), 112 deletions(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index c4c878c9061b..0a82914ea314 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -2401,15 +2401,19 @@ static void hidpp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev,
 
 struct hidpp_ff_private_data {
 	struct hidpp_device *hidpp;
+	struct workqueue_struct *wq;
+	atomic_t workqueue_size;
+	u8 num_effects;
+	int effect_ids[] __counted_by(num_effects);
+};
+
+struct g920_data {
+	struct hidpp_ff_private_data *ff;
 	u8 feature_index;
-	u8 version;
-	u16 gain;
-	s16 range;
 	u8 slot_autocenter;
 	u8 num_effects;
-	int *effect_ids;
-	struct workqueue_struct *wq;
-	atomic_t workqueue_size;
+	u16 gain;
+	u16 range;
 };
 
 struct hidpp_ff_work_data {
@@ -2473,6 +2477,7 @@ static void hidpp_ff_work_handler(struct work_struct *w)
 {
 	struct hidpp_ff_work_data *wd = container_of(w, struct hidpp_ff_work_data, work);
 	struct hidpp_ff_private_data *data = wd->data;
+	struct g920_data *g920 = data->hidpp->private_data;
 	struct hidpp_report response;
 	u8 slot;
 	int ret;
@@ -2480,7 +2485,7 @@ static void hidpp_ff_work_handler(struct work_struct *w)
 	/* add slot number if needed */
 	switch (wd->effect_id) {
 	case HIDPP_FF_EFFECTID_AUTOCENTER:
-		wd->params[0] = data->slot_autocenter;
+		wd->params[0] = g920->slot_autocenter;
 		break;
 	case HIDPP_FF_EFFECTID_NONE:
 		/* leave slot as zero */
@@ -2492,8 +2497,9 @@ static void hidpp_ff_work_handler(struct work_struct *w)
 	}
 
 	/* send command and wait for reply */
-	ret = hidpp_send_fap_command_sync(data->hidpp, data->feature_index,
-		wd->command, wd->params, wd->size, &response);
+	ret = hidpp_send_fap_command_sync(data->hidpp, g920->feature_index,
+					  wd->command, wd->params, wd->size,
+					  &response);
 
 	if (ret) {
 		hid_err(data->hidpp->hid_dev, "Failed to send command to device!\n");
@@ -2510,7 +2516,7 @@ static void hidpp_ff_work_handler(struct work_struct *w)
 				data->effect_ids[slot-1] = wd->effect_id;
 			else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER)
 				/* autocenter spring uploaded */
-				data->slot_autocenter = slot;
+				g920->slot_autocenter = slot;
 		}
 		break;
 	case HIDPP_FF_DESTROY_EFFECT:
@@ -2521,14 +2527,14 @@ static void hidpp_ff_work_handler(struct work_struct *w)
 				data->effect_ids[slot-1] = -1;
 			else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER)
 				/* autocenter spring destroyed */
-				data->slot_autocenter = 0;
+				g920->slot_autocenter = 0;
 		}
 		break;
 	case HIDPP_FF_SET_GLOBAL_GAINS:
-		data->gain = (wd->params[0] << 8) + wd->params[1];
+		g920->gain = (wd->params[0] << 8) + wd->params[1];
 		break;
 	case HIDPP_FF_SET_APERTURE:
-		data->range = (wd->params[0] << 8) + wd->params[1];
+		g920->range = (wd->params[0] << 8) + wd->params[1];
 		break;
 	default:
 		/* no action needed */
@@ -2775,19 +2781,17 @@ static void hidpp_ff_set_gain(struct input_dev *dev, u16 gain)
 static ssize_t hidpp_ff_range_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
 	struct hid_device *hid = to_hid_device(dev);
-	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
-	struct input_dev *idev = hidinput->input;
-	struct hidpp_ff_private_data *data = idev->ff->private;
+	struct hidpp_device *hidpp = hid_get_drvdata(hid);
+	struct g920_data *g920 = hidpp->private_data;
 
-	return scnprintf(buf, PAGE_SIZE, "%u\n", data->range);
+	return scnprintf(buf, PAGE_SIZE, "%u\n", g920->range);
 }
 
 static ssize_t hidpp_ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
 {
 	struct hid_device *hid = to_hid_device(dev);
-	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
-	struct input_dev *idev = hidinput->input;
-	struct hidpp_ff_private_data *data = idev->ff->private;
+	struct hidpp_device *hidpp = hid_get_drvdata(hid);
+	struct g920_data *g920 = hidpp->private_data;
 	u8 params[2];
 	int range = simple_strtoul(buf, NULL, 10);
 
@@ -2796,7 +2800,7 @@ static ssize_t hidpp_ff_range_store(struct device *dev, struct device_attribute
 	params[0] = range >> 8;
 	params[1] = range & 0x00FF;
 
-	hidpp_ff_queue_work(data, -1, HIDPP_FF_SET_APERTURE, params, ARRAY_SIZE(params));
+	hidpp_ff_queue_work(g920->ff, -1, HIDPP_FF_SET_APERTURE, params, ARRAY_SIZE(params));
 
 	return count;
 }
@@ -2806,13 +2810,15 @@ static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, hidpp
 static void hidpp_ff_destroy(struct ff_device *ff)
 {
 	struct hidpp_ff_private_data *data = ff->private;
-	struct hid_device *hid = data->hidpp->hid_dev;
+	struct hidpp_device *hidpp = data->hidpp;
+	struct hid_device *hid = hidpp->hid_dev;
+	struct g920_data *g920 = hidpp->private_data;
 
 	hid_info(hid, "Unloading HID++ force feedback.\n");
 
 	device_remove_file(&hid->dev, &dev_attr_range);
 	destroy_workqueue(data->wq);
-	kfree(data->effect_ids);
+	g920->ff = NULL;
 }
 
 static void hidpp_ff_close(struct input_dev *dev)
@@ -2824,16 +2830,14 @@ static void hidpp_ff_close(struct input_dev *dev)
 	hid_hw_close(hid);
 }
 
-static int hidpp_ff_init(struct hidpp_device *hidpp,
-			 struct hidpp_ff_private_data *data)
+static int hidpp_ff_init(struct hidpp_device *hidpp, struct input_dev *dev)
 {
 	struct hid_device *hid = hidpp->hid_dev;
-	struct hid_input *hidinput;
-	struct input_dev *dev;
+	struct g920_data *g920 = hidpp->private_data;
 	struct usb_device_descriptor *udesc;
 	u16 bcdDevice;
 	struct ff_device *ff;
-	int error, j, num_slots = data->num_effects;
+	int error, j, num_slots = g920->num_effects;
 	u8 version;
 
 	if (!hid_is_usb(hid)) {
@@ -2841,17 +2845,22 @@ static int hidpp_ff_init(struct hidpp_device *hidpp,
 		return -ENODEV;
 	}
 
-	if (list_empty(&hid->inputs)) {
-		hid_err(hid, "no inputs found\n");
-		return -ENODEV;
-	}
-	hidinput = list_entry(hid->inputs.next, struct hid_input, list);
-	dev = hidinput->input;
+	struct hidpp_ff_private_data *data __free(kfree) =
+		kzalloc_flex(*data, effect_ids, num_slots);
+	if (!data)
+		return -ENOMEM;
 
-	if (!dev) {
-		hid_err(hid, "Struct input_dev not set!\n");
-		return -EINVAL;
-	}
+	data->hidpp = hidpp;
+	data->num_effects = num_slots;
+	for (j = 0; j < num_slots; j++)
+		data->effect_ids[j] = -1;
+
+	/* init the hardware command queue */
+	atomic_set(&data->workqueue_size, 0);
+
+	data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue");
+	if (!data->wq)
+		return -ENOMEM;
 
 	/* Get firmware release */
 	udesc = &(hid_to_usb_dev(hid)->descriptor);
@@ -2866,38 +2875,16 @@ static int hidpp_ff_init(struct hidpp_device *hidpp,
 			set_bit(hidpp_ff_effects_v2[j], dev->ffbit);
 
 	error = input_ff_create(dev, num_slots - HIDPP_FF_RESERVED_SLOTS);
-
 	if (error) {
-		hid_err(dev, "Failed to create FF device!\n");
+		hid_err(hid, "Failed to create FF device!\n");
+		bitmap_zero(dev->ffbit, FF_CNT);
+		destroy_workqueue(data->wq);
 		return error;
 	}
-	/*
-	 * Create a copy of passed data, so we can transfer memory
-	 * ownership to FF core
-	 */
-	data = kmemdup(data, sizeof(*data), GFP_KERNEL);
-	if (!data) {
-		error = -ENOMEM;
-		goto err_destroy_ff;
-	}
-	data->effect_ids = kzalloc_objs(int, num_slots);
-	if (!data->effect_ids) {
-		error = -ENOMEM;
-		goto err_free_data;
-	}
-	data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue");
-	if (!data->wq) {
-		error = -ENOMEM;
-		goto err_free_effect_ids;
-	}
-
-	data->hidpp = hidpp;
-	data->version = version;
-	for (j = 0; j < num_slots; j++)
-		data->effect_ids[j] = -1;
 
 	ff = dev->ff;
-	ff->private = data;
+	g920->ff = data;
+	ff->private = no_free_ptr(data);
 
 	ff->upload = hidpp_ff_upload_effect;
 	ff->erase = hidpp_ff_erase_effect;
@@ -2913,21 +2900,10 @@ static int hidpp_ff_init(struct hidpp_device *hidpp,
 	if (error)
 		hid_warn(hidpp->hid_dev, "Unable to create sysfs interface for \"range\", errno %d!\n", error);
 
-	/* init the hardware command queue */
-	atomic_set(&data->workqueue_size, 0);
-
 	hid_info(hid, "Force feedback support loaded (firmware release %d).\n",
 		 version);
 
 	return 0;
-
-err_free_effect_ids:
-	kfree(data->effect_ids);
-err_free_data:
-	kfree(data);
-err_destroy_ff:
-	input_ff_destroy(dev);
-	return error;
 }
 
 /* ************************************************************************** */
@@ -3430,7 +3406,7 @@ static int k400_connect(struct hid_device *hdev)
 #define HIDPP_PAGE_G920_FORCE_FEEDBACK			0x8123
 
 static int g920_ff_set_autocenter(struct hidpp_device *hidpp,
-				  struct hidpp_ff_private_data *data)
+				  struct g920_data *data)
 {
 	struct hidpp_report response;
 	u8 params[HIDPP_AUTOCENTER_PARAMS_LENGTH] = {
@@ -3445,30 +3421,35 @@ static int g920_ff_set_autocenter(struct hidpp_device *hidpp,
 					  HIDPP_FF_DOWNLOAD_EFFECT,
 					  params, ARRAY_SIZE(params),
 					  &response);
-	if (ret)
+	if (ret) {
 		hid_warn(hidpp->hid_dev, "Failed to autocenter device!\n");
-	else
-		data->slot_autocenter = response.fap.params[0];
+		return ret < 0 ? ret : -EPROTO;
+	}
 
-	return ret;
+	data->slot_autocenter = response.fap.params[0];
+	return 0;
 }
 
-static int g920_get_config(struct hidpp_device *hidpp,
-			   struct hidpp_ff_private_data *data)
+static int g920_get_config(struct hidpp_device *hidpp)
 {
+	struct g920_data data = {};
 	struct hidpp_report response;
 	int ret;
 
-	memset(data, 0, sizeof(*data));
-
 	/* Find feature and store for later use */
 	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK,
-				     &data->feature_index);
-	if (ret)
-		return ret;
+				     &data.feature_index);
+	if (ret) {
+		if (ret < 0)
+			return ret;
+		hid_err(hidpp->hid_dev,
+			"%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
 
 	/* Read number of slots available in device */
-	ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
+	ret = hidpp_send_fap_command_sync(hidpp, data.feature_index,
 					  HIDPP_FF_GET_INFO,
 					  NULL, 0,
 					  &response);
@@ -3476,45 +3457,59 @@ static int g920_get_config(struct hidpp_device *hidpp,
 		if (ret < 0)
 			return ret;
 		hid_err(hidpp->hid_dev,
-			"%s: received protocol error 0x%02x\n", __func__, ret);
+			"%s: received protocol error 0x%02x\n",
+			__func__, ret);
 		return -EPROTO;
 	}
 
-	data->num_effects = response.fap.params[0];
+	if (response.fap.params[0] <= HIDPP_FF_RESERVED_SLOTS ||
+	    response.fap.params[0] > FF_MAX_EFFECTS + HIDPP_FF_RESERVED_SLOTS) {
+		hid_err(hidpp->hid_dev,
+			"Invalid force feedback slot count %u\n",
+			response.fap.params[0]);
+		return -EINVAL;
+	}
+
+	data.num_effects = response.fap.params[0];
 
 	/* reset all forces */
-	ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
+	ret = hidpp_send_fap_command_sync(hidpp, data.feature_index,
 					  HIDPP_FF_RESET_ALL,
 					  NULL, 0,
 					  &response);
 	if (ret)
 		hid_warn(hidpp->hid_dev, "Failed to reset all forces!\n");
 
-	ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
+	ret = hidpp_send_fap_command_sync(hidpp, data.feature_index,
 					  HIDPP_FF_GET_APERTURE,
 					  NULL, 0,
 					  &response);
-	if (ret) {
+	if (ret)
 		hid_warn(hidpp->hid_dev,
 			 "Failed to read range from device!\n");
-	}
-	data->range = ret ?
+	data.range = ret ?
 		900 : get_unaligned_be16(&response.fap.params[0]);
 
 	/* Read the current gain values */
-	ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
+	ret = hidpp_send_fap_command_sync(hidpp, data.feature_index,
 					  HIDPP_FF_GET_GLOBAL_GAINS,
 					  NULL, 0,
 					  &response);
 	if (ret)
 		hid_warn(hidpp->hid_dev,
 			 "Failed to read gain values from device!\n");
-	data->gain = ret ?
+	data.gain = ret ?
 		0xffff : get_unaligned_be16(&response.fap.params[0]);
 
 	/* ignore boost value at response.fap.params[2] */
 
-	return g920_ff_set_autocenter(hidpp, data);
+	ret = g920_ff_set_autocenter(hidpp, &data);
+	if (ret)
+		return ret;
+
+	hidpp->private_data = devm_kmemdup(&hidpp->hid_dev->dev, &data,
+					   sizeof(data), GFP_KERNEL);
+	return hidpp->private_data ? 0 : -ENOMEM;
 }
 
 /* -------------------------------------------------------------------------- */
@@ -4103,21 +4098,15 @@ static int hidpp_input_configured(struct hid_device *hdev, struct hid_input *hid
 		return 0;
 
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
-		struct hidpp_ff_private_data data;
-
 		if (!list_is_first(&hidinput->list, &hdev->inputs))
 			return 0;
 
-		hid_device_io_start(hdev);
-		ret = g920_get_config(hidpp, &data);
-		hid_device_io_stop(hdev);
-		if (!ret)
-			ret = hidpp_ff_init(hidpp, &data);
-
-		if (ret) {
-			hid_warn(hidpp->hid_dev,
-				 "Unable to initialize force feedback support, errno %d\n",
-				 ret);
+		if (hidpp->private_data) {
+			ret = hidpp_ff_init(hidpp, input);
+			if (ret)
+				hid_warn(hdev,
+					 "Unable to initialize force feedback support, errno %d\n",
+					 ret);
 		}
 	}
 
@@ -4686,7 +4675,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 {
 	struct hidpp_device *hidpp;
 	int ret;
-	unsigned int connect_mask = HID_CONNECT_DEFAULT;
+	unsigned int connect_mask;
 
 	/* report_fixup needs drvdata to be set before we call hid_parse */
 	hidpp = devm_kzalloc(&hdev->dev, sizeof(*hidpp), GFP_KERNEL);
@@ -4775,6 +4764,16 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	else
 		hidpp_non_unifying_init(hidpp);
 
+	if ((hidpp->quirks & HIDPP_QUIRK_CLASS_G920) && hid_is_usb(hdev)) {
+		ret = g920_get_config(hidpp);
+		if (ret)
+			hid_warn(hdev,
+				 "Unable to query force feedback config, errno %d\n",
+				 ret);
+	}
+
+	/* None of the HID++ devices use generic USB PID force-feedback. */
+	connect_mask = HID_CONNECT_DEFAULT & ~HID_CONNECT_FF;
 	if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
 		connect_mask &= ~HID_CONNECT_HIDINPUT;
 
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 09/18] HID: logitech-hidpp: use sysfs_emit() in hidpp_ff_range_show()
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (7 preceding siblings ...)
  2026-11-09  6:10 ` [PATCH 08/18] HID: logitech-hidpp: rework G920 force-feedback initialization Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 10/18] HID: logitech-hidpp: manage G920 range sysfs attribute via dev_groups Dmitry Torokhov
                   ` (8 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel

Formatting the G920 wheel range sysfs output with scnprintf() passes
buffer sizes manually instead of using the standard sysfs helper. Use
sysfs_emit() in hidpp_ff_range_show() to align with current kernel sysfs
conventions.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 0a82914ea314..1ed940352fd2 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -2784,7 +2784,7 @@ static ssize_t hidpp_ff_range_show(struct device *dev, struct device_attribute *
 	struct hidpp_device *hidpp = hid_get_drvdata(hid);
 	struct g920_data *g920 = hidpp->private_data;
 
-	return scnprintf(buf, PAGE_SIZE, "%u\n", g920->range);
+	return sysfs_emit(buf, "%u\n", g920->range);
 }
 
 static ssize_t hidpp_ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 10/18] HID: logitech-hidpp: manage G920 range sysfs attribute via dev_groups
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (8 preceding siblings ...)
  2026-11-09  6:10 ` [PATCH 09/18] HID: logitech-hidpp: use sysfs_emit() in hidpp_ff_range_show() Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 11/18] HID: logitech-hidpp: shut down G920 force feedback on input unregister Dmitry Torokhov
                   ` (7 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel

Creating and removing the G920 range attribute manually inside force
feedback initialization and destruction ties sysfs cleanup to input
device release rather than driver unbind, leaving a dangling attribute
if userspace holds an open event handle across disconnect. Manual sysfs
management also adds avoidable boilerplate.

Register the attribute group through driver dev_groups with a visibility
callback checking initialized G920 force-feedback state, allowing driver
core to create and tear down the attribute cleanly around probe and
remove.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 45 ++++++++++++++++++++++++++------
 1 file changed, 37 insertions(+), 8 deletions(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 1ed940352fd2..ad3edd15cdd2 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -2807,16 +2807,47 @@ static ssize_t hidpp_ff_range_store(struct device *dev, struct device_attribute
 
 static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, hidpp_ff_range_show, hidpp_ff_range_store);
 
+static struct attribute *g920_range_attrs[] = {
+	&dev_attr_range.attr,
+	NULL
+};
+
+static umode_t g920_range_attr_is_visible(struct kobject *kobj,
+					  struct attribute *attr, int idx)
+{
+	struct device *dev = kobj_to_dev(kobj);
+	struct hid_device *hdev = to_hid_device(dev);
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct g920_data *g920;
+
+	if (!hidpp || !(hidpp->quirks & HIDPP_QUIRK_CLASS_G920))
+		return 0;
+
+	g920 = hidpp->private_data;
+	if (!g920 || !g920->ff)
+		return 0;
+
+	return attr->mode;
+}
+
+static const struct attribute_group g920_range_group = {
+	.attrs = g920_range_attrs,
+	.is_visible = g920_range_attr_is_visible,
+};
+
+static const struct attribute_group *hidpp_dev_groups[] = {
+	&g920_range_group,
+	NULL
+};
+
 static void hidpp_ff_destroy(struct ff_device *ff)
 {
 	struct hidpp_ff_private_data *data = ff->private;
 	struct hidpp_device *hidpp = data->hidpp;
-	struct hid_device *hid = hidpp->hid_dev;
 	struct g920_data *g920 = hidpp->private_data;
 
-	hid_info(hid, "Unloading HID++ force feedback.\n");
+	hid_info(hidpp->hid_dev, "Unloading HID++ force feedback.\n");
 
-	device_remove_file(&hid->dev, &dev_attr_range);
 	destroy_workqueue(data->wq);
 	g920->ff = NULL;
 }
@@ -2895,11 +2926,6 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, struct input_dev *dev)
 
 	dev->close = hidpp_ff_close;
 
-	/* Create sysfs interface */
-	error = device_create_file(&(hidpp->hid_dev->dev), &dev_attr_range);
-	if (error)
-		hid_warn(hidpp->hid_dev, "Unable to create sysfs interface for \"range\", errno %d!\n", error);
-
 	hid_info(hid, "Force feedback support loaded (firmware release %d).\n",
 		 version);
 
@@ -5033,6 +5059,9 @@ static struct hid_driver hidpp_driver = {
 	.input_configured = hidpp_input_configured,
 	.input_mapping = hidpp_input_mapping,
 	.input_mapped = hidpp_input_mapped,
+	.driver = {
+		.dev_groups = hidpp_dev_groups,
+	},
 };
 
 module_hid_driver(hidpp_driver);
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 11/18] HID: logitech-hidpp: shut down G920 force feedback on input unregister
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (9 preceding siblings ...)
  2026-11-09  6:10 ` [PATCH 10/18] HID: logitech-hidpp: manage G920 range sysfs attribute via dev_groups Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 12/18] HID: logitech-hidpp: use unaligned accessors for multi-byte values Dmitry Torokhov
                   ` (6 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel

Force-feedback teardown currently runs only in the force-feedback
destroy callback, which is deferred until the last open event handle
is closed. If userspace holds an open handle across USB disconnect or
driver unbind, device removal frees driver state and destroys the
command mutex while queued force-feedback work is still active, causing
use-after-free errors.

Move workqueue teardown into the force-feedback stop callback so input
unregistration drains queued work while driver and HID state are still
alive. Keep calling stop from destroy as a safety net for registration
failures, guarded so repeated teardown is a safe no-op.

Fixes: 08c453f6d073 ("HID: logitech-hidpp: do all FF cleanup in hidpp_ff_destroy()")
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index ad3edd15cdd2..5169f3873257 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -2840,16 +2840,35 @@ static const struct attribute_group *hidpp_dev_groups[] = {
 	NULL
 };
 
-static void hidpp_ff_destroy(struct ff_device *ff)
+static void hidpp_ff_stop(struct ff_device *ff)
 {
 	struct hidpp_ff_private_data *data = ff->private;
-	struct hidpp_device *hidpp = data->hidpp;
-	struct g920_data *g920 = hidpp->private_data;
+	struct hidpp_device *hidpp;
+	struct g920_data *g920;
+
+	hidpp = data->hidpp;
+	if (!hidpp)
+		return;
 
 	hid_info(hidpp->hid_dev, "Unloading HID++ force feedback.\n");
 
 	destroy_workqueue(data->wq);
+
+	g920 = hidpp->private_data;
 	g920->ff = NULL;
+	data->wq = NULL;
+	data->hidpp = NULL;
+}
+
+static void hidpp_ff_destroy(struct ff_device *ff)
+{
+	/*
+	 * Workqueue teardown normally runs in ff->stop() when the input device
+	 * is unregistered. Still call it here as a safety net for error paths
+	 * before registration completes; once stopped, it becomes a safe no-op
+	 * that never touches freed driver data.
+	 */
+	hidpp_ff_stop(ff);
 }
 
 static void hidpp_ff_close(struct input_dev *dev)
@@ -2922,6 +2941,7 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, struct input_dev *dev)
 	ff->playback = hidpp_ff_playback;
 	ff->set_gain = hidpp_ff_set_gain;
 	ff->set_autocenter = hidpp_ff_set_autocenter;
+	ff->stop = hidpp_ff_stop;
 	ff->destroy = hidpp_ff_destroy;
 
 	dev->close = hidpp_ff_close;
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 12/18] HID: logitech-hidpp: use unaligned accessors for multi-byte values
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (10 preceding siblings ...)
  2026-11-09  6:10 ` [PATCH 11/18] HID: logitech-hidpp: shut down G920 force feedback on input unregister Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 13/18] HID: logitech-hidpp: store device serial as a byte array Dmitry Torokhov
                   ` (5 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel

Several paths in the Logitech HID++ driver manually split and reassemble
16-bit protocol values using shifts and masks instead of using standard
kernel helpers, which obscures endianness intent and adds unnecessary
noise.

Replace open-coded 16-bit big-endian packing and unpacking with
get_unaligned_be16() and put_unaligned_be16().

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 87 ++++++++++++--------------------
 1 file changed, 33 insertions(+), 54 deletions(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 5169f3873257..c4ebfc4d66fe 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -951,7 +951,9 @@ static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature,
 {
 	struct hidpp_report response;
 	int ret;
-	u8 params[2] = { feature >> 8, feature & 0x00FF };
+	u8 params[2];
+
+	put_unaligned_be16(feature, params);
 
 	ret = hidpp_send_fap_command_sync(hidpp,
 			HIDPP_PAGE_ROOT_IDX,
@@ -2156,7 +2158,7 @@ static int hidpp_solar_battery_event(struct hidpp_device *hidpp,
 
 	switch (function) {
 	case EVENT_SOLAR_BATTERY_LIGHT_MEASURE:
-		lux = (report->fap.params[1] << 8) | report->fap.params[2];
+		lux = get_unaligned_be16(&report->fap.params[1]);
 		if (lux > 200)
 			status = POWER_SUPPLY_STATUS_CHARGING;
 		else
@@ -2531,10 +2533,10 @@ static void hidpp_ff_work_handler(struct work_struct *w)
 		}
 		break;
 	case HIDPP_FF_SET_GLOBAL_GAINS:
-		g920->gain = (wd->params[0] << 8) + wd->params[1];
+		g920->gain = get_unaligned_be16(&wd->params[0]);
 		break;
 	case HIDPP_FF_SET_APERTURE:
-		g920->range = (wd->params[0] << 8) + wd->params[1];
+		g920->range = get_unaligned_be16(&wd->params[0]);
 		break;
 	default:
 		/* no action needed */
@@ -2581,23 +2583,18 @@ static int hidpp_ff_upload_effect(struct input_dev *dev, struct ff_effect *effec
 	int force;
 
 	/* set common parameters */
-	params[2] = effect->replay.length >> 8;
-	params[3] = effect->replay.length & 255;
-	params[4] = effect->replay.delay >> 8;
-	params[5] = effect->replay.delay & 255;
+	put_unaligned_be16(effect->replay.length, &params[2]);
+	put_unaligned_be16(effect->replay.delay, &params[4]);
 
 	switch (effect->type) {
 	case FF_CONSTANT:
 		force = (effect->u.constant.level * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
 		params[1] = HIDPP_FF_EFFECT_CONSTANT;
-		params[6] = force >> 8;
-		params[7] = force & 255;
+		put_unaligned_be16(force, &params[6]);
 		params[8] = effect->u.constant.envelope.attack_level >> 7;
-		params[9] = effect->u.constant.envelope.attack_length >> 8;
-		params[10] = effect->u.constant.envelope.attack_length & 255;
+		put_unaligned_be16(effect->u.constant.envelope.attack_length, &params[9]);
 		params[11] = effect->u.constant.envelope.fade_level >> 7;
-		params[12] = effect->u.constant.envelope.fade_length >> 8;
-		params[13] = effect->u.constant.envelope.fade_length & 255;
+		put_unaligned_be16(effect->u.constant.envelope.fade_length, &params[12]);
 		size = 14;
 		dbg_hid("Uploading constant force level=%d in dir %d = %d\n",
 				effect->u.constant.level,
@@ -2631,20 +2628,14 @@ static int hidpp_ff_upload_effect(struct input_dev *dev, struct ff_effect *effec
 			return -EINVAL;
 		}
 		force = (effect->u.periodic.magnitude * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
-		params[6] = effect->u.periodic.magnitude >> 8;
-		params[7] = effect->u.periodic.magnitude & 255;
-		params[8] = effect->u.periodic.offset >> 8;
-		params[9] = effect->u.periodic.offset & 255;
-		params[10] = effect->u.periodic.period >> 8;
-		params[11] = effect->u.periodic.period & 255;
-		params[12] = effect->u.periodic.phase >> 8;
-		params[13] = effect->u.periodic.phase & 255;
+		put_unaligned_be16(effect->u.periodic.magnitude, &params[6]);
+		put_unaligned_be16(effect->u.periodic.offset, &params[8]);
+		put_unaligned_be16(effect->u.periodic.period, &params[10]);
+		put_unaligned_be16(effect->u.periodic.phase, &params[12]);
 		params[14] = effect->u.periodic.envelope.attack_level >> 7;
-		params[15] = effect->u.periodic.envelope.attack_length >> 8;
-		params[16] = effect->u.periodic.envelope.attack_length & 255;
+		put_unaligned_be16(effect->u.periodic.envelope.attack_length, &params[15]);
 		params[17] = effect->u.periodic.envelope.fade_level >> 7;
-		params[18] = effect->u.periodic.envelope.fade_length >> 8;
-		params[19] = effect->u.periodic.envelope.fade_length & 255;
+		put_unaligned_be16(effect->u.periodic.envelope.fade_length, &params[18]);
 		size = 20;
 		dbg_hid("Uploading periodic force mag=%d/dir=%d, offset=%d, period=%d ms, phase=%d\n",
 				effect->u.periodic.magnitude, effect->direction,
@@ -2661,17 +2652,13 @@ static int hidpp_ff_upload_effect(struct input_dev *dev, struct ff_effect *effec
 	case FF_RAMP:
 		params[1] = HIDPP_FF_EFFECT_RAMP;
 		force = (effect->u.ramp.start_level * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
-		params[6] = force >> 8;
-		params[7] = force & 255;
+		put_unaligned_be16(force, &params[6]);
 		force = (effect->u.ramp.end_level * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
-		params[8] = force >> 8;
-		params[9] = force & 255;
+		put_unaligned_be16(force, &params[8]);
 		params[10] = effect->u.ramp.envelope.attack_level >> 7;
-		params[11] = effect->u.ramp.envelope.attack_length >> 8;
-		params[12] = effect->u.ramp.envelope.attack_length & 255;
+		put_unaligned_be16(effect->u.ramp.envelope.attack_length, &params[11]);
 		params[13] = effect->u.ramp.envelope.fade_level >> 7;
-		params[14] = effect->u.ramp.envelope.fade_length >> 8;
-		params[15] = effect->u.ramp.envelope.fade_length & 255;
+		put_unaligned_be16(effect->u.ramp.envelope.fade_length, &params[14]);
 		size = 16;
 		dbg_hid("Uploading ramp force level=%d -> %d in dir %d = %d\n",
 				effect->u.ramp.start_level,
@@ -2688,18 +2675,12 @@ static int hidpp_ff_upload_effect(struct input_dev *dev, struct ff_effect *effec
 	case FF_SPRING:
 	case FF_DAMPER:
 		params[1] = HIDPP_FF_CONDITION_CMDS[effect->type - FF_SPRING];
-		params[6] = effect->u.condition[0].left_saturation >> 9;
-		params[7] = (effect->u.condition[0].left_saturation >> 1) & 255;
-		params[8] = effect->u.condition[0].left_coeff >> 8;
-		params[9] = effect->u.condition[0].left_coeff & 255;
-		params[10] = effect->u.condition[0].deadband >> 9;
-		params[11] = (effect->u.condition[0].deadband >> 1) & 255;
-		params[12] = effect->u.condition[0].center >> 8;
-		params[13] = effect->u.condition[0].center & 255;
-		params[14] = effect->u.condition[0].right_coeff >> 8;
-		params[15] = effect->u.condition[0].right_coeff & 255;
-		params[16] = effect->u.condition[0].right_saturation >> 9;
-		params[17] = (effect->u.condition[0].right_saturation >> 1) & 255;
+		put_unaligned_be16(effect->u.condition[0].left_saturation >> 1, &params[6]);
+		put_unaligned_be16(effect->u.condition[0].left_coeff, &params[8]);
+		put_unaligned_be16(effect->u.condition[0].deadband >> 1, &params[10]);
+		put_unaligned_be16(effect->u.condition[0].center, &params[12]);
+		put_unaligned_be16(effect->u.condition[0].right_coeff, &params[14]);
+		put_unaligned_be16(effect->u.condition[0].right_saturation >> 1, &params[16]);
 		size = 18;
 		dbg_hid("Uploading %s force left coeff=%d, left sat=%d, right coeff=%d, right sat=%d\n",
 				HIDPP_FF_CONDITION_NAMES[effect->type - FF_SPRING],
@@ -2753,10 +2734,10 @@ static void hidpp_ff_set_autocenter(struct input_dev *dev, u16 magnitude)
 	/* zero delay and duration */
 	params[2] = params[3] = params[4] = params[5] = 0;
 	/* set coeff to 25% of saturation */
-	params[8] = params[14] = magnitude >> 11;
-	params[9] = params[15] = (magnitude >> 3) & 255;
-	params[6] = params[16] = magnitude >> 9;
-	params[7] = params[17] = (magnitude >> 1) & 255;
+	put_unaligned_be16(magnitude >> 3, &params[8]);
+	put_unaligned_be16(magnitude >> 3, &params[14]);
+	put_unaligned_be16(magnitude >> 1, &params[6]);
+	put_unaligned_be16(magnitude >> 1, &params[16]);
 	/* zero deadband and center */
 	params[10] = params[11] = params[12] = params[13] = 0;
 
@@ -2770,8 +2751,7 @@ static void hidpp_ff_set_gain(struct input_dev *dev, u16 gain)
 
 	dbg_hid("Setting gain to %d.\n", gain);
 
-	params[0] = gain >> 8;
-	params[1] = gain & 255;
+	put_unaligned_be16(gain, params);
 	params[2] = 0; /* no boost */
 	params[3] = 0;
 
@@ -2797,8 +2777,7 @@ static ssize_t hidpp_ff_range_store(struct device *dev, struct device_attribute
 
 	range = clamp(range, 180, 900);
 
-	params[0] = range >> 8;
-	params[1] = range & 0x00FF;
+	put_unaligned_be16(range, params);
 
 	hidpp_ff_queue_work(g920->ff, -1, HIDPP_FF_SET_APERTURE, params, ARRAY_SIZE(params));
 
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 13/18] HID: logitech-hidpp: store device serial as a byte array
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (11 preceding siblings ...)
  2026-11-09  6:10 ` [PATCH 12/18] HID: logitech-hidpp: use unaligned accessors for multi-byte values Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 14/18] HID: logitech-hidpp: use guard() for send_mutex Dmitry Torokhov
                   ` (4 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel

Device serial numbers are read from 4-byte protocol payloads only to be
formatted as raw byte sequences with %4phD. Storing them as u32 requires
unaligned pointer casts and dereferences that depend on endian-neutral
memory layout, and the FAP-based serial helper reads from the RAP union
member instead of the matching FAP payload.

Represent device serial numbers as 4-byte arrays, copy the payload bytes
with a fixed serial length constant, and read the FAP device information
response from the FAP payload directly.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 28 +++++++++++++---------------
 1 file changed, 13 insertions(+), 15 deletions(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index c4ebfc4d66fe..3a3baeb9fbb9 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -48,6 +48,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
 #define HIDPP_REPORT_SHORT_LENGTH		7
 #define HIDPP_REPORT_LONG_LENGTH		20
 #define HIDPP_REPORT_VERY_LONG_MAX_LENGTH	64
+#define HIDPP_SERIAL_LENGTH			4
 
 #define HIDPP_REPORT_SHORT_SUPPORTED		BIT(0)
 #define HIDPP_REPORT_LONG_SUPPORTED		BIT(1)
@@ -889,7 +890,8 @@ static char *hidpp_unifying_get_name(struct hidpp_device *hidpp_dev)
 	return name;
 }
 
-static int hidpp_unifying_get_serial(struct hidpp_device *hidpp, u32 *serial)
+static int hidpp_unifying_get_serial(struct hidpp_device *hidpp,
+				     u8 serial[HIDPP_SERIAL_LENGTH])
 {
 	struct hidpp_report response;
 	int ret;
@@ -903,11 +905,7 @@ static int hidpp_unifying_get_serial(struct hidpp_device *hidpp, u32 *serial)
 	if (ret)
 		return ret;
 
-	/*
-	 * We don't care about LE or BE, we will output it as a string
-	 * with %4phD, so we need to keep the order.
-	 */
-	*serial = *((u32 *)&response.rap.params[1]);
+	memcpy(serial, &response.rap.params[1], HIDPP_SERIAL_LENGTH);
 	return 0;
 }
 
@@ -915,14 +913,14 @@ static int hidpp_unifying_init(struct hidpp_device *hidpp)
 {
 	struct hid_device *hdev = hidpp->hid_dev;
 	const char *name;
-	u32 serial;
+	u8 serial[HIDPP_SERIAL_LENGTH];
 	int ret;
 
-	ret = hidpp_unifying_get_serial(hidpp, &serial);
+	ret = hidpp_unifying_get_serial(hidpp, serial);
 	if (ret)
 		return ret;
 
-	snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", &serial);
+	snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", serial);
 	dbg_hid("HID++ Unifying: Got serial: %s\n", hdev->uniq);
 
 	name = hidpp_unifying_get_name(hidpp);
@@ -1030,7 +1028,8 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
 
 #define CMD_GET_DEVICE_INFO				0x00
 
-static int hidpp_get_serial(struct hidpp_device *hidpp, u32 *serial)
+static int hidpp_get_serial(struct hidpp_device *hidpp,
+			    u8 serial[HIDPP_SERIAL_LENGTH])
 {
 	struct hidpp_report response;
 	u8 feature_index;
@@ -1047,22 +1046,21 @@ static int hidpp_get_serial(struct hidpp_device *hidpp, u32 *serial)
 	if (ret)
 		return ret;
 
-	/* See hidpp_unifying_get_serial() */
-	*serial = *((u32 *)&response.rap.params[1]);
+	memcpy(serial, &response.fap.params[1], HIDPP_SERIAL_LENGTH);
 	return 0;
 }
 
 static int hidpp_serial_init(struct hidpp_device *hidpp)
 {
 	struct hid_device *hdev = hidpp->hid_dev;
-	u32 serial;
+	u8 serial[HIDPP_SERIAL_LENGTH];
 	int ret;
 
-	ret = hidpp_get_serial(hidpp, &serial);
+	ret = hidpp_get_serial(hidpp, serial);
 	if (ret)
 		return ret;
 
-	snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", &serial);
+	snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", serial);
 	dbg_hid("HID++ DeviceInformation: Got serial: %s\n", hdev->uniq);
 
 	return 0;
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 14/18] HID: logitech-hidpp: use guard() for send_mutex
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (12 preceding siblings ...)
  2026-11-09  6:10 ` [PATCH 13/18] HID: logitech-hidpp: store device serial as a byte array Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:10 ` [PATCH 15/18] HID: logitech-hidpp: use __free(kfree) for temporary allocations Dmitry Torokhov
                   ` (3 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel

Manual mutex_lock() and mutex_unlock() calls force early loop exits to
break to a shared unlock path instead of returning directly. Convert
hidpp_send_message_sync() to use guard(mutex) so lock release is bound
to the function scope and busy-retry exits can return immediately.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 3a3baeb9fbb9..bf13e5fbe142 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -361,24 +361,22 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp,
 	int ret;
 	int max_retries = 3;
 
-	mutex_lock(&hidpp->send_mutex);
+	guard(mutex)(&hidpp->send_mutex);
 
 	do {
 		ret = __do_hidpp_send_message_sync(hidpp, message, response);
 		if (response->report_id == REPORT_ID_HIDPP_SHORT &&
 		    ret != HIDPP_ERROR_BUSY)
-			break;
+			return ret;
 		if ((response->report_id == REPORT_ID_HIDPP_LONG ||
 		     response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
 		    ret != HIDPP20_ERROR_BUSY)
-			break;
+			return ret;
 
 		dbg_hid("%s:got busy hidpp error %02X, retrying\n", __func__, ret);
 	} while (--max_retries);
 
-	mutex_unlock(&hidpp->send_mutex);
 	return ret;
-
 }
 
 /*
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 15/18] HID: logitech-hidpp: use __free(kfree) for temporary allocations
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (13 preceding siblings ...)
  2026-11-09  6:10 ` [PATCH 14/18] HID: logitech-hidpp: use guard() for send_mutex Dmitry Torokhov
@ 2026-11-09  6:10 ` Dmitry Torokhov
  2026-11-09  6:11 ` [PATCH 16/18] HID: logitech-hidpp: use alloc_ordered_workqueue() for FF queue Dmitry Torokhov
                   ` (2 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:10 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel

Managing short-lived heap allocations manually with explicit kfree()
calls adds boilerplate cleanup paths and forces callers to hold
temporary return variables across calls.

Convert temporary message and device-name allocations in command helpers
and device initialization paths to use __free(kfree) with no_free_ptr()
ownership transfer where the buffer is returned to callers.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 44 ++++++++++----------------------
 1 file changed, 14 insertions(+), 30 deletions(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index bf13e5fbe142..7f5de4caf118 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -390,18 +390,15 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
 	u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count,
 	struct hidpp_report *response)
 {
-	struct hidpp_report *message;
-	int ret;
-
-	if (param_count > sizeof(message->fap.params)) {
+	if (param_count > sizeof_field(struct hidpp_report, fap.params)) {
 		hid_dbg(hidpp->hid_dev,
 			"Invalid number of parameters passed to command (%d != %llu)\n",
 			param_count,
-			(unsigned long long) sizeof(message->fap.params));
+			(unsigned long long)sizeof_field(struct hidpp_report, fap.params));
 		return -EINVAL;
 	}
 
-	message = kzalloc_obj(struct hidpp_report);
+	struct hidpp_report *message __free(kfree) = kzalloc_obj(*message);
 	if (!message)
 		return -ENOMEM;
 
@@ -413,9 +410,7 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
 	message->fap.funcindex_clientid = funcindex_clientid | LINUX_KERNEL_SW_ID;
 	memcpy(&message->fap.params, params, param_count);
 
-	ret = hidpp_send_message_sync(hidpp, message, response);
-	kfree(message);
-	return ret;
+	return hidpp_send_message_sync(hidpp, message, response);
 }
 
 /*
@@ -429,8 +424,7 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
 	u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count,
 	struct hidpp_report *response)
 {
-	struct hidpp_report *message;
-	int ret, max_count;
+	int max_count;
 
 	/* Send as long report if short reports are not supported. */
 	if (report_id == REPORT_ID_HIDPP_SHORT &&
@@ -454,7 +448,7 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
 	if (param_count > max_count)
 		return -EINVAL;
 
-	message = kzalloc_obj(struct hidpp_report);
+	struct hidpp_report *message __free(kfree) = kzalloc_obj(*message);
 	if (!message)
 		return -ENOMEM;
 	message->report_id = report_id;
@@ -462,9 +456,7 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
 	message->rap.reg_address = reg_address;
 	memcpy(&message->rap.params, params, param_count);
 
-	ret = hidpp_send_message_sync(hidpp_dev, message, response);
-	kfree(message);
-	return ret;
+	return hidpp_send_message_sync(hidpp_dev, message, response);
 }
 
 static inline bool hidpp_match_answer(struct hidpp_report *question,
@@ -910,7 +902,6 @@ static int hidpp_unifying_get_serial(struct hidpp_device *hidpp,
 static int hidpp_unifying_init(struct hidpp_device *hidpp)
 {
 	struct hid_device *hdev = hidpp->hid_dev;
-	const char *name;
 	u8 serial[HIDPP_SERIAL_LENGTH];
 	int ret;
 
@@ -921,14 +912,13 @@ static int hidpp_unifying_init(struct hidpp_device *hidpp)
 	snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", serial);
 	dbg_hid("HID++ Unifying: Got serial: %s\n", hdev->uniq);
 
-	name = hidpp_unifying_get_name(hidpp);
+	char *name __free(kfree) = hidpp_unifying_get_name(hidpp);
 	if (!name)
 		return -EIO;
 
 	snprintf(hdev->name, sizeof(hdev->name), "%s", name);
 	dbg_hid("HID++ Unifying: Got name: %s\n", name);
 
-	kfree(name);
 	return 0;
 }
 
@@ -1142,7 +1132,6 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp)
 {
 	u8 feature_index;
 	u8 __name_length;
-	char *name;
 	unsigned index = 0;
 	int ret;
 
@@ -1156,7 +1145,7 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp)
 	if (ret)
 		return NULL;
 
-	name = kzalloc(__name_length + 1, GFP_KERNEL);
+	char *name __free(kfree) = kzalloc(__name_length + 1, GFP_KERNEL);
 	if (!name)
 		return NULL;
 
@@ -1164,17 +1153,15 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp)
 		ret = hidpp_devicenametype_get_device_name(hidpp,
 			feature_index, index, name + index,
 			__name_length - index);
-		if (ret <= 0) {
-			kfree(name);
+		if (ret <= 0)
 			return NULL;
-		}
 		index += ret;
 	}
 
 	/* include the terminating '\0' */
 	hidpp_prefix_name(&name, __name_length + 1);
 
-	return name;
+	return no_free_ptr(name);
 }
 
 /* -------------------------------------------------------------------------- */
@@ -4432,17 +4419,15 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
 static void hidpp_non_unifying_init(struct hidpp_device *hidpp)
 {
 	struct hid_device *hdev = hidpp->hid_dev;
-	char *name;
 
 	/* Bluetooth devices already have their serialnr set */
 	if (hid_is_usb(hdev))
 		hidpp_serial_init(hidpp);
 
-	name = hidpp_get_device_name(hidpp);
+	char *name __free(kfree) = hidpp_get_device_name(hidpp);
 	if (name) {
 		dbg_hid("HID++: Got name: %s\n", name);
 		snprintf(hdev->name, sizeof(hdev->name), "%s", name);
-		kfree(name);
 	}
 }
 
@@ -4489,7 +4474,7 @@ static void hidpp_connect_event(struct work_struct *work)
 	struct hidpp_device *hidpp = container_of(work, struct hidpp_device, work);
 	struct hid_device *hdev = hidpp->hid_dev;
 	struct input_dev *input;
-	char *name, *devm_name;
+	char *devm_name;
 	int ret;
 
 	/* Get device version to check if it is connected */
@@ -4547,11 +4532,10 @@ static void hidpp_connect_event(struct work_struct *work)
 	}
 
 	if (hidpp->name == hdev->name && hidpp->protocol_major >= 2) {
-		name = hidpp_get_device_name(hidpp);
+		char *name __free(kfree) = hidpp_get_device_name(hidpp);
 		if (name) {
 			devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
 						   "%s", name);
-			kfree(name);
 			if (!devm_name)
 				return;
 
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 16/18] HID: logitech-hidpp: use alloc_ordered_workqueue() for FF queue
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (14 preceding siblings ...)
  2026-11-09  6:10 ` [PATCH 15/18] HID: logitech-hidpp: use __free(kfree) for temporary allocations Dmitry Torokhov
@ 2026-11-09  6:11 ` Dmitry Torokhov
  2026-11-09  6:11 ` [PATCH 17/18] HID: logitech-hidpp: manage send_mutex with devm_mutex_init() Dmitry Torokhov
  2026-11-09  6:11 ` [PATCH 18/18] HID: logitech-hidpp: manage ps_attribute_group with devm_device_add_group() Dmitry Torokhov
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel

create_singlethread_workqueue() is a deprecated wrapper around
alloc_ordered_workqueue() that unconditionally enables WQ_MEM_RECLAIM,
allocating a rescuer thread. Force-feedback packet submission does not
run on a memory-reclaim path, so allocate the send queue with
alloc_ordered_workqueue() and zero flags to preserve strict in-order
execution without unnecessary rescuer overhead.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 7f5de4caf118..d0ec973a4b49 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -2870,7 +2870,7 @@ static int hidpp_ff_init(struct hidpp_device *hidpp, struct input_dev *dev)
 	/* init the hardware command queue */
 	atomic_set(&data->workqueue_size, 0);
 
-	data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue");
+	data->wq = alloc_ordered_workqueue("hidpp-ff-sendqueue", 0);
 	if (!data->wq)
 		return -ENOMEM;
 
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 17/18] HID: logitech-hidpp: manage send_mutex with devm_mutex_init()
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (15 preceding siblings ...)
  2026-11-09  6:11 ` [PATCH 16/18] HID: logitech-hidpp: use alloc_ordered_workqueue() for FF queue Dmitry Torokhov
@ 2026-11-09  6:11 ` Dmitry Torokhov
  2026-11-09  6:11 ` [PATCH 18/18] HID: logitech-hidpp: manage ps_attribute_group with devm_device_add_group() Dmitry Torokhov
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel

Manually initializing and destroying the HID++ command mutex in probe
and remove destroys the mutex before devres tears down managed child
resources such as the delayed input device and battery power supply.
Switching to devm_mutex_init() eliminates manual teardown boilerplate
while keeping the mutex valid throughout child device teardown.

Initialize the command mutex with devm_mutex_init() during probe,
returning early on allocation failure, and drop manual mutex
destruction from the error path and device removal handler.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index d0ec973a4b49..7ff7b7d8e2d2 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -4732,9 +4732,12 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 
 	INIT_WORK(&hidpp->work, hidpp_connect_event);
 	INIT_WORK(&hidpp->reset_hi_res_work, hidpp_reset_hi_res_handler);
-	mutex_init(&hidpp->send_mutex);
 	init_waitqueue_head(&hidpp->wait);
 
+	ret = devm_mutex_init(&hdev->dev, &hidpp->send_mutex);
+	if (ret)
+		return ret;
+
 	/* indicates we are handling the battery properties in the kernel */
 	ret = sysfs_create_group(&hdev->dev.kobj, &ps_attribute_group);
 	if (ret)
@@ -4810,7 +4813,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	hid_hw_stop(hdev);
 hid_hw_start_fail:
 	sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
-	mutex_destroy(&hidpp->send_mutex);
 	return ret;
 }
 
@@ -4827,7 +4829,6 @@ static void hidpp_remove(struct hid_device *hdev)
 	disable_work_sync(&hidpp->reset_hi_res_work);
 
 	hid_hw_stop(hdev);
-	mutex_destroy(&hidpp->send_mutex);
 }
 
 #define LDJ_DEVICE(product) \
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH 18/18] HID: logitech-hidpp: manage ps_attribute_group with devm_device_add_group()
  2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
                   ` (16 preceding siblings ...)
  2026-11-09  6:11 ` [PATCH 17/18] HID: logitech-hidpp: manage send_mutex with devm_mutex_init() Dmitry Torokhov
@ 2026-11-09  6:11 ` Dmitry Torokhov
  17 siblings, 0 replies; 19+ messages in thread
From: Dmitry Torokhov @ 2026-11-09  6:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Gideon Heinz
  Cc: Filipe Laíns, Bastien Nocera, Hans de Goede, Edwin Velds,
	Stuart Hayhurst, Andrey Smirnov, linux-input, linux-kernel

Managing the power-supply sysfs attribute group manually requires
explicit cleanup in both probe failure paths and device removal, even
though all surrounding device state uses devres.

Switch to devm_device_add_group() while keeping synchronous creation
before connecting subdrivers and non-fatal warning behavior on failure.
Drop the manual removal calls, eliminate the empty start-failure unwind
label in probe, and simplify device removal accordingly.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/hid/hid-logitech-hidpp.c | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 7ff7b7d8e2d2..83a408f91d8b 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -4739,7 +4739,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 		return ret;
 
 	/* indicates we are handling the battery properties in the kernel */
-	ret = sysfs_create_group(&hdev->dev.kobj, &ps_attribute_group);
+	ret = devm_device_add_group(&hdev->dev, &ps_attribute_group);
 	if (ret)
 		hid_warn(hdev, "Cannot allocate sysfs group for %s\n",
 			 hdev->name);
@@ -4753,7 +4753,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	ret = hid_hw_start(hdev, 0);
 	if (ret) {
 		hid_err(hdev, "hw start failed\n");
-		goto hid_hw_start_fail;
+		return ret;
 	}
 
 	ret = hid_hw_open(hdev);
@@ -4811,8 +4811,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	hid_hw_close(hdev);
 hid_hw_open_fail:
 	hid_hw_stop(hdev);
-hid_hw_start_fail:
-	sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
 	return ret;
 }
 
@@ -4820,13 +4818,10 @@ static void hidpp_remove(struct hid_device *hdev)
 {
 	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
 
-	if (!hidpp)
-		return hid_hw_stop(hdev);
-
-	sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
-
-	disable_work_sync(&hidpp->work);
-	disable_work_sync(&hidpp->reset_hi_res_work);
+	if (hidpp) {
+		disable_work_sync(&hidpp->work);
+		disable_work_sync(&hidpp->reset_hi_res_work);
+	}
 
 	hid_hw_stop(hdev);
 }
-- 
2.56.0.506.g506a7eabc5-goog


^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2026-11-09  6:11 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-09  6:10 [PATCH 00/18] HID: logitech-hidpp: G920 force-feedback fixes, rework, and cleanups Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 01/18] HID: logitech-hidpp: fix G920 force-feedback initialization timeout Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 02/18] HID: logitech-hidpp: use GFP_ATOMIC in hidpp_ff_queue_work() Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 03/18] HID: logitech-hidpp: drain G920 FF workqueue before closing transport Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 04/18] HID: logitech-hidpp: disable works before stopping transport Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 05/18] HID: logitech-hidpp: do not store ERR_PTR in battery->ps Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 06/18] HID: logitech-hidpp: avoid NULL subtraction in consumer key report fixup Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 07/18] HID: logitech-hidpp: fix off-by-one in G920 force-feedback slot tracking Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 08/18] HID: logitech-hidpp: rework G920 force-feedback initialization Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 09/18] HID: logitech-hidpp: use sysfs_emit() in hidpp_ff_range_show() Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 10/18] HID: logitech-hidpp: manage G920 range sysfs attribute via dev_groups Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 11/18] HID: logitech-hidpp: shut down G920 force feedback on input unregister Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 12/18] HID: logitech-hidpp: use unaligned accessors for multi-byte values Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 13/18] HID: logitech-hidpp: store device serial as a byte array Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 14/18] HID: logitech-hidpp: use guard() for send_mutex Dmitry Torokhov
2026-11-09  6:10 ` [PATCH 15/18] HID: logitech-hidpp: use __free(kfree) for temporary allocations Dmitry Torokhov
2026-11-09  6:11 ` [PATCH 16/18] HID: logitech-hidpp: use alloc_ordered_workqueue() for FF queue Dmitry Torokhov
2026-11-09  6:11 ` [PATCH 17/18] HID: logitech-hidpp: manage send_mutex with devm_mutex_init() Dmitry Torokhov
2026-11-09  6:11 ` [PATCH 18/18] HID: logitech-hidpp: manage ps_attribute_group with devm_device_add_group() Dmitry Torokhov

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

all inboxes | Powered by JetHome®