diff options
author | Samuel Thibault <samuel.thibault@ens-lyon.org> | 2014-11-27 11:09:02 +1100 |
---|---|---|
committer | Stephen Rothwell <sfr@canb.auug.org.au> | 2014-11-27 11:09:02 +1100 |
commit | f4add98b81e6cfa37f05acf8b28dbe10087bac49 (patch) | |
tree | 1f499cfaad3407f01e2d262957beb7561b173454 /drivers/input | |
parent | 87edffc2f73d3681c3fdd2ef4a581a45a778bda5 (diff) |
Defer input led work to workqueue
When the kbd changes its led state (e.g. caps lock), this triggers
(led_trigger_event) the kbd-capsl trigger, which is by default used by the
vt::capsl LED, which triggers (led_trigger_event) the vt-capsl trigger.
These two nested led_trigger_event calls take a trig->leddev_list_lock
lock and thus lockdep complains.
Actually the user can make the vt::capsl LED use its own vt-capsl trigger
and thus build a loop. This produces an immediate oops.
This changeset defers the second led_trigger_event call into a workqueue,
which avoids the nested locking altogether. This does not prevent the
user from shooting himself in the foot by creating a vt::capsl <->
vt-capsl loop, but the only consequence is the workqueue threads eating
some CPU until the user breaks the loop, which is not too bad.
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Diffstat (limited to 'drivers/input')
-rw-r--r-- | drivers/input/leds.c | 29 |
1 files changed, 28 insertions, 1 deletions
diff --git a/drivers/input/leds.c b/drivers/input/leds.c index 985fa7ebeec7..dca4a506c660 100644 --- a/drivers/input/leds.c +++ b/drivers/input/leds.c @@ -100,13 +100,24 @@ static unsigned long vt_led_registered[BITS_TO_LONGS(LED_CNT)]; /* Number of input devices having each LED */ static int vt_led_references[LED_CNT]; +static int vt_led_state[LED_CNT]; +static struct work_struct vt_led_work[LED_CNT]; + +static void vt_led_cb(struct work_struct *work) +{ + int led = work - vt_led_work; + + led_trigger_event(&vt_led_triggers[led], vt_led_state[led]); +} + /* VT LED state change, tell the VT trigger. */ static void vt_led_set(struct led_classdev *cdev, enum led_brightness brightness) { int led = cdev - vt_leds; - led_trigger_event(&vt_led_triggers[led], !!brightness); + vt_led_state[led] = !!brightness; + schedule_work(&vt_led_work[led]); } /* LED state change for some keyboard, notify that keyboard. */ @@ -244,6 +255,22 @@ void input_led_disconnect(struct input_dev *dev) mutex_unlock(&vt_led_registered_lock); } +static int __init input_led_init(void) +{ + unsigned i; + + for (i = 0; i < LED_CNT; i++) + INIT_WORK(&vt_led_work[i], vt_led_cb); + + return 0; +} + +static void __exit input_led_exit(void) +{ +} + MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("User LED support for input layer"); MODULE_AUTHOR("Samuel Thibault <samuel.thibault@ens-lyon.org>"); +module_init(input_led_init); +module_exit(input_led_exit); |