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 * Take a controller snapshot. Roundtrip it through a save and restore and make 18 * sure that all the data is the same across the two. 19 */ 20 21 #include <err.h> 22 #include <string.h> 23 24 #include "libnvme_test_common.h" 25 26 static bool 27 info_roundtrip_pci(nvme_ctrl_info_t *info, nvme_ctrl_info_t *rest_info) 28 { 29 bool ret = true; 30 uint32_t id32, rest_id32; 31 uint16_t id16, rest_id16; 32 uint8_t id8, rest_id8; 33 34 if (!nvme_ctrl_info_pci_vid(info, &id16)) { 35 libnvme_test_ctrl_info_warn(info, "failed to get PCI vendor " 36 "from original snapshot"); 37 ret = false; 38 } else if (!nvme_ctrl_info_pci_vid(rest_info, &rest_id16)) { 39 libnvme_test_ctrl_info_warn(info, "failed to get PCI vendor " 40 "from restored snapshot"); 41 ret = false; 42 } else if (id16 != rest_id16) { 43 warnx("TEST FAILED: PCI vendor mismatch: was %u now %u", 44 id16, rest_id16); 45 ret = false; 46 } else { 47 (void) printf("TEST PASSED: PCI vendor successfully " 48 "restored\n"); 49 } 50 51 if (!nvme_ctrl_info_pci_did(info, &id16)) { 52 libnvme_test_ctrl_info_warn(info, "failed to get PCI device " 53 "from original snapshot"); 54 ret = false; 55 } else if (!nvme_ctrl_info_pci_did(rest_info, &rest_id16)) { 56 libnvme_test_ctrl_info_warn(info, "failed to get PCI device " 57 "from restored snapshot"); 58 ret = false; 59 } else if (id16 != rest_id16) { 60 warnx("TEST FAILED: PCI device mismatch: was %u now %u", 61 id16, rest_id16); 62 ret = false; 63 } else { 64 (void) printf("TEST PASSED: PCI device successfully " 65 "restored\n"); 66 } 67 68 if (!nvme_ctrl_info_pci_subvid(info, &id16)) { 69 libnvme_test_ctrl_info_warn(info, "failed to get PCI subsystem " 70 "vendor from original snapshot"); 71 ret = false; 72 } else if (!nvme_ctrl_info_pci_subvid(rest_info, &rest_id16)) { 73 libnvme_test_ctrl_info_warn(info, "failed to get PCI subsystem " 74 "vendor from restored snapshot"); 75 ret = false; 76 } else if (id16 != rest_id16) { 77 warnx("TEST FAILED: PCI subsystem vendor mismatch: was %u " 78 "now %u", id16, rest_id16); 79 ret = false; 80 } else { 81 (void) printf("TEST PASSED: PCI subsystem vendor successfully " 82 "restored\n"); 83 } 84 85 if (!nvme_ctrl_info_pci_subsys(info, &id16)) { 86 libnvme_test_ctrl_info_warn(info, "failed to get PCI subsystem " 87 "id from original snapshot"); 88 ret = false; 89 } else if (!nvme_ctrl_info_pci_subsys(rest_info, &rest_id16)) { 90 libnvme_test_ctrl_info_warn(info, "failed to get PCI subsystem " 91 "id from restored snapshot"); 92 ret = false; 93 } else if (id16 != rest_id16) { 94 warnx("TEST FAILED: PCI subsystem id mismatch: was %u " 95 "now %u", id16, rest_id16); 96 ret = false; 97 } else { 98 (void) printf("TEST PASSED: PCI subsystem id successfully " 99 "restored\n"); 100 } 101 102 if (!nvme_ctrl_info_pci_rev(info, &id8)) { 103 libnvme_test_ctrl_info_warn(info, "failed to get PCI revision " 104 "from original snapshot"); 105 ret = false; 106 } else if (!nvme_ctrl_info_pci_rev(rest_info, &rest_id8)) { 107 libnvme_test_ctrl_info_warn(info, "failed to get PCI revision " 108 "from restored snapshot"); 109 ret = false; 110 } else if (id8 != rest_id8) { 111 warnx("TEST FAILED: PCI revision mismatch: was %u now %u", 112 id8, rest_id8); 113 ret = false; 114 } else { 115 (void) printf("TEST PASSED: PCI revision successfully " 116 "restored\n"); 117 } 118 119 if (!nvme_ctrl_info_pci_mps_min(info, &id32)) { 120 libnvme_test_ctrl_info_warn(info, "failed to get PCI MPS min " 121 "from original snapshot"); 122 ret = false; 123 } else if (!nvme_ctrl_info_pci_mps_min(rest_info, &rest_id32)) { 124 libnvme_test_ctrl_info_warn(info, "failed to get PCI MPS min " 125 "from restored snapshot"); 126 ret = false; 127 } else if (id32 != rest_id32) { 128 warnx("TEST FAILED: PCI MPS min mismatch: was %u now %u", 129 id32, rest_id32); 130 ret = false; 131 } else { 132 (void) printf("TEST PASSED: PCI MPS min successfully " 133 "restored\n"); 134 } 135 136 if (!nvme_ctrl_info_pci_mps_max(info, &id32)) { 137 libnvme_test_ctrl_info_warn(info, "failed to get PCI MPS max " 138 "from original snapshot"); 139 ret = false; 140 } else if (!nvme_ctrl_info_pci_mps_max(rest_info, &rest_id32)) { 141 libnvme_test_ctrl_info_warn(info, "failed to get PCI MPS max " 142 "from restored snapshot"); 143 ret = false; 144 } else if (id32 != rest_id32) { 145 warnx("TEST FAILED: PCI MPS max mismatch: was %u now %u", 146 id32, rest_id32); 147 ret = false; 148 } else { 149 (void) printf("TEST PASSED: PCI MPS max successfully " 150 "restored\n"); 151 } 152 153 if (!nvme_ctrl_info_pci_nintrs(info, &id32)) { 154 libnvme_test_ctrl_info_warn(info, "failed to get PCI intr " 155 "count from original snapshot"); 156 ret = false; 157 } else if (!nvme_ctrl_info_pci_nintrs(rest_info, &rest_id32)) { 158 libnvme_test_ctrl_info_warn(info, "failed to get PCI intr " 159 "count from restored snapshot"); 160 ret = false; 161 } else if (id32 != rest_id32) { 162 warnx("TEST FAILED: PCI intr count mismatch: was %u now %u", 163 id32, rest_id32); 164 ret = false; 165 } else { 166 (void) printf("TEST PASSED: PCI intr count successfully " 167 "restored\n"); 168 } 169 170 return (ret); 171 } 172 173 static bool 174 info_roundtrip_ns(nvme_ctrl_info_t *info, nvme_ctrl_info_t *rest_info) 175 { 176 bool ret = true; 177 nvme_uint128_t u128, rest_u128; 178 const nvme_identify_nsid_t *idns, *rest_idns; 179 180 if (!nvme_ctrl_info_cap(info, &u128)) { 181 libnvme_test_ctrl_info_warn(info, "failed to get NVM capacity " 182 "from original snapshot"); 183 ret = false; 184 } else if (!nvme_ctrl_info_cap(rest_info, &rest_u128)) { 185 libnvme_test_ctrl_info_warn(info, "failed to get NVM capacity " 186 "from restored snapshot"); 187 ret = false; 188 } else if (memcmp(&u128, &rest_u128, sizeof (nvme_uint128_t)) != 0) { 189 warnx("TEST FAILED: NVM capacity mismatch"); 190 ret = false; 191 } else { 192 (void) printf("TEST PASSED: NVM capacity successfully " 193 "restored\n"); 194 } 195 196 if (!nvme_ctrl_info_unalloc_cap(info, &u128)) { 197 libnvme_test_ctrl_info_warn(info, "failed to get NVM " 198 "unallocated capacity from original snapshot"); 199 ret = false; 200 } else if (!nvme_ctrl_info_unalloc_cap(rest_info, &rest_u128)) { 201 libnvme_test_ctrl_info_warn(info, "failed to get NVM " 202 "unallocated capacity from restored snapshot"); 203 ret = false; 204 } else if (memcmp(&u128, &rest_u128, sizeof (nvme_uint128_t)) != 0) { 205 warnx("TEST FAILED: NVM unallocated capacity mismatch"); 206 ret = false; 207 } else { 208 (void) printf("TEST PASSED: NVM unallocated capacity " 209 "successfully restored\n"); 210 } 211 212 if (!nvme_ctrl_info_common_ns(info, &idns)) { 213 libnvme_test_ctrl_info_warn(info, "failed to get common ns " 214 "from original snapshot"); 215 ret = false; 216 } else if (!nvme_ctrl_info_common_ns(rest_info, &rest_idns)) { 217 libnvme_test_ctrl_info_warn(info, "failed to get common ns " 218 "from restored snapshot"); 219 ret = false; 220 } else if (memcmp(idns, rest_idns, 221 sizeof (nvme_identify_nsid_t)) != 0) { 222 warnx("TEST FAILED: Common Identify Namespace mismatch"); 223 ret = false; 224 } else { 225 (void) printf("TEST PASSED: common identify namespace " 226 "successfully restored\n"); 227 } 228 229 return (ret); 230 } 231 232 static bool 233 info_roundtrip_lba(nvme_ctrl_info_t *info, nvme_ctrl_info_t *rest_info) 234 { 235 bool ret = true; 236 const uint32_t nlbas = nvme_ctrl_info_nformats(info); 237 238 for (uint32_t i = 0; i < nlbas; i++) { 239 const nvme_nvm_lba_fmt_t *fmt, *rest_fmt; 240 241 if (!nvme_ctrl_info_format(info, i, &fmt)) { 242 /* 243 * Some devices like the Kioxia KCD6XLUL3T84 have holes 244 * in their LBA space. Skip such instances. 245 */ 246 if (nvme_ctrl_info_err(info) == NVME_INFO_ERR_BAD_FMT) { 247 continue; 248 } 249 250 libnvme_test_ctrl_info_warn(info, "failed to get " 251 "LBA format %u from original snapshot", i); 252 ret = false; 253 continue; 254 } 255 256 if (!nvme_ctrl_info_format(rest_info, i, &rest_fmt)) { 257 libnvme_test_ctrl_info_warn(info, "failed to get " 258 "LBA format %u from restored snapshot", i); 259 ret = false; 260 continue; 261 } 262 263 (void) printf("TEST PASSED: successfully got LBA format %u\n", 264 i); 265 if (nvme_nvm_lba_fmt_id(fmt) != i) { 266 warnx("TEST FAILED: format %u from original snapshot " 267 "has wrong format id: %u\n", i, 268 nvme_nvm_lba_fmt_id(fmt)); 269 ret = false; 270 } 271 272 if (nvme_nvm_lba_fmt_id(rest_fmt) != i) { 273 warnx("TEST FAILED: format %u from restored snapshot " 274 "has wrong format id: %u\n", i, 275 nvme_nvm_lba_fmt_id(rest_fmt)); 276 ret = false; 277 } 278 279 if (nvme_nvm_lba_fmt_meta_size(fmt) != 280 nvme_nvm_lba_fmt_meta_size(rest_fmt)) { 281 warnx("TEST FAILED: LBA %u metadata size mismatch: " 282 "was %u, now %u", i, 283 nvme_nvm_lba_fmt_meta_size(fmt), 284 nvme_nvm_lba_fmt_meta_size(rest_fmt)); 285 ret = false; 286 } else { 287 (void) printf("TEST PASSED: LBA %u metadata " 288 "successfully restored\n", i); 289 } 290 291 if (nvme_nvm_lba_fmt_data_size(fmt) != 292 nvme_nvm_lba_fmt_data_size(rest_fmt)) { 293 warnx("TEST FAILED: LBA %u data size mismatch: " 294 "was %" PRIu64 ", now %" PRIu64, i, 295 nvme_nvm_lba_fmt_data_size(fmt), 296 nvme_nvm_lba_fmt_data_size(rest_fmt)); 297 ret = false; 298 } else { 299 (void) printf("TEST PASSED: LBA %u data size " 300 "successfully restored\n", i); 301 } 302 303 if (nvme_nvm_lba_fmt_rel_perf(fmt) != 304 nvme_nvm_lba_fmt_rel_perf(rest_fmt)) { 305 warnx("TEST FAILED: LBA %u relative perf mismatch: " 306 "was %u, now %u", i, 307 nvme_nvm_lba_fmt_rel_perf(fmt), 308 nvme_nvm_lba_fmt_rel_perf(rest_fmt)); 309 ret = false; 310 } else { 311 (void) printf("TEST PASSED: LBA %u relative perf " 312 "successfully restored\n", i); 313 } 314 } 315 316 return (ret); 317 } 318 319 int 320 main(void) 321 { 322 int ret = EXIT_SUCCESS; 323 nvme_t *nvme; 324 nvme_ctrl_t *ctrl; 325 nvme_ctrl_info_t *info, *rest_info; 326 nvlist_t *nvl; 327 const nvme_identify_ctrl_t *ctrlid, *rest_ctrlid; 328 const nvme_version_t *vers, *rest_vers; 329 330 libnvme_test_init(&nvme, &ctrl); 331 if (!nvme_ctrl_info_snap(ctrl, &info)) { 332 libnvme_test_ctrl_fatal(ctrl, "failed to take a snapshot"); 333 } 334 335 if (!nvme_ctrl_info_persist(info, &nvl)) { 336 libnvme_test_ctrl_info_fatal(info, "failed to persist the " 337 "controller snapshot"); 338 } 339 340 if (!nvme_ctrl_info_restore(nvme, nvl, &rest_info)) { 341 libnvme_test_hdl_fatal(nvme, "failed to restore controller " 342 "snapshot"); 343 } 344 345 if (nvme_ctrl_info_vendor(info) != nvme_ctrl_info_vendor(rest_info)) { 346 warnx("TEST FAILED: vendor mismatch: orig 0x%x, restored: 0x%x", 347 nvme_ctrl_info_vendor(info), 348 nvme_ctrl_info_vendor(rest_info)); 349 ret = EXIT_FAILURE; 350 } else { 351 (void) printf("TEST PASSED: successfully matched vendor id\n"); 352 } 353 354 ctrlid = nvme_ctrl_info_identify(info); 355 rest_ctrlid = nvme_ctrl_info_identify(rest_info); 356 if (memcmp(ctrlid, rest_ctrlid, sizeof (nvme_identify_ctrl_t)) != 0) { 357 warnx("TEST FAILED: Identify info mismatched after restore"); 358 ret = EXIT_FAILURE; 359 } else { 360 (void) printf("TEST PASSED: identify controller successfully " 361 "restored\n"); 362 } 363 364 vers = nvme_ctrl_info_version(info); 365 rest_vers = nvme_ctrl_info_version(rest_info); 366 if (vers->v_major != rest_vers->v_major) { 367 warnx("TEST FAILED: mismatched major version: was %u, found %u", 368 vers->v_major, rest_vers->v_major); 369 ret = EXIT_FAILURE; 370 } else { 371 (void) printf("TEST PASSED: major version successfully " 372 "restored\n"); 373 } 374 375 if (vers->v_minor != rest_vers->v_minor) { 376 warnx("TEST FAILED: mismatched minor version: was %u, found %u", 377 vers->v_minor, rest_vers->v_minor); 378 ret = EXIT_FAILURE; 379 } else { 380 (void) printf("TEST PASSED: minor version successfully " 381 "restored\n"); 382 } 383 384 if (strcmp(nvme_ctrl_info_model(info), 385 nvme_ctrl_info_model(rest_info)) != 0) { 386 warnx("TEST FAILED: model string mismatch"); 387 ret = EXIT_FAILURE; 388 } else { 389 (void) printf("TEST PASSED: model successfully restored\n"); 390 } 391 392 if (strcmp(nvme_ctrl_info_serial(info), 393 nvme_ctrl_info_serial(rest_info)) != 0) { 394 warnx("TEST FAILED: serial string mismatch"); 395 ret = EXIT_FAILURE; 396 } else { 397 (void) printf("TEST PASSED: serial successfully restored\n"); 398 } 399 400 if (strcmp(nvme_ctrl_info_fwrev(info), 401 nvme_ctrl_info_fwrev(rest_info)) != 0) { 402 warnx("TEST FAILED: fwrev string mismatch"); 403 ret = EXIT_FAILURE; 404 } else { 405 (void) printf("TEST PASSED: fwrev successfully restored\n"); 406 } 407 408 if (nvme_ctrl_info_nns(info) != nvme_ctrl_info_nns(rest_info)) { 409 warnx("TEST FAILED: number of namespaces mismatch: was %u, " 410 "now %u", nvme_ctrl_info_nns(info), 411 nvme_ctrl_info_nns(rest_info)); 412 ret = EXIT_FAILURE; 413 } else { 414 (void) printf("TEST PASSED: number of namespaces successfully " 415 "restored\n"); 416 } 417 418 if (nvme_ctrl_info_type(info) != nvme_ctrl_info_type(rest_info)) { 419 warnx("TEST FAILED: controller type mismatch: was %u, " 420 "now %u", nvme_ctrl_info_type(info), 421 nvme_ctrl_info_type(rest_info)); 422 ret = EXIT_FAILURE; 423 } else { 424 (void) printf("TEST PASSED: controller type successfully " 425 "restored\n"); 426 } 427 428 if (nvme_ctrl_info_transport(info) != 429 nvme_ctrl_info_transport(rest_info)) { 430 warnx("TEST FAILED: controller transport mismatch: was %u, " 431 "now %u", nvme_ctrl_info_transport(info), 432 nvme_ctrl_info_transport(rest_info)); 433 ret = EXIT_FAILURE; 434 } else { 435 (void) printf("TEST PASSED: controller transport successfully " 436 "restored\n"); 437 } 438 439 if (nvme_ctrl_info_transport(info) == NVME_CTRL_TRANSPORT_PCI && 440 !info_roundtrip_pci(info, rest_info)) { 441 ret = EXIT_FAILURE; 442 } 443 444 if (ctrlid->id_oacs.oa_nsmgmt != 0 && !info_roundtrip_ns(info, 445 rest_info)) { 446 ret = EXIT_FAILURE; 447 } 448 449 if (nvme_ctrl_info_nformats(info) != 450 nvme_ctrl_info_nformats(rest_info)) { 451 warnx("TEST FAILED: number of LBA formats mismatch: was %u, " 452 "now %u", nvme_ctrl_info_nformats(info), 453 nvme_ctrl_info_nformats(rest_info)); 454 ret = EXIT_FAILURE; 455 } else { 456 (void) printf("TEST PASSED: number of LBA formats successfully " 457 "restored\n"); 458 } 459 460 if (nvme_ctrl_info_nformats(info) > 0 && !info_roundtrip_lba(info, 461 rest_info)) { 462 ret = EXIT_FAILURE; 463 } 464 465 nvme_ctrl_info_free(rest_info); 466 nvme_ctrl_info_free(info); 467 nvme_ctrl_fini(ctrl); 468 nvme_fini(nvme); 469 470 if (ret == EXIT_SUCCESS) { 471 (void) printf("All tests exited successfully\n"); 472 } 473 474 return (ret); 475 } 476