xref: /illumos-gate/usr/src/cmd/nvmeadm/nvmeadm.c (revision 430fb0518974971393f591123b410c866df1855a)
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 2017 Joyent, Inc.
14  * Copyright 2021 Oxide Computer Company
15  * Copyright 2022 Tintri by DDN, Inc. All rights reserved.
16  */
17 
18 /*
19  * nvmeadm -- NVMe administration utility
20  *
21  * nvmeadm [-v] [-d] [-h] <command> [<ctl>[/<ns>][,...]] [args]
22  * commands:	list
23  *		identify
24  *		get-logpage <logpage name>
25  *		get-features <feature>[,...]
26  *		format ...
27  *		secure-erase ...
28  *		detach ...
29  *		attach ...
30  *		list-firmware ...
31  *		load-firmware ...
32  *		commit-firmware ...
33  *		activate-firmware ...
34  */
35 
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <stddef.h>
39 #include <unistd.h>
40 #include <fcntl.h>
41 #include <strings.h>
42 #include <ctype.h>
43 #include <err.h>
44 #include <sys/sunddi.h>
45 #include <libdevinfo.h>
46 
47 #include <sys/nvme.h>
48 
49 #include "nvmeadm.h"
50 
51 /*
52  * Assertions to make sure that we've properly captured various aspects of the
53  * packed structures and haven't broken them during updates.
54  */
55 CTASSERT(sizeof (nvme_identify_ctrl_t) == NVME_IDENTIFY_BUFSIZE);
56 CTASSERT(offsetof(nvme_identify_ctrl_t, id_oacs) == 256);
57 CTASSERT(offsetof(nvme_identify_ctrl_t, id_sqes) == 512);
58 CTASSERT(offsetof(nvme_identify_ctrl_t, id_oncs) == 520);
59 CTASSERT(offsetof(nvme_identify_ctrl_t, id_subnqn) == 768);
60 CTASSERT(offsetof(nvme_identify_ctrl_t, id_nvmof) == 1792);
61 CTASSERT(offsetof(nvme_identify_ctrl_t, id_psd) == 2048);
62 CTASSERT(offsetof(nvme_identify_ctrl_t, id_vs) == 3072);
63 
64 CTASSERT(sizeof (nvme_identify_nsid_t) == NVME_IDENTIFY_BUFSIZE);
65 CTASSERT(offsetof(nvme_identify_nsid_t, id_fpi) == 32);
66 CTASSERT(offsetof(nvme_identify_nsid_t, id_anagrpid) == 92);
67 CTASSERT(offsetof(nvme_identify_nsid_t, id_nguid) == 104);
68 CTASSERT(offsetof(nvme_identify_nsid_t, id_lbaf) == 128);
69 CTASSERT(offsetof(nvme_identify_nsid_t, id_vs) == 384);
70 
71 CTASSERT(sizeof (nvme_identify_nsid_list_t) == NVME_IDENTIFY_BUFSIZE);
72 CTASSERT(sizeof (nvme_identify_ctrl_list_t) == NVME_IDENTIFY_BUFSIZE);
73 
74 CTASSERT(sizeof (nvme_identify_primary_caps_t) == NVME_IDENTIFY_BUFSIZE);
75 CTASSERT(offsetof(nvme_identify_primary_caps_t, nipc_vqfrt) == 32);
76 CTASSERT(offsetof(nvme_identify_primary_caps_t, nipc_vifrt) == 64);
77 
78 CTASSERT(sizeof (nvme_nschange_list_t) == 4096);
79 
80 
81 struct nvme_feature {
82 	char *f_name;
83 	char *f_short;
84 	uint8_t f_feature;
85 	size_t f_bufsize;
86 	uint_t f_getflags;
87 	int (*f_get)(int, const nvme_feature_t *, const nvme_process_arg_t *);
88 	void (*f_print)(uint64_t, void *, size_t, nvme_identify_ctrl_t *,
89 	    nvme_version_t *);
90 };
91 
92 #define	NVMEADM_F_CTRL	1
93 #define	NVMEADM_F_NS	2
94 #define	NVMEADM_F_BOTH	(NVMEADM_F_CTRL | NVMEADM_F_NS)
95 
96 #define	NVMEADM_C_MULTI	1
97 #define	NVMEADM_C_EXCL	2
98 
99 struct nvmeadm_cmd {
100 	char *c_name;
101 	const char *c_desc;
102 	const char *c_flagdesc;
103 	int (*c_func)(int, const nvme_process_arg_t *);
104 	void (*c_usage)(const char *);
105 	void (*c_optparse)(nvme_process_arg_t *);
106 	int c_flags;
107 };
108 
109 
110 static void usage(const nvmeadm_cmd_t *);
111 static void nvme_walk(nvme_process_arg_t *, di_node_t);
112 static boolean_t nvme_match(nvme_process_arg_t *);
113 
114 static int nvme_process(di_node_t, di_minor_t, void *);
115 
116 static int do_list(int, const nvme_process_arg_t *);
117 static int do_identify(int, const nvme_process_arg_t *);
118 static int do_identify_ctrl(int, const nvme_process_arg_t *);
119 static int do_identify_ns(int, const nvme_process_arg_t *);
120 static int do_get_logpage_error(int, const nvme_process_arg_t *);
121 static int do_get_logpage_health(int, const nvme_process_arg_t *);
122 static int do_get_logpage_fwslot(int, const nvme_process_arg_t *);
123 static int do_get_logpage(int, const nvme_process_arg_t *);
124 static int do_get_feat_common(int, const nvme_feature_t *,
125     const nvme_process_arg_t *);
126 static int do_get_feat_intr_vect(int, const nvme_feature_t *,
127     const nvme_process_arg_t *);
128 static int do_get_feat_temp_thresh(int, const nvme_feature_t *,
129     const nvme_process_arg_t *);
130 static int do_get_features(int, const nvme_process_arg_t *);
131 static int do_format(int, const nvme_process_arg_t *);
132 static int do_secure_erase(int, const nvme_process_arg_t *);
133 static int do_attach_detach(int, const nvme_process_arg_t *);
134 static int do_firmware_load(int, const nvme_process_arg_t *);
135 static int do_firmware_commit(int, const nvme_process_arg_t *);
136 static int do_firmware_activate(int, const nvme_process_arg_t *);
137 
138 static void optparse_list(nvme_process_arg_t *);
139 static void optparse_identify(nvme_process_arg_t *);
140 static void optparse_identify_ctrl(nvme_process_arg_t *);
141 static void optparse_identify_ns(nvme_process_arg_t *);
142 static void optparse_secure_erase(nvme_process_arg_t *);
143 
144 static void usage_list(const char *);
145 static void usage_identify(const char *);
146 static void usage_identify_ctrl(const char *);
147 static void usage_identify_ns(const char *);
148 static void usage_get_logpage(const char *);
149 static void usage_get_features(const char *);
150 static void usage_format(const char *);
151 static void usage_secure_erase(const char *);
152 static void usage_attach_detach(const char *);
153 static void usage_firmware_list(const char *);
154 static void usage_firmware_load(const char *);
155 static void usage_firmware_commit(const char *);
156 static void usage_firmware_activate(const char *);
157 
158 int verbose;
159 int debug;
160 
161 #define	NVMEADM_O_SE_CRYPTO	0x00000004
162 
163 #define	NVMEADM_O_ID_NSID_LIST	0x00000008
164 #define	NVMEADM_O_ID_COMMON_NS	0x00000010
165 #define	NVMEADM_O_ID_CTRL_LIST	0x00000020
166 #define	NVMEADM_O_ID_DESC_LIST	0x00000040
167 #define	NVMEADM_O_ID_ALLOC_NS	0x00000080
168 
169 static int exitcode;
170 
171 /*
172  * Nvmeadm subcommand definitons.
173  *
174  * When adding a new subcommand, please check that the commands still
175  * line up in the usage() message, and adjust the format string in
176  * usage() below if necessary.
177  */
178 static const nvmeadm_cmd_t nvmeadm_cmds[] = {
179 	{
180 		"list",
181 		"list controllers and namespaces",
182 		"  -p\t\tprint parsable output\n"
183 		    "  -o field\tselect a field for parsable output\n",
184 		do_list, usage_list, optparse_list,
185 		NVMEADM_C_MULTI
186 	},
187 	{
188 		"identify",
189 		"identify controllers and/or namespaces",
190 		"  -C\t\tget Common Namespace Identification\n"
191 		"  -a\t\tget only allocated namespace information\n"
192 		"  -c\t\tget controller identifier list\n"
193 		"  -d\t\tget namespace identification descriptors list\n"
194 		"  -n\t\tget namespaces identifier list\n",
195 		do_identify, usage_identify, optparse_identify,
196 		NVMEADM_C_MULTI
197 	},
198 	{
199 		"identify-controller",
200 		"identify controllers",
201 		"  -C\t\tget Common Namespace Identification\n"
202 		"  -a\t\tget only allocated namespace information\n"
203 		"  -c\t\tget controller identifier list\n"
204 		"  -n\t\tget namespaces identifier list\n",
205 		do_identify_ctrl, usage_identify_ctrl, optparse_identify_ctrl,
206 		NVMEADM_C_MULTI
207 	},
208 	{
209 		"identify-namespace",
210 		"identify namespaces",
211 		"  -c\t\tget attached controller identifier list\n"
212 		"  -d\t\tget namespace identification descriptors list\n",
213 		do_identify_ns, usage_identify_ns, optparse_identify_ns,
214 		NVMEADM_C_MULTI
215 	},
216 	{
217 		"get-logpage",
218 		"get a log page from controllers and/or namespaces",
219 		NULL,
220 		do_get_logpage, usage_get_logpage, NULL,
221 		NVMEADM_C_MULTI
222 	},
223 	{
224 		"get-features",
225 		"get features from controllers and/or namespaces",
226 		NULL,
227 		do_get_features, usage_get_features, NULL,
228 		NVMEADM_C_MULTI
229 	},
230 	{
231 		"format",
232 		"format namespace(s) of a controller",
233 		NULL,
234 		do_format, usage_format, NULL,
235 		NVMEADM_C_EXCL
236 	},
237 	{
238 		"secure-erase",
239 		"secure erase namespace(s) of a controller",
240 		"  -c  Do a cryptographic erase.",
241 		do_secure_erase, usage_secure_erase, optparse_secure_erase,
242 		NVMEADM_C_EXCL
243 	},
244 	{
245 		"detach",
246 		"detach blkdev(4D) from namespace(s) of a controller",
247 		NULL,
248 		do_attach_detach, usage_attach_detach, NULL,
249 		NVMEADM_C_EXCL
250 	},
251 	{
252 		"attach",
253 		"attach blkdev(4D) to namespace(s) of a controller",
254 		NULL,
255 		do_attach_detach, usage_attach_detach, NULL,
256 		NVMEADM_C_EXCL
257 	},
258 	{
259 		"list-firmware",
260 		"list firmware on a controller",
261 		NULL,
262 		do_get_logpage_fwslot, usage_firmware_list, NULL,
263 		0
264 	},
265 	{
266 		"load-firmware",
267 		"load firmware to a controller",
268 		NULL,
269 		do_firmware_load, usage_firmware_load, NULL,
270 		0
271 	},
272 	{
273 		"commit-firmware",
274 		"commit downloaded firmware to a slot of a controller",
275 		NULL,
276 		do_firmware_commit, usage_firmware_commit, NULL,
277 		0
278 	},
279 	{
280 		"activate-firmware",
281 		"activate a firmware slot of a controller",
282 		NULL,
283 		do_firmware_activate, usage_firmware_activate, NULL,
284 		0
285 	},
286 	{
287 		NULL, NULL, NULL,
288 		NULL, NULL, NULL, 0
289 	}
290 };
291 
292 static const nvme_feature_t features[] = {
293 	{ "Arbitration", "",
294 	    NVME_FEAT_ARBITRATION, 0, NVMEADM_F_CTRL,
295 	    do_get_feat_common, nvme_print_feat_arbitration },
296 	{ "Power Management", "",
297 	    NVME_FEAT_POWER_MGMT, 0, NVMEADM_F_CTRL,
298 	    do_get_feat_common, nvme_print_feat_power_mgmt },
299 	{ "LBA Range Type", "range",
300 	    NVME_FEAT_LBA_RANGE, NVME_LBA_RANGE_BUFSIZE, NVMEADM_F_NS,
301 	    do_get_feat_common, nvme_print_feat_lba_range },
302 	{ "Temperature Threshold", "",
303 	    NVME_FEAT_TEMPERATURE, 0, NVMEADM_F_CTRL,
304 	    do_get_feat_temp_thresh, nvme_print_feat_temperature },
305 	{ "Error Recovery", "",
306 	    NVME_FEAT_ERROR, 0, NVMEADM_F_CTRL,
307 	    do_get_feat_common, nvme_print_feat_error },
308 	{ "Volatile Write Cache", "cache",
309 	    NVME_FEAT_WRITE_CACHE, 0, NVMEADM_F_CTRL,
310 	    do_get_feat_common, nvme_print_feat_write_cache },
311 	{ "Number of Queues", "queues",
312 	    NVME_FEAT_NQUEUES, 0, NVMEADM_F_CTRL,
313 	    do_get_feat_common, nvme_print_feat_nqueues },
314 	{ "Interrupt Coalescing", "coalescing",
315 	    NVME_FEAT_INTR_COAL, 0, NVMEADM_F_CTRL,
316 	    do_get_feat_common, nvme_print_feat_intr_coal },
317 	{ "Interrupt Vector Configuration", "vector",
318 	    NVME_FEAT_INTR_VECT, 0, NVMEADM_F_CTRL,
319 	    do_get_feat_intr_vect, nvme_print_feat_intr_vect },
320 	{ "Write Atomicity", "atomicity",
321 	    NVME_FEAT_WRITE_ATOM, 0, NVMEADM_F_CTRL,
322 	    do_get_feat_common, nvme_print_feat_write_atom },
323 	{ "Asynchronous Event Configuration", "event",
324 	    NVME_FEAT_ASYNC_EVENT, 0, NVMEADM_F_CTRL,
325 	    do_get_feat_common, nvme_print_feat_async_event },
326 	{ "Autonomous Power State Transition", "",
327 	    NVME_FEAT_AUTO_PST, NVME_AUTO_PST_BUFSIZE, NVMEADM_F_CTRL,
328 	    do_get_feat_common, nvme_print_feat_auto_pst },
329 	{ "Software Progress Marker", "progress",
330 	    NVME_FEAT_PROGRESS, 0, NVMEADM_F_CTRL,
331 	    do_get_feat_common, nvme_print_feat_progress },
332 	{ NULL, NULL, 0, 0, B_FALSE, NULL }
333 };
334 
335 
336 int
337 main(int argc, char **argv)
338 {
339 	int c;
340 	const nvmeadm_cmd_t *cmd;
341 	di_node_t node;
342 	nvme_process_arg_t npa = { 0 };
343 	int help = 0;
344 	char *tmp, *lasts = NULL;
345 	char *ctrl = NULL;
346 
347 	while ((c = getopt(argc, argv, "dhv")) != -1) {
348 		switch (c) {
349 		case 'd':
350 			debug++;
351 			break;
352 
353 		case 'v':
354 			verbose++;
355 			break;
356 
357 		case 'h':
358 			help++;
359 			break;
360 
361 		case '?':
362 			usage(NULL);
363 			exit(-1);
364 		}
365 	}
366 
367 	if (optind == argc) {
368 		usage(NULL);
369 		if (help)
370 			exit(0);
371 		else
372 			exit(-1);
373 	}
374 
375 	/* Look up the specified command in the command table. */
376 	for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++)
377 		if (strcmp(cmd->c_name, argv[optind]) == 0)
378 			break;
379 
380 	if (cmd->c_name == NULL) {
381 		usage(NULL);
382 		exit(-1);
383 	}
384 
385 	if (help) {
386 		usage(cmd);
387 		exit(0);
388 	}
389 
390 	npa.npa_cmd = cmd;
391 	npa.npa_interactive = B_TRUE;
392 	npa.npa_excl = ((cmd->c_flags & NVMEADM_C_EXCL) != 0);
393 
394 	optind++;
395 
396 	/*
397 	 * Store the remaining arguments for use by the command. Give the
398 	 * command a chance to process the options across the board before going
399 	 * into each controller.
400 	 */
401 	npa.npa_argc = argc - optind;
402 	npa.npa_argv = &argv[optind];
403 
404 	if (cmd->c_optparse != NULL) {
405 		cmd->c_optparse(&npa);
406 	}
407 
408 	/*
409 	 * All commands but "list" require a ctl/ns argument. However, this
410 	 * should not be passed through to the command in its subsequent
411 	 * arguments.
412 	 */
413 	if ((npa.npa_argc == 0 || (strncmp(npa.npa_argv[0], "nvme", 4) != 0)) &&
414 	    cmd->c_func != do_list) {
415 		warnx("missing controller/namespace name");
416 		usage(cmd);
417 		exit(-1);
418 	}
419 
420 	if (npa.npa_argc > 0) {
421 		ctrl = npa.npa_argv[0];
422 		npa.npa_argv++;
423 		npa.npa_argc--;
424 	} else {
425 		ctrl = NULL;
426 	}
427 
428 	/*
429 	 * Make sure we're not running commands on multiple controllers that
430 	 * aren't allowed to do that.
431 	 */
432 	if (ctrl != NULL && strchr(ctrl, ',') != NULL &&
433 	    (cmd->c_flags & NVMEADM_C_MULTI) == 0) {
434 		warnx("%s not allowed on multiple controllers",
435 		    cmd->c_name);
436 		usage(cmd);
437 		exit(-1);
438 	}
439 
440 	/*
441 	 * Get controller/namespace arguments and run command.
442 	 */
443 	npa.npa_name = strtok_r(ctrl, ",", &lasts);
444 	do {
445 		if (npa.npa_name != NULL) {
446 			tmp = strchr(npa.npa_name, '/');
447 			if (tmp != NULL) {
448 				*tmp++ = '\0';
449 				npa.npa_nsid = tmp;
450 				npa.npa_isns = B_TRUE;
451 			}
452 		}
453 
454 		if ((node = di_init("/", DINFOSUBTREE | DINFOMINOR)) == NULL)
455 			err(-1, "failed to initialize libdevinfo");
456 		nvme_walk(&npa, node);
457 		di_fini(node);
458 
459 		if (npa.npa_found == 0) {
460 			if (npa.npa_name != NULL) {
461 				warnx("%s%.*s%.*s: no such controller or "
462 				    "namespace", npa.npa_name,
463 				    npa.npa_isns ? -1 : 0, "/",
464 				    npa.npa_isns ? -1 : 0, npa.npa_nsid);
465 			} else {
466 				warnx("no controllers found");
467 			}
468 			exitcode--;
469 		}
470 		npa.npa_found = 0;
471 		npa.npa_name = strtok_r(NULL, ",", &lasts);
472 	} while (npa.npa_name != NULL);
473 
474 	exit(exitcode);
475 }
476 
477 static void
478 nvme_oferr(const char *fmt, ...)
479 {
480 	va_list ap;
481 
482 	va_start(ap, fmt);
483 	verrx(-1, fmt, ap);
484 }
485 
486 static void
487 usage(const nvmeadm_cmd_t *cmd)
488 {
489 	const char *progname = getprogname();
490 
491 	(void) fprintf(stderr, "usage:\n");
492 	(void) fprintf(stderr, "  %s -h %s\n", progname,
493 	    cmd != NULL ? cmd->c_name : "[<command>]");
494 	(void) fprintf(stderr, "  %s [-dv] ", progname);
495 
496 	if (cmd != NULL) {
497 		cmd->c_usage(cmd->c_name);
498 	} else {
499 		(void) fprintf(stderr,
500 		    "<command> <ctl>[/<ns>][,...] [<args>]\n");
501 		(void) fprintf(stderr,
502 		    "\n  Manage NVMe controllers and namespaces.\n");
503 		(void) fprintf(stderr, "\ncommands:\n");
504 
505 		for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++) {
506 			/*
507 			 * The longest nvmeadm subcommand is 19 characters long.
508 			 * The format string needs to be updated every time a
509 			 * longer subcommand is added.
510 			 */
511 			(void) fprintf(stderr, "  %-19s - %s\n",
512 			    cmd->c_name, cmd->c_desc);
513 		}
514 	}
515 	(void) fprintf(stderr, "\n%s flags:\n"
516 	    "  -h\t\tprint usage information\n"
517 	    "  -d\t\tprint information useful for debugging %s\n"
518 	    "  -v\t\tprint verbose information\n",
519 	    progname, progname);
520 
521 	if (cmd != NULL && cmd->c_flagdesc != NULL) {
522 		(void) fprintf(stderr, "\n%s %s flags:\n",
523 		    progname, cmd->c_name);
524 		(void) fprintf(stderr, "%s\n", cmd->c_flagdesc);
525 	}
526 }
527 
528 static boolean_t
529 nvme_match(nvme_process_arg_t *npa)
530 {
531 	char *name;
532 	char *nsid = NULL;
533 
534 	if (npa->npa_name == NULL)
535 		return (B_TRUE);
536 
537 	if (asprintf(&name, "%s%d", di_driver_name(npa->npa_node),
538 	    di_instance(npa->npa_node)) < 0)
539 		err(-1, "nvme_match()");
540 
541 	if (strcmp(name, npa->npa_name) != 0) {
542 		free(name);
543 		return (B_FALSE);
544 	}
545 
546 	free(name);
547 
548 	if (npa->npa_isns) {
549 		if (npa->npa_nsid == NULL)
550 			return (B_TRUE);
551 
552 		nsid = di_minor_name(npa->npa_minor);
553 
554 		if (nsid == NULL || strcmp(npa->npa_nsid, nsid) != 0)
555 			return (B_FALSE);
556 	}
557 
558 	return (B_TRUE);
559 }
560 
561 char *
562 nvme_dskname(const nvme_process_arg_t *npa)
563 {
564 	char *path = NULL;
565 	di_node_t child;
566 	di_dim_t dim;
567 	char *addr;
568 	char *disk_ctd;
569 	char *diskname = NULL;
570 
571 	dim = di_dim_init();
572 
573 	for (child = di_child_node(npa->npa_node);
574 	    child != DI_NODE_NIL;
575 	    child = di_sibling_node(child)) {
576 		addr = di_bus_addr(child);
577 		if (addr == NULL)
578 			continue;
579 
580 		if (addr[0] == 'w')
581 			addr++;
582 
583 		if (strncasecmp(addr, di_minor_name(npa->npa_minor),
584 		    strchrnul(addr, ',') - addr) != 0)
585 			continue;
586 
587 		path = di_dim_path_dev(dim, di_driver_name(child),
588 		    di_instance(child), "c");
589 
590 		/*
591 		 * Error out if we didn't get a path, or if it's too short for
592 		 * the following operations to be safe.
593 		 */
594 		if (path == NULL || strlen(path) < 2)
595 			goto fail;
596 
597 		/* Chop off 's0' and get everything past the last '/' */
598 		path[strlen(path) - 2] = '\0';
599 		disk_ctd = strrchr(path, '/');
600 		if (disk_ctd == NULL)
601 			goto fail;
602 		diskname = strdup(++disk_ctd);
603 		if (diskname == NULL)
604 			goto fail;
605 
606 		free(path);
607 		break;
608 	}
609 
610 	di_dim_fini(dim);
611 
612 	return (diskname);
613 
614 fail:
615 	free(path);
616 	err(-1, "nvme_dskname");
617 }
618 
619 static int
620 nvme_process(di_node_t node, di_minor_t minor, void *arg)
621 {
622 	nvme_process_arg_t *npa = arg;
623 	int fd;
624 
625 	npa->npa_node = node;
626 	npa->npa_minor = minor;
627 
628 	if (!nvme_match(npa))
629 		return (DI_WALK_CONTINUE);
630 
631 	if ((fd = nvme_open(minor, npa->npa_excl)) < 0)
632 		return (DI_WALK_CONTINUE);
633 
634 	npa->npa_found++;
635 
636 	npa->npa_path = di_devfs_path(node);
637 	if (npa->npa_path == NULL)
638 		goto out;
639 
640 	npa->npa_version = nvme_version(fd);
641 	if (npa->npa_version == NULL)
642 		goto out;
643 
644 	npa->npa_idctl = nvme_identify(fd, NVME_IDENTIFY_CTRL);
645 	if (npa->npa_idctl == NULL)
646 		goto out;
647 
648 	if (nvme_version_check(npa->npa_version, 1, 2) &&
649 	    npa->npa_idctl->id_oacs.oa_nsmgmt != 0 &&
650 	    npa->npa_isns) {
651 		/*
652 		 * We prefer NVME_IDENTIFY_NSID_ALLOC when supported as that can
653 		 * return data on inactive namespaces, too.
654 		 */
655 		npa->npa_idns = nvme_identify(fd, NVME_IDENTIFY_NSID_ALLOC);
656 	} else {
657 		npa->npa_idns = nvme_identify(fd, NVME_IDENTIFY_NSID);
658 	}
659 
660 	if (npa->npa_idns == NULL)
661 		goto out;
662 
663 	npa->npa_dsk = NULL;
664 	if (npa->npa_isns) {
665 		npa->npa_ns_state = nvme_namespace_state(fd);
666 		if ((npa->npa_ns_state & NVME_NS_STATE_ATTACHED) != 0)
667 			npa->npa_dsk = nvme_dskname(npa);
668 	}
669 
670 
671 	exitcode += npa->npa_cmd->c_func(fd, npa);
672 
673 out:
674 	di_devfs_path_free(npa->npa_path);
675 	free(npa->npa_version);
676 	free(npa->npa_idctl);
677 	free(npa->npa_idns);
678 	free(npa->npa_dsk);
679 
680 	npa->npa_version = NULL;
681 	npa->npa_idctl = NULL;
682 	npa->npa_idns = NULL;
683 	npa->npa_dsk = NULL;
684 
685 	nvme_close(fd);
686 
687 	return (DI_WALK_CONTINUE);
688 }
689 
690 static void
691 nvme_walk(nvme_process_arg_t *npa, di_node_t node)
692 {
693 	char *minor_nodetype = DDI_NT_NVME_NEXUS;
694 
695 	if (npa->npa_isns)
696 		minor_nodetype = DDI_NT_NVME_ATTACHMENT_POINT;
697 
698 	(void) di_walk_minor(node, minor_nodetype, 0, npa, nvme_process);
699 }
700 
701 static void
702 usage_list(const char *c_name)
703 {
704 	(void) fprintf(stderr, "%s "
705 	    "[-p -o field[,...]] [<ctl>[/<ns>][,...]\n\n"
706 	    "  List NVMe controllers and their namespaces. If no "
707 	    "controllers and/or name-\n  spaces are specified, all "
708 	    "controllers and namespaces in the system will be\n  "
709 	    "listed.\n", c_name);
710 }
711 
712 static void
713 optparse_list(nvme_process_arg_t *npa)
714 {
715 	int c;
716 	uint_t oflags = 0;
717 	boolean_t parse = B_FALSE;
718 	const char *fields = NULL;
719 
720 	optind = 0;
721 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":o:p")) != -1) {
722 		switch (c) {
723 		case 'o':
724 			fields = optarg;
725 			break;
726 
727 		case 'p':
728 			parse = B_TRUE;
729 			oflags |= OFMT_PARSABLE;
730 			break;
731 
732 		case '?':
733 			errx(-1, "unknown option: -%c", optopt);
734 
735 		case ':':
736 			errx(-1, "option -%c requires an argument", optopt);
737 		}
738 	}
739 
740 	if (fields != NULL && !parse) {
741 		errx(-1, "-o can only be used when in parsable mode (-p)");
742 	}
743 
744 	if (parse && fields == NULL) {
745 		errx(-1, "parsable mode (-p) requires one to specify output "
746 		    "fields with -o");
747 	}
748 
749 	if (parse) {
750 		ofmt_status_t oferr;
751 
752 		oferr = ofmt_open(fields, nvme_list_ofmt, oflags, 0,
753 		    &npa->npa_ofmt);
754 		ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);
755 	}
756 
757 	npa->npa_argc -= optind;
758 	npa->npa_argv += optind;
759 }
760 
761 static int
762 do_list_nsid(int fd, const nvme_process_arg_t *npa)
763 {
764 	_NOTE(ARGUNUSED(fd));
765 	char *dskname;
766 
767 	if (!npa->npa_interactive &&
768 	    (npa->npa_ns_state & NVME_NS_STATE_IGNORED) != 0 &&
769 	    verbose == 0)
770 		return (0);
771 
772 	if (npa->npa_ofmt != NULL) {
773 		ofmt_print(npa->npa_ofmt, (void *)npa);
774 		return (0);
775 	}
776 
777 	if (npa->npa_ns_state == NVME_NS_STATE_IGNORED) {
778 		(void) printf("  %s/%s (unallocated)\n", npa->npa_name,
779 		    di_minor_name(npa->npa_minor));
780 	} else {
781 		if ((npa->npa_ns_state & NVME_NS_STATE_ATTACHED) != 0) {
782 			dskname = npa->npa_dsk;
783 		} else if ((npa->npa_ns_state & NVME_NS_STATE_ACTIVE) != 0) {
784 			if ((npa->npa_ns_state & NVME_NS_STATE_IGNORED) != 0) {
785 				dskname = "ignored";
786 			} else {
787 				dskname = "unattached";
788 			}
789 		} else if ((npa->npa_ns_state & NVME_NS_STATE_ALLOCATED) != 0) {
790 			dskname = "inactive";
791 		} else {
792 			dskname = "invalid state";
793 		}
794 		(void) printf("  %s/%s (%s): ", npa->npa_name,
795 		    di_minor_name(npa->npa_minor), dskname);
796 		nvme_print_nsid_summary(npa->npa_idns);
797 	}
798 
799 	return (0);
800 }
801 
802 static int
803 do_list(int fd, const nvme_process_arg_t *npa)
804 {
805 	_NOTE(ARGUNUSED(fd));
806 
807 	nvme_process_arg_t ns_npa = { 0 };
808 	nvmeadm_cmd_t cmd = { 0 };
809 	char *name;
810 
811 	if (asprintf(&name, "%s%d", di_driver_name(npa->npa_node),
812 	    di_instance(npa->npa_node)) < 0)
813 		err(-1, "do_list()");
814 
815 	if (npa->npa_ofmt == NULL) {
816 		(void) printf("%s: ", name);
817 		nvme_print_ctrl_summary(npa->npa_idctl, npa->npa_version);
818 	}
819 
820 	ns_npa.npa_name = name;
821 	ns_npa.npa_isns = B_TRUE;
822 	ns_npa.npa_nsid = npa->npa_nsid;
823 	cmd = *(npa->npa_cmd);
824 	cmd.c_func = do_list_nsid;
825 	ns_npa.npa_cmd = &cmd;
826 	ns_npa.npa_ofmt = npa->npa_ofmt;
827 	ns_npa.npa_idctl = npa->npa_idctl;
828 
829 	nvme_walk(&ns_npa, npa->npa_node);
830 
831 	free(name);
832 
833 	return (exitcode);
834 }
835 
836 static void
837 optparse_identify_ctrl(nvme_process_arg_t *npa)
838 {
839 	int c;
840 
841 	optind = 0;
842 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":Cacn")) != -1) {
843 		switch (c) {
844 		case 'C':
845 			npa->npa_cmdflags |= NVMEADM_O_ID_COMMON_NS;
846 			break;
847 
848 		case 'a':
849 			npa->npa_cmdflags |= NVMEADM_O_ID_ALLOC_NS;
850 			break;
851 
852 		case 'c':
853 			npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST;
854 			break;
855 
856 		case 'n':
857 			npa->npa_cmdflags |= NVMEADM_O_ID_NSID_LIST;
858 			break;
859 
860 		case '?':
861 			errx(-1, "unknown option: -%c", optopt);
862 
863 		case ':':
864 			errx(-1, "option -%c requires an argument", optopt);
865 		}
866 	}
867 
868 	npa->npa_argc -= optind;
869 	npa->npa_argv += optind;
870 }
871 
872 static void
873 usage_identify_ctrl(const char *c_name)
874 {
875 	(void) fprintf(stderr, "%s [-C | -c | [-a] -n] <ctl>[,...]\n\n"
876 	    "  Print detailed information about the specified NVMe "
877 	    "controllers.\n", c_name);
878 }
879 
880 static int
881 do_identify_ctrl(int fd, const nvme_process_arg_t *npa)
882 {
883 	boolean_t alloc = B_FALSE;
884 
885 	if (npa->npa_isns)
886 		errx(-1, "identify-controller cannot be used on namespaces");
887 
888 	if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0 &&
889 	    npa->npa_cmdflags != NVMEADM_O_ID_COMMON_NS) {
890 		errx(-1, "-C cannot be combined with other flags");
891 	}
892 
893 	if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 &&
894 	    npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) {
895 		errx(-1, "-c cannot be combined with other flags");
896 	}
897 
898 	if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0 &&
899 	    npa->npa_cmdflags !=
900 	    (NVMEADM_O_ID_ALLOC_NS | NVMEADM_O_ID_NSID_LIST)) {
901 		errx(-1, "-a can only be used together with -n");
902 	}
903 
904 	if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0) {
905 		if (!nvme_version_check(npa->npa_version, 1, 2)) {
906 			warnx("%s: -a is not supported on NVMe v%u.%u",
907 			    npa->npa_name, npa->npa_version->v_major,
908 			    npa->npa_version->v_minor);
909 			return (-1);
910 		}
911 
912 		if (npa->npa_idctl->id_oacs.oa_nsmgmt == 0) {
913 			warnx("%s: Namespace Management not supported",
914 			    npa->npa_name);
915 			return (-1);
916 		}
917 
918 		alloc = B_TRUE;
919 	}
920 
921 	if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0) {
922 		if (!nvme_version_check(npa->npa_version, 1, 2)) {
923 			warnx("%s: -C is not supported on NVMe v%u.%u",
924 			    npa->npa_name, npa->npa_version->v_major,
925 			    npa->npa_version->v_minor);
926 			return (-1);
927 		}
928 
929 		if (npa->npa_idctl->id_oacs.oa_nsmgmt == 0) {
930 			warnx("%s: Namespace Management not supported",
931 			    npa->npa_name);
932 			return (-1);
933 		}
934 
935 		(void) printf("%s: ", npa->npa_name);
936 		nvme_print_identify_nsid(npa->npa_idns, npa->npa_version);
937 	} else if ((npa->npa_cmdflags & NVMEADM_O_ID_NSID_LIST) != 0) {
938 		char *caption = "Identify Active Namespace List";
939 		nvme_identify_nsid_list_t *idnslist;
940 
941 		if (!nvme_version_check(npa->npa_version, 1, 1)) {
942 			warnx("%s: -n is not supported on NVMe v%u.%u",
943 			    npa->npa_name, npa->npa_version->v_major,
944 			    npa->npa_version->v_minor);
945 			return (-1);
946 		}
947 
948 		idnslist = nvme_identify(fd, alloc ?
949 		    NVME_IDENTIFY_NSID_ALLOC_LIST : NVME_IDENTIFY_NSID_LIST);
950 
951 		if (idnslist == NULL)
952 			return (-1);
953 
954 		if (alloc)
955 			caption = "Identify Allocated Namespace List";
956 
957 		(void) printf("%s: ", npa->npa_name);
958 
959 		nvme_print_identify_nsid_list(caption, idnslist);
960 		free(idnslist);
961 	} else if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0) {
962 		nvme_identify_ctrl_list_t *ctlist;
963 
964 		if (!nvme_version_check(npa->npa_version, 1, 2)) {
965 			warnx("%s: -c is not supported on NVMe v%u.%u",
966 			    npa->npa_name, npa->npa_version->v_major,
967 			    npa->npa_version->v_minor);
968 			return (-1);
969 		}
970 
971 		if (npa->npa_idctl->id_oacs.oa_nsmgmt == 0) {
972 			warnx("%s: Namespace Management not supported",
973 			    npa->npa_name);
974 			return (-1);
975 		}
976 
977 		ctlist = nvme_identify(fd, NVME_IDENTIFY_CTRL_LIST);
978 		if (ctlist == NULL)
979 			return (-1);
980 
981 		(void) printf("%s: ", npa->npa_name);
982 		nvme_print_identify_ctrl_list("Identify Controller List",
983 		    ctlist);
984 		free(ctlist);
985 	} else {
986 		nvme_capabilities_t *cap;
987 
988 		cap = nvme_capabilities(fd);
989 		if (cap == NULL)
990 			return (-1);
991 
992 		(void) printf("%s: ", npa->npa_name);
993 		nvme_print_identify_ctrl(npa->npa_idctl, cap, npa->npa_version);
994 
995 		free(cap);
996 	}
997 
998 	return (0);
999 }
1000 
1001 static void
1002 optparse_identify_ns(nvme_process_arg_t *npa)
1003 {
1004 	int c;
1005 
1006 	optind = 0;
1007 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":cd")) != -1) {
1008 		switch (c) {
1009 		case 'c':
1010 			npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST;
1011 			break;
1012 
1013 		case 'd':
1014 			npa->npa_cmdflags |= NVMEADM_O_ID_DESC_LIST;
1015 			break;
1016 
1017 		case '?':
1018 			errx(-1, "unknown option: -%c", optopt);
1019 
1020 		case ':':
1021 			errx(-1, "option -%c requires an argument", optopt);
1022 		}
1023 	}
1024 
1025 	npa->npa_argc -= optind;
1026 	npa->npa_argv += optind;
1027 }
1028 
1029 static void
1030 usage_identify_ns(const char *c_name)
1031 {
1032 	(void) fprintf(stderr, "%s [-c | -d ] <ctl>/<ns>[,...]\n\n"
1033 	    "  Print detailed information about the specified NVMe "
1034 	    "namespaces.\n", c_name);
1035 }
1036 
1037 static int
1038 do_identify_ns(int fd, const nvme_process_arg_t *npa)
1039 {
1040 	if (!npa->npa_isns)
1041 		errx(-1, "identify-namespace cannot be used on controllers");
1042 
1043 	if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 &&
1044 	    npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) {
1045 		errx(-1, "-c cannot be combined with other flags");
1046 	}
1047 
1048 	if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0 &&
1049 	    npa->npa_cmdflags != NVMEADM_O_ID_DESC_LIST) {
1050 		errx(-1, "-d cannot be combined with other flags");
1051 	}
1052 
1053 	if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0) {
1054 		errx(-1, "-a cannot be used on namespaces");
1055 	}
1056 
1057 	if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0) {
1058 		nvme_identify_ctrl_list_t *ctlist;
1059 
1060 		if (!nvme_version_check(npa->npa_version, 1, 2)) {
1061 			warnx("%s: -c is not supported on NVMe v%u.%u",
1062 			    npa->npa_name, npa->npa_version->v_major,
1063 			    npa->npa_version->v_minor);
1064 			return (-1);
1065 		}
1066 
1067 		if (npa->npa_idctl->id_oacs.oa_nsmgmt == 0) {
1068 			warnx("%s: Namespace Management not supported",
1069 			    npa->npa_name);
1070 			return (-1);
1071 		}
1072 
1073 		ctlist = nvme_identify(fd, NVME_IDENTIFY_NSID_CTRL_LIST);
1074 		if (ctlist == NULL)
1075 			return (-1);
1076 
1077 		(void) printf("%s/%s: ", npa->npa_name,
1078 		    di_minor_name(npa->npa_minor));
1079 		nvme_print_identify_ctrl_list(
1080 		    "Identify Attached Controller List", ctlist);
1081 		free(ctlist);
1082 	} else if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0) {
1083 		nvme_identify_nsid_desc_t *nsdesc;
1084 
1085 		if (!nvme_version_check(npa->npa_version, 1, 3)) {
1086 			warnx("%s: -d is not supported on NVMe v%u.%u",
1087 			    npa->npa_name, npa->npa_version->v_major,
1088 			    npa->npa_version->v_minor);
1089 			return (-1);
1090 		}
1091 
1092 		nsdesc = nvme_identify(fd, NVME_IDENTIFY_NSID_DESC);
1093 		if (nsdesc == NULL)
1094 			return (-1);
1095 
1096 		(void) printf("%s/%s: ", npa->npa_name,
1097 		    di_minor_name(npa->npa_minor));
1098 		nvme_print_identify_nsid_desc(nsdesc);
1099 		free(nsdesc);
1100 	} else {
1101 		(void) printf("%s/%s: ", npa->npa_name,
1102 		    di_minor_name(npa->npa_minor));
1103 		nvme_print_identify_nsid(npa->npa_idns, npa->npa_version);
1104 	}
1105 
1106 	return (0);
1107 }
1108 
1109 static void
1110 optparse_identify(nvme_process_arg_t *npa)
1111 {
1112 	int c;
1113 
1114 	optind = 0;
1115 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":Cacdn")) != -1) {
1116 		switch (c) {
1117 		case 'C':
1118 			npa->npa_cmdflags |= NVMEADM_O_ID_COMMON_NS;
1119 			break;
1120 
1121 		case 'a':
1122 			npa->npa_cmdflags |= NVMEADM_O_ID_ALLOC_NS;
1123 			break;
1124 
1125 		case 'c':
1126 			npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST;
1127 			break;
1128 
1129 		case 'd':
1130 			npa->npa_cmdflags |= NVMEADM_O_ID_DESC_LIST;
1131 			break;
1132 
1133 		case 'n':
1134 			npa->npa_cmdflags |= NVMEADM_O_ID_NSID_LIST;
1135 			break;
1136 
1137 		case '?':
1138 			errx(-1, "unknown option: -%c", optopt);
1139 
1140 		case ':':
1141 			errx(-1, "option -%c requires an argument", optopt);
1142 
1143 		}
1144 	}
1145 
1146 	if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0 &&
1147 	    (npa->npa_cmdflags &
1148 	    ~(NVMEADM_O_ID_ALLOC_NS | NVMEADM_O_ID_NSID_LIST)) != 0) {
1149 		errx(-1, "-a can only be used alone or together with -n");
1150 	}
1151 
1152 	if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0 &&
1153 	    npa->npa_cmdflags != NVMEADM_O_ID_COMMON_NS) {
1154 		errx(-1, "-C cannot be combined with other flags");
1155 
1156 	}
1157 
1158 	if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 &&
1159 	    npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) {
1160 		errx(-1, "-c cannot be combined with other flags");
1161 	}
1162 
1163 	if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0 &&
1164 	    npa->npa_cmdflags != NVMEADM_O_ID_DESC_LIST) {
1165 		errx(-1, "-d cannot be combined with other flags");
1166 	}
1167 
1168 	npa->npa_argc -= optind;
1169 	npa->npa_argv += optind;
1170 }
1171 
1172 static void
1173 usage_identify(const char *c_name)
1174 {
1175 	(void) fprintf(stderr,
1176 	    "%s [ -C | -c | -d | [-a] -n ] <ctl>[/<ns>][,...]\n\n"
1177 	    "  Print detailed information about the specified NVMe "
1178 	    "controllers and/or name-\n  spaces.\n", c_name);
1179 }
1180 
1181 static int
1182 do_identify(int fd, const nvme_process_arg_t *npa)
1183 {
1184 	if (npa->npa_isns) {
1185 		if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0)
1186 			errx(-1, "-C cannot be used on namespaces");
1187 
1188 		if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0)
1189 			errx(-1, "-a cannot be used on namespaces");
1190 
1191 		if ((npa->npa_cmdflags & NVMEADM_O_ID_NSID_LIST) != 0)
1192 			errx(-1, "-n cannot be used on namespaces");
1193 
1194 		return (do_identify_ns(fd, npa));
1195 	} else {
1196 		if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0)
1197 			errx(-1, "-d cannot be used on controllers");
1198 
1199 		return (do_identify_ctrl(fd, npa));
1200 	}
1201 }
1202 
1203 static void
1204 usage_get_logpage(const char *c_name)
1205 {
1206 	(void) fprintf(stderr, "%s <ctl>[/<ns>][,...] <logpage>\n\n"
1207 	    "  Print the specified log page of the specified NVMe "
1208 	    "controllers and/or name-\n  spaces. Supported log pages "
1209 	    "are error, health, and firmware.\n", c_name);
1210 }
1211 
1212 static void
1213 usage_firmware_list(const char *c_name)
1214 {
1215 	(void) fprintf(stderr, "%s <ctl>\n\n"
1216 	    "  Print the log page that contains the list of firmware "
1217 	    "images installed on the specified NVMe controller.\n", c_name);
1218 }
1219 
1220 static int
1221 do_get_logpage_error(int fd, const nvme_process_arg_t *npa)
1222 {
1223 	int nlog = npa->npa_idctl->id_elpe + 1;
1224 	size_t bufsize = sizeof (nvme_error_log_entry_t) * nlog;
1225 	nvme_error_log_entry_t *elog;
1226 
1227 	if (npa->npa_isns)
1228 		errx(-1, "Error Log not available on a per-namespace basis");
1229 
1230 	elog = nvme_get_logpage(fd, NVME_LOGPAGE_ERROR, &bufsize);
1231 
1232 	if (elog == NULL)
1233 		return (-1);
1234 
1235 	nlog = bufsize / sizeof (nvme_error_log_entry_t);
1236 
1237 	(void) printf("%s: ", npa->npa_name);
1238 	nvme_print_error_log(nlog, elog, npa->npa_version);
1239 
1240 	free(elog);
1241 
1242 	return (0);
1243 }
1244 
1245 static int
1246 do_get_logpage_health(int fd, const nvme_process_arg_t *npa)
1247 {
1248 	size_t bufsize = sizeof (nvme_health_log_t);
1249 	nvme_health_log_t *hlog;
1250 
1251 	if (npa->npa_isns) {
1252 		if (npa->npa_idctl->id_lpa.lp_smart == 0)
1253 			errx(-1, "SMART/Health information not available "
1254 			    "on a per-namespace basis on this controller");
1255 	}
1256 
1257 	hlog = nvme_get_logpage(fd, NVME_LOGPAGE_HEALTH, &bufsize);
1258 
1259 	if (hlog == NULL)
1260 		return (-1);
1261 
1262 	(void) printf("%s: ", npa->npa_name);
1263 	nvme_print_health_log(hlog, npa->npa_idctl, npa->npa_version);
1264 
1265 	free(hlog);
1266 
1267 	return (0);
1268 }
1269 
1270 static int
1271 do_get_logpage_fwslot(int fd, const nvme_process_arg_t *npa)
1272 {
1273 	size_t bufsize = sizeof (nvme_fwslot_log_t);
1274 	nvme_fwslot_log_t *fwlog;
1275 
1276 	if (npa->npa_isns)
1277 		errx(-1, "Firmware Slot information not available on a "
1278 		    "per-namespace basis");
1279 
1280 	fwlog = nvme_get_logpage(fd, NVME_LOGPAGE_FWSLOT, &bufsize);
1281 
1282 	if (fwlog == NULL)
1283 		return (-1);
1284 
1285 	(void) printf("%s: ", npa->npa_name);
1286 	nvme_print_fwslot_log(fwlog, npa->npa_idctl);
1287 
1288 	free(fwlog);
1289 
1290 	return (0);
1291 }
1292 
1293 static int
1294 do_get_logpage(int fd, const nvme_process_arg_t *npa)
1295 {
1296 	int ret = 0;
1297 	int (*func)(int, const nvme_process_arg_t *);
1298 
1299 	if (npa->npa_argc < 1) {
1300 		warnx("missing logpage name");
1301 		usage(npa->npa_cmd);
1302 		exit(-1);
1303 	}
1304 
1305 	if (strcmp(npa->npa_argv[0], "error") == 0)
1306 		func = do_get_logpage_error;
1307 	else if (strcmp(npa->npa_argv[0], "health") == 0)
1308 		func = do_get_logpage_health;
1309 	else if (strcmp(npa->npa_argv[0], "firmware") == 0)
1310 		func = do_get_logpage_fwslot;
1311 	else
1312 		errx(-1, "invalid log page: %s", npa->npa_argv[0]);
1313 
1314 	if (npa->npa_isns &&
1315 	    (npa->npa_ns_state & NVME_NS_STATE_ACTIVE) == 0)
1316 		errx(-1, "cannot get logpage: namespace is inactive");
1317 
1318 	ret = func(fd, npa);
1319 	return (ret);
1320 }
1321 
1322 static void
1323 usage_get_features(const char *c_name)
1324 {
1325 	const nvme_feature_t *feat;
1326 
1327 	(void) fprintf(stderr, "%s <ctl>[/<ns>][,...] [<feature>[,...]]\n\n"
1328 	    "  Print the specified features of the specified NVMe controllers "
1329 	    "and/or\n  namespaces. Supported features are:\n\n", c_name);
1330 	(void) fprintf(stderr, "    %-35s %-14s %s\n",
1331 	    "FEATURE NAME", "SHORT NAME", "CONTROLLER/NAMESPACE");
1332 	for (feat = &features[0]; feat->f_feature != 0; feat++) {
1333 		char *type;
1334 
1335 		if ((feat->f_getflags & NVMEADM_F_BOTH) == NVMEADM_F_BOTH)
1336 			type = "both";
1337 		else if ((feat->f_getflags & NVMEADM_F_CTRL) != 0)
1338 			type = "controller only";
1339 		else
1340 			type = "namespace only";
1341 
1342 		(void) fprintf(stderr, "    %-35s %-14s %s\n",
1343 		    feat->f_name, feat->f_short, type);
1344 	}
1345 
1346 }
1347 
1348 static int
1349 do_get_feat_common(int fd, const nvme_feature_t *feat,
1350     const nvme_process_arg_t *npa)
1351 {
1352 	void *buf = NULL;
1353 	size_t bufsize = feat->f_bufsize;
1354 	uint64_t res;
1355 
1356 	if (nvme_get_feature(fd, feat->f_feature, 0, &res, &bufsize, &buf)
1357 	    == B_FALSE)
1358 		return (EINVAL);
1359 
1360 	nvme_print(2, feat->f_name, -1, NULL);
1361 	feat->f_print(res, buf, bufsize, npa->npa_idctl, npa->npa_version);
1362 	free(buf);
1363 
1364 	return (0);
1365 }
1366 
1367 static int
1368 do_get_feat_temp_thresh_one(int fd, const nvme_feature_t *feat,
1369     const char *label, uint16_t tmpsel, uint16_t thsel,
1370     const nvme_process_arg_t *npa)
1371 {
1372 	uint64_t res;
1373 	void *buf = NULL;
1374 	size_t bufsize = feat->f_bufsize;
1375 	nvme_temp_threshold_t tt;
1376 
1377 	tt.r = 0;
1378 	tt.b.tt_tmpsel = tmpsel;
1379 	tt.b.tt_thsel = thsel;
1380 
1381 	if (!nvme_get_feature(fd, feat->f_feature, tt.r, &res, &bufsize,
1382 	    &buf)) {
1383 		return (EINVAL);
1384 	}
1385 
1386 	feat->f_print(res, (void *)label, 0, npa->npa_idctl, npa->npa_version);
1387 	free(buf);
1388 	return (0);
1389 }
1390 
1391 /*
1392  * In NVMe 1.2, the specification allowed for up to 8 sensors to be on the
1393  * device and changed the main device to have a composite temperature sensor. As
1394  * a result, there is a set of thresholds for each sensor. In addition, they
1395  * added both an over-temperature and under-temperature threshold. Since most
1396  * devices don't actually implement all the sensors, we get the health page and
1397  * see which sensors have a non-zero value to determine how to proceed.
1398  */
1399 static int
1400 do_get_feat_temp_thresh(int fd, const nvme_feature_t *feat,
1401     const nvme_process_arg_t *npa)
1402 {
1403 	int ret;
1404 	size_t bufsize = sizeof (nvme_health_log_t);
1405 	nvme_health_log_t *hlog;
1406 
1407 	nvme_print(2, feat->f_name, -1, NULL);
1408 	if ((ret = do_get_feat_temp_thresh_one(fd, feat,
1409 	    "Composite Over Temp. Threshold", 0, NVME_TEMP_THRESH_OVER,
1410 	    npa)) != 0) {
1411 		return (ret);
1412 	}
1413 
1414 	if (!nvme_version_check(npa->npa_version, 1, 2)) {
1415 		return (0);
1416 	}
1417 
1418 	if ((ret = do_get_feat_temp_thresh_one(fd, feat,
1419 	    "Composite Under Temp. Threshold", 0, NVME_TEMP_THRESH_UNDER,
1420 	    npa)) != 0) {
1421 		return (ret);
1422 	}
1423 
1424 	hlog = nvme_get_logpage(fd, NVME_LOGPAGE_HEALTH, &bufsize);
1425 	if (hlog == NULL) {
1426 		warnx("failed to get health log page, unable to get "
1427 		    "thresholds for additional sensors");
1428 		return (0);
1429 	}
1430 
1431 	if (hlog->hl_temp_sensor_1 != 0) {
1432 		(void) do_get_feat_temp_thresh_one(fd, feat,
1433 		    "Temp. Sensor 1 Over Temp. Threshold", 1,
1434 		    NVME_TEMP_THRESH_OVER, npa);
1435 		(void) do_get_feat_temp_thresh_one(fd, feat,
1436 		    "Temp. Sensor 1 Under Temp. Threshold", 1,
1437 		    NVME_TEMP_THRESH_UNDER, npa);
1438 	}
1439 
1440 	if (hlog->hl_temp_sensor_2 != 0) {
1441 		(void) do_get_feat_temp_thresh_one(fd, feat,
1442 		    "Temp. Sensor 2 Over Temp. Threshold", 2,
1443 		    NVME_TEMP_THRESH_OVER, npa);
1444 		(void) do_get_feat_temp_thresh_one(fd, feat,
1445 		    "Temp. Sensor 2 Under Temp. Threshold", 2,
1446 		    NVME_TEMP_THRESH_UNDER, npa);
1447 	}
1448 
1449 	if (hlog->hl_temp_sensor_3 != 0) {
1450 		(void) do_get_feat_temp_thresh_one(fd, feat,
1451 		    "Temp. Sensor 3 Over Temp. Threshold", 3,
1452 		    NVME_TEMP_THRESH_OVER, npa);
1453 		(void) do_get_feat_temp_thresh_one(fd, feat,
1454 		    "Temp. Sensor 3 Under Temp. Threshold", 3,
1455 		    NVME_TEMP_THRESH_UNDER, npa);
1456 	}
1457 
1458 	if (hlog->hl_temp_sensor_4 != 0) {
1459 		(void) do_get_feat_temp_thresh_one(fd, feat,
1460 		    "Temp. Sensor 4 Over Temp. Threshold", 4,
1461 		    NVME_TEMP_THRESH_OVER, npa);
1462 		(void) do_get_feat_temp_thresh_one(fd, feat,
1463 		    "Temp. Sensor 4 Under Temp. Threshold", 4,
1464 		    NVME_TEMP_THRESH_UNDER, npa);
1465 	}
1466 
1467 	if (hlog->hl_temp_sensor_5 != 0) {
1468 		(void) do_get_feat_temp_thresh_one(fd, feat,
1469 		    "Temp. Sensor 5 Over Temp. Threshold", 5,
1470 		    NVME_TEMP_THRESH_OVER, npa);
1471 		(void) do_get_feat_temp_thresh_one(fd, feat,
1472 		    "Temp. Sensor 5 Under Temp. Threshold", 5,
1473 		    NVME_TEMP_THRESH_UNDER, npa);
1474 	}
1475 
1476 	if (hlog->hl_temp_sensor_6 != 0) {
1477 		(void) do_get_feat_temp_thresh_one(fd, feat,
1478 		    "Temp. Sensor 6 Over Temp. Threshold", 6,
1479 		    NVME_TEMP_THRESH_OVER, npa);
1480 		(void) do_get_feat_temp_thresh_one(fd, feat,
1481 		    "Temp. Sensor 6 Under Temp. Threshold", 6,
1482 		    NVME_TEMP_THRESH_UNDER, npa);
1483 	}
1484 
1485 	if (hlog->hl_temp_sensor_7 != 0) {
1486 		(void) do_get_feat_temp_thresh_one(fd, feat,
1487 		    "Temp. Sensor 7 Over Temp. Threshold", 7,
1488 		    NVME_TEMP_THRESH_OVER, npa);
1489 		(void) do_get_feat_temp_thresh_one(fd, feat,
1490 		    "Temp. Sensor 7 Under Temp. Threshold", 7,
1491 		    NVME_TEMP_THRESH_UNDER, npa);
1492 	}
1493 
1494 	if (hlog->hl_temp_sensor_8 != 0) {
1495 		(void) do_get_feat_temp_thresh_one(fd, feat,
1496 		    "Temp. Sensor 8 Over Temp. Threshold", 8,
1497 		    NVME_TEMP_THRESH_OVER, npa);
1498 		(void) do_get_feat_temp_thresh_one(fd, feat,
1499 		    "Temp. Sensor 8 Under Temp. Threshold", 8,
1500 		    NVME_TEMP_THRESH_UNDER, npa);
1501 	}
1502 	free(hlog);
1503 	return (0);
1504 }
1505 
1506 static int
1507 do_get_feat_intr_vect(int fd, const nvme_feature_t *feat,
1508     const nvme_process_arg_t *npa)
1509 {
1510 	uint64_t res;
1511 	uint64_t arg;
1512 	int intr_cnt;
1513 
1514 	intr_cnt = nvme_intr_cnt(fd);
1515 
1516 	if (intr_cnt == -1)
1517 		return (EINVAL);
1518 
1519 	nvme_print(2, feat->f_name, -1, NULL);
1520 
1521 	for (arg = 0; arg < intr_cnt; arg++) {
1522 		if (nvme_get_feature(fd, feat->f_feature, arg, &res, NULL, NULL)
1523 		    == B_FALSE)
1524 			return (EINVAL);
1525 
1526 		feat->f_print(res, NULL, 0, npa->npa_idctl, npa->npa_version);
1527 	}
1528 
1529 	return (0);
1530 }
1531 
1532 static int
1533 do_get_features(int fd, const nvme_process_arg_t *npa)
1534 {
1535 	const nvme_feature_t *feat;
1536 	char *f, *flist, *lasts;
1537 	boolean_t header_printed = B_FALSE;
1538 
1539 	if (npa->npa_argc > 1)
1540 		errx(-1, "unexpected arguments");
1541 
1542 	if (npa->npa_isns &&
1543 	    (npa->npa_ns_state & NVME_NS_STATE_ACTIVE) == 0)
1544 		errx(-1, "cannot get feature: namespace is inactive");
1545 
1546 	/*
1547 	 * No feature list given, print all supported features.
1548 	 */
1549 	if (npa->npa_argc == 0) {
1550 		(void) printf("%s: Get Features\n", npa->npa_name);
1551 		for (feat = &features[0]; feat->f_feature != 0; feat++) {
1552 			if ((npa->npa_isns &&
1553 			    (feat->f_getflags & NVMEADM_F_NS) == 0) ||
1554 			    (!npa->npa_isns &&
1555 			    (feat->f_getflags & NVMEADM_F_CTRL) == 0))
1556 				continue;
1557 
1558 			(void) feat->f_get(fd, feat, npa);
1559 		}
1560 
1561 		return (0);
1562 	}
1563 
1564 	/*
1565 	 * Process feature list.
1566 	 */
1567 	flist = strdup(npa->npa_argv[0]);
1568 	if (flist == NULL)
1569 		err(-1, "do_get_features");
1570 
1571 	for (f = strtok_r(flist, ",", &lasts);
1572 	    f != NULL;
1573 	    f = strtok_r(NULL, ",", &lasts)) {
1574 		while (isspace(*f))
1575 			f++;
1576 
1577 		for (feat = &features[0]; feat->f_feature != 0; feat++) {
1578 			if (strncasecmp(feat->f_name, f, strlen(f)) == 0 ||
1579 			    strncasecmp(feat->f_short, f, strlen(f)) == 0)
1580 				break;
1581 		}
1582 
1583 		if (feat->f_feature == 0) {
1584 			warnx("unknown feature %s", f);
1585 			continue;
1586 		}
1587 
1588 		if ((npa->npa_isns &&
1589 		    (feat->f_getflags & NVMEADM_F_NS) == 0) ||
1590 		    (!npa->npa_isns &&
1591 		    (feat->f_getflags & NVMEADM_F_CTRL) == 0)) {
1592 			warnx("feature %s %s supported for namespaces",
1593 			    feat->f_name,
1594 			    (feat->f_getflags & NVMEADM_F_NS) != 0 ?
1595 			    "only" : "not");
1596 			continue;
1597 		}
1598 
1599 		if (!header_printed) {
1600 			(void) printf("%s: Get Features\n", npa->npa_name);
1601 			header_printed = B_TRUE;
1602 		}
1603 
1604 		if (feat->f_get(fd, feat, npa) != 0) {
1605 			warnx("unsupported feature: %s", feat->f_name);
1606 			continue;
1607 		}
1608 	}
1609 
1610 	free(flist);
1611 	return (0);
1612 }
1613 
1614 static int
1615 do_format_common(int fd, const nvme_process_arg_t *npa, unsigned long lbaf,
1616     unsigned long ses)
1617 {
1618 	nvme_process_arg_t ns_npa = { 0 };
1619 	nvmeadm_cmd_t cmd = { 0 };
1620 
1621 	if (npa->npa_isns &&
1622 	    (npa->npa_ns_state & NVME_NS_STATE_ACTIVE) == 0) {
1623 		errx(-1, "cannot %s: namespace is inactive",
1624 		    npa->npa_cmd->c_name);
1625 	}
1626 
1627 	cmd = *(npa->npa_cmd);
1628 	cmd.c_func = do_attach_detach;
1629 	cmd.c_name = "detach";
1630 	ns_npa = *npa;
1631 	ns_npa.npa_cmd = &cmd;
1632 
1633 	if (do_attach_detach(fd, &ns_npa) != 0)
1634 		return (exitcode);
1635 	if (nvme_format_nvm(fd, lbaf, ses) == B_FALSE) {
1636 		warn("%s failed", npa->npa_cmd->c_name);
1637 		exitcode += -1;
1638 	}
1639 	cmd.c_name = "attach";
1640 	exitcode += do_attach_detach(fd, &ns_npa);
1641 
1642 	return (exitcode);
1643 }
1644 
1645 static void
1646 usage_format(const char *c_name)
1647 {
1648 	(void) fprintf(stderr, "%s <ctl>[/<ns>] [<lba-format>]\n\n"
1649 	    "  Format one or all namespaces of the specified NVMe "
1650 	    "controller. Supported LBA\n  formats can be queried with "
1651 	    "the \"%s identify\" command on the namespace\n  to be "
1652 	    "formatted.\n", c_name, getprogname());
1653 }
1654 
1655 static int
1656 do_format(int fd, const nvme_process_arg_t *npa)
1657 {
1658 	unsigned long lbaf;
1659 
1660 	if (npa->npa_idctl->id_oacs.oa_format == 0)
1661 		errx(-1, "%s not supported", npa->npa_cmd->c_name);
1662 
1663 	if (npa->npa_isns && npa->npa_idctl->id_fna.fn_format != 0)
1664 		errx(-1, "%s not supported on individual namespace",
1665 		    npa->npa_cmd->c_name);
1666 
1667 
1668 	if (npa->npa_argc > 0) {
1669 		errno = 0;
1670 		lbaf = strtoul(npa->npa_argv[0], NULL, 10);
1671 
1672 		if (errno != 0 || lbaf > NVME_FRMT_MAX_LBAF)
1673 			errx(-1, "invalid LBA format %d", lbaf + 1);
1674 
1675 		if (npa->npa_idns->id_lbaf[lbaf].lbaf_ms != 0)
1676 			errx(-1, "LBA formats with metadata not supported");
1677 	} else {
1678 		lbaf = npa->npa_idns->id_flbas.lba_format;
1679 	}
1680 
1681 	return (do_format_common(fd, npa, lbaf, 0));
1682 }
1683 
1684 static void
1685 usage_secure_erase(const char *c_name)
1686 {
1687 	(void) fprintf(stderr, "%s [-c] <ctl>[/<ns>]\n\n"
1688 	    "  Secure-Erase one or all namespaces of the specified "
1689 	    "NVMe controller.\n", c_name);
1690 }
1691 
1692 static void
1693 optparse_secure_erase(nvme_process_arg_t *npa)
1694 {
1695 	int c;
1696 
1697 	optind = 0;
1698 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":c")) != -1) {
1699 		switch (c) {
1700 		case 'c':
1701 			npa->npa_cmdflags |= NVMEADM_O_SE_CRYPTO;
1702 			break;
1703 
1704 		case '?':
1705 			errx(-1, "unknown option: -%c", optopt);
1706 
1707 		case ':':
1708 			errx(-1, "option -%c requires an argument", optopt);
1709 
1710 		}
1711 	}
1712 
1713 	npa->npa_argc -= optind;
1714 	npa->npa_argv += optind;
1715 }
1716 
1717 static int
1718 do_secure_erase(int fd, const nvme_process_arg_t *npa)
1719 {
1720 	unsigned long lbaf;
1721 	uint8_t ses = NVME_FRMT_SES_USER;
1722 
1723 	if (npa->npa_argc > 0)
1724 		errx(-1, "Too many arguments");
1725 
1726 	if (npa->npa_idctl->id_oacs.oa_format == 0)
1727 		errx(-1, "%s not supported", npa->npa_cmd->c_name);
1728 
1729 	if (npa->npa_isns && npa->npa_idctl->id_fna.fn_sec_erase != 0)
1730 		errx(-1, "%s not supported on individual namespace",
1731 		    npa->npa_cmd->c_name);
1732 
1733 	if ((npa->npa_cmdflags & NVMEADM_O_SE_CRYPTO) != 0)
1734 		ses = NVME_FRMT_SES_CRYPTO;
1735 
1736 	if (ses == NVME_FRMT_SES_CRYPTO &&
1737 	    npa->npa_idctl->id_fna.fn_crypt_erase == 0)
1738 		errx(-1, "cryptographic %s not supported",
1739 		    npa->npa_cmd->c_name);
1740 
1741 	lbaf = npa->npa_idns->id_flbas.lba_format;
1742 
1743 	return (do_format_common(fd, npa, lbaf, ses));
1744 }
1745 
1746 static void
1747 usage_attach_detach(const char *c_name)
1748 {
1749 	(void) fprintf(stderr, "%s <ctl>[/<ns>]\n\n"
1750 	    "  %c%s blkdev(4D) %s one or all namespaces of the "
1751 	    "specified NVMe controller.\n",
1752 	    c_name, toupper(c_name[0]), &c_name[1],
1753 	    c_name[0] == 'd' ? "from" : "to");
1754 }
1755 
1756 static int
1757 do_attach_detach(int fd, const nvme_process_arg_t *npa)
1758 {
1759 	char *c_name = npa->npa_cmd->c_name;
1760 
1761 	if (!npa->npa_isns) {
1762 		nvme_process_arg_t ns_npa = { 0 };
1763 
1764 		ns_npa.npa_name = npa->npa_name;
1765 		ns_npa.npa_isns = B_TRUE;
1766 		ns_npa.npa_cmd = npa->npa_cmd;
1767 		ns_npa.npa_excl = npa->npa_excl;
1768 
1769 		nvme_walk(&ns_npa, npa->npa_node);
1770 
1771 		return (exitcode);
1772 	}
1773 
1774 	/*
1775 	 * Unless the user interactively requested a particular namespace to be
1776 	 * attached or detached, don't even try to attach or detach namespaces
1777 	 * that are ignored by the driver, thereby avoiding printing pointless
1778 	 * error messages.
1779 	 */
1780 	if (!npa->npa_interactive &&
1781 	    (npa->npa_ns_state & NVME_NS_STATE_IGNORED))
1782 		return (0);
1783 
1784 	if ((c_name[0] == 'd' ? nvme_detach : nvme_attach)(fd)
1785 	    == B_FALSE) {
1786 		warn("%s failed", c_name);
1787 		return (-1);
1788 	}
1789 
1790 	return (0);
1791 }
1792 
1793 static void
1794 usage_firmware_load(const char *c_name)
1795 {
1796 	(void) fprintf(stderr, "%s <ctl> <image file> [<offset>]\n\n"
1797 	    "  Load firmware <image file> to offset <offset>.\n"
1798 	    "  The firmware needs to be committed to a slot using "
1799 	    "\"nvmeadm commit-firmware\"\n  command.\n", c_name);
1800 }
1801 
1802 /*
1803  * Read exactly len bytes, or until eof.
1804  */
1805 static ssize_t
1806 read_block(int fd, char *buf, size_t len)
1807 {
1808 	size_t remain;
1809 	ssize_t bytes;
1810 
1811 	remain = len;
1812 	while (remain > 0) {
1813 		bytes = read(fd, buf, remain);
1814 		if (bytes == 0)
1815 			break;
1816 
1817 		if (bytes < 0) {
1818 			if (errno == EINTR)
1819 				continue;
1820 
1821 			return (-1);
1822 		}
1823 
1824 		buf += bytes;
1825 		remain -= bytes;
1826 	}
1827 
1828 	return (len - remain);
1829 }
1830 
1831 /*
1832  * Convert a string to a valid firmware upload offset (in bytes).
1833  */
1834 static offset_t
1835 get_fw_offsetb(char *str)
1836 {
1837 	longlong_t offsetb;
1838 	char *valend;
1839 
1840 	errno = 0;
1841 	offsetb = strtoll(str, &valend, 0);
1842 	if (errno != 0 || *valend != '\0' || offsetb < 0 ||
1843 	    offsetb > NVME_FW_OFFSETB_MAX)
1844 		errx(-1, "Offset must be numeric and in the range of 0 to %llu",
1845 		    NVME_FW_OFFSETB_MAX);
1846 
1847 	if ((offsetb & NVME_DWORD_MASK) != 0)
1848 		errx(-1, "Offset must be multiple of %d", NVME_DWORD_SIZE);
1849 
1850 	return ((offset_t)offsetb);
1851 }
1852 
1853 #define	FIRMWARE_READ_BLKSIZE	(64 * 1024)		/* 64K */
1854 
1855 static int
1856 do_firmware_load(int fd, const nvme_process_arg_t *npa)
1857 {
1858 	int fw_fd;
1859 	ssize_t len;
1860 	offset_t offset = 0;
1861 	size_t size;
1862 	uint16_t sc;
1863 	char buf[FIRMWARE_READ_BLKSIZE];
1864 
1865 	if (npa->npa_argc > 2)
1866 		errx(-1, "Too many arguments");
1867 
1868 	if (npa->npa_argc == 0)
1869 		errx(-1, "Requires firmware file name, and an "
1870 		    "optional offset");
1871 
1872 	if (npa->npa_isns)
1873 		errx(-1, "Firmware loading not available on a per-namespace "
1874 		    "basis");
1875 
1876 	if (npa->npa_argc == 2)
1877 		offset = get_fw_offsetb(npa->npa_argv[1]);
1878 
1879 	fw_fd = open(npa->npa_argv[0], O_RDONLY);
1880 	if (fw_fd < 0)
1881 		errx(-1, "Failed to open \"%s\": %s", npa->npa_argv[0],
1882 		    strerror(errno));
1883 
1884 	size = 0;
1885 	do {
1886 		len = read_block(fw_fd, buf, sizeof (buf));
1887 
1888 		if (len < 0)
1889 			errx(-1, "Error reading \"%s\": %s", npa->npa_argv[0],
1890 			    strerror(errno));
1891 
1892 		if (len == 0)
1893 			break;
1894 
1895 		if (!nvme_firmware_load(fd, buf, len, offset, &sc))
1896 			errx(-1, "Error loading \"%s\": %s", npa->npa_argv[0],
1897 			    nvme_fw_error(errno, sc));
1898 
1899 		offset += len;
1900 		size += len;
1901 	} while (len == sizeof (buf));
1902 
1903 	(void) close(fw_fd);
1904 
1905 	if (verbose)
1906 		(void) printf("%zu bytes downloaded.\n", size);
1907 
1908 	return (0);
1909 }
1910 
1911 /*
1912  * Convert str to a valid firmware slot number.
1913  */
1914 static uint_t
1915 get_slot_number(char *str)
1916 {
1917 	longlong_t slot;
1918 	char *valend;
1919 
1920 	errno = 0;
1921 	slot = strtoll(str, &valend, 0);
1922 	if (errno != 0 || *valend != '\0' ||
1923 	    slot < NVME_FW_SLOT_MIN || slot > NVME_FW_SLOT_MAX)
1924 		errx(-1, "Slot must be numeric and in the range of %d to %d",
1925 		    NVME_FW_SLOT_MIN, NVME_FW_SLOT_MAX);
1926 
1927 	return ((uint_t)slot);
1928 }
1929 
1930 static void
1931 usage_firmware_commit(const char *c_name)
1932 {
1933 	(void) fprintf(stderr, "%s <ctl> <slot>\n\n"
1934 	    "  Commit previously downloaded firmware to slot <slot>.\n"
1935 	    "  The firmware is only activated after a "
1936 	    "\"nvmeadm activate-firmware\" command.\n", c_name);
1937 }
1938 
1939 static int
1940 do_firmware_commit(int fd, const nvme_process_arg_t *npa)
1941 {
1942 	uint_t slot;
1943 	uint16_t sc;
1944 
1945 	if (npa->npa_argc > 1)
1946 		errx(-1, "Too many arguments");
1947 
1948 	if (npa->npa_argc == 0)
1949 		errx(-1, "Firmware slot number is required");
1950 
1951 	if (npa->npa_isns)
1952 		errx(-1, "Firmware committing not available on a per-namespace "
1953 		    "basis");
1954 
1955 	slot = get_slot_number(npa->npa_argv[0]);
1956 
1957 	if (slot == 1 && npa->npa_idctl->id_frmw.fw_readonly)
1958 		errx(-1, "Cannot commit firmware to slot 1: slot is read-only");
1959 
1960 	if (!nvme_firmware_commit(fd, slot, NVME_FWC_SAVE, &sc))
1961 		errx(-1, "Failed to commit firmware to slot %u: %s",
1962 		    slot, nvme_fw_error(errno, sc));
1963 
1964 	if (verbose)
1965 		(void) printf("Firmware committed to slot %u.\n", slot);
1966 
1967 	return (0);
1968 }
1969 
1970 static void
1971 usage_firmware_activate(const char *c_name)
1972 {
1973 	(void) fprintf(stderr, "%s <ctl> <slot>\n\n"
1974 	    "  Activate firmware in slot <slot>.\n"
1975 	    "  The firmware will be in use after the next system reset.\n",
1976 	    c_name);
1977 }
1978 
1979 static int
1980 do_firmware_activate(int fd, const nvme_process_arg_t *npa)
1981 {
1982 	uint_t slot;
1983 	uint16_t sc;
1984 
1985 	if (npa->npa_argc > 1)
1986 		errx(-1, "Too many arguments");
1987 
1988 	if (npa->npa_argc == 0)
1989 		errx(-1, "Firmware slot number is required");
1990 
1991 	if (npa->npa_isns)
1992 		errx(-1, "Firmware activation not available on a per-namespace "
1993 		    "basis");
1994 
1995 	slot = get_slot_number(npa->npa_argv[0]);
1996 
1997 	if (!nvme_firmware_commit(fd, slot, NVME_FWC_ACTIVATE, &sc))
1998 		errx(-1, "Failed to activate slot %u: %s", slot,
1999 		    nvme_fw_error(errno, sc));
2000 
2001 	if (verbose)
2002 		printf("Slot %u activated: %s.\n", slot,
2003 		    nvme_fw_error(errno, sc));
2004 
2005 	return (0);
2006 }
2007 
2008 /*
2009  * While the NVME_VERSION_ATLEAST macro exists, specifying a version of 1.0
2010  * causes GCC to helpfully flag the -Wtype-limits warning because a uint_t is
2011  * always >= 0. In many cases it's useful to always indicate what version
2012  * something was added in to simplify code (e.g. nvmeadm_print_bit) and we'd
2013  * rather just say it's version 1.0 rather than making folks realize that a
2014  * hardcoded true is equivalent. Therefore we have this function which can't
2015  * trigger this warning today (and adds a minor amount of type safety). If GCC
2016  * or clang get smart enough to see through this, then we'll have to just
2017  * disable the warning for the single minor comparison (and reformat this a bit
2018  * to minimize the impact).
2019  */
2020 boolean_t
2021 nvme_version_check(nvme_version_t *vers, uint_t major, uint_t minor)
2022 {
2023 	if (vers->v_major > major) {
2024 		return (B_TRUE);
2025 	}
2026 
2027 	return (vers->v_major == major && vers->v_minor >= minor);
2028 }
2029