blob: 9ffc5303d2e2883f3421aa4b1c5c0f66d9967fc0 [file] [log] [blame]
/*
* nvme structure declarations and helper functions for the
* io_uring_cmd engine.
*/
#include "nvme.h"
int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u,
struct iovec *iov)
{
struct nvme_data *data = FILE_ENG_DATA(io_u->file);
__u64 slba;
__u32 nlb;
memset(cmd, 0, sizeof(struct nvme_uring_cmd));
if (io_u->ddir == DDIR_READ)
cmd->opcode = nvme_cmd_read;
else if (io_u->ddir == DDIR_WRITE)
cmd->opcode = nvme_cmd_write;
else
return -ENOTSUP;
slba = io_u->offset >> data->lba_shift;
nlb = (io_u->xfer_buflen >> data->lba_shift) - 1;
/* cdw10 and cdw11 represent starting lba */
cmd->cdw10 = slba & 0xffffffff;
cmd->cdw11 = slba >> 32;
/* cdw12 represent number of lba's for read/write */
cmd->cdw12 = nlb;
if (iov) {
iov->iov_base = io_u->xfer_buf;
iov->iov_len = io_u->xfer_buflen;
cmd->addr = (__u64)(uintptr_t)iov;
cmd->data_len = 1;
} else {
cmd->addr = (__u64)(uintptr_t)io_u->xfer_buf;
cmd->data_len = io_u->xfer_buflen;
}
cmd->nsid = data->nsid;
return 0;
}
static int nvme_identify(int fd, __u32 nsid, enum nvme_identify_cns cns,
enum nvme_csi csi, void *data)
{
struct nvme_passthru_cmd cmd = {
.opcode = nvme_admin_identify,
.nsid = nsid,
.addr = (__u64)(uintptr_t)data,
.data_len = NVME_IDENTIFY_DATA_SIZE,
.cdw10 = cns,
.cdw11 = csi << NVME_IDENTIFY_CSI_SHIFT,
.timeout_ms = NVME_DEFAULT_IOCTL_TIMEOUT,
};
return ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd);
}
int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz,
__u64 *nlba)
{
struct nvme_id_ns ns;
int namespace_id;
int fd, err;
if (f->filetype != FIO_TYPE_CHAR) {
log_err("ioengine io_uring_cmd only works with nvme ns "
"generic char devices (/dev/ngXnY)\n");
return 1;
}
fd = open(f->file_name, O_RDONLY);
if (fd < 0)
return -errno;
namespace_id = ioctl(fd, NVME_IOCTL_ID);
if (namespace_id < 0) {
log_err("failed to fetch namespace-id");
close(fd);
return -errno;
}
/*
* Identify namespace to get namespace-id, namespace size in LBA's
* and LBA data size.
*/
err = nvme_identify(fd, namespace_id, NVME_IDENTIFY_CNS_NS,
NVME_CSI_NVM, &ns);
if (err) {
log_err("failed to fetch identify namespace\n");
close(fd);
return err;
}
*nsid = namespace_id;
*lba_sz = 1 << ns.lbaf[(ns.flbas & 0x0f)].ds;
*nlba = ns.nsze;
close(fd);
return 0;
}
int fio_nvme_get_zoned_model(struct thread_data *td, struct fio_file *f,
enum zbd_zoned_model *model)
{
struct nvme_data *data = FILE_ENG_DATA(f);
struct nvme_id_ns ns;
struct nvme_passthru_cmd cmd;
int fd, ret = 0;
if (f->filetype != FIO_TYPE_CHAR)
return -EINVAL;
/* File is not yet opened */
fd = open(f->file_name, O_RDONLY | O_LARGEFILE);
if (fd < 0)
return -errno;
/* Using nvme_id_ns for data as sizes are same */
ret = nvme_identify(fd, data->nsid, NVME_IDENTIFY_CNS_CSI_CTRL,
NVME_CSI_ZNS, &ns);
if (ret) {
*model = ZBD_NONE;
goto out;
}
memset(&cmd, 0, sizeof(struct nvme_passthru_cmd));
/* Using nvme_id_ns for data as sizes are same */
ret = nvme_identify(fd, data->nsid, NVME_IDENTIFY_CNS_CSI_NS,
NVME_CSI_ZNS, &ns);
if (ret) {
*model = ZBD_NONE;
goto out;
}
*model = ZBD_HOST_MANAGED;
out:
close(fd);
return 0;
}
static int nvme_report_zones(int fd, __u32 nsid, __u64 slba, __u32 zras_feat,
__u32 data_len, void *data)
{
struct nvme_passthru_cmd cmd = {
.opcode = nvme_zns_cmd_mgmt_recv,
.nsid = nsid,
.addr = (__u64)(uintptr_t)data,
.data_len = data_len,
.cdw10 = slba & 0xffffffff,
.cdw11 = slba >> 32,
.cdw12 = (data_len >> 2) - 1,
.cdw13 = NVME_ZNS_ZRA_REPORT_ZONES | zras_feat,
.timeout_ms = NVME_DEFAULT_IOCTL_TIMEOUT,
};
return ioctl(fd, NVME_IOCTL_IO_CMD, &cmd);
}
int fio_nvme_report_zones(struct thread_data *td, struct fio_file *f,
uint64_t offset, struct zbd_zone *zbdz,
unsigned int nr_zones)
{
struct nvme_data *data = FILE_ENG_DATA(f);
struct nvme_zone_report *zr;
struct nvme_zns_id_ns zns_ns;
struct nvme_id_ns ns;
unsigned int i = 0, j, zones_fetched = 0;
unsigned int max_zones, zones_chunks = 1024;
int fd, ret = 0;
__u32 zr_len;
__u64 zlen;
/* File is not yet opened */
fd = open(f->file_name, O_RDONLY | O_LARGEFILE);
if (fd < 0)
return -errno;
zones_fetched = 0;
zr_len = sizeof(*zr) + (zones_chunks * sizeof(struct nvme_zns_desc));
zr = calloc(1, zr_len);
if (!zr) {
close(fd);
return -ENOMEM;
}
ret = nvme_identify(fd, data->nsid, NVME_IDENTIFY_CNS_NS,
NVME_CSI_NVM, &ns);
if (ret) {
log_err("%s: nvme_identify_ns failed, err=%d\n", f->file_name,
ret);
goto out;
}
ret = nvme_identify(fd, data->nsid, NVME_IDENTIFY_CNS_CSI_NS,
NVME_CSI_ZNS, &zns_ns);
if (ret) {
log_err("%s: nvme_zns_identify_ns failed, err=%d\n",
f->file_name, ret);
goto out;
}
zlen = zns_ns.lbafe[ns.flbas & 0x0f].zsze << data->lba_shift;
max_zones = (f->real_file_size - offset) / zlen;
if (max_zones < nr_zones)
nr_zones = max_zones;
if (nr_zones < zones_chunks)
zones_chunks = nr_zones;
while (zones_fetched < nr_zones) {
if (zones_fetched + zones_chunks >= nr_zones) {
zones_chunks = nr_zones - zones_fetched;
zr_len = sizeof(*zr) + (zones_chunks * sizeof(struct nvme_zns_desc));
}
ret = nvme_report_zones(fd, data->nsid, offset >> data->lba_shift,
NVME_ZNS_ZRAS_FEAT_ERZ, zr_len, (void *)zr);
if (ret) {
log_err("%s: nvme_zns_report_zones failed, err=%d\n",
f->file_name, ret);
goto out;
}
/* Transform the zone-report */
for (j = 0; j < zr->nr_zones; j++, i++) {
struct nvme_zns_desc *desc = (struct nvme_zns_desc *)&(zr->entries[j]);
zbdz[i].start = desc->zslba << data->lba_shift;
zbdz[i].len = zlen;
zbdz[i].wp = desc->wp << data->lba_shift;
zbdz[i].capacity = desc->zcap << data->lba_shift;
/* Zone Type is stored in first 4 bits. */
switch (desc->zt & 0x0f) {
case NVME_ZONE_TYPE_SEQWRITE_REQ:
zbdz[i].type = ZBD_ZONE_TYPE_SWR;
break;
default:
log_err("%s: invalid type for zone at offset %llu.\n",
f->file_name, desc->zslba);
ret = -EIO;
goto out;
}
/* Zone State is stored in last 4 bits. */
switch (desc->zs >> 4) {
case NVME_ZNS_ZS_EMPTY:
zbdz[i].cond = ZBD_ZONE_COND_EMPTY;
break;
case NVME_ZNS_ZS_IMPL_OPEN:
zbdz[i].cond = ZBD_ZONE_COND_IMP_OPEN;
break;
case NVME_ZNS_ZS_EXPL_OPEN:
zbdz[i].cond = ZBD_ZONE_COND_EXP_OPEN;
break;
case NVME_ZNS_ZS_CLOSED:
zbdz[i].cond = ZBD_ZONE_COND_CLOSED;
break;
case NVME_ZNS_ZS_FULL:
zbdz[i].cond = ZBD_ZONE_COND_FULL;
break;
case NVME_ZNS_ZS_READ_ONLY:
case NVME_ZNS_ZS_OFFLINE:
default:
/* Treat all these conditions as offline (don't use!) */
zbdz[i].cond = ZBD_ZONE_COND_OFFLINE;
zbdz[i].wp = zbdz[i].start;
}
}
zones_fetched += zr->nr_zones;
offset += zr->nr_zones * zlen;
}
ret = zones_fetched;
out:
free(zr);
close(fd);
return ret;
}
int fio_nvme_reset_wp(struct thread_data *td, struct fio_file *f,
uint64_t offset, uint64_t length)
{
struct nvme_data *data = FILE_ENG_DATA(f);
unsigned int nr_zones;
unsigned long long zslba;
int i, fd, ret = 0;
/* If the file is not yet opened, open it for this function. */
fd = f->fd;
if (fd < 0) {
fd = open(f->file_name, O_RDWR | O_LARGEFILE);
if (fd < 0)
return -errno;
}
zslba = offset >> data->lba_shift;
nr_zones = (length + td->o.zone_size - 1) / td->o.zone_size;
for (i = 0; i < nr_zones; i++, zslba += (td->o.zone_size >> data->lba_shift)) {
struct nvme_passthru_cmd cmd = {
.opcode = nvme_zns_cmd_mgmt_send,
.nsid = data->nsid,
.cdw10 = zslba & 0xffffffff,
.cdw11 = zslba >> 32,
.cdw13 = NVME_ZNS_ZSA_RESET,
.addr = (__u64)(uintptr_t)NULL,
.data_len = 0,
.timeout_ms = NVME_DEFAULT_IOCTL_TIMEOUT,
};
ret = ioctl(fd, NVME_IOCTL_IO_CMD, &cmd);
}
if (f->fd < 0)
close(fd);
return -ret;
}
int fio_nvme_get_max_open_zones(struct thread_data *td, struct fio_file *f,
unsigned int *max_open_zones)
{
struct nvme_data *data = FILE_ENG_DATA(f);
struct nvme_zns_id_ns zns_ns;
int fd, ret = 0;
fd = open(f->file_name, O_RDONLY | O_LARGEFILE);
if (fd < 0)
return -errno;
ret = nvme_identify(fd, data->nsid, NVME_IDENTIFY_CNS_CSI_NS,
NVME_CSI_ZNS, &zns_ns);
if (ret) {
log_err("%s: nvme_zns_identify_ns failed, err=%d\n",
f->file_name, ret);
goto out;
}
*max_open_zones = zns_ns.mor + 1;
out:
close(fd);
return ret;
}