[PATCH 02/10] HID: hid-lenovo-go-s: Bound stale reply window before reusing send_cmd_complete
From: Derek J. Clark
Date: Mon Sep 14 2026 - 18:53:23 EST
mcu_property_out() reinits send_cmd_complete immediately after a
timeout is detected, before the next command that reuses it is sent.
If the MCU's reply to the timed-out command arrives after this reinit
but before the next command's wait begins, it silently satisfies the
next, unrelated command's wait instead of the one it actually answers,
handing that caller stale data with no way to detect the mismatch.
Track when a command has timed out via cmd_orphaned. Before the next
command reuses the completion, wait a bounded 25ms for a stale reply
to arrive and be consumed, then unconditionally clear the flag and
reinit the completion. This does not fully eliminate the window in
which an unrelated reply could still be received, but bounds it to
a short interval right before a new command is sent. Behavior matches
the solution to the same problem in hid-msi.
Fixes: a23f3497bf208c59ad ("HID: hid-lenovo-go-s: Add Lenovo Legion Go S Series HID Driver")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Derek J. Clark <derekjohn.clark@xxxxxxxxx>
---
drivers/hid/hid-lenovo-go-s.c | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c
index 68301d4c762a..36505d8402ff 100644
--- a/drivers/hid/hid-lenovo-go-s.c
+++ b/drivers/hid/hid-lenovo-go-s.c
@@ -37,6 +37,7 @@ static struct hid_gos_cfg {
struct completion send_cmd_complete;
struct led_classdev *led_cdev;
struct hid_device *hdev;
+ bool orphan_ack_pending;
struct mutex cfg_mutex; /*ensure single synchronous output report*/
int cmd_status;
u8 gp_auto_sleep_time;
@@ -454,6 +455,19 @@ static int mcu_property_out(struct hid_device *hdev, u8 command, u8 index,
return -EINVAL;
guard(mutex)(&drvdata.cfg_mutex);
+
+ /*
+ * A reply to the previous command may still be in flight. Give it a
+ * short window to arrive and be consumed before this call reinits the
+ * completion, so a late reply can't be mistaken for this command's.
+ */
+ if (drvdata.orphan_ack_pending) {
+ wait_for_completion_timeout(&drvdata.send_cmd_complete, msecs_to_jiffies(25));
+ drvdata.orphan_ack_pending = false;
+ drvdata.cmd_status = -ETIMEDOUT;
+ }
+ reinit_completion(&drvdata.send_cmd_complete);
+
/* We can't use a devm_alloc reusable buffer without side effects during suspend */
dmabuf = kzalloc(GO_S_PACKET_SIZE, GFP_KERNEL);
if (!dmabuf)
@@ -479,7 +493,9 @@ static int mcu_property_out(struct hid_device *hdev, u8 command, u8 index,
msecs_to_jiffies(timeout));
ret = ret > 0 ? drvdata.cmd_status : ret ?: -EBUSY;
- reinit_completion(&drvdata.send_cmd_complete);
+ if (ret)
+ drvdata.orphan_ack_pending = true;
+
return ret;
}
--
2.55.0