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