file-posix: add tracking of the zone write pointers

Since Linux doesn't have a user API to issue zone append operations to
zoned devices from user space, the file-posix driver is modified to add
zone append emulation using regular writes. To do this, the file-posix
driver tracks the wp location of all zones of the device. It uses an
array of uint64_t. The most significant bit of each wp location indicates
if the zone type is conventional zones.

The zones wp can be changed due to the following operations issued:
- zone reset: change the wp to the start offset of that zone
- zone finish: change to the end location of that zone
- write to a zone
- zone append

Signed-off-by: Sam Li <faithilikerun@gmail.com>
Message-id: 20230508051510.177850-2-faithilikerun@gmail.com
[Fix errno propagation from handle_aiocb_zone_mgmt()
--Stefan]
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Sam Li 2023-05-08 13:15:07 +08:00 committed by Stefan Hajnoczi
parent 90fd974668
commit a3c41f06d5
3 changed files with 193 additions and 4 deletions

View File

@ -1323,9 +1323,93 @@ static int hdev_get_max_segments(int fd, struct stat *st)
}
#if defined(CONFIG_BLKZONED)
/*
* If the reset_all flag is true, then the wps of zone whose state is
* not readonly or offline should be all reset to the start sector.
* Else, take the real wp of the device.
*/
static int get_zones_wp(BlockDriverState *bs, int fd, int64_t offset,
unsigned int nrz, bool reset_all)
{
struct blk_zone *blkz;
size_t rep_size;
uint64_t sector = offset >> BDRV_SECTOR_BITS;
BlockZoneWps *wps = bs->wps;
unsigned int j = offset / bs->bl.zone_size;
unsigned int n = 0, i = 0;
int ret;
rep_size = sizeof(struct blk_zone_report) + nrz * sizeof(struct blk_zone);
g_autofree struct blk_zone_report *rep = NULL;
rep = g_malloc(rep_size);
blkz = (struct blk_zone *)(rep + 1);
while (n < nrz) {
memset(rep, 0, rep_size);
rep->sector = sector;
rep->nr_zones = nrz - n;
do {
ret = ioctl(fd, BLKREPORTZONE, rep);
} while (ret != 0 && errno == EINTR);
if (ret != 0) {
error_report("%d: ioctl BLKREPORTZONE at %" PRId64 " failed %d",
fd, offset, errno);
return -errno;
}
if (!rep->nr_zones) {
break;
}
for (i = 0; i < rep->nr_zones; ++i, ++n, ++j) {
/*
* The wp tracking cares only about sequential writes required and
* sequential write preferred zones so that the wp can advance to
* the right location.
* Use the most significant bit of the wp location to indicate the
* zone type: 0 for SWR/SWP zones and 1 for conventional zones.
*/
if (blkz[i].type == BLK_ZONE_TYPE_CONVENTIONAL) {
wps->wp[j] |= 1ULL << 63;
} else {
switch(blkz[i].cond) {
case BLK_ZONE_COND_FULL:
case BLK_ZONE_COND_READONLY:
/* Zone not writable */
wps->wp[j] = (blkz[i].start + blkz[i].len) << BDRV_SECTOR_BITS;
break;
case BLK_ZONE_COND_OFFLINE:
/* Zone not writable nor readable */
wps->wp[j] = (blkz[i].start) << BDRV_SECTOR_BITS;
break;
default:
if (reset_all) {
wps->wp[j] = blkz[i].start << BDRV_SECTOR_BITS;
} else {
wps->wp[j] = blkz[i].wp << BDRV_SECTOR_BITS;
}
break;
}
}
}
sector = blkz[i - 1].start + blkz[i - 1].len;
}
return 0;
}
static void update_zones_wp(BlockDriverState *bs, int fd, int64_t offset,
unsigned int nrz)
{
if (get_zones_wp(bs, fd, offset, nrz, 0) < 0) {
error_report("update zone wp failed");
}
}
static void raw_refresh_zoned_limits(BlockDriverState *bs, struct stat *st,
Error **errp)
{
BDRVRawState *s = bs->opaque;
BlockZoneModel zoned;
int ret;
@ -1376,6 +1460,23 @@ static void raw_refresh_zoned_limits(BlockDriverState *bs, struct stat *st,
if (ret > 0) {
bs->bl.max_append_sectors = ret >> BDRV_SECTOR_BITS;
}
ret = get_sysfs_long_val(st, "physical_block_size");
if (ret >= 0) {
bs->bl.write_granularity = ret;
}
/* The refresh_limits() function can be called multiple times. */
g_free(bs->wps);
bs->wps = g_malloc(sizeof(BlockZoneWps) +
sizeof(int64_t) * bs->bl.nr_zones);
ret = get_zones_wp(bs, s->fd, 0, bs->bl.nr_zones, 0);
if (ret < 0) {
error_setg_errno(errp, -ret, "report wps failed");
bs->wps = NULL;
return;
}
qemu_co_mutex_init(&bs->wps->colock);
}
#else /* !defined(CONFIG_BLKZONED) */
static void raw_refresh_zoned_limits(BlockDriverState *bs, struct stat *st,
@ -2061,7 +2162,7 @@ static int handle_aiocb_zone_mgmt(void *opaque)
ret = ioctl(fd, aiocb->zone_mgmt.op, &range);
} while (ret != 0 && errno == EINTR);
return ret;
return ret < 0 ? -errno : ret;
}
#endif
@ -2347,9 +2448,15 @@ static int coroutine_fn raw_co_prw(BlockDriverState *bs, uint64_t offset,
{
BDRVRawState *s = bs->opaque;
RawPosixAIOData acb;
int ret;
if (fd_open(bs) < 0)
return -EIO;
#if defined(CONFIG_BLKZONED)
if (type & QEMU_AIO_WRITE && bs->wps) {
qemu_co_mutex_lock(&bs->wps->colock);
}
#endif
/*
* When using O_DIRECT, the request must be aligned to be able to use
@ -2362,12 +2469,15 @@ static int coroutine_fn raw_co_prw(BlockDriverState *bs, uint64_t offset,
#ifdef CONFIG_LINUX_IO_URING
} else if (s->use_linux_io_uring) {
assert(qiov->size == bytes);
return luring_co_submit(bs, s->fd, offset, qiov, type);
ret = luring_co_submit(bs, s->fd, offset, qiov, type);
goto out;
#endif
#ifdef CONFIG_LINUX_AIO
} else if (s->use_linux_aio) {
assert(qiov->size == bytes);
return laio_co_submit(s->fd, offset, qiov, type, s->aio_max_batch);
ret = laio_co_submit(s->fd, offset, qiov, type,
s->aio_max_batch);
goto out;
#endif
}
@ -2384,7 +2494,35 @@ static int coroutine_fn raw_co_prw(BlockDriverState *bs, uint64_t offset,
};
assert(qiov->size == bytes);
return raw_thread_pool_submit(handle_aiocb_rw, &acb);
ret = raw_thread_pool_submit(handle_aiocb_rw, &acb);
goto out; /* Avoid the compiler err of unused label */
out:
#if defined(CONFIG_BLKZONED)
{
BlockZoneWps *wps = bs->wps;
if (ret == 0) {
if (type & QEMU_AIO_WRITE && wps && bs->bl.zone_size) {
uint64_t *wp = &wps->wp[offset / bs->bl.zone_size];
if (!BDRV_ZT_IS_CONV(*wp)) {
/* Advance the wp if needed */
if (offset + bytes > *wp) {
*wp = offset + bytes;
}
}
}
} else {
if (type & QEMU_AIO_WRITE) {
update_zones_wp(bs, s->fd, 0, 1);
}
}
if (type & QEMU_AIO_WRITE && wps) {
qemu_co_mutex_unlock(&wps->colock);
}
}
#endif
return ret;
}
static int coroutine_fn raw_co_preadv(BlockDriverState *bs, int64_t offset,
@ -2487,6 +2625,9 @@ static void raw_close(BlockDriverState *bs)
BDRVRawState *s = bs->opaque;
if (s->fd >= 0) {
#if defined(CONFIG_BLKZONED)
g_free(bs->wps);
#endif
qemu_close(s->fd);
s->fd = -1;
}
@ -3284,6 +3425,7 @@ static int coroutine_fn raw_co_zone_mgmt(BlockDriverState *bs, BlockZoneOp op,
const char *op_name;
unsigned long zo;
int ret;
BlockZoneWps *wps = bs->wps;
int64_t capacity = bs->total_sectors << BDRV_SECTOR_BITS;
zone_size = bs->bl.zone_size;
@ -3301,6 +3443,14 @@ static int coroutine_fn raw_co_zone_mgmt(BlockDriverState *bs, BlockZoneOp op,
return -EINVAL;
}
uint32_t i = offset / bs->bl.zone_size;
uint32_t nrz = len / bs->bl.zone_size;
uint64_t *wp = &wps->wp[i];
if (BDRV_ZT_IS_CONV(*wp) && len != capacity) {
error_report("zone mgmt operations are not allowed for conventional zones");
return -EIO;
}
switch (op) {
case BLK_ZO_OPEN:
op_name = "BLKOPENZONE";
@ -3338,7 +3488,27 @@ static int coroutine_fn raw_co_zone_mgmt(BlockDriverState *bs, BlockZoneOp op,
len >> BDRV_SECTOR_BITS);
ret = raw_thread_pool_submit(handle_aiocb_zone_mgmt, &acb);
if (ret != 0) {
update_zones_wp(bs, s->fd, offset, i);
error_report("ioctl %s failed %d", op_name, ret);
return ret;
}
if (zo == BLKRESETZONE && len == capacity) {
ret = get_zones_wp(bs, s->fd, 0, bs->bl.nr_zones, 1);
if (ret < 0) {
error_report("reporting single wp failed");
return ret;
}
} else if (zo == BLKRESETZONE) {
for (unsigned int j = 0; j < nrz; ++j) {
wp[j] = offset + j * zone_size;
}
} else if (zo == BLKFINISHZONE) {
for (unsigned int j = 0; j < nrz; ++j) {
/* The zoned device allows the last zone smaller that the
* zone size. */
wp[j] = MIN(offset + (j + 1) * zone_size, offset + len);
}
}
return ret;

View File

@ -118,6 +118,14 @@ typedef struct BlockZoneDescriptor {
BlockZoneState state;
} BlockZoneDescriptor;
/*
* Track write pointers of a zone in bytes.
*/
typedef struct BlockZoneWps {
CoMutex colock;
uint64_t wp[];
} BlockZoneWps;
typedef struct BlockDriverInfo {
/* in bytes, 0 if irrelevant */
int cluster_size;
@ -240,6 +248,12 @@ typedef enum {
#define BDRV_SECTOR_BITS 9
#define BDRV_SECTOR_SIZE (1ULL << BDRV_SECTOR_BITS)
/*
* Get the first most significant bit of wp. If it is zero, then
* the zone type is SWR.
*/
#define BDRV_ZT_IS_CONV(wp) (wp & (1ULL << 63))
#define BDRV_REQUEST_MAX_SECTORS MIN_CONST(SIZE_MAX >> BDRV_SECTOR_BITS, \
INT_MAX >> BDRV_SECTOR_BITS)
#define BDRV_REQUEST_MAX_BYTES (BDRV_REQUEST_MAX_SECTORS << BDRV_SECTOR_BITS)

View File

@ -891,6 +891,8 @@ typedef struct BlockLimits {
/* maximum number of active zones */
uint32_t max_active_zones;
uint32_t write_granularity;
} BlockLimits;
typedef struct BdrvOpBlocker BdrvOpBlocker;
@ -1252,6 +1254,9 @@ struct BlockDriverState {
CoMutex bsc_modify_lock;
/* Always non-NULL, but must only be dereferenced under an RCU read guard */
BdrvBlockStatusCache *block_status_cache;
/* array of write pointers' location of each zone in the zoned device. */
BlockZoneWps *wps;
};
struct BlockBackendRootState {