Christoph Hellwigzloop: remove irq-safe locking64b437c7 months agoHistory
1772 lines · 41.5 KB · C
RawDownload
// 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.