linux-block.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Enzo Matsumiya <ematsumiya@suse.de>
To: linux-leds@vger.kernel.org, linux-block@vger.kernel.org
Cc: u.kleine-koenig@pengutronix.de,
	Enzo Matsumiya <ematsumiya@suse.de>, Jens Axboe <axboe@kernel.dk>,
	Pavel Machek <pavel@ucw.cz>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	linux-kernel@vger.kernel.org
Subject: [RFC PATCH 2/2] leds: trigger: implement block trigger
Date: Fri, 30 Apr 2021 15:32:11 -0300	[thread overview]
Message-ID: <20210430183216.27458-3-ematsumiya@suse.de> (raw)
In-Reply-To: <20210430183216.27458-1-ematsumiya@suse.de>

Usage (using capslock LED as example/test):

    openSUSE-tw:/sys/class/leds/input0::capslock # echo block > trigger
    openSUSE-tw:/sys/class/leds/input0::capslock # cat trigger
    none usb-gadget ... ... [block]

A single "block" trigger is created, with each non-empty block device
being available to have its stats polled.

    openSUSE-tw:/sys/class/leds/input0::capslock # ls
    block_devices  brightness  device  max_brightness  power  subsystem  trigger  uevent
    openSUSE-tw:/sys/class/leds/input0::capslock # ls block_devices/
    sda  sr0
    openSUSE-tw:/sys/class/leds/input0::capslock # cat block_devices/sda
    1
    openSUSE-tw:/sys/class/leds/input0::capslock # echo 0 > block_devices/sr0

Activity is then represented in an accumulated manner (part_read_stat_accum()),
with a fixed blinking interval of 50ms.

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 drivers/leds/trigger/Kconfig         |  10 +
 drivers/leds/trigger/Makefile        |   1 +
 drivers/leds/trigger/ledtrig-block.c | 293 +++++++++++++++++++++++++++
 3 files changed, 304 insertions(+)
 create mode 100644 drivers/leds/trigger/ledtrig-block.c

diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig
index b77a01bd27f4..bead31a19148 100644
--- a/drivers/leds/trigger/Kconfig
+++ b/drivers/leds/trigger/Kconfig
@@ -153,4 +153,14 @@ config LEDS_TRIGGER_TTY
 
 	  When build as a module this driver will be called ledtrig-tty.
 
+config LEDS_TRIGGER_BLOCK
+	tristate "LED Block Device Trigger"
+	depends on BLOCK
+	default m
+	help
+	  This allows LEDs to be controlled by block device activity.
+	  This trigger doesn't require the lower level drivers to have any
+	  instrumentation. The activity is collected by polling the disk stats.
+	  If unsure, say Y.
+
 endif # LEDS_TRIGGERS
diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile
index 25c4db97cdd4..cadc77d95802 100644
--- a/drivers/leds/trigger/Makefile
+++ b/drivers/leds/trigger/Makefile
@@ -16,3 +16,4 @@ obj-$(CONFIG_LEDS_TRIGGER_NETDEV)	+= ledtrig-netdev.o
 obj-$(CONFIG_LEDS_TRIGGER_PATTERN)	+= ledtrig-pattern.o
 obj-$(CONFIG_LEDS_TRIGGER_AUDIO)	+= ledtrig-audio.o
 obj-$(CONFIG_LEDS_TRIGGER_TTY)		+= ledtrig-tty.o
+obj-$(CONFIG_LEDS_TRIGGER_BLOCK)	+= ledtrig-block.o
diff --git a/drivers/leds/trigger/ledtrig-block.c b/drivers/leds/trigger/ledtrig-block.c
new file mode 100644
index 000000000000..b00dbf916876
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-block.c
@@ -0,0 +1,293 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * LED block trigger
+ *
+ * Copyright (C) 2021 Enzo Matsumiya <ematsumiya@suse.de>
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/genhd.h>
+#include <linux/leds.h>
+#include <linux/workqueue.h>
+#include <linux/part_stat.h>
+
+#include "../leds.h"
+
+extern struct class block_class;
+extern const struct device_type disk_type;
+
+struct ledtrig_blk_data {
+	struct led_classdev *led_cdev;
+	struct list_head block_devices;
+};
+
+struct ledtrig_blk_device {
+	struct list_head list;
+
+	struct ledtrig_blk_data *data;
+
+	struct gendisk *disk;
+	struct device_attribute attr;
+	struct delayed_work work;
+	struct mutex lock;
+	u64 last_activity;
+	bool observed;
+};
+
+/*
+ * Blink interval in msecs
+ */
+#define BLINK_INTERVAL 50
+
+
+/*
+ * Helpers
+ */
+
+static int _for_each_blk(void *data,
+			  int (*fn)(void *, struct gendisk *))
+{
+	struct class_dev_iter iter;
+	struct device *dev;
+	int err;
+
+	/* iterate through all block devices on the system */
+	class_dev_iter_init(&iter, &block_class, NULL, &disk_type);
+	while ((dev = class_dev_iter_next(&iter))) {
+	        struct gendisk *disk = dev_to_disk(dev);
+
+	        err = fn(data, disk);
+
+	        if (err) {
+	                pr_err("error running fn() on disk %s\n", disk->disk_name);
+	                return err;
+	        }
+	}
+	class_dev_iter_exit(&iter);
+
+	return 0;
+}
+
+
+/*
+ * Device attr
+ */
+
+static ssize_t ledtrig_blk_device_show(struct device *dev,
+				       struct device_attribute *attr, char *buf)
+{
+	struct ledtrig_blk_device *device = container_of(attr,
+							 struct ledtrig_blk_device,
+							 attr);
+	bool observed;
+
+	mutex_lock(&device->lock);
+	observed = device->observed;
+	mutex_unlock(&device->lock);
+
+	return sprintf(buf, "%d\n", observed) + 1;
+}
+
+static ssize_t ledtrig_blk_device_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t size)
+{
+	struct ledtrig_blk_device *device = container_of(attr,
+							 struct ledtrig_blk_device,
+							 attr);
+	int err = -EINVAL;
+
+	mutex_lock(&device->lock);
+	if (!strcmp(buf, "0") || !strcmp(buf, "0\n"))
+		device->observed = 0;
+	else if (!strcmp(buf, "1") || !strcmp(buf, "1\n"))
+		device->observed = 1;
+	else
+		goto out_unlock;
+
+	err = size;
+
+out_unlock:
+	mutex_unlock(&device->lock);
+
+	return err;
+}
+
+static struct attribute *devices_attrs[] = {
+	NULL,
+};
+
+static const struct attribute_group devices_group = {
+	.name = "block_devices",
+	.attrs = devices_attrs,
+};
+
+
+/*
+ * Work
+ */
+
+static void ledtrig_blk_work(struct work_struct *work)
+{
+	struct ledtrig_blk_device *device = container_of(work, struct ledtrig_blk_device, work.work);
+	struct gendisk *disk;
+	unsigned long interval = BLINK_INTERVAL;
+	u64 activity;
+
+	if (!device->observed)
+		goto out;
+
+	disk = device->disk;
+	activity = part_stat_read_accum(disk->part0, ios);
+
+	if (device->last_activity != activity) {
+		led_stop_software_blink(device->data->led_cdev);
+		led_blink_set_oneshot(device->data->led_cdev, &interval, &interval, 0);
+
+		device->last_activity = activity;
+	}
+
+out:
+	schedule_delayed_work(&device->work, interval * 2);
+}
+
+
+/*
+ * Adding & removing block devices
+ */
+
+static int ledtrig_blk_add_device(void *data,
+				  struct gendisk *disk)
+{
+	struct ledtrig_blk_data *led_blk_data = (struct ledtrig_blk_data *) data;
+	struct led_classdev *led_cdev = led_blk_data->led_cdev;
+	struct ledtrig_blk_device *device;
+	int err;
+
+	device = kzalloc(sizeof(*device), GFP_KERNEL);
+	if (!device) {
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	device->data = led_blk_data;
+	device->observed = true;
+
+	sysfs_attr_init(&device->attr.attr);
+	device->attr.attr.name = disk->disk_name;
+	device->attr.attr.mode = S_IRUSR | S_IWUSR;
+	device->attr.show = ledtrig_blk_device_show;
+	device->attr.store = ledtrig_blk_device_store;
+	device->disk = disk;
+	device->last_activity = 0;
+
+	INIT_DELAYED_WORK(&device->work, ledtrig_blk_work);
+	mutex_init(&device->lock);
+
+	list_add_tail(&device->list, &led_blk_data->block_devices);
+
+	err = sysfs_add_file_to_group(&led_cdev->dev->kobj, &device->attr.attr,
+				      devices_group.name);
+	if (err)
+		goto err_free;
+
+	schedule_delayed_work(&device->work, BLINK_INTERVAL * 2);
+
+	return 0;
+
+err_free:
+	kfree(device);
+err_out:
+	return err;
+}
+
+static int ledtrig_blk_add_all_devices(struct ledtrig_blk_data *led_blk_data)
+{
+	(void)_for_each_blk(led_blk_data, ledtrig_blk_add_device);
+
+	return 0;
+}
+
+static void ledtrig_blk_remove_device(struct ledtrig_blk_data *led_blk_data,
+				      struct ledtrig_blk_device *device)
+{
+	struct led_classdev *led_cdev = led_blk_data->led_cdev;
+
+	list_del(&device->list);
+	sysfs_remove_file_from_group(&led_cdev->dev->kobj, &device->attr.attr,
+				     devices_group.name);
+	kfree(device);
+}
+
+
+/*
+ * Init, exit, etc
+ */
+
+static int ledtrig_blk_activate(struct led_classdev *led_cdev)
+{
+	struct ledtrig_blk_data *led_blk_data;
+	int err;
+
+	led_blk_data = kzalloc(sizeof(*led_blk_data), GFP_KERNEL);
+	if (!led_blk_data)
+		return -ENOMEM;
+
+	led_blk_data->led_cdev = led_cdev;
+
+	/* List of devices */
+	INIT_LIST_HEAD(&led_blk_data->block_devices);
+	err = sysfs_create_group(&led_cdev->dev->kobj, &devices_group);
+	if (err)
+		goto err_free;
+
+	ledtrig_blk_add_all_devices(led_blk_data);
+	led_set_trigger_data(led_cdev, led_blk_data);
+
+	return 0;
+
+err_free:
+	kfree(led_blk_data);
+	return err;
+}
+
+static void ledtrig_blk_deactivate(struct led_classdev *led_cdev)
+{
+	struct ledtrig_blk_data *led_blk_data = led_get_trigger_data(led_cdev);
+	struct ledtrig_blk_device *device, *tmp;
+
+	list_for_each_entry_safe(device, tmp, &led_blk_data->block_devices, list) {
+		cancel_delayed_work_sync(&device->work);
+		ledtrig_blk_remove_device(led_blk_data, device);
+	}
+
+	sysfs_remove_group(&led_cdev->dev->kobj, &devices_group);
+
+	kfree(led_blk_data);
+}
+
+static struct led_trigger ledtrig_blk_trigger = {
+	.name = "block",
+	.activate = ledtrig_blk_activate,
+	.deactivate = ledtrig_blk_deactivate,
+};
+
+static int __init ledtrig_blk_init(void)
+{
+	return led_trigger_register(&ledtrig_blk_trigger);
+}
+
+static void __exit ledtrig_blk_exit(void)
+{
+	led_trigger_unregister(&ledtrig_blk_trigger);
+}
+
+module_init(ledtrig_blk_init);
+module_exit(ledtrig_blk_exit);
+
+MODULE_AUTHOR("Enzo Matsumiya <ematsumiya@suse.de>");
+MODULE_DESCRIPTION("LED block trigger");
+MODULE_LICENSE("GPL v2");
+
-- 
2.31.1


  parent reply	other threads:[~2021-04-30 18:32 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-04-30 18:32 [RFC PATCH 0/2] leds: trigger: introduce block trigger Enzo Matsumiya
2021-04-30 18:32 ` [RFC PATCH 1/2] block: export block_class and disk_type symbols Enzo Matsumiya
2021-05-01  6:24   ` Greg Kroah-Hartman
2021-05-03  2:37     ` Enzo Matsumiya
2021-05-03  4:48       ` Greg Kroah-Hartman
2021-05-03  7:04   ` Christoph Hellwig
2021-05-03 16:50     ` Enzo Matsumiya
2021-04-30 18:32 ` Enzo Matsumiya [this message]
2021-04-30 18:52   ` [RFC PATCH 2/2] leds: trigger: implement block trigger Randy Dunlap
2021-05-03  2:38     ` Enzo Matsumiya
2021-04-30 20:11   ` Marek Behun
2021-05-03  2:46     ` Enzo Matsumiya
2021-05-03  5:53   ` Hannes Reinecke
2021-05-03 10:11     ` Pavel Machek
2021-05-03 16:56       ` Enzo Matsumiya
2021-05-04 15:43         ` Pavel Machek

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210430183216.27458-3-ematsumiya@suse.de \
    --to=ematsumiya@suse.de \
    --cc=axboe@kernel.dk \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-block@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=pavel@ucw.cz \
    --cc=u.kleine-koenig@pengutronix.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).