xref: /illumos-gate/usr/src/cmd/nvmeadm/nvmeadm.c (revision b9538c2103fde34bfe8a8c2b3e878ecd1586d182)
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  */
16 
17 /*
18  * nvmeadm -- NVMe administration utility
19  *
20  * nvmeadm [-v] [-d] [-h] <command> [<ctl>[/<ns>][,...]] [args]
21  * commands:	list
22  *		identify
23  *		get-logpage <logpage name>
24  *		get-features <feature>[,...]
25  *		format ...
26  *		secure-erase ...
27  *		detach ...
28  *		attach ...
29  *		get-param ...
30  *		set-param ...
31  *		load-firmware ...
32  *		activate-firmware ...
33  *		write-uncorrectable ...
34  *		compare ...
35  *		compare-and-write ...
36  */
37 
38 #include <stdio.h>
39 #include <stdlib.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 typedef struct nvme_process_arg nvme_process_arg_t;
51 typedef struct nvme_feature nvme_feature_t;
52 typedef struct nvmeadm_cmd nvmeadm_cmd_t;
53 
54 struct nvme_process_arg {
55 	int npa_argc;
56 	char **npa_argv;
57 	char *npa_name;
58 	char *npa_nsid;
59 	int npa_found;
60 	boolean_t npa_isns;
61 	const nvmeadm_cmd_t *npa_cmd;
62 	di_node_t npa_node;
63 	di_minor_t npa_minor;
64 	char *npa_path;
65 	char *npa_dsk;
66 	nvme_identify_ctrl_t *npa_idctl;
67 	nvme_identify_nsid_t *npa_idns;
68 	nvme_version_t *npa_version;
69 };
70 
71 struct nvme_feature {
72 	char *f_name;
73 	char *f_short;
74 	uint8_t f_feature;
75 	size_t f_bufsize;
76 	uint_t f_getflags;
77 	int (*f_get)(int, const nvme_feature_t *, nvme_identify_ctrl_t *);
78 	void (*f_print)(uint64_t, void *, size_t, nvme_identify_ctrl_t *);
79 };
80 
81 #define	NVMEADM_CTRL	1
82 #define	NVMEADM_NS	2
83 #define	NVMEADM_BOTH	(NVMEADM_CTRL | NVMEADM_NS)
84 
85 struct nvmeadm_cmd {
86 	char *c_name;
87 	char *c_desc;
88 	char *c_flagdesc;
89 	int (*c_func)(int, const nvme_process_arg_t *);
90 	void (*c_usage)(const char *);
91 	boolean_t c_multi;
92 };
93 
94 
95 static void usage(const nvmeadm_cmd_t *);
96 static void nvme_walk(nvme_process_arg_t *, di_node_t);
97 static boolean_t nvme_match(nvme_process_arg_t *);
98 
99 static int nvme_process(di_node_t, di_minor_t, void *);
100 
101 static int do_list(int, const nvme_process_arg_t *);
102 static int do_identify(int, const nvme_process_arg_t *);
103 static int do_get_logpage_error(int, const nvme_process_arg_t *);
104 static int do_get_logpage_health(int, const nvme_process_arg_t *);
105 static int do_get_logpage_fwslot(int, const nvme_process_arg_t *);
106 static int do_get_logpage(int, const nvme_process_arg_t *);
107 static int do_get_feat_common(int, const nvme_feature_t *,
108     nvme_identify_ctrl_t *);
109 static int do_get_feat_intr_vect(int, const nvme_feature_t *,
110     nvme_identify_ctrl_t *);
111 static int do_get_features(int, const nvme_process_arg_t *);
112 static int do_format(int, const nvme_process_arg_t *);
113 static int do_secure_erase(int, const nvme_process_arg_t *);
114 static int do_attach_detach(int, const nvme_process_arg_t *);
115 
116 static void usage_list(const char *);
117 static void usage_identify(const char *);
118 static void usage_get_logpage(const char *);
119 static void usage_get_features(const char *);
120 static void usage_format(const char *);
121 static void usage_secure_erase(const char *);
122 static void usage_attach_detach(const char *);
123 
124 int verbose;
125 int debug;
126 static int exitcode;
127 
128 static const nvmeadm_cmd_t nvmeadm_cmds[] = {
129 	{
130 		"list",
131 		"list controllers and namespaces",
132 		NULL,
133 		do_list, usage_list, B_TRUE
134 	},
135 	{
136 		"identify",
137 		"identify controllers and/or namespaces",
138 		NULL,
139 		do_identify, usage_identify, B_TRUE
140 	},
141 	{
142 		"get-logpage",
143 		"get a log page from controllers and/or namespaces",
144 		NULL,
145 		do_get_logpage, usage_get_logpage, B_TRUE
146 	},
147 	{
148 		"get-features",
149 		"get features from controllers and/or namespaces",
150 		NULL,
151 		do_get_features, usage_get_features, B_TRUE
152 	},
153 	{
154 		"format",
155 		"format namespace(s) of a controller",
156 		NULL,
157 		do_format, usage_format, B_FALSE
158 	},
159 	{
160 		"secure-erase",
161 		"secure erase namespace(s) of a controller",
162 		"  -c  Do a cryptographic erase.",
163 		do_secure_erase, usage_secure_erase, B_FALSE
164 	},
165 	{
166 		"detach",
167 		"detach blkdev(7d) from namespace(s) of a controller",
168 		NULL,
169 		do_attach_detach, usage_attach_detach, B_FALSE
170 	},
171 	{
172 		"attach",
173 		"attach blkdev(7d) to namespace(s) of a controller",
174 		NULL,
175 		do_attach_detach, usage_attach_detach, B_FALSE
176 	},
177 	{
178 		NULL, NULL, NULL,
179 		NULL, NULL, B_FALSE
180 	}
181 };
182 
183 static const nvme_feature_t features[] = {
184 	{ "Arbitration", "",
185 	    NVME_FEAT_ARBITRATION, 0, NVMEADM_CTRL,
186 	    do_get_feat_common, nvme_print_feat_arbitration },
187 	{ "Power Management", "",
188 	    NVME_FEAT_POWER_MGMT, 0, NVMEADM_CTRL,
189 	    do_get_feat_common, nvme_print_feat_power_mgmt },
190 	{ "LBA Range Type", "range",
191 	    NVME_FEAT_LBA_RANGE, NVME_LBA_RANGE_BUFSIZE, NVMEADM_NS,
192 	    do_get_feat_common, nvme_print_feat_lba_range },
193 	{ "Temperature Threshold", "",
194 	    NVME_FEAT_TEMPERATURE, 0, NVMEADM_CTRL,
195 	    do_get_feat_common, nvme_print_feat_temperature },
196 	{ "Error Recovery", "",
197 	    NVME_FEAT_ERROR, 0, NVMEADM_CTRL,
198 	    do_get_feat_common, nvme_print_feat_error },
199 	{ "Volatile Write Cache", "cache",
200 	    NVME_FEAT_WRITE_CACHE, 0, NVMEADM_CTRL,
201 	    do_get_feat_common, nvme_print_feat_write_cache },
202 	{ "Number of Queues", "queues",
203 	    NVME_FEAT_NQUEUES, 0, NVMEADM_CTRL,
204 	    do_get_feat_common, nvme_print_feat_nqueues },
205 	{ "Interrupt Coalescing", "coalescing",
206 	    NVME_FEAT_INTR_COAL, 0, NVMEADM_CTRL,
207 	    do_get_feat_common, nvme_print_feat_intr_coal },
208 	{ "Interrupt Vector Configuration", "vector",
209 	    NVME_FEAT_INTR_VECT, 0, NVMEADM_CTRL,
210 	    do_get_feat_intr_vect, nvme_print_feat_intr_vect },
211 	{ "Write Atomicity", "atomicity",
212 	    NVME_FEAT_WRITE_ATOM, 0, NVMEADM_CTRL,
213 	    do_get_feat_common, nvme_print_feat_write_atom },
214 	{ "Asynchronous Event Configuration", "event",
215 	    NVME_FEAT_ASYNC_EVENT, 0, NVMEADM_CTRL,
216 	    do_get_feat_common, nvme_print_feat_async_event },
217 	{ "Autonomous Power State Transition", "",
218 	    NVME_FEAT_AUTO_PST, NVME_AUTO_PST_BUFSIZE, NVMEADM_CTRL,
219 	    do_get_feat_common, nvme_print_feat_auto_pst },
220 	{ "Software Progress Marker", "progress",
221 	    NVME_FEAT_PROGRESS, 0, NVMEADM_CTRL,
222 	    do_get_feat_common, nvme_print_feat_progress },
223 	{ NULL, NULL, 0, 0, B_FALSE, NULL }
224 };
225 
226 
227 int
228 main(int argc, char **argv)
229 {
230 	int c;
231 	extern int optind;
232 	const nvmeadm_cmd_t *cmd;
233 	di_node_t node;
234 	nvme_process_arg_t npa = { 0 };
235 	int help = 0;
236 	char *tmp, *lasts = NULL;
237 
238 	while ((c = getopt(argc, argv, "dhv")) != -1) {
239 		switch (c) {
240 		case 'd':
241 			debug++;
242 			break;
243 		case 'v':
244 			verbose++;
245 			break;
246 		case 'h':
247 			help++;
248 			break;
249 		case '?':
250 			usage(NULL);
251 			exit(-1);
252 		}
253 	}
254 
255 	if (optind == argc) {
256 		usage(NULL);
257 		if (help)
258 			exit(0);
259 		else
260 			exit(-1);
261 	}
262 
263 	/* Look up the specified command in the command table. */
264 	for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++)
265 		if (strcmp(cmd->c_name, argv[optind]) == 0)
266 			break;
267 
268 	if (cmd->c_name == NULL) {
269 		usage(NULL);
270 		exit(-1);
271 	}
272 
273 	if (help) {
274 		usage(cmd);
275 		exit(0);
276 	}
277 
278 	npa.npa_cmd = cmd;
279 
280 	optind++;
281 
282 	/*
283 	 * All commands but "list" require a ctl/ns argument.
284 	 */
285 	if ((optind == argc || (strncmp(argv[optind], "nvme", 4) != 0)) &&
286 	    cmd->c_func != do_list) {
287 		warnx("missing controller/namespace name");
288 		usage(cmd);
289 		exit(-1);
290 	}
291 
292 
293 	/* Store the remaining arguments for use by the command. */
294 	npa.npa_argc = argc - optind - 1;
295 	npa.npa_argv = &argv[optind + 1];
296 
297 	/*
298 	 * Make sure we're not running commands on multiple controllers that
299 	 * aren't allowed to do that.
300 	 */
301 	if (argv[optind] != NULL && strchr(argv[optind], ',') != NULL &&
302 	    cmd->c_multi == B_FALSE) {
303 		warnx("%s not allowed on multiple controllers",
304 		    cmd->c_name);
305 		usage(cmd);
306 		exit(-1);
307 	}
308 
309 	/*
310 	 * Get controller/namespace arguments and run command.
311 	 */
312 	npa.npa_name = strtok_r(argv[optind], ",", &lasts);
313 	do {
314 		if (npa.npa_name != NULL) {
315 			tmp = strchr(npa.npa_name, '/');
316 			if (tmp != NULL) {
317 				*tmp++ = '\0';
318 				npa.npa_nsid = tmp;
319 				npa.npa_isns = B_TRUE;
320 			}
321 		}
322 
323 		if ((node = di_init("/", DINFOSUBTREE | DINFOMINOR)) == NULL)
324 			err(-1, "failed to initialize libdevinfo");
325 		nvme_walk(&npa, node);
326 		di_fini(node);
327 
328 		if (npa.npa_found == 0) {
329 			if (npa.npa_name != NULL) {
330 				warnx("%s%.*s%.*s: no such controller or "
331 				    "namespace", npa.npa_name,
332 				    npa.npa_isns ? -1 : 0, "/",
333 				    npa.npa_isns ? -1 : 0, npa.npa_nsid);
334 			} else {
335 				warnx("no controllers found");
336 			}
337 			exitcode--;
338 		}
339 		npa.npa_found = 0;
340 		npa.npa_name = strtok_r(NULL, ",", &lasts);
341 	} while (npa.npa_name != NULL);
342 
343 	exit(exitcode);
344 }
345 
346 static void
347 usage(const nvmeadm_cmd_t *cmd)
348 {
349 	(void) fprintf(stderr, "usage:\n");
350 	(void) fprintf(stderr, "  %s -h %s\n", getprogname(),
351 	    cmd != NULL ? cmd->c_name : "[<command>]");
352 	(void) fprintf(stderr, "  %s [-dv] ", getprogname());
353 
354 	if (cmd != NULL) {
355 		cmd->c_usage(cmd->c_name);
356 	} else {
357 		(void) fprintf(stderr,
358 		    "<command> <ctl>[/<ns>][,...] [<args>]\n");
359 		(void) fprintf(stderr,
360 		    "\n  Manage NVMe controllers and namespaces.\n");
361 		(void) fprintf(stderr, "\ncommands:\n");
362 
363 		for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++)
364 			(void) fprintf(stderr, "  %-15s - %s\n",
365 			    cmd->c_name, cmd->c_desc);
366 	}
367 	(void) fprintf(stderr, "\nflags:\n"
368 	    "  -h  print usage information\n"
369 	    "  -d  print information useful for debugging %s\n"
370 	    "  -v  print verbose information\n", getprogname());
371 	if (cmd != NULL && cmd->c_flagdesc != NULL)
372 		(void) fprintf(stderr, "%s\n", cmd->c_flagdesc);
373 }
374 
375 static boolean_t
376 nvme_match(nvme_process_arg_t *npa)
377 {
378 	char *name;
379 	char *nsid = NULL;
380 
381 	if (npa->npa_name == NULL)
382 		return (B_TRUE);
383 
384 	if (asprintf(&name, "%s%d", di_driver_name(npa->npa_node),
385 	    di_instance(npa->npa_node)) < 0)
386 		err(-1, "nvme_match()");
387 
388 	if (strcmp(name, npa->npa_name) != 0) {
389 		free(name);
390 		return (B_FALSE);
391 	}
392 
393 	free(name);
394 
395 	if (npa->npa_isns) {
396 		if (npa->npa_nsid == NULL)
397 			return (B_TRUE);
398 
399 		nsid = di_minor_name(npa->npa_minor);
400 
401 		if (nsid == NULL || strcmp(npa->npa_nsid, nsid) != 0)
402 			return (B_FALSE);
403 	}
404 
405 	return (B_TRUE);
406 }
407 
408 char *
409 nvme_dskname(const nvme_process_arg_t *npa)
410 {
411 	char *path = NULL;
412 	di_node_t child;
413 	di_dim_t dim;
414 	char *addr;
415 
416 	dim = di_dim_init();
417 
418 	for (child = di_child_node(npa->npa_node);
419 	    child != DI_NODE_NIL;
420 	    child = di_sibling_node(child)) {
421 		addr = di_bus_addr(child);
422 		if (addr == NULL)
423 			continue;
424 
425 		if (addr[0] == 'w')
426 			addr++;
427 
428 		if (strncasecmp(addr, di_minor_name(npa->npa_minor),
429 		    strchrnul(addr, ',') - addr) != 0)
430 			continue;
431 
432 		path = di_dim_path_dev(dim, di_driver_name(child),
433 		    di_instance(child), "c");
434 
435 		/*
436 		 * Error out if we didn't get a path, or if it's too short for
437 		 * the following operations to be safe.
438 		 */
439 		if (path == NULL || strlen(path) < 2)
440 			goto fail;
441 
442 		/* Chop off 's0' and get everything past the last '/' */
443 		path[strlen(path) - 2] = '\0';
444 		path = strrchr(path, '/');
445 		if (path == NULL)
446 			goto fail;
447 		path++;
448 
449 		break;
450 	}
451 
452 	di_dim_fini(dim);
453 
454 	return (path);
455 
456 fail:
457 	err(-1, "nvme_dskname");
458 }
459 
460 static int
461 nvme_process(di_node_t node, di_minor_t minor, void *arg)
462 {
463 	nvme_process_arg_t *npa = arg;
464 	int fd;
465 
466 	npa->npa_node = node;
467 	npa->npa_minor = minor;
468 
469 	if (!nvme_match(npa))
470 		return (DI_WALK_CONTINUE);
471 
472 	if ((fd = nvme_open(minor)) < 0)
473 		return (DI_WALK_CONTINUE);
474 
475 	npa->npa_found++;
476 
477 	npa->npa_path = di_devfs_path(node);
478 	if (npa->npa_path == NULL)
479 		goto out;
480 
481 	npa->npa_version = nvme_version(fd);
482 	if (npa->npa_version == NULL)
483 		goto out;
484 
485 	npa->npa_idctl = nvme_identify_ctrl(fd);
486 	if (npa->npa_idctl == NULL)
487 		goto out;
488 
489 	npa->npa_idns = nvme_identify_nsid(fd);
490 	if (npa->npa_idns == NULL)
491 		goto out;
492 
493 	if (npa->npa_isns)
494 		npa->npa_dsk = nvme_dskname(npa);
495 
496 	exitcode += npa->npa_cmd->c_func(fd, npa);
497 
498 out:
499 	di_devfs_path_free(npa->npa_path);
500 	free(npa->npa_dsk);
501 	free(npa->npa_version);
502 	free(npa->npa_idctl);
503 	free(npa->npa_idns);
504 
505 	npa->npa_version = NULL;
506 	npa->npa_idctl = NULL;
507 	npa->npa_idns = NULL;
508 
509 	nvme_close(fd);
510 
511 	return (DI_WALK_CONTINUE);
512 }
513 
514 static void
515 nvme_walk(nvme_process_arg_t *npa, di_node_t node)
516 {
517 	char *minor_nodetype = DDI_NT_NVME_NEXUS;
518 
519 	if (npa->npa_isns)
520 		minor_nodetype = DDI_NT_NVME_ATTACHMENT_POINT;
521 
522 	(void) di_walk_minor(node, minor_nodetype, 0, npa, nvme_process);
523 }
524 
525 static void
526 usage_list(const char *c_name)
527 {
528 	(void) fprintf(stderr, "%s [<ctl>[/<ns>][,...]\n\n"
529 	    "  List NVMe controllers and their namespaces. If no "
530 	    "controllers and/or name-\n  spaces are specified, all "
531 	    "controllers and namespaces in the system will be\n  "
532 	    "listed.\n", c_name);
533 }
534 
535 static int
536 do_list_nsid(int fd, const nvme_process_arg_t *npa)
537 {
538 	_NOTE(ARGUNUSED(fd));
539 	const uint_t format = npa->npa_idns->id_flbas.lba_format;
540 	const uint_t bshift = npa->npa_idns->id_lbaf[format].lbaf_lbads;
541 
542 	/*
543 	 * Some devices have extra namespaces with illegal block sizes and
544 	 * zero blocks. Don't list them when verbose operation isn't requested.
545 	 */
546 	if ((bshift < 9 || npa->npa_idns->id_nsize == 0) && verbose == 0)
547 		return (0);
548 
549 	(void) printf("  %s/%s (%s): ", npa->npa_name,
550 	    di_minor_name(npa->npa_minor),
551 	    npa->npa_dsk != NULL ? npa->npa_dsk : "unattached");
552 	nvme_print_nsid_summary(npa->npa_idns);
553 
554 	return (0);
555 }
556 
557 static int
558 do_list(int fd, const nvme_process_arg_t *npa)
559 {
560 	_NOTE(ARGUNUSED(fd));
561 
562 	nvme_process_arg_t ns_npa = { 0 };
563 	nvmeadm_cmd_t cmd = { 0 };
564 	char *name;
565 
566 	if (asprintf(&name, "%s%d", di_driver_name(npa->npa_node),
567 	    di_instance(npa->npa_node)) < 0)
568 		err(-1, "do_list()");
569 
570 	(void) printf("%s: ", name);
571 	nvme_print_ctrl_summary(npa->npa_idctl, npa->npa_version);
572 
573 	ns_npa.npa_name = name;
574 	ns_npa.npa_isns = B_TRUE;
575 	ns_npa.npa_nsid = npa->npa_nsid;
576 	cmd = *(npa->npa_cmd);
577 	cmd.c_func = do_list_nsid;
578 	ns_npa.npa_cmd = &cmd;
579 
580 	nvme_walk(&ns_npa, npa->npa_node);
581 
582 	free(name);
583 
584 	return (exitcode);
585 }
586 
587 static void
588 usage_identify(const char *c_name)
589 {
590 	(void) fprintf(stderr, "%s <ctl>[/<ns>][,...]\n\n"
591 	    "  Print detailed information about the specified NVMe "
592 	    "controllers and/or name-\n  spaces.\n", c_name);
593 }
594 
595 static int
596 do_identify(int fd, const nvme_process_arg_t *npa)
597 {
598 	if (!npa->npa_isns) {
599 		nvme_capabilities_t *cap;
600 
601 		cap = nvme_capabilities(fd);
602 		if (cap == NULL)
603 			return (-1);
604 
605 		(void) printf("%s: ", npa->npa_name);
606 		nvme_print_identify_ctrl(npa->npa_idctl, cap,
607 		    npa->npa_version);
608 
609 		free(cap);
610 	} else {
611 		(void) printf("%s/%s: ", npa->npa_name,
612 		    di_minor_name(npa->npa_minor));
613 		nvme_print_identify_nsid(npa->npa_idns,
614 		    npa->npa_version);
615 	}
616 
617 	return (0);
618 }
619 
620 static void
621 usage_get_logpage(const char *c_name)
622 {
623 	(void) fprintf(stderr, "%s <ctl>[/<ns>][,...] <logpage>\n\n"
624 	    "  Print the specified log page of the specified NVMe "
625 	    "controllers and/or name-\n  spaces. Supported log pages "
626 	    "are error, health, and firmware.\n", c_name);
627 }
628 
629 static int
630 do_get_logpage_error(int fd, const nvme_process_arg_t *npa)
631 {
632 	int nlog = npa->npa_idctl->id_elpe + 1;
633 	size_t bufsize = sizeof (nvme_error_log_entry_t) * nlog;
634 	nvme_error_log_entry_t *elog;
635 
636 	if (npa->npa_isns)
637 		errx(-1, "Error Log not available on a per-namespace basis");
638 
639 	elog = nvme_get_logpage(fd, NVME_LOGPAGE_ERROR, &bufsize);
640 
641 	if (elog == NULL)
642 		return (-1);
643 
644 	nlog = bufsize / sizeof (nvme_error_log_entry_t);
645 
646 	(void) printf("%s: ", npa->npa_name);
647 	nvme_print_error_log(nlog, elog);
648 
649 	free(elog);
650 
651 	return (0);
652 }
653 
654 static int
655 do_get_logpage_health(int fd, const nvme_process_arg_t *npa)
656 {
657 	size_t bufsize = sizeof (nvme_health_log_t);
658 	nvme_health_log_t *hlog;
659 
660 	if (npa->npa_isns) {
661 		if (npa->npa_idctl->id_lpa.lp_smart == 0)
662 			errx(-1, "SMART/Health information not available "
663 			    "on a per-namespace basis on this controller");
664 	}
665 
666 	hlog = nvme_get_logpage(fd, NVME_LOGPAGE_HEALTH, &bufsize);
667 
668 	if (hlog == NULL)
669 		return (-1);
670 
671 	(void) printf("%s: ", npa->npa_name);
672 	nvme_print_health_log(hlog, npa->npa_idctl);
673 
674 	free(hlog);
675 
676 	return (0);
677 }
678 
679 static int
680 do_get_logpage_fwslot(int fd, const nvme_process_arg_t *npa)
681 {
682 	size_t bufsize = sizeof (nvme_fwslot_log_t);
683 	nvme_fwslot_log_t *fwlog;
684 
685 	if (npa->npa_isns)
686 		errx(-1, "Firmware Slot information not available on a "
687 		    "per-namespace basis");
688 
689 	fwlog = nvme_get_logpage(fd, NVME_LOGPAGE_FWSLOT, &bufsize);
690 
691 	if (fwlog == NULL)
692 		return (-1);
693 
694 	(void) printf("%s: ", npa->npa_name);
695 	nvme_print_fwslot_log(fwlog);
696 
697 	free(fwlog);
698 
699 	return (0);
700 }
701 
702 static int
703 do_get_logpage(int fd, const nvme_process_arg_t *npa)
704 {
705 	int ret = 0;
706 	int (*func)(int, const nvme_process_arg_t *);
707 
708 	if (npa->npa_argc < 1) {
709 		warnx("missing logpage name");
710 		usage(npa->npa_cmd);
711 		exit(-1);
712 	}
713 
714 	if (strcmp(npa->npa_argv[0], "error") == 0)
715 		func = do_get_logpage_error;
716 	else if (strcmp(npa->npa_argv[0], "health") == 0)
717 		func = do_get_logpage_health;
718 	else if (strcmp(npa->npa_argv[0], "firmware") == 0)
719 		func = do_get_logpage_fwslot;
720 	else
721 		errx(-1, "invalid log page: %s", npa->npa_argv[0]);
722 
723 	ret = func(fd, npa);
724 	return (ret);
725 }
726 
727 static void
728 usage_get_features(const char *c_name)
729 {
730 	const nvme_feature_t *feat;
731 
732 	(void) fprintf(stderr, "%s <ctl>[/<ns>][,...] [<feature>[,...]]\n\n"
733 	    "  Print the specified features of the specified NVMe controllers "
734 	    "and/or\n  namespaces. Supported features are:\n\n", c_name);
735 	(void) fprintf(stderr, "    %-35s %-14s %s\n",
736 	    "FEATURE NAME", "SHORT NAME", "CONTROLLER/NAMESPACE");
737 	for (feat = &features[0]; feat->f_feature != 0; feat++) {
738 		char *type;
739 
740 		if ((feat->f_getflags & NVMEADM_BOTH) == NVMEADM_BOTH)
741 			type = "both";
742 		else if ((feat->f_getflags & NVMEADM_CTRL) != 0)
743 			type = "controller only";
744 		else
745 			type = "namespace only";
746 
747 		(void) fprintf(stderr, "    %-35s %-14s %s\n",
748 		    feat->f_name, feat->f_short, type);
749 	}
750 
751 }
752 
753 static int
754 do_get_feat_common(int fd, const nvme_feature_t *feat,
755     nvme_identify_ctrl_t *idctl)
756 {
757 	void *buf = NULL;
758 	size_t bufsize = feat->f_bufsize;
759 	uint64_t res;
760 
761 	if (nvme_get_feature(fd, feat->f_feature, 0, &res, &bufsize, &buf)
762 	    == B_FALSE)
763 		return (EINVAL);
764 
765 	nvme_print(2, feat->f_name, -1, NULL);
766 	feat->f_print(res, buf, bufsize, idctl);
767 	free(buf);
768 
769 	return (0);
770 }
771 
772 static int
773 do_get_feat_intr_vect(int fd, const nvme_feature_t *feat,
774     nvme_identify_ctrl_t *idctl)
775 {
776 	uint64_t res;
777 	uint64_t arg;
778 	int intr_cnt;
779 
780 	intr_cnt = nvme_intr_cnt(fd);
781 
782 	if (intr_cnt == -1)
783 		return (EINVAL);
784 
785 	nvme_print(2, feat->f_name, -1, NULL);
786 
787 	for (arg = 0; arg < intr_cnt; arg++) {
788 		if (nvme_get_feature(fd, feat->f_feature, arg, &res, NULL, NULL)
789 		    == B_FALSE)
790 			return (EINVAL);
791 
792 		feat->f_print(res, NULL, 0, idctl);
793 	}
794 
795 	return (0);
796 }
797 
798 static int
799 do_get_features(int fd, const nvme_process_arg_t *npa)
800 {
801 	const nvme_feature_t *feat;
802 	char *f, *flist, *lasts;
803 	boolean_t header_printed = B_FALSE;
804 
805 	if (npa->npa_argc > 1)
806 		errx(-1, "unexpected arguments");
807 
808 	/*
809 	 * No feature list given, print all supported features.
810 	 */
811 	if (npa->npa_argc == 0) {
812 		(void) printf("%s: Get Features\n", npa->npa_name);
813 		for (feat = &features[0]; feat->f_feature != 0; feat++) {
814 			if ((npa->npa_isns &&
815 			    (feat->f_getflags & NVMEADM_NS) == 0) ||
816 			    (!npa->npa_isns &&
817 			    (feat->f_getflags & NVMEADM_CTRL) == 0))
818 				continue;
819 
820 			(void) feat->f_get(fd, feat, npa->npa_idctl);
821 		}
822 
823 		return (0);
824 	}
825 
826 	/*
827 	 * Process feature list.
828 	 */
829 	flist = strdup(npa->npa_argv[0]);
830 	if (flist == NULL)
831 		err(-1, "do_get_features");
832 
833 	for (f = strtok_r(flist, ",", &lasts);
834 	    f != NULL;
835 	    f = strtok_r(NULL, ",", &lasts)) {
836 		while (isspace(*f))
837 			f++;
838 
839 		for (feat = &features[0]; feat->f_feature != 0; feat++) {
840 			if (strncasecmp(feat->f_name, f, strlen(f)) == 0 ||
841 			    strncasecmp(feat->f_short, f, strlen(f)) == 0)
842 				break;
843 		}
844 
845 		if (feat->f_feature == 0) {
846 			warnx("unknown feature %s", f);
847 			continue;
848 		}
849 
850 		if ((npa->npa_isns &&
851 		    (feat->f_getflags & NVMEADM_NS) == 0) ||
852 		    (!npa->npa_isns &&
853 		    (feat->f_getflags & NVMEADM_CTRL) == 0)) {
854 			warnx("feature %s %s supported for namespaces",
855 			    feat->f_name, (feat->f_getflags & NVMEADM_NS) != 0 ?
856 			    "only" : "not");
857 			continue;
858 		}
859 
860 		if (!header_printed) {
861 			(void) printf("%s: Get Features\n", npa->npa_name);
862 			header_printed = B_TRUE;
863 		}
864 
865 		if (feat->f_get(fd, feat, npa->npa_idctl) != 0) {
866 			warnx("unsupported feature: %s", feat->f_name);
867 			continue;
868 		}
869 	}
870 
871 	free(flist);
872 	return (0);
873 }
874 
875 static int
876 do_format_common(int fd, const nvme_process_arg_t *npa, unsigned long lbaf,
877     unsigned long ses)
878 {
879 	nvme_process_arg_t ns_npa = { 0 };
880 	nvmeadm_cmd_t cmd = { 0 };
881 
882 	cmd = *(npa->npa_cmd);
883 	cmd.c_func = do_attach_detach;
884 	cmd.c_name = "detach";
885 	ns_npa = *npa;
886 	ns_npa.npa_cmd = &cmd;
887 
888 	if (do_attach_detach(fd, &ns_npa) != 0)
889 		return (exitcode);
890 	if (nvme_format_nvm(fd, lbaf, ses) == B_FALSE) {
891 		warn("%s failed", npa->npa_cmd->c_name);
892 		exitcode += -1;
893 	}
894 	cmd.c_name = "attach";
895 	exitcode += do_attach_detach(fd, &ns_npa);
896 
897 	return (exitcode);
898 }
899 
900 static void
901 usage_format(const char *c_name)
902 {
903 	(void) fprintf(stderr, "%s <ctl>[/<ns>] [<lba-format>]\n\n"
904 	    "  Format one or all namespaces of the specified NVMe "
905 	    "controller. Supported LBA\n  formats can be queried with "
906 	    "the \"%s identify\" command on the namespace\n  to be "
907 	    "formatted.\n", c_name, getprogname());
908 }
909 
910 static int
911 do_format(int fd, const nvme_process_arg_t *npa)
912 {
913 	unsigned long lbaf;
914 
915 	if (npa->npa_idctl->id_oacs.oa_format == 0)
916 		errx(-1, "%s not supported", npa->npa_cmd->c_name);
917 
918 	if (npa->npa_isns && npa->npa_idctl->id_fna.fn_format != 0)
919 		errx(-1, "%s not supported on individual namespace",
920 		    npa->npa_cmd->c_name);
921 
922 
923 	if (npa->npa_argc > 0) {
924 		errno = 0;
925 		lbaf = strtoul(npa->npa_argv[0], NULL, 10);
926 
927 		if (errno != 0 || lbaf > NVME_FRMT_MAX_LBAF)
928 			errx(-1, "invalid LBA format %d", lbaf + 1);
929 
930 		if (npa->npa_idns->id_lbaf[lbaf].lbaf_ms != 0)
931 			errx(-1, "LBA formats with metadata not supported");
932 	} else {
933 		lbaf = npa->npa_idns->id_flbas.lba_format;
934 	}
935 
936 	return (do_format_common(fd, npa, lbaf, 0));
937 }
938 
939 static void
940 usage_secure_erase(const char *c_name)
941 {
942 	(void) fprintf(stderr, "%s <ctl>[/<ns>] [-c]\n\n"
943 	    "  Secure-Erase one or all namespaces of the specified "
944 	    "NVMe controller.\n", c_name);
945 }
946 
947 static int
948 do_secure_erase(int fd, const nvme_process_arg_t *npa)
949 {
950 	unsigned long lbaf;
951 	uint8_t ses = NVME_FRMT_SES_USER;
952 
953 	if (npa->npa_idctl->id_oacs.oa_format == 0)
954 		errx(-1, "%s not supported", npa->npa_cmd->c_name);
955 
956 	if (npa->npa_isns && npa->npa_idctl->id_fna.fn_sec_erase != 0)
957 		errx(-1, "%s not supported on individual namespace",
958 		    npa->npa_cmd->c_name);
959 
960 	if (npa->npa_argc > 0) {
961 		if (strcmp(npa->npa_argv[0], "-c") == 0)
962 			ses = NVME_FRMT_SES_CRYPTO;
963 		else
964 			usage(npa->npa_cmd);
965 	}
966 
967 	if (ses == NVME_FRMT_SES_CRYPTO &&
968 	    npa->npa_idctl->id_fna.fn_crypt_erase == 0)
969 		errx(-1, "cryptographic %s not supported",
970 		    npa->npa_cmd->c_name);
971 
972 	lbaf = npa->npa_idns->id_flbas.lba_format;
973 
974 	return (do_format_common(fd, npa, lbaf, ses));
975 }
976 
977 static void
978 usage_attach_detach(const char *c_name)
979 {
980 	(void) fprintf(stderr, "%s <ctl>[/<ns>]\n\n"
981 	    "  %c%s blkdev(7d) %s one or all namespaces of the "
982 	    "specified NVMe controller.\n",
983 	    c_name, toupper(c_name[0]), &c_name[1],
984 	    c_name[0] == 'd' ? "from" : "to");
985 }
986 
987 static int
988 do_attach_detach(int fd, const nvme_process_arg_t *npa)
989 {
990 	char *c_name = npa->npa_cmd->c_name;
991 
992 	if (!npa->npa_isns) {
993 		nvme_process_arg_t ns_npa = { 0 };
994 
995 		ns_npa.npa_name = npa->npa_name;
996 		ns_npa.npa_isns = B_TRUE;
997 		ns_npa.npa_cmd = npa->npa_cmd;
998 
999 		nvme_walk(&ns_npa, npa->npa_node);
1000 
1001 		return (exitcode);
1002 	} else {
1003 		if ((c_name[0] == 'd' ? nvme_detach : nvme_attach)(fd)
1004 		    == B_FALSE) {
1005 			warn("%s failed", c_name);
1006 			return (-1);
1007 		}
1008 	}
1009 
1010 	return (0);
1011 }
1012