engines/xnvme: fixes for xnvme ioengine
[fio.git] / engines / xnvme.c
1 /*
2  * fio xNVMe IO Engine
3  *
4  * IO engine using the xNVMe C API.
5  *
6  * See: http://xnvme.io/
7  *
8  * SPDX-License-Identifier: Apache-2.0
9  */
10 #include <stdlib.h>
11 #include <assert.h>
12 #include <libxnvme.h>
13 #include <libxnvme_libconf.h>
14 #include <libxnvme_nvm.h>
15 #include <libxnvme_znd.h>
16 #include <libxnvme_spec_fs.h>
17 #include "fio.h"
18 #include "zbd_types.h"
19 #include "optgroup.h"
20
21 static pthread_mutex_t g_serialize = PTHREAD_MUTEX_INITIALIZER;
22
23 struct xnvme_fioe_fwrap {
24         /* fio file representation */
25         struct fio_file *fio_file;
26
27         /* xNVMe device handle */
28         struct xnvme_dev *dev;
29         /* xNVMe device geometry */
30         const struct xnvme_geo *geo;
31
32         struct xnvme_queue *queue;
33
34         uint32_t ssw;
35         uint32_t lba_nbytes;
36
37         uint8_t _pad[24];
38 };
39 XNVME_STATIC_ASSERT(sizeof(struct xnvme_fioe_fwrap) == 64, "Incorrect size")
40
41 struct xnvme_fioe_data {
42         /* I/O completion queue */
43         struct io_u **iocq;
44
45         /* # of iocq entries; incremented via getevents()/cb_pool() */
46         uint64_t completed;
47
48         /*
49          *  # of errors; incremented when observed on completion via
50          *  getevents()/cb_pool()
51          */
52         uint64_t ecount;
53
54         /* Controller which device/file to select */
55         int32_t prev;
56         int32_t cur;
57
58         /* Number of devices/files for which open() has been called */
59         int64_t nopen;
60         /* Number of devices/files allocated in files[] */
61         uint64_t nallocated;
62
63         struct iovec *iovec;
64
65         uint8_t _pad[8];
66
67         struct xnvme_fioe_fwrap files[];
68 };
69 XNVME_STATIC_ASSERT(sizeof(struct xnvme_fioe_data) == 64, "Incorrect size")
70
71 struct xnvme_fioe_options {
72         void *padding;
73         unsigned int hipri;
74         unsigned int sqpoll_thread;
75         unsigned int xnvme_dev_nsid;
76         unsigned int xnvme_iovec;
77         char *xnvme_be;
78         char *xnvme_async;
79         char *xnvme_sync;
80         char *xnvme_admin;
81 };
82
83 static struct fio_option options[] = {
84         {
85                 .name = "hipri",
86                 .lname = "High Priority",
87                 .type = FIO_OPT_STR_SET,
88                 .off1 = offsetof(struct xnvme_fioe_options, hipri),
89                 .help = "Use polled IO completions",
90                 .category = FIO_OPT_C_ENGINE,
91                 .group = FIO_OPT_G_XNVME,
92         },
93         {
94                 .name = "sqthread_poll",
95                 .lname = "Kernel SQ thread polling",
96                 .type = FIO_OPT_STR_SET,
97                 .off1 = offsetof(struct xnvme_fioe_options, sqpoll_thread),
98                 .help = "Offload submission/completion to kernel thread",
99                 .category = FIO_OPT_C_ENGINE,
100                 .group = FIO_OPT_G_XNVME,
101         },
102         {
103                 .name = "xnvme_be",
104                 .lname = "xNVMe Backend",
105                 .type = FIO_OPT_STR_STORE,
106                 .off1 = offsetof(struct xnvme_fioe_options, xnvme_be),
107                 .help = "Select xNVMe backend [spdk,linux,fbsd]",
108                 .category = FIO_OPT_C_ENGINE,
109                 .group = FIO_OPT_G_XNVME,
110         },
111         {
112                 .name = "xnvme_async",
113                 .lname = "xNVMe Asynchronous command-interface",
114                 .type = FIO_OPT_STR_STORE,
115                 .off1 = offsetof(struct xnvme_fioe_options, xnvme_async),
116                 .help = "Select xNVMe async. interface: [emu,thrpool,io_uring,libaio,posix,nil]",
117                 .category = FIO_OPT_C_ENGINE,
118                 .group = FIO_OPT_G_XNVME,
119         },
120         {
121                 .name = "xnvme_sync",
122                 .lname = "xNVMe Synchronous. command-interface",
123                 .type = FIO_OPT_STR_STORE,
124                 .off1 = offsetof(struct xnvme_fioe_options, xnvme_sync),
125                 .help = "Select xNVMe sync. interface: [nvme,psync]",
126                 .category = FIO_OPT_C_ENGINE,
127                 .group = FIO_OPT_G_XNVME,
128         },
129         {
130                 .name = "xnvme_admin",
131                 .lname = "xNVMe Admin command-interface",
132                 .type = FIO_OPT_STR_STORE,
133                 .off1 = offsetof(struct xnvme_fioe_options, xnvme_admin),
134                 .help = "Select xNVMe admin. cmd-interface: [nvme,block,file_as_ns]",
135                 .category = FIO_OPT_C_ENGINE,
136                 .group = FIO_OPT_G_XNVME,
137         },
138         {
139                 .name = "xnvme_dev_nsid",
140                 .lname = "xNVMe Namespace-Identifier, for user-space NVMe driver",
141                 .type = FIO_OPT_INT,
142                 .off1 = offsetof(struct xnvme_fioe_options, xnvme_dev_nsid),
143                 .help = "xNVMe Namespace-Identifier, for user-space NVMe driver",
144                 .category = FIO_OPT_C_ENGINE,
145                 .group = FIO_OPT_G_XNVME,
146         },
147         {
148                 .name = "xnvme_iovec",
149                 .lname = "Vectored IOs",
150                 .type = FIO_OPT_STR_SET,
151                 .off1 = offsetof(struct xnvme_fioe_options, xnvme_iovec),
152                 .help = "Send vectored IOs",
153                 .category = FIO_OPT_C_ENGINE,
154                 .group = FIO_OPT_G_XNVME,
155         },
156
157         {
158                 .name = NULL,
159         },
160 };
161
162 static void cb_pool(struct xnvme_cmd_ctx *ctx, void *cb_arg)
163 {
164         struct io_u *io_u = cb_arg;
165         struct xnvme_fioe_data *xd = io_u->mmap_data;
166
167         if (xnvme_cmd_ctx_cpl_status(ctx)) {
168                 xnvme_cmd_ctx_pr(ctx, XNVME_PR_DEF);
169                 xd->ecount += 1;
170                 io_u->error = EIO;
171         }
172
173         xd->iocq[xd->completed++] = io_u;
174         xnvme_queue_put_cmd_ctx(ctx->async.queue, ctx);
175 }
176
177 static struct xnvme_opts xnvme_opts_from_fioe(struct thread_data *td)
178 {
179         struct xnvme_fioe_options *o = td->eo;
180         struct xnvme_opts opts = xnvme_opts_default();
181
182         opts.nsid = o->xnvme_dev_nsid;
183         opts.be = o->xnvme_be;
184         opts.async = o->xnvme_async;
185         opts.sync = o->xnvme_sync;
186         opts.admin = o->xnvme_admin;
187
188         opts.poll_io = o->hipri;
189         opts.poll_sq = o->sqpoll_thread;
190
191         opts.direct = td->o.odirect;
192
193         return opts;
194 }
195
196 static void _dev_close(struct thread_data *td, struct xnvme_fioe_fwrap *fwrap)
197 {
198         if (fwrap->dev)
199                 xnvme_queue_term(fwrap->queue);
200
201         xnvme_dev_close(fwrap->dev);
202
203         memset(fwrap, 0, sizeof(*fwrap));
204 }
205
206 static void xnvme_fioe_cleanup(struct thread_data *td)
207 {
208         struct xnvme_fioe_data *xd = NULL;
209         int err;
210
211         if (!td->io_ops_data)
212                 return;
213
214         xd = td->io_ops_data;
215
216         err = pthread_mutex_lock(&g_serialize);
217         if (err)
218                 log_err("ioeng->cleanup(): pthread_mutex_lock(), err(%d)\n", err);
219                 /* NOTE: not returning here */
220
221         for (uint64_t i = 0; i < xd->nallocated; ++i)
222                 _dev_close(td, &xd->files[i]);
223
224         if (!err) {
225                 err = pthread_mutex_unlock(&g_serialize);
226                 if (err)
227                         log_err("ioeng->cleanup(): pthread_mutex_unlock(), err(%d)\n", err);
228         }
229
230         free(xd->iocq);
231         free(xd->iovec);
232         free(xd);
233         td->io_ops_data = NULL;
234 }
235
236 /**
237  * Helper function setting up device handles as addressed by the naming
238  * convention of the given `fio_file` filename.
239  *
240  * Checks thread-options for explicit control of asynchronous implementation via
241  * the ``--xnvme_async={thrpool,emu,posix,io_uring,libaio,nil}``.
242  */
243 static int _dev_open(struct thread_data *td, struct fio_file *f)
244 {
245         struct xnvme_opts opts = xnvme_opts_from_fioe(td);
246         struct xnvme_fioe_data *xd = td->io_ops_data;
247         struct xnvme_fioe_fwrap *fwrap;
248         int flags = 0;
249         int err;
250
251         if (f->fileno > (int)xd->nallocated) {
252                 log_err("ioeng->_dev_open(%s): invalid assumption\n", f->file_name);
253                 return 1;
254         }
255
256         fwrap = &xd->files[f->fileno];
257
258         err = pthread_mutex_lock(&g_serialize);
259         if (err) {
260                 log_err("ioeng->_dev_open(%s): pthread_mutex_lock(), err(%d)\n", f->file_name,
261                         err);
262                 return -err;
263         }
264
265         fwrap->dev = xnvme_dev_open(f->file_name, &opts);
266         if (!fwrap->dev) {
267                 log_err("ioeng->_dev_open(%s): xnvme_dev_open(), err(%d)\n", f->file_name, errno);
268                 goto failure;
269         }
270         fwrap->geo = xnvme_dev_get_geo(fwrap->dev);
271
272         if (xnvme_queue_init(fwrap->dev, td->o.iodepth, flags, &(fwrap->queue))) {
273                 log_err("ioeng->_dev_open(%s): xnvme_queue_init(), err(?)\n", f->file_name);
274                 goto failure;
275         }
276         xnvme_queue_set_cb(fwrap->queue, cb_pool, NULL);
277
278         fwrap->ssw = xnvme_dev_get_ssw(fwrap->dev);
279         fwrap->lba_nbytes = fwrap->geo->lba_nbytes;
280
281         fwrap->fio_file = f;
282         fwrap->fio_file->filetype = FIO_TYPE_BLOCK;
283         fwrap->fio_file->real_file_size = fwrap->geo->tbytes;
284         fio_file_set_size_known(fwrap->fio_file);
285
286         err = pthread_mutex_unlock(&g_serialize);
287         if (err)
288                 log_err("ioeng->_dev_open(%s): pthread_mutex_unlock(), err(%d)\n", f->file_name,
289                         err);
290
291         return 0;
292
293 failure:
294         xnvme_queue_term(fwrap->queue);
295         xnvme_dev_close(fwrap->dev);
296
297         err = pthread_mutex_unlock(&g_serialize);
298         if (err)
299                 log_err("ioeng->_dev_open(%s): pthread_mutex_unlock(), err(%d)\n", f->file_name,
300                         err);
301
302         return 1;
303 }
304
305 static int xnvme_fioe_init(struct thread_data *td)
306 {
307         struct xnvme_fioe_data *xd = NULL;
308         struct fio_file *f;
309         unsigned int i;
310
311         if (!td->o.use_thread) {
312                 log_err("ioeng->init(): --thread=1 is required\n");
313                 return 1;
314         }
315
316         /* Allocate xd and iocq */
317         xd = calloc(1, sizeof(*xd) + sizeof(*xd->files) * td->o.nr_files);
318         if (!xd) {
319                 log_err("ioeng->init(): !calloc(), err(%d)\n", errno);
320                 return 1;
321         }
322
323         xd->iocq = calloc(td->o.iodepth, sizeof(struct io_u *));
324         if (!xd->iocq) {
325                 free(xd);
326                 log_err("ioeng->init(): !calloc(xd->iocq), err(%d)\n", errno);
327                 return 1;
328         }
329
330         xd->iovec = calloc(td->o.iodepth, sizeof(*xd->iovec));
331         if (!xd->iovec) {
332                 free(xd->iocq);
333                 free(xd);
334                 log_err("ioeng->init(): !calloc(xd->iovec), err(%d)\n", errno);
335                 return 1;
336         }
337
338         xd->prev = -1;
339         td->io_ops_data = xd;
340
341         for_each_file(td, f, i)
342         {
343                 if (_dev_open(td, f)) {
344                         /*
345                          * Note: We are not freeing xd, iocq and iovec. This
346                          * will be done as part of cleanup routine.
347                          */
348                         log_err("ioeng->init(): failed; _dev_open(%s)\n", f->file_name);
349                         return 1;
350                 }
351
352                 ++(xd->nallocated);
353         }
354
355         if (xd->nallocated != td->o.nr_files) {
356                 log_err("ioeng->init(): failed; nallocated != td->o.nr_files\n");
357                 return 1;
358         }
359
360         return 0;
361 }
362
363 /* NOTE: using the first device for buffer-allocators) */
364 static int xnvme_fioe_iomem_alloc(struct thread_data *td, size_t total_mem)
365 {
366         struct xnvme_fioe_data *xd = td->io_ops_data;
367         struct xnvme_fioe_fwrap *fwrap = &xd->files[0];
368
369         if (!fwrap->dev) {
370                 log_err("ioeng->iomem_alloc(): failed; no dev-handle\n");
371                 return 1;
372         }
373
374         td->orig_buffer = xnvme_buf_alloc(fwrap->dev, total_mem);
375
376         return td->orig_buffer == NULL;
377 }
378
379 /* NOTE: using the first device for buffer-allocators) */
380 static void xnvme_fioe_iomem_free(struct thread_data *td)
381 {
382         struct xnvme_fioe_data *xd = NULL;
383         struct xnvme_fioe_fwrap *fwrap = NULL;
384
385         if (!td->io_ops_data)
386                 return;
387
388         xd = td->io_ops_data;
389         fwrap = &xd->files[0];
390
391         if (!fwrap->dev) {
392                 log_err("ioeng->iomem_free(): failed no dev-handle\n");
393                 return;
394         }
395
396         xnvme_buf_free(fwrap->dev, td->orig_buffer);
397 }
398
399 static int xnvme_fioe_io_u_init(struct thread_data *td, struct io_u *io_u)
400 {
401         io_u->mmap_data = td->io_ops_data;
402
403         return 0;
404 }
405
406 static void xnvme_fioe_io_u_free(struct thread_data *td, struct io_u *io_u)
407 {
408         io_u->mmap_data = NULL;
409 }
410
411 static struct io_u *xnvme_fioe_event(struct thread_data *td, int event)
412 {
413         struct xnvme_fioe_data *xd = td->io_ops_data;
414
415         assert(event >= 0);
416         assert((unsigned)event < xd->completed);
417
418         return xd->iocq[event];
419 }
420
421 static int xnvme_fioe_getevents(struct thread_data *td, unsigned int min, unsigned int max,
422                                 const struct timespec *t)
423 {
424         struct xnvme_fioe_data *xd = td->io_ops_data;
425         struct xnvme_fioe_fwrap *fwrap = NULL;
426         int nfiles = xd->nallocated;
427         int err = 0;
428
429         if (xd->prev != -1 && ++xd->prev < nfiles) {
430                 fwrap = &xd->files[xd->prev];
431                 xd->cur = xd->prev;
432         }
433
434         xd->completed = 0;
435         for (;;) {
436                 if (fwrap == NULL || xd->cur == nfiles) {
437                         fwrap = &xd->files[0];
438                         xd->cur = 0;
439                 }
440
441                 while (fwrap != NULL && xd->cur < nfiles && err >= 0) {
442                         err = xnvme_queue_poke(fwrap->queue, max - xd->completed);
443                         if (err < 0) {
444                                 switch (err) {
445                                 case -EBUSY:
446                                 case -EAGAIN:
447                                         usleep(1);
448                                         break;
449
450                                 default:
451                                         log_err("ioeng->getevents(): unhandled IO error\n");
452                                         assert(false);
453                                         return 0;
454                                 }
455                         }
456                         if (xd->completed >= min) {
457                                 xd->prev = xd->cur;
458                                 return xd->completed;
459                         }
460                         xd->cur++;
461                         fwrap = &xd->files[xd->cur];
462
463                         if (err < 0) {
464                                 switch (err) {
465                                 case -EBUSY:
466                                 case -EAGAIN:
467                                         usleep(1);
468                                         break;
469                                 }
470                         }
471                 }
472         }
473
474         xd->cur = 0;
475
476         return xd->completed;
477 }
478
479 static enum fio_q_status xnvme_fioe_queue(struct thread_data *td, struct io_u *io_u)
480 {
481         struct xnvme_fioe_data *xd = td->io_ops_data;
482         struct xnvme_fioe_fwrap *fwrap;
483         struct xnvme_cmd_ctx *ctx;
484         uint32_t nsid;
485         uint64_t slba;
486         uint16_t nlb;
487         int err;
488         bool vectored_io = ((struct xnvme_fioe_options *)td->eo)->xnvme_iovec;
489
490         fio_ro_check(td, io_u);
491
492         fwrap = &xd->files[io_u->file->fileno];
493         nsid = xnvme_dev_get_nsid(fwrap->dev);
494
495         slba = io_u->offset >> fwrap->ssw;
496         nlb = (io_u->xfer_buflen >> fwrap->ssw) - 1;
497
498         ctx = xnvme_queue_get_cmd_ctx(fwrap->queue);
499         ctx->async.cb_arg = io_u;
500
501         ctx->cmd.common.nsid = nsid;
502         ctx->cmd.nvm.slba = slba;
503         ctx->cmd.nvm.nlb = nlb;
504
505         switch (io_u->ddir) {
506         case DDIR_READ:
507                 ctx->cmd.common.opcode = XNVME_SPEC_NVM_OPC_READ;
508                 break;
509
510         case DDIR_WRITE:
511                 ctx->cmd.common.opcode = XNVME_SPEC_NVM_OPC_WRITE;
512                 break;
513
514         default:
515                 log_err("ioeng->queue(): ENOSYS: %u\n", io_u->ddir);
516                 xnvme_queue_put_cmd_ctx(ctx->async.queue, ctx);
517
518                 io_u->error = ENOSYS;
519                 assert(false);
520                 return FIO_Q_COMPLETED;
521         }
522
523         if (vectored_io) {
524                 xd->iovec[io_u->index].iov_base = io_u->xfer_buf;
525                 xd->iovec[io_u->index].iov_len = io_u->xfer_buflen;
526
527                 err = xnvme_cmd_passv(ctx, &xd->iovec[io_u->index], 1, io_u->xfer_buflen, NULL, 0,
528                                       0);
529         } else {
530                 err = xnvme_cmd_pass(ctx, io_u->xfer_buf, io_u->xfer_buflen, NULL, 0);
531         }
532         switch (err) {
533         case 0:
534                 return FIO_Q_QUEUED;
535
536         case -EBUSY:
537         case -EAGAIN:
538                 xnvme_queue_put_cmd_ctx(ctx->async.queue, ctx);
539                 return FIO_Q_BUSY;
540
541         default:
542                 log_err("ioeng->queue(): err: '%d'\n", err);
543
544                 xnvme_queue_put_cmd_ctx(ctx->async.queue, ctx);
545
546                 io_u->error = abs(err);
547                 assert(false);
548                 return FIO_Q_COMPLETED;
549         }
550 }
551
552 static int xnvme_fioe_close(struct thread_data *td, struct fio_file *f)
553 {
554         struct xnvme_fioe_data *xd = td->io_ops_data;
555
556         dprint(FD_FILE, "xnvme close %s -- nopen: %ld\n", f->file_name, xd->nopen);
557
558         --(xd->nopen);
559
560         return 0;
561 }
562
563 static int xnvme_fioe_open(struct thread_data *td, struct fio_file *f)
564 {
565         struct xnvme_fioe_data *xd = td->io_ops_data;
566
567         dprint(FD_FILE, "xnvme open %s -- nopen: %ld\n", f->file_name, xd->nopen);
568
569         if (f->fileno > (int)xd->nallocated) {
570                 log_err("ioeng->open(): f->fileno > xd->nallocated; invalid assumption\n");
571                 return 1;
572         }
573         if (xd->files[f->fileno].fio_file != f) {
574                 log_err("ioeng->open(): fio_file != f; invalid assumption\n");
575                 return 1;
576         }
577
578         ++(xd->nopen);
579
580         return 0;
581 }
582
583 static int xnvme_fioe_invalidate(struct thread_data *td, struct fio_file *f)
584 {
585         /* Consider only doing this with be:spdk */
586         return 0;
587 }
588
589 static int xnvme_fioe_get_max_open_zones(struct thread_data *td, struct fio_file *f,
590                                          unsigned int *max_open_zones)
591 {
592         struct xnvme_opts opts = xnvme_opts_from_fioe(td);
593         struct xnvme_dev *dev;
594         const struct xnvme_spec_znd_idfy_ns *zns;
595         int err = 0, err_lock;
596
597         if (f->filetype != FIO_TYPE_FILE && f->filetype != FIO_TYPE_BLOCK &&
598             f->filetype != FIO_TYPE_CHAR) {
599                 log_info("ioeng->get_max_open_zoned(): ignoring filetype: %d\n", f->filetype);
600                 return 0;
601         }
602         err_lock = pthread_mutex_lock(&g_serialize);
603         if (err_lock) {
604                 log_err("ioeng->get_max_open_zones(): pthread_mutex_lock(), err(%d)\n", err_lock);
605                 return -err_lock;
606         }
607
608         dev = xnvme_dev_open(f->file_name, &opts);
609         if (!dev) {
610                 log_err("ioeng->get_max_open_zones(): xnvme_dev_open(), err(%d)\n", err_lock);
611                 err = -errno;
612                 goto exit;
613         }
614         if (xnvme_dev_get_geo(dev)->type != XNVME_GEO_ZONED) {
615                 errno = EINVAL;
616                 err = -errno;
617                 goto exit;
618         }
619
620         zns = (void *)xnvme_dev_get_ns_css(dev);
621         if (!zns) {
622                 log_err("ioeng->get_max_open_zones(): xnvme_dev_get_ns_css(), err(%d)\n", errno);
623                 err = -errno;
624                 goto exit;
625         }
626
627         /*
628          * intentional overflow as the value is zero-based and NVMe
629          * defines 0xFFFFFFFF as unlimited thus overflowing to 0 which
630          * is how fio indicates unlimited and otherwise just converting
631          * to one-based.
632          */
633         *max_open_zones = zns->mor + 1;
634
635 exit:
636         xnvme_dev_close(dev);
637         err_lock = pthread_mutex_unlock(&g_serialize);
638         if (err_lock)
639                 log_err("ioeng->get_max_open_zones(): pthread_mutex_unlock(), err(%d)\n",
640                         err_lock);
641
642         return err;
643 }
644
645 /**
646  * Currently, this function is called before of I/O engine initialization, so,
647  * we cannot consult the file-wrapping done when 'fioe' initializes.
648  * Instead we just open based on the given filename.
649  *
650  * TODO: unify the different setup methods, consider keeping the handle around,
651  * and consider how to support the --be option in this usecase
652  */
653 static int xnvme_fioe_get_zoned_model(struct thread_data *td, struct fio_file *f,
654                                       enum zbd_zoned_model *model)
655 {
656         struct xnvme_opts opts = xnvme_opts_from_fioe(td);
657         struct xnvme_dev *dev;
658         int err = 0, err_lock;
659
660         if (f->filetype != FIO_TYPE_FILE && f->filetype != FIO_TYPE_BLOCK &&
661             f->filetype != FIO_TYPE_CHAR) {
662                 log_info("ioeng->get_zoned_model(): ignoring filetype: %d\n", f->filetype);
663                 return -EINVAL;
664         }
665
666         err = pthread_mutex_lock(&g_serialize);
667         if (err) {
668                 log_err("ioeng->get_zoned_model(): pthread_mutex_lock(), err(%d)\n", err);
669                 return -err;
670         }
671
672         dev = xnvme_dev_open(f->file_name, &opts);
673         if (!dev) {
674                 log_err("ioeng->get_zoned_model(): xnvme_dev_open(%s) failed, errno: %d\n",
675                         f->file_name, errno);
676                 err = -errno;
677                 goto exit;
678         }
679
680         switch (xnvme_dev_get_geo(dev)->type) {
681         case XNVME_GEO_UNKNOWN:
682                 dprint(FD_ZBD, "%s: got 'unknown', assigning ZBD_NONE\n", f->file_name);
683                 *model = ZBD_NONE;
684                 break;
685
686         case XNVME_GEO_CONVENTIONAL:
687                 dprint(FD_ZBD, "%s: got 'conventional', assigning ZBD_NONE\n", f->file_name);
688                 *model = ZBD_NONE;
689                 break;
690
691         case XNVME_GEO_ZONED:
692                 dprint(FD_ZBD, "%s: got 'zoned', assigning ZBD_HOST_MANAGED\n", f->file_name);
693                 *model = ZBD_HOST_MANAGED;
694                 break;
695
696         default:
697                 dprint(FD_ZBD, "%s: hit-default, assigning ZBD_NONE\n", f->file_name);
698                 *model = ZBD_NONE;
699                 errno = EINVAL;
700                 err = -errno;
701                 break;
702         }
703
704 exit:
705         xnvme_dev_close(dev);
706
707         err_lock = pthread_mutex_unlock(&g_serialize);
708         if (err_lock)
709                 log_err("ioeng->get_zoned_model(): pthread_mutex_unlock(), err(%d)\n", err_lock);
710
711         return err;
712 }
713
714 /**
715  * Fills the given ``zbdz`` with at most ``nr_zones`` zone-descriptors.
716  *
717  * The implementation converts the NVMe Zoned Command Set log-pages for Zone
718  * descriptors into the Linux Kernel Zoned Block Report format.
719  *
720  * NOTE: This function is called before I/O engine initialization, that is,
721  * before ``_dev_open`` has been called and file-wrapping is setup. Thus is has
722  * to do the ``_dev_open`` itself, and shut it down again once it is done
723  * retrieving the log-pages and converting them to the report format.
724  *
725  * TODO: unify the different setup methods, consider keeping the handle around,
726  * and consider how to support the --async option in this usecase
727  */
728 static int xnvme_fioe_report_zones(struct thread_data *td, struct fio_file *f, uint64_t offset,
729                                    struct zbd_zone *zbdz, unsigned int nr_zones)
730 {
731         struct xnvme_opts opts = xnvme_opts_from_fioe(td);
732         const struct xnvme_spec_znd_idfy_lbafe *lbafe = NULL;
733         struct xnvme_dev *dev = NULL;
734         const struct xnvme_geo *geo = NULL;
735         struct xnvme_znd_report *rprt = NULL;
736         uint32_t ssw;
737         uint64_t slba;
738         unsigned int limit = 0;
739         int err = 0, err_lock;
740
741         dprint(FD_ZBD, "%s: report_zones() offset: %zu, nr_zones: %u\n", f->file_name, offset,
742                nr_zones);
743
744         err = pthread_mutex_lock(&g_serialize);
745         if (err) {
746                 log_err("ioeng->report_zones(%s): pthread_mutex_lock(), err(%d)\n", f->file_name,
747                         err);
748                 return -err;
749         }
750
751         dev = xnvme_dev_open(f->file_name, &opts);
752         if (!dev) {
753                 log_err("ioeng->report_zones(%s): xnvme_dev_open(), err(%d)\n", f->file_name,
754                         errno);
755                 goto exit;
756         }
757
758         geo = xnvme_dev_get_geo(dev);
759         ssw = xnvme_dev_get_ssw(dev);
760         lbafe = xnvme_znd_dev_get_lbafe(dev);
761
762         limit = nr_zones > geo->nzone ? geo->nzone : nr_zones;
763
764         dprint(FD_ZBD, "%s: limit: %u\n", f->file_name, limit);
765
766         slba = ((offset >> ssw) / geo->nsect) * geo->nsect;
767
768         rprt = xnvme_znd_report_from_dev(dev, slba, limit, 0);
769         if (!rprt) {
770                 log_err("ioeng->report_zones(%s): xnvme_znd_report_from_dev(), err(%d)\n",
771                         f->file_name, errno);
772                 err = -errno;
773                 goto exit;
774         }
775         if (rprt->nentries != limit) {
776                 log_err("ioeng->report_zones(%s): nentries != nr_zones\n", f->file_name);
777                 err = 1;
778                 goto exit;
779         }
780         if (offset > geo->tbytes) {
781                 log_err("ioeng->report_zones(%s): out-of-bounds\n", f->file_name);
782                 goto exit;
783         }
784
785         /* Transform the zone-report */
786         for (uint32_t idx = 0; idx < rprt->nentries; ++idx) {
787                 struct xnvme_spec_znd_descr *descr = XNVME_ZND_REPORT_DESCR(rprt, idx);
788
789                 zbdz[idx].start = descr->zslba << ssw;
790                 zbdz[idx].len = lbafe->zsze << ssw;
791                 zbdz[idx].capacity = descr->zcap << ssw;
792                 zbdz[idx].wp = descr->wp << ssw;
793
794                 switch (descr->zt) {
795                 case XNVME_SPEC_ZND_TYPE_SEQWR:
796                         zbdz[idx].type = ZBD_ZONE_TYPE_SWR;
797                         break;
798
799                 default:
800                         log_err("ioeng->report_zones(%s): invalid type for zone at offset(%zu)\n",
801                                 f->file_name, zbdz[idx].start);
802                         err = -EIO;
803                         goto exit;
804                 }
805
806                 switch (descr->zs) {
807                 case XNVME_SPEC_ZND_STATE_EMPTY:
808                         zbdz[idx].cond = ZBD_ZONE_COND_EMPTY;
809                         break;
810                 case XNVME_SPEC_ZND_STATE_IOPEN:
811                         zbdz[idx].cond = ZBD_ZONE_COND_IMP_OPEN;
812                         break;
813                 case XNVME_SPEC_ZND_STATE_EOPEN:
814                         zbdz[idx].cond = ZBD_ZONE_COND_EXP_OPEN;
815                         break;
816                 case XNVME_SPEC_ZND_STATE_CLOSED:
817                         zbdz[idx].cond = ZBD_ZONE_COND_CLOSED;
818                         break;
819                 case XNVME_SPEC_ZND_STATE_FULL:
820                         zbdz[idx].cond = ZBD_ZONE_COND_FULL;
821                         break;
822
823                 case XNVME_SPEC_ZND_STATE_RONLY:
824                 case XNVME_SPEC_ZND_STATE_OFFLINE:
825                 default:
826                         zbdz[idx].cond = ZBD_ZONE_COND_OFFLINE;
827                         break;
828                 }
829         }
830
831 exit:
832         xnvme_buf_virt_free(rprt);
833
834         xnvme_dev_close(dev);
835
836         err_lock = pthread_mutex_unlock(&g_serialize);
837         if (err_lock)
838                 log_err("ioeng->report_zones(): pthread_mutex_unlock(), err: %d\n", err_lock);
839
840         dprint(FD_ZBD, "err: %d, nr_zones: %d\n", err, (int)nr_zones);
841
842         return err ? err : (int)limit;
843 }
844
845 /**
846  * NOTE: This function may get called before I/O engine initialization, that is,
847  * before ``_dev_open`` has been called and file-wrapping is setup. In such
848  * case it has to do ``_dev_open`` itself, and shut it down again once it is
849  * done resetting write pointer of zones.
850  */
851 static int xnvme_fioe_reset_wp(struct thread_data *td, struct fio_file *f, uint64_t offset,
852                                uint64_t length)
853 {
854         struct xnvme_opts opts = xnvme_opts_from_fioe(td);
855         struct xnvme_fioe_data *xd = NULL;
856         struct xnvme_fioe_fwrap *fwrap = NULL;
857         struct xnvme_dev *dev = NULL;
858         const struct xnvme_geo *geo = NULL;
859         uint64_t first, last;
860         uint32_t ssw;
861         uint32_t nsid;
862         int err = 0, err_lock;
863
864         if (td->io_ops_data) {
865                 xd = td->io_ops_data;
866                 fwrap = &xd->files[f->fileno];
867
868                 assert(fwrap->dev);
869                 assert(fwrap->geo);
870
871                 dev = fwrap->dev;
872                 geo = fwrap->geo;
873                 ssw = fwrap->ssw;
874         } else {
875                 err = pthread_mutex_lock(&g_serialize);
876                 if (err) {
877                         log_err("ioeng->reset_wp(): pthread_mutex_lock(), err(%d)\n", err);
878                         return -err;
879                 }
880
881                 dev = xnvme_dev_open(f->file_name, &opts);
882                 if (!dev) {
883                         log_err("ioeng->reset_wp(): xnvme_dev_open(%s) failed, errno(%d)\n",
884                                 f->file_name, errno);
885                         goto exit;
886                 }
887                 geo = xnvme_dev_get_geo(dev);
888                 ssw = xnvme_dev_get_ssw(dev);
889         }
890
891         nsid = xnvme_dev_get_nsid(dev);
892
893         first = ((offset >> ssw) / geo->nsect) * geo->nsect;
894         last = (((offset + length) >> ssw) / geo->nsect) * geo->nsect;
895         dprint(FD_ZBD, "first: 0x%lx, last: 0x%lx\n", first, last);
896
897         for (uint64_t zslba = first; zslba < last; zslba += geo->nsect) {
898                 struct xnvme_cmd_ctx ctx = xnvme_cmd_ctx_from_dev(dev);
899
900                 if (zslba >= (geo->nsect * geo->nzone)) {
901                         log_err("ioeng->reset_wp(): out-of-bounds\n");
902                         err = 0;
903                         break;
904                 }
905
906                 err = xnvme_znd_mgmt_send(&ctx, nsid, zslba, false,
907                                           XNVME_SPEC_ZND_CMD_MGMT_SEND_RESET, 0x0, NULL);
908                 if (err || xnvme_cmd_ctx_cpl_status(&ctx)) {
909                         err = err ? err : -EIO;
910                         log_err("ioeng->reset_wp(): err(%d), sc(%d)", err, ctx.cpl.status.sc);
911                         goto exit;
912                 }
913         }
914
915 exit:
916         if (!td->io_ops_data) {
917                 xnvme_dev_close(dev);
918
919                 err_lock = pthread_mutex_unlock(&g_serialize);
920                 if (err_lock)
921                         log_err("ioeng->reset_wp(): pthread_mutex_unlock(), err(%d)\n", err_lock);
922         }
923
924         return err;
925 }
926
927 static int xnvme_fioe_get_file_size(struct thread_data *td, struct fio_file *f)
928 {
929         struct xnvme_opts opts = xnvme_opts_from_fioe(td);
930         struct xnvme_dev *dev;
931         int ret = 0, err;
932
933         if (fio_file_size_known(f))
934                 return 0;
935
936         ret = pthread_mutex_lock(&g_serialize);
937         if (ret) {
938                 log_err("ioeng->reset_wp(): pthread_mutex_lock(), err(%d)\n", ret);
939                 return -ret;
940         }
941
942         dev = xnvme_dev_open(f->file_name, &opts);
943         if (!dev) {
944                 log_err("%s: failed retrieving device handle, errno: %d\n", f->file_name, errno);
945                 ret = -errno;
946                 goto exit;
947         }
948
949         f->real_file_size = xnvme_dev_get_geo(dev)->tbytes;
950         fio_file_set_size_known(f);
951         f->filetype = FIO_TYPE_BLOCK;
952
953 exit:
954         xnvme_dev_close(dev);
955         err = pthread_mutex_unlock(&g_serialize);
956         if (err)
957                 log_err("ioeng->reset_wp(): pthread_mutex_unlock(), err(%d)\n", err);
958
959         return ret;
960 }
961
962 FIO_STATIC struct ioengine_ops ioengine = {
963         .name = "xnvme",
964         .version = FIO_IOOPS_VERSION,
965         .options = options,
966         .option_struct_size = sizeof(struct xnvme_fioe_options),
967         .flags = FIO_DISKLESSIO | FIO_NODISKUTIL | FIO_NOEXTEND | FIO_MEMALIGN | FIO_RAWIO,
968
969         .cleanup = xnvme_fioe_cleanup,
970         .init = xnvme_fioe_init,
971
972         .iomem_free = xnvme_fioe_iomem_free,
973         .iomem_alloc = xnvme_fioe_iomem_alloc,
974
975         .io_u_free = xnvme_fioe_io_u_free,
976         .io_u_init = xnvme_fioe_io_u_init,
977
978         .event = xnvme_fioe_event,
979         .getevents = xnvme_fioe_getevents,
980         .queue = xnvme_fioe_queue,
981
982         .close_file = xnvme_fioe_close,
983         .open_file = xnvme_fioe_open,
984         .get_file_size = xnvme_fioe_get_file_size,
985
986         .invalidate = xnvme_fioe_invalidate,
987         .get_max_open_zones = xnvme_fioe_get_max_open_zones,
988         .get_zoned_model = xnvme_fioe_get_zoned_model,
989         .report_zones = xnvme_fioe_report_zones,
990         .reset_wp = xnvme_fioe_reset_wp,
991 };
992
993 static void fio_init fio_xnvme_register(void)
994 {
995         register_ioengine(&ioengine);
996 }
997
998 static void fio_exit fio_xnvme_unregister(void)
999 {
1000         unregister_ioengine(&ioengine);
1001 }