* [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured()
@ 2026-08-03 18:46 Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 01/21] HID: core: automatically initialize generic FF if no other FF is present Dmitry Torokhov
` (20 more replies)
0 siblings, 21 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
When a HID driver calls hid_hw_start() with the HID_CONNECT_HIDINPUT
flag (included in HID_CONNECT_DEFAULT), the HID core immediately registers
the input device with the input subsystem, making it live and accessible to
userspace.
Historically, many HID drivers initialized force-feedback capabilities (via
input_ff_create_memless() or custom workqueues) in their probe() callback
after calling hid_hw_start(). This introduces a window where userspace can
open the input node and trigger force-feedback ioctls before the driver has
finished preparing its private structures or workqueues, leading to potential
NULL pointer dereferences and race conditions.
To eliminate this anti-pattern across the subsystem, this series:
- Enhances the HID core to automatically handle driver force-feedback
initialization during input device registration.
- Refactors individual HID drivers to perform all force-feedback setup
inside the .input_configured() callback, ensuring the input device is
fully prepared before it is exposed to userspace.
- Adds documentation and a Coccinelle script to prevent future regressions.
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
Dmitry Torokhov (21):
HID: core: automatically initialize generic FF if no other FF is present
HID: add documentation and Coccinelle script for FF registration race
HID: axff: move FF initialization to .input_configured()
HID: betop: move FF initialization to .input_configured()
HID: bigben: move FF initialization to .input_configured()
HID: dragonrise: move FF initialization to .input_configured()
HID: emsff: move FF initialization to .input_configured()
HID: gaff: move FF initialization to .input_configured()
HID: stadia: use open/close to manage workqueue lifecycle
HID: stadia: move FF initialization to .input_configured()
HID: holtek: move FF initialization to .input_configured()
HID: move generic FF initialization into hidinput_connect()
HID: microsoft: move FF initialization to .input_configured()
HID: pantherlord: move FF initialization to .input_configured()
HID: thrustmaster: move FF initialization to .input_configured()
HID: zeroplus: move FF initialization to .input_configured()
HID: mayflash: move FF initialization to .input_configured()
HID: smartjoyplus: move FF initialization to .input_configured()
HID: megaworld: move FF initialization to .input_configured()
HID: logitech-hidpp: move FF initialization to .input_configured()
HID: haptic: move FF initialization into .input_configured()
Documentation/hid/hidintro.rst | 50 ++++++++++++
drivers/hid/hid-axff.c | 40 +++-------
drivers/hid/hid-betopff.c | 33 +++-----
drivers/hid/hid-bigbenff.c | 89 ++++++++++-----------
drivers/hid/hid-core.c | 8 +-
drivers/hid/hid-dr.c | 66 ++++-----------
drivers/hid/hid-emsff.c | 50 ++----------
drivers/hid/hid-gaff.c | 53 +++----------
drivers/hid/hid-google-stadiaff.c | 112 ++++++++++----------------
drivers/hid/hid-haptic.c | 45 ++++-------
drivers/hid/hid-haptic.h | 6 +-
drivers/hid/hid-holtekff.c | 46 +++--------
drivers/hid/hid-input.c | 21 ++++-
drivers/hid/hid-logitech-hidpp.c | 36 +++++----
drivers/hid/hid-megaworld.c | 51 +++---------
drivers/hid/hid-mf.c | 77 +++++++-----------
drivers/hid/hid-microsoft.c | 38 ++-------
drivers/hid/hid-multitouch.c | 10 +--
drivers/hid/hid-pl.c | 150 +++++++++++++++--------------------
drivers/hid/hid-sjoy.c | 83 ++++++++-----------
drivers/hid/hid-tmff.c | 47 ++++-------
drivers/hid/hid-zpff.c | 43 ++--------
include/linux/hid.h | 2 +-
scripts/coccinelle/hid/ff_race.cocci | 34 ++++++++
24 files changed, 465 insertions(+), 725 deletions(-)
---
base-commit: 415606a7be939835db9b0d6b711887586646346d
change-id: 20260802-hid-ff-input-configured-397bc1503256
Thanks.
--
Dmitry
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 01/21] HID: core: automatically initialize generic FF if no other FF is present
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 02/21] HID: add documentation and Coccinelle script for FF registration race Dmitry Torokhov
` (19 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
Some HID drivers initialize their own force-feedback support within
their .input_configured() callback. In such cases, we should skip
the generic PID force-feedback initialization to avoid conflicts and
redundant setup.
Add hid_has_ff_input() helper and use it to check for existing FF
capabilities before calling hdev->ff_init().
Since we now have a dynamic way to detect if force-feedback is needed,
the HID_CONNECT_FF flag is redundant for conflict resolution and can
be ignored in the core initialization logic. Generic PID support will
now be attempted by default for any claimed input device that doesn't
already have FF capabilities.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-core.c | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index f107f5103b35..2767a171eae9 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2287,6 +2287,18 @@ static const BIN_ATTR_RO(report_descriptor, HID_MAX_DESCRIPTOR_SIZE);
static const DEVICE_ATTR_RO(country);
+static bool hid_has_ff_input(struct hid_device *hdev)
+{
+ struct hid_input *hidinput;
+
+ list_for_each_entry(hidinput, &hdev->inputs, list) {
+ if (test_bit(EV_FF, hidinput->input->evbit))
+ return true;
+ }
+
+ return false;
+}
+
int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
{
static const char *types[] = { "Device", "Pointer", "Mouse", "Device",
@@ -2336,7 +2348,8 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
hid_process_ordering(hdev);
if ((hdev->claimed & HID_CLAIMED_INPUT) &&
- (connect_mask & HID_CONNECT_FF) && hdev->ff_init)
+ (connect_mask & HID_CONNECT_FF) && hdev->ff_init &&
+ !hid_has_ff_input(hdev))
hdev->ff_init(hdev);
len = 0;
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 02/21] HID: add documentation and Coccinelle script for FF registration race
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 01/21] HID: core: automatically initialize generic FF if no other FF is present Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 03/21] HID: axff: move FF initialization to .input_configured() Dmitry Torokhov
` (18 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
HID drivers that rely on the HID core to register input devices must
ensure that all private data and capabilities (like force-feedback) are
fully initialized before registration.
When hid_hw_start() is called with HID_CONNECT_HIDINPUT, the input
device is registered immediately. This is racy if the driver attempts to
augment the input device in probe() after starting the hardware.
The correct way to handle this is to use the .input_configured()
callback.
Add documentation and a Coccinelle script to detect and prevent this
anti-pattern.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
Documentation/hid/hidintro.rst | 50 ++++++++++++++++++++++++++++++++++++
scripts/coccinelle/hid/ff_race.cocci | 34 ++++++++++++++++++++++++
2 files changed, 84 insertions(+)
diff --git a/Documentation/hid/hidintro.rst b/Documentation/hid/hidintro.rst
index 73523e315ebd..5d367dfca0b8 100644
--- a/Documentation/hid/hidintro.rst
+++ b/Documentation/hid/hidintro.rst
@@ -522,3 +522,53 @@ This should really be your last resort.
vendor: 0x093a
product: 0x2510
...
+
+Input Device Registration and Lifecycle
+========================================
+
+HID drivers that rely on the HID core to register input devices (by using the
+``HID_CONNECT_HIDINPUT`` flag, which is part of ``HID_CONNECT_DEFAULT``)
+must be aware of the registration timing.
+
+When ``hid_hw_start(hdev, flags)`` is called with ``HID_CONNECT_HIDINPUT``,
+the HID core immediately parses the report descriptor, allocates ``input_dev``
+structures, and calls ``input_register_device()`` for each of them.
+
+This means the input device becomes **live and visible to userspace** before
+``hid_hw_start()`` returns.
+
+If a driver needs to perform additional configuration on the input device (such
+as adding force-feedback support, setting extra bits in ``evbit``, or
+assigning custom event handlers), doing so in the ``probe`` function after
+``hid_hw_start()`` is **incorrect and racy**. Userspace may trigger
+callbacks (like ``play_effect``) via ioctls immediately after registration,
+leading to potential NULL pointer dereferences if the driver hasn't finished
+initializing its private data.
+
+The correct way to augment an input device before it is registered is to use the
+``.input_configured`` callback in ``struct hid_driver``. This hook is
+called by the HID core after the ``input_dev`` is fully formed but **before**
+``input_register_device()`` is invoked.
+
+Example:
+
+.. code-block:: c
+
+ static int my_input_configured(struct hid_device *hdev, struct hid_input *hidinput)
+ {
+ struct input_dev *input = hidinput->input;
+
+ /* Initialize private data and capabilities here */
+ set_bit(EV_FF, input->evbit);
+ return input_ff_create_memless(input, NULL, my_play_effect);
+ }
+
+ static struct hid_driver my_driver = {
+ .name = "my_driver",
+ .probe = my_probe,
+ .input_configured = my_input_configured,
+ };
+
+Drivers that require even more control over the lifecycle should mask out
+``HID_CONNECT_HIDINPUT`` and call ``input_register_device()`` manually
+when they are ready.
diff --git a/scripts/coccinelle/hid/ff_race.cocci b/scripts/coccinelle/hid/ff_race.cocci
new file mode 100644
index 000000000000..479f5d1e3184
--- /dev/null
+++ b/scripts/coccinelle/hid/ff_race.cocci
@@ -0,0 +1,34 @@
+/// Detect HID drivers that initialize force-feedback after hid_hw_start()
+/// when HID_CONNECT_HIDINPUT is used. This is a lifecycle violation as
+/// the input device is already registered.
+//
+// Confidence: High
+// Copyright: (C) 2026 Gemini. GPLv2.
+
+virtual report
+
+@r@
+identifier probe_fn;
+expression hdev, flags;
+position p1, p2;
+@@
+
+probe_fn(struct hid_device *hdev, ...) {
+ <...
+ hid_hw_start@p1(hdev, flags)
+ ...
+ \(input_ff_create\|input_ff_create_memless\)@p2(...)
+ ...>
+}
+
+@script:python depends on report@
+p1 << r.p1;
+p2 << r.p2;
+flags << r.flags;
+@@
+
+# Check if flags include HID_CONNECT_HIDINPUT (0x01) or HID_CONNECT_DEFAULT (0x0f)
+# Note: HID_CONNECT_DEFAULT is 0x0f, HID_CONNECT_HIDINPUT is 0x01
+if "HID_CONNECT_HIDINPUT" in flags or "HID_CONNECT_DEFAULT" in flags:
+ msg = "WARNING: force-feedback initialized after hid_hw_start() with HID_CONNECT_HIDINPUT. Input device is already registered at this point. Use .input_configured() instead."
+ coccilib.report.print_report(p2[0], msg)
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 03/21] HID: axff: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 01/21] HID: core: automatically initialize generic FF if no other FF is present Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 02/21] HID: add documentation and Coccinelle script for FF registration race Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 04/21] HID: betop: " Dmitry Torokhov
` (17 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start() with HID_CONNECT_DEFAULT. This is racy as
the input device is already registered and visible to userspace at that
point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-axff.c | 40 ++++++++++++----------------------------
1 file changed, 12 insertions(+), 28 deletions(-)
diff --git a/drivers/hid/hid-axff.c b/drivers/hid/hid-axff.c
index 3c5c2bf02425..2f46447086df 100644
--- a/drivers/hid/hid-axff.c
+++ b/drivers/hid/hid-axff.c
@@ -59,30 +59,24 @@ static int axff_play(struct input_dev *dev, void *data, struct ff_effect *effect
return 0;
}
-static int axff_init(struct hid_device *hid)
+static int ax_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct axff_device *axff;
struct hid_report *report;
- struct hid_input *hidinput;
- struct list_head *report_list =&hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct input_dev *dev;
+ struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+ struct input_dev *dev = hidinput->input;
int field_count = 0;
int i, j;
int error;
- if (list_empty(&hid->inputs)) {
- hid_err(hid, "no inputs found\n");
- return -ENODEV;
- }
- hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
- dev = hidinput->input;
+ if (!list_is_first(&hidinput->list, &hid->inputs))
+ return 0;
- if (list_empty(report_list)) {
+ report = list_first_entry_or_null(report_list, struct hid_report, list);
+ if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
-
- report = list_first_entry(report_list, struct hid_report, list);
for (i = 0; i < report->maxfield; i++) {
for (j = 0; j < report->field[i]->report_count; j++) {
report->field[i]->value[j] = 0x00;
@@ -100,13 +94,13 @@ static int axff_init(struct hid_device *hid)
if (!axff)
return -ENOMEM;
+ axff->report = report;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, axff, axff_play);
if (error)
goto err_free_mem;
- axff->report = report;
hid_hw_request(hid, axff->report, HID_REQ_SET_REPORT);
hid_info(hid, "Force Feedback for ACRUX game controllers by Sergei Kolzun <x0r@dv-life.ru>\n");
@@ -118,7 +112,8 @@ static int axff_init(struct hid_device *hid)
return error;
}
#else
-static inline int axff_init(struct hid_device *hid)
+static inline int ax_input_configured(struct hid_device *hid,
+ struct hid_input *hidinput)
{
return 0;
}
@@ -136,23 +131,11 @@ static int ax_probe(struct hid_device *hdev, const struct hid_device_id *id)
return error;
}
- error = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+ error = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (error) {
hid_err(hdev, "hw start failed\n");
return error;
}
-
- error = axff_init(hdev);
- if (error) {
- /*
- * Do not fail device initialization completely as device
- * may still be partially operable, just warn.
- */
- hid_warn(hdev,
- "Failed to enable force feedback support, error: %d\n",
- error);
- }
-
/*
* We need to start polling device right away, otherwise
* it will go into a coma.
@@ -185,6 +168,7 @@ static struct hid_driver ax_driver = {
.id_table = ax_devices,
.probe = ax_probe,
.remove = ax_remove,
+ .input_configured = ax_input_configured,
};
module_hid_driver(ax_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 04/21] HID: betop: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (2 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 03/21] HID: axff: move FF initialization to .input_configured() Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 05/21] HID: bigben: " Dmitry Torokhov
` (16 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start() with HID_CONNECT_DEFAULT. This is racy as
the input device is already registered and visible to userspace at that
point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-betopff.c | 33 +++++++++++----------------------
1 file changed, 11 insertions(+), 22 deletions(-)
diff --git a/drivers/hid/hid-betopff.c b/drivers/hid/hid-betopff.c
index 8a7fe895926c..f802046a688a 100644
--- a/drivers/hid/hid-betopff.c
+++ b/drivers/hid/hid-betopff.c
@@ -52,31 +52,24 @@ static int hid_betopff_play(struct input_dev *dev, void *data,
return 0;
}
-static int betopff_init(struct hid_device *hid)
+static int betop_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct betopff_device *betopff;
struct hid_report *report;
- struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct input_dev *dev;
+ struct input_dev *dev = hidinput->input;
int error;
int i, j;
- if (list_empty(&hid->inputs)) {
- hid_err(hid, "no inputs found\n");
- return -ENODEV;
- }
+ if (!list_is_first(&hidinput->list, &hid->inputs))
+ return 0;
- hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
- dev = hidinput->input;
-
- if (list_empty(report_list)) {
+ report = list_first_entry_or_null(report_list, struct hid_report, list);
+ if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
-
- report = list_first_entry(report_list, struct hid_report, list);
/*
* Actually there are 4 fields for 4 Bytes as below:
* -----------------------------------------
@@ -104,6 +97,7 @@ static int betopff_init(struct hid_device *hid)
if (!betopff)
return -ENOMEM;
+ betopff->report = report;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, betopff, hid_betopff_play);
@@ -112,7 +106,6 @@ static int betopff_init(struct hid_device *hid)
return error;
}
- betopff->report = report;
hid_hw_request(hid, betopff->report, HID_REQ_SET_REPORT);
hid_info(hid, "Force feedback for betop devices by huangbo <huangbobupt@163.com>\n");
@@ -130,20 +123,15 @@ static int betop_probe(struct hid_device *hdev, const struct hid_device_id *id)
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
- goto err;
+ return ret;
}
- ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
- goto err;
+ return ret;
}
-
- betopff_init(hdev);
-
return 0;
-err:
- return ret;
}
static const struct hid_device_id betop_devices[] = {
@@ -159,6 +147,7 @@ static struct hid_driver betop_driver = {
.name = "betop",
.id_table = betop_devices,
.probe = betop_probe,
+ .input_configured = betop_input_configured,
};
module_hid_driver(betop_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 05/21] HID: bigben: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (3 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 04/21] HID: betop: " Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 06/21] HID: dragonrise: " Dmitry Torokhov
` (15 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start() with HID_CONNECT_DEFAULT. This is racy as
the input device is already registered and visible to userspace at that
point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-bigbenff.c | 89 ++++++++++++++++++++++------------------------
1 file changed, 43 insertions(+), 46 deletions(-)
diff --git a/drivers/hid/hid-bigbenff.c b/drivers/hid/hid-bigbenff.c
index 9f05465358d9..3c87317ccc6b 100644
--- a/drivers/hid/hid-bigbenff.c
+++ b/drivers/hid/hid-bigbenff.c
@@ -366,58 +366,29 @@ static void bigben_remove(struct hid_device *hid)
hid_hw_stop(hid);
}
-static int bigben_probe(struct hid_device *hid,
- const struct hid_device_id *id)
+static int bigben_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
- struct bigben_device *bigben;
- struct hid_input *hidinput;
+ struct bigben_device *bigben = hid_get_drvdata(hid);
+ struct input_dev *input_dev = hidinput->input;
struct led_classdev *led;
char *name;
size_t name_sz;
int n, error;
- bigben = devm_kzalloc(&hid->dev, sizeof(*bigben), GFP_KERNEL);
- if (!bigben)
- return -ENOMEM;
- hid_set_drvdata(hid, bigben);
- bigben->hid = hid;
- bigben->removed = false;
-
- error = hid_parse(hid);
- if (error) {
- hid_err(hid, "parse failed\n");
- return error;
- }
-
- error = hid_hw_start(hid, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
- if (error) {
- hid_err(hid, "hw start failed\n");
- return error;
- }
+ if (!list_is_first(&hidinput->list, &hid->inputs))
+ return 0;
bigben->report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 8);
if (!bigben->report) {
hid_err(hid, "no output report found\n");
- error = -ENODEV;
- goto error_hw_stop;
- }
-
- if (list_empty(&hid->inputs)) {
- hid_err(hid, "no inputs found\n");
- error = -ENODEV;
- goto error_hw_stop;
+ return -ENODEV;
}
- hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
- set_bit(FF_RUMBLE, hidinput->input->ffbit);
-
- INIT_WORK(&bigben->worker, bigben_worker);
- spin_lock_init(&bigben->lock);
+ set_bit(FF_RUMBLE, input_dev->ffbit);
- error = input_ff_create_memless(hidinput->input, NULL,
- hid_bigben_play_effect);
+ error = input_ff_create_memless(input_dev, NULL, hid_bigben_play_effect);
if (error)
- goto error_hw_stop;
+ return error;
name_sz = strlen(dev_name(&hid->dev)) + strlen(":red:bigben#") + 1;
@@ -427,10 +398,9 @@ static int bigben_probe(struct hid_device *hid,
sizeof(struct led_classdev) + name_sz,
GFP_KERNEL
);
- if (!led) {
- error = -ENOMEM;
- goto error_hw_stop;
- }
+ if (!led)
+ return -ENOMEM;
+
name = (void *)(&led[1]);
snprintf(name, name_sz,
"%s:red:bigben%d",
@@ -444,7 +414,7 @@ static int bigben_probe(struct hid_device *hid,
bigben->leds[n] = led;
error = devm_led_classdev_register(&hid->dev, led);
if (error)
- goto error_hw_stop;
+ return error;
}
/* initial state: LED1 is on, no rumble effect */
@@ -458,10 +428,36 @@ static int bigben_probe(struct hid_device *hid,
hid_info(hid, "LED and force feedback support for BigBen gamepad\n");
return 0;
+}
-error_hw_stop:
- hid_hw_stop(hid);
- return error;
+static int bigben_probe(struct hid_device *hid, const struct hid_device_id *id)
+{
+ struct bigben_device *bigben;
+ int error;
+
+ bigben = devm_kzalloc(&hid->dev, sizeof(*bigben), GFP_KERNEL);
+ if (!bigben)
+ return -ENOMEM;
+
+ hid_set_drvdata(hid, bigben);
+ bigben->hid = hid;
+ bigben->removed = false;
+ INIT_WORK(&bigben->worker, bigben_worker);
+ spin_lock_init(&bigben->lock);
+
+ error = hid_parse(hid);
+ if (error) {
+ hid_err(hid, "parse failed\n");
+ return error;
+ }
+
+ error = hid_hw_start(hid, HID_CONNECT_DEFAULT);
+ if (error) {
+ hid_err(hid, "hw start failed\n");
+ return error;
+ }
+
+ return 0;
}
static const __u8 *bigben_report_fixup(struct hid_device *hid, __u8 *rdesc,
@@ -487,6 +483,7 @@ static struct hid_driver bigben_driver = {
.probe = bigben_probe,
.report_fixup = bigben_report_fixup,
.remove = bigben_remove,
+ .input_configured = bigben_input_configured,
};
module_hid_driver(bigben_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 06/21] HID: dragonrise: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (4 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 05/21] HID: bigben: " Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 07/21] HID: emsff: " Dmitry Torokhov
` (14 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start() with HID_CONNECT_DEFAULT. This is racy as
the input device is already registered and visible to userspace at that
point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-dr.c | 66 ++++++++++++----------------------------------------
1 file changed, 15 insertions(+), 51 deletions(-)
diff --git a/drivers/hid/hid-dr.c b/drivers/hid/hid-dr.c
index 8a8f68a7feb0..a1e10ee8df4d 100644
--- a/drivers/hid/hid-dr.c
+++ b/drivers/hid/hid-dr.c
@@ -71,29 +71,26 @@ static int drff_play(struct input_dev *dev, void *data,
return 0;
}
-static int drff_init(struct hid_device *hid)
+static int dr_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct drff_device *drff;
struct hid_report *report;
- struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct input_dev *dev;
+ struct input_dev *dev = hidinput->input;
int error;
- if (list_empty(&hid->inputs)) {
- hid_err(hid, "no inputs found\n");
- return -ENODEV;
- }
- hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
- dev = hidinput->input;
+ if (!list_is_first(&hidinput->list, &hid->inputs))
+ return 0;
+
+ if (hid->product != 0x0006)
+ return 0;
- if (list_empty(report_list)) {
+ report = list_first_entry_or_null(report_list, struct hid_report, list);
+ if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
-
- report = list_first_entry(report_list, struct hid_report, list);
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
@@ -108,6 +105,7 @@ static int drff_init(struct hid_device *hid)
if (!drff)
return -ENOMEM;
+ drff->report = report;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, drff, drff_play);
@@ -116,7 +114,6 @@ static int drff_init(struct hid_device *hid)
return error;
}
- drff->report = report;
drff->report->field[0]->value[0] = 0xf3;
drff->report->field[0]->value[1] = 0x00;
drff->report->field[0]->value[2] = 0x00;
@@ -132,7 +129,8 @@ static int drff_init(struct hid_device *hid)
return 0;
}
#else
-static inline int drff_init(struct hid_device *hid)
+static inline int dr_input_configured(struct hid_device *hid,
+ struct hid_input *hidinput)
{
return 0;
}
@@ -266,43 +264,9 @@ static int dr_input_mapping(struct hid_device *hdev, struct hid_input *hi,
return 0;
}
-static int dr_probe(struct hid_device *hdev, const struct hid_device_id *id)
-{
- int ret;
-
- dev_dbg(&hdev->dev, "DragonRise Inc. HID hardware probe...");
-
- ret = hid_parse(hdev);
- if (ret) {
- hid_err(hdev, "parse failed\n");
- goto err;
- }
-
- ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
- if (ret) {
- hid_err(hdev, "hw start failed\n");
- goto err;
- }
-
- switch (hdev->product) {
- case 0x0006:
- ret = drff_init(hdev);
- if (ret) {
- dev_err(&hdev->dev, "force feedback init failed\n");
- hid_hw_stop(hdev);
- goto err;
- }
- break;
- }
-
- return 0;
-err:
- return ret;
-}
-
static const struct hid_device_id dr_devices[] = {
- { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006), },
- { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011), },
+ { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006), },
+ { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011), },
{ }
};
MODULE_DEVICE_TABLE(hid, dr_devices);
@@ -311,8 +275,8 @@ static struct hid_driver dr_driver = {
.name = "dragonrise",
.id_table = dr_devices,
.report_fixup = dr_report_fixup,
- .probe = dr_probe,
.input_mapping = dr_input_mapping,
+ .input_configured = dr_input_configured,
};
module_hid_driver(dr_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 07/21] HID: emsff: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (5 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 06/21] HID: dragonrise: " Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 08/21] HID: gaff: " Dmitry Torokhov
` (13 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start() with HID_CONNECT_DEFAULT. This is racy as
the input device is already registered and visible to userspace at that
point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-emsff.c | 50 ++++++++-----------------------------------------
1 file changed, 8 insertions(+), 42 deletions(-)
diff --git a/drivers/hid/hid-emsff.c b/drivers/hid/hid-emsff.c
index 1b4ad18f6051..d8e559e0524f 100644
--- a/drivers/hid/hid-emsff.c
+++ b/drivers/hid/hid-emsff.c
@@ -43,29 +43,23 @@ static int emsff_play(struct input_dev *dev, void *data,
return 0;
}
-static int emsff_init(struct hid_device *hid)
+static int ems_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct emsff_device *emsff;
struct hid_report *report;
- struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct input_dev *dev;
+ struct input_dev *dev = hidinput->input;
int error;
- if (list_empty(&hid->inputs)) {
- hid_err(hid, "no inputs found\n");
- return -ENODEV;
- }
- hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
- dev = hidinput->input;
+ if (!list_is_first(&hidinput->list, &hid->inputs))
+ return 0;
- if (list_empty(report_list)) {
+ report = list_first_entry_or_null(report_list, struct hid_report, list);
+ if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
-
- report = list_first_entry(report_list, struct hid_report, list);
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
@@ -80,6 +74,7 @@ static int emsff_init(struct hid_device *hid)
if (!emsff)
return -ENOMEM;
+ emsff->report = report;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, emsff, emsff_play);
@@ -88,7 +83,6 @@ static int emsff_init(struct hid_device *hid)
return error;
}
- emsff->report = report;
emsff->report->field[0]->value[0] = 0x01;
emsff->report->field[0]->value[1] = 0x00;
emsff->report->field[0]->value[2] = 0x00;
@@ -103,34 +97,6 @@ static int emsff_init(struct hid_device *hid)
return 0;
}
-static int ems_probe(struct hid_device *hdev, const struct hid_device_id *id)
-{
- int ret;
-
- ret = hid_parse(hdev);
- if (ret) {
- hid_err(hdev, "parse failed\n");
- goto err;
- }
-
- ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
- if (ret) {
- hid_err(hdev, "hw start failed\n");
- goto err;
- }
-
- ret = emsff_init(hdev);
- if (ret) {
- dev_err(&hdev->dev, "force feedback init failed\n");
- hid_hw_stop(hdev);
- goto err;
- }
-
- return 0;
-err:
- return ret;
-}
-
static const struct hid_device_id ems_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II) },
{ }
@@ -140,7 +106,7 @@ MODULE_DEVICE_TABLE(hid, ems_devices);
static struct hid_driver ems_driver = {
.name = "hkems",
.id_table = ems_devices,
- .probe = ems_probe,
+ .input_configured = ems_input_configured,
};
module_hid_driver(ems_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 08/21] HID: gaff: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (6 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 07/21] HID: emsff: " Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 09/21] HID: stadia: use open/close to manage workqueue lifecycle Dmitry Torokhov
` (12 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start() with HID_CONNECT_DEFAULT. This is racy as
the input device is already registered and visible to userspace at that
point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-gaff.c | 53 ++++++++++----------------------------------------
1 file changed, 10 insertions(+), 43 deletions(-)
diff --git a/drivers/hid/hid-gaff.c b/drivers/hid/hid-gaff.c
index 8b99686b63df..ec793a179b47 100644
--- a/drivers/hid/hid-gaff.c
+++ b/drivers/hid/hid-gaff.c
@@ -60,32 +60,23 @@ static int hid_gaff_play(struct input_dev *dev, void *data,
return 0;
}
-static int gaff_init(struct hid_device *hid)
+static int gaff_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct gaff_device *gaff;
struct hid_report *report;
- struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct list_head *report_ptr = report_list;
- struct input_dev *dev;
+ struct input_dev *dev = hidinput->input;
int error;
- if (list_empty(&hid->inputs)) {
- hid_err(hid, "no inputs found\n");
- return -ENODEV;
- }
- hidinput = list_entry(hid->inputs.next, struct hid_input, list);
- dev = hidinput->input;
+ if (!list_is_first(&hidinput->list, &hid->inputs))
+ return 0;
- if (list_empty(report_list)) {
+ report = list_first_entry_or_null(report_list, struct hid_report, list);
+ if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
-
- report_ptr = report_ptr->next;
-
- report = list_entry(report_ptr, struct hid_report, list);
if (report->maxfield < 1) {
hid_err(hid, "no fields in the report\n");
return -ENODEV;
@@ -100,6 +91,7 @@ static int gaff_init(struct hid_device *hid)
if (!gaff)
return -ENOMEM;
+ gaff->report = report;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, gaff, hid_gaff_play);
@@ -108,7 +100,6 @@ static int gaff_init(struct hid_device *hid)
return error;
}
- gaff->report = report;
gaff->report->field[0]->value[0] = 0x51;
gaff->report->field[0]->value[1] = 0x00;
gaff->report->field[0]->value[2] = 0x00;
@@ -125,37 +116,13 @@ static int gaff_init(struct hid_device *hid)
return 0;
}
#else
-static inline int gaff_init(struct hid_device *hdev)
+static inline int gaff_input_configured(struct hid_device *hdev,
+ struct hid_input *hidinput)
{
return 0;
}
#endif
-static int ga_probe(struct hid_device *hdev, const struct hid_device_id *id)
-{
- int ret;
-
- dev_dbg(&hdev->dev, "Greenasia HID hardware probe...");
-
- ret = hid_parse(hdev);
- if (ret) {
- hid_err(hdev, "parse failed\n");
- goto err;
- }
-
- ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
- if (ret) {
- hid_err(hdev, "hw start failed\n");
- goto err;
- }
-
- gaff_init(hdev);
-
- return 0;
-err:
- return ret;
-}
-
static const struct hid_device_id ga_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, 0x0012), },
{ }
@@ -165,7 +132,7 @@ MODULE_DEVICE_TABLE(hid, ga_devices);
static struct hid_driver ga_driver = {
.name = "greenasia",
.id_table = ga_devices,
- .probe = ga_probe,
+ .input_configured = gaff_input_configured,
};
module_hid_driver(ga_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 09/21] HID: stadia: use open/close to manage workqueue lifecycle
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (7 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 08/21] HID: gaff: " Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 10/21] HID: stadia: move FF initialization to .input_configured() Dmitry Torokhov
` (11 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
Override input device open() and close() callbacks to enable and disable
the force-feedback workqueue item synchronously.
When the input device is opened by userspace, call hid_hw_open() and
enable_work(). When it is closed, disable_work_sync() ensures that any
pending or running work item is cancelled/flushed and no further work
items can be scheduled.
In close(), zero out magnitudes and issue a final report to turn off the
rumble motors on the physical controller before shutting down transport
I/O.
Pack strong and weak magnitudes into a single u32 integer using
WRITE_ONCE() and READ_ONCE() for atomic, lockless updates.
This allows eliminating the manual 'removed' boolean flag and spinlock
completely.
Assisted-by: Antigravity:gemini-3.6-flash
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-google-stadiaff.c | 71 +++++++++++++++++++++------------------
1 file changed, 38 insertions(+), 33 deletions(-)
diff --git a/drivers/hid/hid-google-stadiaff.c b/drivers/hid/hid-google-stadiaff.c
index 6b38d2421d3d..d6a73d210599 100644
--- a/drivers/hid/hid-google-stadiaff.c
+++ b/drivers/hid/hid-google-stadiaff.c
@@ -17,10 +17,7 @@
struct stadiaff_device {
struct hid_device *hid;
struct hid_report *report;
- spinlock_t lock;
- bool removed;
- uint16_t strong_magnitude;
- uint16_t weak_magnitude;
+ u32 magnitudes;
struct work_struct work;
};
@@ -29,12 +26,10 @@ static void stadiaff_work(struct work_struct *work)
struct stadiaff_device *stadiaff =
container_of(work, struct stadiaff_device, work);
struct hid_field *rumble_field = stadiaff->report->field[0];
- unsigned long flags;
+ u32 mags = READ_ONCE(stadiaff->magnitudes);
- spin_lock_irqsave(&stadiaff->lock, flags);
- rumble_field->value[0] = stadiaff->strong_magnitude;
- rumble_field->value[1] = stadiaff->weak_magnitude;
- spin_unlock_irqrestore(&stadiaff->lock, flags);
+ rumble_field->value[0] = mags & 0xffff;
+ rumble_field->value[1] = (mags >> 16) & 0xffff;
hid_hw_request(stadiaff->hid, stadiaff->report, HID_REQ_SET_REPORT);
}
@@ -44,19 +39,41 @@ static int stadiaff_play(struct input_dev *dev, void *data,
{
struct hid_device *hid = input_get_drvdata(dev);
struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
- unsigned long flags;
+ u32 mags = (u32)effect->u.rumble.strong_magnitude |
+ ((u32)effect->u.rumble.weak_magnitude << 16);
- spin_lock_irqsave(&stadiaff->lock, flags);
- if (!stadiaff->removed) {
- stadiaff->strong_magnitude = effect->u.rumble.strong_magnitude;
- stadiaff->weak_magnitude = effect->u.rumble.weak_magnitude;
- schedule_work(&stadiaff->work);
- }
- spin_unlock_irqrestore(&stadiaff->lock, flags);
+ WRITE_ONCE(stadiaff->magnitudes, mags);
+ schedule_work(&stadiaff->work);
return 0;
}
+static int stadia_input_open(struct input_dev *dev)
+{
+ struct hid_device *hid = input_get_drvdata(dev);
+ struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
+ int error;
+
+ error = hid_hw_open(hid);
+ if (error)
+ return error;
+
+ enable_work(&stadiaff->work);
+ return 0;
+}
+
+static void stadia_input_close(struct input_dev *dev)
+{
+ struct hid_device *hid = input_get_drvdata(dev);
+ struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
+
+ WRITE_ONCE(stadiaff->magnitudes, 0);
+ stadiaff_work(&stadiaff->work);
+ disable_work_sync(&stadiaff->work);
+
+ hid_hw_close(hid);
+}
+
static int stadiaff_init(struct hid_device *hid)
{
struct stadiaff_device *stadiaff;
@@ -90,11 +107,13 @@ static int stadiaff_init(struct hid_device *hid)
if (error)
return error;
- stadiaff->removed = false;
stadiaff->hid = hid;
stadiaff->report = report;
INIT_WORK(&stadiaff->work, stadiaff_work);
- spin_lock_init(&stadiaff->lock);
+ disable_work_sync(&stadiaff->work);
+
+ dev->open = stadia_input_open;
+ dev->close = stadia_input_close;
hid_info(hid, "Force Feedback for Google Stadia controller\n");
@@ -127,19 +146,6 @@ static int stadia_probe(struct hid_device *hdev, const struct hid_device_id *id)
return 0;
}
-static void stadia_remove(struct hid_device *hid)
-{
- struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
- unsigned long flags;
-
- spin_lock_irqsave(&stadiaff->lock, flags);
- stadiaff->removed = true;
- spin_unlock_irqrestore(&stadiaff->lock, flags);
-
- cancel_work_sync(&stadiaff->work);
- hid_hw_stop(hid);
-}
-
static const struct hid_device_id stadia_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) },
@@ -151,7 +157,6 @@ static struct hid_driver stadia_driver = {
.name = "stadia",
.id_table = stadia_devices,
.probe = stadia_probe,
- .remove = stadia_remove,
};
module_hid_driver(stadia_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 10/21] HID: stadia: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (8 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 09/21] HID: stadia: use open/close to manage workqueue lifecycle Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 11/21] HID: holtek: " Dmitry Torokhov
` (10 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start() with HID_CONNECT_DEFAULT. This is racy as
the input device is already registered and visible to userspace at that
point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-google-stadiaff.c | 41 +++++----------------------------------
1 file changed, 5 insertions(+), 36 deletions(-)
diff --git a/drivers/hid/hid-google-stadiaff.c b/drivers/hid/hid-google-stadiaff.c
index d6a73d210599..0214aae6b0fa 100644
--- a/drivers/hid/hid-google-stadiaff.c
+++ b/drivers/hid/hid-google-stadiaff.c
@@ -74,20 +74,15 @@ static void stadia_input_close(struct input_dev *dev)
hid_hw_close(hid);
}
-static int stadiaff_init(struct hid_device *hid)
+static int stadia_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct stadiaff_device *stadiaff;
struct hid_report *report;
- struct hid_input *hidinput;
- struct input_dev *dev;
+ struct input_dev *dev = hidinput->input;
int error;
- if (list_empty(&hid->inputs)) {
- hid_err(hid, "no inputs found\n");
- return -ENODEV;
- }
- hidinput = list_entry(hid->inputs.next, struct hid_input, list);
- dev = hidinput->input;
+ if (!list_is_first(&hidinput->list, &hid->inputs))
+ return 0;
report = hid_validate_values(hid, HID_OUTPUT_REPORT,
STADIA_FF_REPORT_ID, 0, 2);
@@ -120,32 +115,6 @@ static int stadiaff_init(struct hid_device *hid)
return 0;
}
-static int stadia_probe(struct hid_device *hdev, const struct hid_device_id *id)
-{
- int ret;
-
- ret = hid_parse(hdev);
- if (ret) {
- hid_err(hdev, "parse failed\n");
- return ret;
- }
-
- ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
- if (ret) {
- hid_err(hdev, "hw start failed\n");
- return ret;
- }
-
- ret = stadiaff_init(hdev);
- if (ret) {
- hid_err(hdev, "force feedback init failed\n");
- hid_hw_stop(hdev);
- return ret;
- }
-
- return 0;
-}
-
static const struct hid_device_id stadia_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) },
@@ -156,7 +125,7 @@ MODULE_DEVICE_TABLE(hid, stadia_devices);
static struct hid_driver stadia_driver = {
.name = "stadia",
.id_table = stadia_devices,
- .probe = stadia_probe,
+ .input_configured = stadia_input_configured,
};
module_hid_driver(stadia_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 11/21] HID: holtek: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (9 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 10/21] HID: stadia: move FF initialization to .input_configured() Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 12/21] HID: move generic FF initialization into hidinput_connect() Dmitry Torokhov
` (9 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start() with HID_CONNECT_DEFAULT. This is racy as
the input device is already registered and visible to userspace at that
point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-holtekff.c | 46 +++++++++-------------------------------------
1 file changed, 9 insertions(+), 37 deletions(-)
diff --git a/drivers/hid/hid-holtekff.c b/drivers/hid/hid-holtekff.c
index 32d08f7a660d..4834d42b2fa6 100644
--- a/drivers/hid/hid-holtekff.c
+++ b/drivers/hid/hid-holtekff.c
@@ -120,30 +120,24 @@ static int holtekff_play(struct input_dev *dev, void *data,
return 0;
}
-static int holtekff_init(struct hid_device *hid)
+static int holtek_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct holtekff_device *holtekff;
struct hid_report *report;
- struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct input_dev *dev;
+ struct input_dev *dev = hidinput->input;
int error;
- if (list_empty(&hid->inputs)) {
- hid_err(hid, "no inputs found\n");
- return -ENODEV;
- }
- hidinput = list_entry(hid->inputs.next, struct hid_input, list);
- dev = hidinput->input;
+ if (!list_is_first(&hidinput->list, &hid->inputs))
+ return 0;
- if (list_empty(report_list)) {
+ report = list_first_entry_or_null(report_list, struct hid_report, list);
+ if (!report) {
hid_err(hid, "no output report found\n");
return -ENODEV;
}
- report = list_entry(report_list->next, struct hid_report, list);
-
if (report->maxfield < 1 || report->field[0]->report_count != 7) {
hid_err(hid, "unexpected output report layout\n");
return -ENODEV;
@@ -172,35 +166,13 @@ static int holtekff_init(struct hid_device *hid)
return 0;
}
#else
-static inline int holtekff_init(struct hid_device *hid)
+static inline int holtek_input_configured(struct hid_device *hid,
+ struct hid_input *hidinput)
{
return 0;
}
#endif
-static int holtek_probe(struct hid_device *hdev, const struct hid_device_id *id)
-{
- int ret;
-
- ret = hid_parse(hdev);
- if (ret) {
- hid_err(hdev, "parse failed\n");
- goto err;
- }
-
- ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
- if (ret) {
- hid_err(hdev, "hw start failed\n");
- goto err;
- }
-
- holtekff_init(hdev);
-
- return 0;
-err:
- return ret;
-}
-
static const struct hid_device_id holtek_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) },
{ }
@@ -210,7 +182,7 @@ MODULE_DEVICE_TABLE(hid, holtek_devices);
static struct hid_driver holtek_driver = {
.name = "holtek",
.id_table = holtek_devices,
- .probe = holtek_probe,
+ .input_configured = holtek_input_configured,
};
module_hid_driver(holtek_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 12/21] HID: move generic FF initialization into hidinput_connect()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (10 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 11/21] HID: holtek: " Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 13/21] HID: microsoft: move FF initialization to .input_configured() Dmitry Torokhov
` (8 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
Generic force-feedback initialization (pidff) currently happens in
hid_connect() after hidinput_connect() has already registered the input
devices. This is racy as the device is live and visible to userspace
before FF support is fully set up.
Move the call to hdev->ff_init() into hidinput_connect(), ensuring it
runs before input_register_device() is called. This closes the race
window for standard PID-capable devices.
The initialization now also checks (connect_mask & HID_CONNECT_FF) and
!hid_has_ff_input() to avoid conflicts with custom FF implementations
and respect driver opt-outs.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-core.c | 21 ++-------------------
drivers/hid/hid-input.c | 21 +++++++++++++++++++--
include/linux/hid.h | 2 +-
3 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 2767a171eae9..d8cba852d2b5 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2287,18 +2287,6 @@ static const BIN_ATTR_RO(report_descriptor, HID_MAX_DESCRIPTOR_SIZE);
static const DEVICE_ATTR_RO(country);
-static bool hid_has_ff_input(struct hid_device *hdev)
-{
- struct hid_input *hidinput;
-
- list_for_each_entry(hidinput, &hdev->inputs, list) {
- if (test_bit(EV_FF, hidinput->input->evbit))
- return true;
- }
-
- return false;
-}
-
int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
{
static const char *types[] = { "Device", "Pointer", "Mouse", "Device",
@@ -2324,8 +2312,8 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
if (hid_hiddev(hdev))
connect_mask |= HID_CONNECT_HIDDEV_FORCE;
- if ((connect_mask & HID_CONNECT_HIDINPUT) && !hidinput_connect(hdev,
- connect_mask & HID_CONNECT_HIDINPUT_FORCE))
+ if ((connect_mask & HID_CONNECT_HIDINPUT) &&
+ !hidinput_connect(hdev, connect_mask))
hdev->claimed |= HID_CLAIMED_INPUT;
if ((connect_mask & HID_CONNECT_HIDDEV) && hdev->hiddev_connect &&
@@ -2347,11 +2335,6 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
hid_process_ordering(hdev);
- if ((hdev->claimed & HID_CLAIMED_INPUT) &&
- (connect_mask & HID_CONNECT_FF) && hdev->ff_init &&
- !hid_has_ff_input(hdev))
- hdev->ff_init(hdev);
-
len = 0;
if (hdev->claimed & HID_CLAIMED_INPUT)
len += sprintf(buf + len, "input");
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index 3487600cadb4..70ec1b7e7d38 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -2317,7 +2317,19 @@ static inline void hidinput_configure_usages(struct hid_input *hidinput,
* Read all reports and initialize the absolute field values.
*/
-int hidinput_connect(struct hid_device *hid, unsigned int force)
+static bool hid_has_ff_input(struct hid_device *hdev)
+{
+ struct hid_input *hidinput;
+
+ list_for_each_entry(hidinput, &hdev->inputs, list) {
+ if (test_bit(EV_FF, hidinput->input->evbit))
+ return true;
+ }
+
+ return false;
+}
+
+int hidinput_connect(struct hid_device *hid, unsigned int connect_mask)
{
struct hid_driver *drv = hid->driver;
struct hid_report *report;
@@ -2330,7 +2342,7 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
hid->status &= ~HID_STAT_DUP_DETECTED;
- if (!force) {
+ if (!(connect_mask & HID_CONNECT_HIDINPUT_FORCE)) {
for (i = 0; i < hid->maxcollection; i++) {
struct hid_collection *col = &hid->collection[i];
if (col->type == HID_COLLECTION_APPLICATION ||
@@ -2396,6 +2408,11 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
continue;
}
+ if (list_is_first(&hidinput->list, &hid->inputs) &&
+ (connect_mask & HID_CONNECT_FF) && hid->ff_init &&
+ !hid_has_ff_input(hid))
+ hid->ff_init(hid);
+
if (input_register_device(hidinput->input))
goto out_unwind;
hidinput->registered = true;
diff --git a/include/linux/hid.h b/include/linux/hid.h
index b240baa95ab5..451c3e05d167 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -1021,7 +1021,7 @@ extern void hid_unregister_driver(struct hid_driver *);
extern void hidinput_hid_event(struct hid_device *, struct hid_field *, struct hid_usage *, __s32);
extern void hidinput_report_event(struct hid_device *hid, struct hid_report *report);
-extern int hidinput_connect(struct hid_device *hid, unsigned int force);
+extern int hidinput_connect(struct hid_device *hid, unsigned int connect_mask);
extern void hidinput_disconnect(struct hid_device *);
void hidinput_reset_resume(struct hid_device *hid);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 13/21] HID: microsoft: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (11 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 12/21] HID: move generic FF initialization into hidinput_connect() Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 14/21] HID: pantherlord: " Dmitry Torokhov
` (7 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start(). This is racy as the input device is
already registered and visible to userspace at that point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-microsoft.c | 38 ++++++++------------------------------
1 file changed, 8 insertions(+), 30 deletions(-)
diff --git a/drivers/hid/hid-microsoft.c b/drivers/hid/hid-microsoft.c
index 18ac21c0bcb2..a7d3493a6141 100644
--- a/drivers/hid/hid-microsoft.c
+++ b/drivers/hid/hid-microsoft.c
@@ -323,22 +323,17 @@ static int ms_play_effect(struct input_dev *dev, void *data,
return 0;
}
-static int ms_init_ff(struct hid_device *hdev)
+static int ms_input_configured(struct hid_device *hdev, struct hid_input *hidinput)
{
- struct hid_input *hidinput;
- struct input_dev *input_dev;
struct ms_data *ms = hid_get_drvdata(hdev);
-
- if (list_empty(&hdev->inputs)) {
- hid_err(hdev, "no inputs found\n");
- return -ENODEV;
- }
- hidinput = list_entry(hdev->inputs.next, struct hid_input, list);
- input_dev = hidinput->input;
+ struct input_dev *input_dev = hidinput->input;
if (!(ms->quirks & MS_QUIRK_FF))
return 0;
+ if (!list_is_first(&hidinput->list, &hdev->inputs))
+ return 0;
+
ms->hdev = hdev;
INIT_WORK(&ms->ff_worker, ms_ff_worker);
@@ -352,16 +347,6 @@ static int ms_init_ff(struct hid_device *hdev)
return input_ff_create_memless(input_dev, NULL, ms_play_effect);
}
-static void ms_remove_ff(struct hid_device *hdev)
-{
- struct ms_data *ms = hid_get_drvdata(hdev);
-
- if (!(ms->quirks & MS_QUIRK_FF))
- return;
-
- cancel_work_sync(&ms->ff_worker);
-}
-
static int ms_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
unsigned long quirks = id->driver_data;
@@ -385,29 +370,21 @@ static int ms_probe(struct hid_device *hdev, const struct hid_device_id *id)
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
- goto err_free;
+ return ret;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT | ((quirks & MS_HIDINPUT) ?
HID_CONNECT_HIDINPUT_FORCE : 0));
if (ret) {
hid_err(hdev, "hw start failed\n");
- goto err_free;
+ return ret;
}
- ret = ms_init_ff(hdev);
- if (ret)
- hid_err(hdev, "could not initialize ff, continuing anyway");
-
return 0;
-err_free:
- return ret;
}
-
static void ms_remove(struct hid_device *hdev)
{
hid_hw_stop(hdev);
- ms_remove_ff(hdev);
}
static const struct hid_device_id ms_devices[] = {
@@ -469,6 +446,7 @@ static struct hid_driver ms_driver = {
.report_fixup = ms_report_fixup,
.input_mapping = ms_input_mapping,
.input_mapped = ms_input_mapped,
+ .input_configured = ms_input_configured,
.event = ms_event,
.probe = ms_probe,
.remove = ms_remove,
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 14/21] HID: pantherlord: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (12 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 13/21] HID: microsoft: move FF initialization to .input_configured() Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 15/21] HID: thrustmaster: " Dmitry Torokhov
` (6 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start(). This is racy as the input device is
already registered and visible to userspace at that point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-pl.c | 150 ++++++++++++++++++++++-----------------------------
1 file changed, 65 insertions(+), 85 deletions(-)
diff --git a/drivers/hid/hid-pl.c b/drivers/hid/hid-pl.c
index 8bba29ef6c7a..cae56a6c941b 100644
--- a/drivers/hid/hid-pl.c
+++ b/drivers/hid/hid-pl.c
@@ -62,15 +62,13 @@ static int hid_plff_play(struct input_dev *dev, void *data,
return 0;
}
-static int plff_init(struct hid_device *hid)
+static int pl_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct plff_device *plff;
struct hid_report *report;
- struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct list_head *report_ptr = report_list;
- struct input_dev *dev;
+ struct input_dev *dev = hidinput->input;
int error;
s32 maxval;
s32 *strong;
@@ -83,89 +81,80 @@ static int plff_init(struct hid_device *hid)
The input reports also contain a field which contains
8 ff00.0001 usages and 8 boolean values. Their meaning is
currently unknown.
-
+
A version of the 0e8f:0003 exists that has all the values in
separate fields and misses the extra input field, thus resembling
Zeroplus (hid-zpff) devices.
*/
- if (list_empty(report_list)) {
+ if (!list_is_first(&hidinput->list, &hid->inputs))
+ return 0;
+
+ report = list_first_entry_or_null(report_list, struct hid_report, list);
+ if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
+ if (report->maxfield < 1) {
+ hid_err(hid, "no fields in the report\n");
+ return -ENODEV;
+ }
- list_for_each_entry(hidinput, &hid->inputs, list) {
-
- report_ptr = report_ptr->next;
-
- if (report_ptr == report_list) {
- hid_err(hid, "required output report is missing\n");
- return -ENODEV;
- }
-
- report = list_entry(report_ptr, struct hid_report, list);
- if (report->maxfield < 1) {
- hid_err(hid, "no fields in the report\n");
- return -ENODEV;
- }
-
- maxval = 0x7f;
- if (report->field[0]->report_count >= 4) {
- report->field[0]->value[0] = 0x00;
- report->field[0]->value[1] = 0x00;
- strong = &report->field[0]->value[2];
- weak = &report->field[0]->value[3];
- hid_dbg(hid, "detected single-field device");
- } else if (report->field[0]->maxusage == 1 &&
- report->field[0]->usage[0].hid ==
- (HID_UP_LED | 0x43) &&
- report->maxfield >= 4 &&
- report->field[0]->report_count >= 1 &&
- report->field[1]->report_count >= 1 &&
- report->field[2]->report_count >= 1 &&
- report->field[3]->report_count >= 1) {
- report->field[0]->value[0] = 0x00;
- report->field[1]->value[0] = 0x00;
- strong = &report->field[2]->value[0];
- weak = &report->field[3]->value[0];
- if (hid->vendor == USB_VENDOR_ID_JESS2)
- maxval = 0xff;
- hid_dbg(hid, "detected 4-field device");
- } else {
- hid_err(hid, "not enough fields or values\n");
- return -ENODEV;
- }
-
- plff = kzalloc_obj(struct plff_device);
- if (!plff)
- return -ENOMEM;
-
- dev = hidinput->input;
-
- set_bit(FF_RUMBLE, dev->ffbit);
-
- error = input_ff_create_memless(dev, plff, hid_plff_play);
- if (error) {
- kfree(plff);
- return error;
- }
-
- plff->report = report;
- plff->strong = strong;
- plff->weak = weak;
- plff->maxval = maxval;
-
- *strong = 0x00;
- *weak = 0x00;
- hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
+ maxval = 0x7f;
+ if (report->field[0]->report_count >= 4) {
+ report->field[0]->value[0] = 0x00;
+ report->field[0]->value[1] = 0x00;
+ strong = &report->field[0]->value[2];
+ weak = &report->field[0]->value[3];
+ hid_dbg(hid, "detected single-field device");
+ } else if (report->field[0]->maxusage == 1 &&
+ report->field[0]->usage[0].hid ==
+ (HID_UP_LED | 0x43) &&
+ report->maxfield >= 4 &&
+ report->field[0]->report_count >= 1 &&
+ report->field[1]->report_count >= 1 &&
+ report->field[2]->report_count >= 1 &&
+ report->field[3]->report_count >= 1) {
+ report->field[0]->value[0] = 0x00;
+ report->field[1]->value[0] = 0x00;
+ strong = &report->field[2]->value[0];
+ weak = &report->field[3]->value[0];
+ if (hid->vendor == USB_VENDOR_ID_JESS2)
+ maxval = 0xff;
+ hid_dbg(hid, "detected 4-field device");
+ } else {
+ hid_err(hid, "not enough fields or values\n");
+ return -ENODEV;
}
- hid_info(hid, "Force feedback for PantherLord/GreenAsia devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
+ plff = kzalloc_obj(struct plff_device);
+ if (!plff)
+ return -ENOMEM;
+
+ dev = hidinput->input;
+
+ set_bit(FF_RUMBLE, dev->ffbit);
+
+ error = input_ff_create_memless(dev, plff, hid_plff_play);
+ if (error) {
+ kfree(plff);
+ return error;
+ }
+
+ plff->report = report;
+ plff->strong = strong;
+ plff->weak = weak;
+ plff->maxval = maxval;
+
+ *strong = 0x00;
+ *weak = 0x00;
+ hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
return 0;
}
#else
-static inline int plff_init(struct hid_device *hid)
+static inline int pl_input_configured(struct hid_device *hid,
+ struct hid_input *hidinput)
{
return 0;
}
@@ -181,27 +170,17 @@ static int pl_probe(struct hid_device *hdev, const struct hid_device_id *id)
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
- goto err;
+ return ret;
}
- ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
- goto err;
+ return ret;
}
- ret = plff_init(hdev);
- if (ret)
- goto stop;
-
return 0;
-
-stop:
- hid_hw_stop(hdev);
-err:
- return ret;
}
-
static const struct hid_device_id pl_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR),
.driver_data = 1 }, /* Twin USB Joystick */
@@ -217,6 +196,7 @@ static struct hid_driver pl_driver = {
.name = "pantherlord",
.id_table = pl_devices,
.probe = pl_probe,
+ .input_configured = pl_input_configured,
};
module_hid_driver(pl_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 15/21] HID: thrustmaster: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (13 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 14/21] HID: pantherlord: " Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 16/21] HID: zeroplus: " Dmitry Torokhov
` (5 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start(). This is racy as the input device is
already registered and visible to userspace at that point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-tmff.c | 47 ++++++++++++++---------------------------------
1 file changed, 14 insertions(+), 33 deletions(-)
diff --git a/drivers/hid/hid-tmff.c b/drivers/hid/hid-tmff.c
index 423f395d01ac..0ed152e0dba9 100644
--- a/drivers/hid/hid-tmff.c
+++ b/drivers/hid/hid-tmff.c
@@ -115,22 +115,25 @@ static int tmff_play(struct input_dev *dev, void *data,
return 0;
}
-static int tmff_init(struct hid_device *hid, const signed short *ff_bits)
+static int tm_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct tmff_device *tmff;
struct hid_report *report;
struct list_head *report_list;
- struct hid_input *hidinput;
- struct input_dev *input_dev;
+ struct input_dev *input_dev = hidinput->input;
+ const struct hid_device_id *id;
+ const signed short *ff_bits;
int error;
int i;
- if (list_empty(&hid->inputs)) {
- hid_err(hid, "no inputs found\n");
+ if (!list_is_first(&hidinput->list, &hid->inputs))
+ return 0;
+
+ id = hid_match_device(hid, hid->driver);
+ if (!id)
return -ENODEV;
- }
- hidinput = list_entry(hid->inputs.next, struct hid_input, list);
- input_dev = hidinput->input;
+
+ ff_bits = (void *)id->driver_data;
tmff = kzalloc_obj(struct tmff_device);
if (!tmff)
@@ -204,35 +207,13 @@ static int tmff_init(struct hid_device *hid, const signed short *ff_bits)
return error;
}
#else
-static inline int tmff_init(struct hid_device *hid, const signed short *ff_bits)
+static inline int tm_input_configured(struct hid_device *hid,
+ struct hid_input *hidinput)
{
return 0;
}
#endif
-static int tm_probe(struct hid_device *hdev, const struct hid_device_id *id)
-{
- int ret;
-
- ret = hid_parse(hdev);
- if (ret) {
- hid_err(hdev, "parse failed\n");
- goto err;
- }
-
- ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
- if (ret) {
- hid_err(hdev, "hw start failed\n");
- goto err;
- }
-
- tmff_init(hdev, (void *)id->driver_data);
-
- return 0;
-err:
- return ret;
-}
-
static const struct hid_device_id tm_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300),
.driver_data = (unsigned long)ff_rumble },
@@ -261,7 +242,7 @@ MODULE_DEVICE_TABLE(hid, tm_devices);
static struct hid_driver tm_driver = {
.name = "thrustmaster",
.id_table = tm_devices,
- .probe = tm_probe,
+ .input_configured = tm_input_configured,
};
module_hid_driver(tm_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 16/21] HID: zeroplus: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (14 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 15/21] HID: thrustmaster: " Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 17/21] HID: mayflash: " Dmitry Torokhov
` (4 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start(). This is racy as the input device is
already registered and visible to userspace at that point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-zpff.c | 43 ++++++++-----------------------------------
1 file changed, 8 insertions(+), 35 deletions(-)
diff --git a/drivers/hid/hid-zpff.c b/drivers/hid/hid-zpff.c
index d8e023c8aa84..b565c59d3dfe 100644
--- a/drivers/hid/hid-zpff.c
+++ b/drivers/hid/hid-zpff.c
@@ -50,20 +50,15 @@ static int zpff_play(struct input_dev *dev, void *data,
return 0;
}
-static int zpff_init(struct hid_device *hid)
+static int zp_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct zpff_device *zpff;
struct hid_report *report;
- struct hid_input *hidinput;
- struct input_dev *dev;
+ struct input_dev *dev = hidinput->input;
int i, error;
- if (list_empty(&hid->inputs)) {
- hid_err(hid, "no inputs found\n");
- return -ENODEV;
- }
- hidinput = list_entry(hid->inputs.next, struct hid_input, list);
- dev = hidinput->input;
+ if (!list_is_first(&hidinput->list, &hid->inputs))
+ return 0;
for (i = 0; i < 4; i++) {
report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, i, 1);
@@ -75,6 +70,7 @@ static int zpff_init(struct hid_device *hid)
if (!zpff)
return -ENOMEM;
+ zpff->report = report;
set_bit(FF_RUMBLE, dev->ffbit);
error = input_ff_create_memless(dev, zpff, zpff_play);
@@ -83,7 +79,6 @@ static int zpff_init(struct hid_device *hid)
return error;
}
- zpff->report = report;
zpff->report->field[0]->value[0] = 0x00;
zpff->report->field[1]->value[0] = 0x02;
zpff->report->field[2]->value[0] = 0x00;
@@ -95,35 +90,13 @@ static int zpff_init(struct hid_device *hid)
return 0;
}
#else
-static inline int zpff_init(struct hid_device *hid)
+static inline int zp_input_configured(struct hid_device *hid,
+ struct hid_input *hidinput)
{
return 0;
}
#endif
-static int zp_probe(struct hid_device *hdev, const struct hid_device_id *id)
-{
- int ret;
-
- ret = hid_parse(hdev);
- if (ret) {
- hid_err(hdev, "parse failed\n");
- goto err;
- }
-
- ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
- if (ret) {
- hid_err(hdev, "hw start failed\n");
- goto err;
- }
-
- zpff_init(hdev);
-
- return 0;
-err:
- return ret;
-}
-
static const struct hid_device_id zp_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0005) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0030) },
@@ -134,7 +107,7 @@ MODULE_DEVICE_TABLE(hid, zp_devices);
static struct hid_driver zp_driver = {
.name = "zeroplus",
.id_table = zp_devices,
- .probe = zp_probe,
+ .input_configured = zp_input_configured,
};
module_hid_driver(zp_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 17/21] HID: mayflash: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (15 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 16/21] HID: zeroplus: " Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 18/21] HID: smartjoyplus: " Dmitry Torokhov
` (3 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start(). This is racy as the input device is
already registered and visible to userspace at that point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-mf.c | 77 ++++++++++++++++++----------------------------------
1 file changed, 27 insertions(+), 50 deletions(-)
diff --git a/drivers/hid/hid-mf.c b/drivers/hid/hid-mf.c
index 6ff54a1ec697..136e8b41d5f4 100644
--- a/drivers/hid/hid-mf.c
+++ b/drivers/hid/hid-mf.c
@@ -54,61 +54,45 @@ static int mf_play(struct input_dev *dev, void *data, struct ff_effect *effect)
return 0;
}
-static int mf_init(struct hid_device *hid)
+static int mf_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct mf_device *mf;
-
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
-
- struct list_head *report_ptr;
struct hid_report *report;
-
- struct list_head *input_ptr = &hid->inputs;
- struct hid_input *input;
-
- struct input_dev *dev;
-
+ struct input_dev *dev = hidinput->input;
int error;
- /* Setup each of the four inputs */
- list_for_each(report_ptr, report_list) {
- report = list_entry(report_ptr, struct hid_report, list);
+ if (!list_is_first(&hidinput->list, &hid->inputs))
+ return 0;
- if (report->maxfield < 1 || report->field[0]->report_count < 2) {
- hid_err(hid, "Invalid report, this should never happen!\n");
- return -ENODEV;
- }
-
- if (list_is_last(input_ptr, &hid->inputs)) {
- hid_err(hid, "Missing input, this should never happen!\n");
- return -ENODEV;
- }
-
- input_ptr = input_ptr->next;
- input = list_entry(input_ptr, struct hid_input, list);
+ report = list_first_entry_or_null(report_list, struct hid_report, list);
+ if (!report) {
+ hid_err(hid, "no output reports found\n");
+ return -ENODEV;
+ }
- mf = kzalloc_obj(struct mf_device);
- if (!mf)
- return -ENOMEM;
+ if (report->maxfield < 1 || report->field[0]->report_count < 2) {
+ hid_err(hid, "Invalid report, this should never happen!\n");
+ return -ENODEV;
+ }
- dev = input->input;
- set_bit(FF_RUMBLE, dev->ffbit);
+ mf = kzalloc_obj(struct mf_device);
+ if (!mf)
+ return -ENOMEM;
- error = input_ff_create_memless(dev, mf, mf_play);
- if (error) {
- kfree(mf);
- return error;
- }
+ mf->report = report;
+ set_bit(FF_RUMBLE, dev->ffbit);
- mf->report = report;
- mf->report->field[0]->value[0] = 0x00;
- mf->report->field[0]->value[1] = 0x00;
- hid_hw_request(hid, mf->report, HID_REQ_SET_REPORT);
+ error = input_ff_create_memless(dev, mf, mf_play);
+ if (error) {
+ kfree(mf);
+ return error;
}
- hid_info(hid, "Force feedback for HJZ Mayflash game controller "
- "adapters by Marcel Hasler <mahasler@gmail.com>\n");
+ mf->report->field[0]->value[0] = 0x00;
+ mf->report->field[0]->value[1] = 0x00;
+ hid_hw_request(hid, mf->report, HID_REQ_SET_REPORT);
return 0;
}
@@ -128,22 +112,14 @@ static int mf_probe(struct hid_device *hid, const struct hid_device_id *id)
return error;
}
- error = hid_hw_start(hid, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+ error = hid_hw_start(hid, HID_CONNECT_DEFAULT);
if (error) {
hid_err(hid, "HID hw start failed\n");
return error;
}
- error = mf_init(hid);
- if (error) {
- hid_err(hid, "Force feedback init failed.\n");
- hid_hw_stop(hid);
- return error;
- }
-
return 0;
}
-
static const struct hid_device_id mf_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_PS3),
.driver_data = HID_QUIRK_MULTI_INPUT },
@@ -163,6 +139,7 @@ static struct hid_driver mf_driver = {
.name = "hid_mf",
.id_table = mf_devices,
.probe = mf_probe,
+ .input_configured = mf_input_configured,
};
module_hid_driver(mf_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 18/21] HID: smartjoyplus: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (16 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 17/21] HID: mayflash: " Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 19/21] HID: megaworld: " Dmitry Torokhov
` (2 subsequent siblings)
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start(). This is racy as the input device is
already registered and visible to userspace at that point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-sjoy.c | 83 +++++++++++++++++++++-----------------------------
1 file changed, 34 insertions(+), 49 deletions(-)
diff --git a/drivers/hid/hid-sjoy.c b/drivers/hid/hid-sjoy.c
index 963c45113204..193ab2a6146e 100644
--- a/drivers/hid/hid-sjoy.c
+++ b/drivers/hid/hid-sjoy.c
@@ -48,68 +48,56 @@ static int hid_sjoyff_play(struct input_dev *dev, void *data,
return 0;
}
-static int sjoyff_init(struct hid_device *hid)
+static int sjoy_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct sjoyff_device *sjoyff;
struct hid_report *report;
- struct hid_input *hidinput;
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct list_head *report_ptr = report_list;
- struct input_dev *dev;
+ struct input_dev *dev = hidinput->input;
int error;
- if (list_empty(report_list)) {
+ if (!list_is_first(&hidinput->list, &hid->inputs))
+ return 0;
+
+ report = list_first_entry_or_null(report_list, struct hid_report, list);
+ if (!report) {
hid_err(hid, "no output reports found\n");
return -ENODEV;
}
+ if (report->maxfield < 1) {
+ hid_err(hid, "no fields in the report\n");
+ return -ENODEV;
+ }
- list_for_each_entry(hidinput, &hid->inputs, list) {
- report_ptr = report_ptr->next;
-
- if (report_ptr == report_list) {
- hid_err(hid, "required output report is missing\n");
- return -ENODEV;
- }
-
- report = list_entry(report_ptr, struct hid_report, list);
- if (report->maxfield < 1) {
- hid_err(hid, "no fields in the report\n");
- return -ENODEV;
- }
-
- if (report->field[0]->report_count < 3) {
- hid_err(hid, "not enough values in the field\n");
- return -ENODEV;
- }
-
- sjoyff = kzalloc_obj(struct sjoyff_device);
- if (!sjoyff)
- return -ENOMEM;
+ if (report->field[0]->report_count < 3) {
+ hid_err(hid, "not enough values in the field\n");
+ return -ENODEV;
+ }
- dev = hidinput->input;
+ sjoyff = kzalloc_obj(struct sjoyff_device);
+ if (!sjoyff)
+ return -ENOMEM;
- set_bit(FF_RUMBLE, dev->ffbit);
+ set_bit(FF_RUMBLE, dev->ffbit);
- sjoyff->report = report;
- sjoyff->report->field[0]->value[0] = 0x01;
- sjoyff->report->field[0]->value[1] = 0x00;
- sjoyff->report->field[0]->value[2] = 0x00;
- hid_hw_request(hid, sjoyff->report, HID_REQ_SET_REPORT);
+ sjoyff->report = report;
+ sjoyff->report->field[0]->value[0] = 0x01;
+ sjoyff->report->field[0]->value[1] = 0x00;
+ sjoyff->report->field[0]->value[2] = 0x00;
+ hid_hw_request(hid, sjoyff->report, HID_REQ_SET_REPORT);
- error = input_ff_create_memless(dev, sjoyff, hid_sjoyff_play);
- if (error) {
- kfree(sjoyff);
- return error;
- }
+ error = input_ff_create_memless(dev, sjoyff, hid_sjoyff_play);
+ if (error) {
+ kfree(sjoyff);
+ return error;
}
- hid_info(hid, "Force feedback for SmartJoy PLUS PS2/USB adapter\n");
-
return 0;
}
#else
-static inline int sjoyff_init(struct hid_device *hid)
+static inline int sjoy_input_configured(struct hid_device *hid,
+ struct hid_input *hidinput)
{
return 0;
}
@@ -124,20 +112,16 @@ static int sjoy_probe(struct hid_device *hdev, const struct hid_device_id *id)
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
- goto err;
+ return ret;
}
- ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
- goto err;
+ return ret;
}
- sjoyff_init(hdev);
-
return 0;
-err:
- return ret;
}
static const struct hid_device_id sjoy_devices[] = {
@@ -165,6 +149,7 @@ static struct hid_driver sjoy_driver = {
.name = "smartjoyplus",
.id_table = sjoy_devices,
.probe = sjoy_probe,
+ .input_configured = sjoy_input_configured,
};
module_hid_driver(sjoy_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 19/21] HID: megaworld: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (17 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 18/21] HID: smartjoyplus: " Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 20/21] HID: logitech-hidpp: " Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 21/21] HID: haptic: move FF initialization into .input_configured() Dmitry Torokhov
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_hw_start(). This is racy as the input device is
already registered and visible to userspace at that point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-megaworld.c | 51 ++++++++++-----------------------------------
1 file changed, 11 insertions(+), 40 deletions(-)
diff --git a/drivers/hid/hid-megaworld.c b/drivers/hid/hid-megaworld.c
index 81acdbc3a00f..d5c868974275 100644
--- a/drivers/hid/hid-megaworld.c
+++ b/drivers/hid/hid-megaworld.c
@@ -35,21 +35,16 @@ static int mwctrl_play(struct input_dev *dev, void *data,
return 0;
}
-static int mwctrl_init(struct hid_device *hid)
+static int mwctrl_input_configured(struct hid_device *hid, struct hid_input *hidinput)
{
struct mwctrl_device *mwctrl;
struct hid_report *report;
- struct hid_input *hidinput;
- struct input_dev *dev;
+ struct input_dev *dev = hidinput->input;
int error;
int i;
- if (list_empty(&hid->inputs)) {
- hid_err(hid, "no inputs found\n");
- return -ENODEV;
- }
- hidinput = list_entry(hid->inputs.next, struct hid_input, list);
- dev = hidinput->input;
+ if (!list_is_first(&hidinput->list, &hid->inputs))
+ return 0;
for (i = 0; i < 4; i++) {
report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, i, 1);
@@ -61,16 +56,7 @@ static int mwctrl_init(struct hid_device *hid)
if (!mwctrl)
return -ENOMEM;
- set_bit(FF_RUMBLE, dev->ffbit);
-
- error = input_ff_create_memless(dev, mwctrl, mwctrl_play);
- if (error) {
- kfree(mwctrl);
- return error;
- }
-
mwctrl->report = report;
-
/* Field 0 is always 2, and field 1 is always 0. The original
* windows driver has a 5 bytes command, where the 5th byte is
* a repeat of the 3rd byte, however the device has only 4
@@ -82,30 +68,15 @@ static int mwctrl_init(struct hid_device *hid)
mwctrl->strong = &report->field[2]->value[0];
mwctrl->weak = &report->field[3]->value[0];
- return 0;
-}
-
-static int mwctrl_probe(struct hid_device *hdev, const struct hid_device_id *id)
-{
- int ret;
-
- ret = hid_parse(hdev);
- if (ret) {
- hid_err(hdev, "parse failed\n");
- return ret;
- }
+ set_bit(FF_RUMBLE, dev->ffbit);
- ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
- if (ret) {
- hid_err(hdev, "hw start failed\n");
- return ret;
+ error = input_ff_create_memless(dev, mwctrl, mwctrl_play);
+ if (error) {
+ kfree(mwctrl);
+ return error;
}
- ret = mwctrl_init(hdev);
- if (ret)
- hid_hw_stop(hdev);
-
- return ret;
+ return 0;
}
static const struct hid_device_id mwctrl_devices[] = {
@@ -118,7 +89,7 @@ MODULE_DEVICE_TABLE(hid, mwctrl_devices);
static struct hid_driver mwctrl_driver = {
.name = "megaworld",
.id_table = mwctrl_devices,
- .probe = mwctrl_probe,
+ .input_configured = mwctrl_input_configured,
};
module_hid_driver(mwctrl_driver);
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 20/21] HID: logitech-hidpp: move FF initialization to .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (18 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 19/21] HID: megaworld: " Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 21/21] HID: haptic: move FF initialization into .input_configured() Dmitry Torokhov
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
The driver currently initializes force-feedback in its probe() function
after calling hid_connect(). This is racy as the input device is
already registered and visible to userspace at that point.
Move the FF initialization to the .input_configured() callback to ensure
the device is fully prepared before registration.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-logitech-hidpp.c | 36 +++++++++++++++++++-----------------
1 file changed, 19 insertions(+), 17 deletions(-)
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 90b0184df777..db53b45b0752 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -3861,15 +3861,32 @@ static void hidpp_populate_input(struct hidpp_device *hidpp,
hidpp10_extra_mouse_buttons_populate_input(hidpp, input);
}
-static int hidpp_input_configured(struct hid_device *hdev,
- struct hid_input *hidinput)
+static int hidpp_input_configured(struct hid_device *hdev, struct hid_input *hidinput)
{
struct hidpp_device *hidpp = hid_get_drvdata(hdev);
struct input_dev *input = hidinput->input;
+ int ret;
if (!hidpp)
return 0;
+ if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
+ struct hidpp_ff_private_data data;
+
+ if (!list_is_first(&hidinput->list, &hdev->inputs))
+ return 0;
+
+ ret = g920_get_config(hidpp, &data);
+ if (!ret)
+ ret = hidpp_ff_init(hidpp, &data);
+
+ if (ret) {
+ hid_warn(hidpp->hid_dev,
+ "Unable to initialize force feedback support, errno %d\n",
+ ret);
+ }
+ }
+
hidpp_populate_input(hidpp, input);
return 0;
@@ -4530,21 +4547,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
schedule_work(&hidpp->work);
flush_work(&hidpp->work);
- if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
- struct hidpp_ff_private_data data;
-
- ret = g920_get_config(hidpp, &data);
- if (!ret)
- ret = hidpp_ff_init(hidpp, &data);
-
- if (ret) {
- hid_warn(hidpp->hid_dev,
- "Unable to initialize force feedback support, errno %d\n",
- ret);
- ret = 0;
- }
- }
-
/*
* This relies on logi_dj_ll_close() being a no-op so that DJ connection
* events will still be received.
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 21/21] HID: haptic: move FF initialization into .input_configured()
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
` (19 preceding siblings ...)
2026-08-03 18:46 ` [PATCH 20/21] HID: logitech-hidpp: " Dmitry Torokhov
@ 2026-08-03 18:46 ` Dmitry Torokhov
20 siblings, 0 replies; 22+ messages in thread
From: Dmitry Torokhov @ 2026-08-03 18:46 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Shuah Khan,
Julia Lawall, Nicolas Palix, Filipe Laíns, Bastien Nocera
Cc: linux-input, linux-kernel, linux-doc, cocci
Refactor hid_haptic_init() to take a direct pointer to input_dev and
integrate its invocation into hid_haptic_input_configured().
Update hid-multitouch to rely on the refactored callback to perform the
force-feedback initialization during the registration loop. This ensures
that force-feedback capabilities are set up before the input device is
registered and exposed to userspace, closing the registration race.
Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-haptic.c | 45 +++++++++++++++++---------------------------
drivers/hid/hid-haptic.h | 6 ++++--
drivers/hid/hid-multitouch.c | 10 +---------
3 files changed, 22 insertions(+), 39 deletions(-)
diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c
index deadab28cdbe..5d365a9767dd 100644
--- a/drivers/hid/hid-haptic.c
+++ b/drivers/hid/hid-haptic.c
@@ -82,16 +82,24 @@ int hid_haptic_input_configured(struct hid_device *hdev,
struct hid_haptic_device *haptic,
struct hid_input *hi)
{
+ int error;
- if (hi->application == HID_DG_TOUCHPAD) {
- if (haptic->auto_trigger_report &&
- haptic->manual_trigger_report) {
- __set_bit(INPUT_PROP_PRESSUREPAD, hi->input->propbit);
- return 1;
- }
+ if (hi->application != HID_DG_TOUCHPAD)
+ return -1;
+
+ if (!haptic->auto_trigger_report || !haptic->manual_trigger_report)
+ return 0;
+
+ __set_bit(INPUT_PROP_PRESSUREPAD, hi->input->propbit);
+
+ error = hid_haptic_init(hdev, haptic, hi->input);
+ if (error) {
+ dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
+ hdev->name);
return 0;
}
- return -1;
+
+ return 1;
}
EXPORT_SYMBOL_GPL(hid_haptic_input_configured);
@@ -401,11 +409,9 @@ static void hid_haptic_destroy(struct ff_device *ff)
}
int hid_haptic_init(struct hid_device *hdev,
- struct hid_haptic_device **haptic_ptr)
+ struct hid_haptic_device *haptic,
+ struct input_dev *dev)
{
- struct hid_haptic_device *haptic = *haptic_ptr;
- struct input_dev *dev = NULL;
- struct hid_input *hidinput;
struct ff_device *ff;
int ret = 0, r;
struct ff_haptic_effect stop_effect = {
@@ -447,19 +453,6 @@ int hid_haptic_init(struct hid_device *hdev,
for (r = 0; r < haptic->auto_trigger_report->maxfield; r++)
parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]);
- list_for_each_entry(hidinput, &hdev->inputs, list) {
- if (hidinput->application == HID_DG_TOUCHPAD) {
- dev = hidinput->input;
- break;
- }
- }
-
- if (!dev) {
- dev_err(&hdev->dev, "Failed to find the input device\n");
- ret = -ENODEV;
- goto duration_map;
- }
-
haptic->input_dev = dev;
haptic->manual_trigger_report_len =
hid_report_len(haptic->manual_trigger_report);
@@ -535,10 +528,6 @@ int hid_haptic_init(struct hid_device *hdev,
input_free:
input_ff_destroy(dev);
- /* Do not let double free happen, input_ff_destroy will call
- * hid_haptic_destroy.
- */
- *haptic_ptr = NULL;
/* Restore dev flush and event */
dev->flush = flush;
dev->event = event;
diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h
index c6539ac04c1d..6332991a7844 100644
--- a/drivers/hid/hid-haptic.h
+++ b/drivers/hid/hid-haptic.h
@@ -69,7 +69,8 @@ int hid_haptic_input_mapping(struct hid_device *hdev,
int hid_haptic_input_configured(struct hid_device *hdev,
struct hid_haptic_device *haptic,
struct hid_input *hi);
-int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr);
+int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device *haptic,
+ struct input_dev *dev);
void hid_haptic_handle_press_release(struct hid_haptic_device *haptic);
void hid_haptic_pressure_reset(struct hid_haptic_device *haptic);
void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
@@ -107,7 +108,8 @@ static inline
void hid_haptic_reset(struct hid_device *hdev, struct hid_haptic_device *haptic)
{}
static inline
-int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr)
+int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device *haptic,
+ struct input_dev *dev)
{
return 0;
}
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index edb37b4c867e..15218e92aaa4 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -2189,16 +2189,8 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
- if (td->is_haptic_touchpad) {
- if (hid_haptic_init(hdev, &td->haptic)) {
- dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
- hdev->name);
- td->is_haptic_touchpad = false;
- devm_kfree(&hdev->dev, td->haptic);
- }
- } else {
+ if (!td->is_haptic_touchpad)
devm_kfree(&hdev->dev, td->haptic);
- }
return 0;
}
--
2.55.0.629.g250fe7f194-goog
^ permalink raw reply related [flat|nested] 22+ messages in thread
end of thread, other threads:[~2026-08-03 18:47 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-03 18:46 [PATCH 00/21] HID: fix racy force feedback initialization via .input_configured() Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 01/21] HID: core: automatically initialize generic FF if no other FF is present Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 02/21] HID: add documentation and Coccinelle script for FF registration race Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 03/21] HID: axff: move FF initialization to .input_configured() Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 04/21] HID: betop: " Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 05/21] HID: bigben: " Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 06/21] HID: dragonrise: " Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 07/21] HID: emsff: " Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 08/21] HID: gaff: " Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 09/21] HID: stadia: use open/close to manage workqueue lifecycle Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 10/21] HID: stadia: move FF initialization to .input_configured() Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 11/21] HID: holtek: " Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 12/21] HID: move generic FF initialization into hidinput_connect() Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 13/21] HID: microsoft: move FF initialization to .input_configured() Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 14/21] HID: pantherlord: " Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 15/21] HID: thrustmaster: " Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 16/21] HID: zeroplus: " Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 17/21] HID: mayflash: " Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 18/21] HID: smartjoyplus: " Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 19/21] HID: megaworld: " Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 20/21] HID: logitech-hidpp: " Dmitry Torokhov
2026-08-03 18:46 ` [PATCH 21/21] HID: haptic: move FF initialization into .input_configured() Dmitry Torokhov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).