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