Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

leds: Introduce ordered workqueue for LEDs events instead of system_wq

This allows to setup ordered workqueue for LEDs events. This may be
useful, because default 'system_wq' does not guarantee execution order
of each work_struct, thus for several brightness update requests (for
multiple LEDs), real brightness switch could be in random order.

Yes, for sysfs-based LEDs we have flush_work() call inside
brightness_store() operation, but it's blocking call, so userspace
caller can be blocked at a long time, which means LEDs animation stream
can be broken.

Ordered workqueue has the same behaviour as system_wq + flush_work(),
but all scheduled works are async and userspace caller is not blocked,
which it better for userspace animation scheduling.

Signed-off-by: Alexey Romanov <avromanov@salutedevices.com>
Signed-off-by: Dmitry Rokosov <ddrokosov@salutedevices.com>
Link: https://lore.kernel.org/r/20240903223936.21292-1-ddrokosov@salutedevices.com
[Lee: Couple of style fix-ups]
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Dmitry Rokosov and committed by
Lee Jones
32360bf6 64dd44a6

+15 -4
+11 -1
drivers/leds/led-class.c
··· 25 25 static DEFINE_MUTEX(leds_lookup_lock); 26 26 static LIST_HEAD(leds_lookup_list); 27 27 28 + static struct workqueue_struct *leds_wq; 29 + 28 30 static ssize_t brightness_show(struct device *dev, 29 31 struct device_attribute *attr, char *buf) 30 32 { ··· 59 57 if (state == LED_OFF) 60 58 led_trigger_remove(led_cdev); 61 59 led_set_brightness(led_cdev, state); 62 - flush_work(&led_cdev->set_brightness_work); 63 60 64 61 ret = size; 65 62 unlock: ··· 550 549 551 550 led_update_brightness(led_cdev); 552 551 552 + led_cdev->wq = leds_wq; 553 + 553 554 led_init_core(led_cdev); 554 555 555 556 #ifdef CONFIG_LEDS_TRIGGERS ··· 670 667 671 668 static int __init leds_init(void) 672 669 { 670 + leds_wq = alloc_ordered_workqueue("leds", 0); 671 + if (!leds_wq) { 672 + pr_err("Failed to create LEDs ordered workqueue\n"); 673 + return -ENOMEM; 674 + } 675 + 673 676 return class_register(&leds_class); 674 677 } 675 678 676 679 static void __exit leds_exit(void) 677 680 { 678 681 class_unregister(&leds_class); 682 + destroy_workqueue(leds_wq); 679 683 } 680 684 681 685 subsys_initcall(leds_init);
+3 -3
drivers/leds/led-core.c
··· 273 273 led_cdev->delayed_delay_on = delay_on; 274 274 led_cdev->delayed_delay_off = delay_off; 275 275 set_bit(LED_SET_BLINK, &led_cdev->work_flags); 276 - schedule_work(&led_cdev->set_brightness_work); 276 + queue_work(led_cdev->wq, &led_cdev->set_brightness_work); 277 277 return; 278 278 } 279 279 ··· 304 304 */ 305 305 if (!brightness) { 306 306 set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags); 307 - schedule_work(&led_cdev->set_brightness_work); 307 + queue_work(led_cdev->wq, &led_cdev->set_brightness_work); 308 308 } else { 309 309 set_bit(LED_BLINK_BRIGHTNESS_CHANGE, 310 310 &led_cdev->work_flags); ··· 340 340 set_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags); 341 341 } 342 342 343 - schedule_work(&led_cdev->set_brightness_work); 343 + queue_work(led_cdev->wq, &led_cdev->set_brightness_work); 344 344 } 345 345 EXPORT_SYMBOL_GPL(led_set_brightness_nopm); 346 346
+1
include/linux/leds.h
··· 171 171 int new_blink_brightness; 172 172 void (*flash_resume)(struct led_classdev *led_cdev); 173 173 174 + struct workqueue_struct *wq; /* LED workqueue */ 174 175 struct work_struct set_brightness_work; 175 176 int delayed_set_value; 176 177 unsigned long delayed_delay_on;