From: Jacek Anaszewski <jacek.anaszewski@gmail.com>
To: linux-leds@vger.kernel.org
Cc: linux-kernel@vger.kernel.org, pavel@ucw.cz,
craig.mcqueen@innerrange.com.au,
"Hans de Goede" <hdegoede@redhat.com>,
"Sakari Ailus" <sakari.ailus@linux.intel.com>,
"Matthieu CASTET" <matthieu.castet@parrot.com>,
"Dan Murphy" <dmurphy@ti.com>,
"Andy Shevchenko" <andriy.shevchenko@linux.intel.com>,
"Ben Whitten" <ben.whitten@gmail.com>,
"Bjorn Andersson" <bjorn.andersson@linaro.org>,
"Andrew Lunn" <andrew@lunn.ch>,
"Alan Mizrahi" <alan@mizrahi.com.ve>,
"Vadim Pasternak" <vadimp@mellanox.com>,
"David Lin" <dtwlin@google.com>, "Joel Stanley" <joel@jms.id.au>,
"Cédric Le Goater" <clg@kaod.org>, "Willy Tarreau" <w@1wt.eu>,
"Andrew Jeffery" <andrew@aj.id.au>,
"Javier Martinez Canillas" <javier@dowhile0.org>,
"Colin King" <colin.king@canonical.com>
Subject: Re: [PATCH] led: core: Fix race on software blink cancellation
Date: Thu, 8 Feb 2018 21:55:49 +0100 [thread overview]
Message-ID: <57a6d1f7-c1d0-3871-fed8-84d094dd079f@gmail.com> (raw)
In-Reply-To: <1516223562-3837-1-git-send-email-jacek.anaszewski@gmail.com>
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 <craig.mcqueen@innerrange.com.au>
> Signed-off-by: Jacek Anaszewski <jacek.anaszewski@gmail.com>
> ---
> 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;
>
next prev parent reply other threads:[~2018-02-08 20:57 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-01-17 21:12 Jacek Anaszewski
2018-02-08 20:55 ` Jacek Anaszewski [this message]
2018-02-08 21:50 ` Pavel Machek
2018-02-09 21:35 ` Jacek Anaszewski
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=57a6d1f7-c1d0-3871-fed8-84d094dd079f@gmail.com \
--to=jacek.anaszewski@gmail.com \
--cc=alan@mizrahi.com.ve \
--cc=andrew@aj.id.au \
--cc=andrew@lunn.ch \
--cc=andriy.shevchenko@linux.intel.com \
--cc=ben.whitten@gmail.com \
--cc=bjorn.andersson@linaro.org \
--cc=clg@kaod.org \
--cc=colin.king@canonical.com \
--cc=craig.mcqueen@innerrange.com.au \
--cc=dmurphy@ti.com \
--cc=dtwlin@google.com \
--cc=hdegoede@redhat.com \
--cc=javier@dowhile0.org \
--cc=joel@jms.id.au \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-leds@vger.kernel.org \
--cc=matthieu.castet@parrot.com \
--cc=pavel@ucw.cz \
--cc=sakari.ailus@linux.intel.com \
--cc=vadimp@mellanox.com \
--cc=w@1wt.eu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Powered by JetHome