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