| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * LED Triggers Core |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright 2005-2007 Openedhand Ltd. |
|---|
| 5 | 6 | * |
|---|
| 6 | 7 | * Author: Richard Purdie <rpurdie@openedhand.com> |
|---|
| 7 | | - * |
|---|
| 8 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 9 | | - * it under the terms of the GNU General Public License version 2 as |
|---|
| 10 | | - * published by the Free Software Foundation. |
|---|
| 11 | | - * |
|---|
| 12 | 8 | */ |
|---|
| 13 | 9 | |
|---|
| 14 | 10 | #include <linux/export.h> |
|---|
| .. | .. |
|---|
| 20 | 16 | #include <linux/rwsem.h> |
|---|
| 21 | 17 | #include <linux/leds.h> |
|---|
| 22 | 18 | #include <linux/slab.h> |
|---|
| 19 | +#include <linux/mm.h> |
|---|
| 23 | 20 | #include "leds.h" |
|---|
| 24 | 21 | |
|---|
| 25 | 22 | /* |
|---|
| .. | .. |
|---|
| 30 | 27 | |
|---|
| 31 | 28 | /* Used by LED Class */ |
|---|
| 32 | 29 | |
|---|
| 33 | | -ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr, |
|---|
| 34 | | - const char *buf, size_t count) |
|---|
| 30 | +static inline bool |
|---|
| 31 | +trigger_relevant(struct led_classdev *led_cdev, struct led_trigger *trig) |
|---|
| 35 | 32 | { |
|---|
| 33 | + return !trig->trigger_type || trig->trigger_type == led_cdev->trigger_type; |
|---|
| 34 | +} |
|---|
| 35 | + |
|---|
| 36 | +ssize_t led_trigger_write(struct file *filp, struct kobject *kobj, |
|---|
| 37 | + struct bin_attribute *bin_attr, char *buf, |
|---|
| 38 | + loff_t pos, size_t count) |
|---|
| 39 | +{ |
|---|
| 40 | + struct device *dev = kobj_to_dev(kobj); |
|---|
| 36 | 41 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
|---|
| 37 | 42 | struct led_trigger *trig; |
|---|
| 38 | 43 | int ret = count; |
|---|
| .. | .. |
|---|
| 51 | 56 | |
|---|
| 52 | 57 | down_read(&triggers_list_lock); |
|---|
| 53 | 58 | list_for_each_entry(trig, &trigger_list, next_trig) { |
|---|
| 54 | | - if (sysfs_streq(buf, trig->name)) { |
|---|
| 59 | + if (sysfs_streq(buf, trig->name) && trigger_relevant(led_cdev, trig)) { |
|---|
| 55 | 60 | down_write(&led_cdev->trigger_lock); |
|---|
| 56 | 61 | led_trigger_set(led_cdev, trig); |
|---|
| 57 | 62 | up_write(&led_cdev->trigger_lock); |
|---|
| .. | .. |
|---|
| 68 | 73 | mutex_unlock(&led_cdev->led_access); |
|---|
| 69 | 74 | return ret; |
|---|
| 70 | 75 | } |
|---|
| 71 | | -EXPORT_SYMBOL_GPL(led_trigger_store); |
|---|
| 76 | +EXPORT_SYMBOL_GPL(led_trigger_write); |
|---|
| 72 | 77 | |
|---|
| 73 | | -ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr, |
|---|
| 74 | | - char *buf) |
|---|
| 78 | +__printf(3, 4) |
|---|
| 79 | +static int led_trigger_snprintf(char *buf, ssize_t size, const char *fmt, ...) |
|---|
| 75 | 80 | { |
|---|
| 76 | | - struct led_classdev *led_cdev = dev_get_drvdata(dev); |
|---|
| 81 | + va_list args; |
|---|
| 82 | + int i; |
|---|
| 83 | + |
|---|
| 84 | + va_start(args, fmt); |
|---|
| 85 | + if (size <= 0) |
|---|
| 86 | + i = vsnprintf(NULL, 0, fmt, args); |
|---|
| 87 | + else |
|---|
| 88 | + i = vscnprintf(buf, size, fmt, args); |
|---|
| 89 | + va_end(args); |
|---|
| 90 | + |
|---|
| 91 | + return i; |
|---|
| 92 | +} |
|---|
| 93 | + |
|---|
| 94 | +static int led_trigger_format(char *buf, size_t size, |
|---|
| 95 | + struct led_classdev *led_cdev) |
|---|
| 96 | +{ |
|---|
| 77 | 97 | struct led_trigger *trig; |
|---|
| 78 | | - int len = 0; |
|---|
| 98 | + int len = led_trigger_snprintf(buf, size, "%s", |
|---|
| 99 | + led_cdev->trigger ? "none" : "[none]"); |
|---|
| 100 | + |
|---|
| 101 | + list_for_each_entry(trig, &trigger_list, next_trig) { |
|---|
| 102 | + bool hit; |
|---|
| 103 | + |
|---|
| 104 | + if (!trigger_relevant(led_cdev, trig)) |
|---|
| 105 | + continue; |
|---|
| 106 | + |
|---|
| 107 | + hit = led_cdev->trigger && !strcmp(led_cdev->trigger->name, trig->name); |
|---|
| 108 | + |
|---|
| 109 | + len += led_trigger_snprintf(buf + len, size - len, |
|---|
| 110 | + " %s%s%s", hit ? "[" : "", |
|---|
| 111 | + trig->name, hit ? "]" : ""); |
|---|
| 112 | + } |
|---|
| 113 | + |
|---|
| 114 | + len += led_trigger_snprintf(buf + len, size - len, "\n"); |
|---|
| 115 | + |
|---|
| 116 | + return len; |
|---|
| 117 | +} |
|---|
| 118 | + |
|---|
| 119 | +/* |
|---|
| 120 | + * It was stupid to create 10000 cpu triggers, but we are stuck with it now. |
|---|
| 121 | + * Don't make that mistake again. We work around it here by creating binary |
|---|
| 122 | + * attribute, which is not limited by length. This is _not_ good design, do not |
|---|
| 123 | + * copy it. |
|---|
| 124 | + */ |
|---|
| 125 | +ssize_t led_trigger_read(struct file *filp, struct kobject *kobj, |
|---|
| 126 | + struct bin_attribute *attr, char *buf, |
|---|
| 127 | + loff_t pos, size_t count) |
|---|
| 128 | +{ |
|---|
| 129 | + struct device *dev = kobj_to_dev(kobj); |
|---|
| 130 | + struct led_classdev *led_cdev = dev_get_drvdata(dev); |
|---|
| 131 | + void *data; |
|---|
| 132 | + int len; |
|---|
| 79 | 133 | |
|---|
| 80 | 134 | down_read(&triggers_list_lock); |
|---|
| 81 | 135 | down_read(&led_cdev->trigger_lock); |
|---|
| 82 | 136 | |
|---|
| 83 | | - if (!led_cdev->trigger) |
|---|
| 84 | | - len += scnprintf(buf+len, PAGE_SIZE - len, "[none] "); |
|---|
| 85 | | - else |
|---|
| 86 | | - len += scnprintf(buf+len, PAGE_SIZE - len, "none "); |
|---|
| 87 | | - |
|---|
| 88 | | - list_for_each_entry(trig, &trigger_list, next_trig) { |
|---|
| 89 | | - if (led_cdev->trigger && !strcmp(led_cdev->trigger->name, |
|---|
| 90 | | - trig->name)) |
|---|
| 91 | | - len += scnprintf(buf+len, PAGE_SIZE - len, "[%s] ", |
|---|
| 92 | | - trig->name); |
|---|
| 93 | | - else |
|---|
| 94 | | - len += scnprintf(buf+len, PAGE_SIZE - len, "%s ", |
|---|
| 95 | | - trig->name); |
|---|
| 137 | + len = led_trigger_format(NULL, 0, led_cdev); |
|---|
| 138 | + data = kvmalloc(len + 1, GFP_KERNEL); |
|---|
| 139 | + if (!data) { |
|---|
| 140 | + up_read(&led_cdev->trigger_lock); |
|---|
| 141 | + up_read(&triggers_list_lock); |
|---|
| 142 | + return -ENOMEM; |
|---|
| 96 | 143 | } |
|---|
| 144 | + len = led_trigger_format(data, len + 1, led_cdev); |
|---|
| 145 | + |
|---|
| 97 | 146 | up_read(&led_cdev->trigger_lock); |
|---|
| 98 | 147 | up_read(&triggers_list_lock); |
|---|
| 99 | 148 | |
|---|
| 100 | | - len += scnprintf(len+buf, PAGE_SIZE - len, "\n"); |
|---|
| 149 | + len = memory_read_from_buffer(buf, count, &pos, data, len); |
|---|
| 150 | + |
|---|
| 151 | + kvfree(data); |
|---|
| 152 | + |
|---|
| 101 | 153 | return len; |
|---|
| 102 | 154 | } |
|---|
| 103 | | -EXPORT_SYMBOL_GPL(led_trigger_show); |
|---|
| 155 | +EXPORT_SYMBOL_GPL(led_trigger_read); |
|---|
| 104 | 156 | |
|---|
| 105 | 157 | /* Caller must ensure led_cdev->trigger_lock held */ |
|---|
| 106 | 158 | int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) |
|---|
| .. | .. |
|---|
| 201 | 253 | down_read(&triggers_list_lock); |
|---|
| 202 | 254 | down_write(&led_cdev->trigger_lock); |
|---|
| 203 | 255 | list_for_each_entry(trig, &trigger_list, next_trig) { |
|---|
| 204 | | - if (!strcmp(led_cdev->default_trigger, trig->name)) |
|---|
| 256 | + if (!strcmp(led_cdev->default_trigger, trig->name) && |
|---|
| 257 | + trigger_relevant(led_cdev, trig)) { |
|---|
| 258 | + led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER; |
|---|
| 205 | 259 | led_trigger_set(led_cdev, trig); |
|---|
| 260 | + break; |
|---|
| 261 | + } |
|---|
| 206 | 262 | } |
|---|
| 207 | 263 | up_write(&led_cdev->trigger_lock); |
|---|
| 208 | 264 | up_read(&triggers_list_lock); |
|---|
| .. | .. |
|---|
| 235 | 291 | down_write(&triggers_list_lock); |
|---|
| 236 | 292 | /* Make sure the trigger's name isn't already in use */ |
|---|
| 237 | 293 | list_for_each_entry(_trig, &trigger_list, next_trig) { |
|---|
| 238 | | - if (!strcmp(_trig->name, trig->name)) { |
|---|
| 294 | + if (!strcmp(_trig->name, trig->name) && |
|---|
| 295 | + (trig->trigger_type == _trig->trigger_type || |
|---|
| 296 | + !trig->trigger_type || !_trig->trigger_type)) { |
|---|
| 239 | 297 | up_write(&triggers_list_lock); |
|---|
| 240 | 298 | return -EEXIST; |
|---|
| 241 | 299 | } |
|---|
| .. | .. |
|---|
| 249 | 307 | list_for_each_entry(led_cdev, &leds_list, node) { |
|---|
| 250 | 308 | down_write(&led_cdev->trigger_lock); |
|---|
| 251 | 309 | if (!led_cdev->trigger && led_cdev->default_trigger && |
|---|
| 252 | | - !strcmp(led_cdev->default_trigger, trig->name)) |
|---|
| 310 | + !strcmp(led_cdev->default_trigger, trig->name) && |
|---|
| 311 | + trigger_relevant(led_cdev, trig)) { |
|---|
| 312 | + led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER; |
|---|
| 253 | 313 | led_trigger_set(led_cdev, trig); |
|---|
| 314 | + } |
|---|
| 254 | 315 | up_write(&led_cdev->trigger_lock); |
|---|
| 255 | 316 | } |
|---|
| 256 | 317 | up_read(&leds_list_lock); |
|---|
| .. | .. |
|---|
| 311 | 372 | } |
|---|
| 312 | 373 | EXPORT_SYMBOL_GPL(devm_led_trigger_register); |
|---|
| 313 | 374 | |
|---|
| 314 | | -/* Simple LED Tigger Interface */ |
|---|
| 375 | +/* Simple LED Trigger Interface */ |
|---|
| 315 | 376 | |
|---|
| 316 | 377 | void led_trigger_event(struct led_trigger *trig, |
|---|
| 317 | 378 | enum led_brightness brightness) |
|---|