[RFC PATCH 6/8] block: Add kernel APIs to set & clear per-block device LED triggers

From: Ian Pilcher
Date: Wed Jul 28 2021 - 21:54:47 EST


* Clear LED trigger (decrement trigger reference count) when device
is deleted, e.g. when a USB disk is unplugged.

Signed-off-by: Ian Pilcher <arequipeno@xxxxxxxxx>
---
block/blk-ledtrig.c | 131 ++++++++++++++++++++++++++++++++++++
block/blk-ledtrig.h | 5 ++
block/genhd.c | 2 +
include/linux/blk-ledtrig.h | 5 ++
4 files changed, 143 insertions(+)

diff --git a/block/blk-ledtrig.c b/block/blk-ledtrig.c
index 6392ab4169f9..7c8fdff88683 100644
--- a/block/blk-ledtrig.c
+++ b/block/blk-ledtrig.c
@@ -348,3 +348,134 @@ void __init blk_ledtrig_init(void)
init_error_new:
pr_err("failed to initialize blkdev LED triggers (%d)\n", ret);
}
+
+
+/*
+ *
+ * Set a device trigger
+ *
+ */
+
+static int __blk_ledtrig_set(struct gendisk *const gd, const char *const name,
+ const size_t name_len)
+{
+ struct blk_ledtrig *t;
+ bool already_set;
+ int ret;
+
+ ret = mutex_lock_interruptible(&blk_ledtrig_list_mutex);
+ if (unlikely(ret != 0))
+ goto set_exit_return;
+
+ t = blk_ledtrig_find(name, name_len);
+ if (t == NULL) {
+ pr_warn("blockdev LED trigger named %.*s doesn't exist\n",
+ (int)name_len, name);
+ ret = -ENODEV;
+ goto set_exit_unlock_list;
+ }
+
+ ret = mutex_lock_interruptible(&t->refcount_mutex);
+ if (unlikely(ret != 0))
+ goto set_exit_unlock_list;
+
+ // Holding the refcount mutex blocks __blk_ledtrig_delete, so we don't
+ // actually need to hold the list mutex anymore, but it makes the flow
+ // much simpler to do so
+
+ if (WARN_ON_ONCE(t->refcount == INT_MAX)) {
+ ret = -ERANGE;
+ goto set_exit_unlock_refcount;
+ }
+
+ ret = mutex_lock_interruptible(&gd->ledtrig_mutex);
+ if (unlikely(ret != 0))
+ goto set_exit_unlock_refcount;
+
+ if (gd->ledtrig == NULL) {
+ already_set = false;
+ gd->ledtrig = t;
+ } else {
+ already_set = true;
+ }
+
+ mutex_unlock(&gd->ledtrig_mutex);
+
+ if (already_set) {
+ pr_warn("blockdev trigger for %s already set\n",
+ gd->disk_name);
+ ret = -EBUSY;
+ goto set_exit_unlock_refcount;
+ }
+
+ ++(t->refcount);
+ ret = 0;
+
+set_exit_unlock_refcount:
+ mutex_unlock(&t->refcount_mutex);
+set_exit_unlock_list:
+ mutex_unlock(&blk_ledtrig_list_mutex);
+set_exit_return:
+ return ret;
+}
+
+/**
+ * blk_ledtrig_set() - set the LED trigger for a block device
+ * @gd: the block device
+ * @name: the name of the LED trigger
+ *
+ * Context: Process context (can sleep). Takes and releases
+ * @blk_ledtrig_list_mutex, trigger's @refcount_mutex,
+ * and @gd->ledtrig_mutex.
+ *
+ * Return: 0 on success; -@errno on error
+ */
+int blk_ledtrig_set(struct gendisk *const gd, const char *const name)
+{
+ return __blk_ledtrig_set(gd, name, strlen(name));
+}
+EXPORT_SYMBOL_GPL(blk_ledtrig_set);
+
+
+/*
+ *
+ * Clear a device trigger
+ *
+ */
+
+/**
+ * blk_ledtrig_clear() - clear the LED trigger of a block device
+ * @gd: the block device
+ *
+ * Context: Process context (can sleep). Takes and releases
+ * @gd->ledtrig_mutex and @gd->ledtrig->refcount_mutex.
+ *
+ * Return: @true if the trigger was actually cleared; @false if it wasn't set
+ */
+bool blk_ledtrig_clear(struct gendisk *const gd)
+{
+ struct blk_ledtrig *t;
+ bool changed;
+ int new_refcount;
+
+ mutex_lock(&gd->ledtrig_mutex);
+
+ t = gd->ledtrig;
+ if (t == NULL) {
+ changed = false;
+ goto clear_exit_unlock_ledtrig;
+ }
+
+ mutex_lock(&t->refcount_mutex);
+ new_refcount = --(t->refcount);
+ mutex_unlock(&t->refcount_mutex);
+
+ gd->ledtrig = NULL;
+ changed = true;
+
+clear_exit_unlock_ledtrig:
+ mutex_unlock(&gd->ledtrig_mutex);
+ WARN_ON(changed && (new_refcount < 0));
+ return changed;
+}
+EXPORT_SYMBOL_GPL(blk_ledtrig_clear);
diff --git a/block/blk-ledtrig.h b/block/blk-ledtrig.h
index 5854b21a210c..9b718d45783f 100644
--- a/block/blk-ledtrig.h
+++ b/block/blk-ledtrig.h
@@ -24,6 +24,11 @@ static inline void blk_ledtrig_disk_init(struct gendisk *const gd)
static inline void blk_ledtrig_init(void) {}
static inline void blk_ledtrig_disk_init(const struct gendisk *gd) {}

+// Real function (declared in include/linux/blk-ledtrig.h) returns a bool.
+// This is only here for del_gendisk() (in genhd.c), which doesn't check
+// the return value.
+static inline void blk_ledtrig_clear(const struct gendisk *gd) {}
+
#endif // CONFIG_BLK_LED_TRIGGERS

#endif // _BLOCK_BLK_LEDTRIG_H
diff --git a/block/genhd.c b/block/genhd.c
index 420325447c5d..fb1617f21d79 100644
--- a/block/genhd.c
+++ b/block/genhd.c
@@ -24,6 +24,7 @@
#include <linux/log2.h>
#include <linux/pm_runtime.h>
#include <linux/badblocks.h>
+#include <linux/blk-ledtrig.h>

#include "blk.h"
#include "blk-ledtrig.h"
@@ -583,6 +584,7 @@ void del_gendisk(struct gendisk *disk)
if (WARN_ON_ONCE(!disk->queue))
return;

+ blk_ledtrig_clear(disk);
blk_integrity_del(disk);
disk_del_events(disk);

diff --git a/include/linux/blk-ledtrig.h b/include/linux/blk-ledtrig.h
index 6f73635f65ec..4ab4658df280 100644
--- a/include/linux/blk-ledtrig.h
+++ b/include/linux/blk-ledtrig.h
@@ -11,8 +11,13 @@

#ifdef CONFIG_BLK_LED_TRIGGERS

+#include <linux/genhd.h>
+#include <linux/types.h>
+
int blk_ledtrig_create(const char *name);
int blk_ledtrig_delete(const char *name);
+int blk_ledtrig_set(struct gendisk *const gd, const char *const name);
+bool blk_ledtrig_clear(struct gendisk *const gd);

#endif // CONFIG_BLK_LED_TRIGGERS

--
2.31.1