* 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