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