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