diff options
Diffstat (limited to 'engines')
-rw-r--r-- | engines/libzbc.c | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/engines/libzbc.c b/engines/libzbc.c new file mode 100644 index 00000000..8c682de6 --- /dev/null +++ b/engines/libzbc.c @@ -0,0 +1,422 @@ +/* + * 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" + +struct libzbc_data { + struct zbc_device *zdev; + enum zbc_dev_model model; + uint64_t nr_sectors; +}; + +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; + + 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)) { + if (!read_only) + flags |= O_RDWR; + } else if (td_read(td)) { + if (f->filetype == FIO_TYPE_CHAR && !read_only) + flags |= O_RDWR; + else + flags |= O_RDONLY; + } else if (td_trim(td)) { + td_verror(td, EINVAL, "libzbc does not support trim"); + log_err("%s: libzbc does not support trim\n", + f->file_name); + return -EINVAL; + } + + if (td->o.oatomic) { + td_verror(td, EINVAL, "libzbc does not support O_ATOMIC"); + log_err("%s: libzbc does not support O_ATOMIC\n", + f->file_name); + return -EINVAL; + } + + 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); + return ret; + } + + ret = libzbc_get_dev_info(ld, f); + if (ret) { + zbc_close(ld->zdev); + free(ld); + return ret; + } + + td->io_ops_data = ld; +out: + if (p_ld) + *p_ld = ld; + + return 0; +} + +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) { + *model = ZBD_IGNORE; + return 0; + } + + 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; + + 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; + break; + } + } + + 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; +} + +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) { + log_err("Unsupported operation %u\n", io_u->ddir); + ret = -EINVAL; + } + if (ret < 0) + io_u->error = -ret; + + return FIO_Q_COMPLETED; +} + +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, + .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); +} |