diff mbox series

leds: core: Support blocking HW blink operations

Message ID 20181127150106.20213-1-linus.walleij@linaro.org
State Superseded
Headers show
Series leds: core: Support blocking HW blink operations | expand

Commit Message

Linus Walleij Nov. 27, 2018, 3:01 p.m. UTC
I ran into this when working on a keyboard driver for
the Razer family: the .blink_set() callback for setting
hardware blinking on a LED only exist in a non-blocking
(fastpath) variant, such as when blinking can be enabled
by simply writing a memory-mapped register and protected
by spinlocks.

On USB keyboards with blinkable LEDs controlled with USB
out-of-band commands this will of course not work: these
calls need to come from process context.

To support this: add a new .blink_set_blocking() callback
in the same vein as .brightness_set_blocking() and add
a flag and some code to the delayed work so that this
will be able to fire the .blink_set_blocking() call.

ALl of this will be handled transparently from the
led_blink_set() call so all current users can keep
using that.

Signed-off-by: Linus Walleij <linus.walleij@linaro.org>

---
 drivers/leds/led-core.c | 49 ++++++++++++++++++++++++++++++++++++++---
 include/linux/leds.h    |  9 ++++++++
 2 files changed, 55 insertions(+), 3 deletions(-)

-- 
2.19.1

Comments

Pavel Machek Nov. 27, 2018, 6:51 p.m. UTC | #1
Nits:

> ALl of this will be handled transparently from the

> led_blink_set() call so all current users can keep

> using that.


"All". Plus, your lines are rather short.

> 

> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>

> ---

>  drivers/leds/led-core.c | 49 ++++++++++++++++++++++++++++++++++++++---

>  include/linux/leds.h    |  9 ++++++++

>  2 files changed, 55 insertions(+), 3 deletions(-)

> 

> diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c

> index ede4fa0ac2cc..94870a3d59af 100644

> --- a/drivers/leds/led-core.c

> +++ b/drivers/leds/led-core.c

> @@ -108,6 +108,20 @@ 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_HW_SET, &led_cdev->work_flags)) {

> +		if (led_cdev->blink_set)

> +			ret = led_cdev->blink_set(led_cdev,

> +						  &led_cdev->blink_delay_on,

> +						   &led_cdev->blink_delay_off);


How can we get here?

> +		else if (led_cdev->blink_set_blocking)

> +			ret = led_cdev->blink_set_blocking(led_cdev,

> +						&led_cdev->blink_delay_on,

> +						&led_cdev->blink_delay_off);

> +		if (ret)

> +			dev_err(led_cdev->dev,

> +				"setting hardware blink failed (%d)\n", ret);

> +	}


This is a bit sad, as it disallows returning errors back to
userspace. And I'm pretty sure little hardware can support all the
possible delays....

>  	mod_timer(&led_cdev->blink_timer, jiffies + 1);

>  }

>  

> +static int led_set_hardware_blink_nosleep(struct led_classdev *led_cdev,

> +					   unsigned long *delay_on,

> +					   unsigned long *delay_off)

> +{

> +	/* We have a non-blocking call, use it */

> +	if (led_cdev->blink_set)

> +		return led_cdev->blink_set(led_cdev, delay_on, delay_off);

> +

> +	/* Tell the worker to set up the blinking hardware */

> +	if (delay_on)

> +		led_cdev->blink_delay_on = *delay_on;

> +	else

> +		led_cdev->blink_delay_on = 0;

> +	if (delay_off)

> +		led_cdev->blink_delay_off = *delay_off;

> +	else

> +		led_cdev->blink_delay_off = 0;

> +	set_bit(LED_BLINK_HW_SET, &led_cdev->work_flags);

> +	schedule_work(&led_cdev->set_brightness_work);

> +

> +	return 0;

> +}

>  

>  static void led_blink_setup(struct led_classdev *led_cdev,

>  		     unsigned long *delay_on,

>  		     unsigned long *delay_off)

>  {

> +	/*

> +	 * If not oneshot, and we have hardware support for blinking,

> +	 * relay the request to the driver.

> +	 */

>  	if (!test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) &&

> -	    led_cdev->blink_set &&

> -	    !led_cdev->blink_set(led_cdev, delay_on, delay_off))

> -		return;

> +	    (led_cdev->blink_set || led_cdev->blink_set_blocking)) {

> +		/* If this fails we fall back to software blink */

> +		if (!led_set_hardware_blink_nosleep(led_cdev,

> +						    delay_on, delay_off))

> +			return;

> +	}

>  


So, when can this actually be called from atomic context?

(And deferring to work queue to set hardware blinking for one shot
... does not make sense.)

led_trigger_blink_setup uses read_lock() -- unsafe from atomic AFAICT.

									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
Pavel Machek June 1, 2019, 8:37 p.m. UTC | #2
On Tue 2018-11-27 16:01:06, Linus Walleij wrote:
> I ran into this when working on a keyboard driver for

> the Razer family: the .blink_set() callback for setting

> hardware blinking on a LED only exist in a non-blocking

> (fastpath) variant, such as when blinking can be enabled

> by simply writing a memory-mapped register and protected

> by spinlocks.

> 

> On USB keyboards with blinkable LEDs controlled with USB

> out-of-band commands this will of course not work: these

> calls need to come from process context.

> 

> To support this: add a new .blink_set_blocking() callback

> in the same vein as .brightness_set_blocking() and add

> a flag and some code to the delayed work so that this

> will be able to fire the .blink_set_blocking() call.

> 

> ALl of this will be handled transparently from the

> led_blink_set() call so all current users can keep

> using that.

> 

> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>


Fun. I just realized thinkpad x60 needs something similar...

-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
Linus Walleij June 1, 2019, 9:47 p.m. UTC | #3
On Sat, Jun 1, 2019 at 10:37 PM Pavel Machek <pavel@ucw.cz> wrote:
> On Tue 2018-11-27 16:01:06, Linus Walleij wrote:


> > I ran into this when working on a keyboard driver for

> > the Razer family: the .blink_set() callback for setting

> > hardware blinking on a LED only exist in a non-blocking

> > (fastpath) variant, such as when blinking can be enabled

> > by simply writing a memory-mapped register and protected

> > by spinlocks.

> >

> > On USB keyboards with blinkable LEDs controlled with USB

> > out-of-band commands this will of course not work: these

> > calls need to come from process context.

> >

> > To support this: add a new .blink_set_blocking() callback

> > in the same vein as .brightness_set_blocking() and add

> > a flag and some code to the delayed work so that this

> > will be able to fire the .blink_set_blocking() call.

> >

> > ALl of this will be handled transparently from the

> > led_blink_set() call so all current users can keep

> > using that.

> >

> > Signed-off-by: Linus Walleij <linus.walleij@linaro.org>

>

> Fun. I just realized thinkpad x60 needs something similar...


Hm yeah. The discussion with Jacek came to the conclusion that he
thinks (if I understand correctly!) that the LED core is too helpful and
client drivers should create process contexts instead (like workers
I suppose) and use the opaque interfaces from there, whether they
are blocking or not, and that it was a mistake from the beginning
to create a helper thread inside the LED core.

I like APIs that are narrow and deep so I would prefer to do it my
way (i.e. this patch) but arguably it is a matter of taste.

I hope to get back to this patch set at some point.

Yours,
Linus Walleij
Jacek Anaszewski June 2, 2019, 11:20 a.m. UTC | #4
On 6/1/19 11:47 PM, Linus Walleij wrote:
> On Sat, Jun 1, 2019 at 10:37 PM Pavel Machek <pavel@ucw.cz> wrote:

>> On Tue 2018-11-27 16:01:06, Linus Walleij wrote:

> 

>>> I ran into this when working on a keyboard driver for

>>> the Razer family: the .blink_set() callback for setting

>>> hardware blinking on a LED only exist in a non-blocking

>>> (fastpath) variant, such as when blinking can be enabled

>>> by simply writing a memory-mapped register and protected

>>> by spinlocks.

>>>

>>> On USB keyboards with blinkable LEDs controlled with USB

>>> out-of-band commands this will of course not work: these

>>> calls need to come from process context.

>>>

>>> To support this: add a new .blink_set_blocking() callback

>>> in the same vein as .brightness_set_blocking() and add

>>> a flag and some code to the delayed work so that this

>>> will be able to fire the .blink_set_blocking() call.

>>>

>>> ALl of this will be handled transparently from the

>>> led_blink_set() call so all current users can keep

>>> using that.

>>>

>>> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>

>>

>> Fun. I just realized thinkpad x60 needs something similar...

> 

> Hm yeah. The discussion with Jacek came to the conclusion that he

> thinks (if I understand correctly!) that the LED core is too helpful and

> client drivers should create process contexts instead (like workers

> I suppose) and use the opaque interfaces from there, whether they

> are blocking or not, and that it was a mistake from the beginning

> to create a helper thread inside the LED core.

> 

> I like APIs that are narrow and deep so I would prefer to do it my

> way (i.e. this patch) but arguably it is a matter of taste.

> 

> I hope to get back to this patch set at some point.


Well, yes, I missed the fact that we already had the use case
in mainline where blink_set is called from atomic context,
which is led_trigger_blink{_oneshot}().

So, Pavel, you seem to have good setup for testing this Linus'
patch.

-- 
Best regards,
Jacek Anaszewski
Pavel Machek June 2, 2019, 9:05 p.m. UTC | #5
On Sun 2019-06-02 13:20:20, Jacek Anaszewski wrote:
> On 6/1/19 11:47 PM, Linus Walleij wrote:

> >On Sat, Jun 1, 2019 at 10:37 PM Pavel Machek <pavel@ucw.cz> wrote:

> >>On Tue 2018-11-27 16:01:06, Linus Walleij wrote:

> >

> >>>I ran into this when working on a keyboard driver for

> >>>the Razer family: the .blink_set() callback for setting

> >>>hardware blinking on a LED only exist in a non-blocking

> >>>(fastpath) variant, such as when blinking can be enabled

> >>>by simply writing a memory-mapped register and protected

> >>>by spinlocks.

> >>>

> >>>On USB keyboards with blinkable LEDs controlled with USB

> >>>out-of-band commands this will of course not work: these

> >>>calls need to come from process context.

> >>>

> >>>To support this: add a new .blink_set_blocking() callback

> >>>in the same vein as .brightness_set_blocking() and add

> >>>a flag and some code to the delayed work so that this

> >>>will be able to fire the .blink_set_blocking() call.

> >>>

> >>>ALl of this will be handled transparently from the

> >>>led_blink_set() call so all current users can keep

> >>>using that.

> >>>

> >>>Signed-off-by: Linus Walleij <linus.walleij@linaro.org>

> >>

> >>Fun. I just realized thinkpad x60 needs something similar...

> >

> >Hm yeah. The discussion with Jacek came to the conclusion that he

> >thinks (if I understand correctly!) that the LED core is too helpful and

> >client drivers should create process contexts instead (like workers

> >I suppose) and use the opaque interfaces from there, whether they

> >are blocking or not, and that it was a mistake from the beginning

> >to create a helper thread inside the LED core.

> >

> >I like APIs that are narrow and deep so I would prefer to do it my

> >way (i.e. this patch) but arguably it is a matter of taste.

> >

> >I hope to get back to this patch set at some point.

> 

> Well, yes, I missed the fact that we already had the use case

> in mainline where blink_set is called from atomic context,

> which is led_trigger_blink{_oneshot}().


Yep.

> So, Pavel, you seem to have good setup for testing this Linus'

> patch.


OTOH that is _not_ passed down to drivers, as they can blink, but not
do oneshot, and blinking on X60 is limited to single frequency so
likely not useful for triggers.

But we probably want to annotate can/can not sleep and use lockdep to
catch the bugs.

									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
diff mbox series

Patch

diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c
index ede4fa0ac2cc..94870a3d59af 100644
--- a/drivers/leds/led-core.c
+++ b/drivers/leds/led-core.c
@@ -108,6 +108,20 @@  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_HW_SET, &led_cdev->work_flags)) {
+		if (led_cdev->blink_set)
+			ret = led_cdev->blink_set(led_cdev,
+						  &led_cdev->blink_delay_on,
+						  &led_cdev->blink_delay_off);
+		else if (led_cdev->blink_set_blocking)
+			ret = led_cdev->blink_set_blocking(led_cdev,
+						&led_cdev->blink_delay_on,
+						&led_cdev->blink_delay_off);
+		if (ret)
+			dev_err(led_cdev->dev,
+				"setting hardware blink failed (%d)\n", ret);
+	}
+
 	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);
@@ -157,15 +171,44 @@  static void led_set_software_blink(struct led_classdev *led_cdev,
 	mod_timer(&led_cdev->blink_timer, jiffies + 1);
 }
 
+static int led_set_hardware_blink_nosleep(struct led_classdev *led_cdev,
+					   unsigned long *delay_on,
+					   unsigned long *delay_off)
+{
+	/* We have a non-blocking call, use it */
+	if (led_cdev->blink_set)
+		return led_cdev->blink_set(led_cdev, delay_on, delay_off);
+
+	/* Tell the worker to set up the blinking hardware */
+	if (delay_on)
+		led_cdev->blink_delay_on = *delay_on;
+	else
+		led_cdev->blink_delay_on = 0;
+	if (delay_off)
+		led_cdev->blink_delay_off = *delay_off;
+	else
+		led_cdev->blink_delay_off = 0;
+	set_bit(LED_BLINK_HW_SET, &led_cdev->work_flags);
+	schedule_work(&led_cdev->set_brightness_work);
+
+	return 0;
+}
 
 static void led_blink_setup(struct led_classdev *led_cdev,
 		     unsigned long *delay_on,
 		     unsigned long *delay_off)
 {
+	/*
+	 * If not oneshot, and we have hardware support for blinking,
+	 * relay the request to the driver.
+	 */
 	if (!test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) &&
-	    led_cdev->blink_set &&
-	    !led_cdev->blink_set(led_cdev, delay_on, delay_off))
-		return;
+	    (led_cdev->blink_set || led_cdev->blink_set_blocking)) {
+		/* If this fails we fall back to software blink */
+		if (!led_set_hardware_blink_nosleep(led_cdev,
+						    delay_on, delay_off))
+			return;
+	}
 
 	/* blink with 1 Hz as default if nothing specified */
 	if (!*delay_on && !*delay_off)
diff --git a/include/linux/leds.h b/include/linux/leds.h
index 7393a316d9fa..57f3df0774e7 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -61,6 +61,7 @@  struct led_classdev {
 #define LED_BLINK_INVERT		3
 #define LED_BLINK_BRIGHTNESS_CHANGE 	4
 #define LED_BLINK_DISABLE		5
+#define LED_BLINK_HW_SET		6
 
 	/* Set LED brightness level
 	 * Must not sleep. Use brightness_set_blocking for drivers
@@ -88,6 +89,14 @@  struct led_classdev {
 	int		(*blink_set)(struct led_classdev *led_cdev,
 				     unsigned long *delay_on,
 				     unsigned long *delay_off);
+	/*
+	 * Set LED blinking immediately but may sleep (block) the caller
+	 * to access the LED device register.
+	 */
+	int		(*blink_set_blocking)(struct led_classdev *led_cdev,
+					      unsigned long *delay_on,
+					      unsigned long *delay_off);
+
 
 	int (*pattern_set)(struct led_classdev *led_cdev,
 			   struct led_pattern *pattern, u32 len, int repeat);