// SPDX-License-Identifier: GPL-2.0-only/* * Copyright (c) 2025, Christoph Hellwig. * Copyright (c) 2025, Western Digital Corporation or its affiliates. * * Zoned Loop Device driver - exports a zoned block device using one file per * zone as backing storage. */#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h>#include <linux/blk-mq.h>#include <linux/blkzoned.h>#include <linux/pagemap.h>#include <linux/miscdevice.h>#include <linux/falloc.h>#include <linux/mutex.h>#include <linux/parser.h>#include <linux/seq_file.h>#include <linux/xattr.h> /* * Options for adding (and removing) a device. */enum { ZLOOP_OPT_ERR = 0, ZLOOP_OPT_ID = (1 << 0), ZLOOP_OPT_CAPACITY = (1 << 1), ZLOOP_OPT_ZONE_SIZE = (1 << 2), ZLOOP_OPT_ZONE_CAPACITY = (1 << 3), ZLOOP_OPT_NR_CONV_ZONES = (1 << 4), ZLOOP_OPT_BASE_DIR = (1 << 5), ZLOOP_OPT_NR_QUEUES = (1 << 6), ZLOOP_OPT_QUEUE_DEPTH = (1 << 7), ZLOOP_OPT_BUFFERED_IO = (1 << 8), ZLOOP_OPT_ZONE_APPEND = (1 << 9), ZLOOP_OPT_ORDERED_ZONE_APPEND = (1 << 10), ZLOOP_OPT_DISCARD_WRITE_CACHE = (1 << 11), ZLOOP_OPT_MAX_OPEN_ZONES = (1 << 12),}; static const match_table_t zloop_opt_tokens = { { ZLOOP_OPT_ID, "id=%d" }, { ZLOOP_OPT_CAPACITY, "capacity_mb=%u" }, { ZLOOP_OPT_ZONE_SIZE, "zone_size_mb=%u" }, { ZLOOP_OPT_ZONE_CAPACITY, "zone_capacity_mb=%u" }, { ZLOOP_OPT_NR_CONV_ZONES, "conv_zones=%u" }, { ZLOOP_OPT_BASE_DIR, "base_dir=%s" }, { ZLOOP_OPT_NR_QUEUES, "nr_queues=%u" }, { ZLOOP_OPT_QUEUE_DEPTH, "queue_depth=%u" }, { 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_MAX_OPEN_ZONES, "max_open_zones=%u" }, { ZLOOP_OPT_ERR, NULL }}; /* Default values for the "add" operation. */#define ZLOOP_DEF_ID -1#define ZLOOP_DEF_ZONE_SIZE ((256ULL * SZ_1M) >> SECTOR_SHIFT)#define ZLOOP_DEF_NR_ZONES 64#define ZLOOP_DEF_NR_CONV_ZONES 8#define ZLOOP_DEF_MAX_OPEN_ZONES 0#define ZLOOP_DEF_BASE_DIR "/var/local/zloop"#define ZLOOP_DEF_NR_QUEUES 1#define ZLOOP_DEF_QUEUE_DEPTH 128#define ZLOOP_DEF_BUFFERED_IO false#define ZLOOP_DEF_ZONE_APPEND true#define ZLOOP_DEF_ORDERED_ZONE_APPEND false /* Arbitrary limit on the zone size (16GB). */#define ZLOOP_MAX_ZONE_SIZE_MB 16384 struct zloop_options { unsigned int mask; int id; sector_t capacity; sector_t zone_size; sector_t zone_capacity; unsigned int nr_conv_zones; unsigned int max_open_zones; char *base_dir; unsigned int nr_queues; unsigned int queue_depth; bool buffered_io; bool zone_append; bool ordered_zone_append; bool discard_write_cache;}; /* * Device states. */enum { Zlo_creating = 0, Zlo_live, Zlo_deleting,}; enum zloop_zone_flags { ZLOOP_ZONE_CONV = 0, ZLOOP_ZONE_SEQ_ERROR,}; /* * Zone descriptor. * Locking order: z.lock -> z.wp_lock -> zlo.open_zones_lock */struct zloop_zone { struct list_head open_zone_entry; struct file *file; unsigned long flags; struct mutex lock; spinlock_t wp_lock; enum blk_zone_cond cond; sector_t start; sector_t wp; gfp_t old_gfp_mask;}; struct zloop_device { unsigned int id; unsigned int state; struct blk_mq_tag_set tag_set; struct gendisk *disk; struct workqueue_struct *workqueue; bool buffered_io; bool zone_append; bool ordered_zone_append; bool discard_write_cache; const char *base_dir; struct file *data_dir; unsigned int zone_shift; sector_t zone_size; sector_t zone_capacity; unsigned int nr_zones; unsigned int nr_conv_zones; unsigned int max_open_zones; unsigned int block_size; spinlock_t open_zones_lock; struct list_head open_zones_lru_list; unsigned int nr_open_zones; struct zloop_zone zones[] __counted_by(nr_zones);}; struct zloop_cmd { struct work_struct work; atomic_t ref; sector_t sector; sector_t nr_sectors; long ret; struct kiocb iocb; struct bio_vec *bvec;}; static DEFINE_IDR(zloop_index_idr);static DEFINE_MUTEX(zloop_ctl_mutex); static unsigned int rq_zone_no(struct request *rq){ struct zloop_device *zlo = rq->q->queuedata; return blk_rq_pos(rq) >> zlo->zone_shift;} /* * Open an already open zone. This is mostly a no-op, except for the imp open -> * exp open condition change that may happen. We also move a zone at the tail of * the list of open zones so that if we need to * implicitly close one open zone, we can do so in LRU order. */static inline void zloop_lru_rotate_open_zone(struct zloop_device *zlo, struct zloop_zone *zone){ if (zlo->max_open_zones) { spin_lock(&zlo->open_zones_lock); list_move_tail(&zone->open_zone_entry, &zlo->open_zones_lru_list); spin_unlock(&zlo->open_zones_lock); }} static inline void zloop_lru_remove_open_zone(struct zloop_device *zlo, struct zloop_zone *zone){ if (zone->cond == BLK_ZONE_COND_IMP_OPEN || zone->cond == BLK_ZONE_COND_EXP_OPEN) { spin_lock(&zlo->open_zones_lock); list_del_init(&zone->open_zone_entry); zlo->nr_open_zones--; spin_unlock(&zlo->open_zones_lock); }} static inline bool zloop_can_open_zone(struct zloop_device *zlo){ return !zlo->max_open_zones || zlo->nr_open_zones < zlo->max_open_zones;} /* * If we have reached the maximum open zones limit, attempt to close an * implicitly open zone (if we have any) so that we can implicitly open another * zone without exceeding the maximum number of open zones. */static bool zloop_close_imp_open_zone(struct zloop_device *zlo){ struct zloop_zone *zone; lockdep_assert_held(&zlo->open_zones_lock); if (zloop_can_open_zone(zlo)) return true; list_for_each_entry(zone, &zlo->open_zones_lru_list, open_zone_entry) { if (zone->cond == BLK_ZONE_COND_IMP_OPEN) { zone->cond = BLK_ZONE_COND_CLOSED; list_del_init(&zone->open_zone_entry); zlo->nr_open_zones--; return true; } } return false;} static bool zloop_open_closed_or_empty_zone(struct zloop_device *zlo, struct zloop_zone *zone, bool explicit){ spin_lock(&zlo->open_zones_lock); if (explicit) { /* * Explicit open: we cannot allow this if we have reached the * maximum open zones limit. */ if (!zloop_can_open_zone(zlo)) goto fail; zone->cond = BLK_ZONE_COND_EXP_OPEN; } else { /* * Implicit open case: if we have reached the maximum open zones * limit, try to close an implicitly open zone first. */ if (!zloop_close_imp_open_zone(zlo)) goto fail; zone->cond = BLK_ZONE_COND_IMP_OPEN; } zlo->nr_open_zones++; list_add_tail(&zone->open_zone_entry, &zlo->open_zones_lru_list); spin_unlock(&zlo->open_zones_lock); return true; fail: spin_unlock(&zlo->open_zones_lock); return false;} static bool zloop_do_open_zone(struct zloop_device *zlo, struct zloop_zone *zone, bool explicit){ switch (zone->cond) { case BLK_ZONE_COND_IMP_OPEN: case BLK_ZONE_COND_EXP_OPEN: if (explicit) zone->cond = BLK_ZONE_COND_EXP_OPEN; zloop_lru_rotate_open_zone(zlo, zone); return true; case BLK_ZONE_COND_EMPTY: case BLK_ZONE_COND_CLOSED: return zloop_open_closed_or_empty_zone(zlo, zone, explicit); default: return false; }} static void zloop_mark_full(struct zloop_device *zlo, struct zloop_zone *zone){ lockdep_assert_held(&zone->wp_lock); zloop_lru_remove_open_zone(zlo, zone); zone->cond = BLK_ZONE_COND_FULL; zone->wp = ULLONG_MAX;} static void zloop_mark_empty(struct zloop_device *zlo, struct zloop_zone *zone){ lockdep_assert_held(&zone->wp_lock); zloop_lru_remove_open_zone(zlo, zone); zone->cond = BLK_ZONE_COND_EMPTY; zone->wp = zone->start;} static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no){ struct zloop_zone *zone = &zlo->zones[zone_no]; struct kstat stat; sector_t file_sectors; int ret; lockdep_assert_held(&zone->lock); 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", zone_no, ret); set_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags); return ret; } file_sectors = stat.size >> SECTOR_SHIFT; if (file_sectors > zlo->zone_capacity) { pr_err("Zone %u file too large (%llu sectors > %llu)\n", zone_no, file_sectors, zlo->zone_capacity); return -EINVAL; } if (!IS_ALIGNED(stat.size, zlo->block_size)) { pr_err("Zone %u file size (%llu) not aligned to block size %u\n", zone_no, stat.size, zlo->block_size); return -EINVAL; } spin_lock(&zone->wp_lock); if (!file_sectors) { zloop_mark_empty(zlo, zone); } else if (file_sectors == zlo->zone_capacity) { zloop_mark_full(zlo, zone); } else { if (zone->cond != BLK_ZONE_COND_IMP_OPEN && zone->cond != BLK_ZONE_COND_EXP_OPEN) zone->cond = BLK_ZONE_COND_CLOSED; zone->wp = zone->start + file_sectors; } spin_unlock(&zone->wp_lock); return 0;} static int zloop_open_zone(struct zloop_device *zlo, unsigned int zone_no){ struct zloop_zone *zone = &zlo->zones[zone_no]; int ret = 0; if (test_bit(ZLOOP_ZONE_CONV, &zone->flags)) return -EIO; mutex_lock(&zone->lock); if (test_and_clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags)) { ret = zloop_update_seq_zone(zlo, zone_no); if (ret) goto unlock; } if (!zloop_do_open_zone(zlo, zone, true)) ret = -EIO; unlock: mutex_unlock(&zone->lock); return ret;} static int zloop_close_zone(struct zloop_device *zlo, unsigned int zone_no){ struct zloop_zone *zone = &zlo->zones[zone_no]; int ret = 0; if (test_bit(ZLOOP_ZONE_CONV, &zone->flags)) return -EIO; mutex_lock(&zone->lock); if (test_and_clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags)) { ret = zloop_update_seq_zone(zlo, zone_no); if (ret) goto unlock; } switch (zone->cond) { case BLK_ZONE_COND_CLOSED: break; case BLK_ZONE_COND_IMP_OPEN: case BLK_ZONE_COND_EXP_OPEN: spin_lock(&zone->wp_lock); zloop_lru_remove_open_zone(zlo, zone); if (zone->wp == zone->start) zone->cond = BLK_ZONE_COND_EMPTY; else zone->cond = BLK_ZONE_COND_CLOSED; spin_unlock(&zone->wp_lock); break; case BLK_ZONE_COND_EMPTY: case BLK_ZONE_COND_FULL: default: ret = -EIO; break; } unlock: mutex_unlock(&zone->lock); return ret;} static int zloop_reset_zone(struct zloop_device *zlo, unsigned int zone_no){ struct zloop_zone *zone = &zlo->zones[zone_no]; int ret = 0; if (test_bit(ZLOOP_ZONE_CONV, &zone->flags)) return -EIO; mutex_lock(&zone->lock); 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)) { 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); return ret;} static int zloop_reset_all_zones(struct zloop_device *zlo){ unsigned int i; int ret; for (i = zlo->nr_conv_zones; i < zlo->nr_zones; i++) { ret = zloop_reset_zone(zlo, i); if (ret) return ret; } return 0;} static int zloop_finish_zone(struct zloop_device *zlo, unsigned int zone_no){ struct zloop_zone *zone = &zlo->zones[zone_no]; int ret = 0; if (test_bit(ZLOOP_ZONE_CONV, &zone->flags)) return -EIO; mutex_lock(&zone->lock); if (!test_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags) && zone->cond == BLK_ZONE_COND_FULL) goto unlock; if (vfs_truncate(&zone->file->f_path, zlo->zone_size << SECTOR_SHIFT)) { set_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags); ret = -EIO; goto unlock; } spin_lock(&zone->wp_lock); zloop_mark_full(zlo, zone); clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags); spin_unlock(&zone->wp_lock); unlock: mutex_unlock(&zone->lock); return ret;} static void zloop_put_cmd(struct zloop_cmd *cmd){ struct request *rq = blk_mq_rq_from_pdu(cmd);Lines 1–500 of 1772. Load more
// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2025, Christoph Hellwig. * Copyright (c) 2025, Western Digital Corporation or its affiliates. * * Zoned Loop Device driver - exports a zoned block device using one file per * zone as backing storage. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h> #include <linux/blk-mq.h> #include <linux/blkzoned.h> #include <linux/pagemap.h> #include <linux/miscdevice.h> #include <linux/falloc.h> #include <linux/mutex.h> #include <linux/parser.h> #include <linux/seq_file.h> #include <linux/xattr.h> /* * Options for adding (and removing) a device. */ enum { ZLOOP_OPT_ERR = 0, ZLOOP_OPT_ID = (1 << 0), ZLOOP_OPT_CAPACITY = (1 << 1), ZLOOP_OPT_ZONE_SIZE = (1 << 2), ZLOOP_OPT_ZONE_CAPACITY = (1 << 3), ZLOOP_OPT_NR_CONV_ZONES = (1 << 4), ZLOOP_OPT_BASE_DIR = (1 << 5), ZLOOP_OPT_NR_QUEUES = (1 << 6), ZLOOP_OPT_QUEUE_DEPTH = (1 << 7), ZLOOP_OPT_BUFFERED_IO = (1 << 8), ZLOOP_OPT_ZONE_APPEND = (1 << 9), ZLOOP_OPT_ORDERED_ZONE_APPEND = (1 << 10), ZLOOP_OPT_DISCARD_WRITE_CACHE = (1 << 11), ZLOOP_OPT_MAX_OPEN_ZONES = (1 << 12), }; static const match_table_t zloop_opt_tokens = { { ZLOOP_OPT_ID, "id=%d" }, { ZLOOP_OPT_CAPACITY, "capacity_mb=%u" }, { ZLOOP_OPT_ZONE_SIZE, "zone_size_mb=%u" }, { ZLOOP_OPT_ZONE_CAPACITY, "zone_capacity_mb=%u" }, { ZLOOP_OPT_NR_CONV_ZONES, "conv_zones=%u" }, { ZLOOP_OPT_BASE_DIR, "base_dir=%s" }, { ZLOOP_OPT_NR_QUEUES, "nr_queues=%u" }, { ZLOOP_OPT_QUEUE_DEPTH, "queue_depth=%u" }, { 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_MAX_OPEN_ZONES, "max_open_zones=%u" }, { ZLOOP_OPT_ERR, NULL } }; /* Default values for the "add" operation. */ #define ZLOOP_DEF_ID -1 #define ZLOOP_DEF_ZONE_SIZE ((256ULL * SZ_1M) >> SECTOR_SHIFT) #define ZLOOP_DEF_NR_ZONES 64 #define ZLOOP_DEF_NR_CONV_ZONES 8 #define ZLOOP_DEF_MAX_OPEN_ZONES 0 #define ZLOOP_DEF_BASE_DIR "/var/local/zloop" #define ZLOOP_DEF_NR_QUEUES 1 #define ZLOOP_DEF_QUEUE_DEPTH 128 #define ZLOOP_DEF_BUFFERED_IO false #define ZLOOP_DEF_ZONE_APPEND true #define ZLOOP_DEF_ORDERED_ZONE_APPEND false /* Arbitrary limit on the zone size (16GB). */ #define ZLOOP_MAX_ZONE_SIZE_MB 16384 struct zloop_options { unsigned int mask; int id; sector_t capacity; sector_t zone_size; sector_t zone_capacity; unsigned int nr_conv_zones; unsigned int max_open_zones; char *base_dir; unsigned int nr_queues; unsigned int queue_depth; bool buffered_io; bool zone_append; bool ordered_zone_append; bool discard_write_cache; }; /* * Device states. */ enum { Zlo_creating = 0, Zlo_live, Zlo_deleting, }; enum zloop_zone_flags { ZLOOP_ZONE_CONV = 0, ZLOOP_ZONE_SEQ_ERROR, }; /* * Zone descriptor. * Locking order: z.lock -> z.wp_lock -> zlo.open_zones_lock */ struct zloop_zone { struct list_head open_zone_entry; struct file *file; unsigned long flags; struct mutex lock; spinlock_t wp_lock; enum blk_zone_cond cond; sector_t start; sector_t wp; gfp_t old_gfp_mask; }; struct zloop_device { unsigned int id; unsigned int state; struct blk_mq_tag_set tag_set; struct gendisk *disk; struct workqueue_struct *workqueue; bool buffered_io; bool zone_append; bool ordered_zone_append; bool discard_write_cache; const char *base_dir; struct file *data_dir; unsigned int zone_shift; sector_t zone_size; sector_t zone_capacity; unsigned int nr_zones; unsigned int nr_conv_zones; unsigned int max_open_zones; unsigned int block_size; spinlock_t open_zones_lock; struct list_head open_zones_lru_list; unsigned int nr_open_zones; struct zloop_zone zones[] __counted_by(nr_zones); }; struct zloop_cmd { struct work_struct work; atomic_t ref; sector_t sector; sector_t nr_sectors; long ret; struct kiocb iocb; struct bio_vec *bvec; }; static DEFINE_IDR(zloop_index_idr); static DEFINE_MUTEX(zloop_ctl_mutex); static unsigned int rq_zone_no(struct request *rq) { struct zloop_device *zlo = rq->q->queuedata; return blk_rq_pos(rq) >> zlo->zone_shift; } /* * Open an already open zone. This is mostly a no-op, except for the imp open -> * exp open condition change that may happen. We also move a zone at the tail of * the list of open zones so that if we need to * implicitly close one open zone, we can do so in LRU order. */ static inline void zloop_lru_rotate_open_zone(struct zloop_device *zlo, struct zloop_zone *zone) { if (zlo->max_open_zones) { spin_lock(&zlo->open_zones_lock); list_move_tail(&zone->open_zone_entry, &zlo->open_zones_lru_list); spin_unlock(&zlo->open_zones_lock); } } static inline void zloop_lru_remove_open_zone(struct zloop_device *zlo, struct zloop_zone *zone) { if (zone->cond == BLK_ZONE_COND_IMP_OPEN || zone->cond == BLK_ZONE_COND_EXP_OPEN) { spin_lock(&zlo->open_zones_lock); list_del_init(&zone->open_zone_entry); zlo->nr_open_zones--; spin_unlock(&zlo->open_zones_lock); } } static inline bool zloop_can_open_zone(struct zloop_device *zlo) { return !zlo->max_open_zones || zlo->nr_open_zones < zlo->max_open_zones; } /* * If we have reached the maximum open zones limit, attempt to close an * implicitly open zone (if we have any) so that we can implicitly open another * zone without exceeding the maximum number of open zones. */ static bool zloop_close_imp_open_zone(struct zloop_device *zlo) { struct zloop_zone *zone; lockdep_assert_held(&zlo->open_zones_lock); if (zloop_can_open_zone(zlo)) return true; list_for_each_entry(zone, &zlo->open_zones_lru_list, open_zone_entry) { if (zone->cond == BLK_ZONE_COND_IMP_OPEN) { zone->cond = BLK_ZONE_COND_CLOSED; list_del_init(&zone->open_zone_entry); zlo->nr_open_zones--; return true; } } return false; } static bool zloop_open_closed_or_empty_zone(struct zloop_device *zlo, struct zloop_zone *zone, bool explicit) { spin_lock(&zlo->open_zones_lock); if (explicit) { /* * Explicit open: we cannot allow this if we have reached the * maximum open zones limit. */ if (!zloop_can_open_zone(zlo)) goto fail; zone->cond = BLK_ZONE_COND_EXP_OPEN; } else { /* * Implicit open case: if we have reached the maximum open zones * limit, try to close an implicitly open zone first. */ if (!zloop_close_imp_open_zone(zlo)) goto fail; zone->cond = BLK_ZONE_COND_IMP_OPEN; } zlo->nr_open_zones++; list_add_tail(&zone->open_zone_entry, &zlo->open_zones_lru_list); spin_unlock(&zlo->open_zones_lock); return true; fail: spin_unlock(&zlo->open_zones_lock); return false; } static bool zloop_do_open_zone(struct zloop_device *zlo, struct zloop_zone *zone, bool explicit) { switch (zone->cond) { case BLK_ZONE_COND_IMP_OPEN: case BLK_ZONE_COND_EXP_OPEN: if (explicit) zone->cond = BLK_ZONE_COND_EXP_OPEN; zloop_lru_rotate_open_zone(zlo, zone); return true; case BLK_ZONE_COND_EMPTY: case BLK_ZONE_COND_CLOSED: return zloop_open_closed_or_empty_zone(zlo, zone, explicit); default: return false; } } static void zloop_mark_full(struct zloop_device *zlo, struct zloop_zone *zone) { lockdep_assert_held(&zone->wp_lock); zloop_lru_remove_open_zone(zlo, zone); zone->cond = BLK_ZONE_COND_FULL; zone->wp = ULLONG_MAX; } static void zloop_mark_empty(struct zloop_device *zlo, struct zloop_zone *zone) { lockdep_assert_held(&zone->wp_lock); zloop_lru_remove_open_zone(zlo, zone); zone->cond = BLK_ZONE_COND_EMPTY; zone->wp = zone->start; } static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no) { struct zloop_zone *zone = &zlo->zones[zone_no]; struct kstat stat; sector_t file_sectors; int ret; lockdep_assert_held(&zone->lock); 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", zone_no, ret); set_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags); return ret; } file_sectors = stat.size >> SECTOR_SHIFT; if (file_sectors > zlo->zone_capacity) { pr_err("Zone %u file too large (%llu sectors > %llu)\n", zone_no, file_sectors, zlo->zone_capacity); return -EINVAL; } if (!IS_ALIGNED(stat.size, zlo->block_size)) { pr_err("Zone %u file size (%llu) not aligned to block size %u\n", zone_no, stat.size, zlo->block_size); return -EINVAL; } spin_lock(&zone->wp_lock); if (!file_sectors) { zloop_mark_empty(zlo, zone); } else if (file_sectors == zlo->zone_capacity) { zloop_mark_full(zlo, zone); } else { if (zone->cond != BLK_ZONE_COND_IMP_OPEN && zone->cond != BLK_ZONE_COND_EXP_OPEN) zone->cond = BLK_ZONE_COND_CLOSED; zone->wp = zone->start + file_sectors; } spin_unlock(&zone->wp_lock); return 0; } static int zloop_open_zone(struct zloop_device *zlo, unsigned int zone_no) { struct zloop_zone *zone = &zlo->zones[zone_no]; int ret = 0; if (test_bit(ZLOOP_ZONE_CONV, &zone->flags)) return -EIO; mutex_lock(&zone->lock); if (test_and_clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags)) { ret = zloop_update_seq_zone(zlo, zone_no); if (ret) goto unlock; } if (!zloop_do_open_zone(zlo, zone, true)) ret = -EIO; unlock: mutex_unlock(&zone->lock); return ret; } static int zloop_close_zone(struct zloop_device *zlo, unsigned int zone_no) { struct zloop_zone *zone = &zlo->zones[zone_no]; int ret = 0; if (test_bit(ZLOOP_ZONE_CONV, &zone->flags)) return -EIO; mutex_lock(&zone->lock); if (test_and_clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags)) { ret = zloop_update_seq_zone(zlo, zone_no); if (ret) goto unlock; } switch (zone->cond) { case BLK_ZONE_COND_CLOSED: break; case BLK_ZONE_COND_IMP_OPEN: case BLK_ZONE_COND_EXP_OPEN: spin_lock(&zone->wp_lock); zloop_lru_remove_open_zone(zlo, zone); if (zone->wp == zone->start) zone->cond = BLK_ZONE_COND_EMPTY; else zone->cond = BLK_ZONE_COND_CLOSED; spin_unlock(&zone->wp_lock); break; case BLK_ZONE_COND_EMPTY: case BLK_ZONE_COND_FULL: default: ret = -EIO; break; } unlock: mutex_unlock(&zone->lock); return ret; } static int zloop_reset_zone(struct zloop_device *zlo, unsigned int zone_no) { struct zloop_zone *zone = &zlo->zones[zone_no]; int ret = 0; if (test_bit(ZLOOP_ZONE_CONV, &zone->flags)) return -EIO; mutex_lock(&zone->lock); 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)) { 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); return ret; } static int zloop_reset_all_zones(struct zloop_device *zlo) { unsigned int i; int ret; for (i = zlo->nr_conv_zones; i < zlo->nr_zones; i++) { ret = zloop_reset_zone(zlo, i); if (ret) return ret; } return 0; } static int zloop_finish_zone(struct zloop_device *zlo, unsigned int zone_no) { struct zloop_zone *zone = &zlo->zones[zone_no]; int ret = 0; if (test_bit(ZLOOP_ZONE_CONV, &zone->flags)) return -EIO; mutex_lock(&zone->lock); if (!test_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags) && zone->cond == BLK_ZONE_COND_FULL) goto unlock; if (vfs_truncate(&zone->file->f_path, zlo->zone_size << SECTOR_SHIFT)) { set_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags); ret = -EIO; goto unlock; } spin_lock(&zone->wp_lock); zloop_mark_full(zlo, zone); clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags); spin_unlock(&zone->wp_lock); unlock: mutex_unlock(&zone->lock); return ret; } static void zloop_put_cmd(struct zloop_cmd *cmd) { struct request *rq = blk_mq_rq_from_pdu(cmd);