]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
leds: core: Fix race condition for software blink
authorCraig McQueen <craig@mcqueen.au>
Thu, 23 Apr 2026 11:36:38 +0000 (21:36 +1000)
committerLee Jones <lee@kernel.org>
Wed, 17 Jun 2026 10:29:05 +0000 (11:29 +0100)
led_set_brightness() function: Change handling of software blink to
avoid race conditions when stopping blink and setting brightness.

Triggers may call led_set_brightness(LED_OFF),
led_set_brightness(LED_FULL) in quick succession to disable blinking and
turn the LED on. If the delayed work task has not yet disabled blinking
by the time the second call occurs, then the brightness also needs to be
changed in the delayed work task.

Signed-off-by: Craig McQueen <craig@mcqueen.au>
Link: https://patch.msgid.link/20260423113638.2079302-1-craig@mcqueen.au
Signed-off-by: Lee Jones <lee@kernel.org>
drivers/leds/led-core.c

index 385e78af1daced6a8795f8c515452cb9b72aaf5b..073c547068cc3197e34d890c5f52ce3369551d4a 100644 (file)
@@ -303,24 +303,31 @@ EXPORT_SYMBOL_GPL(led_stop_software_blink);
 
 void led_set_brightness(struct led_classdev *led_cdev, unsigned int brightness)
 {
-       /*
-        * If software blink is active, delay brightness setting
-        * until the next timer tick.
-        */
-       if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) {
+       if (brightness) {
                /*
-                * If we need to disable soft blinking delegate this to the
-                * work queue task to avoid problems in case we are called
-                * from hard irq context.
+                * If software blink disable is pending, also queue brightness setting.
+                * If software blink is active, delay brightness setting
+                * until the next timer tick.
                 */
-               if (!brightness) {
-                       set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags);
+               if (test_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags) ||
+                   test_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) {
+                       led_cdev->delayed_set_value = brightness;
+                       set_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags);
                        queue_work(led_cdev->wq, &led_cdev->set_brightness_work);
-               } else {
-                       set_bit(LED_BLINK_BRIGHTNESS_CHANGE,
-                               &led_cdev->work_flags);
+                       return;
+               } else if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) {
                        led_cdev->new_blink_brightness = brightness;
+                       set_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags);
+                       return;
                }
+       } else if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) {
+               /*
+                * If we need to disable soft blinking delegate this to the
+                * work queue task to avoid problems in case we are called
+                * from hard irq context.
+                */
+               set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags);
+               queue_work(led_cdev->wq, &led_cdev->set_brightness_work);
                return;
        }