Commit 11f70002 authored by Akinobu Mita's avatar Akinobu Mita Committed by Pavel
Browse files

leds: remove PAGE_SIZE limit of /sys/class/leds/<led>/trigger



Reading /sys/class/leds/<led>/trigger returns all available LED triggers.
However, the size of this file is limited to PAGE_SIZE because of the
limitation for sysfs attribute.

Enabling LED CPU trigger on systems with thousands of CPUs easily hits
PAGE_SIZE limit, and makes it impossible to see all available LED triggers
and which trigger is currently activated.

We work around it here by converting /sys/class/leds/<led>/trigger to
binary attribute, which is not limited by length. This is _not_ good
design, do not copy it.

Signed-off-by: default avatarAkinobu Mita <akinobu.mita@gmail.com>
Cc: "Rafael J. Wysocki" <rafael@kernel.org>
Cc: Pavel Machek <pavel@ucw.cz>
Cc: Dan Murphy <dmurphy@ti.com>A
Reviewed-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: default avatarPavel Machek <pavel@ucw.cz>
parent a2cafdfd
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -74,13 +74,13 @@ static ssize_t max_brightness_show(struct device *dev,
static DEVICE_ATTR_RO(max_brightness);

#ifdef CONFIG_LEDS_TRIGGERS
static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
static struct attribute *led_trigger_attrs[] = {
	&dev_attr_trigger.attr,
static BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0);
static struct bin_attribute *led_trigger_bin_attrs[] = {
	&bin_attr_trigger,
	NULL,
};
static const struct attribute_group led_trigger_group = {
	.attrs = led_trigger_attrs,
	.bin_attrs = led_trigger_bin_attrs,
};
#endif

+68 −22
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
#include <linux/rwsem.h>
#include <linux/leds.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include "leds.h"

/*
@@ -26,9 +27,11 @@ LIST_HEAD(trigger_list);

 /* Used by LED Class */

ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
		const char *buf, size_t count)
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;
@@ -64,39 +67,82 @@ unlock:
	mutex_unlock(&led_cdev->led_access);
	return ret;
}
EXPORT_SYMBOL_GPL(led_trigger_store);
EXPORT_SYMBOL_GPL(led_trigger_write);

ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,
		char *buf)
__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)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	struct led_trigger *trig;
	int len = 0;
	int len = led_trigger_snprintf(buf, size, "%s",
				       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);

		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;

	down_read(&triggers_list_lock);
	down_read(&led_cdev->trigger_lock);

	if (!led_cdev->trigger)
		len += scnprintf(buf+len, PAGE_SIZE - len, "[none] ");
	else
		len += scnprintf(buf+len, PAGE_SIZE - len, "none ");

	list_for_each_entry(trig, &trigger_list, next_trig) {
		if (led_cdev->trigger && !strcmp(led_cdev->trigger->name,
							trig->name))
			len += scnprintf(buf+len, PAGE_SIZE - len, "[%s] ",
					 trig->name);
		else
			len += scnprintf(buf+len, PAGE_SIZE - len, "%s ",
					 trig->name);
	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;
	}
	len = led_trigger_format(data, len + 1, led_cdev);

	up_read(&led_cdev->trigger_lock);
	up_read(&triggers_list_lock);

	len += scnprintf(len+buf, PAGE_SIZE - len, "\n");
	len = memory_read_from_buffer(buf, count, &pos, data, len);

	kvfree(data);

	return len;
}
EXPORT_SYMBOL_GPL(led_trigger_show);
EXPORT_SYMBOL_GPL(led_trigger_read);

/* Caller must ensure led_cdev->trigger_lock held */
int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
+6 −0
Original line number Diff line number Diff line
@@ -23,6 +23,12 @@ void led_set_brightness_nopm(struct led_classdev *led_cdev,
				enum led_brightness value);
void led_set_brightness_nosleep(struct led_classdev *led_cdev,
				enum led_brightness value);
ssize_t led_trigger_read(struct file *filp, struct kobject *kobj,
			struct bin_attribute *attr, char *buf,
			loff_t pos, size_t count);
ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
			struct bin_attribute *bin_attr, char *buf,
			loff_t pos, size_t count);

extern struct rw_semaphore leds_list_lock;
extern struct list_head leds_list;
+0 −5
Original line number Diff line number Diff line
@@ -361,11 +361,6 @@ struct led_trigger {
#define led_trigger_get_led(dev)	((struct led_classdev *)dev_get_drvdata((dev)))
#define led_trigger_get_drvdata(dev)	(led_get_trigger_data(led_trigger_get_led(dev)))

ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
			const char *buf, size_t count);
ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,
			char *buf);

/* Registration functions for complex triggers */
extern int led_trigger_register(struct led_trigger *trigger);
extern void led_trigger_unregister(struct led_trigger *trigger);