blob: 0fa6bfd168d05b0e503a7edeb9603333e20064ae [file] [log] [blame]
/*
* Copyright (C) 2019 Western Digital Corporation or its affiliates.
*
* This file is released under the GPL.
*
* libzbc engine
* IO engine using libzbc library to talk to SMR disks.
*/
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <libzbc/zbc.h>
#include "fio.h"
#include "err.h"
#include "zbd_types.h"
#include "zbd.h"
struct libzbc_data {
struct zbc_device *zdev;
enum zbc_dev_model model;
uint64_t nr_sectors;
uint32_t max_open_seq_req;
};
static int libzbc_get_dev_info(struct libzbc_data *ld, struct fio_file *f)
{
struct zbc_device_info *zinfo;
zinfo = calloc(1, sizeof(*zinfo));
if (!zinfo)
return -ENOMEM;
zbc_get_device_info(ld->zdev, zinfo);
ld->model = zinfo->zbd_model;
ld->nr_sectors = zinfo->zbd_sectors;
ld->max_open_seq_req = zinfo->zbd_max_nr_open_seq_req;
dprint(FD_ZBD, "%s: vendor_id:%s, type: %s, model: %s\n",
f->file_name, zinfo->zbd_vendor_id,
zbc_device_type_str(zinfo->zbd_type),
zbc_device_model_str(zinfo->zbd_model));
free(zinfo);
return 0;
}
static int libzbc_open_dev(struct thread_data *td, struct fio_file *f,
struct libzbc_data **p_ld)
{
struct libzbc_data *ld = td->io_ops_data;
int ret, flags = OS_O_DIRECT;
if (ld) {
/* Already open */
assert(ld->zdev);
goto out;
}
if (f->filetype != FIO_TYPE_BLOCK && f->filetype != FIO_TYPE_CHAR) {
td_verror(td, EINVAL, "wrong file type");
log_err("ioengine libzbc only works on block or character devices\n");
return -EINVAL;
}
if (td_write(td) || td_trim(td)) {
if (!read_only)
flags |= O_RDWR;
} else if (td_read(td)) {
flags |= O_RDONLY;
}
ld = calloc(1, sizeof(*ld));
if (!ld)
return -ENOMEM;
ret = zbc_open(f->file_name,
flags | ZBC_O_DRV_SCSI | ZBC_O_DRV_ATA,
&ld->zdev);
if (ret) {
log_err("%s: zbc_open() failed, err=%d\n",
f->file_name, ret);
goto err;
}
ret = libzbc_get_dev_info(ld, f);
if (ret)
goto err_close;
td->io_ops_data = ld;
out:
if (p_ld)
*p_ld = ld;
return 0;
err_close:
zbc_close(ld->zdev);
err:
free(ld);
return ret;
}
static int libzbc_close_dev(struct thread_data *td)
{
struct libzbc_data *ld = td->io_ops_data;
int ret = 0;
td->io_ops_data = NULL;
if (ld) {
if (ld->zdev)
ret = zbc_close(ld->zdev);
free(ld);
}
return ret;
}
static int libzbc_open_file(struct thread_data *td, struct fio_file *f)
{
return libzbc_open_dev(td, f, NULL);
}
static int libzbc_close_file(struct thread_data *td, struct fio_file *f)
{
int ret;
ret = libzbc_close_dev(td);
if (ret)
log_err("%s: close device failed err %d\n",
f->file_name, ret);
return ret;
}
static void libzbc_cleanup(struct thread_data *td)
{
libzbc_close_dev(td);
}
static int libzbc_invalidate(struct thread_data *td, struct fio_file *f)
{
/* Passthrough IO do not cache data. Nothing to do */
return 0;
}
static int libzbc_get_file_size(struct thread_data *td, struct fio_file *f)
{
struct libzbc_data *ld;
int ret;
if (fio_file_size_known(f))
return 0;
ret = libzbc_open_dev(td, f, &ld);
if (ret)
return ret;
f->real_file_size = ld->nr_sectors << 9;
fio_file_set_size_known(f);
return 0;
}
static int libzbc_get_zoned_model(struct thread_data *td, struct fio_file *f,
enum zbd_zoned_model *model)
{
struct libzbc_data *ld;
int ret;
if (f->filetype != FIO_TYPE_BLOCK && f->filetype != FIO_TYPE_CHAR)
return -EINVAL;
ret = libzbc_open_dev(td, f, &ld);
if (ret)
return ret;
switch (ld->model) {
case ZBC_DM_HOST_AWARE:
*model = ZBD_HOST_AWARE;
break;
case ZBC_DM_HOST_MANAGED:
*model = ZBD_HOST_MANAGED;
break;
default:
*model = ZBD_NONE;
break;
}
return 0;
}
static int libzbc_report_zones(struct thread_data *td, struct fio_file *f,
uint64_t offset, struct zbd_zone *zbdz,
unsigned int nr_zones)
{
struct libzbc_data *ld;
uint64_t sector = offset >> 9;
struct zbc_zone *zones;
unsigned int i;
int ret;
ret = libzbc_open_dev(td, f, &ld);
if (ret)
return ret;
if (sector >= ld->nr_sectors)
return 0;
zones = calloc(nr_zones, sizeof(struct zbc_zone));
if (!zones) {
ret = -ENOMEM;
goto out;
}
ret = zbc_report_zones(ld->zdev, sector, ZBC_RO_ALL, zones, &nr_zones);
if (ret < 0) {
log_err("%s: zbc_report_zones failed, err=%d\n",
f->file_name, ret);
goto out;
}
for (i = 0; i < nr_zones; i++, zbdz++) {
zbdz->start = zones[i].zbz_start << 9;
zbdz->len = zones[i].zbz_length << 9;
zbdz->wp = zones[i].zbz_write_pointer << 9;
/*
* ZBC/ZAC do not define zone capacity, so use the zone size as
* the zone capacity.
*/
zbdz->capacity = zbdz->len;
switch (zones[i].zbz_type) {
case ZBC_ZT_CONVENTIONAL:
zbdz->type = ZBD_ZONE_TYPE_CNV;
break;
case ZBC_ZT_SEQUENTIAL_REQ:
zbdz->type = ZBD_ZONE_TYPE_SWR;
break;
case ZBC_ZT_SEQUENTIAL_PREF:
zbdz->type = ZBD_ZONE_TYPE_SWP;
break;
default:
td_verror(td, errno, "invalid zone type");
log_err("%s: invalid type for zone at sector %llu.\n",
f->file_name, (unsigned long long)zbdz->start);
ret = -EIO;
goto out;
}
switch (zones[i].zbz_condition) {
case ZBC_ZC_NOT_WP:
zbdz->cond = ZBD_ZONE_COND_NOT_WP;
break;
case ZBC_ZC_EMPTY:
zbdz->cond = ZBD_ZONE_COND_EMPTY;
break;
case ZBC_ZC_IMP_OPEN:
zbdz->cond = ZBD_ZONE_COND_IMP_OPEN;
break;
case ZBC_ZC_EXP_OPEN:
zbdz->cond = ZBD_ZONE_COND_EXP_OPEN;
break;
case ZBC_ZC_CLOSED:
zbdz->cond = ZBD_ZONE_COND_CLOSED;
break;
case ZBC_ZC_FULL:
zbdz->cond = ZBD_ZONE_COND_FULL;
break;
case ZBC_ZC_RDONLY:
case ZBC_ZC_OFFLINE:
default:
/* Treat all these conditions as offline (don't use!) */
zbdz->cond = ZBD_ZONE_COND_OFFLINE;
zbdz->wp = zbdz->start;
}
}
ret = nr_zones;
out:
free(zones);
return ret;
}
static int libzbc_reset_wp(struct thread_data *td, struct fio_file *f,
uint64_t offset, uint64_t length)
{
struct libzbc_data *ld = td->io_ops_data;
uint64_t sector = offset >> 9;
uint64_t end_sector = (offset + length) >> 9;
unsigned int nr_zones;
struct zbc_errno err;
int i, ret;
assert(ld);
assert(ld->zdev);
nr_zones = (length + td->o.zone_size - 1) / td->o.zone_size;
if (!sector && end_sector >= ld->nr_sectors) {
/* Reset all zones */
ret = zbc_reset_zone(ld->zdev, 0, ZBC_OP_ALL_ZONES);
if (ret)
goto err;
return 0;
}
for (i = 0; i < nr_zones; i++, sector += td->o.zone_size >> 9) {
ret = zbc_reset_zone(ld->zdev, sector, 0);
if (ret)
goto err;
}
return 0;
err:
zbc_errno(ld->zdev, &err);
td_verror(td, errno, "zbc_reset_zone failed");
if (err.sk)
log_err("%s: reset wp failed %s:%s\n",
f->file_name,
zbc_sk_str(err.sk), zbc_asc_ascq_str(err.asc_ascq));
return -ret;
}
static int libzbc_move_zone_wp(struct thread_data *td, struct fio_file *f,
struct zbd_zone *z, uint64_t length,
const char *buf)
{
struct libzbc_data *ld = td->io_ops_data;
uint64_t sector = z->wp >> 9;
size_t count = length >> 9;
struct zbc_errno err;
int ret;
assert(ld);
assert(ld->zdev);
assert(buf);
ret = zbc_pwrite(ld->zdev, buf, count, sector);
if (ret == count)
return 0;
zbc_errno(ld->zdev, &err);
td_verror(td, errno, "zbc_write for write pointer move failed");
if (err.sk)
log_err("%s: wp move failed %s:%s\n",
f->file_name,
zbc_sk_str(err.sk), zbc_asc_ascq_str(err.asc_ascq));
return -ret;
}
static int libzbc_finish_zone(struct thread_data *td, struct fio_file *f,
uint64_t offset, uint64_t length)
{
struct libzbc_data *ld = td->io_ops_data;
uint64_t sector = offset >> 9;
unsigned int nr_zones;
struct zbc_errno err;
int i, ret;
assert(ld);
assert(ld->zdev);
nr_zones = (length + td->o.zone_size - 1) / td->o.zone_size;
assert(nr_zones > 0);
for (i = 0; i < nr_zones; i++, sector += td->o.zone_size >> 9) {
ret = zbc_finish_zone(ld->zdev, sector, 0);
if (ret)
goto err;
}
return 0;
err:
zbc_errno(ld->zdev, &err);
td_verror(td, errno, "zbc_finish_zone failed");
if (err.sk)
log_err("%s: finish zone failed %s:%s\n",
f->file_name,
zbc_sk_str(err.sk), zbc_asc_ascq_str(err.asc_ascq));
return -ret;
}
static int libzbc_get_max_open_zones(struct thread_data *td, struct fio_file *f,
unsigned int *max_open_zones)
{
struct libzbc_data *ld;
int ret;
ret = libzbc_open_dev(td, f, &ld);
if (ret)
return ret;
if (ld->max_open_seq_req == ZBC_NO_LIMIT)
*max_open_zones = 0;
else
*max_open_zones = ld->max_open_seq_req;
return 0;
}
ssize_t libzbc_rw(struct thread_data *td, struct io_u *io_u)
{
struct libzbc_data *ld = td->io_ops_data;
struct fio_file *f = io_u->file;
uint64_t sector = io_u->offset >> 9;
size_t count = io_u->xfer_buflen >> 9;
struct zbc_errno err;
ssize_t ret;
if (io_u->ddir == DDIR_WRITE)
ret = zbc_pwrite(ld->zdev, io_u->xfer_buf, count, sector);
else
ret = zbc_pread(ld->zdev, io_u->xfer_buf, count, sector);
if (ret == count)
return ret;
if (ret > 0) {
log_err("Short %s, len=%zu, ret=%zd\n",
io_u->ddir == DDIR_READ ? "read" : "write",
count << 9, ret << 9);
return -EIO;
}
/* I/O error */
zbc_errno(ld->zdev, &err);
td_verror(td, errno, "libzbc i/o failed");
if (err.sk) {
log_err("%s: op %u offset %llu+%llu failed (%s:%s), err %zd\n",
f->file_name, io_u->ddir,
io_u->offset, io_u->xfer_buflen,
zbc_sk_str(err.sk),
zbc_asc_ascq_str(err.asc_ascq), ret);
} else {
log_err("%s: op %u offset %llu+%llu failed, err %zd\n",
f->file_name, io_u->ddir,
io_u->offset, io_u->xfer_buflen, ret);
}
return -EIO;
}
static enum fio_q_status libzbc_queue(struct thread_data *td, struct io_u *io_u)
{
struct libzbc_data *ld = td->io_ops_data;
struct fio_file *f = io_u->file;
ssize_t ret = 0;
fio_ro_check(td, io_u);
dprint(FD_ZBD, "%p:%s: libzbc queue %llu\n",
td, f->file_name, io_u->offset);
if (io_u->ddir == DDIR_READ || io_u->ddir == DDIR_WRITE) {
ret = libzbc_rw(td, io_u);
} else if (ddir_sync(io_u->ddir)) {
ret = zbc_flush(ld->zdev);
if (ret)
log_err("zbc_flush error %zd\n", ret);
} else if (io_u->ddir == DDIR_TRIM) {
ret = zbd_do_io_u_trim(td, io_u);
if (!ret)
ret = EINVAL;
} else {
log_err("Unsupported operation %u\n", io_u->ddir);
ret = -EINVAL;
}
if (ret < 0)
io_u->error = -ret;
return FIO_Q_COMPLETED;
}
FIO_STATIC struct ioengine_ops ioengine = {
.name = "libzbc",
.version = FIO_IOOPS_VERSION,
.open_file = libzbc_open_file,
.close_file = libzbc_close_file,
.cleanup = libzbc_cleanup,
.invalidate = libzbc_invalidate,
.get_file_size = libzbc_get_file_size,
.get_zoned_model = libzbc_get_zoned_model,
.report_zones = libzbc_report_zones,
.reset_wp = libzbc_reset_wp,
.move_zone_wp = libzbc_move_zone_wp,
.get_max_open_zones = libzbc_get_max_open_zones,
.finish_zone = libzbc_finish_zone,
.queue = libzbc_queue,
.flags = FIO_SYNCIO | FIO_NOEXTEND | FIO_RAWIO,
};
static void fio_init fio_libzbc_register(void)
{
register_ioengine(&ioengine);
}
static void fio_exit fio_libzbc_unregister(void)
{
unregister_ioengine(&ioengine);
}