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