[PATCH] Input: evdev: drain queued events before reporting device removal
"zhangliuyang.zly" <[email protected]> Wed, 29 Jul 2026 17:27:53 +0800
| Newsgroups | org.kernel.vger.linux-input,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
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 <[email protected]> --- 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