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