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  * Test namespace information snapshots. Because devices are all different, we
18  * mostly try to get a device's identify namespace and then compare that to the
19  * fields we have here.
20  */
21 
22 #include <stdlib.h>
23 #include <stdbool.h>
24 #include <err.h>
25 #include <umem.h>
26 
27 #include "libnvme_test_common.h"
28 
29 static bool
ns_info_test_inactive(nvme_ns_info_t * info,uint32_t nsid)30 ns_info_test_inactive(nvme_ns_info_t *info, uint32_t nsid)
31 {
32 	uint8_t guid[16];
33 	bool ret = true;
34 	uint64_t val;
35 	const nvme_nvm_lba_fmt_t *fmt;
36 	const char *bd;
37 
38 	if (nvme_ns_info_nguid(info, guid)) {
39 		warnx("TEST FAILED: ns %u returned a namespace guid in error",
40 		    nsid);
41 		ret = false;
42 	} else if (nvme_ns_info_err(info) != NVME_INFO_ERR_NS_INACTIVE) {
43 		warnx("TEST FAILED: ns %u nvme_ns_info_nguid() returned "
44 		    "wrong error %s (0x%x), not NVME_INFO_ERR_NS_INACTIVE "
45 		    "(0x%x)", nsid, nvme_ns_info_errtostr(info,
46 		    nvme_ns_info_err(info)), nvme_ns_info_err(info),
47 		    NVME_INFO_ERR_NS_INACTIVE);
48 		ret = false;
49 	} else {
50 		(void) printf("TEST PASSED: ns %u: nvme_ns_info_nguid() "
51 		    "returned NVME_INFO_ERR_NS_INACTIVE\n", nsid);
52 	}
53 
54 	if (nvme_ns_info_eui64(info, guid)) {
55 		warnx("TEST FAILED: ns %u returned a namespace eui64 in error",
56 		    nsid);
57 		ret = false;
58 	} else if (nvme_ns_info_err(info) != NVME_INFO_ERR_NS_INACTIVE) {
59 		warnx("TEST FAILED: ns %u nvme_ns_info_eui64() returned "
60 		    "wrong error %s (0x%x), not NVME_INFO_ERR_NS_INACTIVE "
61 		    "(0x%x)", nsid, nvme_ns_info_errtostr(info,
62 		    nvme_ns_info_err(info)), nvme_ns_info_err(info),
63 		    NVME_INFO_ERR_NS_INACTIVE);
64 		ret = false;
65 	} else {
66 		(void) printf("TEST PASSED: ns %u: nvme_ns_info_eui64() "
67 		    "returned NVME_INFO_ERR_NS_INACTIVE\n", nsid);
68 	}
69 
70 	if (nvme_ns_info_size(info, &val)) {
71 		warnx("TEST FAILED: ns %u returned a namespace size in error",
72 		    nsid);
73 		ret = false;
74 	} else if (nvme_ns_info_err(info) != NVME_INFO_ERR_NS_INACTIVE) {
75 		warnx("TEST FAILED: ns %u nvme_ns_info_size() returned "
76 		    "wrong error %s (0x%x), not NVME_INFO_ERR_NS_INACTIVE "
77 		    "(0x%x)", nsid, nvme_ns_info_errtostr(info,
78 		    nvme_ns_info_err(info)), nvme_ns_info_err(info),
79 		    NVME_INFO_ERR_NS_INACTIVE);
80 		ret = false;
81 	} else {
82 		(void) printf("TEST PASSED: ns %u: nvme_ns_info_size() "
83 		    "returned NVME_INFO_ERR_NS_INACTIVE\n", nsid);
84 	}
85 
86 	if (nvme_ns_info_cap(info, &val)) {
87 		warnx("TEST FAILED: ns %u returned a namespace cap in error",
88 		    nsid);
89 		ret = false;
90 	} else if (nvme_ns_info_err(info) != NVME_INFO_ERR_NS_INACTIVE) {
91 		warnx("TEST FAILED: ns %u nvme_ns_info_cap() returned "
92 		    "wrong error %s (0x%x), not NVME_INFO_ERR_NS_INACTIVE "
93 		    "(0x%x)", nsid, nvme_ns_info_errtostr(info,
94 		    nvme_ns_info_err(info)), nvme_ns_info_err(info),
95 		    NVME_INFO_ERR_NS_INACTIVE);
96 		ret = false;
97 	} else {
98 		(void) printf("TEST PASSED: ns %u: nvme_ns_info_cap() "
99 		    "returned NVME_INFO_ERR_NS_INACTIVE\n", nsid);
100 	}
101 
102 	if (nvme_ns_info_use(info, &val)) {
103 		warnx("TEST FAILED: ns %u returned a namespace use in error",
104 		    nsid);
105 		ret = false;
106 	} else if (nvme_ns_info_err(info) != NVME_INFO_ERR_NS_INACTIVE) {
107 		warnx("TEST FAILED: ns %u nvme_ns_info_use() returned "
108 		    "wrong error %s (0x%x), not NVME_INFO_ERR_NS_INACTIVE "
109 		    "(0x%x)", nsid, nvme_ns_info_errtostr(info,
110 		    nvme_ns_info_err(info)), nvme_ns_info_err(info),
111 		    NVME_INFO_ERR_NS_INACTIVE);
112 		ret = false;
113 	} else {
114 		(void) printf("TEST PASSED: ns %u: nvme_ns_info_use() "
115 		    "returned NVME_INFO_ERR_NS_INACTIVE\n", nsid);
116 	}
117 
118 	if (nvme_ns_info_curformat(info, &fmt)) {
119 		warnx("TEST FAILED: ns %u returned a current format in error",
120 		    nsid);
121 		ret = false;
122 	} else if (nvme_ns_info_err(info) != NVME_INFO_ERR_NS_INACTIVE) {
123 		warnx("TEST FAILED: ns %u nvme_ns_info_curformat() returned "
124 		    "wrong error %s (0x%x), not NVME_INFO_ERR_NS_INACTIVE "
125 		    "(0x%x)", nsid, nvme_ns_info_errtostr(info,
126 		    nvme_ns_info_err(info)), nvme_ns_info_err(info),
127 		    NVME_INFO_ERR_NS_INACTIVE);
128 		ret = false;
129 	} else {
130 		(void) printf("TEST PASSED: ns %u: nvme_ns_info_curformat() "
131 		    "returned NVME_INFO_ERR_NS_INACTIVE\n", nsid);
132 	}
133 
134 	if (nvme_ns_info_format(info, 0, &fmt)) {
135 		warnx("TEST FAILED: ns %u returned format 0 in error",
136 		    nsid);
137 		ret = false;
138 	} else if (nvme_ns_info_err(info) != NVME_INFO_ERR_NS_INACTIVE) {
139 		warnx("TEST FAILED: ns %u nvme_ns_info_format() returned "
140 		    "wrong error %s (0x%x), not NVME_INFO_ERR_NS_INACTIVE "
141 		    "(0x%x)", nsid, nvme_ns_info_errtostr(info,
142 		    nvme_ns_info_err(info)), nvme_ns_info_err(info),
143 		    NVME_INFO_ERR_NS_INACTIVE);
144 		ret = false;
145 	} else {
146 		(void) printf("TEST PASSED: ns %u: nvme_ns_info_curformat() "
147 		    "returned NVME_INFO_ERR_NS_INACTIVE\n", nsid);
148 	}
149 
150 	if (nvme_ns_info_bd_addr(info, &bd)) {
151 		warnx("TEST FAILED: ns %u returned a blkdev address in error",
152 		    nsid);
153 		ret = false;
154 	} else if (nvme_ns_info_err(info) != NVME_INFO_ERR_NS_NO_BLKDEV) {
155 		warnx("TEST FAILED: ns %u nvme_ns_info_bd_addr() returned "
156 		    "wrong error %s (0x%x), not NVME_INFO_ERR_NS_NO_BLKDEV "
157 		    "(0x%x)", nsid, nvme_ns_info_errtostr(info,
158 		    nvme_ns_info_err(info)), nvme_ns_info_err(info),
159 		    NVME_INFO_ERR_NS_NO_BLKDEV);
160 		ret = false;
161 	} else {
162 		(void) printf("TEST PASSED: ns %u: nvme_ns_info_curformat() "
163 		    "returned NVME_INFO_ERR_NS_NO_BLKDEV\n", nsid);
164 	}
165 
166 	return (ret);
167 }
168 
169 static bool
ns_info_test_size(nvme_ns_info_t * info,bool (* func)(nvme_ns_info_t *,uint64_t *),uint64_t exp_size,const char * name,uint32_t nsid)170 ns_info_test_size(nvme_ns_info_t *info,
171     bool (*func)(nvme_ns_info_t *, uint64_t *), uint64_t exp_size,
172     const char *name, uint32_t nsid)
173 {
174 	uint64_t val;
175 
176 	if (!func(info, &val)) {
177 		libnvme_test_ns_info_warn(info, "ns %u nvme_ns_info_%s() "
178 		    "unexpected failed", nsid, name);
179 		return (false);
180 	} else if (val != exp_size) {
181 		warnx("TEST FAILED: ns %u: nvme_ns_info_%s() value was 0x%"
182 		    PRIx64 ", but expected 0x%" PRIx64, nsid, name, val,
183 		    exp_size);
184 		return (false);
185 	} else {
186 		(void) printf("TEST PASSED: ns %u: nvme_ns_info_%s() returned "
187 		    "correct value\n", nsid, name);
188 		return (true);
189 	}
190 }
191 
192 static bool
ns_info_test(nvme_ctrl_t * ctrl,const nvme_version_t * vers,uint32_t nsid)193 ns_info_test(nvme_ctrl_t *ctrl, const nvme_version_t *vers, uint32_t nsid)
194 {
195 	bool ret = true;
196 	nvme_ns_t *ns = NULL;
197 	nvme_ns_info_t *info = NULL;
198 	nvme_ns_disc_level_t level;
199 	const nvme_identify_nsid_t *idns;
200 	const nvme_nvm_lba_fmt_t *fmt;
201 	uint32_t nfmt;
202 
203 	/*
204 	 * We do this to test both ways of taking a snapshot.
205 	 */
206 	if ((nsid % 2) == 0) {
207 		if (!nvme_ns_init(ctrl, nsid, &ns)) {
208 			libnvme_test_ctrl_warn(ctrl, "failed to init ns %u",
209 			    nsid);
210 			ret = false;
211 			goto done;
212 		}
213 
214 		if (!nvme_ns_info_snap(ns, &info)) {
215 			libnvme_test_ctrl_warn(ctrl, "failed to take snapshot "
216 			    "of ns %u", nsid);
217 			ret = false;
218 			goto done;
219 		}
220 	} else {
221 		if (!nvme_ctrl_ns_info_snap(ctrl, nsid, &info)) {
222 			libnvme_test_ctrl_warn(ctrl, "failed to take snapshot "
223 			    "of ns %u", nsid);
224 			ret = false;
225 			goto done;
226 		}
227 	}
228 
229 	(void) printf("TEST PASSED: ns %u: successfully got info snapshot\n",
230 	    nsid);
231 	if (nvme_ns_info_nsid(info) != nsid) {
232 		warnx("TEST FAILED: nsid %u info snapshot returned wrong "
233 		    "nsid: %u", nsid, nvme_ns_info_nsid(info));
234 		ret = false;
235 	} else {
236 		(void) printf("TEST PASSED: ns %u: info snapshot had correct "
237 		    "nsid\n", nsid);
238 	}
239 
240 	/*
241 	 * The rest of the information snapshot APIs depend on if the namespace
242 	 * is active in the controller. If it is not, then we expect each
243 	 * function to fail. Even if we have an active namespace, this may fail
244 	 * if a controller isn't of a sufficient version.
245 	 */
246 	level = nvme_ns_info_level(info);
247 	if (level < NVME_NS_DISC_F_ACTIVE) {
248 		if (!ns_info_test_inactive(info, nsid)) {
249 			ret = false;
250 		}
251 		goto done;
252 	}
253 
254 	idns = nvme_ns_info_identify(info);
255 
256 	/*
257 	 * We don't explicitly test the GUID logic or blkdev address here. That
258 	 * is done in the ns-disc.c test.
259 	 */
260 
261 	if (!ns_info_test_size(info, nvme_ns_info_size, idns->id_nsize, "size",
262 	    nsid)) {
263 		ret = false;
264 	}
265 
266 	if (!ns_info_test_size(info, nvme_ns_info_cap, idns->id_ncap, "cap",
267 	    nsid)) {
268 		ret = false;
269 	}
270 
271 	if (!ns_info_test_size(info, nvme_ns_info_use, idns->id_nuse, "use",
272 	    nsid)) {
273 		ret = false;
274 	}
275 
276 	if (!nvme_ns_info_curformat(info, &fmt)) {
277 		libnvme_test_ns_info_warn(info, "ns %u failed to get current "
278 		    "format", nsid);
279 		ret = false;
280 	} else if (nvme_nvm_lba_fmt_id(fmt) != idns->id_flbas.lba_format) {
281 		warnx("TEST FAILED: current LBA format 0x%x does not match "
282 		    "identify namespace 0x%x", nvme_nvm_lba_fmt_id(fmt),
283 		    idns->id_flbas.lba_format);
284 		ret = false;
285 	} else {
286 		(void) printf("TEST PASSED: ns %u: nvme_ns_info_curformat() "
287 		    "returned correct format\n", nsid);
288 	}
289 
290 	if (!nvme_ns_info_nformats(info, &nfmt)) {
291 		libnvme_test_ns_info_warn(info, "ns %u failed to get number "
292 		    "of formats", nsid);
293 		ret = false;
294 	} else if (nfmt != idns->id_nlbaf + 1) {
295 		warnx("TEST FAILED: number of LBA formats 0x%x does not match "
296 		    "identify namespace 0x%x", nvme_nvm_lba_fmt_id(fmt),
297 		    idns->id_nlbaf + 1);
298 		ret = false;
299 	} else {
300 		(void) printf("TEST PASSED: ns %u: nvme_ns_info_nformats() "
301 		    "returned correct number of formats\n", nsid);
302 	}
303 
304 	if (nvme_ns_info_format(info, 0x7777, &fmt)) {
305 		warnx("TEST FAILED: ns %u erroneously returned info for format "
306 		    "0x7777", nsid);
307 		ret = false;
308 	} else if (nvme_ns_info_err(info) != NVME_INFO_ERR_BAD_FMT) {
309 		warnx("TEST FAILED: ns %u nvme_ns_info_format() returned "
310 		    "wrong error %s (0x%x), not NVME_INFO_ERR_BAD_FMT "
311 		    "(0x%x)", nsid, nvme_ns_info_errtostr(info,
312 		    nvme_ns_info_err(info)), nvme_ns_info_err(info),
313 		    NVME_INFO_ERR_BAD_FMT);
314 		ret = false;
315 	} else {
316 		(void) printf("TEST PASSED: ns %u: invalid format id 0x7777 "
317 		    "correctly rejected\n", nsid);
318 	}
319 
320 done:
321 	nvme_ns_info_free(info);
322 	nvme_ns_fini(ns);
323 	return (ret);
324 }
325 
326 static bool
ns_info_bad_snap(nvme_ctrl_t * ctrl,uint32_t nsid,nvme_ns_info_t ** infop,nvme_err_t exp_err,const char * desc)327 ns_info_bad_snap(nvme_ctrl_t *ctrl, uint32_t nsid, nvme_ns_info_t **infop,
328     nvme_err_t exp_err, const char *desc)
329 {
330 	if (nvme_ctrl_ns_info_snap(ctrl, nsid, infop)) {
331 		warnx("TEST FAILED: nvme_ctrl_ns_info_snap() erroneously "
332 		    "passed despite %s", desc);
333 		return (false);
334 	} else if (nvme_ctrl_err(ctrl) != exp_err) {
335 		warnx("TEST FAILED: nvme_ctrl_ns_info_snap() returned "
336 		    "wrong error %s (0x%x), not %s (0x%x)",
337 		    nvme_ctrl_errtostr(ctrl, nvme_ctrl_err(ctrl)),
338 		    nvme_ctrl_err(ctrl), nvme_ctrl_errtostr(ctrl,
339 		    exp_err), exp_err);
340 
341 		return (false);
342 	} else {
343 		(void) printf("TEST PASSED: nvme_ctrl_ns_info_snap() failed "
344 		    "correctly for %s\n", desc);
345 		return (true);
346 	}
347 }
348 
349 int
main(void)350 main(void)
351 {
352 	int ret = EXIT_SUCCESS;
353 	nvme_t *nvme;
354 	nvme_ctrl_t *ctrl;
355 	nvme_ctrl_info_t *info;
356 	nvme_ns_info_t *ns_info;
357 	uint32_t nns;
358 	const nvme_version_t *vers;
359 
360 	libnvme_test_init(&nvme, &ctrl);
361 
362 	if (!nvme_ctrl_info_snap(ctrl, &info)) {
363 		libnvme_test_ctrl_fatal(ctrl, "failed to take information "
364 		    "snapshot");
365 	}
366 
367 	nns = nvme_ctrl_info_nns(info);
368 	if (nns == 0) {
369 		errx(EXIT_FAILURE, "TEST FAILED: somehow discovered 0 "
370 		    "namespaces");
371 	}
372 
373 	vers = nvme_ctrl_info_version(info);
374 	for (uint32_t i = 1; i <= nns; i++) {
375 		if (!ns_info_test(ctrl, vers, i)) {
376 			ret = EXIT_FAILURE;
377 		}
378 	}
379 
380 	/*
381 	 * Explicitly verify a few failures of namespace information snapshots.
382 	 */
383 	if (!ns_info_bad_snap(ctrl, NVME_NSID_BCAST, &ns_info,
384 	    NVME_ERR_NS_RANGE, "invalid nsid")) {
385 		ret = EXIT_FAILURE;
386 	}
387 
388 	if (!ns_info_bad_snap(ctrl, 1, NULL, NVME_ERR_BAD_PTR,
389 	    "invalid output pointer")) {
390 		ret = EXIT_FAILURE;
391 	}
392 
393 	umem_setmtbf(1);
394 	if (!ns_info_bad_snap(ctrl, 1, &ns_info, NVME_ERR_NO_MEM,
395 	    "no memory")) {
396 		ret = EXIT_FAILURE;
397 	}
398 	umem_setmtbf(0);
399 
400 	if (ret == EXIT_SUCCESS) {
401 		(void) printf("All tests passed successfully\n");
402 	}
403 
404 	nvme_ctrl_info_free(info);
405 	nvme_ctrl_fini(ctrl);
406 	nvme_fini(nvme);
407 	return (ret);
408 }
409