aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans de Goede <hdegoede@redhat.com>2016-11-08 08:38:46 -0500
committerJacek Anaszewski <j.anaszewski@samsung.com>2016-11-22 06:07:05 -0500
commita9c6ce57ec2f136d08141e8220a0ffaca216f7b0 (patch)
treef7a577053e7c3453ae8939d87b903dde4754b2e7
parent8338eab50ffb3399a938d723f9605383ed9f8473 (diff)
led: core: Use atomic bit-field for the blink-flags
All the LED_BLINK* flags are accessed read-modify-write from e.g. led_set_brightness and led_blink_set_oneshot while both set_brightness_work and the blink_timer may be running. If these race then the modify step done by one of them may be lost, switch the LED_BLINK* flags to a new atomic work_flags bit-field to avoid this race. Signed-off-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Jacek Anaszewski <j.anaszewski@samsung.com>
-rw-r--r--drivers/leds/led-class.c1
-rw-r--r--drivers/leds/led-core.c52
-rw-r--r--include/linux/leds.h24
3 files changed, 42 insertions, 35 deletions
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index d946eb166781..326ee6e925a2 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -204,6 +204,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
204 dev_warn(parent, "Led %s renamed to %s due to name collision", 204 dev_warn(parent, "Led %s renamed to %s due to name collision",
205 led_cdev->name, dev_name(led_cdev->dev)); 205 led_cdev->name, dev_name(led_cdev->dev));
206 206
207 led_cdev->work_flags = 0;
207#ifdef CONFIG_LEDS_TRIGGERS 208#ifdef CONFIG_LEDS_TRIGGERS
208 init_rwsem(&led_cdev->trigger_lock); 209 init_rwsem(&led_cdev->trigger_lock);
209#endif 210#endif
diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c
index 3bce44893021..bd6bb4d44f05 100644
--- a/drivers/leds/led-core.c
+++ b/drivers/leds/led-core.c
@@ -53,12 +53,13 @@ static void led_timer_function(unsigned long data)
53 53
54 if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) { 54 if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
55 led_set_brightness_nosleep(led_cdev, LED_OFF); 55 led_set_brightness_nosleep(led_cdev, LED_OFF);
56 led_cdev->flags &= ~LED_BLINK_SW; 56 clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
57 return; 57 return;
58 } 58 }
59 59
60 if (led_cdev->flags & LED_BLINK_ONESHOT_STOP) { 60 if (test_and_clear_bit(LED_BLINK_ONESHOT_STOP,
61 led_cdev->flags &= ~(LED_BLINK_ONESHOT_STOP | LED_BLINK_SW); 61 &led_cdev->work_flags)) {
62 clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
62 return; 63 return;
63 } 64 }
64 65
@@ -73,10 +74,9 @@ static void led_timer_function(unsigned long data)
73 * Do it only if there is no pending blink brightness 74 * Do it only if there is no pending blink brightness
74 * change, to avoid overwriting the new value. 75 * change, to avoid overwriting the new value.
75 */ 76 */
76 if (!(led_cdev->flags & LED_BLINK_BRIGHTNESS_CHANGE)) 77 if (!test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE,
78 &led_cdev->work_flags))
77 led_cdev->blink_brightness = brightness; 79 led_cdev->blink_brightness = brightness;
78 else
79 led_cdev->flags &= ~LED_BLINK_BRIGHTNESS_CHANGE;
80 brightness = LED_OFF; 80 brightness = LED_OFF;
81 delay = led_cdev->blink_delay_off; 81 delay = led_cdev->blink_delay_off;
82 } 82 }
@@ -87,13 +87,15 @@ static void led_timer_function(unsigned long data)
87 * the final blink state so that the led is toggled each delay_on + 87 * the final blink state so that the led is toggled each delay_on +
88 * delay_off milliseconds in worst case. 88 * delay_off milliseconds in worst case.
89 */ 89 */
90 if (led_cdev->flags & LED_BLINK_ONESHOT) { 90 if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags)) {
91 if (led_cdev->flags & LED_BLINK_INVERT) { 91 if (test_bit(LED_BLINK_INVERT, &led_cdev->work_flags)) {
92 if (brightness) 92 if (brightness)
93 led_cdev->flags |= LED_BLINK_ONESHOT_STOP; 93 set_bit(LED_BLINK_ONESHOT_STOP,
94 &led_cdev->work_flags);
94 } else { 95 } else {
95 if (!brightness) 96 if (!brightness)
96 led_cdev->flags |= LED_BLINK_ONESHOT_STOP; 97 set_bit(LED_BLINK_ONESHOT_STOP,
98 &led_cdev->work_flags);
97 } 99 }
98 } 100 }
99 101
@@ -106,10 +108,9 @@ static void set_brightness_delayed(struct work_struct *ws)
106 container_of(ws, struct led_classdev, set_brightness_work); 108 container_of(ws, struct led_classdev, set_brightness_work);
107 int ret = 0; 109 int ret = 0;
108 110
109 if (led_cdev->flags & LED_BLINK_DISABLE) { 111 if (test_and_clear_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) {
110 led_cdev->delayed_set_value = LED_OFF; 112 led_cdev->delayed_set_value = LED_OFF;
111 led_stop_software_blink(led_cdev); 113 led_stop_software_blink(led_cdev);
112 led_cdev->flags &= ~LED_BLINK_DISABLE;
113 } 114 }
114 115
115 ret = __led_set_brightness(led_cdev, led_cdev->delayed_set_value); 116 ret = __led_set_brightness(led_cdev, led_cdev->delayed_set_value);
@@ -152,7 +153,7 @@ static void led_set_software_blink(struct led_classdev *led_cdev,
152 return; 153 return;
153 } 154 }
154 155
155 led_cdev->flags |= LED_BLINK_SW; 156 set_bit(LED_BLINK_SW, &led_cdev->work_flags);
156 mod_timer(&led_cdev->blink_timer, jiffies + 1); 157 mod_timer(&led_cdev->blink_timer, jiffies + 1);
157} 158}
158 159
@@ -161,7 +162,7 @@ static void led_blink_setup(struct led_classdev *led_cdev,
161 unsigned long *delay_on, 162 unsigned long *delay_on,
162 unsigned long *delay_off) 163 unsigned long *delay_off)
163{ 164{
164 if (!(led_cdev->flags & LED_BLINK_ONESHOT) && 165 if (!test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) &&
165 led_cdev->blink_set && 166 led_cdev->blink_set &&
166 !led_cdev->blink_set(led_cdev, delay_on, delay_off)) 167 !led_cdev->blink_set(led_cdev, delay_on, delay_off))
167 return; 168 return;
@@ -188,8 +189,8 @@ void led_blink_set(struct led_classdev *led_cdev,
188{ 189{
189 del_timer_sync(&led_cdev->blink_timer); 190 del_timer_sync(&led_cdev->blink_timer);
190 191
191 led_cdev->flags &= ~LED_BLINK_ONESHOT; 192 clear_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags);
192 led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; 193 clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags);
193 194
194 led_blink_setup(led_cdev, delay_on, delay_off); 195 led_blink_setup(led_cdev, delay_on, delay_off);
195} 196}
@@ -200,17 +201,17 @@ void led_blink_set_oneshot(struct led_classdev *led_cdev,
200 unsigned long *delay_off, 201 unsigned long *delay_off,
201 int invert) 202 int invert)
202{ 203{
203 if ((led_cdev->flags & LED_BLINK_ONESHOT) && 204 if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) &&
204 timer_pending(&led_cdev->blink_timer)) 205 timer_pending(&led_cdev->blink_timer))
205 return; 206 return;
206 207
207 led_cdev->flags |= LED_BLINK_ONESHOT; 208 set_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags);
208 led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; 209 clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags);
209 210
210 if (invert) 211 if (invert)
211 led_cdev->flags |= LED_BLINK_INVERT; 212 set_bit(LED_BLINK_INVERT, &led_cdev->work_flags);
212 else 213 else
213 led_cdev->flags &= ~LED_BLINK_INVERT; 214 clear_bit(LED_BLINK_INVERT, &led_cdev->work_flags);
214 215
215 led_blink_setup(led_cdev, delay_on, delay_off); 216 led_blink_setup(led_cdev, delay_on, delay_off);
216} 217}
@@ -221,7 +222,7 @@ void led_stop_software_blink(struct led_classdev *led_cdev)
221 del_timer_sync(&led_cdev->blink_timer); 222 del_timer_sync(&led_cdev->blink_timer);
222 led_cdev->blink_delay_on = 0; 223 led_cdev->blink_delay_on = 0;
223 led_cdev->blink_delay_off = 0; 224 led_cdev->blink_delay_off = 0;
224 led_cdev->flags &= ~LED_BLINK_SW; 225 clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
225} 226}
226EXPORT_SYMBOL_GPL(led_stop_software_blink); 227EXPORT_SYMBOL_GPL(led_stop_software_blink);
227 228
@@ -232,17 +233,18 @@ void led_set_brightness(struct led_classdev *led_cdev,
232 * If software blink is active, delay brightness setting 233 * If software blink is active, delay brightness setting
233 * until the next timer tick. 234 * until the next timer tick.
234 */ 235 */
235 if (led_cdev->flags & LED_BLINK_SW) { 236 if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) {
236 /* 237 /*
237 * If we need to disable soft blinking delegate this to the 238 * If we need to disable soft blinking delegate this to the
238 * work queue task to avoid problems in case we are called 239 * work queue task to avoid problems in case we are called
239 * from hard irq context. 240 * from hard irq context.
240 */ 241 */
241 if (brightness == LED_OFF) { 242 if (brightness == LED_OFF) {
242 led_cdev->flags |= LED_BLINK_DISABLE; 243 set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags);
243 schedule_work(&led_cdev->set_brightness_work); 244 schedule_work(&led_cdev->set_brightness_work);
244 } else { 245 } else {
245 led_cdev->flags |= LED_BLINK_BRIGHTNESS_CHANGE; 246 set_bit(LED_BLINK_BRIGHTNESS_CHANGE,
247 &led_cdev->work_flags);
246 led_cdev->blink_brightness = brightness; 248 led_cdev->blink_brightness = brightness;
247 } 249 }
248 return; 250 return;
diff --git a/include/linux/leds.h b/include/linux/leds.h
index ddfcb2df3656..21c598b366f8 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -42,16 +42,20 @@ struct led_classdev {
42#define LED_UNREGISTERING (1 << 1) 42#define LED_UNREGISTERING (1 << 1)
43 /* Upper 16 bits reflect control information */ 43 /* Upper 16 bits reflect control information */
44#define LED_CORE_SUSPENDRESUME (1 << 16) 44#define LED_CORE_SUSPENDRESUME (1 << 16)
45#define LED_BLINK_SW (1 << 17) 45#define LED_SYSFS_DISABLE (1 << 17)
46#define LED_BLINK_ONESHOT (1 << 18) 46#define LED_DEV_CAP_FLASH (1 << 18)
47#define LED_BLINK_ONESHOT_STOP (1 << 19) 47#define LED_HW_PLUGGABLE (1 << 19)
48#define LED_BLINK_INVERT (1 << 20) 48#define LED_PANIC_INDICATOR (1 << 20)
49#define LED_BLINK_BRIGHTNESS_CHANGE (1 << 21) 49
50#define LED_BLINK_DISABLE (1 << 22) 50 /* set_brightness_work / blink_timer flags, atomic, private. */
51#define LED_SYSFS_DISABLE (1 << 23) 51 unsigned long work_flags;
52#define LED_DEV_CAP_FLASH (1 << 24) 52
53#define LED_HW_PLUGGABLE (1 << 25) 53#define LED_BLINK_SW 0
54#define LED_PANIC_INDICATOR (1 << 26) 54#define LED_BLINK_ONESHOT 1
55#define LED_BLINK_ONESHOT_STOP 2
56#define LED_BLINK_INVERT 3
57#define LED_BLINK_BRIGHTNESS_CHANGE 4
58#define LED_BLINK_DISABLE 5
55 59
56 /* Set LED brightness level 60 /* Set LED brightness level
57 * Must not sleep. Use brightness_set_blocking for drivers 61 * Must not sleep. Use brightness_set_blocking for drivers