This commit allows inversion of triggers by appending "-inverted" to the name of a trigger. This can be useful when dealing with device that lack separate LEDs for indicating e.g. disk activity. With this commit applied the power LED can be used as a disk activity indicator by setting its default state to on and using the "disk-activity-inverted" trigger. The LED will then turn off briefly when there is disk activity. Signed-off-by: Tobias Schramm <t.schramm@xxxxxxxxxxx> --- drivers/leds/led-triggers.c | 148 +++++++++++++++++++++++++++--------- include/linux/leds.h | 1 + 2 files changed, 111 insertions(+), 38 deletions(-) diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index 79e30d2cb7a5..c40c58c6e345 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -27,14 +27,80 @@ LIST_HEAD(trigger_list); /* Used by LED Class */ +#define TRIGGER_INVERT_SUFFIX "-inverted" + +/* + * Check suffix of trigger name agains TRIGGER_INVERT_SUFFIX + */ +static bool led_trigger_is_inverted(const char *trigname) +{ + if (strlen(trigname) >= strlen(TRIGGER_INVERT_SUFFIX)) { + return !strcmp(trigname + strlen(trigname) - + strlen(TRIGGER_INVERT_SUFFIX), + TRIGGER_INVERT_SUFFIX); + } + + return false; +} + +/* + * Get length of trigger name name without TRIGGER_INVERT_SUFFIX + */ +static size_t led_trigger_get_name_len(const char *trigname) +{ + // Subtract length of TRIGGER_INVERT_SUFFIX if trigger is inverted + if (led_trigger_is_inverted(trigname)) + return strlen(trigname) - strlen(TRIGGER_INVERT_SUFFIX); + return strlen(trigname); +} + +/* + * Find and set led trigger by name + */ +static int led_trigger_set_str_(struct led_classdev *led_cdev, + const char *trigname, bool lock) +{ + struct led_trigger *trig; + bool inverted = led_trigger_is_inverted(trigname); + size_t len = led_trigger_get_name_len(trigname); + + down_read(&triggers_list_lock); + list_for_each_entry(trig, &trigger_list, next_trig) { + /* Compare trigger name without inversion suffix */ + if (strlen(trig->name) == len && + !strncmp(trigname, trig->name, len)) { + if (lock) + down_write(&led_cdev->trigger_lock); + led_trigger_set(led_cdev, trig); + if (inverted) + led_cdev->flags |= LED_INVERT_TRIGGER; + else + led_cdev->flags &= ~LED_INVERT_TRIGGER; + if (lock) + up_write(&led_cdev->trigger_lock); + + up_read(&triggers_list_lock); + return 0; + } + } + /* we come here only if trigname matches no trigger */ + up_read(&triggers_list_lock); + return -EINVAL; +} + +#define led_trigger_set_str(cdev, name) led_trigger_set_str_(cdev, name, true) +#define led_trigger_set_str_unlocked(cdev, name) \ + led_trigger_set_str_(cdev, name, false) + + ssize_t led_trigger_write(struct file *filp, struct kobject *kobj, struct bin_attribute *bin_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); - struct led_trigger *trig; int ret = count; + char *name; mutex_lock(&led_cdev->led_access); @@ -48,20 +114,10 @@ ssize_t led_trigger_write(struct file *filp, struct kobject *kobj, goto unlock; } - down_read(&triggers_list_lock); - list_for_each_entry(trig, &trigger_list, next_trig) { - if (sysfs_streq(buf, trig->name)) { - down_write(&led_cdev->trigger_lock); - led_trigger_set(led_cdev, trig); - up_write(&led_cdev->trigger_lock); - - up_read(&triggers_list_lock); - goto unlock; - } - } - /* we come here only if buf matches no trigger */ - ret = -EINVAL; - up_read(&triggers_list_lock); + name = strim(buf); + ret = led_trigger_set_str(led_cdev, name); + if (!ret) + ret = count; unlock: mutex_unlock(&led_cdev->led_access); @@ -93,12 +149,22 @@ static int led_trigger_format(char *buf, size_t size, led_cdev->trigger ? "none" : "[none]"); list_for_each_entry(trig, &trigger_list, next_trig) { - bool hit = led_cdev->trigger && - !strcmp(led_cdev->trigger->name, trig->name); + bool hit = led_cdev->trigger == trig; + bool inverted = led_cdev->flags & LED_INVERT_TRIGGER; + + /* print non-inverted trigger */ + len += led_trigger_snprintf(buf + len, size - len, + " %s%s%s", + hit && !inverted ? "[" : "", + trig->name, + hit && !inverted ? "]" : ""); + /* print inverted trigger */ len += led_trigger_snprintf(buf + len, size - len, - " %s%s%s", hit ? "[" : "", - trig->name, hit ? "]" : ""); + " %s%s"TRIGGER_INVERT_SUFFIX"%s", + hit && inverted ? "[" : "", + trig->name, + hit && inverted ? "]" : ""); } len += led_trigger_snprintf(buf + len, size - len, "\n"); @@ -235,21 +301,15 @@ EXPORT_SYMBOL_GPL(led_trigger_remove); void led_trigger_set_default(struct led_classdev *led_cdev) { - struct led_trigger *trig; + bool found; if (!led_cdev->default_trigger) return; down_read(&triggers_list_lock); - down_write(&led_cdev->trigger_lock); - list_for_each_entry(trig, &trigger_list, next_trig) { - if (!strcmp(led_cdev->default_trigger, trig->name)) { - led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER; - led_trigger_set(led_cdev, trig); - break; - } - } - up_write(&led_cdev->trigger_lock); + found = !led_trigger_set_str(led_cdev, led_cdev->default_trigger); + if (found) + led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER; up_read(&triggers_list_lock); } EXPORT_SYMBOL_GPL(led_trigger_set_default); @@ -292,11 +352,14 @@ int led_trigger_register(struct led_trigger *trig) /* Register with any LEDs that have this as a default trigger */ down_read(&leds_list_lock); list_for_each_entry(led_cdev, &leds_list, node) { + bool found; + down_write(&led_cdev->trigger_lock); - if (!led_cdev->trigger && led_cdev->default_trigger && - !strcmp(led_cdev->default_trigger, trig->name)) { - led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER; - led_trigger_set(led_cdev, trig); + if (!led_cdev->trigger && led_cdev->default_trigger) { + found = !led_trigger_set_str_unlocked(led_cdev, + led_cdev->default_trigger); + if (found) + led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER; } up_write(&led_cdev->trigger_lock); } @@ -369,8 +432,14 @@ void led_trigger_event(struct led_trigger *trig, return; read_lock(&trig->leddev_list_lock); - list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) - led_set_brightness(led_cdev, brightness); + list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) { + /* Reverse brightness if LED is inverted */ + if (led_cdev->flags & LED_INVERT_TRIGGER) + led_set_brightness(led_cdev, + led_cdev->max_brightness - brightness); + else + led_set_brightness(led_cdev, brightness); + } read_unlock(&trig->leddev_list_lock); } EXPORT_SYMBOL_GPL(led_trigger_event); @@ -388,10 +457,13 @@ static void led_trigger_blink_setup(struct led_trigger *trig, read_lock(&trig->leddev_list_lock); list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) { - if (oneshot) + bool trigger_inverted = + !!(led_cdev->flags & LED_INVERT_TRIGGER); + if (oneshot) { + /* use logical xnor to determine inversion parameter */ led_blink_set_oneshot(led_cdev, delay_on, delay_off, - invert); - else + (!!invert) == trigger_inverted); + } else led_blink_set(led_cdev, delay_on, delay_off); } read_unlock(&trig->leddev_list_lock); diff --git a/include/linux/leds.h b/include/linux/leds.h index 75353e5f9d13..fa707170b69e 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -74,6 +74,7 @@ struct led_classdev { #define LED_BRIGHT_HW_CHANGED BIT(21) #define LED_RETAIN_AT_SHUTDOWN BIT(22) #define LED_INIT_DEFAULT_TRIGGER BIT(23) +#define LED_INVERT_TRIGGER BIT(24) /* set_brightness_work / blink_timer flags, atomic, private. */ unsigned long work_flags; -- 2.24.1