2019-06-04 11:11:33 +03:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2006-03-31 14:31:05 +04:00
|
|
|
/*
|
|
|
|
* LED Triggers Core
|
|
|
|
*
|
2007-07-09 02:19:31 +04:00
|
|
|
* Copyright 2005-2007 Openedhand Ltd.
|
2006-03-31 14:31:05 +04:00
|
|
|
*
|
|
|
|
* Author: Richard Purdie <rpurdie@openedhand.com>
|
|
|
|
*/
|
|
|
|
|
2016-08-15 23:54:48 +03:00
|
|
|
#include <linux/export.h>
|
2006-03-31 14:31:05 +04:00
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/list.h>
|
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/timer.h>
|
2007-11-10 16:29:04 +03:00
|
|
|
#include <linux/rwsem.h>
|
2006-03-31 14:31:05 +04:00
|
|
|
#include <linux/leds.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 11:04:11 +03:00
|
|
|
#include <linux/slab.h>
|
2019-09-29 17:18:49 +03:00
|
|
|
#include <linux/mm.h>
|
2006-03-31 14:31:05 +04:00
|
|
|
#include "leds.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Nests outside led_cdev->trigger_lock
|
|
|
|
*/
|
2007-11-10 16:29:04 +03:00
|
|
|
static DECLARE_RWSEM(triggers_list_lock);
|
2016-04-29 01:03:38 +03:00
|
|
|
LIST_HEAD(trigger_list);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2008-03-09 23:59:57 +03:00
|
|
|
/* Used by LED Class */
|
|
|
|
|
2020-07-16 20:17:28 +03:00
|
|
|
static inline bool
|
|
|
|
trigger_relevant(struct led_classdev *led_cdev, struct led_trigger *trig)
|
|
|
|
{
|
|
|
|
return !trig->trigger_type || trig->trigger_type == led_cdev->trigger_type;
|
|
|
|
}
|
|
|
|
|
2019-09-29 17:18:49 +03:00
|
|
|
ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
|
|
|
|
struct bin_attribute *bin_attr, char *buf,
|
|
|
|
loff_t pos, size_t count)
|
2006-03-31 14:31:05 +04:00
|
|
|
{
|
2019-09-29 17:18:49 +03:00
|
|
|
struct device *dev = kobj_to_dev(kobj);
|
2007-07-09 02:19:31 +04:00
|
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
2006-03-31 14:31:05 +04:00
|
|
|
struct led_trigger *trig;
|
2014-09-22 19:21:04 +04:00
|
|
|
int ret = count;
|
|
|
|
|
|
|
|
mutex_lock(&led_cdev->led_access);
|
|
|
|
|
|
|
|
if (led_sysfs_is_disabled(led_cdev)) {
|
|
|
|
ret = -EBUSY;
|
|
|
|
goto unlock;
|
|
|
|
}
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2016-03-09 01:08:36 +03:00
|
|
|
if (sysfs_streq(buf, "none")) {
|
2008-03-09 23:54:37 +03:00
|
|
|
led_trigger_remove(led_cdev);
|
2014-09-22 19:21:04 +04:00
|
|
|
goto unlock;
|
2006-03-31 14:31:05 +04:00
|
|
|
}
|
|
|
|
|
2007-11-10 16:29:04 +03:00
|
|
|
down_read(&triggers_list_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
list_for_each_entry(trig, &trigger_list, next_trig) {
|
2020-07-16 20:17:28 +03:00
|
|
|
if (sysfs_streq(buf, trig->name) && trigger_relevant(led_cdev, trig)) {
|
2007-11-10 16:29:04 +03:00
|
|
|
down_write(&led_cdev->trigger_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
led_trigger_set(led_cdev, trig);
|
2007-11-10 16:29:04 +03:00
|
|
|
up_write(&led_cdev->trigger_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2007-11-10 16:29:04 +03:00
|
|
|
up_read(&triggers_list_lock);
|
2014-09-22 19:21:04 +04:00
|
|
|
goto unlock;
|
2006-03-31 14:31:05 +04:00
|
|
|
}
|
|
|
|
}
|
2016-07-02 00:08:54 +03:00
|
|
|
/* we come here only if buf matches no trigger */
|
|
|
|
ret = -EINVAL;
|
2007-11-10 16:29:04 +03:00
|
|
|
up_read(&triggers_list_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2014-09-22 19:21:04 +04:00
|
|
|
unlock:
|
|
|
|
mutex_unlock(&led_cdev->led_access);
|
|
|
|
return ret;
|
2006-03-31 14:31:05 +04:00
|
|
|
}
|
2019-09-29 17:18:49 +03:00
|
|
|
EXPORT_SYMBOL_GPL(led_trigger_write);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2019-09-29 17:18:49 +03:00
|
|
|
__printf(3, 4)
|
|
|
|
static int led_trigger_snprintf(char *buf, ssize_t size, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
va_start(args, fmt);
|
|
|
|
if (size <= 0)
|
|
|
|
i = vsnprintf(NULL, 0, fmt, args);
|
|
|
|
else
|
|
|
|
i = vscnprintf(buf, size, fmt, args);
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int led_trigger_format(char *buf, size_t size,
|
|
|
|
struct led_classdev *led_cdev)
|
2006-03-31 14:31:05 +04:00
|
|
|
{
|
|
|
|
struct led_trigger *trig;
|
2019-09-29 17:18:49 +03:00
|
|
|
int len = led_trigger_snprintf(buf, size, "%s",
|
|
|
|
led_cdev->trigger ? "none" : "[none]");
|
|
|
|
|
|
|
|
list_for_each_entry(trig, &trigger_list, next_trig) {
|
2020-07-16 20:17:28 +03:00
|
|
|
bool hit;
|
|
|
|
|
|
|
|
if (!trigger_relevant(led_cdev, trig))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
hit = led_cdev->trigger && !strcmp(led_cdev->trigger->name, trig->name);
|
2019-09-29 17:18:49 +03:00
|
|
|
|
|
|
|
len += led_trigger_snprintf(buf + len, size - len,
|
|
|
|
" %s%s%s", hit ? "[" : "",
|
|
|
|
trig->name, hit ? "]" : "");
|
|
|
|
}
|
|
|
|
|
|
|
|
len += led_trigger_snprintf(buf + len, size - len, "\n");
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* It was stupid to create 10000 cpu triggers, but we are stuck with it now.
|
|
|
|
* Don't make that mistake again. We work around it here by creating binary
|
|
|
|
* attribute, which is not limited by length. This is _not_ good design, do not
|
|
|
|
* copy it.
|
|
|
|
*/
|
|
|
|
ssize_t led_trigger_read(struct file *filp, struct kobject *kobj,
|
|
|
|
struct bin_attribute *attr, char *buf,
|
|
|
|
loff_t pos, size_t count)
|
|
|
|
{
|
|
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
|
|
void *data;
|
|
|
|
int len;
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2007-11-10 16:29:04 +03:00
|
|
|
down_read(&triggers_list_lock);
|
|
|
|
down_read(&led_cdev->trigger_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2019-09-29 17:18:49 +03:00
|
|
|
len = led_trigger_format(NULL, 0, led_cdev);
|
|
|
|
data = kvmalloc(len + 1, GFP_KERNEL);
|
|
|
|
if (!data) {
|
|
|
|
up_read(&led_cdev->trigger_lock);
|
|
|
|
up_read(&triggers_list_lock);
|
|
|
|
return -ENOMEM;
|
2006-03-31 14:31:05 +04:00
|
|
|
}
|
2019-09-29 17:18:49 +03:00
|
|
|
len = led_trigger_format(data, len + 1, led_cdev);
|
|
|
|
|
2007-11-10 16:29:04 +03:00
|
|
|
up_read(&led_cdev->trigger_lock);
|
|
|
|
up_read(&triggers_list_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2019-09-29 17:18:49 +03:00
|
|
|
len = memory_read_from_buffer(buf, count, &pos, data, len);
|
|
|
|
|
|
|
|
kvfree(data);
|
|
|
|
|
2006-03-31 14:31:05 +04:00
|
|
|
return len;
|
|
|
|
}
|
2019-09-29 17:18:49 +03:00
|
|
|
EXPORT_SYMBOL_GPL(led_trigger_read);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
|
|
|
/* Caller must ensure led_cdev->trigger_lock held */
|
2018-07-02 23:05:21 +03:00
|
|
|
int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
|
2006-03-31 14:31:05 +04:00
|
|
|
{
|
|
|
|
unsigned long flags;
|
2012-08-27 05:31:49 +04:00
|
|
|
char *event = NULL;
|
|
|
|
char *envp[2];
|
|
|
|
const char *name;
|
2018-07-02 23:05:21 +03:00
|
|
|
int ret;
|
2012-08-27 05:31:49 +04:00
|
|
|
|
2016-09-18 21:24:29 +03:00
|
|
|
if (!led_cdev->trigger && !trig)
|
2018-07-02 23:05:21 +03:00
|
|
|
return 0;
|
2016-09-18 21:24:29 +03:00
|
|
|
|
2012-08-27 05:31:49 +04:00
|
|
|
name = trig ? trig->name : "none";
|
|
|
|
event = kasprintf(GFP_KERNEL, "TRIGGER=%s", name);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
|
|
|
/* Remove any existing trigger */
|
|
|
|
if (led_cdev->trigger) {
|
|
|
|
write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);
|
|
|
|
list_del(&led_cdev->trig_list);
|
2008-03-09 23:59:57 +03:00
|
|
|
write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock,
|
|
|
|
flags);
|
2012-08-15 17:44:34 +04:00
|
|
|
cancel_work_sync(&led_cdev->set_brightness_work);
|
|
|
|
led_stop_software_blink(led_cdev);
|
2006-03-31 14:31:05 +04:00
|
|
|
if (led_cdev->trigger->deactivate)
|
|
|
|
led_cdev->trigger->deactivate(led_cdev);
|
2018-07-02 23:05:22 +03:00
|
|
|
device_remove_groups(led_cdev->dev, led_cdev->trigger->groups);
|
2008-07-17 01:51:14 +04:00
|
|
|
led_cdev->trigger = NULL;
|
2018-07-02 23:05:23 +03:00
|
|
|
led_cdev->trigger_data = NULL;
|
|
|
|
led_cdev->activated = false;
|
2012-06-14 00:34:30 +04:00
|
|
|
led_set_brightness(led_cdev, LED_OFF);
|
2006-03-31 14:31:05 +04:00
|
|
|
}
|
2012-04-19 07:46:33 +04:00
|
|
|
if (trig) {
|
|
|
|
write_lock_irqsave(&trig->leddev_list_lock, flags);
|
|
|
|
list_add_tail(&led_cdev->trig_list, &trig->led_cdevs);
|
|
|
|
write_unlock_irqrestore(&trig->leddev_list_lock, flags);
|
|
|
|
led_cdev->trigger = trig;
|
2018-07-02 23:05:21 +03:00
|
|
|
|
2012-04-19 07:46:33 +04:00
|
|
|
if (trig->activate)
|
2018-07-02 23:05:21 +03:00
|
|
|
ret = trig->activate(led_cdev);
|
|
|
|
else
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
goto err_activate;
|
2018-07-02 23:05:22 +03:00
|
|
|
|
|
|
|
ret = device_add_groups(led_cdev->dev, trig->groups);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(led_cdev->dev, "Failed to add trigger attributes\n");
|
|
|
|
goto err_add_groups;
|
|
|
|
}
|
2006-03-31 14:31:05 +04:00
|
|
|
}
|
2012-08-27 05:31:49 +04:00
|
|
|
|
|
|
|
if (event) {
|
|
|
|
envp[0] = event;
|
|
|
|
envp[1] = NULL;
|
2016-09-19 13:44:50 +03:00
|
|
|
if (kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp))
|
|
|
|
dev_err(led_cdev->dev,
|
|
|
|
"%s: Error sending uevent\n", __func__);
|
2012-08-27 05:31:49 +04:00
|
|
|
kfree(event);
|
|
|
|
}
|
2018-07-02 23:05:21 +03:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
2018-07-02 23:05:22 +03:00
|
|
|
err_add_groups:
|
|
|
|
|
|
|
|
if (trig->deactivate)
|
|
|
|
trig->deactivate(led_cdev);
|
2018-07-02 23:05:21 +03:00
|
|
|
err_activate:
|
2018-07-02 23:05:22 +03:00
|
|
|
|
2018-07-02 23:05:21 +03:00
|
|
|
write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);
|
|
|
|
list_del(&led_cdev->trig_list);
|
|
|
|
write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock, flags);
|
2019-09-04 00:18:19 +03:00
|
|
|
led_cdev->trigger = NULL;
|
|
|
|
led_cdev->trigger_data = NULL;
|
2018-07-02 23:05:21 +03:00
|
|
|
led_set_brightness(led_cdev, LED_OFF);
|
2019-08-19 23:41:42 +03:00
|
|
|
kfree(event);
|
2018-07-02 23:05:21 +03:00
|
|
|
|
|
|
|
return ret;
|
2006-03-31 14:31:05 +04:00
|
|
|
}
|
2008-03-09 23:59:57 +03:00
|
|
|
EXPORT_SYMBOL_GPL(led_trigger_set);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2008-03-09 23:54:37 +03:00
|
|
|
void led_trigger_remove(struct led_classdev *led_cdev)
|
|
|
|
{
|
|
|
|
down_write(&led_cdev->trigger_lock);
|
|
|
|
led_trigger_set(led_cdev, NULL);
|
|
|
|
up_write(&led_cdev->trigger_lock);
|
|
|
|
}
|
2008-03-09 23:59:57 +03:00
|
|
|
EXPORT_SYMBOL_GPL(led_trigger_remove);
|
2008-03-09 23:54:37 +03:00
|
|
|
|
2006-03-31 14:31:05 +04:00
|
|
|
void led_trigger_set_default(struct led_classdev *led_cdev)
|
|
|
|
{
|
|
|
|
struct led_trigger *trig;
|
|
|
|
|
|
|
|
if (!led_cdev->default_trigger)
|
|
|
|
return;
|
|
|
|
|
2007-11-10 16:29:04 +03:00
|
|
|
down_read(&triggers_list_lock);
|
|
|
|
down_write(&led_cdev->trigger_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
list_for_each_entry(trig, &trigger_list, next_trig) {
|
2020-07-16 20:17:28 +03:00
|
|
|
if (!strcmp(led_cdev->default_trigger, trig->name) &&
|
|
|
|
trigger_relevant(led_cdev, trig)) {
|
2018-12-10 12:29:58 +03:00
|
|
|
led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER;
|
2006-03-31 14:31:05 +04:00
|
|
|
led_trigger_set(led_cdev, trig);
|
2018-12-10 12:29:57 +03:00
|
|
|
break;
|
|
|
|
}
|
2006-03-31 14:31:05 +04:00
|
|
|
}
|
2007-11-10 16:29:04 +03:00
|
|
|
up_write(&led_cdev->trigger_lock);
|
|
|
|
up_read(&triggers_list_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
}
|
2008-03-09 23:59:57 +03:00
|
|
|
EXPORT_SYMBOL_GPL(led_trigger_set_default);
|
|
|
|
|
2012-11-04 13:54:34 +04:00
|
|
|
void led_trigger_rename_static(const char *name, struct led_trigger *trig)
|
|
|
|
{
|
|
|
|
/* new name must be on a temporary string to prevent races */
|
|
|
|
BUG_ON(name == trig->name);
|
|
|
|
|
|
|
|
down_write(&triggers_list_lock);
|
|
|
|
/* this assumes that trig->name was originaly allocated to
|
|
|
|
* non constant storage */
|
|
|
|
strcpy((char *)trig->name, name);
|
|
|
|
up_write(&triggers_list_lock);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(led_trigger_rename_static);
|
|
|
|
|
2008-03-09 23:59:57 +03:00
|
|
|
/* LED Trigger Interface */
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2012-04-19 07:46:33 +04:00
|
|
|
int led_trigger_register(struct led_trigger *trig)
|
2006-03-31 14:31:05 +04:00
|
|
|
{
|
|
|
|
struct led_classdev *led_cdev;
|
2012-04-19 07:46:33 +04:00
|
|
|
struct led_trigger *_trig;
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2012-04-19 07:46:33 +04:00
|
|
|
rwlock_init(&trig->leddev_list_lock);
|
|
|
|
INIT_LIST_HEAD(&trig->led_cdevs);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2007-11-10 16:29:04 +03:00
|
|
|
down_write(&triggers_list_lock);
|
2009-02-18 01:18:04 +03:00
|
|
|
/* Make sure the trigger's name isn't already in use */
|
2012-04-19 07:46:33 +04:00
|
|
|
list_for_each_entry(_trig, &trigger_list, next_trig) {
|
2020-07-16 20:17:28 +03:00
|
|
|
if (!strcmp(_trig->name, trig->name) &&
|
|
|
|
(trig->trigger_type == _trig->trigger_type ||
|
|
|
|
!trig->trigger_type || !_trig->trigger_type)) {
|
2009-02-18 01:18:04 +03:00
|
|
|
up_write(&triggers_list_lock);
|
|
|
|
return -EEXIST;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Add to the list of led triggers */
|
2012-04-19 07:46:33 +04:00
|
|
|
list_add_tail(&trig->next_trig, &trigger_list);
|
2007-11-10 16:29:04 +03:00
|
|
|
up_write(&triggers_list_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
|
|
|
/* Register with any LEDs that have this as a default trigger */
|
2008-01-01 02:09:44 +03:00
|
|
|
down_read(&leds_list_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
list_for_each_entry(led_cdev, &leds_list, node) {
|
2007-11-10 16:29:04 +03:00
|
|
|
down_write(&led_cdev->trigger_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
if (!led_cdev->trigger && led_cdev->default_trigger &&
|
2020-07-16 20:17:28 +03:00
|
|
|
!strcmp(led_cdev->default_trigger, trig->name) &&
|
|
|
|
trigger_relevant(led_cdev, trig)) {
|
2018-12-10 12:29:59 +03:00
|
|
|
led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER;
|
2012-04-19 07:46:33 +04:00
|
|
|
led_trigger_set(led_cdev, trig);
|
2018-12-10 12:29:59 +03:00
|
|
|
}
|
2007-11-10 16:29:04 +03:00
|
|
|
up_write(&led_cdev->trigger_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
}
|
2008-01-01 02:09:44 +03:00
|
|
|
up_read(&leds_list_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2008-03-09 23:59:57 +03:00
|
|
|
EXPORT_SYMBOL_GPL(led_trigger_register);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2012-04-19 07:46:33 +04:00
|
|
|
void led_trigger_unregister(struct led_trigger *trig)
|
2006-03-31 14:31:05 +04:00
|
|
|
{
|
|
|
|
struct led_classdev *led_cdev;
|
|
|
|
|
2014-04-04 21:01:13 +04:00
|
|
|
if (list_empty_careful(&trig->next_trig))
|
|
|
|
return;
|
|
|
|
|
2006-03-31 14:31:05 +04:00
|
|
|
/* Remove from the list of led triggers */
|
2007-11-10 16:29:04 +03:00
|
|
|
down_write(&triggers_list_lock);
|
2014-04-04 21:01:13 +04:00
|
|
|
list_del_init(&trig->next_trig);
|
2007-11-10 16:29:04 +03:00
|
|
|
up_write(&triggers_list_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
|
|
|
/* Remove anyone actively using this trigger */
|
2008-01-01 02:09:44 +03:00
|
|
|
down_read(&leds_list_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
list_for_each_entry(led_cdev, &leds_list, node) {
|
2007-11-10 16:29:04 +03:00
|
|
|
down_write(&led_cdev->trigger_lock);
|
2012-04-19 07:46:33 +04:00
|
|
|
if (led_cdev->trigger == trig)
|
2006-03-31 14:31:05 +04:00
|
|
|
led_trigger_set(led_cdev, NULL);
|
2007-11-10 16:29:04 +03:00
|
|
|
up_write(&led_cdev->trigger_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
}
|
2008-01-01 02:09:44 +03:00
|
|
|
up_read(&leds_list_lock);
|
2006-03-31 14:31:05 +04:00
|
|
|
}
|
2008-03-09 23:59:57 +03:00
|
|
|
EXPORT_SYMBOL_GPL(led_trigger_unregister);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2016-01-02 03:36:41 +03:00
|
|
|
static void devm_led_trigger_release(struct device *dev, void *res)
|
|
|
|
{
|
|
|
|
led_trigger_unregister(*(struct led_trigger **)res);
|
|
|
|
}
|
|
|
|
|
|
|
|
int devm_led_trigger_register(struct device *dev,
|
|
|
|
struct led_trigger *trig)
|
|
|
|
{
|
|
|
|
struct led_trigger **dr;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
dr = devres_alloc(devm_led_trigger_release, sizeof(*dr),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!dr)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
*dr = trig;
|
|
|
|
|
|
|
|
rc = led_trigger_register(trig);
|
|
|
|
if (rc)
|
|
|
|
devres_free(dr);
|
|
|
|
else
|
|
|
|
devres_add(dev, dr);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(devm_led_trigger_register);
|
|
|
|
|
2020-06-09 18:49:29 +03:00
|
|
|
/* Simple LED Trigger Interface */
|
2008-03-09 23:59:57 +03:00
|
|
|
|
2012-04-19 07:46:33 +04:00
|
|
|
void led_trigger_event(struct led_trigger *trig,
|
2008-03-09 23:59:57 +03:00
|
|
|
enum led_brightness brightness)
|
2006-03-31 14:31:05 +04:00
|
|
|
{
|
2013-12-28 19:00:26 +04:00
|
|
|
struct led_classdev *led_cdev;
|
leds: trigger: fix potential deadlock with libata
We have the following potential deadlock condition:
========================================================
WARNING: possible irq lock inversion dependency detected
5.10.0-rc2+ #25 Not tainted
--------------------------------------------------------
swapper/3/0 just changed the state of lock:
ffff8880063bd618 (&host->lock){-...}-{2:2}, at: ata_bmdma_interrupt+0x27/0x200
but this lock took another, HARDIRQ-READ-unsafe lock in the past:
(&trig->leddev_list_lock){.+.?}-{2:2}
and interrupts could create inverse lock ordering between them.
other info that might help us debug this:
Possible interrupt unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&trig->leddev_list_lock);
local_irq_disable();
lock(&host->lock);
lock(&trig->leddev_list_lock);
<Interrupt>
lock(&host->lock);
*** DEADLOCK ***
no locks held by swapper/3/0.
the shortest dependencies between 2nd lock and 1st lock:
-> (&trig->leddev_list_lock){.+.?}-{2:2} ops: 46 {
HARDIRQ-ON-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
IN-SOFTIRQ-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
kbd_bh+0x9e/0xc0
tasklet_action_common.constprop.0+0xe9/0x100
tasklet_action+0x22/0x30
__do_softirq+0xcc/0x46d
run_ksoftirqd+0x3f/0x70
smpboot_thread_fn+0x116/0x1f0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
SOFTIRQ-ON-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
INITIAL READ USE at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
}
... key at: [<ffffffff83da4c00>] __key.0+0x0/0x10
... acquired at:
_raw_read_lock+0x42/0x90
led_trigger_blink_oneshot+0x3b/0x90
ledtrig_disk_activity+0x3c/0xa0
ata_qc_complete+0x26/0x450
ata_do_link_abort+0xa3/0xe0
ata_port_freeze+0x2e/0x40
ata_hsm_qc_complete+0x94/0xa0
ata_sff_hsm_move+0x177/0x7a0
ata_sff_pio_task+0xc7/0x1b0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
-> (&host->lock){-...}-{2:2} ops: 69 {
IN-HARDIRQ-W at:
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_bmdma_interrupt+0x27/0x200
__handle_irq_event_percpu+0xd5/0x2b0
handle_irq_event+0x57/0xb0
handle_edge_irq+0x8c/0x230
asm_call_irq_on_stack+0xf/0x20
common_interrupt+0x100/0x1c0
asm_common_interrupt+0x1e/0x40
native_safe_halt+0xe/0x10
arch_cpu_idle+0x15/0x20
default_idle_call+0x59/0x1c0
do_idle+0x22c/0x2c0
cpu_startup_entry+0x20/0x30
start_secondary+0x11d/0x150
secondary_startup_64_no_verify+0xa6/0xab
INITIAL USE at:
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_dev_init+0x54/0xe0
ata_link_init+0x8b/0xd0
ata_port_alloc+0x1f1/0x210
ata_host_alloc+0xf1/0x130
ata_host_alloc_pinfo+0x14/0xb0
ata_pci_sff_prepare_host+0x41/0xa0
ata_pci_bmdma_prepare_host+0x14/0x30
piix_init_one+0x21f/0x600
local_pci_probe+0x48/0x80
pci_device_probe+0x105/0x1c0
really_probe+0x221/0x490
driver_probe_device+0xe9/0x160
device_driver_attach+0xb2/0xc0
__driver_attach+0x91/0x150
bus_for_each_dev+0x81/0xc0
driver_attach+0x1e/0x20
bus_add_driver+0x138/0x1f0
driver_register+0x91/0xf0
__pci_register_driver+0x73/0x80
piix_init+0x1e/0x2e
do_one_initcall+0x5f/0x2d0
kernel_init_freeable+0x26f/0x2cf
kernel_init+0xe/0x113
ret_from_fork+0x1f/0x30
}
... key at: [<ffffffff83d9fdc0>] __key.6+0x0/0x10
... acquired at:
__lock_acquire+0x9da/0x2370
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_bmdma_interrupt+0x27/0x200
__handle_irq_event_percpu+0xd5/0x2b0
handle_irq_event+0x57/0xb0
handle_edge_irq+0x8c/0x230
asm_call_irq_on_stack+0xf/0x20
common_interrupt+0x100/0x1c0
asm_common_interrupt+0x1e/0x40
native_safe_halt+0xe/0x10
arch_cpu_idle+0x15/0x20
default_idle_call+0x59/0x1c0
do_idle+0x22c/0x2c0
cpu_startup_entry+0x20/0x30
start_secondary+0x11d/0x150
secondary_startup_64_no_verify+0xa6/0xab
This lockdep splat is reported after:
commit e918188611f0 ("locking: More accurate annotations for read_lock()")
To clarify:
- read-locks are recursive only in interrupt context (when
in_interrupt() returns true)
- after acquiring host->lock in CPU1, another cpu (i.e. CPU2) may call
write_lock(&trig->leddev_list_lock) that would be blocked by CPU0
that holds trig->leddev_list_lock in read-mode
- when CPU1 (ata_ac_complete()) tries to read-lock
trig->leddev_list_lock, it would be blocked by the write-lock waiter
on CPU2 (because we are not in interrupt context, so the read-lock is
not recursive)
- at this point if an interrupt happens on CPU0 and
ata_bmdma_interrupt() is executed it will try to acquire host->lock,
that is held by CPU1, that is currently blocked by CPU2, so:
* CPU0 blocked by CPU1
* CPU1 blocked by CPU2
* CPU2 blocked by CPU0
*** DEADLOCK ***
The deadlock scenario is better represented by the following schema
(thanks to Boqun Feng <boqun.feng@gmail.com> for the schema and the
detailed explanation of the deadlock condition):
CPU 0: CPU 1: CPU 2:
----- ----- -----
led_trigger_event():
read_lock(&trig->leddev_list_lock);
<workqueue>
ata_hsm_qc_complete():
spin_lock_irqsave(&host->lock);
write_lock(&trig->leddev_list_lock);
ata_port_freeze():
ata_do_link_abort():
ata_qc_complete():
ledtrig_disk_activity():
led_trigger_blink_oneshot():
read_lock(&trig->leddev_list_lock);
// ^ not in in_interrupt() context, so could get blocked by CPU 2
<interrupt>
ata_bmdma_interrupt():
spin_lock_irqsave(&host->lock);
Fix by using read_lock_irqsave/irqrestore() in led_trigger_event(), so
that no interrupt can happen in between, preventing the deadlock
condition.
Apply the same change to led_trigger_blink_setup() as well, since the
same deadlock scenario can also happen in power_supply_update_bat_leds()
-> led_trigger_blink() -> led_trigger_blink_setup() (workqueue context),
and potentially prevent other similar usages.
Link: https://lore.kernel.org/lkml/20201101092614.GB3989@xps-13-7390/
Fixes: eb25cb9956cc ("leds: convert IDE trigger to common disk trigger")
Signed-off-by: Andrea Righi <andrea.righi@canonical.com>
Signed-off-by: Pavel Machek <pavel@ucw.cz>
2020-11-25 18:18:22 +03:00
|
|
|
unsigned long flags;
|
2008-03-09 23:59:57 +03:00
|
|
|
|
2012-04-19 07:46:33 +04:00
|
|
|
if (!trig)
|
2008-03-09 23:59:57 +03:00
|
|
|
return;
|
|
|
|
|
leds: trigger: fix potential deadlock with libata
We have the following potential deadlock condition:
========================================================
WARNING: possible irq lock inversion dependency detected
5.10.0-rc2+ #25 Not tainted
--------------------------------------------------------
swapper/3/0 just changed the state of lock:
ffff8880063bd618 (&host->lock){-...}-{2:2}, at: ata_bmdma_interrupt+0x27/0x200
but this lock took another, HARDIRQ-READ-unsafe lock in the past:
(&trig->leddev_list_lock){.+.?}-{2:2}
and interrupts could create inverse lock ordering between them.
other info that might help us debug this:
Possible interrupt unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&trig->leddev_list_lock);
local_irq_disable();
lock(&host->lock);
lock(&trig->leddev_list_lock);
<Interrupt>
lock(&host->lock);
*** DEADLOCK ***
no locks held by swapper/3/0.
the shortest dependencies between 2nd lock and 1st lock:
-> (&trig->leddev_list_lock){.+.?}-{2:2} ops: 46 {
HARDIRQ-ON-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
IN-SOFTIRQ-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
kbd_bh+0x9e/0xc0
tasklet_action_common.constprop.0+0xe9/0x100
tasklet_action+0x22/0x30
__do_softirq+0xcc/0x46d
run_ksoftirqd+0x3f/0x70
smpboot_thread_fn+0x116/0x1f0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
SOFTIRQ-ON-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
INITIAL READ USE at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
}
... key at: [<ffffffff83da4c00>] __key.0+0x0/0x10
... acquired at:
_raw_read_lock+0x42/0x90
led_trigger_blink_oneshot+0x3b/0x90
ledtrig_disk_activity+0x3c/0xa0
ata_qc_complete+0x26/0x450
ata_do_link_abort+0xa3/0xe0
ata_port_freeze+0x2e/0x40
ata_hsm_qc_complete+0x94/0xa0
ata_sff_hsm_move+0x177/0x7a0
ata_sff_pio_task+0xc7/0x1b0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
-> (&host->lock){-...}-{2:2} ops: 69 {
IN-HARDIRQ-W at:
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_bmdma_interrupt+0x27/0x200
__handle_irq_event_percpu+0xd5/0x2b0
handle_irq_event+0x57/0xb0
handle_edge_irq+0x8c/0x230
asm_call_irq_on_stack+0xf/0x20
common_interrupt+0x100/0x1c0
asm_common_interrupt+0x1e/0x40
native_safe_halt+0xe/0x10
arch_cpu_idle+0x15/0x20
default_idle_call+0x59/0x1c0
do_idle+0x22c/0x2c0
cpu_startup_entry+0x20/0x30
start_secondary+0x11d/0x150
secondary_startup_64_no_verify+0xa6/0xab
INITIAL USE at:
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_dev_init+0x54/0xe0
ata_link_init+0x8b/0xd0
ata_port_alloc+0x1f1/0x210
ata_host_alloc+0xf1/0x130
ata_host_alloc_pinfo+0x14/0xb0
ata_pci_sff_prepare_host+0x41/0xa0
ata_pci_bmdma_prepare_host+0x14/0x30
piix_init_one+0x21f/0x600
local_pci_probe+0x48/0x80
pci_device_probe+0x105/0x1c0
really_probe+0x221/0x490
driver_probe_device+0xe9/0x160
device_driver_attach+0xb2/0xc0
__driver_attach+0x91/0x150
bus_for_each_dev+0x81/0xc0
driver_attach+0x1e/0x20
bus_add_driver+0x138/0x1f0
driver_register+0x91/0xf0
__pci_register_driver+0x73/0x80
piix_init+0x1e/0x2e
do_one_initcall+0x5f/0x2d0
kernel_init_freeable+0x26f/0x2cf
kernel_init+0xe/0x113
ret_from_fork+0x1f/0x30
}
... key at: [<ffffffff83d9fdc0>] __key.6+0x0/0x10
... acquired at:
__lock_acquire+0x9da/0x2370
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_bmdma_interrupt+0x27/0x200
__handle_irq_event_percpu+0xd5/0x2b0
handle_irq_event+0x57/0xb0
handle_edge_irq+0x8c/0x230
asm_call_irq_on_stack+0xf/0x20
common_interrupt+0x100/0x1c0
asm_common_interrupt+0x1e/0x40
native_safe_halt+0xe/0x10
arch_cpu_idle+0x15/0x20
default_idle_call+0x59/0x1c0
do_idle+0x22c/0x2c0
cpu_startup_entry+0x20/0x30
start_secondary+0x11d/0x150
secondary_startup_64_no_verify+0xa6/0xab
This lockdep splat is reported after:
commit e918188611f0 ("locking: More accurate annotations for read_lock()")
To clarify:
- read-locks are recursive only in interrupt context (when
in_interrupt() returns true)
- after acquiring host->lock in CPU1, another cpu (i.e. CPU2) may call
write_lock(&trig->leddev_list_lock) that would be blocked by CPU0
that holds trig->leddev_list_lock in read-mode
- when CPU1 (ata_ac_complete()) tries to read-lock
trig->leddev_list_lock, it would be blocked by the write-lock waiter
on CPU2 (because we are not in interrupt context, so the read-lock is
not recursive)
- at this point if an interrupt happens on CPU0 and
ata_bmdma_interrupt() is executed it will try to acquire host->lock,
that is held by CPU1, that is currently blocked by CPU2, so:
* CPU0 blocked by CPU1
* CPU1 blocked by CPU2
* CPU2 blocked by CPU0
*** DEADLOCK ***
The deadlock scenario is better represented by the following schema
(thanks to Boqun Feng <boqun.feng@gmail.com> for the schema and the
detailed explanation of the deadlock condition):
CPU 0: CPU 1: CPU 2:
----- ----- -----
led_trigger_event():
read_lock(&trig->leddev_list_lock);
<workqueue>
ata_hsm_qc_complete():
spin_lock_irqsave(&host->lock);
write_lock(&trig->leddev_list_lock);
ata_port_freeze():
ata_do_link_abort():
ata_qc_complete():
ledtrig_disk_activity():
led_trigger_blink_oneshot():
read_lock(&trig->leddev_list_lock);
// ^ not in in_interrupt() context, so could get blocked by CPU 2
<interrupt>
ata_bmdma_interrupt():
spin_lock_irqsave(&host->lock);
Fix by using read_lock_irqsave/irqrestore() in led_trigger_event(), so
that no interrupt can happen in between, preventing the deadlock
condition.
Apply the same change to led_trigger_blink_setup() as well, since the
same deadlock scenario can also happen in power_supply_update_bat_leds()
-> led_trigger_blink() -> led_trigger_blink_setup() (workqueue context),
and potentially prevent other similar usages.
Link: https://lore.kernel.org/lkml/20201101092614.GB3989@xps-13-7390/
Fixes: eb25cb9956cc ("leds: convert IDE trigger to common disk trigger")
Signed-off-by: Andrea Righi <andrea.righi@canonical.com>
Signed-off-by: Pavel Machek <pavel@ucw.cz>
2020-11-25 18:18:22 +03:00
|
|
|
read_lock_irqsave(&trig->leddev_list_lock, flags);
|
2013-12-28 19:00:26 +04:00
|
|
|
list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list)
|
2012-08-15 17:44:34 +04:00
|
|
|
led_set_brightness(led_cdev, brightness);
|
leds: trigger: fix potential deadlock with libata
We have the following potential deadlock condition:
========================================================
WARNING: possible irq lock inversion dependency detected
5.10.0-rc2+ #25 Not tainted
--------------------------------------------------------
swapper/3/0 just changed the state of lock:
ffff8880063bd618 (&host->lock){-...}-{2:2}, at: ata_bmdma_interrupt+0x27/0x200
but this lock took another, HARDIRQ-READ-unsafe lock in the past:
(&trig->leddev_list_lock){.+.?}-{2:2}
and interrupts could create inverse lock ordering between them.
other info that might help us debug this:
Possible interrupt unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&trig->leddev_list_lock);
local_irq_disable();
lock(&host->lock);
lock(&trig->leddev_list_lock);
<Interrupt>
lock(&host->lock);
*** DEADLOCK ***
no locks held by swapper/3/0.
the shortest dependencies between 2nd lock and 1st lock:
-> (&trig->leddev_list_lock){.+.?}-{2:2} ops: 46 {
HARDIRQ-ON-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
IN-SOFTIRQ-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
kbd_bh+0x9e/0xc0
tasklet_action_common.constprop.0+0xe9/0x100
tasklet_action+0x22/0x30
__do_softirq+0xcc/0x46d
run_ksoftirqd+0x3f/0x70
smpboot_thread_fn+0x116/0x1f0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
SOFTIRQ-ON-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
INITIAL READ USE at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
}
... key at: [<ffffffff83da4c00>] __key.0+0x0/0x10
... acquired at:
_raw_read_lock+0x42/0x90
led_trigger_blink_oneshot+0x3b/0x90
ledtrig_disk_activity+0x3c/0xa0
ata_qc_complete+0x26/0x450
ata_do_link_abort+0xa3/0xe0
ata_port_freeze+0x2e/0x40
ata_hsm_qc_complete+0x94/0xa0
ata_sff_hsm_move+0x177/0x7a0
ata_sff_pio_task+0xc7/0x1b0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
-> (&host->lock){-...}-{2:2} ops: 69 {
IN-HARDIRQ-W at:
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_bmdma_interrupt+0x27/0x200
__handle_irq_event_percpu+0xd5/0x2b0
handle_irq_event+0x57/0xb0
handle_edge_irq+0x8c/0x230
asm_call_irq_on_stack+0xf/0x20
common_interrupt+0x100/0x1c0
asm_common_interrupt+0x1e/0x40
native_safe_halt+0xe/0x10
arch_cpu_idle+0x15/0x20
default_idle_call+0x59/0x1c0
do_idle+0x22c/0x2c0
cpu_startup_entry+0x20/0x30
start_secondary+0x11d/0x150
secondary_startup_64_no_verify+0xa6/0xab
INITIAL USE at:
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_dev_init+0x54/0xe0
ata_link_init+0x8b/0xd0
ata_port_alloc+0x1f1/0x210
ata_host_alloc+0xf1/0x130
ata_host_alloc_pinfo+0x14/0xb0
ata_pci_sff_prepare_host+0x41/0xa0
ata_pci_bmdma_prepare_host+0x14/0x30
piix_init_one+0x21f/0x600
local_pci_probe+0x48/0x80
pci_device_probe+0x105/0x1c0
really_probe+0x221/0x490
driver_probe_device+0xe9/0x160
device_driver_attach+0xb2/0xc0
__driver_attach+0x91/0x150
bus_for_each_dev+0x81/0xc0
driver_attach+0x1e/0x20
bus_add_driver+0x138/0x1f0
driver_register+0x91/0xf0
__pci_register_driver+0x73/0x80
piix_init+0x1e/0x2e
do_one_initcall+0x5f/0x2d0
kernel_init_freeable+0x26f/0x2cf
kernel_init+0xe/0x113
ret_from_fork+0x1f/0x30
}
... key at: [<ffffffff83d9fdc0>] __key.6+0x0/0x10
... acquired at:
__lock_acquire+0x9da/0x2370
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_bmdma_interrupt+0x27/0x200
__handle_irq_event_percpu+0xd5/0x2b0
handle_irq_event+0x57/0xb0
handle_edge_irq+0x8c/0x230
asm_call_irq_on_stack+0xf/0x20
common_interrupt+0x100/0x1c0
asm_common_interrupt+0x1e/0x40
native_safe_halt+0xe/0x10
arch_cpu_idle+0x15/0x20
default_idle_call+0x59/0x1c0
do_idle+0x22c/0x2c0
cpu_startup_entry+0x20/0x30
start_secondary+0x11d/0x150
secondary_startup_64_no_verify+0xa6/0xab
This lockdep splat is reported after:
commit e918188611f0 ("locking: More accurate annotations for read_lock()")
To clarify:
- read-locks are recursive only in interrupt context (when
in_interrupt() returns true)
- after acquiring host->lock in CPU1, another cpu (i.e. CPU2) may call
write_lock(&trig->leddev_list_lock) that would be blocked by CPU0
that holds trig->leddev_list_lock in read-mode
- when CPU1 (ata_ac_complete()) tries to read-lock
trig->leddev_list_lock, it would be blocked by the write-lock waiter
on CPU2 (because we are not in interrupt context, so the read-lock is
not recursive)
- at this point if an interrupt happens on CPU0 and
ata_bmdma_interrupt() is executed it will try to acquire host->lock,
that is held by CPU1, that is currently blocked by CPU2, so:
* CPU0 blocked by CPU1
* CPU1 blocked by CPU2
* CPU2 blocked by CPU0
*** DEADLOCK ***
The deadlock scenario is better represented by the following schema
(thanks to Boqun Feng <boqun.feng@gmail.com> for the schema and the
detailed explanation of the deadlock condition):
CPU 0: CPU 1: CPU 2:
----- ----- -----
led_trigger_event():
read_lock(&trig->leddev_list_lock);
<workqueue>
ata_hsm_qc_complete():
spin_lock_irqsave(&host->lock);
write_lock(&trig->leddev_list_lock);
ata_port_freeze():
ata_do_link_abort():
ata_qc_complete():
ledtrig_disk_activity():
led_trigger_blink_oneshot():
read_lock(&trig->leddev_list_lock);
// ^ not in in_interrupt() context, so could get blocked by CPU 2
<interrupt>
ata_bmdma_interrupt():
spin_lock_irqsave(&host->lock);
Fix by using read_lock_irqsave/irqrestore() in led_trigger_event(), so
that no interrupt can happen in between, preventing the deadlock
condition.
Apply the same change to led_trigger_blink_setup() as well, since the
same deadlock scenario can also happen in power_supply_update_bat_leds()
-> led_trigger_blink() -> led_trigger_blink_setup() (workqueue context),
and potentially prevent other similar usages.
Link: https://lore.kernel.org/lkml/20201101092614.GB3989@xps-13-7390/
Fixes: eb25cb9956cc ("leds: convert IDE trigger to common disk trigger")
Signed-off-by: Andrea Righi <andrea.righi@canonical.com>
Signed-off-by: Pavel Machek <pavel@ucw.cz>
2020-11-25 18:18:22 +03:00
|
|
|
read_unlock_irqrestore(&trig->leddev_list_lock, flags);
|
2006-03-31 14:31:05 +04:00
|
|
|
}
|
2008-03-09 23:59:57 +03:00
|
|
|
EXPORT_SYMBOL_GPL(led_trigger_event);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2012-06-15 18:15:27 +04:00
|
|
|
static void led_trigger_blink_setup(struct led_trigger *trig,
|
2012-05-27 03:19:22 +04:00
|
|
|
unsigned long *delay_on,
|
|
|
|
unsigned long *delay_off,
|
|
|
|
int oneshot,
|
|
|
|
int invert)
|
2011-01-07 19:28:16 +03:00
|
|
|
{
|
2013-12-28 19:00:26 +04:00
|
|
|
struct led_classdev *led_cdev;
|
leds: trigger: fix potential deadlock with libata
We have the following potential deadlock condition:
========================================================
WARNING: possible irq lock inversion dependency detected
5.10.0-rc2+ #25 Not tainted
--------------------------------------------------------
swapper/3/0 just changed the state of lock:
ffff8880063bd618 (&host->lock){-...}-{2:2}, at: ata_bmdma_interrupt+0x27/0x200
but this lock took another, HARDIRQ-READ-unsafe lock in the past:
(&trig->leddev_list_lock){.+.?}-{2:2}
and interrupts could create inverse lock ordering between them.
other info that might help us debug this:
Possible interrupt unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&trig->leddev_list_lock);
local_irq_disable();
lock(&host->lock);
lock(&trig->leddev_list_lock);
<Interrupt>
lock(&host->lock);
*** DEADLOCK ***
no locks held by swapper/3/0.
the shortest dependencies between 2nd lock and 1st lock:
-> (&trig->leddev_list_lock){.+.?}-{2:2} ops: 46 {
HARDIRQ-ON-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
IN-SOFTIRQ-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
kbd_bh+0x9e/0xc0
tasklet_action_common.constprop.0+0xe9/0x100
tasklet_action+0x22/0x30
__do_softirq+0xcc/0x46d
run_ksoftirqd+0x3f/0x70
smpboot_thread_fn+0x116/0x1f0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
SOFTIRQ-ON-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
INITIAL READ USE at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
}
... key at: [<ffffffff83da4c00>] __key.0+0x0/0x10
... acquired at:
_raw_read_lock+0x42/0x90
led_trigger_blink_oneshot+0x3b/0x90
ledtrig_disk_activity+0x3c/0xa0
ata_qc_complete+0x26/0x450
ata_do_link_abort+0xa3/0xe0
ata_port_freeze+0x2e/0x40
ata_hsm_qc_complete+0x94/0xa0
ata_sff_hsm_move+0x177/0x7a0
ata_sff_pio_task+0xc7/0x1b0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
-> (&host->lock){-...}-{2:2} ops: 69 {
IN-HARDIRQ-W at:
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_bmdma_interrupt+0x27/0x200
__handle_irq_event_percpu+0xd5/0x2b0
handle_irq_event+0x57/0xb0
handle_edge_irq+0x8c/0x230
asm_call_irq_on_stack+0xf/0x20
common_interrupt+0x100/0x1c0
asm_common_interrupt+0x1e/0x40
native_safe_halt+0xe/0x10
arch_cpu_idle+0x15/0x20
default_idle_call+0x59/0x1c0
do_idle+0x22c/0x2c0
cpu_startup_entry+0x20/0x30
start_secondary+0x11d/0x150
secondary_startup_64_no_verify+0xa6/0xab
INITIAL USE at:
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_dev_init+0x54/0xe0
ata_link_init+0x8b/0xd0
ata_port_alloc+0x1f1/0x210
ata_host_alloc+0xf1/0x130
ata_host_alloc_pinfo+0x14/0xb0
ata_pci_sff_prepare_host+0x41/0xa0
ata_pci_bmdma_prepare_host+0x14/0x30
piix_init_one+0x21f/0x600
local_pci_probe+0x48/0x80
pci_device_probe+0x105/0x1c0
really_probe+0x221/0x490
driver_probe_device+0xe9/0x160
device_driver_attach+0xb2/0xc0
__driver_attach+0x91/0x150
bus_for_each_dev+0x81/0xc0
driver_attach+0x1e/0x20
bus_add_driver+0x138/0x1f0
driver_register+0x91/0xf0
__pci_register_driver+0x73/0x80
piix_init+0x1e/0x2e
do_one_initcall+0x5f/0x2d0
kernel_init_freeable+0x26f/0x2cf
kernel_init+0xe/0x113
ret_from_fork+0x1f/0x30
}
... key at: [<ffffffff83d9fdc0>] __key.6+0x0/0x10
... acquired at:
__lock_acquire+0x9da/0x2370
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_bmdma_interrupt+0x27/0x200
__handle_irq_event_percpu+0xd5/0x2b0
handle_irq_event+0x57/0xb0
handle_edge_irq+0x8c/0x230
asm_call_irq_on_stack+0xf/0x20
common_interrupt+0x100/0x1c0
asm_common_interrupt+0x1e/0x40
native_safe_halt+0xe/0x10
arch_cpu_idle+0x15/0x20
default_idle_call+0x59/0x1c0
do_idle+0x22c/0x2c0
cpu_startup_entry+0x20/0x30
start_secondary+0x11d/0x150
secondary_startup_64_no_verify+0xa6/0xab
This lockdep splat is reported after:
commit e918188611f0 ("locking: More accurate annotations for read_lock()")
To clarify:
- read-locks are recursive only in interrupt context (when
in_interrupt() returns true)
- after acquiring host->lock in CPU1, another cpu (i.e. CPU2) may call
write_lock(&trig->leddev_list_lock) that would be blocked by CPU0
that holds trig->leddev_list_lock in read-mode
- when CPU1 (ata_ac_complete()) tries to read-lock
trig->leddev_list_lock, it would be blocked by the write-lock waiter
on CPU2 (because we are not in interrupt context, so the read-lock is
not recursive)
- at this point if an interrupt happens on CPU0 and
ata_bmdma_interrupt() is executed it will try to acquire host->lock,
that is held by CPU1, that is currently blocked by CPU2, so:
* CPU0 blocked by CPU1
* CPU1 blocked by CPU2
* CPU2 blocked by CPU0
*** DEADLOCK ***
The deadlock scenario is better represented by the following schema
(thanks to Boqun Feng <boqun.feng@gmail.com> for the schema and the
detailed explanation of the deadlock condition):
CPU 0: CPU 1: CPU 2:
----- ----- -----
led_trigger_event():
read_lock(&trig->leddev_list_lock);
<workqueue>
ata_hsm_qc_complete():
spin_lock_irqsave(&host->lock);
write_lock(&trig->leddev_list_lock);
ata_port_freeze():
ata_do_link_abort():
ata_qc_complete():
ledtrig_disk_activity():
led_trigger_blink_oneshot():
read_lock(&trig->leddev_list_lock);
// ^ not in in_interrupt() context, so could get blocked by CPU 2
<interrupt>
ata_bmdma_interrupt():
spin_lock_irqsave(&host->lock);
Fix by using read_lock_irqsave/irqrestore() in led_trigger_event(), so
that no interrupt can happen in between, preventing the deadlock
condition.
Apply the same change to led_trigger_blink_setup() as well, since the
same deadlock scenario can also happen in power_supply_update_bat_leds()
-> led_trigger_blink() -> led_trigger_blink_setup() (workqueue context),
and potentially prevent other similar usages.
Link: https://lore.kernel.org/lkml/20201101092614.GB3989@xps-13-7390/
Fixes: eb25cb9956cc ("leds: convert IDE trigger to common disk trigger")
Signed-off-by: Andrea Righi <andrea.righi@canonical.com>
Signed-off-by: Pavel Machek <pavel@ucw.cz>
2020-11-25 18:18:22 +03:00
|
|
|
unsigned long flags;
|
2011-01-07 19:28:16 +03:00
|
|
|
|
2012-04-19 07:46:33 +04:00
|
|
|
if (!trig)
|
2011-01-07 19:28:16 +03:00
|
|
|
return;
|
|
|
|
|
leds: trigger: fix potential deadlock with libata
We have the following potential deadlock condition:
========================================================
WARNING: possible irq lock inversion dependency detected
5.10.0-rc2+ #25 Not tainted
--------------------------------------------------------
swapper/3/0 just changed the state of lock:
ffff8880063bd618 (&host->lock){-...}-{2:2}, at: ata_bmdma_interrupt+0x27/0x200
but this lock took another, HARDIRQ-READ-unsafe lock in the past:
(&trig->leddev_list_lock){.+.?}-{2:2}
and interrupts could create inverse lock ordering between them.
other info that might help us debug this:
Possible interrupt unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&trig->leddev_list_lock);
local_irq_disable();
lock(&host->lock);
lock(&trig->leddev_list_lock);
<Interrupt>
lock(&host->lock);
*** DEADLOCK ***
no locks held by swapper/3/0.
the shortest dependencies between 2nd lock and 1st lock:
-> (&trig->leddev_list_lock){.+.?}-{2:2} ops: 46 {
HARDIRQ-ON-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
IN-SOFTIRQ-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
kbd_bh+0x9e/0xc0
tasklet_action_common.constprop.0+0xe9/0x100
tasklet_action+0x22/0x30
__do_softirq+0xcc/0x46d
run_ksoftirqd+0x3f/0x70
smpboot_thread_fn+0x116/0x1f0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
SOFTIRQ-ON-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
INITIAL READ USE at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
}
... key at: [<ffffffff83da4c00>] __key.0+0x0/0x10
... acquired at:
_raw_read_lock+0x42/0x90
led_trigger_blink_oneshot+0x3b/0x90
ledtrig_disk_activity+0x3c/0xa0
ata_qc_complete+0x26/0x450
ata_do_link_abort+0xa3/0xe0
ata_port_freeze+0x2e/0x40
ata_hsm_qc_complete+0x94/0xa0
ata_sff_hsm_move+0x177/0x7a0
ata_sff_pio_task+0xc7/0x1b0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
-> (&host->lock){-...}-{2:2} ops: 69 {
IN-HARDIRQ-W at:
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_bmdma_interrupt+0x27/0x200
__handle_irq_event_percpu+0xd5/0x2b0
handle_irq_event+0x57/0xb0
handle_edge_irq+0x8c/0x230
asm_call_irq_on_stack+0xf/0x20
common_interrupt+0x100/0x1c0
asm_common_interrupt+0x1e/0x40
native_safe_halt+0xe/0x10
arch_cpu_idle+0x15/0x20
default_idle_call+0x59/0x1c0
do_idle+0x22c/0x2c0
cpu_startup_entry+0x20/0x30
start_secondary+0x11d/0x150
secondary_startup_64_no_verify+0xa6/0xab
INITIAL USE at:
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_dev_init+0x54/0xe0
ata_link_init+0x8b/0xd0
ata_port_alloc+0x1f1/0x210
ata_host_alloc+0xf1/0x130
ata_host_alloc_pinfo+0x14/0xb0
ata_pci_sff_prepare_host+0x41/0xa0
ata_pci_bmdma_prepare_host+0x14/0x30
piix_init_one+0x21f/0x600
local_pci_probe+0x48/0x80
pci_device_probe+0x105/0x1c0
really_probe+0x221/0x490
driver_probe_device+0xe9/0x160
device_driver_attach+0xb2/0xc0
__driver_attach+0x91/0x150
bus_for_each_dev+0x81/0xc0
driver_attach+0x1e/0x20
bus_add_driver+0x138/0x1f0
driver_register+0x91/0xf0
__pci_register_driver+0x73/0x80
piix_init+0x1e/0x2e
do_one_initcall+0x5f/0x2d0
kernel_init_freeable+0x26f/0x2cf
kernel_init+0xe/0x113
ret_from_fork+0x1f/0x30
}
... key at: [<ffffffff83d9fdc0>] __key.6+0x0/0x10
... acquired at:
__lock_acquire+0x9da/0x2370
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_bmdma_interrupt+0x27/0x200
__handle_irq_event_percpu+0xd5/0x2b0
handle_irq_event+0x57/0xb0
handle_edge_irq+0x8c/0x230
asm_call_irq_on_stack+0xf/0x20
common_interrupt+0x100/0x1c0
asm_common_interrupt+0x1e/0x40
native_safe_halt+0xe/0x10
arch_cpu_idle+0x15/0x20
default_idle_call+0x59/0x1c0
do_idle+0x22c/0x2c0
cpu_startup_entry+0x20/0x30
start_secondary+0x11d/0x150
secondary_startup_64_no_verify+0xa6/0xab
This lockdep splat is reported after:
commit e918188611f0 ("locking: More accurate annotations for read_lock()")
To clarify:
- read-locks are recursive only in interrupt context (when
in_interrupt() returns true)
- after acquiring host->lock in CPU1, another cpu (i.e. CPU2) may call
write_lock(&trig->leddev_list_lock) that would be blocked by CPU0
that holds trig->leddev_list_lock in read-mode
- when CPU1 (ata_ac_complete()) tries to read-lock
trig->leddev_list_lock, it would be blocked by the write-lock waiter
on CPU2 (because we are not in interrupt context, so the read-lock is
not recursive)
- at this point if an interrupt happens on CPU0 and
ata_bmdma_interrupt() is executed it will try to acquire host->lock,
that is held by CPU1, that is currently blocked by CPU2, so:
* CPU0 blocked by CPU1
* CPU1 blocked by CPU2
* CPU2 blocked by CPU0
*** DEADLOCK ***
The deadlock scenario is better represented by the following schema
(thanks to Boqun Feng <boqun.feng@gmail.com> for the schema and the
detailed explanation of the deadlock condition):
CPU 0: CPU 1: CPU 2:
----- ----- -----
led_trigger_event():
read_lock(&trig->leddev_list_lock);
<workqueue>
ata_hsm_qc_complete():
spin_lock_irqsave(&host->lock);
write_lock(&trig->leddev_list_lock);
ata_port_freeze():
ata_do_link_abort():
ata_qc_complete():
ledtrig_disk_activity():
led_trigger_blink_oneshot():
read_lock(&trig->leddev_list_lock);
// ^ not in in_interrupt() context, so could get blocked by CPU 2
<interrupt>
ata_bmdma_interrupt():
spin_lock_irqsave(&host->lock);
Fix by using read_lock_irqsave/irqrestore() in led_trigger_event(), so
that no interrupt can happen in between, preventing the deadlock
condition.
Apply the same change to led_trigger_blink_setup() as well, since the
same deadlock scenario can also happen in power_supply_update_bat_leds()
-> led_trigger_blink() -> led_trigger_blink_setup() (workqueue context),
and potentially prevent other similar usages.
Link: https://lore.kernel.org/lkml/20201101092614.GB3989@xps-13-7390/
Fixes: eb25cb9956cc ("leds: convert IDE trigger to common disk trigger")
Signed-off-by: Andrea Righi <andrea.righi@canonical.com>
Signed-off-by: Pavel Machek <pavel@ucw.cz>
2020-11-25 18:18:22 +03:00
|
|
|
read_lock_irqsave(&trig->leddev_list_lock, flags);
|
2013-12-28 19:00:26 +04:00
|
|
|
list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) {
|
2012-05-27 03:19:22 +04:00
|
|
|
if (oneshot)
|
|
|
|
led_blink_set_oneshot(led_cdev, delay_on, delay_off,
|
|
|
|
invert);
|
|
|
|
else
|
|
|
|
led_blink_set(led_cdev, delay_on, delay_off);
|
2011-01-07 19:28:16 +03:00
|
|
|
}
|
leds: trigger: fix potential deadlock with libata
We have the following potential deadlock condition:
========================================================
WARNING: possible irq lock inversion dependency detected
5.10.0-rc2+ #25 Not tainted
--------------------------------------------------------
swapper/3/0 just changed the state of lock:
ffff8880063bd618 (&host->lock){-...}-{2:2}, at: ata_bmdma_interrupt+0x27/0x200
but this lock took another, HARDIRQ-READ-unsafe lock in the past:
(&trig->leddev_list_lock){.+.?}-{2:2}
and interrupts could create inverse lock ordering between them.
other info that might help us debug this:
Possible interrupt unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&trig->leddev_list_lock);
local_irq_disable();
lock(&host->lock);
lock(&trig->leddev_list_lock);
<Interrupt>
lock(&host->lock);
*** DEADLOCK ***
no locks held by swapper/3/0.
the shortest dependencies between 2nd lock and 1st lock:
-> (&trig->leddev_list_lock){.+.?}-{2:2} ops: 46 {
HARDIRQ-ON-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
IN-SOFTIRQ-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
kbd_bh+0x9e/0xc0
tasklet_action_common.constprop.0+0xe9/0x100
tasklet_action+0x22/0x30
__do_softirq+0xcc/0x46d
run_ksoftirqd+0x3f/0x70
smpboot_thread_fn+0x116/0x1f0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
SOFTIRQ-ON-R at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
INITIAL READ USE at:
lock_acquire+0x15f/0x420
_raw_read_lock+0x42/0x90
led_trigger_event+0x2b/0x70
rfkill_global_led_trigger_worker+0x94/0xb0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
}
... key at: [<ffffffff83da4c00>] __key.0+0x0/0x10
... acquired at:
_raw_read_lock+0x42/0x90
led_trigger_blink_oneshot+0x3b/0x90
ledtrig_disk_activity+0x3c/0xa0
ata_qc_complete+0x26/0x450
ata_do_link_abort+0xa3/0xe0
ata_port_freeze+0x2e/0x40
ata_hsm_qc_complete+0x94/0xa0
ata_sff_hsm_move+0x177/0x7a0
ata_sff_pio_task+0xc7/0x1b0
process_one_work+0x240/0x560
worker_thread+0x58/0x3d0
kthread+0x151/0x170
ret_from_fork+0x1f/0x30
-> (&host->lock){-...}-{2:2} ops: 69 {
IN-HARDIRQ-W at:
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_bmdma_interrupt+0x27/0x200
__handle_irq_event_percpu+0xd5/0x2b0
handle_irq_event+0x57/0xb0
handle_edge_irq+0x8c/0x230
asm_call_irq_on_stack+0xf/0x20
common_interrupt+0x100/0x1c0
asm_common_interrupt+0x1e/0x40
native_safe_halt+0xe/0x10
arch_cpu_idle+0x15/0x20
default_idle_call+0x59/0x1c0
do_idle+0x22c/0x2c0
cpu_startup_entry+0x20/0x30
start_secondary+0x11d/0x150
secondary_startup_64_no_verify+0xa6/0xab
INITIAL USE at:
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_dev_init+0x54/0xe0
ata_link_init+0x8b/0xd0
ata_port_alloc+0x1f1/0x210
ata_host_alloc+0xf1/0x130
ata_host_alloc_pinfo+0x14/0xb0
ata_pci_sff_prepare_host+0x41/0xa0
ata_pci_bmdma_prepare_host+0x14/0x30
piix_init_one+0x21f/0x600
local_pci_probe+0x48/0x80
pci_device_probe+0x105/0x1c0
really_probe+0x221/0x490
driver_probe_device+0xe9/0x160
device_driver_attach+0xb2/0xc0
__driver_attach+0x91/0x150
bus_for_each_dev+0x81/0xc0
driver_attach+0x1e/0x20
bus_add_driver+0x138/0x1f0
driver_register+0x91/0xf0
__pci_register_driver+0x73/0x80
piix_init+0x1e/0x2e
do_one_initcall+0x5f/0x2d0
kernel_init_freeable+0x26f/0x2cf
kernel_init+0xe/0x113
ret_from_fork+0x1f/0x30
}
... key at: [<ffffffff83d9fdc0>] __key.6+0x0/0x10
... acquired at:
__lock_acquire+0x9da/0x2370
lock_acquire+0x15f/0x420
_raw_spin_lock_irqsave+0x52/0xa0
ata_bmdma_interrupt+0x27/0x200
__handle_irq_event_percpu+0xd5/0x2b0
handle_irq_event+0x57/0xb0
handle_edge_irq+0x8c/0x230
asm_call_irq_on_stack+0xf/0x20
common_interrupt+0x100/0x1c0
asm_common_interrupt+0x1e/0x40
native_safe_halt+0xe/0x10
arch_cpu_idle+0x15/0x20
default_idle_call+0x59/0x1c0
do_idle+0x22c/0x2c0
cpu_startup_entry+0x20/0x30
start_secondary+0x11d/0x150
secondary_startup_64_no_verify+0xa6/0xab
This lockdep splat is reported after:
commit e918188611f0 ("locking: More accurate annotations for read_lock()")
To clarify:
- read-locks are recursive only in interrupt context (when
in_interrupt() returns true)
- after acquiring host->lock in CPU1, another cpu (i.e. CPU2) may call
write_lock(&trig->leddev_list_lock) that would be blocked by CPU0
that holds trig->leddev_list_lock in read-mode
- when CPU1 (ata_ac_complete()) tries to read-lock
trig->leddev_list_lock, it would be blocked by the write-lock waiter
on CPU2 (because we are not in interrupt context, so the read-lock is
not recursive)
- at this point if an interrupt happens on CPU0 and
ata_bmdma_interrupt() is executed it will try to acquire host->lock,
that is held by CPU1, that is currently blocked by CPU2, so:
* CPU0 blocked by CPU1
* CPU1 blocked by CPU2
* CPU2 blocked by CPU0
*** DEADLOCK ***
The deadlock scenario is better represented by the following schema
(thanks to Boqun Feng <boqun.feng@gmail.com> for the schema and the
detailed explanation of the deadlock condition):
CPU 0: CPU 1: CPU 2:
----- ----- -----
led_trigger_event():
read_lock(&trig->leddev_list_lock);
<workqueue>
ata_hsm_qc_complete():
spin_lock_irqsave(&host->lock);
write_lock(&trig->leddev_list_lock);
ata_port_freeze():
ata_do_link_abort():
ata_qc_complete():
ledtrig_disk_activity():
led_trigger_blink_oneshot():
read_lock(&trig->leddev_list_lock);
// ^ not in in_interrupt() context, so could get blocked by CPU 2
<interrupt>
ata_bmdma_interrupt():
spin_lock_irqsave(&host->lock);
Fix by using read_lock_irqsave/irqrestore() in led_trigger_event(), so
that no interrupt can happen in between, preventing the deadlock
condition.
Apply the same change to led_trigger_blink_setup() as well, since the
same deadlock scenario can also happen in power_supply_update_bat_leds()
-> led_trigger_blink() -> led_trigger_blink_setup() (workqueue context),
and potentially prevent other similar usages.
Link: https://lore.kernel.org/lkml/20201101092614.GB3989@xps-13-7390/
Fixes: eb25cb9956cc ("leds: convert IDE trigger to common disk trigger")
Signed-off-by: Andrea Righi <andrea.righi@canonical.com>
Signed-off-by: Pavel Machek <pavel@ucw.cz>
2020-11-25 18:18:22 +03:00
|
|
|
read_unlock_irqrestore(&trig->leddev_list_lock, flags);
|
2011-01-07 19:28:16 +03:00
|
|
|
}
|
2012-05-27 03:19:22 +04:00
|
|
|
|
|
|
|
void led_trigger_blink(struct led_trigger *trig,
|
|
|
|
unsigned long *delay_on,
|
|
|
|
unsigned long *delay_off)
|
|
|
|
{
|
|
|
|
led_trigger_blink_setup(trig, delay_on, delay_off, 0, 0);
|
|
|
|
}
|
2011-01-07 19:28:16 +03:00
|
|
|
EXPORT_SYMBOL_GPL(led_trigger_blink);
|
|
|
|
|
2012-05-27 03:19:22 +04:00
|
|
|
void led_trigger_blink_oneshot(struct led_trigger *trig,
|
|
|
|
unsigned long *delay_on,
|
|
|
|
unsigned long *delay_off,
|
|
|
|
int invert)
|
|
|
|
{
|
|
|
|
led_trigger_blink_setup(trig, delay_on, delay_off, 1, invert);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(led_trigger_blink_oneshot);
|
|
|
|
|
2008-03-09 23:59:57 +03:00
|
|
|
void led_trigger_register_simple(const char *name, struct led_trigger **tp)
|
|
|
|
{
|
2012-04-19 07:46:33 +04:00
|
|
|
struct led_trigger *trig;
|
2008-03-09 23:59:57 +03:00
|
|
|
int err;
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2012-04-19 07:46:33 +04:00
|
|
|
trig = kzalloc(sizeof(struct led_trigger), GFP_KERNEL);
|
2006-03-31 14:31:05 +04:00
|
|
|
|
2012-04-19 07:46:33 +04:00
|
|
|
if (trig) {
|
|
|
|
trig->name = name;
|
|
|
|
err = led_trigger_register(trig);
|
2011-11-01 04:11:59 +04:00
|
|
|
if (err < 0) {
|
2012-04-19 07:46:33 +04:00
|
|
|
kfree(trig);
|
|
|
|
trig = NULL;
|
2012-11-25 11:30:44 +04:00
|
|
|
pr_warn("LED trigger %s failed to register (%d)\n",
|
|
|
|
name, err);
|
2011-11-01 04:11:59 +04:00
|
|
|
}
|
2012-11-25 11:30:44 +04:00
|
|
|
} else {
|
|
|
|
pr_warn("LED trigger %s failed to register (no memory)\n",
|
|
|
|
name);
|
|
|
|
}
|
2012-04-19 07:46:33 +04:00
|
|
|
*tp = trig;
|
2008-03-09 23:59:57 +03:00
|
|
|
}
|
2006-03-31 14:31:05 +04:00
|
|
|
EXPORT_SYMBOL_GPL(led_trigger_register_simple);
|
2008-03-09 23:59:57 +03:00
|
|
|
|
2012-04-19 07:46:33 +04:00
|
|
|
void led_trigger_unregister_simple(struct led_trigger *trig)
|
2008-03-09 23:59:57 +03:00
|
|
|
{
|
2012-04-19 07:46:33 +04:00
|
|
|
if (trig)
|
|
|
|
led_trigger_unregister(trig);
|
|
|
|
kfree(trig);
|
2008-03-09 23:59:57 +03:00
|
|
|
}
|
2006-03-31 14:31:05 +04:00
|
|
|
EXPORT_SYMBOL_GPL(led_trigger_unregister_simple);
|