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