2021-05-26 05:24:56 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/*
|
|
|
|
* Copyright (C) 2021 Western Digital Corporation or its affiliates.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/blkdev.h>
|
|
|
|
|
|
|
|
#include "dm-core.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* User facing dm device block device report zone operation. This calls the
|
|
|
|
* report_zones operation for each target of a device table. This operation is
|
|
|
|
* generally implemented by targets using dm_report_zones().
|
|
|
|
*/
|
|
|
|
int dm_blk_report_zones(struct gendisk *disk, sector_t sector,
|
|
|
|
unsigned int nr_zones, report_zones_cb cb, void *data)
|
|
|
|
{
|
|
|
|
struct mapped_device *md = disk->private_data;
|
|
|
|
struct dm_table *map;
|
|
|
|
int srcu_idx, ret;
|
|
|
|
struct dm_report_zones_args args = {
|
|
|
|
.next_sector = sector,
|
|
|
|
.orig_data = data,
|
|
|
|
.orig_cb = cb,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (dm_suspended_md(md))
|
|
|
|
return -EAGAIN;
|
|
|
|
|
|
|
|
map = dm_get_live_table(md, &srcu_idx);
|
|
|
|
if (!map) {
|
|
|
|
ret = -EIO;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
struct dm_target *tgt;
|
|
|
|
|
|
|
|
tgt = dm_table_find_target(map, args.next_sector);
|
|
|
|
if (WARN_ON_ONCE(!tgt->type->report_zones)) {
|
|
|
|
ret = -EIO;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
args.tgt = tgt;
|
|
|
|
ret = tgt->type->report_zones(tgt, &args,
|
|
|
|
nr_zones - args.zone_idx);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
} while (args.zone_idx < nr_zones &&
|
|
|
|
args.next_sector < get_capacity(disk));
|
|
|
|
|
|
|
|
ret = args.zone_idx;
|
|
|
|
out:
|
|
|
|
dm_put_live_table(md, srcu_idx);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-05-26 05:24:57 +08:00
|
|
|
static int dm_report_zones_cb(struct blk_zone *zone, unsigned int idx,
|
|
|
|
void *data)
|
2021-05-26 05:24:56 +08:00
|
|
|
{
|
|
|
|
struct dm_report_zones_args *args = data;
|
|
|
|
sector_t sector_diff = args->tgt->begin - args->start;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Ignore zones beyond the target range.
|
|
|
|
*/
|
|
|
|
if (zone->start >= args->start + args->tgt->len)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Remap the start sector and write pointer position of the zone
|
|
|
|
* to match its position in the target range.
|
|
|
|
*/
|
|
|
|
zone->start += sector_diff;
|
|
|
|
if (zone->type != BLK_ZONE_TYPE_CONVENTIONAL) {
|
|
|
|
if (zone->cond == BLK_ZONE_COND_FULL)
|
|
|
|
zone->wp = zone->start + zone->len;
|
|
|
|
else if (zone->cond == BLK_ZONE_COND_EMPTY)
|
|
|
|
zone->wp = zone->start;
|
|
|
|
else
|
|
|
|
zone->wp += sector_diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
args->next_sector = zone->start + zone->len;
|
|
|
|
return args->orig_cb(zone, args->zone_idx++, args->orig_data);
|
|
|
|
}
|
2021-05-26 05:24:57 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Helper for drivers of zoned targets to implement struct target_type
|
|
|
|
* report_zones operation.
|
|
|
|
*/
|
|
|
|
int dm_report_zones(struct block_device *bdev, sector_t start, sector_t sector,
|
|
|
|
struct dm_report_zones_args *args, unsigned int nr_zones)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Set the target mapping start sector first so that
|
|
|
|
* dm_report_zones_cb() can correctly remap zone information.
|
|
|
|
*/
|
|
|
|
args->start = start;
|
|
|
|
|
|
|
|
return blkdev_report_zones(bdev, sector, nr_zones,
|
|
|
|
dm_report_zones_cb, args);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(dm_report_zones);
|
2021-05-26 05:24:56 +08:00
|
|
|
|
|
|
|
void dm_set_zones_restrictions(struct dm_table *t, struct request_queue *q)
|
|
|
|
{
|
|
|
|
if (!blk_queue_is_zoned(q))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For a zoned target, the number of zones should be updated for the
|
|
|
|
* correct value to be exposed in sysfs queue/nr_zones. For a BIO based
|
|
|
|
* target, this is all that is needed.
|
|
|
|
*/
|
|
|
|
WARN_ON_ONCE(queue_is_mq(q));
|
|
|
|
q->nr_zones = blkdev_nr_zones(t->md->disk);
|
|
|
|
}
|