* [PATCH 0/2] input: evdev: Fix how SYN_DROPPED is generated
@ 2026-10-22 13:39 Kornel Dulęba
2026-10-22 13:39 ` [PATCH 1/2] input: evdev: Always terminate SYN_DROPPED with SYN_REPORT Kornel Dulęba
2026-10-22 13:39 ` [PATCH 2/2] input: evdev: Read clock directly in __evdev_queue_syn_dropped() Kornel Dulęba
0 siblings, 2 replies; 3+ messages in thread
From: Kornel Dulęba @ 2026-10-22 13:39 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, Kornel Dulęba
This series fixes two issues discovered while working with a gpio-keys
backed device on Android. In short:
1. When a gpio-keys backed device is opened, its initial state is
immediately reported.
2. Android issues EVIOCSCLOCKID right after, before reading any data.
3. evdev flushes the queue, enqueues SYN_DROPPED, and caches the current
time as the device timestamp. Notably, it does not enqueue a
SYN_REPORT.
4. When the next real event is reported by hardware, it is enqueued with
the stale cached timestamp and a prepended SYN_DROPPED.
5. The whole packet, up to the first legitimate SYN_REPORT, is then
ignored by userspace.
SYN_DROPPED needs to be issued after EVIOCSCLOCKID to ensure that any
partially read packet is dropped. At the same time, evdev only defines
the batched events() callback, not the single-event event() one. All
events in a packet up to SYN_REPORT are written to a client's queue at
once while holding client->buffer_lock. The EVIOCSCLOCKID handler takes
the same lock when flushing the queue. Therefore, EVIOCSCLOCKID cannot
inject SYN_DROPPED in the middle of a packet while it is being written
to the queue.
Based on the above, I believe that the best fix here is to emit
SYN_REPORT right after SYN_DROPPED.
Patch 1: Always follow SYN_DROPPED with a SYN_REPORT.
Patch 2: Stamp the SYN_DROPPED+SYN_REPORT events with a timestamp read
directly from the clock, bypassing the cache.
Signed-off-by: Kornel Dulęba <korneld@google.com>
---
Kornel Dulęba (2):
input: evdev: Always terminate SYN_DROPPED with SYN_REPORT
input: evdev: Read clock directly in __evdev_queue_syn_dropped()
drivers/input/evdev.c | 29 ++++++++++++++++++++++++-----
1 file changed, 24 insertions(+), 5 deletions(-)
---
base-commit: 7be4bc52fd9e0e55dc9a2e0232b89427d2c4b614
change-id: 20261022-evdev_syndropped_fix-76352587616b
Best regards,
--
Kornel Dulęba <korneld@google.com>
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH 1/2] input: evdev: Always terminate SYN_DROPPED with SYN_REPORT
2026-10-22 13:39 [PATCH 0/2] input: evdev: Fix how SYN_DROPPED is generated Kornel Dulęba
@ 2026-10-22 13:39 ` Kornel Dulęba
2026-10-22 13:39 ` [PATCH 2/2] input: evdev: Read clock directly in __evdev_queue_syn_dropped() Kornel Dulęba
1 sibling, 0 replies; 3+ messages in thread
From: Kornel Dulęba @ 2026-10-22 13:39 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, Kornel Dulęba
Per Documentation/input/event-codes.rst, on SYN_DROPPED:
"Client should ignore all events up to and including next SYN_REPORT
event and query the device (using EVIOCG* ioctls) to obtain its
current state."
Currently, __evdev_queue_syn_dropped() queues a standalone SYN_DROPPED
event without a terminating SYN_REPORT. This causes two issues:
1. The queued SYN_DROPPED event remains unreadable until the next
hardware event frame arrives and finishes with SYN_REPORT.
2. When the next hardware event frame arrives, SYN_DROPPED is prepended
to it in the same packet. Compliant userspace clients then discard
the entire hardware event frame up to its SYN_REPORT.
This was discovered on an Android system with a gpio-keys backed device:
1. gpio_keys_open() reports the initial state on open().
2. Android follows open() with EVIOCSCLOCKID.
3. This creates an unterminated SYN_DROPPED, causing the subsequent
event to be discarded by userspace.
Fix this by appending SYN_REPORT after SYN_DROPPED in
__evdev_queue_syn_dropped() and updating client->packet_head
accordingly.
Signed-off-by: Kornel Dulęba <korneld@google.com>
---
drivers/input/evdev.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index 8bfaaa45e0b9..c6da2601480f 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -147,6 +147,7 @@ static void __evdev_queue_syn_dropped(struct evdev_client *client)
{
ktime_t *ev_time = input_get_timestamp(client->evdev->handle.dev);
struct timespec64 ts = ktime_to_timespec64(ev_time[client->clk_type]);
+ unsigned int buf_mask = client->bufsize - 1;
struct input_event ev;
memset(&ev, 0, sizeof(ev));
@@ -156,13 +157,24 @@ static void __evdev_queue_syn_dropped(struct evdev_client *client)
ev.code = SYN_DROPPED;
client->buffer[client->head++] = ev;
- client->head &= client->bufsize - 1;
+ client->head &= buf_mask;
if (unlikely(client->head == client->tail)) {
/* drop queue but keep our SYN_DROPPED event */
- client->tail = (client->head - 1) & (client->bufsize - 1);
- client->packet_head = client->tail;
+ client->tail = (client->head - 1) & buf_mask;
}
+
+ ev.code = SYN_REPORT;
+
+ client->buffer[client->head++] = ev;
+ client->head &= buf_mask;
+
+ if (unlikely(client->head == client->tail)) {
+ /* drop queue but keep our SYN_DROPPED and SYN_REPORT events */
+ client->tail = (client->head - 2) & buf_mask;
+ }
+
+ client->packet_head = client->head;
}
static void evdev_queue_syn_dropped(struct evdev_client *client)
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH 2/2] input: evdev: Read clock directly in __evdev_queue_syn_dropped()
2026-10-22 13:39 [PATCH 0/2] input: evdev: Fix how SYN_DROPPED is generated Kornel Dulęba
2026-10-22 13:39 ` [PATCH 1/2] input: evdev: Always terminate SYN_DROPPED with SYN_REPORT Kornel Dulęba
@ 2026-10-22 13:39 ` Kornel Dulęba
1 sibling, 0 replies; 3+ messages in thread
From: Kornel Dulęba @ 2026-10-22 13:39 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, Kornel Dulęba
Currently, __evdev_queue_syn_dropped() calls input_get_timestamp() on
the underlying input_dev to obtain the event timestamp. If no timestamp
is set, input_get_timestamp() caches the current time and returns it;
otherwise, it returns the cached value.
There are two problems with this approach:
1. Locking. The timestamp is obtained in the same way in
evdev_events(), which is called with dev->event_lock held. Calls to
__evdev_queue_syn_dropped() are only serialized with
client->buffer_lock. This makes both accesses likely racy.
2. If no timestamp is cached yet, the subsequent hardware event
generated after __evdev_queue_syn_dropped() is called will receive
the stale cached timestamp.
Fix this by reading the current time directly based on the value of
client->clk_type. Open-coding this is not the prettiest solution, but I
couldn't figure out anything better.
I initially thought about fixing this by resetting the input timestamp
in this function instead, similarly to how INPUT_FLUSH handles it.
However, I decided against it, as that would require non-trivial
locking changes.
Signed-off-by: Kornel Dulęba <korneld@google.com>
---
drivers/input/evdev.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index c6da2601480f..da7396c19188 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -145,11 +145,18 @@ static void __evdev_flush_queue(struct evdev_client *client, unsigned int type)
static void __evdev_queue_syn_dropped(struct evdev_client *client)
{
- ktime_t *ev_time = input_get_timestamp(client->evdev->handle.dev);
- struct timespec64 ts = ktime_to_timespec64(ev_time[client->clk_type]);
unsigned int buf_mask = client->bufsize - 1;
struct input_event ev;
+ ktime_t time;
+ struct timespec64 ts;
+
+ time = client->clk_type == INPUT_CLK_REAL ?
+ ktime_get_real() :
+ client->clk_type == INPUT_CLK_MONO ?
+ ktime_get() :
+ ktime_get_boottime();
+ ts = ktime_to_timespec64(time);
memset(&ev, 0, sizeof(ev));
ev.input_event_sec = ts.tv_sec;
ev.input_event_usec = ts.tv_nsec / NSEC_PER_USEC;
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-22 13:39 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-22 13:39 [PATCH 0/2] input: evdev: Fix how SYN_DROPPED is generated Kornel Dulęba
2026-10-22 13:39 ` [PATCH 1/2] input: evdev: Always terminate SYN_DROPPED with SYN_REPORT Kornel Dulęba
2026-10-22 13:39 ` [PATCH 2/2] input: evdev: Read clock directly in __evdev_queue_syn_dropped() Kornel Dulęba
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®