Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e76239a3 authored by Christoph Hellwig's avatar Christoph Hellwig Committed by Jens Axboe
Browse files

block: add a report_zones method



Dispatching a report zones command through the request queue is a major
pain due to the command reply payload rewriting necessary. Given that
blkdev_report_zones() is executing everything synchronously, implement
report zones as a block device file operation instead, allowing major
simplification of the code in many places.

sd, null-blk, dm-linear and dm-flakey being the only block device
drivers supporting exposing zoned block devices, these drivers are
modified to provide the device side implementation of the
report_zones() block device file operation.

For device mappers, a new report_zones() target type operation is
defined so that the upper block layer calls blkdev_report_zones() can
be propagated down to the underlying devices of the dm targets.
Implementation for this new operation is added to the dm-linear and
dm-flakey targets.

Reviewed-by: default avatarHannes Reinecke <hare@suse.com>
Signed-off-by: default avatarChristoph Hellwig <hch@lst.de>
[Damien]
* Changed method block_device argument to gendisk
* Various bug fixes and improvements
* Added support for null_blk, dm-linear and dm-flakey.
Reviewed-by: default avatarMartin K. Petersen <martin.petersen@oracle.com>
Reviewed-by: default avatarMike Snitzer <snitzer@redhat.com>
Signed-off-by: default avatarDamien Le Moal <damien.lemoal@wdc.com>
Signed-off-by: default avatarJens Axboe <axboe@kernel.dk>
parent 965b652e
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -2300,7 +2300,6 @@ generic_make_request_checks(struct bio *bio)
		if (!q->limits.max_write_same_sectors)
			goto not_supported;
		break;
	case REQ_OP_ZONE_REPORT:
	case REQ_OP_ZONE_RESET:
		if (!blk_queue_is_zoned(q))
			goto not_supported;
+0 −1
Original line number Diff line number Diff line
@@ -283,7 +283,6 @@ static const char *const op_name[] = {
	REQ_OP_NAME(WRITE),
	REQ_OP_NAME(FLUSH),
	REQ_OP_NAME(DISCARD),
	REQ_OP_NAME(ZONE_REPORT),
	REQ_OP_NAME(SECURE_ERASE),
	REQ_OP_NAME(ZONE_RESET),
	REQ_OP_NAME(WRITE_SAME),
+51 −113
Original line number Diff line number Diff line
@@ -93,13 +93,10 @@ unsigned int blkdev_nr_zones(struct block_device *bdev)
EXPORT_SYMBOL_GPL(blkdev_nr_zones);

/*
 * Check that a zone report belongs to the partition.
 * If yes, fix its start sector and write pointer, copy it in the
 * zone information array and return true. Return false otherwise.
 * Check that a zone report belongs to this partition, and if yes, fix its start
 * sector and write pointer and return true. Return false otherwise.
 */
static bool blkdev_report_zone(struct block_device *bdev,
			       struct blk_zone *rep,
			       struct blk_zone *zone)
static bool blkdev_report_zone(struct block_device *bdev, struct blk_zone *rep)
{
	sector_t offset = get_start_sect(bdev);

@@ -114,11 +111,36 @@ static bool blkdev_report_zone(struct block_device *bdev,
		rep->wp = rep->start + rep->len;
	else
		rep->wp -= offset;
	memcpy(zone, rep, sizeof(struct blk_zone));

	return true;
}

static int blk_report_zones(struct gendisk *disk, sector_t sector,
			    struct blk_zone *zones, unsigned int *nr_zones,
			    gfp_t gfp_mask)
{
	struct request_queue *q = disk->queue;
	unsigned int z = 0, n, nrz = *nr_zones;
	sector_t capacity = get_capacity(disk);
	int ret;

	while (z < nrz && sector < capacity) {
		n = nrz - z;
		ret = disk->fops->report_zones(disk, sector, &zones[z], &n,
					       gfp_mask);
		if (ret)
			return ret;
		if (!n)
			break;
		sector += blk_queue_zone_sectors(q) * n;
		z += n;
	}

	WARN_ON(z > *nr_zones);
	*nr_zones = z;

	return 0;
}

/**
 * blkdev_report_zones - Get zones information
 * @bdev:	Target block device
@@ -133,130 +155,46 @@ static bool blkdev_report_zone(struct block_device *bdev,
 *    requested by @nr_zones. The number of zones actually reported is
 *    returned in @nr_zones.
 */
int blkdev_report_zones(struct block_device *bdev,
			sector_t sector,
			struct blk_zone *zones,
			unsigned int *nr_zones,
int blkdev_report_zones(struct block_device *bdev, sector_t sector,
			struct blk_zone *zones, unsigned int *nr_zones,
			gfp_t gfp_mask)
{
	struct request_queue *q = bdev_get_queue(bdev);
	struct blk_zone_report_hdr *hdr;
	unsigned int nrz = *nr_zones;
	struct page *page;
	unsigned int nr_rep;
	size_t rep_bytes;
	unsigned int nr_pages;
	struct bio *bio;
	struct bio_vec *bv;
	unsigned int i, n, nz;
	unsigned int ofst;
	void *addr;
	unsigned int i, nrz;
	int ret;

	if (!q)
		return -ENXIO;

	if (!blk_queue_is_zoned(q))
		return -EOPNOTSUPP;

	if (!nrz)
		return 0;

	if (sector > bdev->bd_part->nr_sects) {
		*nr_zones = 0;
		return 0;
	}

	/*
	 * The zone report has a header. So make room for it in the
	 * payload. Also make sure that the report fits in a single BIO
	 * that will not be split down the stack.
	 * A block device that advertized itself as zoned must have a
	 * report_zones method. If it does not have one defined, the device
	 * driver has a bug. So warn about that.
	 */
	rep_bytes = sizeof(struct blk_zone_report_hdr) +
		sizeof(struct blk_zone) * nrz;
	rep_bytes = (rep_bytes + PAGE_SIZE - 1) & PAGE_MASK;
	if (rep_bytes > (queue_max_sectors(q) << 9))
		rep_bytes = queue_max_sectors(q) << 9;

	nr_pages = min_t(unsigned int, BIO_MAX_PAGES,
			 rep_bytes >> PAGE_SHIFT);
	nr_pages = min_t(unsigned int, nr_pages,
			 queue_max_segments(q));

	bio = bio_alloc(gfp_mask, nr_pages);
	if (!bio)
		return -ENOMEM;

	bio_set_dev(bio, bdev);
	bio->bi_iter.bi_sector = blk_zone_start(q, sector);
	bio_set_op_attrs(bio, REQ_OP_ZONE_REPORT, 0);
	if (WARN_ON_ONCE(!bdev->bd_disk->fops->report_zones))
		return -EOPNOTSUPP;

	for (i = 0; i < nr_pages; i++) {
		page = alloc_page(gfp_mask);
		if (!page) {
			ret = -ENOMEM;
			goto out;
		}
		if (!bio_add_page(bio, page, PAGE_SIZE, 0)) {
			__free_page(page);
			break;
		}
	if (!*nr_zones || sector >= bdev->bd_part->nr_sects) {
		*nr_zones = 0;
		return 0;
	}

	if (i == 0)
		ret = -ENOMEM;
	else
		ret = submit_bio_wait(bio);
	nrz = min(*nr_zones,
		  __blkdev_nr_zones(q, bdev->bd_part->nr_sects - sector));
	ret = blk_report_zones(bdev->bd_disk, get_start_sect(bdev) + sector,
			       zones, &nrz, gfp_mask);
	if (ret)
		goto out;

	/*
	 * Process the report result: skip the header and go through the
	 * reported zones to fixup and fixup the zone information for
	 * partitions. At the same time, return the zone information into
	 * the zone array.
	 */
	n = 0;
	nz = 0;
	nr_rep = 0;
	bio_for_each_segment_all(bv, bio, i) {

		if (!bv->bv_page)
			break;

		addr = kmap_atomic(bv->bv_page);

		/* Get header in the first page */
		ofst = 0;
		if (!nr_rep) {
			hdr = addr;
			nr_rep = hdr->nr_zones;
			ofst = sizeof(struct blk_zone_report_hdr);
		}

		/* Fixup and report zones */
		while (ofst < bv->bv_len &&
		       n < nr_rep && nz < nrz) {
			if (blkdev_report_zone(bdev, addr + ofst, &zones[nz]))
				nz++;
			ofst += sizeof(struct blk_zone);
			n++;
		}

		kunmap_atomic(addr);
		return ret;

		if (n >= nr_rep || nz >= nrz)
	for (i = 0; i < nrz; i++) {
		if (!blkdev_report_zone(bdev, zones))
			break;

		zones++;
	}

	*nr_zones = nz;
out:
	bio_for_each_segment_all(bv, bio, i)
		__free_page(bv->bv_page);
	bio_put(bio);
	*nr_zones = i;

	return ret;
	return 0;
}
EXPORT_SYMBOL_GPL(blkdev_report_zones);

+7 −4
Original line number Diff line number Diff line
@@ -87,7 +87,9 @@ struct nullb {
#ifdef CONFIG_BLK_DEV_ZONED
int null_zone_init(struct nullb_device *dev);
void null_zone_exit(struct nullb_device *dev);
blk_status_t null_zone_report(struct nullb *nullb, struct bio *bio);
int null_zone_report(struct gendisk *disk, sector_t sector,
		     struct blk_zone *zones, unsigned int *nr_zones,
		     gfp_t gfp_mask);
void null_zone_write(struct nullb_cmd *cmd, sector_t sector,
			unsigned int nr_sectors);
void null_zone_reset(struct nullb_cmd *cmd, sector_t sector);
@@ -97,10 +99,11 @@ static inline int null_zone_init(struct nullb_device *dev)
	return -EINVAL;
}
static inline void null_zone_exit(struct nullb_device *dev) {}
static inline blk_status_t null_zone_report(struct nullb *nullb,
					    struct bio *bio)
static inline int null_zone_report(struct gendisk *disk, sector_t sector,
				   struct blk_zone *zones,
				   unsigned int *nr_zones, gfp_t gfp_mask)
{
	return BLK_STS_NOTSUPP;
	return -EOPNOTSUPP;
}
static inline void null_zone_write(struct nullb_cmd *cmd, sector_t sector,
				   unsigned int nr_sectors)
+1 −22
Original line number Diff line number Diff line
@@ -1129,34 +1129,12 @@ static void null_restart_queue_async(struct nullb *nullb)
		blk_mq_start_stopped_hw_queues(q, true);
}

static bool cmd_report_zone(struct nullb *nullb, struct nullb_cmd *cmd)
{
	struct nullb_device *dev = cmd->nq->dev;

	if (dev->queue_mode == NULL_Q_BIO) {
		if (bio_op(cmd->bio) == REQ_OP_ZONE_REPORT) {
			cmd->error = null_zone_report(nullb, cmd->bio);
			return true;
		}
	} else {
		if (req_op(cmd->rq) == REQ_OP_ZONE_REPORT) {
			cmd->error = null_zone_report(nullb, cmd->rq->bio);
			return true;
		}
	}

	return false;
}

static blk_status_t null_handle_cmd(struct nullb_cmd *cmd)
{
	struct nullb_device *dev = cmd->nq->dev;
	struct nullb *nullb = dev->nullb;
	int err = 0;

	if (cmd_report_zone(nullb, cmd))
		goto out;

	if (test_bit(NULLB_DEV_FL_THROTTLED, &dev->flags)) {
		struct request *rq = cmd->rq;

@@ -1443,6 +1421,7 @@ static const struct block_device_operations null_fops = {
	.owner =	THIS_MODULE,
	.open =		null_open,
	.release =	null_release,
	.report_zones =	null_zone_report,
};

static void null_init_queue(struct nullb *nullb, struct nullb_queue *nq)
Loading