summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig McQueen <craig@mcqueen.au>2026-04-23 14:36:38 +0300
committerLee Jones <lee@kernel.org>2026-06-17 13:29:05 +0300
commitf92135f100669b508dd62b424ab20bcb33494c79 (patch)
treea71098deb0df6087c2c28593f26c307fdd484d3a
parent6ebff754c46e2277c831670fa1f0b5043250eb77 (diff)
downloadlinux-f92135f100669b508dd62b424ab20bcb33494c79.tar.xz
leds: core: Fix race condition for software blink
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>
-rw-r--r--drivers/leds/led-core.c33
1 files changed, 20 insertions, 13 deletions
diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c
index 385e78af1dac..073c547068cc 100644
--- a/drivers/leds/led-core.c
+++ b/drivers/leds/led-core.c
@@ -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;
}