1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2025 Oxide Computer Company
14 */
15
16 /*
17 * Common utilities for libnvme tests.
18 */
19
20 #include <err.h>
21 #include <stdlib.h>
22 #include <time.h>
23
24 #include "libnvme_test_common.h"
25
26 /*
27 * For any test linked against libumem, ensure that umem debugging is enabled by
28 * default. Many tests use umem_setmtbf() and we need to make sure there is no
29 * per-thread cache.
30 */
31 const char *
_umem_debug_init(void)32 _umem_debug_init(void)
33 {
34 return ("default,verbose");
35 }
36
37 const char *
_umem_logging_init(void)38 _umem_logging_init(void)
39 {
40 return ("fail,contents");
41 }
42
43 static void
libnvme_test_hdl_vwarn(nvme_t * nvme,const char * fmt,va_list ap)44 libnvme_test_hdl_vwarn(nvme_t *nvme, const char *fmt, va_list ap)
45 {
46 (void) fprintf(stderr, "TEST FAILED: ");
47 (void) vfprintf(stderr, fmt, ap);
48 (void) fprintf(stderr, ": %s: %s (libnvme: 0x%x, sys: %d)\n",
49 nvme_errmsg(nvme), nvme_errtostr(nvme, nvme_err(nvme)),
50 nvme_err(nvme), nvme_syserr(nvme));
51 }
52
53 static void
libnvme_test_ctrl_vwarn(nvme_ctrl_t * ctrl,const char * fmt,va_list ap)54 libnvme_test_ctrl_vwarn(nvme_ctrl_t *ctrl, const char *fmt, va_list ap)
55 {
56 (void) fprintf(stderr, "TEST FAILED: ");
57 (void) vfprintf(stderr, fmt, ap);
58 (void) fprintf(stderr, ": %s: %s (libnvme: 0x%x, sys: %d)\n",
59 nvme_ctrl_errmsg(ctrl), nvme_ctrl_errtostr(ctrl,
60 nvme_ctrl_err(ctrl)), nvme_ctrl_err(ctrl), nvme_ctrl_syserr(ctrl));
61 }
62
63 static void
libnvme_test_ctrl_info_vwarn(nvme_ctrl_info_t * info,const char * fmt,va_list ap)64 libnvme_test_ctrl_info_vwarn(nvme_ctrl_info_t *info, const char *fmt,
65 va_list ap)
66 {
67 (void) fprintf(stderr, "TEST FAILED: ");
68 (void) vfprintf(stderr, fmt, ap);
69 (void) fprintf(stderr, ": %s: %s (libnvme info: 0x%x, sys: %d)\n",
70 nvme_ctrl_info_errmsg(info), nvme_ctrl_info_errtostr(info,
71 nvme_ctrl_info_err(info)), nvme_ctrl_info_err(info),
72 nvme_ctrl_info_syserr(info));
73 }
74
75 static void
libnvme_test_ns_info_vwarn(nvme_ns_info_t * info,const char * fmt,va_list ap)76 libnvme_test_ns_info_vwarn(nvme_ns_info_t *info, const char *fmt,
77 va_list ap)
78 {
79 (void) fprintf(stderr, "TEST FAILED: ");
80 (void) vfprintf(stderr, fmt, ap);
81 (void) fprintf(stderr, ": %s: %s (libnvme info: 0x%x, sys: %d)\n",
82 nvme_ns_info_errmsg(info), nvme_ns_info_errtostr(info,
83 nvme_ns_info_err(info)), nvme_ns_info_err(info),
84 nvme_ns_info_syserr(info));
85 }
86
87 void
libnvme_test_hdl_warn(nvme_t * nvme,const char * fmt,...)88 libnvme_test_hdl_warn(nvme_t *nvme, const char *fmt, ...)
89 {
90 va_list ap;
91
92 va_start(ap, fmt);
93 libnvme_test_hdl_vwarn(nvme, fmt, ap);
94 va_end(ap);
95 }
96
97 void __NORETURN
libnvme_test_hdl_fatal(nvme_t * nvme,const char * fmt,...)98 libnvme_test_hdl_fatal(nvme_t *nvme, const char *fmt, ...)
99 {
100 va_list ap;
101
102 va_start(ap, fmt);
103 libnvme_test_hdl_vwarn(nvme, fmt, ap);
104 va_end(ap);
105
106 exit(EXIT_FAILURE);
107 }
108
109 void
libnvme_test_ctrl_warn(nvme_ctrl_t * ctrl,const char * fmt,...)110 libnvme_test_ctrl_warn(nvme_ctrl_t *ctrl, const char *fmt, ...)
111 {
112 va_list ap;
113
114 va_start(ap, fmt);
115 libnvme_test_ctrl_vwarn(ctrl, fmt, ap);
116 va_end(ap);
117 }
118
119 void __NORETURN
libnvme_test_ctrl_fatal(nvme_ctrl_t * ctrl,const char * fmt,...)120 libnvme_test_ctrl_fatal(nvme_ctrl_t *ctrl, const char *fmt, ...)
121 {
122 va_list ap;
123
124 va_start(ap, fmt);
125 libnvme_test_ctrl_vwarn(ctrl, fmt, ap);
126 va_end(ap);
127
128 exit(EXIT_FAILURE);
129 }
130
131 void
libnvme_test_ctrl_info_warn(nvme_ctrl_info_t * info,const char * fmt,...)132 libnvme_test_ctrl_info_warn(nvme_ctrl_info_t *info, const char *fmt, ...)
133 {
134 va_list ap;
135
136 va_start(ap, fmt);
137 libnvme_test_ctrl_info_vwarn(info, fmt, ap);
138 va_end(ap);
139 }
140
141 void
libnvme_test_ns_info_warn(nvme_ns_info_t * info,const char * fmt,...)142 libnvme_test_ns_info_warn(nvme_ns_info_t *info, const char *fmt, ...)
143 {
144 va_list ap;
145
146 va_start(ap, fmt);
147 libnvme_test_ns_info_vwarn(info, fmt, ap);
148 va_end(ap);
149 }
150
151 void __NORETURN
libnvme_test_ctrl_info_fatal(nvme_ctrl_info_t * info,const char * fmt,...)152 libnvme_test_ctrl_info_fatal(nvme_ctrl_info_t *info, const char *fmt, ...)
153 {
154 va_list ap;
155
156 va_start(ap, fmt);
157 libnvme_test_ctrl_info_vwarn(info, fmt, ap);
158 va_end(ap);
159
160 exit(EXIT_FAILURE);
161 }
162
163 void
libnvme_test_init(nvme_t ** nvmep,nvme_ctrl_t ** ctrlp)164 libnvme_test_init(nvme_t **nvmep, nvme_ctrl_t **ctrlp)
165 {
166 nvme_t *nvme;
167 nvme_ctrl_t *ctrl;
168 const char *dev;
169
170 nvme = nvme_init();
171 if (nvme == NULL) {
172 err(EXIT_FAILURE, "failed to create libnvme handle");
173 }
174
175 dev = getenv(NVME_TEST_DEV_ENVVAR);
176 if (dev == NULL) {
177 errx(EXIT_FAILURE, "cannot run test, missing required NVMe "
178 "device, please set the %s environment variable",
179 NVME_TEST_DEV_ENVVAR);
180 }
181
182 if (!nvme_ctrl_ns_init(nvme, dev, &ctrl, NULL)) {
183 libnvme_test_hdl_fatal(nvme, "failed to open %s", dev);
184 }
185
186 *nvmep = nvme;
187 *ctrlp = ctrl;
188 }
189
190 bool
libnvme_test_lbaf(nvme_ctrl_info_t * info,uint32_t size,uint32_t * lbap)191 libnvme_test_lbaf(nvme_ctrl_info_t *info, uint32_t size, uint32_t *lbap)
192 {
193 uint32_t nfmts, fmt = UINT32_MAX, fmt_rp = UINT32_MAX;
194
195 nfmts = nvme_ctrl_info_nformats(info);
196 if (nfmts == 0) {
197 warnx("no LBA formats found on device");
198 return (false);
199 }
200
201 for (uint32_t i = 0; i < nfmts; i++) {
202 const nvme_nvm_lba_fmt_t *lba;
203 uint32_t rp;
204
205 if (!nvme_ctrl_info_format(info, i, &lba)) {
206 libnvme_test_ctrl_info_warn(info, "failed to get LBA "
207 "format %u", i);
208 continue;
209 }
210
211 if (nvme_nvm_lba_fmt_meta_size(lba) != 0)
212 continue;
213
214 if (nvme_nvm_lba_fmt_data_size(lba) != size)
215 continue;
216
217 rp = nvme_nvm_lba_fmt_rel_perf(lba);
218 if (rp < fmt_rp) {
219 fmt = i;
220 fmt_rp = rp;
221 }
222 }
223
224 if (fmt != UINT32_MAX) {
225 *lbap = fmt;
226 return (true);
227 }
228
229 return (false);
230 }
231
232 bool
libnvme_test_ns_create(nvme_ctrl_t * ctrl,uint64_t size,uint32_t lbaf,uint32_t * nsid,nvme_err_t * err)233 libnvme_test_ns_create(nvme_ctrl_t *ctrl, uint64_t size, uint32_t lbaf,
234 uint32_t *nsid, nvme_err_t *err)
235 {
236 nvme_ns_create_req_t *req;
237 bool ret = false;
238
239 if (!nvme_ns_create_req_init_by_csi(ctrl, NVME_CSI_NVM, &req)) {
240 libnvme_test_ctrl_warn(ctrl, "failed to initialize namespace "
241 "create request");
242 goto done;
243 }
244
245 if (!nvme_ns_create_req_set_flbas(req, lbaf)) {
246 libnvme_test_ctrl_warn(ctrl, "failed to set flbas for "
247 "namespace create request to 0x%x", lbaf);
248 goto done;
249 }
250
251 if (!nvme_ns_create_req_set_nsze(req, size)) {
252 libnvme_test_ctrl_warn(ctrl, "failed to set nsze for "
253 "namespace create request to 0x%" PRIx64, size);
254 goto done;
255 }
256
257 if (!nvme_ns_create_req_set_ncap(req, size)) {
258 libnvme_test_ctrl_warn(ctrl, "failed to set ncap for "
259 "namespace create request to 0x%" PRIx64, size);
260 goto done;
261 }
262
263 if (!nvme_ns_create_req_set_nmic(req, NVME_NS_NMIC_T_NONE)) {
264 libnvme_test_ctrl_warn(ctrl, "failed to set nmic for "
265 "namespace create request");
266 goto done;
267 }
268
269 ret = nvme_ns_create_req_exec(req);
270 if (err != NULL) {
271 *err = nvme_ctrl_err(ctrl);
272 ret = true;
273 if (*err != NVME_ERR_OK)
274 goto done;
275 } else if (!ret) {
276 libnvme_test_ctrl_warn(ctrl, "failed to execute namespace "
277 "create request");
278 goto done;
279 }
280
281 if (nsid != NULL) {
282 ret = nvme_ns_create_req_get_nsid(req, nsid);
283 if (!ret) {
284 libnvme_test_ctrl_warn(ctrl, "failed to retrieve "
285 "created namespace id");
286 }
287 }
288
289 done:
290 nvme_ns_create_req_fini(req);
291 return (ret);
292 }
293
294 bool
libnvme_test_ns_delete(nvme_ctrl_t * ctrl,uint32_t nsid,nvme_err_t * err)295 libnvme_test_ns_delete(nvme_ctrl_t *ctrl, uint32_t nsid, nvme_err_t *err)
296 {
297 bool ret = true;
298 nvme_ns_delete_req_t *req;
299
300 if (!nvme_ns_delete_req_init(ctrl, &req)) {
301 libnvme_test_ctrl_warn(ctrl, "failed to initialize namespace "
302 "delete request for namespace %u", nsid);
303 return (false);
304 }
305
306 if (!nvme_ns_delete_req_set_nsid(req, nsid)) {
307 libnvme_test_ctrl_warn(ctrl, "failed to set namespace for "
308 "ns %u delete request", nsid);
309 ret = false;
310 goto done;
311 }
312
313 ret = nvme_ns_delete_req_exec(req);
314 if (err != NULL) {
315 *err = nvme_ctrl_err(ctrl);
316 ret = true;
317 } else if (!ret) {
318 libnvme_test_ctrl_warn(ctrl, "failed to execute namespace "
319 "delete request for namespace %u", nsid);
320 }
321
322 done:
323 nvme_ns_delete_req_fini(req);
324 return (ret);
325 }
326
327 bool
libnvme_test_ctrl_attach(nvme_ctrl_t * ctrl,uint32_t nsid,uint32_t type,nvme_err_t * err)328 libnvme_test_ctrl_attach(nvme_ctrl_t *ctrl, uint32_t nsid, uint32_t type,
329 nvme_err_t *err)
330 {
331 nvme_ns_attach_req_t *req = NULL;
332 const char *desc;
333 bool ret = true;
334
335 VERIFY(type == NVME_NS_ATTACH_CTRL_DETACH ||
336 type == NVME_NS_ATTACH_CTRL_ATTACH);
337 if (type == NVME_NS_ATTACH_CTRL_DETACH) {
338 desc = "detach";
339 } else {
340 desc = "attach";
341 }
342
343 if (!nvme_ns_attach_req_init_by_sel(ctrl, type, &req)) {
344 libnvme_test_ctrl_warn(ctrl, "failed to initialize controller "
345 "%s request for ns 0x%x", desc, nsid);
346 ret = false;
347 goto done;
348 }
349
350 if (!nvme_ns_attach_req_set_nsid(req, nsid)) {
351 libnvme_test_ctrl_warn(ctrl, "failed to set namespace for "
352 "ns 0x%x controller %s request", nsid, desc);
353 ret = false;
354 goto done;
355 }
356
357 if (!nvme_ns_attach_req_set_ctrlid_self(req)) {
358 libnvme_test_ctrl_warn(ctrl, "failed to set controller for "
359 "ns 0x%x controller %s request", nsid, desc);
360 ret = false;
361 goto done;
362
363 }
364
365 ret = nvme_ns_attach_req_exec(req);
366 if (err != NULL) {
367 *err = nvme_ctrl_err(ctrl);
368 ret = true;
369 } else if (!ret) {
370 libnvme_test_ctrl_warn(ctrl, "failed to execute controller "
371 "%s request for ns 0x%x", desc, nsid);
372 }
373
374 done:
375 nvme_ns_attach_req_fini(req);
376 return (ret);
377 }
378
379 bool
libnvme_test_ns_blkdev(nvme_ctrl_t * ctrl,uint32_t nsid,bool attach,nvme_err_t * err)380 libnvme_test_ns_blkdev(nvme_ctrl_t *ctrl, uint32_t nsid, bool attach,
381 nvme_err_t *err)
382 {
383 nvme_ns_t *ns;
384 bool ret;
385
386 if (!nvme_ns_init(ctrl, nsid, &ns)) {
387 libnvme_test_ctrl_warn(ctrl, "failed to initialize namespace "
388 "%u", nsid);
389 return (false);
390 }
391
392 if (attach) {
393 ret = nvme_ns_bd_attach(ns);
394 } else {
395 /*
396 * Occasionally we've seen a race on blkdev detach during tests
397 * where we have what is most likely a transient reference. If
398 * we get that the kernel failed to detach, try up to 5 times
399 * and wait 10ms between attempts to just smooth it over.
400 */
401 for (uint32_t i = 0; i < 5; i++) {
402 struct timespec t;
403
404 ret = nvme_ns_bd_detach(ns);
405 if (ret || nvme_ctrl_err(ctrl) !=
406 NVME_ERR_DETACH_KERN) {
407 break;
408 }
409
410 t.tv_sec = 0;
411 t.tv_nsec = MSEC2NSEC(10);
412 (void) nanosleep(&t, NULL);
413 }
414 }
415
416 if (err != NULL) {
417 *err = nvme_ctrl_err(ctrl);
418 ret = true;
419 } else if (!ret) {
420 libnvme_test_ctrl_warn(ctrl, "failed to %s namespace %u",
421 attach ? "attach" : "detach", nsid);
422 }
423 nvme_ns_fini(ns);
424
425 return (ret);
426 }
427
428 /*
429 * Non-fatally ensure that the requested NS is in the state that is asked for.
430 * We assume that the caller already has a lock on the device.
431 */
432 bool
libnvme_test_setup_ns(nvme_ctrl_t * ctrl,nvme_ns_disc_level_t level,uint32_t nsid,uint32_t lbaf)433 libnvme_test_setup_ns(nvme_ctrl_t *ctrl, nvme_ns_disc_level_t level,
434 uint32_t nsid, uint32_t lbaf)
435 {
436 nvme_ns_info_t *info;
437 nvme_ns_disc_level_t cur;
438 uint32_t nsid_out;
439 uint64_t create_size = NVME_TEST_NS_SIZE / NVME_TEST_LBA_SIZE;
440
441 if (!nvme_ctrl_ns_info_snap(ctrl, nsid, &info)) {
442 libnvme_test_ctrl_warn(ctrl, "failed to take snapshot of "
443 "namespace %u", nsid);
444 return (false);
445 }
446 cur = nvme_ns_info_level(info);
447 nvme_ns_info_free(info);
448
449 /*
450 * Whether we end up active or not ignored is basically dependent on
451 * device data. Tests that care ultimately need to check themselves.
452 */
453 if (level == NVME_NS_DISC_F_NOT_IGNORED)
454 level = NVME_NS_DISC_F_ACTIVE;
455
456 while (cur > level) {
457 switch (cur) {
458 case NVME_NS_DISC_F_BLKDEV:
459 if (!libnvme_test_ns_blkdev(ctrl, nsid, false, NULL)) {
460 return (false);
461 }
462 cur = NVME_NS_DISC_F_NOT_IGNORED;
463 break;
464 case NVME_NS_DISC_F_NOT_IGNORED:
465 case NVME_NS_DISC_F_ACTIVE:
466 if (!libnvme_test_ctrl_attach(ctrl, nsid,
467 NVME_NS_ATTACH_CTRL_DETACH, NULL)) {
468 return (false);
469 }
470 cur = NVME_NS_DISC_F_ALLOCATED;
471 break;
472 case NVME_NS_DISC_F_ALLOCATED:
473 if (!libnvme_test_ns_delete(ctrl, nsid, NULL)) {
474 return (false);
475 }
476 cur = NVME_NS_DISC_F_ALL;
477 break;
478
479 case NVME_NS_DISC_F_ALL:
480 abort();
481 }
482 }
483
484 while (cur < level) {
485 switch (cur) {
486 case NVME_NS_DISC_F_BLKDEV:
487 abort();
488 case NVME_NS_DISC_F_NOT_IGNORED:
489 case NVME_NS_DISC_F_ACTIVE:
490 if (!libnvme_test_ns_blkdev(ctrl, nsid, true, NULL)) {
491 return (false);
492 }
493 cur = NVME_NS_DISC_F_BLKDEV;
494 break;
495 case NVME_NS_DISC_F_ALLOCATED:
496 if (!libnvme_test_ctrl_attach(ctrl, nsid,
497 NVME_NS_ATTACH_CTRL_ATTACH, NULL)) {
498 return (false);
499 }
500 cur = NVME_NS_DISC_F_ACTIVE;
501 break;
502 case NVME_NS_DISC_F_ALL:
503 if (!libnvme_test_ns_create(ctrl, create_size, lbaf,
504 &nsid_out, NULL)) {
505 return (false);
506 }
507
508 if (nsid_out != nsid) {
509 warnx("namespace creation resulted in NSID "
510 "%u, but expected %u to be created", nsid,
511 nsid_out);
512 return (false);
513 }
514
515 cur = NVME_NS_DISC_F_ALLOCATED;
516 break;
517 }
518 }
519
520 return (true);
521 }
522
523 bool
libnvme_test_ctrl_err(nvme_ctrl_t * ctrl,uint32_t exp_sct,uint32_t exp_sc,const char * desc)524 libnvme_test_ctrl_err(nvme_ctrl_t *ctrl, uint32_t exp_sct, uint32_t exp_sc,
525 const char *desc)
526 {
527 uint32_t sct, sc;
528 nvme_err_t err = nvme_ctrl_err(ctrl);
529
530 if (err != NVME_ERR_CONTROLLER) {
531 warnx("TEST FAILED: %s: got wrong error: found "
532 "%s (0x%x), not NVME_ERR_CONTROLLER (0x%x)",
533 desc, nvme_ctrl_errtostr(ctrl, err), err,
534 NVME_ERR_CONTROLLER);
535 return (false);
536 }
537
538 nvme_ctrl_deverr(ctrl, &sct, &sc);
539 if (sct != exp_sct && sc != exp_sc) {
540 warnx("TEST FAILED: %s: got incorrect controller error: found "
541 "%s/%s (0x%x/0x%x), but expected %s/%s (0x%x/0x%x)", desc,
542 nvme_scttostr(ctrl, sct),
543 nvme_sctostr(ctrl, NVME_CSI_NVM, sct, sc), sct, sc,
544 nvme_scttostr(ctrl, exp_sct),
545 nvme_sctostr(ctrl, NVME_CSI_NVM, exp_sct, exp_sc), exp_sct,
546 exp_sc);
547 return (false);
548 }
549
550 (void) printf("TEST PASSED: %s: got correct controller error\n", desc);
551 return (true);
552 }
553