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