Any comments? I'd like to have some acks before applying
this patch.

Adding more people showing recently interest in the LED subsystem
related development.

Thanks,
Jacek Anaszewski

On 01/17/2018 10:12 PM, Jacek Anaszewski wrote:
> Commit d23a22a74fde ("leds: delay led_set_brightness if stopping soft-blink")
> made a modifications to the LED core allowing for led_set_brightness() to be
> called from hard-irq context when soft blink is being handled in soft-irq.
> 
> Since that time LED core has undergone modifications related to addition of
> generic support for delegating brightness setting to a workqueue as well as
> subsequent fixes for blink setting use cases.
> 
> After that the LED core code became hard to maintain and analyze, especially
> due to the imposed hard-irq context compatibility. It also turned out that
> in some cases a LED remained off after executing following sequence of 
> commands:
> 
> 1. echo timer > trigger
> 2. echo 0 > brightness
> 3. echo 100 > brightness
> 
> The reason was LED_BLINK_DISABLE operation delegated to a set_brightness_work,
> triggered in 2., which was handled after 3. took effect.
> 
> In order to serialize above operations and in the same time avoid
> code overcomplication the hard-irq context compatibility is being removed,
> which allows to use spin_lock_bh() for LED blink setting serialization.
> 
>>From now, if in hard-irq context, users need to delegate led_set_brightness()
> to a workqueue in the place of use.
> 
> Reported-by: Craig McQueen <[email protected]>
> Signed-off-by: Jacek Anaszewski <[email protected]>
> ---
> Craig,
> 
> It would be great if you could confirm if this fixes your use case.
> 
>  drivers/leds/led-core.c                  | 79 
> +++++++++++++++++++-------------
>  drivers/leds/trigger/ledtrig-activity.c  |  3 --
>  drivers/leds/trigger/ledtrig-heartbeat.c |  3 --
>  include/linux/leds.h                     |  5 +-
>  4 files changed, 49 insertions(+), 41 deletions(-)
> 
> diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c
> index ede4fa0..25d711b 100644
> --- a/drivers/leds/led-core.c
> +++ b/drivers/leds/led-core.c
> @@ -22,6 +22,9 @@
>  DECLARE_RWSEM(leds_list_lock);
>  EXPORT_SYMBOL_GPL(leds_list_lock);
>  
> +DEFINE_SPINLOCK(leds_soft_blink_lock);
> +EXPORT_SYMBOL_GPL(leds_soft_blink_lock);
> +
>  LIST_HEAD(leds_list);
>  EXPORT_SYMBOL_GPL(leds_list);
>  
> @@ -51,26 +54,31 @@ static void led_timer_function(struct timer_list *t)
>       unsigned long brightness;
>       unsigned long delay;
>  
> +     spin_lock_bh(&leds_soft_blink_lock);
> +
> +     /*
> +      * Check if soft blinking wasn't disabled via led_set_brightness()
> +      * in the meantime.
> +      */
> +     if (!test_bit(LED_BLINK_SW, &led_cdev->work_flags))
> +             goto unlock;
> +
>       if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
>               led_set_brightness_nosleep(led_cdev, LED_OFF);
>               clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
> -             return;
> +             goto unlock;
>       }
>  
>       if (test_and_clear_bit(LED_BLINK_ONESHOT_STOP,
>                              &led_cdev->work_flags)) {
>               clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
> -             return;
> +             goto unlock;
>       }
>  
>       brightness = led_get_brightness(led_cdev);
>       if (!brightness) {
>               /* Time to switch the LED on. */
> -             if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE,
> -                                     &led_cdev->work_flags))
> -                     brightness = led_cdev->new_blink_brightness;
> -             else
> -                     brightness = led_cdev->blink_brightness;
> +             brightness = led_cdev->blink_brightness;
>               delay = led_cdev->blink_delay_on;
>       } else {
>               /* Store the current brightness value to be able
> @@ -100,6 +108,9 @@ static void led_timer_function(struct timer_list *t)
>       }
>  
>       mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
> +
> +unlock:
> +     spin_unlock_bh(&leds_soft_blink_lock);
>  }
>  
>  static void set_brightness_delayed(struct work_struct *ws)
> @@ -108,11 +119,6 @@ static void set_brightness_delayed(struct work_struct 
> *ws)
>               container_of(ws, struct led_classdev, set_brightness_work);
>       int ret = 0;
>  
> -     if (test_and_clear_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) {
> -             led_cdev->delayed_set_value = LED_OFF;
> -             led_stop_software_blink(led_cdev);
> -     }
> -
>       ret = __led_set_brightness(led_cdev, led_cdev->delayed_set_value);
>       if (ret == -ENOTSUPP)
>               ret = __led_set_brightness_blocking(led_cdev,
> @@ -131,6 +137,8 @@ static void led_set_software_blink(struct led_classdev 
> *led_cdev,
>  {
>       int current_brightness;
>  
> +     spin_lock_bh(&leds_soft_blink_lock);
> +
>       current_brightness = led_get_brightness(led_cdev);
>       if (current_brightness)
>               led_cdev->blink_brightness = current_brightness;
> @@ -143,18 +151,21 @@ static void led_set_software_blink(struct led_classdev 
> *led_cdev,
>       /* never on - just set to off */
>       if (!delay_on) {
>               led_set_brightness_nosleep(led_cdev, LED_OFF);
> -             return;
> +             goto unlock;
>       }
>  
>       /* never off - just set to brightness */
>       if (!delay_off) {
>               led_set_brightness_nosleep(led_cdev,
>                                          led_cdev->blink_brightness);
> -             return;
> +             goto unlock;
>       }
>  
>       set_bit(LED_BLINK_SW, &led_cdev->work_flags);
>       mod_timer(&led_cdev->blink_timer, jiffies + 1);
> +
> +unlock:
> +     spin_unlock_bh(&leds_soft_blink_lock);
>  }
>  
>  
> @@ -217,40 +228,46 @@ void led_blink_set_oneshot(struct led_classdev 
> *led_cdev,
>  }
>  EXPORT_SYMBOL_GPL(led_blink_set_oneshot);
>  
> -void led_stop_software_blink(struct led_classdev *led_cdev)
> +void led_stop_software_blink_nolock(struct led_classdev *led_cdev)
>  {
> -     del_timer_sync(&led_cdev->blink_timer);
>       led_cdev->blink_delay_on = 0;
>       led_cdev->blink_delay_off = 0;
>       clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
>  }
> +
> +void led_stop_software_blink(struct led_classdev *led_cdev)
> +{
> +     spin_lock_bh(&leds_soft_blink_lock);
> +     led_stop_software_blink_nolock(led_cdev);
> +     spin_unlock_bh(&leds_soft_blink_lock);
> +}
>  EXPORT_SYMBOL_GPL(led_stop_software_blink);
>  
>  void led_set_brightness(struct led_classdev *led_cdev,
>                       enum led_brightness brightness)
>  {
> -     /*
> -      * If software blink is active, delay brightness setting
> -      * until the next timer tick.
> -      */
> +     if (in_irq()) {
> +             dev_err(led_cdev->dev,
> +                     "Cannot set LED brightness from hard irq context!");
> +             WARN_ON(1);
> +             return;
> +     }
> +
> +     spin_lock_bh(&leds_soft_blink_lock);
> +
>       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.
> -              */
>               if (brightness == LED_OFF) {
> -                     set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags);
> -                     schedule_work(&led_cdev->set_brightness_work);
> +                     led_stop_software_blink_nolock(led_cdev);
>               } else {
> -                     set_bit(LED_BLINK_BRIGHTNESS_CHANGE,
> -                             &led_cdev->work_flags);
> -                     led_cdev->new_blink_brightness = brightness;
> +                     led_cdev->blink_brightness = brightness;
> +                     goto unlock;
>               }
> -             return;
>       }
>  
>       led_set_brightness_nosleep(led_cdev, brightness);
> +
> +unlock:
> +     spin_unlock_bh(&leds_soft_blink_lock);
>  }
>  EXPORT_SYMBOL_GPL(led_set_brightness);
>  
> diff --git a/drivers/leds/trigger/ledtrig-activity.c 
> b/drivers/leds/trigger/ledtrig-activity.c
> index 5081894..6f62da7 100644
> --- a/drivers/leds/trigger/ledtrig-activity.c
> +++ b/drivers/leds/trigger/ledtrig-activity.c
> @@ -48,9 +48,6 @@ static void led_activity_function(struct timer_list *t)
>       int cpus;
>       int i;
>  
> -     if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, 
> &led_cdev->work_flags))
> -             led_cdev->blink_brightness = led_cdev->new_blink_brightness;
> -
>       if (unlikely(panic_detected)) {
>               /* full brightness in case of panic */
>               led_set_brightness_nosleep(led_cdev, 
> led_cdev->blink_brightness);
> diff --git a/drivers/leds/trigger/ledtrig-heartbeat.c 
> b/drivers/leds/trigger/ledtrig-heartbeat.c
> index f0896de..1453352 100644
> --- a/drivers/leds/trigger/ledtrig-heartbeat.c
> +++ b/drivers/leds/trigger/ledtrig-heartbeat.c
> @@ -47,9 +47,6 @@ static void led_heartbeat_function(struct timer_list *t)
>               return;
>       }
>  
> -     if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, 
> &led_cdev->work_flags))
> -             led_cdev->blink_brightness = led_cdev->new_blink_brightness;
> -
>       /* acts like an actual heart beat -- ie thump-thump-pause... */
>       switch (heartbeat_data->phase) {
>       case 0:
> diff --git a/include/linux/leds.h b/include/linux/leds.h
> index 5579c64..e520503 100644
> --- a/include/linux/leds.h
> +++ b/include/linux/leds.h
> @@ -51,15 +51,13 @@ struct led_classdev {
>  #define LED_BRIGHT_HW_CHANGED        BIT(21)
>  #define LED_RETAIN_AT_SHUTDOWN       BIT(22)
>  
> -     /* set_brightness_work / blink_timer flags, atomic, private. */
> +     /* blink_timer flags, atomic, private. */
>       unsigned long           work_flags;
>  
>  #define LED_BLINK_SW                 0
>  #define LED_BLINK_ONESHOT            1
>  #define LED_BLINK_ONESHOT_STOP               2
>  #define LED_BLINK_INVERT             3
> -#define LED_BLINK_BRIGHTNESS_CHANGE  4
> -#define LED_BLINK_DISABLE            5
>  
>       /* Set LED brightness level
>        * Must not sleep. Use brightness_set_blocking for drivers
> @@ -97,7 +95,6 @@ struct led_classdev {
>       unsigned long            blink_delay_on, blink_delay_off;
>       struct timer_list        blink_timer;
>       int                      blink_brightness;
> -     int                      new_blink_brightness;
>       void                    (*flash_resume)(struct led_classdev *led_cdev);
>  
>       struct work_struct      set_brightness_work;
> 

Reply via email to