Create a symlink from /sys/class/leds/<LED>/block_devices to each block device that is associated with that LED Ensure device LED is cleared when device is removed Signed-off-by: Ian Pilcher <arequipeno@xxxxxxxxx> --- block/blk-ledtrig.c | 93 +++++++++++++++++++++++++++++++++++++++++++++ block/blk-ledtrig.h | 3 ++ block/genhd.c | 1 + 3 files changed, 97 insertions(+) diff --git a/block/blk-ledtrig.c b/block/blk-ledtrig.c index c5ad57ed9c3b..280fa9edc2dd 100644 --- a/block/blk-ledtrig.c +++ b/block/blk-ledtrig.c @@ -6,9 +6,13 @@ * Copyright 2021 Ian Pilcher <arequipeno@xxxxxxxxx> */ +#include <linux/genhd.h> #include <linux/leds.h> #include <linux/mutex.h> +#include "blk-ledtrig.h" + + /* * * Trigger mutex and LED list @@ -46,3 +50,92 @@ static struct blk_ledtrig_led *blk_ledtrig_find(const char *const led_name, return NULL; } + + +/* + * + * Clear a block device's LED + * + */ + +// Also called from blk_ledtrig_dev_set() +static void blk_ledtrig_dev_cleanup(struct gendisk *const disk, + struct blk_ledtrig_led *const old_led) +{ + sysfs_remove_link(old_led->dir, disk->disk_name); + list_del(&disk->led_dev_list_node); +} + +// Also called from blk_ledtrig_deactivate() +static void blk_ledtrig_dev_clear_locked(struct gendisk *const disk, + struct blk_ledtrig_led *const old_led) +{ + RCU_INIT_POINTER(disk->led, NULL); + if (old_led != NULL) + blk_ledtrig_dev_cleanup(disk, old_led); +} + +// Also called from genhd.c:del_gendisk() +void blk_ledtrig_dev_clear(struct gendisk *const disk) +{ + struct blk_ledtrig_led *old_led; + + mutex_lock(&blk_ledtrig_mutex); + old_led = rcu_dereference_protected(disk->led, + lockdep_is_held(&blk_ledtrig_mutex)); + blk_ledtrig_dev_clear_locked(disk, old_led); + mutex_unlock(&blk_ledtrig_mutex); +} + + +/* + * + * Set a block device's LED + * + */ + +static int blk_ledtrig_dev_set(struct gendisk *const disk, + const char *const led_name, + const size_t name_len) +{ + struct blk_ledtrig_led *new_led, *old_led; + int ret; + + ret = mutex_lock_interruptible(&blk_ledtrig_mutex); + if (ret != 0) + goto led_set_exit_return; + + new_led = blk_ledtrig_find(led_name, name_len); + if (new_led == NULL) { + pr_info("no LED named %.*s associated with blkdev trigger\n", + (int)name_len, led_name); + ret = -ENODEV; + goto led_set_exit_unlock; + } + + old_led = rcu_dereference_protected(disk->led, + lockdep_is_held(&blk_ledtrig_mutex)); + + if (old_led == new_led) { + ret = 0; + goto led_set_exit_unlock; + } + + ret = sysfs_create_link(new_led->dir, &disk_to_dev(disk)->kobj, + disk->disk_name); + if (ret != 0) + goto led_set_exit_unlock; + + if (old_led != NULL) + blk_ledtrig_dev_cleanup(disk, old_led); + + rcu_assign_pointer(disk->led, new_led); + list_add(&disk->led_dev_list_node, &new_led->dev_list); + + ret = 0; + +led_set_exit_unlock: + mutex_unlock(&blk_ledtrig_mutex); +led_set_exit_return: + return ret; +} diff --git a/block/blk-ledtrig.h b/block/blk-ledtrig.h index 95a79d2fe447..66a1302a4174 100644 --- a/block/blk-ledtrig.h +++ b/block/blk-ledtrig.h @@ -16,9 +16,12 @@ static inline void blk_ledtrig_disk_init(struct gendisk *const disk) RCU_INIT_POINTER(disk->led, NULL); } +void blk_ledtrig_dev_clear(struct gendisk *const disk); + #else // CONFIG_BLK_LED_TRIGGERS static inline void blk_ledtrig_disk_init(const struct gendisk *disk) {} +static inline void blk_ledtrig_dev_clear(const struct gendisk *disk) {} #endif // CONFIG_BLK_LED_TRIGGERS diff --git a/block/genhd.c b/block/genhd.c index b168172e664b..9fa734aeab0f 100644 --- a/block/genhd.c +++ b/block/genhd.c @@ -583,6 +583,7 @@ void del_gendisk(struct gendisk *disk) if (WARN_ON_ONCE(!disk->queue)) return; + blk_ledtrig_dev_clear(disk); blk_integrity_del(disk); disk_del_events(disk); -- 2.31.1