xref: /illumos-gate/usr/src/test/nvme-tests/tests/libnvme/ns-disc.c (revision 533affcbc7fc4d0c8132976ea454aaa715fe2307)
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
ns_disc_count_cb(nvme_ctrl_t * ctrl,const nvme_ns_disc_t * disc,void * arg)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
ns_disc_count(nvme_ctrl_t * ctrl,nvme_ns_disc_level_t level,uint32_t exp)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
ns_disc_blkdev_cb(nvme_ctrl_t * ctrl,const nvme_ns_disc_t * disc,void * arg)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
ns_disc_guids_cb(nvme_ctrl_t * ctrl,const nvme_ns_disc_t * disc,void * arg)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
ns_disc_level_cb(nvme_ctrl_t * ctrl,const nvme_ns_disc_t * disc,void * arg)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
ns_disc_bad_disc_init(nvme_ctrl_t * ctrl,nvme_ns_disc_level_t level,nvme_ns_iter_t ** iterp,nvme_err_t exp_err,const char * desc)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
ns_disc_bad_disc(nvme_ctrl_t * ctrl,nvme_ns_disc_level_t level,nvme_ns_disc_f func,nvme_err_t exp_err,const char * desc)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
ns_disc_nop_cb(nvme_ctrl_t * ctrl,const nvme_ns_disc_t * disc,void * arg)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
main(void)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