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 2024 Oxide Computer Company 14 */ 15 16 /* 17 * Iterate over the namespaces and ensure that the information we get in the 18 * discovery is the same as when we take a snapshot. To ensure that nothing 19 * changes out from under us, we take a controller write lock which will ensure 20 * that no modifications can occur. 21 * 22 * In addition, we want to test that discovery filters work so we first go 23 * through and count the different levels. 24 */ 25 26 #include <err.h> 27 #include <string.h> 28 #include <umem.h> 29 30 #include "libnvme_test_common.h" 31 32 static bool 33 ns_disc_count_cb(nvme_ctrl_t *ctrl, const nvme_ns_disc_t *disc, void *arg) 34 { 35 uint32_t *valp = arg; 36 *valp = *valp + 1; 37 return (true); 38 } 39 40 static bool 41 ns_disc_count(nvme_ctrl_t *ctrl, nvme_ns_disc_level_t level, uint32_t exp) 42 { 43 uint32_t count = 0; 44 45 if (!nvme_ns_discover(ctrl, level, ns_disc_count_cb, &count)) { 46 libnvme_test_ctrl_warn(ctrl, "failed to discover at level %u", 47 level); 48 return (false); 49 } else if (count != exp) { 50 warnx("TEST FAILED: ns discovery level %u found 0x%x " 51 "namespaces, but expected 0x%x", level, count, exp); 52 return (false); 53 } else { 54 (void) printf("TEST PASSED: ns discovery level %u had correct " 55 "count (0x%x)\n", level, exp); 56 return (true); 57 } 58 } 59 60 static bool 61 ns_disc_blkdev_cb(nvme_ctrl_t *ctrl, const nvme_ns_disc_t *disc, void *arg) 62 { 63 int *ret = arg; 64 nvme_ns_info_t *info; 65 const uint32_t nsid = nvme_ns_disc_nsid(disc); 66 const char *addr; 67 68 if (nvme_ns_disc_level(disc) < NVME_NS_DISC_F_BLKDEV) { 69 warnx("TEST FAILED: ns %u has level %u, but filtering on " 70 "blkdev (%u)", nsid, nvme_ns_disc_level(disc), 71 NVME_NS_DISC_F_BLKDEV); 72 *ret = EXIT_FAILURE; 73 return (true); 74 } 75 76 if (!nvme_ctrl_ns_info_snap(ctrl, nsid, &info)) { 77 libnvme_test_ctrl_warn(ctrl, "failed to get info snapshot for " 78 "nsid %u", nsid); 79 *ret = EXIT_FAILURE; 80 return (true); 81 } 82 83 if (!nvme_ns_info_bd_addr(info, &addr)) { 84 libnvme_test_ctrl_warn(ctrl, "failed to get bd addr for nsid " 85 "%u", nsid); 86 *ret = EXIT_FAILURE; 87 } else if (addr[0] == '\0') { 88 warnx("TEST FAILED: nsid %u has invalid bd addr", nsid); 89 *ret = EXIT_FAILURE; 90 } else { 91 (void) printf("TEST PASSED: nsid %u bd addr valid\n", nsid); 92 } 93 94 nvme_ns_info_free(info); 95 return (true); 96 } 97 98 static bool 99 ns_disc_guids_cb(nvme_ctrl_t *ctrl, const nvme_ns_disc_t *disc, void *arg) 100 { 101 int *ret = arg; 102 nvme_ns_info_t *info; 103 const uint32_t nsid = nvme_ns_disc_nsid(disc); 104 const nvme_ns_disc_flags_t flags = nvme_ns_disc_flags(disc); 105 uint8_t id[16]; 106 bool bret; 107 108 if (nvme_ns_disc_level(disc) < NVME_NS_DISC_F_ACTIVE) { 109 warnx("TEST FAILED: ns %u has level %u, but filtering on " 110 "active (%u)", nsid, nvme_ns_disc_level(disc), 111 NVME_NS_DISC_F_ACTIVE); 112 *ret = EXIT_FAILURE; 113 return (true); 114 } 115 116 if (!nvme_ctrl_ns_info_snap(ctrl, nsid, &info)) { 117 libnvme_test_ctrl_warn(ctrl, "failed to get info snapshot for " 118 "nsid %u", nsid); 119 *ret = EXIT_FAILURE; 120 return (true); 121 } 122 123 bret = nvme_ns_info_eui64(info, id); 124 if (bret != ((flags & NVME_NS_DISC_F_EUI64_VALID) != 0)) { 125 warnx("TEST FAILED: nvme_ns_info_eui64() returned %s, but " 126 "expected %s from discovery information for nsid %u", 127 bret ? "true" : "false", 128 (flags & NVME_NS_DISC_F_EUI64_VALID) != 0 ? "true" : 129 "false", nsid); 130 *ret = EXIT_FAILURE; 131 } else { 132 (void) printf("TEST PASSED: namespace snapshot and discovery " 133 "agreed on EUI64 presence for nsid %u\n", nsid); 134 } 135 136 if (bret) { 137 const uint8_t *eui64 = nvme_ns_disc_eui64(disc); 138 const uint8_t zero[8] = { 0 }; 139 140 if (memcmp(eui64, id, sizeof (zero)) != 0) { 141 warnx("TEST FAILED: EUI64 differs between " 142 "discovery and info snapshot for nsid %u", nsid); 143 *ret = EXIT_FAILURE; 144 } else { 145 (void) printf("TEST PASSED: EUI64 equal between " 146 "discovery and info snapshot for nsid %u\n", nsid); 147 } 148 149 if (memcmp(id, zero, sizeof (zero)) == 0) { 150 warnx("TEST FAILED: Found invalid zero EUI64 for nsid " 151 "%u", nsid); 152 *ret = EXIT_FAILURE; 153 } else { 154 (void) printf("TEST PASSED: EUI64 is non-zero for " 155 "nsid %u\n", nsid); 156 } 157 } else { 158 if (nvme_ns_disc_eui64(disc) != NULL) { 159 warnx("TEST FAILED: discovery EUI64 was valid, but " 160 "should be NULL for nsid %u", nsid); 161 *ret = EXIT_FAILURE; 162 } else { 163 (void) printf("TEST PASSED: discovery EUI64 correctly " 164 "returned NULL for nsid %u\n", nsid); 165 } 166 167 switch (nvme_ns_info_err(info)) { 168 case NVME_INFO_ERR_VERSION: 169 case NVME_INFO_ERR_MISSING_CAP: 170 (void) printf("TEST PASSED: nvme_ns_info_eui64() " 171 "returned a valid error for nsid %u\n", nsid); 172 break; 173 default: 174 warnx("TEST FAILED: nvme_ns_info_eui64() returned an " 175 "invalid error for nsid %u: %s (%u)", nsid, 176 nvme_ns_info_errtostr(info, nvme_ns_info_err(info)), 177 nvme_ns_info_err(info)); 178 *ret = EXIT_FAILURE; 179 break; 180 } 181 } 182 183 bret = nvme_ns_info_nguid(info, id); 184 if (bret != ((flags & NVME_NS_DISC_F_NGUID_VALID) != 0)) { 185 warnx("TEST FAILED: nvme_ns_info_nguid() returned %s, but " 186 "expected %s from discovery information for nsid %u", 187 bret ? "true" : "false", 188 (flags & NVME_NS_DISC_F_NGUID_VALID) != 0 ? "true" : 189 "false", nsid); 190 *ret = EXIT_FAILURE; 191 } else { 192 (void) printf("TEST PASSED: namespace snapshot and discovery " 193 "agreed on NGUID presence for nsid %u\n", nsid); 194 } 195 196 if (bret) { 197 const uint8_t *nguid = nvme_ns_disc_nguid(disc); 198 const uint8_t zero[16] = { 0 }; 199 200 if (memcmp(nguid, id, sizeof (zero)) != 0) { 201 warnx("TEST FAILED: NGUID differs between " 202 "discovery and info snapshot for nsid %u", nsid); 203 *ret = EXIT_FAILURE; 204 } else { 205 (void) printf("TEST PASSED: NGUID equal between " 206 "discovery and info snapshot for nsid %u\n", nsid); 207 } 208 209 if (memcmp(id, zero, sizeof (zero)) == 0) { 210 warnx("TEST FAILED: Found invalid zero NGUID for nsid " 211 "%u", nsid); 212 *ret = EXIT_FAILURE; 213 } else { 214 (void) printf("TEST PASSED: NGUID is non-zero for " 215 "nsid %u\n", nsid); 216 } 217 } else { 218 if (nvme_ns_disc_nguid(disc) != NULL) { 219 warnx("TEST FAILED: discovery NGUID was valid, but " 220 "should be NULL for nsid %u", nsid); 221 *ret = EXIT_FAILURE; 222 } else { 223 (void) printf("TEST PASSED: discovery NGUID correctly " 224 "returned NULL for nsid %u\n", nsid); 225 } 226 227 switch (nvme_ns_info_err(info)) { 228 case NVME_INFO_ERR_VERSION: 229 case NVME_INFO_ERR_MISSING_CAP: 230 (void) printf("TEST PASSED: nvme_ns_info_nguid() " 231 "returned a valid error for nsid %u\n", nsid); 232 break; 233 default: 234 warnx("TEST FAILED: nvme_ns_info_nguid() returned an " 235 "invalid error for nsid %u: %s (%u)", nsid, 236 nvme_ns_info_errtostr(info, nvme_ns_info_err(info)), 237 nvme_ns_info_err(info)); 238 *ret = EXIT_FAILURE; 239 break; 240 } 241 } 242 nvme_ns_info_free(info); 243 return (true); 244 } 245 246 static bool 247 ns_disc_level_cb(nvme_ctrl_t *ctrl, const nvme_ns_disc_t *disc, void *arg) 248 { 249 int *ret = arg; 250 nvme_ns_info_t *info; 251 const uint32_t nsid = nvme_ns_disc_nsid(disc); 252 253 if (!nvme_ctrl_ns_info_snap(ctrl, nsid, &info)) { 254 libnvme_test_ctrl_warn(ctrl, "failed to get info snapshot for " 255 "nsid %u", nsid); 256 *ret = EXIT_FAILURE; 257 return (true); 258 } 259 260 if (nvme_ns_disc_level(disc) != nvme_ns_info_level(info)) { 261 warnx("TEST FAILED: discovery and ns info snapshot disagree " 262 "on discovery level: disc has %u, info has %u", 263 nvme_ns_disc_level(disc), nvme_ns_info_level(info)); 264 *ret = EXIT_FAILURE; 265 } else { 266 (void) printf("TEST PASSED: discovery and ns info snapshot " 267 "agree for nsid %u\n", nsid); 268 } 269 270 nvme_ns_info_free(info); 271 return (true); 272 } 273 274 static bool 275 ns_disc_bad_disc_init(nvme_ctrl_t *ctrl, nvme_ns_disc_level_t level, 276 nvme_ns_iter_t **iterp, nvme_err_t exp_err, const char *desc) 277 { 278 if (nvme_ns_discover_init(ctrl, level, iterp)) { 279 warnx("TEST FAILED: nvme_ns_discover_init() erroneously " 280 "passed despite %s", desc); 281 nvme_ns_discover_fini(*iterp); 282 return (false); 283 } else if (nvme_ctrl_err(ctrl) != exp_err) { 284 warnx("TEST FAILED: nvme_ns_discover_init() returned " 285 "wrong error %s (0x%x), not %s (0x%x)", 286 nvme_ctrl_errtostr(ctrl, nvme_ctrl_err(ctrl)), 287 nvme_ctrl_err(ctrl), nvme_ctrl_errtostr(ctrl, 288 exp_err), exp_err); 289 return (false); 290 } else { 291 (void) printf("TEST PASSED: nvme_ns_discover_init() failed " 292 "correctly for %s\n", desc); 293 return (true); 294 } 295 } 296 297 static bool 298 ns_disc_bad_disc(nvme_ctrl_t *ctrl, nvme_ns_disc_level_t level, 299 nvme_ns_disc_f func, nvme_err_t exp_err, const char *desc) 300 { 301 if (nvme_ns_discover(ctrl, level, func, NULL)) { 302 warnx("TEST FAILED: nvme_ns_discover() erroneously " 303 "passed despite %s", desc); 304 return (false); 305 } else if (nvme_ctrl_err(ctrl) != exp_err) { 306 warnx("TEST FAILED: nvme_ns_discover() returned " 307 "wrong error %s (0x%x), not %s (0x%x)", 308 nvme_ctrl_errtostr(ctrl, nvme_ctrl_err(ctrl)), 309 nvme_ctrl_err(ctrl), nvme_ctrl_errtostr(ctrl, 310 exp_err), exp_err); 311 return (false); 312 } else { 313 (void) printf("TEST PASSED: nvme_ns_discover() failed " 314 "correctly for %s\n", desc); 315 return (true); 316 } 317 } 318 319 static bool 320 ns_disc_nop_cb(nvme_ctrl_t *ctrl, const nvme_ns_disc_t *disc, void *arg) 321 { 322 return (true); 323 } 324 325 int 326 main(void) 327 { 328 int ret = EXIT_SUCCESS; 329 nvme_t *nvme; 330 nvme_ctrl_t *ctrl; 331 nvme_ctrl_info_t *info; 332 nvme_iter_t iret; 333 nvme_ns_iter_t *iter; 334 const nvme_ns_disc_t *disc; 335 uint32_t nbd = 0, nni = 0, nact = 0, nalloc = 0, nns = 0; 336 337 libnvme_test_init(&nvme, &ctrl); 338 if (!nvme_ctrl_lock(ctrl, NVME_LOCK_L_WRITE, NVME_LOCK_F_DONT_BLOCK)) { 339 libnvme_test_ctrl_fatal(ctrl, "failed to obtain write lock"); 340 } 341 342 if (!nvme_ns_discover_init(ctrl, NVME_NS_DISC_F_ALL, &iter)) { 343 libnvme_test_ctrl_fatal(ctrl, "failed to initialize initial " 344 "ns discovery"); 345 } 346 347 while ((iret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) { 348 switch (nvme_ns_disc_level(disc)) { 349 case NVME_NS_DISC_F_BLKDEV: 350 nbd++; 351 /* FALLTHROUGH */ 352 case NVME_NS_DISC_F_NOT_IGNORED: 353 nni++; 354 /* FALLTHROUGH */ 355 case NVME_NS_DISC_F_ACTIVE: 356 nact++; 357 /* FALLTHROUGH */ 358 case NVME_NS_DISC_F_ALLOCATED: 359 nalloc++; 360 /* FALLTHROUGH */ 361 case NVME_NS_DISC_F_ALL: 362 nns++; 363 break; 364 } 365 } 366 367 nvme_ns_discover_fini(iter); 368 if (iret != NVME_ITER_DONE) { 369 libnvme_test_ctrl_fatal(ctrl, "initial ns discovery failed"); 370 } 371 372 if (!nvme_ctrl_info_snap(ctrl, &info)) { 373 libnvme_test_ctrl_fatal(ctrl, "failed to get info snapshot"); 374 } 375 376 if (nns != nvme_ctrl_info_nns(info)) { 377 warnx("TEST FAILED: discovery found %u namespaces, but the " 378 "identify controller suggests there are %u", nns, 379 nvme_ctrl_info_nns(info)); 380 } 381 382 if (!ns_disc_count(ctrl, NVME_NS_DISC_F_BLKDEV, nbd)) { 383 ret = EXIT_FAILURE; 384 } 385 386 if (!ns_disc_count(ctrl, NVME_NS_DISC_F_NOT_IGNORED, nni)) { 387 ret = EXIT_FAILURE; 388 } 389 390 if (!ns_disc_count(ctrl, NVME_NS_DISC_F_ACTIVE, nact)) { 391 ret = EXIT_FAILURE; 392 } 393 394 if (!ns_disc_count(ctrl, NVME_NS_DISC_F_ALLOCATED, nalloc)) { 395 ret = EXIT_FAILURE; 396 } 397 398 if (!ns_disc_count(ctrl, NVME_NS_DISC_F_ALL, nns)) { 399 ret = EXIT_FAILURE; 400 } 401 402 /* 403 * For anything that has a blkdev address, ensure that our info snapshot 404 * has a valid blkdev address. 405 */ 406 if (!nvme_ns_discover(ctrl, NVME_NS_DISC_F_BLKDEV, ns_disc_blkdev_cb, 407 &ret)) { 408 libnvme_test_ctrl_warn(ctrl, "discovery failed for blkdev " 409 "test"); 410 ret = EXIT_FAILURE; 411 } 412 413 /* 414 * For anything active, check if there are guids and that the 415 * information snapshot matches the same logic. 416 */ 417 if (!nvme_ns_discover(ctrl, NVME_NS_DISC_F_ACTIVE, ns_disc_guids_cb, 418 &ret)) { 419 libnvme_test_ctrl_warn(ctrl, "discovery failed for guids " 420 "test"); 421 ret = EXIT_FAILURE; 422 } 423 424 /* 425 * For everything, make sure the levels match. 426 */ 427 if (!nvme_ns_discover(ctrl, NVME_NS_DISC_F_ALL, ns_disc_level_cb, 428 &ret)) { 429 libnvme_test_ctrl_warn(ctrl, "discovery failed for levels " 430 "test"); 431 ret = EXIT_FAILURE; 432 } 433 434 nvme_ctrl_unlock(ctrl); 435 436 if (!ns_disc_bad_disc_init(ctrl, INT32_MAX, &iter, NVME_ERR_BAD_FLAG, 437 "invalid level")) { 438 ret = EXIT_FAILURE; 439 } 440 441 if (!ns_disc_bad_disc_init(ctrl, NVME_NS_DISC_F_ALL, NULL, 442 NVME_ERR_BAD_PTR, "invalid iter pointer")) { 443 ret = EXIT_FAILURE; 444 } 445 446 if (!ns_disc_bad_disc(ctrl, UINT32_MAX, ns_disc_nop_cb, 447 NVME_ERR_BAD_FLAG, "invalid level")) { 448 ret = EXIT_FAILURE; 449 } 450 451 if (!ns_disc_bad_disc(ctrl, NVME_NS_DISC_F_ALL, NULL, 452 NVME_ERR_BAD_PTR, "invalid function pointer")) { 453 ret = EXIT_FAILURE; 454 } 455 456 umem_setmtbf(1); 457 if (!ns_disc_bad_disc_init(ctrl, NVME_NS_DISC_F_ALL, &iter, 458 NVME_ERR_NO_MEM, "no memory")) { 459 ret = EXIT_FAILURE; 460 } 461 462 if (!ns_disc_bad_disc(ctrl, NVME_NS_DISC_F_ALL, ns_disc_nop_cb, 463 NVME_ERR_NO_MEM, "no memory")) { 464 ret = EXIT_FAILURE; 465 } 466 umem_setmtbf(0); 467 468 if (ret == EXIT_SUCCESS) { 469 (void) printf("All tests passed successfully\n"); 470 } 471 472 nvme_ctrl_info_free(info); 473 nvme_ctrl_fini(ctrl); 474 nvme_fini(nvme); 475 return (ret); 476 } 477