xref: /illumos-gate/usr/src/cmd/nvmeadm/nvmeadm.c (revision 9e3493cb8a0cfe96c9aef9b7da42c6c9b5c24b43)
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 2016 Nexenta Systems, Inc.
14  * Copyright 2017 Joyent, Inc.
15  * Copyright 2019 Western Digital Corporation.
16  * Copyright 2021 Oxide Computer Company
17  */
18 
19 /*
20  * nvmeadm -- NVMe administration utility
21  *
22  * nvmeadm [-v] [-d] [-h] <command> [<ctl>[/<ns>][,...]] [args]
23  * commands:	list
24  *		identify
25  *		get-logpage <logpage name>
26  *		get-features <feature>[,...]
27  *		format ...
28  *		secure-erase ...
29  *		detach ...
30  *		attach ...
31  *		get-param ...
32  *		set-param ...
33  *		load-firmware ...
34  *		commit-firmware ...
35  *		activate-firmware ...
36  */
37 
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <unistd.h>
41 #include <fcntl.h>
42 #include <strings.h>
43 #include <ctype.h>
44 #include <err.h>
45 #include <sys/sunddi.h>
46 #include <libdevinfo.h>
47 
48 #include <sys/nvme.h>
49 
50 #include "nvmeadm.h"
51 
52 struct nvme_feature {
53 	char *f_name;
54 	char *f_short;
55 	uint8_t f_feature;
56 	size_t f_bufsize;
57 	uint_t f_getflags;
58 	int (*f_get)(int, const nvme_feature_t *, const nvme_process_arg_t *);
59 	void (*f_print)(uint64_t, void *, size_t, nvme_identify_ctrl_t *,
60 	    nvme_version_t *);
61 };
62 
63 #define	NVMEADM_CTRL	1
64 #define	NVMEADM_NS	2
65 #define	NVMEADM_BOTH	(NVMEADM_CTRL | NVMEADM_NS)
66 
67 struct nvmeadm_cmd {
68 	char *c_name;
69 	const char *c_desc;
70 	const char *c_flagdesc;
71 	int (*c_func)(int, const nvme_process_arg_t *);
72 	void (*c_usage)(const char *);
73 	boolean_t c_multi;
74 	void (*c_optparse)(nvme_process_arg_t *);
75 };
76 
77 
78 static void usage(const nvmeadm_cmd_t *);
79 static void nvme_walk(nvme_process_arg_t *, di_node_t);
80 static boolean_t nvme_match(nvme_process_arg_t *);
81 
82 static int nvme_process(di_node_t, di_minor_t, void *);
83 
84 static int do_list(int, const nvme_process_arg_t *);
85 static int do_identify(int, const nvme_process_arg_t *);
86 static int do_get_logpage_error(int, const nvme_process_arg_t *);
87 static int do_get_logpage_health(int, const nvme_process_arg_t *);
88 static int do_get_logpage_fwslot(int, const nvme_process_arg_t *);
89 static int do_get_logpage(int, const nvme_process_arg_t *);
90 static int do_get_feat_common(int, const nvme_feature_t *,
91     const nvme_process_arg_t *);
92 static int do_get_feat_intr_vect(int, const nvme_feature_t *,
93     const nvme_process_arg_t *);
94 static int do_get_feat_temp_thresh(int, const nvme_feature_t *,
95     const nvme_process_arg_t *);
96 static int do_get_features(int, const nvme_process_arg_t *);
97 static int do_format(int, const nvme_process_arg_t *);
98 static int do_secure_erase(int, const nvme_process_arg_t *);
99 static int do_attach_detach(int, const nvme_process_arg_t *);
100 static int do_firmware_load(int, const nvme_process_arg_t *);
101 static int do_firmware_commit(int, const nvme_process_arg_t *);
102 static int do_firmware_activate(int, const nvme_process_arg_t *);
103 
104 static void optparse_list(nvme_process_arg_t *);
105 
106 static void usage_list(const char *);
107 static void usage_identify(const char *);
108 static void usage_get_logpage(const char *);
109 static void usage_get_features(const char *);
110 static void usage_format(const char *);
111 static void usage_secure_erase(const char *);
112 static void usage_attach_detach(const char *);
113 static void usage_firmware_load(const char *);
114 static void usage_firmware_commit(const char *);
115 static void usage_firmware_activate(const char *);
116 
117 int verbose;
118 int debug;
119 static int exitcode;
120 
121 static const nvmeadm_cmd_t nvmeadm_cmds[] = {
122 	{
123 		"list",
124 		"list controllers and namespaces",
125 		"  -p\t\tprint parsable output\n"
126 		    "  -o field\tselect a field for parsable output\n",
127 		do_list, usage_list, B_TRUE, optparse_list
128 	},
129 	{
130 		"identify",
131 		"identify controllers and/or namespaces",
132 		NULL,
133 		do_identify, usage_identify, B_TRUE
134 	},
135 	{
136 		"get-logpage",
137 		"get a log page from controllers and/or namespaces",
138 		NULL,
139 		do_get_logpage, usage_get_logpage, B_TRUE
140 	},
141 	{
142 		"get-features",
143 		"get features from controllers and/or namespaces",
144 		NULL,
145 		do_get_features, usage_get_features, B_TRUE
146 	},
147 	{
148 		"format",
149 		"format namespace(s) of a controller",
150 		NULL,
151 		do_format, usage_format, B_FALSE
152 	},
153 	{
154 		"secure-erase",
155 		"secure erase namespace(s) of a controller",
156 		"  -c  Do a cryptographic erase.",
157 		do_secure_erase, usage_secure_erase, B_FALSE
158 	},
159 	{
160 		"detach",
161 		"detach blkdev(7d) from namespace(s) of a controller",
162 		NULL,
163 		do_attach_detach, usage_attach_detach, B_FALSE
164 	},
165 	{
166 		"attach",
167 		"attach blkdev(7d) to namespace(s) of a controller",
168 		NULL,
169 		do_attach_detach, usage_attach_detach, B_FALSE
170 	},
171 	{
172 		"load-firmware",
173 		"load firmware to a controller",
174 		NULL,
175 		do_firmware_load, usage_firmware_load, B_FALSE
176 	},
177 	{
178 		"commit-firmware",
179 		"commit downloaded firmware to a slot of a controller",
180 		NULL,
181 		do_firmware_commit, usage_firmware_commit, B_FALSE
182 	},
183 	{
184 		"activate-firmware",
185 		"activate a firmware slot of a controller",
186 		NULL,
187 		do_firmware_activate, usage_firmware_activate, B_FALSE
188 	},
189 	{
190 		NULL, NULL, NULL,
191 		NULL, NULL, B_FALSE
192 	}
193 };
194 
195 static const nvme_feature_t features[] = {
196 	{ "Arbitration", "",
197 	    NVME_FEAT_ARBITRATION, 0, NVMEADM_CTRL,
198 	    do_get_feat_common, nvme_print_feat_arbitration },
199 	{ "Power Management", "",
200 	    NVME_FEAT_POWER_MGMT, 0, NVMEADM_CTRL,
201 	    do_get_feat_common, nvme_print_feat_power_mgmt },
202 	{ "LBA Range Type", "range",
203 	    NVME_FEAT_LBA_RANGE, NVME_LBA_RANGE_BUFSIZE, NVMEADM_NS,
204 	    do_get_feat_common, nvme_print_feat_lba_range },
205 	{ "Temperature Threshold", "",
206 	    NVME_FEAT_TEMPERATURE, 0, NVMEADM_CTRL,
207 	    do_get_feat_temp_thresh, nvme_print_feat_temperature },
208 	{ "Error Recovery", "",
209 	    NVME_FEAT_ERROR, 0, NVMEADM_CTRL,
210 	    do_get_feat_common, nvme_print_feat_error },
211 	{ "Volatile Write Cache", "cache",
212 	    NVME_FEAT_WRITE_CACHE, 0, NVMEADM_CTRL,
213 	    do_get_feat_common, nvme_print_feat_write_cache },
214 	{ "Number of Queues", "queues",
215 	    NVME_FEAT_NQUEUES, 0, NVMEADM_CTRL,
216 	    do_get_feat_common, nvme_print_feat_nqueues },
217 	{ "Interrupt Coalescing", "coalescing",
218 	    NVME_FEAT_INTR_COAL, 0, NVMEADM_CTRL,
219 	    do_get_feat_common, nvme_print_feat_intr_coal },
220 	{ "Interrupt Vector Configuration", "vector",
221 	    NVME_FEAT_INTR_VECT, 0, NVMEADM_CTRL,
222 	    do_get_feat_intr_vect, nvme_print_feat_intr_vect },
223 	{ "Write Atomicity", "atomicity",
224 	    NVME_FEAT_WRITE_ATOM, 0, NVMEADM_CTRL,
225 	    do_get_feat_common, nvme_print_feat_write_atom },
226 	{ "Asynchronous Event Configuration", "event",
227 	    NVME_FEAT_ASYNC_EVENT, 0, NVMEADM_CTRL,
228 	    do_get_feat_common, nvme_print_feat_async_event },
229 	{ "Autonomous Power State Transition", "",
230 	    NVME_FEAT_AUTO_PST, NVME_AUTO_PST_BUFSIZE, NVMEADM_CTRL,
231 	    do_get_feat_common, nvme_print_feat_auto_pst },
232 	{ "Software Progress Marker", "progress",
233 	    NVME_FEAT_PROGRESS, 0, NVMEADM_CTRL,
234 	    do_get_feat_common, nvme_print_feat_progress },
235 	{ NULL, NULL, 0, 0, B_FALSE, NULL }
236 };
237 
238 
239 int
240 main(int argc, char **argv)
241 {
242 	int c;
243 	const nvmeadm_cmd_t *cmd;
244 	di_node_t node;
245 	nvme_process_arg_t npa = { 0 };
246 	int help = 0;
247 	char *tmp, *lasts = NULL;
248 	char *ctrl = NULL;
249 
250 	while ((c = getopt(argc, argv, "dhv")) != -1) {
251 		switch (c) {
252 		case 'd':
253 			debug++;
254 			break;
255 		case 'v':
256 			verbose++;
257 			break;
258 		case 'h':
259 			help++;
260 			break;
261 		case '?':
262 			usage(NULL);
263 			exit(-1);
264 		}
265 	}
266 
267 	if (optind == argc) {
268 		usage(NULL);
269 		if (help)
270 			exit(0);
271 		else
272 			exit(-1);
273 	}
274 
275 	/* Look up the specified command in the command table. */
276 	for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++)
277 		if (strcmp(cmd->c_name, argv[optind]) == 0)
278 			break;
279 
280 	if (cmd->c_name == NULL) {
281 		usage(NULL);
282 		exit(-1);
283 	}
284 
285 	if (help) {
286 		usage(cmd);
287 		exit(0);
288 	}
289 
290 	npa.npa_cmd = cmd;
291 
292 	optind++;
293 
294 	/*
295 	 * Store the remaining arguments for use by the command. Give the
296 	 * command a chance to process the options across the board before going
297 	 * into each controller.
298 	 */
299 	npa.npa_argc = argc - optind;
300 	npa.npa_argv = &argv[optind];
301 
302 	if (cmd->c_optparse != NULL) {
303 		cmd->c_optparse(&npa);
304 	}
305 
306 	/*
307 	 * All commands but "list" require a ctl/ns argument. However, this
308 	 * should not be passed through to the command in its subsequent
309 	 * arguments.
310 	 */
311 	if ((npa.npa_argc == 0 || (strncmp(npa.npa_argv[0], "nvme", 4) != 0)) &&
312 	    cmd->c_func != do_list) {
313 		warnx("missing controller/namespace name");
314 		usage(cmd);
315 		exit(-1);
316 	}
317 
318 	if (npa.npa_argc > 0) {
319 		ctrl = npa.npa_argv[0];
320 		npa.npa_argv++;
321 		npa.npa_argc--;
322 	} else {
323 		ctrl = NULL;
324 	}
325 
326 	/*
327 	 * Make sure we're not running commands on multiple controllers that
328 	 * aren't allowed to do that.
329 	 */
330 	if (ctrl != NULL && strchr(ctrl, ',') != NULL &&
331 	    cmd->c_multi == B_FALSE) {
332 		warnx("%s not allowed on multiple controllers",
333 		    cmd->c_name);
334 		usage(cmd);
335 		exit(-1);
336 	}
337 
338 	/*
339 	 * Get controller/namespace arguments and run command.
340 	 */
341 	npa.npa_name = strtok_r(ctrl, ",", &lasts);
342 	do {
343 		if (npa.npa_name != NULL) {
344 			tmp = strchr(npa.npa_name, '/');
345 			if (tmp != NULL) {
346 				*tmp++ = '\0';
347 				npa.npa_nsid = tmp;
348 				npa.npa_isns = B_TRUE;
349 			}
350 		}
351 
352 		if ((node = di_init("/", DINFOSUBTREE | DINFOMINOR)) == NULL)
353 			err(-1, "failed to initialize libdevinfo");
354 		nvme_walk(&npa, node);
355 		di_fini(node);
356 
357 		if (npa.npa_found == 0) {
358 			if (npa.npa_name != NULL) {
359 				warnx("%s%.*s%.*s: no such controller or "
360 				    "namespace", npa.npa_name,
361 				    npa.npa_isns ? -1 : 0, "/",
362 				    npa.npa_isns ? -1 : 0, npa.npa_nsid);
363 			} else {
364 				warnx("no controllers found");
365 			}
366 			exitcode--;
367 		}
368 		npa.npa_found = 0;
369 		npa.npa_name = strtok_r(NULL, ",", &lasts);
370 	} while (npa.npa_name != NULL);
371 
372 	exit(exitcode);
373 }
374 
375 static void
376 nvme_oferr(const char *fmt, ...)
377 {
378 	va_list ap;
379 
380 	va_start(ap, fmt);
381 	verrx(-1, fmt, ap);
382 }
383 
384 static void
385 usage(const nvmeadm_cmd_t *cmd)
386 {
387 	(void) fprintf(stderr, "usage:\n");
388 	(void) fprintf(stderr, "  %s -h %s\n", getprogname(),
389 	    cmd != NULL ? cmd->c_name : "[<command>]");
390 	(void) fprintf(stderr, "  %s [-dv] ", getprogname());
391 
392 	if (cmd != NULL) {
393 		cmd->c_usage(cmd->c_name);
394 	} else {
395 		(void) fprintf(stderr,
396 		    "<command> <ctl>[/<ns>][,...] [<args>]\n");
397 		(void) fprintf(stderr,
398 		    "\n  Manage NVMe controllers and namespaces.\n");
399 		(void) fprintf(stderr, "\ncommands:\n");
400 
401 		for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++)
402 			(void) fprintf(stderr, "  %-18s - %s\n",
403 			    cmd->c_name, cmd->c_desc);
404 	}
405 	(void) fprintf(stderr, "\nflags:\n"
406 	    "  -h\t\tprint usage information\n"
407 	    "  -d\t\tprint information useful for debugging %s\n"
408 	    "  -v\t\tprint verbose information\n", getprogname());
409 	if (cmd != NULL && cmd->c_flagdesc != NULL)
410 		(void) fprintf(stderr, "%s\n", cmd->c_flagdesc);
411 }
412 
413 static boolean_t
414 nvme_match(nvme_process_arg_t *npa)
415 {
416 	char *name;
417 	char *nsid = NULL;
418 
419 	if (npa->npa_name == NULL)
420 		return (B_TRUE);
421 
422 	if (asprintf(&name, "%s%d", di_driver_name(npa->npa_node),
423 	    di_instance(npa->npa_node)) < 0)
424 		err(-1, "nvme_match()");
425 
426 	if (strcmp(name, npa->npa_name) != 0) {
427 		free(name);
428 		return (B_FALSE);
429 	}
430 
431 	free(name);
432 
433 	if (npa->npa_isns) {
434 		if (npa->npa_nsid == NULL)
435 			return (B_TRUE);
436 
437 		nsid = di_minor_name(npa->npa_minor);
438 
439 		if (nsid == NULL || strcmp(npa->npa_nsid, nsid) != 0)
440 			return (B_FALSE);
441 	}
442 
443 	return (B_TRUE);
444 }
445 
446 char *
447 nvme_dskname(const nvme_process_arg_t *npa)
448 {
449 	char *path = NULL;
450 	di_node_t child;
451 	di_dim_t dim;
452 	char *addr;
453 
454 	dim = di_dim_init();
455 
456 	for (child = di_child_node(npa->npa_node);
457 	    child != DI_NODE_NIL;
458 	    child = di_sibling_node(child)) {
459 		addr = di_bus_addr(child);
460 		if (addr == NULL)
461 			continue;
462 
463 		if (addr[0] == 'w')
464 			addr++;
465 
466 		if (strncasecmp(addr, di_minor_name(npa->npa_minor),
467 		    strchrnul(addr, ',') - addr) != 0)
468 			continue;
469 
470 		path = di_dim_path_dev(dim, di_driver_name(child),
471 		    di_instance(child), "c");
472 
473 		/*
474 		 * Error out if we didn't get a path, or if it's too short for
475 		 * the following operations to be safe.
476 		 */
477 		if (path == NULL || strlen(path) < 2)
478 			goto fail;
479 
480 		/* Chop off 's0' and get everything past the last '/' */
481 		path[strlen(path) - 2] = '\0';
482 		path = strrchr(path, '/');
483 		if (path == NULL)
484 			goto fail;
485 		path++;
486 
487 		break;
488 	}
489 
490 	di_dim_fini(dim);
491 
492 	return (path);
493 
494 fail:
495 	err(-1, "nvme_dskname");
496 }
497 
498 static int
499 nvme_process(di_node_t node, di_minor_t minor, void *arg)
500 {
501 	nvme_process_arg_t *npa = arg;
502 	int fd;
503 
504 	npa->npa_node = node;
505 	npa->npa_minor = minor;
506 
507 	if (!nvme_match(npa))
508 		return (DI_WALK_CONTINUE);
509 
510 	if ((fd = nvme_open(minor)) < 0)
511 		return (DI_WALK_CONTINUE);
512 
513 	npa->npa_found++;
514 
515 	npa->npa_path = di_devfs_path(node);
516 	if (npa->npa_path == NULL)
517 		goto out;
518 
519 	npa->npa_version = nvme_version(fd);
520 	if (npa->npa_version == NULL)
521 		goto out;
522 
523 	npa->npa_idctl = nvme_identify_ctrl(fd);
524 	if (npa->npa_idctl == NULL)
525 		goto out;
526 
527 	npa->npa_idns = nvme_identify_nsid(fd);
528 	if (npa->npa_idns == NULL)
529 		goto out;
530 
531 	if (npa->npa_isns)
532 		npa->npa_dsk = nvme_dskname(npa);
533 
534 	exitcode += npa->npa_cmd->c_func(fd, npa);
535 
536 out:
537 	di_devfs_path_free(npa->npa_path);
538 	free(npa->npa_dsk);
539 	free(npa->npa_version);
540 	free(npa->npa_idctl);
541 	free(npa->npa_idns);
542 
543 	npa->npa_version = NULL;
544 	npa->npa_idctl = NULL;
545 	npa->npa_idns = NULL;
546 
547 	nvme_close(fd);
548 
549 	return (DI_WALK_CONTINUE);
550 }
551 
552 static void
553 nvme_walk(nvme_process_arg_t *npa, di_node_t node)
554 {
555 	char *minor_nodetype = DDI_NT_NVME_NEXUS;
556 
557 	if (npa->npa_isns)
558 		minor_nodetype = DDI_NT_NVME_ATTACHMENT_POINT;
559 
560 	(void) di_walk_minor(node, minor_nodetype, 0, npa, nvme_process);
561 }
562 
563 static void
564 usage_list(const char *c_name)
565 {
566 	(void) fprintf(stderr, "%s "
567 	    "[-p -o field[,...]] [<ctl>[/<ns>][,...]\n\n"
568 	    "  List NVMe controllers and their namespaces. If no "
569 	    "controllers and/or name-\n  spaces are specified, all "
570 	    "controllers and namespaces in the system will be\n  "
571 	    "listed.\n", c_name);
572 }
573 
574 static void
575 optparse_list(nvme_process_arg_t *npa)
576 {
577 	int c;
578 	uint_t oflags = 0;
579 	boolean_t parse = B_FALSE;
580 	const char *fields = NULL;
581 
582 	optind = 0;
583 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":o:p")) != -1) {
584 		switch (c) {
585 		case 'o':
586 			fields = optarg;
587 			break;
588 		case 'p':
589 			parse = B_TRUE;
590 			oflags |= OFMT_PARSABLE;
591 			break;
592 		case '?':
593 			errx(-1, "unknown list option: -%c", optopt);
594 			break;
595 		case ':':
596 			errx(-1, "option -%c requires an argument", optopt);
597 		default:
598 			break;
599 		}
600 	}
601 
602 	if (fields != NULL && !parse) {
603 		errx(-1, "-o can only be used when in parsable mode (-p)");
604 	}
605 
606 	if (parse && fields == NULL) {
607 		errx(-1, "parsable mode (-p) requires one to specify output "
608 		    "fields with -o");
609 	}
610 
611 	if (parse) {
612 		ofmt_status_t oferr;
613 
614 		oferr = ofmt_open(fields, nvme_list_ofmt, oflags, 0,
615 		    &npa->npa_ofmt);
616 		ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);
617 	}
618 
619 	npa->npa_argc -= optind;
620 	npa->npa_argv += optind;
621 }
622 
623 static int
624 do_list_nsid(int fd, const nvme_process_arg_t *npa)
625 {
626 	_NOTE(ARGUNUSED(fd));
627 	const uint_t format = npa->npa_idns->id_flbas.lba_format;
628 	const uint_t bshift = npa->npa_idns->id_lbaf[format].lbaf_lbads;
629 
630 	/*
631 	 * Some devices have extra namespaces with illegal block sizes and
632 	 * zero blocks. Don't list them when verbose operation isn't requested.
633 	 */
634 	if ((bshift < 9 || npa->npa_idns->id_nsize == 0) && verbose == 0)
635 		return (0);
636 
637 	if (npa->npa_ofmt == NULL) {
638 		(void) printf("  %s/%s (%s): ", npa->npa_name,
639 		    di_minor_name(npa->npa_minor),
640 		    npa->npa_dsk != NULL ? npa->npa_dsk : "unattached");
641 		nvme_print_nsid_summary(npa->npa_idns);
642 	} else {
643 		ofmt_print(npa->npa_ofmt, (void *)npa);
644 	}
645 
646 	return (0);
647 }
648 
649 static int
650 do_list(int fd, const nvme_process_arg_t *npa)
651 {
652 	_NOTE(ARGUNUSED(fd));
653 
654 	nvme_process_arg_t ns_npa = { 0 };
655 	nvmeadm_cmd_t cmd = { 0 };
656 	char *name;
657 
658 	if (asprintf(&name, "%s%d", di_driver_name(npa->npa_node),
659 	    di_instance(npa->npa_node)) < 0)
660 		err(-1, "do_list()");
661 
662 	if (npa->npa_ofmt == NULL) {
663 		(void) printf("%s: ", name);
664 		nvme_print_ctrl_summary(npa->npa_idctl, npa->npa_version);
665 	}
666 
667 	ns_npa.npa_name = name;
668 	ns_npa.npa_isns = B_TRUE;
669 	ns_npa.npa_nsid = npa->npa_nsid;
670 	cmd = *(npa->npa_cmd);
671 	cmd.c_func = do_list_nsid;
672 	ns_npa.npa_cmd = &cmd;
673 	ns_npa.npa_ofmt = npa->npa_ofmt;
674 	ns_npa.npa_idctl = npa->npa_idctl;
675 
676 	nvme_walk(&ns_npa, npa->npa_node);
677 
678 	free(name);
679 
680 	return (exitcode);
681 }
682 
683 static void
684 usage_identify(const char *c_name)
685 {
686 	(void) fprintf(stderr, "%s <ctl>[/<ns>][,...]\n\n"
687 	    "  Print detailed information about the specified NVMe "
688 	    "controllers and/or name-\n  spaces.\n", c_name);
689 }
690 
691 static int
692 do_identify(int fd, const nvme_process_arg_t *npa)
693 {
694 	if (!npa->npa_isns) {
695 		nvme_capabilities_t *cap;
696 
697 		cap = nvme_capabilities(fd);
698 		if (cap == NULL)
699 			return (-1);
700 
701 		(void) printf("%s: ", npa->npa_name);
702 		nvme_print_identify_ctrl(npa->npa_idctl, cap,
703 		    npa->npa_version);
704 
705 		free(cap);
706 	} else {
707 		(void) printf("%s/%s: ", npa->npa_name,
708 		    di_minor_name(npa->npa_minor));
709 		nvme_print_identify_nsid(npa->npa_idns,
710 		    npa->npa_version);
711 	}
712 
713 	return (0);
714 }
715 
716 static void
717 usage_get_logpage(const char *c_name)
718 {
719 	(void) fprintf(stderr, "%s <ctl>[/<ns>][,...] <logpage>\n\n"
720 	    "  Print the specified log page of the specified NVMe "
721 	    "controllers and/or name-\n  spaces. Supported log pages "
722 	    "are error, health, and firmware.\n", c_name);
723 }
724 
725 static int
726 do_get_logpage_error(int fd, const nvme_process_arg_t *npa)
727 {
728 	int nlog = npa->npa_idctl->id_elpe + 1;
729 	size_t bufsize = sizeof (nvme_error_log_entry_t) * nlog;
730 	nvme_error_log_entry_t *elog;
731 
732 	if (npa->npa_isns)
733 		errx(-1, "Error Log not available on a per-namespace basis");
734 
735 	elog = nvme_get_logpage(fd, NVME_LOGPAGE_ERROR, &bufsize);
736 
737 	if (elog == NULL)
738 		return (-1);
739 
740 	nlog = bufsize / sizeof (nvme_error_log_entry_t);
741 
742 	(void) printf("%s: ", npa->npa_name);
743 	nvme_print_error_log(nlog, elog, npa->npa_version);
744 
745 	free(elog);
746 
747 	return (0);
748 }
749 
750 static int
751 do_get_logpage_health(int fd, const nvme_process_arg_t *npa)
752 {
753 	size_t bufsize = sizeof (nvme_health_log_t);
754 	nvme_health_log_t *hlog;
755 
756 	if (npa->npa_isns) {
757 		if (npa->npa_idctl->id_lpa.lp_smart == 0)
758 			errx(-1, "SMART/Health information not available "
759 			    "on a per-namespace basis on this controller");
760 	}
761 
762 	hlog = nvme_get_logpage(fd, NVME_LOGPAGE_HEALTH, &bufsize);
763 
764 	if (hlog == NULL)
765 		return (-1);
766 
767 	(void) printf("%s: ", npa->npa_name);
768 	nvme_print_health_log(hlog, npa->npa_idctl, npa->npa_version);
769 
770 	free(hlog);
771 
772 	return (0);
773 }
774 
775 static int
776 do_get_logpage_fwslot(int fd, const nvme_process_arg_t *npa)
777 {
778 	size_t bufsize = sizeof (nvme_fwslot_log_t);
779 	nvme_fwslot_log_t *fwlog;
780 
781 	if (npa->npa_isns)
782 		errx(-1, "Firmware Slot information not available on a "
783 		    "per-namespace basis");
784 
785 	fwlog = nvme_get_logpage(fd, NVME_LOGPAGE_FWSLOT, &bufsize);
786 
787 	if (fwlog == NULL)
788 		return (-1);
789 
790 	(void) printf("%s: ", npa->npa_name);
791 	nvme_print_fwslot_log(fwlog);
792 
793 	free(fwlog);
794 
795 	return (0);
796 }
797 
798 static int
799 do_get_logpage(int fd, const nvme_process_arg_t *npa)
800 {
801 	int ret = 0;
802 	int (*func)(int, const nvme_process_arg_t *);
803 
804 	if (npa->npa_argc < 1) {
805 		warnx("missing logpage name");
806 		usage(npa->npa_cmd);
807 		exit(-1);
808 	}
809 
810 	if (strcmp(npa->npa_argv[0], "error") == 0)
811 		func = do_get_logpage_error;
812 	else if (strcmp(npa->npa_argv[0], "health") == 0)
813 		func = do_get_logpage_health;
814 	else if (strcmp(npa->npa_argv[0], "firmware") == 0)
815 		func = do_get_logpage_fwslot;
816 	else
817 		errx(-1, "invalid log page: %s", npa->npa_argv[0]);
818 
819 	ret = func(fd, npa);
820 	return (ret);
821 }
822 
823 static void
824 usage_get_features(const char *c_name)
825 {
826 	const nvme_feature_t *feat;
827 
828 	(void) fprintf(stderr, "%s <ctl>[/<ns>][,...] [<feature>[,...]]\n\n"
829 	    "  Print the specified features of the specified NVMe controllers "
830 	    "and/or\n  namespaces. Supported features are:\n\n", c_name);
831 	(void) fprintf(stderr, "    %-35s %-14s %s\n",
832 	    "FEATURE NAME", "SHORT NAME", "CONTROLLER/NAMESPACE");
833 	for (feat = &features[0]; feat->f_feature != 0; feat++) {
834 		char *type;
835 
836 		if ((feat->f_getflags & NVMEADM_BOTH) == NVMEADM_BOTH)
837 			type = "both";
838 		else if ((feat->f_getflags & NVMEADM_CTRL) != 0)
839 			type = "controller only";
840 		else
841 			type = "namespace only";
842 
843 		(void) fprintf(stderr, "    %-35s %-14s %s\n",
844 		    feat->f_name, feat->f_short, type);
845 	}
846 
847 }
848 
849 static int
850 do_get_feat_common(int fd, const nvme_feature_t *feat,
851     const nvme_process_arg_t *npa)
852 {
853 	void *buf = NULL;
854 	size_t bufsize = feat->f_bufsize;
855 	uint64_t res;
856 
857 	if (nvme_get_feature(fd, feat->f_feature, 0, &res, &bufsize, &buf)
858 	    == B_FALSE)
859 		return (EINVAL);
860 
861 	nvme_print(2, feat->f_name, -1, NULL);
862 	feat->f_print(res, buf, bufsize, npa->npa_idctl, npa->npa_version);
863 	free(buf);
864 
865 	return (0);
866 }
867 
868 static int
869 do_get_feat_temp_thresh_one(int fd, const nvme_feature_t *feat,
870     const char *label, uint16_t tmpsel, uint16_t thsel,
871     const nvme_process_arg_t *npa)
872 {
873 	uint64_t res;
874 	void *buf = NULL;
875 	size_t bufsize = feat->f_bufsize;
876 	nvme_temp_threshold_t tt;
877 
878 	tt.r = 0;
879 	tt.b.tt_tmpsel = tmpsel;
880 	tt.b.tt_thsel = thsel;
881 
882 	if (!nvme_get_feature(fd, feat->f_feature, tt.r, &res, &bufsize,
883 	    &buf)) {
884 		return (EINVAL);
885 	}
886 
887 	feat->f_print(res, (void *)label, 0, npa->npa_idctl, npa->npa_version);
888 	free(buf);
889 	return (0);
890 }
891 
892 /*
893  * In NVMe 1.2, the specification allowed for up to 8 sensors to be on the
894  * device and changed the main device to have a composite temperature sensor. As
895  * a result, there is a set of thresholds for each sensor. In addition, they
896  * added both an over-temperature and under-temperature threshold. Since most
897  * devices don't actually implement all the sensors, we get the health page and
898  * see which sensors have a non-zero value to determine how to proceed.
899  */
900 static int
901 do_get_feat_temp_thresh(int fd, const nvme_feature_t *feat,
902     const nvme_process_arg_t *npa)
903 {
904 	int ret;
905 	size_t bufsize = sizeof (nvme_health_log_t);
906 	nvme_health_log_t *hlog;
907 
908 	nvme_print(2, feat->f_name, -1, NULL);
909 	if ((ret = do_get_feat_temp_thresh_one(fd, feat,
910 	    "Composite Over Temp. Threshold", 0, NVME_TEMP_THRESH_OVER,
911 	    npa)) != 0) {
912 		return (ret);
913 	}
914 
915 	if (!nvme_version_check(npa->npa_version, 1, 2)) {
916 		return (0);
917 	}
918 
919 	if ((ret = do_get_feat_temp_thresh_one(fd, feat,
920 	    "Composite Under Temp. Threshold", 0, NVME_TEMP_THRESH_UNDER,
921 	    npa)) != 0) {
922 		return (ret);
923 	}
924 
925 	hlog = nvme_get_logpage(fd, NVME_LOGPAGE_HEALTH, &bufsize);
926 	if (hlog == NULL) {
927 		warnx("failed to get health log page, unable to get "
928 		    "thresholds for additional sensors");
929 		return (0);
930 	}
931 
932 	if (hlog->hl_temp_sensor_1 != 0) {
933 		(void) do_get_feat_temp_thresh_one(fd, feat,
934 		    "Temp. Sensor 1 Over Temp. Threshold", 1,
935 		    NVME_TEMP_THRESH_OVER, npa);
936 		(void) do_get_feat_temp_thresh_one(fd, feat,
937 		    "Temp. Sensor 1 Under Temp. Threshold", 1,
938 		    NVME_TEMP_THRESH_UNDER, npa);
939 	}
940 
941 	if (hlog->hl_temp_sensor_2 != 0) {
942 		(void) do_get_feat_temp_thresh_one(fd, feat,
943 		    "Temp. Sensor 2 Over Temp. Threshold", 2,
944 		    NVME_TEMP_THRESH_OVER, npa);
945 		(void) do_get_feat_temp_thresh_one(fd, feat,
946 		    "Temp. Sensor 2 Under Temp. Threshold", 2,
947 		    NVME_TEMP_THRESH_UNDER, npa);
948 	}
949 
950 	if (hlog->hl_temp_sensor_3 != 0) {
951 		(void) do_get_feat_temp_thresh_one(fd, feat,
952 		    "Temp. Sensor 3 Over Temp. Threshold", 3,
953 		    NVME_TEMP_THRESH_OVER, npa);
954 		(void) do_get_feat_temp_thresh_one(fd, feat,
955 		    "Temp. Sensor 3 Under Temp. Threshold", 3,
956 		    NVME_TEMP_THRESH_UNDER, npa);
957 	}
958 
959 	if (hlog->hl_temp_sensor_4 != 0) {
960 		(void) do_get_feat_temp_thresh_one(fd, feat,
961 		    "Temp. Sensor 4 Over Temp. Threshold", 4,
962 		    NVME_TEMP_THRESH_OVER, npa);
963 		(void) do_get_feat_temp_thresh_one(fd, feat,
964 		    "Temp. Sensor 4 Under Temp. Threshold", 4,
965 		    NVME_TEMP_THRESH_UNDER, npa);
966 	}
967 
968 	if (hlog->hl_temp_sensor_5 != 0) {
969 		(void) do_get_feat_temp_thresh_one(fd, feat,
970 		    "Temp. Sensor 5 Over Temp. Threshold", 5,
971 		    NVME_TEMP_THRESH_OVER, npa);
972 		(void) do_get_feat_temp_thresh_one(fd, feat,
973 		    "Temp. Sensor 5 Under Temp. Threshold", 5,
974 		    NVME_TEMP_THRESH_UNDER, npa);
975 	}
976 
977 	if (hlog->hl_temp_sensor_6 != 0) {
978 		(void) do_get_feat_temp_thresh_one(fd, feat,
979 		    "Temp. Sensor 6 Over Temp. Threshold", 6,
980 		    NVME_TEMP_THRESH_OVER, npa);
981 		(void) do_get_feat_temp_thresh_one(fd, feat,
982 		    "Temp. Sensor 6 Under Temp. Threshold", 6,
983 		    NVME_TEMP_THRESH_UNDER, npa);
984 	}
985 
986 	if (hlog->hl_temp_sensor_7 != 0) {
987 		(void) do_get_feat_temp_thresh_one(fd, feat,
988 		    "Temp. Sensor 7 Over Temp. Threshold", 7,
989 		    NVME_TEMP_THRESH_OVER, npa);
990 		(void) do_get_feat_temp_thresh_one(fd, feat,
991 		    "Temp. Sensor 7 Under Temp. Threshold", 7,
992 		    NVME_TEMP_THRESH_UNDER, npa);
993 	}
994 
995 	if (hlog->hl_temp_sensor_8 != 0) {
996 		(void) do_get_feat_temp_thresh_one(fd, feat,
997 		    "Temp. Sensor 8 Over Temp. Threshold", 8,
998 		    NVME_TEMP_THRESH_OVER, npa);
999 		(void) do_get_feat_temp_thresh_one(fd, feat,
1000 		    "Temp. Sensor 8 Under Temp. Threshold", 8,
1001 		    NVME_TEMP_THRESH_UNDER, npa);
1002 	}
1003 	free(hlog);
1004 	return (0);
1005 }
1006 
1007 static int
1008 do_get_feat_intr_vect(int fd, const nvme_feature_t *feat,
1009     const nvme_process_arg_t *npa)
1010 {
1011 	uint64_t res;
1012 	uint64_t arg;
1013 	int intr_cnt;
1014 
1015 	intr_cnt = nvme_intr_cnt(fd);
1016 
1017 	if (intr_cnt == -1)
1018 		return (EINVAL);
1019 
1020 	nvme_print(2, feat->f_name, -1, NULL);
1021 
1022 	for (arg = 0; arg < intr_cnt; arg++) {
1023 		if (nvme_get_feature(fd, feat->f_feature, arg, &res, NULL, NULL)
1024 		    == B_FALSE)
1025 			return (EINVAL);
1026 
1027 		feat->f_print(res, NULL, 0, npa->npa_idctl, npa->npa_version);
1028 	}
1029 
1030 	return (0);
1031 }
1032 
1033 static int
1034 do_get_features(int fd, const nvme_process_arg_t *npa)
1035 {
1036 	const nvme_feature_t *feat;
1037 	char *f, *flist, *lasts;
1038 	boolean_t header_printed = B_FALSE;
1039 
1040 	if (npa->npa_argc > 1)
1041 		errx(-1, "unexpected arguments");
1042 
1043 	/*
1044 	 * No feature list given, print all supported features.
1045 	 */
1046 	if (npa->npa_argc == 0) {
1047 		(void) printf("%s: Get Features\n", npa->npa_name);
1048 		for (feat = &features[0]; feat->f_feature != 0; feat++) {
1049 			if ((npa->npa_isns &&
1050 			    (feat->f_getflags & NVMEADM_NS) == 0) ||
1051 			    (!npa->npa_isns &&
1052 			    (feat->f_getflags & NVMEADM_CTRL) == 0))
1053 				continue;
1054 
1055 			(void) feat->f_get(fd, feat, npa);
1056 		}
1057 
1058 		return (0);
1059 	}
1060 
1061 	/*
1062 	 * Process feature list.
1063 	 */
1064 	flist = strdup(npa->npa_argv[0]);
1065 	if (flist == NULL)
1066 		err(-1, "do_get_features");
1067 
1068 	for (f = strtok_r(flist, ",", &lasts);
1069 	    f != NULL;
1070 	    f = strtok_r(NULL, ",", &lasts)) {
1071 		while (isspace(*f))
1072 			f++;
1073 
1074 		for (feat = &features[0]; feat->f_feature != 0; feat++) {
1075 			if (strncasecmp(feat->f_name, f, strlen(f)) == 0 ||
1076 			    strncasecmp(feat->f_short, f, strlen(f)) == 0)
1077 				break;
1078 		}
1079 
1080 		if (feat->f_feature == 0) {
1081 			warnx("unknown feature %s", f);
1082 			continue;
1083 		}
1084 
1085 		if ((npa->npa_isns &&
1086 		    (feat->f_getflags & NVMEADM_NS) == 0) ||
1087 		    (!npa->npa_isns &&
1088 		    (feat->f_getflags & NVMEADM_CTRL) == 0)) {
1089 			warnx("feature %s %s supported for namespaces",
1090 			    feat->f_name, (feat->f_getflags & NVMEADM_NS) != 0 ?
1091 			    "only" : "not");
1092 			continue;
1093 		}
1094 
1095 		if (!header_printed) {
1096 			(void) printf("%s: Get Features\n", npa->npa_name);
1097 			header_printed = B_TRUE;
1098 		}
1099 
1100 		if (feat->f_get(fd, feat, npa) != 0) {
1101 			warnx("unsupported feature: %s", feat->f_name);
1102 			continue;
1103 		}
1104 	}
1105 
1106 	free(flist);
1107 	return (0);
1108 }
1109 
1110 static int
1111 do_format_common(int fd, const nvme_process_arg_t *npa, unsigned long lbaf,
1112     unsigned long ses)
1113 {
1114 	nvme_process_arg_t ns_npa = { 0 };
1115 	nvmeadm_cmd_t cmd = { 0 };
1116 
1117 	cmd = *(npa->npa_cmd);
1118 	cmd.c_func = do_attach_detach;
1119 	cmd.c_name = "detach";
1120 	ns_npa = *npa;
1121 	ns_npa.npa_cmd = &cmd;
1122 
1123 	if (do_attach_detach(fd, &ns_npa) != 0)
1124 		return (exitcode);
1125 	if (nvme_format_nvm(fd, lbaf, ses) == B_FALSE) {
1126 		warn("%s failed", npa->npa_cmd->c_name);
1127 		exitcode += -1;
1128 	}
1129 	cmd.c_name = "attach";
1130 	exitcode += do_attach_detach(fd, &ns_npa);
1131 
1132 	return (exitcode);
1133 }
1134 
1135 static void
1136 usage_format(const char *c_name)
1137 {
1138 	(void) fprintf(stderr, "%s <ctl>[/<ns>] [<lba-format>]\n\n"
1139 	    "  Format one or all namespaces of the specified NVMe "
1140 	    "controller. Supported LBA\n  formats can be queried with "
1141 	    "the \"%s identify\" command on the namespace\n  to be "
1142 	    "formatted.\n", c_name, getprogname());
1143 }
1144 
1145 static int
1146 do_format(int fd, const nvme_process_arg_t *npa)
1147 {
1148 	unsigned long lbaf;
1149 
1150 	if (npa->npa_idctl->id_oacs.oa_format == 0)
1151 		errx(-1, "%s not supported", npa->npa_cmd->c_name);
1152 
1153 	if (npa->npa_isns && npa->npa_idctl->id_fna.fn_format != 0)
1154 		errx(-1, "%s not supported on individual namespace",
1155 		    npa->npa_cmd->c_name);
1156 
1157 
1158 	if (npa->npa_argc > 0) {
1159 		errno = 0;
1160 		lbaf = strtoul(npa->npa_argv[0], NULL, 10);
1161 
1162 		if (errno != 0 || lbaf > NVME_FRMT_MAX_LBAF)
1163 			errx(-1, "invalid LBA format %d", lbaf + 1);
1164 
1165 		if (npa->npa_idns->id_lbaf[lbaf].lbaf_ms != 0)
1166 			errx(-1, "LBA formats with metadata not supported");
1167 	} else {
1168 		lbaf = npa->npa_idns->id_flbas.lba_format;
1169 	}
1170 
1171 	return (do_format_common(fd, npa, lbaf, 0));
1172 }
1173 
1174 static void
1175 usage_secure_erase(const char *c_name)
1176 {
1177 	(void) fprintf(stderr, "%s <ctl>[/<ns>] [-c]\n\n"
1178 	    "  Secure-Erase one or all namespaces of the specified "
1179 	    "NVMe controller.\n", c_name);
1180 }
1181 
1182 static int
1183 do_secure_erase(int fd, const nvme_process_arg_t *npa)
1184 {
1185 	unsigned long lbaf;
1186 	uint8_t ses = NVME_FRMT_SES_USER;
1187 
1188 	if (npa->npa_idctl->id_oacs.oa_format == 0)
1189 		errx(-1, "%s not supported", npa->npa_cmd->c_name);
1190 
1191 	if (npa->npa_isns && npa->npa_idctl->id_fna.fn_sec_erase != 0)
1192 		errx(-1, "%s not supported on individual namespace",
1193 		    npa->npa_cmd->c_name);
1194 
1195 	if (npa->npa_argc > 0) {
1196 		if (strcmp(npa->npa_argv[0], "-c") == 0)
1197 			ses = NVME_FRMT_SES_CRYPTO;
1198 		else
1199 			usage(npa->npa_cmd);
1200 	}
1201 
1202 	if (ses == NVME_FRMT_SES_CRYPTO &&
1203 	    npa->npa_idctl->id_fna.fn_crypt_erase == 0)
1204 		errx(-1, "cryptographic %s not supported",
1205 		    npa->npa_cmd->c_name);
1206 
1207 	lbaf = npa->npa_idns->id_flbas.lba_format;
1208 
1209 	return (do_format_common(fd, npa, lbaf, ses));
1210 }
1211 
1212 static void
1213 usage_attach_detach(const char *c_name)
1214 {
1215 	(void) fprintf(stderr, "%s <ctl>[/<ns>]\n\n"
1216 	    "  %c%s blkdev(7d) %s one or all namespaces of the "
1217 	    "specified NVMe controller.\n",
1218 	    c_name, toupper(c_name[0]), &c_name[1],
1219 	    c_name[0] == 'd' ? "from" : "to");
1220 }
1221 
1222 static int
1223 do_attach_detach(int fd, const nvme_process_arg_t *npa)
1224 {
1225 	char *c_name = npa->npa_cmd->c_name;
1226 
1227 	if (!npa->npa_isns) {
1228 		nvme_process_arg_t ns_npa = { 0 };
1229 
1230 		ns_npa.npa_name = npa->npa_name;
1231 		ns_npa.npa_isns = B_TRUE;
1232 		ns_npa.npa_cmd = npa->npa_cmd;
1233 
1234 		nvme_walk(&ns_npa, npa->npa_node);
1235 
1236 		return (exitcode);
1237 	} else {
1238 		if ((c_name[0] == 'd' ? nvme_detach : nvme_attach)(fd)
1239 		    == B_FALSE) {
1240 			warn("%s failed", c_name);
1241 			return (-1);
1242 		}
1243 	}
1244 
1245 	return (0);
1246 }
1247 
1248 static void
1249 usage_firmware_load(const char *c_name)
1250 {
1251 	(void) fprintf(stderr, "%s <ctl> <image file> [<offset>]\n\n"
1252 	    "  Load firmware <image file> to offset <offset>.\n"
1253 	    "  The firmware needs to be committed to a slot using "
1254 	    "\"nvmeadm commit-firmware\"\n  command.\n", c_name);
1255 }
1256 
1257 /*
1258  * Read exactly len bytes, or until eof.
1259  */
1260 static ssize_t
1261 read_block(int fd, char *buf, size_t len)
1262 {
1263 	size_t remain;
1264 	ssize_t bytes;
1265 
1266 	remain = len;
1267 	while (remain > 0) {
1268 		bytes = read(fd, buf, remain);
1269 		if (bytes == 0)
1270 			break;
1271 
1272 		if (bytes < 0) {
1273 			if (errno == EINTR)
1274 				continue;
1275 
1276 			return (-1);
1277 		}
1278 
1279 		buf += bytes;
1280 		remain -= bytes;
1281 	}
1282 
1283 	return (len - remain);
1284 }
1285 
1286 /*
1287  * Convert a string to a valid firmware upload offset (in bytes).
1288  */
1289 static offset_t
1290 get_fw_offsetb(char *str)
1291 {
1292 	longlong_t offsetb;
1293 	char *valend;
1294 
1295 	errno = 0;
1296 	offsetb = strtoll(str, &valend, 0);
1297 	if (errno != 0 || *valend != '\0' || offsetb < 0 ||
1298 	    offsetb > NVME_FW_OFFSETB_MAX)
1299 		errx(-1, "Offset must be numeric and in the range of 0 to %llu",
1300 		    NVME_FW_OFFSETB_MAX);
1301 
1302 	if ((offsetb & NVME_DWORD_MASK) != 0)
1303 		errx(-1, "Offset must be multiple of %d", NVME_DWORD_SIZE);
1304 
1305 	return ((offset_t)offsetb);
1306 }
1307 
1308 #define	FIRMWARE_READ_BLKSIZE	(64 * 1024)		/* 64K */
1309 
1310 static int
1311 do_firmware_load(int fd, const nvme_process_arg_t *npa)
1312 {
1313 	int fw_fd;
1314 	ssize_t len;
1315 	offset_t offset = 0;
1316 	size_t size;
1317 	char buf[FIRMWARE_READ_BLKSIZE];
1318 
1319 	if (npa->npa_argc > 2)
1320 		errx(-1, "Too many arguments");
1321 
1322 	if (npa->npa_argc == 0)
1323 		errx(-1, "Requires firmware file name, and an "
1324 		    "optional offset");
1325 
1326 	if (npa->npa_argc == 2)
1327 		offset = get_fw_offsetb(npa->npa_argv[1]);
1328 
1329 	fw_fd = open(npa->npa_argv[0], O_RDONLY);
1330 	if (fw_fd < 0)
1331 		errx(-1, "Failed to open \"%s\": %s", npa->npa_argv[0],
1332 		    strerror(errno));
1333 
1334 	size = 0;
1335 	do {
1336 		len = read_block(fw_fd, buf, sizeof (buf));
1337 
1338 		if (len < 0)
1339 			errx(-1, "Error reading \"%s\": %s", npa->npa_argv[0],
1340 			    strerror(errno));
1341 
1342 		if (len == 0)
1343 			break;
1344 
1345 		if (!nvme_firmware_load(fd, buf, len, offset))
1346 			errx(-1, "Error loading \"%s\": %s", npa->npa_argv[0],
1347 			    strerror(errno));
1348 
1349 		offset += len;
1350 		size += len;
1351 	} while (len == sizeof (buf));
1352 
1353 	(void) close(fw_fd);
1354 
1355 	if (verbose)
1356 		(void) printf("%zu bytes downloaded.\n", size);
1357 
1358 	return (0);
1359 }
1360 
1361 /*
1362  * Convert str to a valid firmware slot number.
1363  */
1364 static uint_t
1365 get_slot_number(char *str)
1366 {
1367 	longlong_t slot;
1368 	char *valend;
1369 
1370 	errno = 0;
1371 	slot = strtoll(str, &valend, 0);
1372 	if (errno != 0 || *valend != '\0' ||
1373 	    slot < NVME_FW_SLOT_MIN || slot > NVME_FW_SLOT_MAX)
1374 		errx(-1, "Slot must be numeric and in the range of %d to %d",
1375 		    NVME_FW_SLOT_MIN, NVME_FW_SLOT_MAX);
1376 
1377 	return ((uint_t)slot);
1378 }
1379 
1380 static void
1381 usage_firmware_commit(const char *c_name)
1382 {
1383 	(void) fprintf(stderr, "%s <ctl> <slot>\n\n"
1384 	    "  Commit previously downloaded firmware to slot <slot>.\n"
1385 	    "  The firmware is only activated after a "
1386 	    "\"nvmeadm activate-firmware\" command.\n", c_name);
1387 }
1388 
1389 static int
1390 do_firmware_commit(int fd, const nvme_process_arg_t *npa)
1391 {
1392 	uint_t slot;
1393 	uint16_t sct, sc;
1394 
1395 	if (npa->npa_argc > 1)
1396 		errx(-1, "Too many arguments");
1397 
1398 	if (npa->npa_argc == 0)
1399 		errx(-1, "Firmware slot number is required");
1400 
1401 	slot = get_slot_number(npa->npa_argv[0]);
1402 
1403 	if (!nvme_firmware_commit(fd, slot, NVME_FWC_SAVE, &sct, &sc))
1404 		errx(-1, "Failed to commit firmware to slot %u: %s",
1405 		    slot, nvme_str_error(sct, sc));
1406 
1407 	if (verbose)
1408 		(void) printf("Firmware committed to slot %u.\n", slot);
1409 
1410 	return (0);
1411 }
1412 
1413 static void
1414 usage_firmware_activate(const char *c_name)
1415 {
1416 	(void) fprintf(stderr, "%s <ctl> <slot>\n\n"
1417 	    "  Activate firmware in slot <slot>.\n"
1418 	    "  The firmware will be in use after the next system reset.\n",
1419 	    c_name);
1420 }
1421 
1422 static int
1423 do_firmware_activate(int fd, const nvme_process_arg_t *npa)
1424 {
1425 	uint_t slot;
1426 	uint16_t sct, sc;
1427 
1428 	if (npa->npa_argc > 1)
1429 		errx(-1, "Too many arguments");
1430 
1431 	if (npa->npa_argc == 0)
1432 		errx(-1, "Firmware slot number is required");
1433 
1434 	slot = get_slot_number(npa->npa_argv[0]);
1435 
1436 	if (!nvme_firmware_commit(fd, slot, NVME_FWC_ACTIVATE, &sct, &sc))
1437 		errx(-1, "Failed to activate slot %u: %s", slot,
1438 		    nvme_str_error(sct, sc));
1439 
1440 	if (verbose)
1441 		printf("Slot %u activated: %s.\n", slot,
1442 		    nvme_str_error(sct, sc));
1443 
1444 	return (0);
1445 }
1446 
1447 /*
1448  * While the NVME_VERSION_ATLEAST macro exists, specifying a version of 1.0
1449  * causes GCC to helpfully flag the -Wtype-limits warning because a uint_t is
1450  * always >= 0. In many cases it's useful to always indicate what version
1451  * something was added in to simplify code (e.g. nvmeadm_print_bit) and we'd
1452  * rather just say it's version 1.0 rather than making folks realize that a
1453  * hardcoded true is equivalent. Therefore we have this function which can't
1454  * trigger this warning today (and adds a minor amount of type safety). If GCC
1455  * or clang get smart enough to see through this, then we'll have to just
1456  * disable the warning for the single minor comparison (and reformat this a bit
1457  * to minimize the impact).
1458  */
1459 boolean_t
1460 nvme_version_check(nvme_version_t *vers, uint_t major, uint_t minor)
1461 {
1462 	if (vers->v_major > major) {
1463 		return (B_TRUE);
1464 	}
1465 
1466 	return (vers->v_major == major && vers->v_minor >= minor);
1467 }
1468