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