[PATCH v2 1/4] Input: ensure device is ready before delivering events
Dmitry Torokhov <[email protected]> Sun, 2 Aug 2026 17:52:01 -0700
| Newsgroups | org.kernel.vger.linux-input,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
When a device is opened via input_open_device(), the driver's open() callback is invoked. Some drivers, like cm109, submit URBs or perform other hardware initialization in their open() callbacks. However, the input core does not prevent dev->event() from being called concurrently during the driver's open() execution. For instance, if a console beep occurs, the kbd handler might inject an EV_SND event. This can lead to double list_add BUGs if the driver submits the same URB in both open() and event() paths without adequate synchronization. To fix this, introduce a ready flag in the input_dev structure. For complex devices (where dev->open is defined), this flag is set to true only after the driver's open() method successfully completes. The core now checks ready in input_event_dispose() and input_dev_toggle() to prevent events from reaching the hardware before it is fully prepared. For simple devices (no open callback), events are delivered immediately. We also replay the logical state in input_open_device() by calling input_dev_toggle() right after marking the device ready, ensuring no events are permanently lost. In the inhibit path, we ensure that physical feedback (LEDs/sounds) is turned off before the device is closed, and we synchronize the inhibited state transition under the event lock to prevent races with incoming events. Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Dmitry Torokhov <[email protected]> --- v2: - made the patch introducing "ready" flagi first in the series - fixed up Sashiko's comments regarding not shutting off LEDs on close drivers/input/input.c | 103 +++++++++++++++++++++++++++--------------- include/linux/input.h | 12 +++-- 2 files changed, 74 insertions(+), 41 deletions(-) diff --git a/drivers/input/input.c b/drivers/input/input.c index cf6fecea79b8..e57d1023d262 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -318,7 +318,7 @@ static int input_get_disposition(struct input_dev *dev, static void input_event_dispose(struct input_dev *dev, int disposition, unsigned int type, unsigned int code, int value) { - if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) + if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event && dev->ready) dev->event(dev, type, code, value); if (disposition & INPUT_PASS_TO_HANDLERS) { @@ -568,6 +568,48 @@ void input_release_device(struct input_handle *handle) } EXPORT_SYMBOL(input_release_device); +#define INPUT_DO_TOGGLE(dev, type, bits, on) \ + do { \ + int i; \ + bool active; \ + \ + if (!test_bit(EV_##type, dev->evbit)) \ + break; \ + \ + for_each_set_bit(i, dev->bits##bit, type##_CNT) { \ + active = test_bit(i, dev->bits); \ + if (!active && !on) \ + continue; \ + \ + dev->event(dev, EV_##type, i, on ? active : 0); \ + } \ + } while (0) + +/* + * Iterate through the logical state of the input device (LEDs, sounds, + * auto-repeat) and explicitly push that state down to the hardware + * via dev->event() to match the current logical state (if activate is true), + * or forcibly turn off all feedback like LEDs and sounds during teardown + * or suspend (if activate is false). + * + * Primarily used as a state-replay mechanism after a device is opened + * or uninhibited, as events might have been dropped by the core while the + * hardware was not marked as ready. + */ +static void input_dev_toggle(struct input_dev *dev, bool activate) +{ + if (!dev->event || !dev->ready) + return; + + INPUT_DO_TOGGLE(dev, LED, led, activate); + INPUT_DO_TOGGLE(dev, SND, snd, activate); + + if (activate && test_bit(EV_REP, dev->evbit)) { + dev->event(dev, EV_REP, REP_PERIOD, dev->rep[REP_PERIOD]); + dev->event(dev, EV_REP, REP_DELAY, dev->rep[REP_DELAY]); + } +} + /** * input_open_device - open input device * @handle: handle through which device is being accessed @@ -611,6 +653,11 @@ int input_open_device(struct input_handle *handle) } } + scoped_guard(spinlock_irq, &dev->event_lock) { + dev->ready = true; + input_dev_toggle(dev, true); + } + if (dev->poller) input_dev_poller_start(dev->poller); } @@ -651,6 +698,12 @@ void input_close_device(struct input_handle *handle) if (!--dev->users && !dev->inhibited) { if (dev->poller) input_dev_poller_stop(dev->poller); + + scoped_guard(spinlock_irq, &dev->event_lock) { + input_dev_toggle(dev, false); + dev->ready = false; + } + if (dev->close) dev->close(dev); } @@ -1702,37 +1755,6 @@ static int input_dev_uevent(const struct device *device, struct kobj_uevent_env return 0; } -#define INPUT_DO_TOGGLE(dev, type, bits, on) \ - do { \ - int i; \ - bool active; \ - \ - if (!test_bit(EV_##type, dev->evbit)) \ - break; \ - \ - for_each_set_bit(i, dev->bits##bit, type##_CNT) { \ - active = test_bit(i, dev->bits); \ - if (!active && !on) \ - continue; \ - \ - dev->event(dev, EV_##type, i, on ? active : 0); \ - } \ - } while (0) - -static void input_dev_toggle(struct input_dev *dev, bool activate) -{ - if (!dev->event) - return; - - INPUT_DO_TOGGLE(dev, LED, led, activate); - INPUT_DO_TOGGLE(dev, SND, snd, activate); - - if (activate && test_bit(EV_REP, dev->evbit)) { - dev->event(dev, EV_REP, REP_PERIOD, dev->rep[REP_PERIOD]); - dev->event(dev, EV_REP, REP_DELAY, dev->rep[REP_DELAY]); - } -} - /** * input_reset_device() - reset/restore the state of input device * @dev: input device whose state needs to be reset @@ -1760,21 +1782,25 @@ static int input_inhibit_device(struct input_dev *dev) return 0; if (dev->users) { - if (dev->close) - dev->close(dev); if (dev->poller) input_dev_poller_stop(dev->poller); + + scoped_guard(spinlock_irq, &dev->event_lock) { + input_dev_toggle(dev, false); + dev->ready = false; + } + + if (dev->close) + dev->close(dev); } scoped_guard(spinlock_irq, &dev->event_lock) { input_mt_release_slots(dev); input_dev_release_keys(dev); input_handle_event(dev, EV_SYN, SYN_REPORT, 1); - input_dev_toggle(dev, false); + dev->inhibited = true; } - dev->inhibited = true; - return 0; } @@ -1793,6 +1819,9 @@ static int input_uninhibit_device(struct input_dev *dev) if (error) return error; } + scoped_guard(spinlock_irq, &dev->event_lock) + dev->ready = true; + if (dev->poller) input_dev_poller_start(dev->poller); } diff --git a/include/linux/input.h b/include/linux/input.h index 76f7aa226202..f147d27e6d1d 100644 --- a/include/linux/input.h +++ b/include/linux/input.h @@ -128,11 +128,14 @@ enum input_clock_type { * @devres_managed: indicates that devices is managed with devres framework * and needs not be explicitly unregistered or freed. * @timestamp: storage for a timestamp set by input_set_timestamp called - * by a driver + * by a driver * @inhibited: indicates that the input device is inhibited. If that is - * the case then input core ignores any events generated by the device. - * Device's close() is called when it is being inhibited and its open() - * is called when it is being uninhibited. + * the case then input core ignores any events generated by the device. + * Device's close() is called when it is being inhibited and its open() + * is called when it is being uninhibited. + * @ready: indicates that the device has been successfully opened and is + * prepared to process events (like LEDs or sounds) sent from the + * input core. */ struct input_dev { const char *name; @@ -209,6 +212,7 @@ struct input_dev { ktime_t timestamp[INPUT_CLK_MAX]; bool inhibited; + bool ready; }; #define to_input_dev(d) container_of(d, struct input_dev, dev) -- 2.55.0.508.g3f0d502094-goog