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
info_roundtrip_pci(nvme_ctrl_info_t * info,nvme_ctrl_info_t * rest_info)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
info_roundtrip_ns(nvme_ctrl_info_t * info,nvme_ctrl_info_t * rest_info)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
info_roundtrip_lba(nvme_ctrl_info_t * info,nvme_ctrl_info_t * rest_info)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
main(void)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