diff --git a/Documentation/admin-guide/blockdev/zoned_loop.rst b/Documentation/admin-guide/blockdev/zoned_loop.rst index 64277494fb36a..ec39f141af8d6 100644 --- a/Documentation/admin-guide/blockdev/zoned_loop.rst +++ b/Documentation/admin-guide/blockdev/zoned_loop.rst @@ -61,7 +61,7 @@ The options available for the add command can be listed by reading the /dev/zloop-control device:: $ cat /dev/zloop-control - add id=%d,capacity_mb=%u,zone_size_mb=%u,zone_capacity_mb=%u,conv_zones=%u,max_open_zones=%u,base_dir=%s,nr_queues=%u,queue_depth=%u,buffered_io,zone_append=%u,ordered_zone_append,discard_write_cache + add id=%d,capacity_mb=%u,zone_size_mb=%u,zone_capacity_mb=%u,conv_zones=%u,max_open_zones=%u,base_dir=%s,nr_queues=%u,queue_depth=%u,buffered_io,zone_append=%u,ordered_zone_append,discard_write_cache,stor_elements=%u remove id=%d In more details, the options that can be used with the "add" command are as @@ -113,6 +113,12 @@ discard_write_cache Discard all data that was not explicitly persisted using a each zone file to the size recorded during the last flush operation. This simulates power fail events where uncommitted data is lost. +stor_elements Control storage element emulation. The default value is 0, + indicating no emulation. A value of 1 indicates that all + access storage elements (equivalent to read+write head of + a disk) are emulated. A value of 2 enables fractional + access storage element (equivalent to pairs of read and + write heads of a disk) emulation . =================== ========================================================= 3) Deleting a Zoned Device diff --git a/block/blk-zoned.c b/block/blk-zoned.c index 19268afb8752e..8736e80b4080d 100644 --- a/block/blk-zoned.c +++ b/block/blk-zoned.c @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include @@ -292,6 +294,22 @@ static bool disk_zone_is_seq(struct gendisk *disk, sector_t sector) return !blk_zstate_is_conv(zs); } +/** + * disk_zone_is_offline - check if a sector belongs to an offline zone + * @disk: gendisk to check + * @sector: sector number + * + * Check if @sector on @disk is contained in an offline zone. + */ +bool disk_zone_is_offline(struct gendisk *disk, sector_t sector) +{ + if (!blk_queue_is_zoned(disk->queue)) + return false; + + return disk_zone_get_cond(disk, sector) == BLK_ZONE_COND_OFFLINE; +} +EXPORT_SYMBOL_GPL(disk_zone_is_offline); + /** * bdev_zone_is_seq - check if a sector belongs to a sequential write zone * @bdev: block device to check @@ -2526,20 +2544,7 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx, return ret; } -/** - * blk_revalidate_disk_zones - (re)allocate and initialize zone write plugs - * @disk: Target disk - * - * Helper function for low-level device drivers to check, (re) allocate and - * initialize resources used for managing zoned disks. This function should - * normally be called by blk-mq based drivers when a zoned gendisk is probed - * and when the zone configuration of the gendisk changes (e.g. after a format). - * Before calling this function, the device driver must already have set the - * device zone size (chunk_sector limit) and the max zone append limit. - * BIO based drivers can also use this function as long as the device queue - * can be safely frozen. - */ -int blk_revalidate_disk_zones(struct gendisk *disk) +static int disk_revalidate_zones(struct gendisk *disk) { struct blk_revalidate_zone_args args = { }; struct blk_report_zones_args rep_args = { @@ -2550,19 +2555,10 @@ int blk_revalidate_disk_zones(struct gendisk *disk) int retries = 2; int ret; - if (WARN_ON_ONCE(!blk_queue_is_zoned(disk->queue))) - return -EIO; - - /* - * Serialize calls to this function so that we can safely look at and - * eventually change the disk zone information. - */ - mutex_lock(&disk->zone_revalidate_mutex); - again: ret = disk_revalidate_capacity(disk, &args); if (ret) - goto unlock; + return ret; /* * Allocate zone resources if they are needed and we have not done @@ -2575,14 +2571,14 @@ int blk_revalidate_disk_zones(struct gendisk *disk) ret = disk_alloc_zone_resources(disk, args.capacity); if (ret) { memalloc_noio_restore(noio_flag); - goto unlock; + return ret; } } ret = disk_init_revalidate_args(disk, &args); if (ret) { memalloc_noio_restore(noio_flag); - goto unlock; + return ret; } ret = disk->fops->report_zones(disk, 0, UINT_MAX, &rep_args); @@ -2599,8 +2595,6 @@ int blk_revalidate_disk_zones(struct gendisk *disk) if (ret) goto free_args; - mutex_unlock(&disk->zone_revalidate_mutex); - return 0; free_args: @@ -2616,7 +2610,36 @@ int blk_revalidate_disk_zones(struct gendisk *disk) } pr_warn("%s: failed to revalidate zones\n", disk->disk_name); -unlock: + + return ret; +} + +/** + * blk_revalidate_disk_zones - (re)allocate and initialize zone write plugs + * @disk: Target disk + * + * Helper function for low-level device drivers to check, (re) allocate and + * initialize resources used for managing zoned disks. This function should + * normally be called by blk-mq based drivers when a zoned gendisk is probed + * and when the zone configuration of the gendisk changes (e.g. after a format). + * Before calling this function, the device driver must already have set the + * device zone size (chunk_sector limit) and the max zone append limit. + * BIO based drivers can also use this function as long as the device queue + * can be safely frozen. + */ +int blk_revalidate_disk_zones(struct gendisk *disk) +{ + int ret; + + if (WARN_ON_ONCE(!blk_queue_is_zoned(disk->queue))) + return -EIO; + + /* + * Serialize calls to this function so that we can safely look at and + * eventually change the disk zone information. + */ + mutex_lock(&disk->zone_revalidate_mutex); + ret = disk_revalidate_zones(disk); mutex_unlock(&disk->zone_revalidate_mutex); return ret; @@ -2700,5 +2723,438 @@ int queue_zone_wplugs_show(void *data, struct seq_file *m) return 0; } - #endif + +static int disk_se_mgmt_in_progress(struct gendisk *disk, + struct blk_se *elements, + bool *in_progress) +{ + unsigned int i, nr_se = disk->nr_storage_elements; + int ret; + + /* + * Check if we have storage elements being removed or restored. + */ + ret = disk->fops->se_ops->report_elements(disk, elements, &nr_se); + if (ret) { + pr_err("Failed to get storage elements\n"); + return ret; + } + + for (i = 0; i < nr_se; i++) { + switch (elements[i].status) { + case BLK_SE_STS_REMOVE_IN_PROGRESS: + case BLK_SE_STS_RESTORE_IN_PROGRESS: + *in_progress = true; + return 0; + default: + break; + } + } + + *in_progress = false; + + return 0; +} + +static int disk_check_remove_error(struct gendisk *disk, + struct blk_se *elements, + unsigned int element_id) +{ + unsigned int i; + + for (i = 0; i < disk->nr_storage_elements; i++) { + if (elements[i].id == element_id + && elements[i].status == BLK_SE_STS_REMOVE_ERROR) + return -EIO; + } + + return 0; +} + +static int disk_wait_for_se_mgmt_completion(struct gendisk *disk, + struct blk_se *elements, + unsigned int element_id) +{ + bool in_progress; + int ret; + + /* + * Poll the storage elements heath state to detect the end of a + * removal or restoration operation. Since we do not know how long the + * operation may take, we cannot have a timeout on this loop. So check + * for fatal signals so that the user can terminate this retry loop if + * the device misbehaves. + */ + while (1) { + ret = disk_se_mgmt_in_progress(disk, elements, &in_progress); + if (ret || !in_progress) + break; + + if (fatal_signal_pending(current)) { + ret = -EINTR; + break; + } + + /* Not done yet: wait and retry. */ + msleep(1000); + } + + /* Check for remove errors. */ + if (!ret && !in_progress && element_id) + ret = disk_check_remove_error(disk, elements, element_id); + + return ret; +} + +/** + * bdev_report_storage_elements - report the storage elements of a zoned + * block device + * @bdev: the zoned block device to check + * @elements: array of storage element descriptors to fill + * @nr_elements: maximum size of @elements + * + * Fill at most @nr_elements storage element descriptors in the array + * @elements. The number of storage elements filled in the array is returned + * using @nr_elements. If @elements is NULL, only @nr_elements is returned to + * indicate the maximum number of storage elements of @bdev. + * + * Returns 0 on success and a negative error code on failure. + */ +int bdev_report_storage_elements(struct block_device *bdev, + struct blk_se *elements, + unsigned int *nr_elements) +{ + struct gendisk *disk = bdev->bd_disk; + + if (!blk_queue_is_zoned(disk->queue) || !disk->nr_storage_elements) + return -EOPNOTSUPP; + + if (!nr_elements) + return -EINVAL; + + if (!elements) { + *nr_elements = disk->nr_storage_elements; + return 0; + } + + *nr_elements = min(*nr_elements, disk->nr_storage_elements); + if (!*nr_elements) + return -EINVAL; + + return disk->fops->se_ops->report_elements(disk, elements, + nr_elements); +} +EXPORT_SYMBOL_GPL(bdev_report_storage_elements); + +static int blkdev_get_nr_storage_elements_ioctl(struct block_device *bdev, + void __user *argp) +{ + struct gendisk *disk = bdev->bd_disk; + + if (put_user(disk->nr_storage_elements, (unsigned int __user *)argp)) + return -EFAULT; + + return 0; +} + +static int blkdev_report_storage_elements_ioctl(struct block_device *bdev, + void __user *argp) +{ + struct gendisk *disk = bdev->bd_disk; + struct blk_se_report *rep; + int ret; + + if (!argp) + return -EINVAL; + + rep = kzalloc_flex(*rep, elements, disk->nr_storage_elements); + if (!rep) + return -ENOMEM; + + if (copy_from_user(rep, argp, sizeof(struct blk_se_report))) { + ret = -EFAULT; + goto free_rep; + } + + if (rep->nr_elements < disk->nr_storage_elements) { + ret = -EINVAL; + goto free_rep; + } + + rep->nr_reported_elements = disk->nr_storage_elements; + ret = bdev_report_storage_elements(bdev, rep->elements, + &rep->nr_reported_elements); + if (ret) + goto free_rep; + + if (copy_to_user(argp, rep, struct_size_t(struct blk_se_report, + elements, rep->nr_reported_elements))) + ret = -EFAULT; + +free_rep: + kfree(rep); + return ret; +} + +/** + * bdev_remove_storage_element - Remove (depopulate) a storage element of a + * zoned block device + * @bdev: block device to operate on + * @element_id: ID of the storage element to remove + * + * Remove (depopulate) the storage element identified by @element_id from the + * block device @bdev. The caller is responsible for taking care of any + * necessary device write cache flush and invalidation of cached data for the + * zones that will be offlined. + * + * Returns 0 on success and a negative error code on failure. + */ +int bdev_remove_storage_element(struct block_device *bdev, + unsigned int element_id) +{ + struct gendisk *disk = bdev->bd_disk; + unsigned int memflags, noio_flag; + struct blk_se *elements; + int ret, err; + + if (!blk_queue_is_zoned(disk->queue) || !disk->nr_storage_elements) + return -EOPNOTSUPP; + + /* Zero is not a valid storage element ID. */ + if (!element_id) + return -EINVAL; + + noio_flag = memalloc_noio_save(); + elements = kzalloc_objs(struct blk_se, disk->nr_storage_elements); + memalloc_noio_restore(noio_flag); + if (!elements) + return -ENOMEM; + + /* + * Serialize against concurrent revalidation and concurrent storage + * element management operations so that we can see consistent zone + * condition changes. + */ + mutex_lock(&disk->zone_revalidate_mutex); + + /* + * Freeze and unfreeze the queue to flush any outstanding command. + * The caller is responsible for not queuing up more I/Os by higher + * level means. + */ + memflags = blk_mq_freeze_queue(disk->queue); + blk_mq_unfreeze_queue(disk->queue, memflags); + + /* Initiate the storage element removal and wait for its completion. */ + ret = disk->fops->se_ops->remove_element(disk, element_id); + if (!ret) + ret = disk_wait_for_se_mgmt_completion(disk, elements, + element_id); + + /* + * Always revalidate zones, even in the case of an error as we do not + * know what condition the drive zones are in. + */ + err = disk_revalidate_zones(disk); + if (err && !ret) + ret = err; + + mutex_unlock(&disk->zone_revalidate_mutex); + + kfree(elements); + + return ret; +} +EXPORT_SYMBOL_GPL(bdev_remove_storage_element); + +static int blkdev_flush_and_truncate(struct block_device *bdev, + blk_mode_t mode) +{ + int ret; + + inode_lock(bdev->bd_mapping->host); + filemap_invalidate_lock(bdev->bd_mapping); + ret = blkdev_issue_flush(bdev->bd_disk->part0); + if (!ret) + ret = truncate_bdev_range(bdev, mode, 0, + bdev_nr_bytes(bdev) - 1); + filemap_invalidate_unlock(bdev->bd_mapping); + inode_unlock(bdev->bd_mapping->host); + + return ret; +} + +static int blkdev_remove_storage_element_ioctl(struct block_device *bdev, + blk_mode_t mode, void __user *argp) +{ + unsigned int element_id; + int ret; + + if (!(mode & BLK_OPEN_WRITE)) + return -EBADF; + if (bdev_read_only(bdev)) + return -EPERM; + + if (get_user(element_id, (unsigned int __user *)argp)) + return -EFAULT; + + /* + * Flush the device volatile write cache and invalidate all cached data + * so that reads do not return old data for zones that went offline. + * Since a user can only write to zoned devices using direct IOs, we + * should never have any dirty page invalidated and so no data loss as + * long as the device cache flush succeeds, that is, as long as the + * flush does not use degraded storage elements. + */ + ret = blkdev_flush_and_truncate(bdev, mode); + if (ret) + return ret; + + return bdev_remove_storage_element(bdev, element_id); +} + +/** + * bdev_restore_storage_elements - Restore all depopulated storage elements of + * a zoned block device + * @bdev: block device to operate on + * + * Restore all storage elements of @bdev that have been depopulated. Not all + * elements may be restored by this operation. + * + * Returns 0 on success and a negative error code on failure. + */ +int bdev_restore_storage_elements(struct block_device *bdev) +{ + struct gendisk *disk = bdev->bd_disk; + unsigned int memflags, noio_flag; + struct blk_se *elements; + int ret, err; + + if (!blk_queue_is_zoned(disk->queue) || !disk->nr_storage_elements) + return -EOPNOTSUPP; + + noio_flag = memalloc_noio_save(); + elements = kzalloc_objs(struct blk_se, disk->nr_storage_elements); + memalloc_noio_restore(noio_flag); + if (!elements) + return -ENOMEM; + + /* + * Serialize against concurrent revalidation and concurrent storage + * element management operations so that we can see consistent zone + * condition changes. + */ + mutex_lock(&disk->zone_revalidate_mutex); + + /* + * Freeze and unfreeze the queue to flush any outstanding commands. + * The caller is responsible for not queuing up more I/O by higher + * level means. + */ + memflags = blk_mq_freeze_queue(disk->queue); + blk_mq_unfreeze_queue(disk->queue, memflags); + + /* + * Initiate the storage elements restoration and wait for its + * completion. + */ + ret = disk->fops->se_ops->restore_elements(disk); + if (!ret) + ret = disk_wait_for_se_mgmt_completion(disk, elements, 0); + + /* + * Always revalidate zones, even in the case of an error as we do not + * know what condition the drive zones are in. + */ + err = disk_revalidate_zones(disk); + if (err && !ret) + ret = err; + + mutex_unlock(&disk->zone_revalidate_mutex); + + kfree(elements); + + return ret; +} +EXPORT_SYMBOL_GPL(bdev_restore_storage_elements); + +static int blkdev_restore_storage_elements_ioctl(struct block_device *bdev, + blk_mode_t mode) +{ + int ret; + + if (!(mode & BLK_OPEN_WRITE)) + return -EBADF; + if (bdev_read_only(bdev)) + return -EPERM; + + /* + * Storage element restoration is a destructive operation that will + * reset all zones. So fflush the device volatile write cache and + * invalidate all cached data that we may have. + */ + ret = blkdev_flush_and_truncate(bdev, mode); + if (ret) + return ret; + + return bdev_restore_storage_elements(bdev); +} + +int disk_init_storage_element_mgmt(struct gendisk *disk) +{ + int ret; + + /* + * Storage element operations are for zoned disks only and + * optional... + */ + if (!blk_queue_is_zoned(disk->queue) || !disk->fops->se_ops) + return 0; + + /* ...but if supported, all operations must be defined. */ + if (!disk->fops->se_ops->report_elements || + !disk->fops->se_ops->remove_element || + !disk->fops->se_ops->restore_elements) { + pr_err("%s: invalid storage element operations\n", + disk->disk_name); + return -EINVAL; + } + + /* + * Get the number of storage elements of the disk. If this fails, + * do not report an error as the failure may be because the zoned disk + * does not support storage element management. + */ + ret = disk->fops->se_ops->report_elements(disk, NULL, + &disk->nr_storage_elements); + if (ret) + disk->nr_storage_elements = 0; + + return 0; +} + +int blkdev_zone_storage_elements_ioctl(struct block_device *bdev, + blk_mode_t mode, unsigned int cmd, + void __user *argp) +{ + struct gendisk *disk = bdev->bd_disk; + + if (!disk->nr_storage_elements) + return -ENOTTY; + + switch (cmd) { + case BLKGETNRSTORELEMS: + return blkdev_get_nr_storage_elements_ioctl(bdev, argp); + case BLKREPORTSTORELEMS: + return blkdev_report_storage_elements_ioctl(bdev, argp); + case BLKREMOVESTORELEM: + return blkdev_remove_storage_element_ioctl(bdev, mode, argp); + case BLKRESTORESTORELEMS: + return blkdev_restore_storage_elements_ioctl(bdev, mode); + default: + break; + } + + return -ENOTTY; +} diff --git a/block/blk.h b/block/blk.h index 2cc03aa54c532..bbf523c9579cf 100644 --- a/block/blk.h +++ b/block/blk.h @@ -527,6 +527,7 @@ static inline void ioc_clear_queue(struct request_queue *q) #endif /* CONFIG_BLK_ICQ */ #ifdef CONFIG_BLK_DEV_ZONED +int disk_init_storage_element_mgmt(struct gendisk *disk); void disk_init_zone_resources(struct gendisk *disk); void disk_release_zone_resources(struct gendisk *disk); static inline bool bio_zone_write_plugging(struct bio *bio) @@ -578,7 +579,14 @@ int blkdev_report_zones_ioctl(struct block_device *bdev, unsigned int cmd, int blkdev_zone_mgmt_ioctl(struct block_device *bdev, blk_mode_t mode, unsigned int cmd, unsigned long arg); bool bdev_zone_mgmt_allowed(struct block_device *bdev, sector_t sector); +int blkdev_zone_storage_elements_ioctl(struct block_device *bdev, + blk_mode_t mode, unsigned int cmd, + void __user *argp); #else /* CONFIG_BLK_DEV_ZONED */ +static inline int disk_init_storage_element_mgmt(struct gendisk *disk) +{ + return 0; +} static inline void disk_init_zone_resources(struct gendisk *disk) { } @@ -625,6 +633,12 @@ static inline bool bdev_zone_mgmt_allowed(struct block_device *bdev, { return false; } +static inline int blkdev_zone_storage_elements_ioctl(struct block_device *bdev, + blk_mode_t mode, unsigned int cmd, + void __user *argp) +{ + return -ENOTTY; +} #endif /* CONFIG_BLK_DEV_ZONED */ struct block_device *bdev_alloc(struct gendisk *disk, u8 partno); diff --git a/block/genhd.c b/block/genhd.c index 10ca8b4d6eea7..e2cef791ac91a 100644 --- a/block/genhd.c +++ b/block/genhd.c @@ -435,6 +435,10 @@ static int __add_disk(struct device *parent, struct gendisk *disk, if (WARN_ON_ONCE(bdev_nr_sectors(disk->part0) > BLK_DEV_MAX_SECTORS)) return -EINVAL; + ret = disk_init_storage_element_mgmt(disk); + if (ret) + return ret; + if (queue_is_mq(disk->queue)) { /* * ->submit_bio and ->poll_bio are bypassed for blk-mq drivers. diff --git a/block/ioctl.c b/block/ioctl.c index 64b4e6c0f6965..ccf806c2e37d1 100644 --- a/block/ioctl.c +++ b/block/ioctl.c @@ -679,6 +679,11 @@ static int blkdev_common_ioctl(struct block_device *bdev, blk_mode_t mode, return put_uint(argp, bdev_zone_sectors(bdev)); case BLKGETNRZONES: return put_uint(argp, bdev_nr_zones(bdev)); + case BLKGETNRSTORELEMS: + case BLKREPORTSTORELEMS: + case BLKREMOVESTORELEM: + case BLKRESTORESTORELEMS: + return blkdev_zone_storage_elements_ioctl(bdev, mode, cmd, argp); case BLKROGET: return put_int(argp, bdev_read_only(bdev) != 0); case BLKSSZGET: /* get block device logical block size */ diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c index 394dc2408ed73..26d8335508a16 100644 --- a/drivers/block/zloop.c +++ b/drivers/block/zloop.c @@ -37,6 +37,8 @@ enum { ZLOOP_OPT_ORDERED_ZONE_APPEND = (1 << 10), ZLOOP_OPT_DISCARD_WRITE_CACHE = (1 << 11), ZLOOP_OPT_MAX_OPEN_ZONES = (1 << 12), + ZLOOP_OPT_STOR_ELEMENTS = (1 << 13), + ZLOOP_OPT_ELEMENT_ID = (1 << 14), }; static const match_table_t zloop_opt_tokens = { @@ -51,11 +53,23 @@ static const match_table_t zloop_opt_tokens = { { ZLOOP_OPT_BUFFERED_IO, "buffered_io" }, { ZLOOP_OPT_ZONE_APPEND, "zone_append=%u" }, { ZLOOP_OPT_ORDERED_ZONE_APPEND, "ordered_zone_append" }, - { ZLOOP_OPT_DISCARD_WRITE_CACHE, "discard_write_cache" }, + { ZLOOP_OPT_DISCARD_WRITE_CACHE, "discard_write_cache" }, { ZLOOP_OPT_MAX_OPEN_ZONES, "max_open_zones=%u" }, + { ZLOOP_OPT_STOR_ELEMENTS, "stor_elements=%u" }, + { ZLOOP_OPT_ELEMENT_ID, "element_id=%u" }, { ZLOOP_OPT_ERR, NULL } }; +/* Storage elements emulation types. */ +enum zloop_stor_elements { + /* No emulation. */ + ZLOOP_STOR_ELEMENTS_NONE, + /* Emulate read+write storage elements. */ + ZLOOP_STOR_ELEMENTS_RDWR, + /* Emulate pairs of associated read and write storage elements. */ + ZLOOP_STOR_ELEMENTS_PAIRS, +}; + /* Default values for the "add" operation. */ #define ZLOOP_DEF_ID -1 #define ZLOOP_DEF_ZONE_SIZE ((256ULL * SZ_1M) >> SECTOR_SHIFT) @@ -68,6 +82,8 @@ static const match_table_t zloop_opt_tokens = { #define ZLOOP_DEF_BUFFERED_IO false #define ZLOOP_DEF_ZONE_APPEND true #define ZLOOP_DEF_ORDERED_ZONE_APPEND false +#define ZLOOP_DEF_STOR_ELEMENTS ZLOOP_STOR_ELEMENTS_NONE +#define ZLOOP_DEF_ELEMENT_ID 0 /* Arbitrary limit on the zone size (16GB). */ #define ZLOOP_MAX_ZONE_SIZE_MB 16384 @@ -87,6 +103,8 @@ struct zloop_options { bool zone_append; bool ordered_zone_append; bool discard_write_cache; + enum zloop_stor_elements stor_elements; + unsigned int element_id; }; /* @@ -117,6 +135,8 @@ struct zloop_zone { enum blk_zone_cond cond; sector_t start; sector_t wp; + unsigned int wr_se_id; + unsigned int rd_se_id; gfp_t old_gfp_mask; }; @@ -133,6 +153,7 @@ struct zloop_device { bool zone_append; bool ordered_zone_append; bool discard_write_cache; + enum zloop_stor_elements stor_elements; const char *base_dir; struct file *data_dir; @@ -150,6 +171,17 @@ struct zloop_device { struct list_head open_zones_lru_list; unsigned int nr_open_zones; + /* For storage elements emulation. */ + struct mutex stor_elements_lock; + unsigned int nr_elements; + unsigned int max_nr_removed_elements; + unsigned int nr_removed_elements; + struct delayed_work remove_element_work; + unsigned int remove_element_id; + struct delayed_work restore_elements_work; + bool restore_in_progress; + struct blk_se *elements; + struct zloop_zone zones[] __counted_by(nr_zones); }; @@ -289,22 +321,43 @@ static bool zloop_do_open_zone(struct zloop_device *zlo, } } -static void zloop_mark_full(struct zloop_device *zlo, struct zloop_zone *zone) +static void zloop_set_zone_cond(struct zloop_device *zlo, + struct zloop_zone *zone, + enum blk_zone_cond cond) { lockdep_assert_held(&zone->wp_lock); zloop_lru_remove_open_zone(zlo, zone); - zone->cond = BLK_ZONE_COND_FULL; - zone->wp = ULLONG_MAX; + zone->cond = cond; + if (cond == BLK_ZONE_COND_EMPTY) + zone->wp = zone->start; + else + zone->wp = ULLONG_MAX; } -static void zloop_mark_empty(struct zloop_device *zlo, struct zloop_zone *zone) +static inline void zloop_set_zone_full(struct zloop_device *zlo, + struct zloop_zone *zone) { - lockdep_assert_held(&zone->wp_lock); + zloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_FULL); +} - zloop_lru_remove_open_zone(zlo, zone); - zone->cond = BLK_ZONE_COND_EMPTY; - zone->wp = zone->start; +static inline void zloop_set_zone_empty(struct zloop_device *zlo, + struct zloop_zone *zone) +{ + zloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_EMPTY); +} + +static bool zloop_zone_is_offline_or_readonly(struct zloop_device *zlo, + struct zloop_zone *zone) +{ + bool ret; + + spin_lock(&zone->wp_lock); + ret = zone->cond == BLK_ZONE_COND_OFFLINE || + zone->cond == BLK_ZONE_COND_READONLY; + spin_unlock(&zone->wp_lock); + + return ret; } static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no) @@ -316,6 +369,13 @@ static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no) lockdep_assert_held(&zone->lock); + /* + * If the zone was already changed to offline or read-only, we have + * nothing to do. + */ + if (zloop_zone_is_offline_or_readonly(zlo, zone)) + return 0; + ret = vfs_getattr(&zone->file->f_path, &stat, STATX_SIZE, 0); if (ret < 0) { pr_err("Failed to get zone %u file stat (err=%d)\n", @@ -339,9 +399,9 @@ static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no) spin_lock(&zone->wp_lock); if (!file_sectors) { - zloop_mark_empty(zlo, zone); + zloop_set_zone_empty(zlo, zone); } else if (file_sectors == zlo->zone_capacity) { - zloop_mark_full(zlo, zone); + zloop_set_zone_full(zlo, zone); } else { if (zone->cond != BLK_ZONE_COND_IMP_OPEN && zone->cond != BLK_ZONE_COND_EXP_OPEN) @@ -363,6 +423,11 @@ static int zloop_open_zone(struct zloop_device *zlo, unsigned int zone_no) mutex_lock(&zone->lock); + if (zloop_zone_is_offline_or_readonly(zlo, zone)) { + ret = -EIO; + goto unlock; + } + if (test_and_clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags)) { ret = zloop_update_seq_zone(zlo, zone_no); if (ret) @@ -388,6 +453,11 @@ static int zloop_close_zone(struct zloop_device *zlo, unsigned int zone_no) mutex_lock(&zone->lock); + if (zloop_zone_is_offline_or_readonly(zlo, zone)) { + ret = -EIO; + goto unlock; + } + if (test_and_clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags)) { ret = zloop_update_seq_zone(zlo, zone_no); if (ret) @@ -420,7 +490,22 @@ static int zloop_close_zone(struct zloop_device *zlo, unsigned int zone_no) return ret; } -static int zloop_reset_zone(struct zloop_device *zlo, unsigned int zone_no) +static int zloop_do_reset_zone(struct zloop_device *zlo, + struct zloop_zone *zone) +{ + if (vfs_truncate(&zone->file->f_path, 0)) + return -EIO; + + spin_lock(&zone->wp_lock); + zloop_set_zone_empty(zlo, zone); + clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags); + spin_unlock(&zone->wp_lock); + + return 0; +} + +static int zloop_reset_zone(struct zloop_device *zlo, unsigned int zone_no, + bool all_zones) { struct zloop_zone *zone = &zlo->zones[zone_no]; int ret = 0; @@ -430,20 +515,19 @@ static int zloop_reset_zone(struct zloop_device *zlo, unsigned int zone_no) mutex_lock(&zone->lock); + if (zloop_zone_is_offline_or_readonly(zlo, zone)) { + if (!all_zones) + ret = -EIO; + goto unlock; + } + if (!test_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags) && zone->cond == BLK_ZONE_COND_EMPTY) goto unlock; - if (vfs_truncate(&zone->file->f_path, 0)) { + ret = zloop_do_reset_zone(zlo, zone); + if (ret) set_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags); - ret = -EIO; - goto unlock; - } - - spin_lock(&zone->wp_lock); - zloop_mark_empty(zlo, zone); - clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags); - spin_unlock(&zone->wp_lock); unlock: mutex_unlock(&zone->lock); @@ -457,7 +541,7 @@ static int zloop_reset_all_zones(struct zloop_device *zlo) int ret; for (i = zlo->nr_conv_zones; i < zlo->nr_zones; i++) { - ret = zloop_reset_zone(zlo, i); + ret = zloop_reset_zone(zlo, i, true); if (ret) return ret; } @@ -475,6 +559,11 @@ static int zloop_finish_zone(struct zloop_device *zlo, unsigned int zone_no) mutex_lock(&zone->lock); + if (zloop_zone_is_offline_or_readonly(zlo, zone)) { + ret = -EIO; + goto unlock; + } + if (!test_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags) && zone->cond == BLK_ZONE_COND_FULL) goto unlock; @@ -487,7 +576,7 @@ static int zloop_finish_zone(struct zloop_device *zlo, unsigned int zone_no) } spin_lock(&zone->wp_lock); - zloop_mark_full(zlo, zone); + zloop_set_zone_full(zlo, zone); clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags); spin_unlock(&zone->wp_lock); @@ -624,7 +713,7 @@ static int zloop_seq_write_prep(struct zloop_cmd *cmd) if (!is_append || !zlo->ordered_zone_append) { zone->wp += nr_sectors; if (zone->wp == zone_end) - zloop_mark_full(zlo, zone); + zloop_set_zone_full(zlo, zone); } out_unlock: spin_unlock(&zone->wp_lock); @@ -766,7 +855,7 @@ static void zloop_handle_cmd(struct zloop_cmd *cmd) cmd->ret = zloop_flush(zlo); break; case REQ_OP_ZONE_RESET: - cmd->ret = zloop_reset_zone(zlo, rq_zone_no(rq)); + cmd->ret = zloop_reset_zone(zlo, rq_zone_no(rq), false); break; case REQ_OP_ZONE_RESET_ALL: cmd->ret = zloop_reset_all_zones(zlo); @@ -875,30 +964,72 @@ static void zloop_complete_rq(struct request *rq) blk_mq_end_request(rq, sts); } -static bool zloop_set_zone_append_sector(struct request *rq) +static bool zloop_set_zone_append_sector(struct zloop_device *zlo, + struct zloop_zone *zone, + struct request *rq) { - struct zloop_device *zlo = rq->q->queuedata; - unsigned int zone_no = rq_zone_no(rq); - struct zloop_zone *zone = &zlo->zones[zone_no]; sector_t zone_end = zone->start + zlo->zone_capacity; sector_t nr_sectors = blk_rq_sectors(rq); - spin_lock(&zone->wp_lock); - if (zone->cond == BLK_ZONE_COND_FULL || - zone->wp + nr_sectors > zone_end) { - spin_unlock(&zone->wp_lock); + zone->wp + nr_sectors > zone_end) return false; - } rq->__sector = zone->wp; zone->wp += blk_rq_sectors(rq); if (zone->wp >= zone_end) - zloop_mark_full(zlo, zone); + zloop_set_zone_full(zlo, zone); + + return true; +} + + +static bool zloop_prep_rq(struct zloop_device *zlo, struct request *rq) +{ + struct zloop_zone *zone = &zlo->zones[rq_zone_no(rq)]; + bool is_write = op_is_write(req_op(rq)); + bool ret = true; + spin_lock(&zone->wp_lock); + + if (zlo->nr_elements) { + struct blk_se *se; + + /* + * Check the zone condition and the health state of the storage + * element serving the zone: do not allow any operation to + * offline zones or if the storage element serving the zone is + * being removed, and allow only reads to read-only zones. + */ + if (is_write) + se = &zlo->elements[zone->wr_se_id - 1]; + else + se = &zlo->elements[zone->rd_se_id - 1]; + if (READ_ONCE(se->status) == BLK_SE_STS_DEGRADED || + READ_ONCE(se->status) == BLK_SE_STS_REMOVE_IN_PROGRESS) { + ret = false; + goto unlock; + } + + if (zone->cond == BLK_ZONE_COND_OFFLINE || + (zone->cond == BLK_ZONE_COND_READONLY && is_write)) { + ret = false; + goto unlock; + } + } + + /* + * If we need to strongly order zone append operations, set the request + * sector to the zone write pointer location now instead of when the + * command work runs. + */ + if (zlo->ordered_zone_append && req_op(rq) == REQ_OP_ZONE_APPEND) + ret = zloop_set_zone_append_sector(zlo, zone, rq); + +unlock: spin_unlock(&zone->wp_lock); - return true; + return ret; } static blk_status_t zloop_queue_rq(struct blk_mq_hw_ctx *hctx, @@ -914,13 +1045,21 @@ static blk_status_t zloop_queue_rq(struct blk_mq_hw_ctx *hctx, } /* - * If we need to strongly order zone append operations, set the request - * sector to the zone write pointer location now instead of when the - * command work runs. + * Do not allow commands while the restoration of storage elements is + * in progress. */ - if (zlo->ordered_zone_append && req_op(rq) == REQ_OP_ZONE_APPEND) { - if (!zloop_set_zone_append_sector(rq)) + if (READ_ONCE(zlo->restore_in_progress)) + return BLK_STS_IOERR; + + switch (req_op(rq)) { + case REQ_OP_READ: + case REQ_OP_WRITE: + case REQ_OP_ZONE_APPEND: + if (!zloop_prep_rq(zlo, rq)) return BLK_STS_IOERR; + break; + default: + break; } blk_mq_start_request(rq); @@ -1002,11 +1141,317 @@ static int zloop_report_zones(struct gendisk *disk, sector_t sector, return nr_zones; } +static int zloop_report_elements(struct gendisk *disk, + struct blk_se *elements, + unsigned int *nr_elements) +{ + struct zloop_device *zlo = disk->private_data; + struct blk_se *se; + unsigned int i; + + if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_NONE) + return -EOPNOTSUPP; + + mutex_lock(&zlo->stor_elements_lock); + + if (!elements) { + *nr_elements = zlo->nr_elements; + goto unlock; + } + + *nr_elements = min(*nr_elements, zlo->nr_elements); + for (i = 0, se = zlo->elements; i < *nr_elements; i++, se++) { + switch (READ_ONCE(se->status)) { + case BLK_SE_STS_REMOVED: + case BLK_SE_STS_RESTORE_ERROR: + se->restore_allowed = 1; + break; + default: + se->restore_allowed = 0; + } + memcpy(&elements[i], se, sizeof(struct blk_se)); + } + +unlock: + mutex_unlock(&zlo->stor_elements_lock); + + return 0; +} + +static int zloop_degrade_element(struct zloop_device *zlo, + unsigned int element_id) +{ + struct blk_se *se; + int ret = 0; + + if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_NONE) + return -EOPNOTSUPP; + + if (!element_id || element_id > zlo->nr_elements) + return -EINVAL; + + mutex_lock(&zlo->stor_elements_lock); + + se = &zlo->elements[element_id - 1]; + if (READ_ONCE(se->status) == BLK_SE_STS_OK) + WRITE_ONCE(se->status, BLK_SE_STS_DEGRADED); + else + ret = -EINVAL; + + mutex_unlock(&zlo->stor_elements_lock); + + return ret; +} + +static void zloop_remove_element_work(struct work_struct *work) +{ + struct zloop_device *zlo = container_of(work, struct zloop_device, + remove_element_work.work); + struct blk_se *se, *paired_se = NULL; + enum blk_zone_cond cond; + struct zloop_zone *zone; + unsigned int memflags, i; + + mutex_lock(&zlo->stor_elements_lock); + + /* Wait for all IOs to complete. */ + memflags = blk_mq_freeze_queue(zlo->disk->queue); + blk_mq_unfreeze_queue(zlo->disk->queue, memflags); + + /* + * If the element to remove is a read element, zones must go offline and + * the associated write element is also removed. + */ + se = &zlo->elements[zlo->remove_element_id - 1]; + switch (se->type) { + case BLK_SE_TYPE_RDWR: + cond = BLK_ZONE_COND_OFFLINE; + break; + case BLK_SE_TYPE_READ: + cond = BLK_ZONE_COND_OFFLINE; + paired_se = &zlo->elements[se->paired_id - 1]; + break; + case BLK_SE_TYPE_WRITE: + cond = BLK_ZONE_COND_READONLY; + break; + default: + WARN_ON_ONCE(1); + } + + /* + * Change the condition of the zones owned by the (pair of) elements + * being removed and mark the elements removed. + */ + for (i = 0, zone = zlo->zones; i < zlo->nr_zones; i++, zone++) { + if (zone->wr_se_id != se->id && zone->rd_se_id != se->id) + continue; + mutex_lock(&zone->lock); + spin_lock(&zone->wp_lock); + clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags); + zloop_set_zone_cond(zlo, zone, cond); + spin_unlock(&zone->wp_lock); + mutex_unlock(&zone->lock); + } + + WRITE_ONCE(se->status, BLK_SE_STS_REMOVED); + zlo->nr_removed_elements++; + if (paired_se && READ_ONCE(paired_se->status) != BLK_SE_STS_REMOVED) { + WRITE_ONCE(paired_se->status, BLK_SE_STS_REMOVED); + zlo->nr_removed_elements++; + } + + zlo->remove_element_id = 0; + + mutex_unlock(&zlo->stor_elements_lock); +} + +static int zloop_remove_element(struct gendisk *disk, unsigned int element_id) +{ + struct zloop_device *zlo = disk->private_data; + struct blk_se *se, *paired_se = NULL; + unsigned int nr_remove = 1; + int ret = 0; + + if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_NONE) + return -EOPNOTSUPP; + + if (element_id > zlo->nr_elements) + return -EINVAL; + + mutex_lock(&zlo->stor_elements_lock); + + if (zlo->remove_element_id || READ_ONCE(zlo->restore_in_progress)) { + ret = -EBUSY; + goto unlock; + } + + /* + * Get the element to remove. If it is already removed, we have nothing + * to do. + */ + se = &zlo->elements[element_id - 1]; + if (READ_ONCE(se->status) == BLK_SE_STS_REMOVED) + goto unlock; + + /* + * If the element to remove is a read element, the associated write + * element must also be removed. + */ + if (se->type == BLK_SE_TYPE_READ) { + paired_se = &zlo->elements[se->paired_id - 1]; + if (READ_ONCE(paired_se->status) != BLK_SE_STS_REMOVED) + nr_remove = 2; + } + + if (zlo->nr_removed_elements + nr_remove > + zlo->max_nr_removed_elements) { + ret = -EBUSY; + goto unlock; + } + + /* + * Schedule the element removal with a delay, to emulate the (generally + * short) time it takes for a real device to depopulate a head and + * modify the zones. + */ + zlo->remove_element_id = element_id; + WRITE_ONCE(se->status, BLK_SE_STS_REMOVE_IN_PROGRESS); + if (paired_se && READ_ONCE(paired_se->status) != BLK_SE_STS_REMOVED) + WRITE_ONCE(paired_se->status, BLK_SE_STS_REMOVE_IN_PROGRESS); + + schedule_delayed_work(&zlo->remove_element_work, + msecs_to_jiffies(2000)); + +unlock: + mutex_unlock(&zlo->stor_elements_lock); + + return ret; +} + +static void zloop_restore_conv_zone(struct zloop_device *zlo, + struct zloop_zone *zone) +{ + mutex_lock(&zone->lock); + spin_lock(&zone->wp_lock); + if (zone->cond == BLK_ZONE_COND_OFFLINE || + zone->cond == BLK_ZONE_COND_READONLY) + zloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_NOT_WP); + spin_unlock(&zone->wp_lock); + mutex_unlock(&zone->lock); +} + +static int zloop_restore_seq_zone(struct zloop_device *zlo, + struct zloop_zone *zone) +{ + int ret; + + mutex_lock(&zone->lock); + ret = zloop_do_reset_zone(zlo, zone); + if (ret) + set_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags); + mutex_unlock(&zone->lock); + + return ret; +} + +static void zloop_restore_elements_work(struct work_struct *work) +{ + struct zloop_device *zlo = container_of(work, struct zloop_device, + restore_elements_work.work); + struct zloop_zone *zone; + struct blk_se *se; + unsigned int memflags, i; + enum blk_se_status sts; + int ret = 0; + + mutex_lock(&zlo->stor_elements_lock); + + /* Wait for all IOs to complete. */ + memflags = blk_mq_freeze_queue(zlo->disk->queue); + blk_mq_unfreeze_queue(zlo->disk->queue, memflags); + + /* Reset all zones. */ + for (i = 0, zone = zlo->zones; i < zlo->nr_zones; i++, zone++) { + if (test_bit(ZLOOP_ZONE_CONV, &zone->flags)) { + zloop_restore_conv_zone(zlo, zone); + continue; + } + ret = zloop_restore_seq_zone(zlo, zone); + if (ret) + break; + } + + /* + * If we had an error, maintain all elements as removed instead of + * using the restore error status. + */ + if (ret) + sts = BLK_SE_STS_REMOVED; + else + sts = BLK_SE_STS_OK; + + /* Restore all removed elements. */ + for (i = 0, se = zlo->elements; i < zlo->nr_elements; i++, se++) { + if (READ_ONCE(se->status) == BLK_SE_STS_RESTORE_IN_PROGRESS) + WRITE_ONCE(se->status, sts); + } + + if (!ret) + zlo->nr_removed_elements = 0; + + WRITE_ONCE(zlo->restore_in_progress, false); + mutex_unlock(&zlo->stor_elements_lock); +} + +static int zloop_restore_elements(struct gendisk *disk) +{ + struct zloop_device *zlo = disk->private_data; + struct blk_se *se; + unsigned int i; + int ret = 0; + + if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_NONE) + return -EOPNOTSUPP; + + mutex_lock(&zlo->stor_elements_lock); + + if (zlo->remove_element_id || READ_ONCE(zlo->restore_in_progress)) { + ret = -EBUSY; + goto unlock; + } + + /* If we have no removed elements, we have nothing to do. */ + if (!zlo->nr_removed_elements) + goto unlock; + + /* + * Schedule the elements restoration with a delay, to emulate the time + * it takes for a real device to restore all removed heads and reset + * all zones. + */ + WRITE_ONCE(zlo->restore_in_progress, true); + for (i = 0, se = zlo->elements; i < zlo->nr_elements; i++, se++) { + if (READ_ONCE(se->status) == BLK_SE_STS_REMOVED) + WRITE_ONCE(se->status, BLK_SE_STS_RESTORE_IN_PROGRESS); + } + + schedule_delayed_work(&zlo->restore_elements_work, + msecs_to_jiffies(5000)); + +unlock: + mutex_unlock(&zlo->stor_elements_lock); + + return ret; +} + static void zloop_free_disk(struct gendisk *disk) { struct zloop_device *zlo = disk->private_data; unsigned int i; + cancel_delayed_work_sync(&zlo->remove_element_work); + cancel_delayed_work_sync(&zlo->restore_elements_work); + blk_mq_free_tag_set(&zlo->tag_set); for (i = 0; i < zlo->nr_zones; i++) { @@ -1019,15 +1464,24 @@ static void zloop_free_disk(struct gendisk *disk) fput(zlo->data_dir); destroy_workqueue(zlo->workqueue); + kfree(zlo->elements); kfree(zlo->base_dir); kvfree(zlo); } + +static const struct blk_se_ops zloop_se_ops = { + .report_elements = zloop_report_elements, + .remove_element = zloop_remove_element, + .restore_elements = zloop_restore_elements, +}; + static const struct block_device_operations zloop_fops = { .owner = THIS_MODULE, .open = zloop_open, .report_zones = zloop_report_zones, .free_disk = zloop_free_disk, + .se_ops = &zloop_se_ops, }; __printf(3, 4) @@ -1112,6 +1566,24 @@ static int zloop_init_zone(struct zloop_device *zlo, struct zloop_options *opts, if (!opts->buffered_io) oflags |= O_DIRECT; + if (zlo->stor_elements != ZLOOP_STOR_ELEMENTS_NONE) { + unsigned int nr_elems = zlo->nr_elements; + unsigned int se_idx; + + if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_PAIRS) + nr_elems /= 2; + se_idx = zone_no % nr_elems; + zlo->elements[se_idx].nr_zones++; + + zone->wr_se_id = se_idx + 1; + zone->rd_se_id = zone->wr_se_id; + if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_PAIRS) { + zone->rd_se_id += nr_elems; + zlo->elements[zone->rd_se_id - 1].nr_zones = + zlo->elements[se_idx].nr_zones; + } + } + if (zone_no < zlo->nr_conv_zones) { /* Conventional zone file. */ set_bit(ZLOOP_ZONE_CONV, &zone->flags); @@ -1182,6 +1654,79 @@ static int zloop_init_zone(struct zloop_device *zlo, struct zloop_options *opts, return ret; } +#define ZLOOP_MIN_STOR_ELEMENTS 2 +#define ZLOOP_MAX_STOR_ELEMENTS 32 +#define ZLOOP_MIN_ZONES_PER_STOR_ELEMENTS 32 + +static int zloop_create_storage_elements(struct zloop_device *zlo) +{ + struct blk_se *se, *paired_se; + unsigned int i, nr_elems, nr_elements; + + /* + * Calculate the number of storage elements we are going to emulate. + * To achieve a somewhat realistic emulation, we want at least 2 storage + * elements, and no more than 32, targeting at least 32 zones per + * element. + */ + if (zlo->nr_zones <= 64) + nr_elements = 2; + else + nr_elements = + min(ZLOOP_MAX_STOR_ELEMENTS, + zlo->nr_zones / ZLOOP_MIN_ZONES_PER_STOR_ELEMENTS); + + /* + * If we are emulating pairs of read and write storage elements, we need + * double the number of storage element descriptors. + */ + nr_elems = nr_elements; + if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_PAIRS) + nr_elems *= 2; + zlo->elements = kzalloc_objs(struct blk_se, nr_elems); + if (!zlo->elements) + return -ENOMEM; + + for (i = 0, se = zlo->elements; i < nr_elements; i++, se++) { + se->id = i + 1; + if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_PAIRS) + se->type = BLK_SE_TYPE_WRITE; + else + se->type = BLK_SE_TYPE_RDWR; + se->status = BLK_SE_STS_OK; + } + + /* + * If we are emulating pairs of read and write storage elements, + * initialize the read elements paired with the write elements we just + * initialized. + */ + if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_PAIRS) { + for (i = 0; i < nr_elements; i++, se++) { + se->id = nr_elements + i + 1; + paired_se = &zlo->elements[i]; + se->paired_id = paired_se->id; + paired_se->paired_id = se->id; + se->type = BLK_SE_TYPE_READ; + se->status = BLK_SE_STS_OK; + } + } + + /* + * Make sure we do not allow removing all storage elements as that does + * not make any sense. This is consistent with the device advertized + * limit of SCSI and ATA devices supporting the storage element + * depopulation feature. + */ + zlo->nr_elements = nr_elems; + if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_PAIRS) + zlo->max_nr_removed_elements = zlo->nr_elements - 2; + else + zlo->max_nr_removed_elements = zlo->nr_elements - 1; + + return 0; +} + static bool zloop_dev_exists(struct zloop_device *zlo) { struct file *cnv, *seq; @@ -1237,6 +1782,11 @@ static int zloop_ctl_add(struct zloop_options *opts) WRITE_ONCE(zlo->state, Zlo_creating); spin_lock_init(&zlo->open_zones_lock); INIT_LIST_HEAD(&zlo->open_zones_lru_list); + mutex_init(&zlo->stor_elements_lock); + INIT_DELAYED_WORK(&zlo->remove_element_work, + zloop_remove_element_work); + INIT_DELAYED_WORK(&zlo->restore_elements_work, + zloop_restore_elements_work); ret = mutex_lock_killable(&zloop_ctl_mutex); if (ret) @@ -1270,12 +1820,19 @@ static int zloop_ctl_add(struct zloop_options *opts) if (zlo->zone_append) zlo->ordered_zone_append = opts->ordered_zone_append; zlo->discard_write_cache = opts->discard_write_cache; + zlo->stor_elements = opts->stor_elements; + + if (zlo->stor_elements != ZLOOP_STOR_ELEMENTS_NONE) { + ret = zloop_create_storage_elements(zlo); + if (ret) + goto out_free_idr; + } zlo->workqueue = alloc_workqueue("zloop%d", WQ_UNBOUND | WQ_FREEZABLE, opts->nr_queues * opts->queue_depth, zlo->id); if (!zlo->workqueue) { ret = -ENOMEM; - goto out_free_idr; + goto out_destroy_storage_elements; } if (opts->base_dir) @@ -1361,6 +1918,10 @@ static int zloop_ctl_add(struct zloop_options *opts) zlo->id, zlo->nr_zones, ((sector_t)zlo->zone_size << SECTOR_SHIFT) >> 20, zlo->block_size); + if (zlo->nr_elements) + pr_info("zloop%d: %d storage elements\n", + zlo->id, zlo->nr_elements); + pr_info("zloop%d: using %s%s zone append\n", zlo->id, zlo->ordered_zone_append ? "ordered " : "", @@ -1384,6 +1945,8 @@ static int zloop_ctl_add(struct zloop_options *opts) kfree(zlo->base_dir); out_destroy_workqueue: destroy_workqueue(zlo->workqueue); +out_destroy_storage_elements: + kfree(zlo->elements); out_free_idr: mutex_lock(&zloop_ctl_mutex); idr_remove(&zloop_index_idr, zlo->id); @@ -1483,6 +2046,42 @@ static int zloop_ctl_remove(struct zloop_options *opts) return 0; } +static int zloop_ctl_degrade_element(struct zloop_options *opts) +{ + struct zloop_device *zlo; + int ret = 0; + + if (!(opts->mask & ZLOOP_OPT_ID)) { + pr_err("No ID specified for degrade_element\n"); + return -EINVAL; + } + + if (opts->mask & ~(ZLOOP_OPT_ID | ZLOOP_OPT_ELEMENT_ID)) { + pr_err("Invalid option specified for degrade_element\n"); + return -EINVAL; + } + + mutex_lock(&zloop_ctl_mutex); + + zlo = idr_find(&zloop_index_idr, opts->id); + if (!zlo || zlo->state == Zlo_creating) + ret = -ENODEV; + else if (zlo->state == Zlo_deleting) + ret = -EINVAL; + if (ret) + goto unlock; + + ret = zloop_degrade_element(zlo, opts->element_id); + if (!ret) + pr_info("Degraded element %u of device %u\n", + opts->element_id, opts->id); + +unlock: + mutex_unlock(&zloop_ctl_mutex); + + return ret; +} + static int zloop_parse_options(struct zloop_options *opts, const char *buf) { substring_t args[MAX_OPT_ARGS]; @@ -1502,6 +2101,8 @@ static int zloop_parse_options(struct zloop_options *opts, const char *buf) opts->buffered_io = ZLOOP_DEF_BUFFERED_IO; opts->zone_append = ZLOOP_DEF_ZONE_APPEND; opts->ordered_zone_append = ZLOOP_DEF_ORDERED_ZONE_APPEND; + opts->stor_elements = ZLOOP_DEF_STOR_ELEMENTS; + opts->element_id = ZLOOP_DEF_ELEMENT_ID; if (!buf) return 0; @@ -1636,6 +2237,30 @@ static int zloop_parse_options(struct zloop_options *opts, const char *buf) case ZLOOP_OPT_DISCARD_WRITE_CACHE: opts->discard_write_cache = true; break; + case ZLOOP_OPT_STOR_ELEMENTS: + if (match_uint(args, &token)) { + ret = -EINVAL; + goto out; + } + switch (token) { + case ZLOOP_STOR_ELEMENTS_NONE: + case ZLOOP_STOR_ELEMENTS_RDWR: + case ZLOOP_STOR_ELEMENTS_PAIRS: + break; + default: + pr_err("Invalid stor_elements value\n"); + ret = -EINVAL; + goto out; + } + opts->stor_elements = token; + break; + case ZLOOP_OPT_ELEMENT_ID: + if (match_uint(args, &token)) { + ret = -EINVAL; + goto out; + } + opts->element_id = token; + break; case ZLOOP_OPT_ERR: default: pr_warn("unknown parameter or missing value '%s'\n", p); @@ -1664,14 +2289,16 @@ static int zloop_parse_options(struct zloop_options *opts, const char *buf) enum { ZLOOP_CTL_ADD, ZLOOP_CTL_REMOVE, + ZLOOP_CTL_DEGRADE_ELEMENT, }; static struct zloop_ctl_op { int code; const char *name; } zloop_ctl_ops[] = { - { ZLOOP_CTL_ADD, "add" }, - { ZLOOP_CTL_REMOVE, "remove" }, + { ZLOOP_CTL_ADD, "add" }, + { ZLOOP_CTL_REMOVE, "remove" }, + { ZLOOP_CTL_DEGRADE_ELEMENT, "degrade_element" }, { -1, NULL }, }; @@ -1719,6 +2346,9 @@ static ssize_t zloop_ctl_write(struct file *file, const char __user *ubuf, case ZLOOP_CTL_REMOVE: ret = zloop_ctl_remove(&opts); break; + case ZLOOP_CTL_DEGRADE_ELEMENT: + ret = zloop_ctl_degrade_element(&opts); + break; default: pr_err("Invalid operation\n"); ret = -EINVAL; @@ -1742,6 +2372,8 @@ static int zloop_ctl_show(struct seq_file *seq_file, void *private) tok = &zloop_opt_tokens[i]; if (!tok->pattern) break; + if (tok->token == ZLOOP_OPT_ELEMENT_ID) + continue; if (i) seq_putc(seq_file, ','); seq_puts(seq_file, tok->pattern); @@ -1752,6 +2384,10 @@ static int zloop_ctl_show(struct seq_file *seq_file, void *private) seq_puts(seq_file, zloop_ctl_ops[1].name); seq_puts(seq_file, " id=%d\n"); + /* Degrade element operation */ + seq_puts(seq_file, zloop_ctl_ops[2].name); + seq_puts(seq_file, " id=%d,element_id=%d\n"); + return 0; } diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index a1b21ea14e549..48c46f4a1ace8 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -1424,7 +1424,8 @@ static blk_status_t sd_setup_read_write_cmnd(struct scsi_cmnd *cmd) { struct request *rq = scsi_cmd_to_rq(cmd); struct scsi_device *sdp = cmd->device; - struct scsi_disk *sdkp = scsi_disk(rq->q->disk); + struct gendisk *disk = rq->q->disk; + struct scsi_disk *sdkp = scsi_disk(disk); sector_t lba = sectors_to_logical(sdp, blk_rq_pos(rq)); sector_t threshold; unsigned int nr_blocks = sectors_to_logical(sdp, blk_rq_sectors(rq)); @@ -1456,6 +1457,11 @@ static blk_status_t sd_setup_read_write_cmnd(struct scsi_cmnd *cmd) goto fail; } + /* Prevent read/write to offline zones of ZBC disks. */ + if (sdkp->device->type == TYPE_ZBC && + disk_zone_is_offline(disk, blk_rq_pos(rq))) + goto fail; + /* * Some SD card readers can't handle accesses which touch the * last one or two logical blocks. Split accesses as needed. @@ -3937,6 +3943,9 @@ static const struct block_device_operations sd_fops = { .get_unique_id = sd_get_unique_id, .free_disk = scsi_disk_free_disk, .pr_ops = &sd_pr_ops, +#ifdef CONFIG_BLK_DEV_ZONED + .se_ops = &sd_zbc_se_ops, +#endif }; /** diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h index 574af82430169..fc8c49903811e 100644 --- a/drivers/scsi/sd.h +++ b/drivers/scsi/sd.h @@ -156,6 +156,7 @@ struct scsi_disk { unsigned ignore_medium_access_errors : 1; unsigned rscs : 1; /* reduced stream control support */ unsigned use_atomic_write_boundary : 1; + unsigned modify_zones_supported : 1; }; #define to_scsi_disk(obj) container_of(obj, struct scsi_disk, disk_dev) @@ -242,6 +243,8 @@ unsigned int sd_zbc_complete(struct scsi_cmnd *cmd, unsigned int good_bytes, int sd_zbc_report_zones(struct gendisk *disk, sector_t sector, unsigned int nr_zones, struct blk_report_zones_args *args); +extern const struct blk_se_ops sd_zbc_se_ops; + #else /* CONFIG_BLK_DEV_ZONED */ static inline int sd_zbc_read_zones(struct scsi_disk *sdkp, diff --git a/drivers/scsi/sd_zbc.c b/drivers/scsi/sd_zbc.c index 456beaf2e7690..39cd4f9fcce5c 100644 --- a/drivers/scsi/sd_zbc.c +++ b/drivers/scsi/sd_zbc.c @@ -516,6 +516,21 @@ static int sd_zbc_check_capacity(struct scsi_disk *sdkp, unsigned char *buf, return 0; } +/* + * sd_zbc_check_modify_zones - Check if the device supports depopulation + * @sdkp: Target disk + * @buf: command buffer + * + * Check if the device supports the REMOVE ELEMENT AND MODIFY ZONES command. + */ +static inline bool sd_zbc_check_modify_zones(struct scsi_disk *sdkp, + unsigned char *buf) +{ + return scsi_report_opcode(sdkp->device, buf, SD_BUF_SIZE, + SERVICE_ACTION_IN_16, + SAI_REMOVE_ELEMENT_AND_MODIFY_ZONES) == 1; +} + static void sd_zbc_print_zones(struct scsi_disk *sdkp) { if (sdkp->device->type != TYPE_ZBC || !sdkp->capacity) @@ -533,6 +548,239 @@ static void sd_zbc_print_zones(struct scsi_disk *sdkp) sdkp->zone_info.zone_blocks); } +static void sd_zbc_parse_storage_element(struct scsi_disk *sdkp, u8 *desc, + struct blk_se *element) +{ + struct scsi_device *sdp = sdkp->device; + sector_t zone_sectors = sd_zbc_zone_sectors(sdkp); + u64 capacity; + + memset(element, 0, sizeof(*element)); + + element->id = get_unaligned_be32(&desc[4]); + + switch (desc[14]) { + case SCSI_PHYS_ELEM_TYPE_ALL_ACCESS_STORAGE: + element->type = BLK_SE_TYPE_RDWR; + capacity = get_unaligned_be64(&desc[16]); + if (!zone_sectors || capacity == ULLONG_MAX) + element->nr_zones = 0; + else + element->nr_zones = + logical_to_sectors(sdp, capacity) >> + ilog2(zone_sectors); + break; + case SCSI_PHYS_ELEM_TYPE_FRAC_ACCESS_STORAGE: + element->paired_id = get_unaligned_be32(&desc[16]); + if (desc[20] & 0x01) + element->type = BLK_SE_TYPE_READ; + else + element->type = BLK_SE_TYPE_WRITE; + element->nr_zones = get_unaligned_be64(&desc[24]); + break; + default: + element->type = BLK_SE_TYPE_UNKNOWN; + } + + switch (desc[15]) { + case SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS: + case SCSI_PHYS_ELEM_HEALTH_AT_SPEC_LIMITS: + element->status = BLK_SE_STS_OK; + break; + case SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS: + element->status = BLK_SE_STS_DEGRADED; + break; + case SCSI_PHYS_ELEM_HEALTH_DEPOP_REVOKE_ERR: + element->status = BLK_SE_STS_RESTORE_ERROR; + break; + case SCSI_PHYS_ELEM_HEALTH_DEPOP_REVOKE_IN_PROGRESS: + element->status = BLK_SE_STS_RESTORE_IN_PROGRESS; + break; + case SCSI_PHYS_ELEM_HEALTH_DEPOP_ERR: + element->status = BLK_SE_STS_REMOVE_ERROR; + break; + case SCSI_PHYS_ELEM_HEALTH_DEPOP_IN_PROGRESS: + element->status = BLK_SE_STS_REMOVE_IN_PROGRESS; + break; + case SCSI_PHYS_ELEM_HEALTH_DEPOP_OK: + element->status = BLK_SE_STS_REMOVED; + element->restore_allowed = desc[13] & 0x01; + break; + case SCSI_PHYS_ELEM_HEALTH_NOT_REPORTED: + default: + element->status = BLK_SE_STS_UNKNOWN; + break; + } +} + +/* + * Large hard limit on the number of storage elements. This accommodates all + * known devices today and likely forever :) + */ +#define SD_ZBC_MAX_STORAGE_ELEMENTS 255 + +static int sd_zbc_report_storage_elements(struct gendisk *disk, + struct blk_se *elements, + unsigned int *nr_elements) +{ + struct scsi_disk *sdkp = scsi_disk(disk); + struct scsi_device *sdp = sdkp->device; + const int timeout = sdp->request_queue->rq_timeout; + struct scsi_sense_hdr sshdr; + const struct scsi_exec_args exec_args = { + .sshdr = &sshdr, + }; + unsigned char cmd[16]; + unsigned int nr_descs, nr_se; + unsigned int buf_size; + int i, ret = 0, result; + u8 *desc, *buf; + + if (!sdkp->modify_zones_supported) + return -EOPNOTSUPP; + + /* + * We need at least 32B for the report header and 32B for each + * descriptor. + */ + nr_se = min(SD_ZBC_MAX_STORAGE_ELEMENTS, *nr_elements); + buf_size = ALIGN((nr_se + 1) * 32, SECTOR_SIZE); + + buf = kzalloc(buf_size, GFP_NOIO); + if (!buf) + return -ENOMEM; + + memset(cmd, 0, 16); + cmd[0] = SERVICE_ACTION_IN_16; + cmd[1] = SAI_GET_PHYSICAL_ELEMENT_STATUS; + put_unaligned_be32(buf_size, &cmd[10]); + + result = scsi_execute_cmd(sdp, cmd, REQ_OP_DRV_IN, buf, buf_size, + timeout, 1, &exec_args); + if (result) { + sd_printk(KERN_ERR, sdkp, + "GET PHYSICAL ELEMENT STATUS failed\n"); + sd_print_result(sdkp, "GET PHYSICAL ELEMENT STATUS", result); + if (result > 0 && scsi_sense_valid(&sshdr)) + sd_print_sense_hdr(sdkp, &sshdr); + ret = -EIO; + goto free_buf; + } + + nr_descs = get_unaligned_be32(&buf[0]); + if (!nr_descs) { + sd_printk(KERN_ERR, sdkp, + "Invalid number of phys element descriptors\n"); + ret = -EIO; + goto free_buf; + } + if (nr_descs > SD_ZBC_MAX_STORAGE_ELEMENTS) { + sd_printk(KERN_ERR, sdkp, + "Too many phys element descriptors\n"); + ret = -EIO; + goto free_buf; + } + + if (!elements) { + *nr_elements = nr_descs; + goto free_buf; + } + + nr_descs = get_unaligned_be32(&buf[4]); + if (!nr_descs) { + sd_printk(KERN_ERR, sdkp, + "Invalid number of reported phys element descriptors\n"); + ret = -EIO; + goto free_buf; + } + + desc = &buf[32]; + for (i = 0; i < min(nr_se, nr_descs); i++, desc += 32) + sd_zbc_parse_storage_element(sdkp, desc, &elements[i]); + *nr_elements = i; + +free_buf: + kfree(buf); + + return ret; +} + +static int sd_zbc_remove_storage_element(struct gendisk *disk, + unsigned int element_id) +{ + struct scsi_disk *sdkp = scsi_disk(disk); + struct scsi_device *sdp = sdkp->device; + const int timeout = sdp->request_queue->rq_timeout; + struct scsi_sense_hdr sshdr; + const struct scsi_exec_args exec_args = { + .sshdr = &sshdr, + }; + unsigned char cmd[16]; + int result; + + if (!sdkp->modify_zones_supported) + return -EOPNOTSUPP; + + memset(cmd, 0, 16); + cmd[0] = SERVICE_ACTION_IN_16; + cmd[1] = SAI_REMOVE_ELEMENT_AND_MODIFY_ZONES; + put_unaligned_be32(element_id, &cmd[10]); + + result = scsi_execute_cmd(sdp, cmd, REQ_OP_DRV_IN, NULL, 0, + timeout, 1, &exec_args); + if (result) { + sd_printk(KERN_ERR, sdkp, + "REMOVE ELEMENT AND MODIFY ZONES failed\n"); + sd_print_result(sdkp, + "REMOVE ELEMENT AND MODIFY ZONES", result); + if (result > 0 && scsi_sense_valid(&sshdr)) + sd_print_sense_hdr(sdkp, &sshdr); + return -EIO; + } + + return 0; +} + +static int sd_zbc_restore_storage_elements(struct gendisk *disk) +{ + struct scsi_disk *sdkp = scsi_disk(disk); + struct scsi_device *sdp = sdkp->device; + const int timeout = sdp->request_queue->rq_timeout; + struct scsi_sense_hdr sshdr; + const struct scsi_exec_args exec_args = { + .sshdr = &sshdr, + }; + unsigned char cmd[16]; + int result; + + if (!sdkp->modify_zones_supported) + return -EOPNOTSUPP; + + memset(cmd, 0, 16); + cmd[0] = SERVICE_ACTION_IN_16; + cmd[1] = SAI_RESTORE_ELEMENTS_AND_REBUILD; + + result = scsi_execute_cmd(sdp, cmd, REQ_OP_DRV_IN, NULL, 0, + timeout, 1, &exec_args); + if (result) { + sd_printk(KERN_ERR, sdkp, + "RESTORE ELEMENTS AND REBUILD failed\n"); + sd_print_result(sdkp, + "RESTORE ELEMENTS AND REBUILD", result); + if (result > 0 && scsi_sense_valid(&sshdr)) + sd_print_sense_hdr(sdkp, &sshdr); + return -EIO; + } + + return 0; +} + +const struct blk_se_ops sd_zbc_se_ops = { + .report_elements = sd_zbc_report_storage_elements, + .remove_element = sd_zbc_remove_storage_element, + .restore_elements = sd_zbc_restore_storage_elements, +}; + /* * Call blk_revalidate_disk_zones() if any of the zoned disk properties have * changed that make it necessary to call that function. Called by @@ -554,9 +802,15 @@ int sd_zbc_revalidate_zones(struct scsi_disk *sdkp) if (!blk_queue_is_zoned(q)) return 0; + /* + * If the zone size and number of zones has not changed, and the disk + * does not support depopulating heads, skip the rather slow call to + * blk_revalidate_disk_zones(). + */ if (sdkp->zone_info.zone_blocks == zone_blocks && sdkp->zone_info.nr_zones == nr_zones && - disk->nr_zones == nr_zones) + disk->nr_zones == nr_zones && + !sdkp->modify_zones_supported) return 0; sdkp->zone_info.zone_blocks = zone_blocks; @@ -620,6 +874,9 @@ int sd_zbc_read_zones(struct scsi_disk *sdkp, struct queue_limits *lim, if (ret != 0) goto err; + /* Check if REMOVE ELEMENT AND MODIFY ZONES is supported. */ + sdkp->modify_zones_supported = sd_zbc_check_modify_zones(sdkp, buf); + nr_zones = round_up(sdkp->capacity, zone_blocks) >> ilog2(zone_blocks); if (nr_zones > INT_MAX) { sd_printk(KERN_ERR, sdkp, "Too many zones (%llu)\n", diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index d003a9d2d1f6c..20b77c6b451fc 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -189,6 +189,11 @@ struct gendisk { struct disk_events *ev; #ifdef CONFIG_BLK_DEV_ZONED + /* + * Total number of storage elements of the disk. This is always 0 for + * disks that do not support storage element management. + */ + unsigned int nr_storage_elements; /* * Zoned block device information. Reads of this information must be * protected with blk_queue_enter() / blk_queue_exit() or by holding a @@ -942,6 +947,9 @@ static inline unsigned int disk_zone_capacity(struct gendisk *disk, return disk->last_zone_capacity; return disk->zone_capacity; } + +bool disk_zone_is_offline(struct gendisk *disk, sector_t sector); + static inline unsigned int bdev_zone_capacity(struct block_device *bdev, sector_t pos) { @@ -956,6 +964,12 @@ static inline unsigned int disk_nr_zones(struct gendisk *disk) return 0; } +static inline bool disk_zone_is_offline(struct gendisk *disk, + sector_t sector) +{ + return false; +} + static inline bool bdev_zone_is_seq(struct block_device *bdev, sector_t sector) { return false; @@ -1574,6 +1588,22 @@ enum blk_unique_id { BLK_UID_NAA = 3, }; +/* Storage element management operations. */ +struct blk_se_ops { + int (*report_elements)(struct gendisk *disk, + struct blk_se *elements, + unsigned int *nr_elements); + int (*remove_element)(struct gendisk *disk, unsigned int element_id); + int (*restore_elements)(struct gendisk *disk); +}; + +int bdev_report_storage_elements(struct block_device *bdev, + struct blk_se *elements, + unsigned int *nr_elements); +int bdev_remove_storage_element(struct block_device *bdev, + unsigned int element_id); +int bdev_restore_storage_elements(struct block_device *bdev); + struct block_device_operations { void (*submit_bio)(struct bio *bio); int (*poll_bio)(struct bio *bio, struct io_comp_batch *iob, @@ -1601,6 +1631,7 @@ struct block_device_operations { enum blk_unique_id id_type); struct module *owner; const struct pr_ops *pr_ops; + const struct blk_se_ops *se_ops; /* * Special callback for probing GPT entry at a given sector. diff --git a/include/uapi/linux/blkzoned.h b/include/uapi/linux/blkzoned.h index 6638361209667..56b8ea7c6a86d 100644 --- a/include/uapi/linux/blkzoned.h +++ b/include/uapi/linux/blkzoned.h @@ -208,4 +208,87 @@ struct blk_zone_range { #define BLKFINISHZONE _IOW(0x12, 136, struct blk_zone_range) #define BLKREPORTZONEV2 _IOWR(0x12, 142, struct blk_zone_report) +/** + * enum blk_se_type - Type of a zoned device storage element. + * + * @BLK_SE_TYPE_RDWR: The storage element handles both reads and writes. + * @BLK_SE_TYPE_READ: The storage element handles reads only. + * @BLK_SE_TYPE_WRITE: The storage element handles writes only. + * @BLK_SE_TYPE_UNKNOWN: The storage element type is not known. + */ +enum blk_se_type { + BLK_SE_TYPE_RDWR = 0x01, + BLK_SE_TYPE_READ = 0x02, + BLK_SE_TYPE_WRITE = 0x03, + BLK_SE_TYPE_UNKNOWN = 0xFF, +}; + +/** + * enum blk_se_status - Status of a zoned device storage element. + * + * @BLK_SE_STS_OK: The storage element is operating normally. + * @BLK_SE_STS_DEGRADED: The storage element has degraded and is not operating + * normally. + * @BLK_SE_STS_REMOVE_IN_PROGRESS: The storage element is being removed. + * @BLK_SE_STS_REMOVE_ERROR: The storage element removal failed. + * @BLK_SE_STS_RESTORE_IN_PROGRESS: The storage element is being restored. + * @BLK_SE_STS_RESTORE_ERROR: The storage element restoration failed. + * @BLK_SE_STS_REMOVED: The storage element was removed. + * @BLK_SE_STS_UNKNOWN: The storage element status is unknown. + */ +enum blk_se_status { + BLK_SE_STS_OK = 0x01, + BLK_SE_STS_DEGRADED = 0x02, + BLK_SE_STS_REMOVE_IN_PROGRESS = 0x03, + BLK_SE_STS_REMOVE_ERROR = 0x04, + BLK_SE_STS_RESTORE_IN_PROGRESS = 0x05, + BLK_SE_STS_RESTORE_ERROR = 0x06, + BLK_SE_STS_REMOVED = 0x07, + BLK_SE_STS_UNKNOWN = 0xFF, +}; + +/** + * struct blk_se - Zoned device storage element descriptor. + * + * @id: The ID of the element (cannot be 0). + * @paired_id: The ID of the paired element for an element that is not + * of type BLK_SE_TYPE_RDWR. + * @nr_zones: The number of zones that the storage element handles. + * @type: The type of the storage element (enum blk_se_type). + * @status: The health status of the storage element + * (enum blk_se_status). + * @restore_allowed: Indicate if the storage element can be restored. + * @reserved: unused field to align this structure size to 32B + */ +struct blk_se { + __u32 id; + __u32 paired_id; + __u64 nr_zones; + __u8 type; + __u8 status; + __u8 restore_allowed; + __u8 reserved[13]; +}; + +struct blk_se_report { + __u32 nr_elements; + __u32 nr_reported_elements; + __u64 reserved; + struct blk_se elements[]; +}; + +/** + * Zoned block device storage element management ioctl's: + * + * @BLKGETNRSTORELEMS: Get the number of storage elements of the device. + * @BLKREPORTSTORELEMS: Get the device storage elements. + * @BLKREMOVESTORELEM: Remove (depopulate) one storage element of a device. + * @BLKRESTORESTORELEM: Restore (repopulate if possible) all storage elements + * that have been removed. + */ +#define BLKGETNRSTORELEMS _IOR(0x12, 143, __u32) +#define BLKREPORTSTORELEMS _IOWR(0x12, 144, struct blk_se_report) +#define BLKREMOVESTORELEM _IOW(0x12, 145, __u32) +#define BLKRESTORESTORELEMS _IO(0x12, 146) + #endif /* _UAPI_BLKZONED_H */ diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index 34c6f219462a5..8a979326aa7f0 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -309,6 +309,7 @@ struct file_attr { /* 130-136 and 142 are used by zoned block device ioctls (uapi/linux/blkzoned.h) */ /* 137-141 are used by blk-crypto ioctls (uapi/linux/blk-crypto.h) */ #define BLKTRACESETUP2 _IOWR(0x12, 142, struct blk_user_trace_setup2) +/* 143-146 are used by storage element management for zoned block devices. */ #define BMAP_IOCTL 1 /* obsolete - kept for compatibility */ #define FIBMAP _IO(0x00,1) /* bmap access */