[PATCH] Input: evdev: drain queued events before reporting device removal
From: zhangliuyang.zly
Date: Wed Jul 29 2026 - 05:37:05 EST
When an input device is unregistered, the input core calls
input_dev_release_keys() and sends a SYN_REPORT before closing the input
handles. This is intended to deliver synthetic key-up events for keys that
are still pressed when the device disappears.
However, evdev_read() currently checks evdev->exist before draining the
per-client event queue. If evdev_disconnect() marks the evdev node dead
before userspace reads the queued synthetic release packet, read() returns
-ENODEV immediately and the queued key-up events are lost from userspace's
point of view.
This can happen on Android with USB OTG 2.4G keyboard receivers. The input
core generates the release event during disconnect, but Android EventHub
may observe the evdev hangup/removal first and then fail to read the
pending EV_KEY value=0 event. The framework then falls back to device
reset/cancel semantics instead of dispatching a normal ACTION_UP.
Allow evdev_read() to drain already queued events even after evdev->exist
is cleared. Return -ENODEV only when the client queue is empty, or when
the client has been revoked. Keep zero-length reads compatible with the
previous error-checking behavior.
Signed-off-by: zhangliuyang.zly <zhangliuyang.zly@xxxxxxxxxxxxx>
---
drivers/input/evdev.c | 34 ++++++++++++++++++++++++++--------
1 file changed, 26 insertions(+), 8 deletions(-)
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index c7325226cb86..89bdd7a2c763 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -565,20 +565,38 @@ static ssize_t evdev_read(struct file *file, char __user *buffer,
if (count != 0 && count < input_event_size())
return -EINVAL;
- for (;;) {
+ /*
+ * count == 0 is special - no IO is done but we still check for
+ * error conditions, preserving the historical behavior.
+ */
+ if (count == 0) {
if (!evdev->exist || client->revoked)
return -ENODEV;
- if (client->packet_head == client->tail &&
- (file->f_flags & O_NONBLOCK))
- return -EAGAIN;
+ return 0;
+ }
+ for (;;) {
/*
- * count == 0 is special - no IO is done but we check
- * for error conditions (see above).
+ * A revoked client must not consume any more events.
*/
- if (count == 0)
- break;
+ if (client->revoked)
+ return -ENODEV;
+
+ /*
+ * The input core may queue synthetic release events during
+ * device unregister before evdev_disconnect() marks the evdev
+ * node dead. Do not drop those already queued events by
+ * returning -ENODEV too early. Drain the client queue first and
+ * report -ENODEV only when there is nothing left to read.
+ */
+ if (client->packet_head == client->tail) {
+ if (!evdev->exist)
+ return -ENODEV;
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ }
while (read + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {
--
2.54.0