xref: /illumos-gate/usr/src/cmd/nvmeadm/nvmeadm.c (revision 02ac56e010f18fc0c5aafe47377586d8ba8c897c)
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 2024 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  *		list-logpages [logpage name],...
25  *		get-logpage <logpage name>
26  *		get-features <feature>[,...]
27  *		format ...
28  *		secure-erase ...
29  *		detach ...
30  *		attach ...
31  *		list-firmware ...
32  *		load-firmware ...
33  *		commit-firmware ...
34  *		activate-firmware ...
35  */
36 
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <stddef.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 #include <sys/sysmacros.h>
48 
49 #include <sys/nvme.h>
50 
51 #include "nvmeadm.h"
52 
53 /*
54  * Assertions to make sure that we've properly captured various aspects of the
55  * packed structures and haven't broken them during updates.
56  */
57 CTASSERT(sizeof (nvme_identify_ctrl_t) == NVME_IDENTIFY_BUFSIZE);
58 CTASSERT(offsetof(nvme_identify_ctrl_t, id_oacs) == 256);
59 CTASSERT(offsetof(nvme_identify_ctrl_t, id_sqes) == 512);
60 CTASSERT(offsetof(nvme_identify_ctrl_t, id_oncs) == 520);
61 CTASSERT(offsetof(nvme_identify_ctrl_t, id_subnqn) == 768);
62 CTASSERT(offsetof(nvme_identify_ctrl_t, id_nvmof) == 1792);
63 CTASSERT(offsetof(nvme_identify_ctrl_t, id_psd) == 2048);
64 CTASSERT(offsetof(nvme_identify_ctrl_t, id_vs) == 3072);
65 
66 CTASSERT(sizeof (nvme_identify_nsid_t) == NVME_IDENTIFY_BUFSIZE);
67 CTASSERT(offsetof(nvme_identify_nsid_t, id_fpi) == 32);
68 CTASSERT(offsetof(nvme_identify_nsid_t, id_anagrpid) == 92);
69 CTASSERT(offsetof(nvme_identify_nsid_t, id_nguid) == 104);
70 CTASSERT(offsetof(nvme_identify_nsid_t, id_lbaf) == 128);
71 CTASSERT(offsetof(nvme_identify_nsid_t, id_vs) == 384);
72 
73 CTASSERT(sizeof (nvme_identify_nsid_list_t) == NVME_IDENTIFY_BUFSIZE);
74 CTASSERT(sizeof (nvme_identify_ctrl_list_t) == NVME_IDENTIFY_BUFSIZE);
75 
76 CTASSERT(sizeof (nvme_identify_primary_caps_t) == NVME_IDENTIFY_BUFSIZE);
77 CTASSERT(offsetof(nvme_identify_primary_caps_t, nipc_vqfrt) == 32);
78 CTASSERT(offsetof(nvme_identify_primary_caps_t, nipc_vifrt) == 64);
79 
80 CTASSERT(sizeof (nvme_nschange_list_t) == 4096);
81 
82 #define	NVMEADM_F_CTRL	1
83 #define	NVMEADM_F_NS	2
84 #define	NVMEADM_F_BOTH	(NVMEADM_F_CTRL | NVMEADM_F_NS)
85 
86 static void usage(const nvmeadm_cmd_t *);
87 static bool nvmeadm_ctrl_disc_cb(nvme_t *, const nvme_ctrl_disc_t *, void *);
88 
89 static int do_list(const nvme_process_arg_t *);
90 static int do_identify(const nvme_process_arg_t *);
91 static int do_identify_ctrl(const nvme_process_arg_t *);
92 static int do_identify_ns(const nvme_process_arg_t *);
93 static int do_list_logs(const nvme_process_arg_t *);
94 static int do_get_logpage_fwslot(const nvme_process_arg_t *);
95 static int do_get_logpage(const nvme_process_arg_t *);
96 static int do_list_features(const nvme_process_arg_t *);
97 static boolean_t do_get_feat_intr_vect(const nvme_process_arg_t *,
98     const nvme_feat_disc_t *, const nvmeadm_feature_t *);
99 static boolean_t do_get_feat_temp_thresh(const nvme_process_arg_t *,
100     const nvme_feat_disc_t *, const nvmeadm_feature_t *);
101 static int do_get_features(const nvme_process_arg_t *);
102 static int do_format(const nvme_process_arg_t *);
103 static int do_secure_erase(const nvme_process_arg_t *);
104 static int do_attach(const nvme_process_arg_t *);
105 static int do_detach(const nvme_process_arg_t *);
106 static int do_firmware_load(const nvme_process_arg_t *);
107 static int do_firmware_commit(const nvme_process_arg_t *);
108 static int do_firmware_activate(const nvme_process_arg_t *);
109 
110 static void optparse_list(nvme_process_arg_t *);
111 static void optparse_identify(nvme_process_arg_t *);
112 static void optparse_identify_ctrl(nvme_process_arg_t *);
113 static void optparse_identify_ns(nvme_process_arg_t *);
114 static void optparse_list_logs(nvme_process_arg_t *);
115 static void optparse_get_logpage(nvme_process_arg_t *);
116 static void optparse_list_features(nvme_process_arg_t *);
117 static void optparse_secure_erase(nvme_process_arg_t *);
118 
119 static void usage_list(const char *);
120 static void usage_identify(const char *);
121 static void usage_identify_ctrl(const char *);
122 static void usage_identify_ns(const char *);
123 static void usage_list_logs(const char *);
124 static void usage_get_logpage(const char *);
125 static void usage_list_features(const char *);
126 static void usage_get_features(const char *);
127 static void usage_format(const char *);
128 static void usage_secure_erase(const char *);
129 static void usage_attach_detach(const char *);
130 static void usage_firmware_list(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 
138 /*
139  * nvmeadm Secure-erase specific options
140  */
141 #define	NVMEADM_O_SE_CRYPTO	0x00000004
142 
143 /*
144  * nvmeadm identify specific options
145  */
146 #define	NVMEADM_O_ID_NSID_LIST	0x00000008
147 #define	NVMEADM_O_ID_COMMON_NS	0x00000010
148 #define	NVMEADM_O_ID_CTRL_LIST	0x00000020
149 #define	NVMEADM_O_ID_DESC_LIST	0x00000040
150 #define	NVMEADM_O_ID_ALLOC_NS	0x00000080
151 
152 /*
153  * nvmeadm List specific options
154  */
155 #define	NVMEADM_O_LS_CTRL	0x00000100
156 
157 static int exitcode;
158 
159 /*
160  * Nvmeadm subcommand definitons.
161  *
162  * When adding a new subcommand, please check that the commands still
163  * line up in the usage() message, and adjust the format string in
164  * usage() below if necessary.
165  */
166 static const nvmeadm_cmd_t nvmeadm_cmds[] = {
167 	{
168 		"list",
169 		"list controllers and namespaces",
170 		"  -c\t\tlist only controllers\n"
171 		"  -p\t\tprint parsable output\n"
172 		"  -o field\tselect a field for parsable output\n",
173 		"  model\t\tthe model name of the device\n"
174 		"  serial\tthe serial number of the device\n"
175 		"  fwrev\t\tthe device's current firmware revision\n"
176 		"  version\tthe device's NVMe specification version\n"
177 		"  capacity\tthe capacity of the device in bytes\n"
178 		"  instance\tthe device driver instance (e.g. nvme3)\n"
179 		"  unallocated\tthe amount of unallocated NVM in bytes",
180 		do_list, usage_list, optparse_list,
181 		NVMEADM_C_MULTI
182 	},
183 	{
184 		"identify",
185 		"identify controllers and/or namespaces",
186 		"  -C\t\tget Common Namespace Identification\n"
187 		"  -a\t\tget only allocated namespace information\n"
188 		"  -c\t\tget controller identifier list\n"
189 		"  -d\t\tget namespace identification descriptors list\n"
190 		"  -n\t\tget namespaces identifier list",
191 		NULL,
192 		do_identify, usage_identify, optparse_identify,
193 		NVMEADM_C_MULTI
194 	},
195 	{
196 		"identify-controller",
197 		"identify controllers",
198 		"  -C\t\tget Common Namespace Identification\n"
199 		"  -a\t\tget only allocated namespace information\n"
200 		"  -c\t\tget controller identifier list\n"
201 		"  -n\t\tget namespaces identifier list",
202 		NULL,
203 		do_identify_ctrl, usage_identify_ctrl, optparse_identify_ctrl,
204 		NVMEADM_C_MULTI
205 	},
206 	{
207 		"identify-namespace",
208 		"identify namespaces",
209 		"  -c\t\tget attached controller identifier list\n"
210 		"  -d\t\tget namespace identification descriptors list",
211 		NULL,
212 		do_identify_ns, usage_identify_ns, optparse_identify_ns,
213 		NVMEADM_C_MULTI
214 	},
215 	{
216 		"list-logpages",
217 		"list a device's supported log pages",
218 		"  -a\t\tprint all log pages, including unimplemented ones\n"
219 		"  -H\t\tomit column headers\n"
220 		"  -o field\tselect a field for parsable output\n"
221 		"  -p\t\tprint parsable output\n"
222 		"  -s scope\tprint logs that match the specified scopes "
223 		"(default is based on\n\t\tdevice)\n",
224 		"  device\tthe name of the controller or namespace\n"
225 		"  name\t\tthe name of the log page\n"
226 		"  desc\t\ta description of the loage page\n"
227 		"  scope\t\tthe valid device scopes for the log page\n"
228 		"  fields\tthe list of fields in the get log request that may "
229 		"be set or required\n\t\t(e.g. lsi, lsp, rae, etc.)\n"
230 		"  csi\t\tthe command set interface the log page belongs to\n"
231 		"  lid\t\tthe log page's numeric ID\n"
232 		"  impl\t\tindicates whether the device implements the log "
233 		"page\n"
234 		"  size\t\tthe size of the log page for fixed size logs\n"
235 		"  minsize\tthe minimum size required to determine the full "
236 		"log page size\n\t\tfor variable-length pages\n"
237 		"  sources\twhere information for this log page came from\n"
238 		"  kind\t\tindicates the kind of log page e.g. standard, "
239 		"vendor-specific,\n\t\tetc.",
240 		do_list_logs, usage_list_logs, optparse_list_logs,
241 		NVMEADM_C_MULTI
242 	},
243 	{
244 		"get-logpage",
245 		"get a log page from controllers and/or namespaces",
246 		"  -O file\toutput log raw binary data to a file\n",
247 		NULL,
248 		do_get_logpage, usage_get_logpage, optparse_get_logpage,
249 		NVMEADM_C_MULTI
250 	},
251 	{
252 		"list-features",
253 		"list a device's supported features",
254 		"  -a\t\tprint all features, including unsupported\n"
255 		"  -H\t\tomit column headers\n"
256 		"  -o field\tselect a field for parsable output\n"
257 		"  -p\t\tprint parsable output",
258 		"  device\tthe name of the controller or namespace\n"
259 		"  short\t\tthe short name of the feature\n"
260 		"  spec\t\tthe longer feature description from the NVMe spec\n"
261 		"  fid\t\tthe numeric feature ID\n"
262 		"  scope\t\tthe valid device scopes for the feature\n"
263 		"  kind\t\tindicates the kind of feature e.g. standard, "
264 		"vendor-specific,\n\t\tetc.\n"
265 		"  csi\t\tindicates the features command set interface\n"
266 		"  flags\t\tindicates additional properties of the feature\n"
267 		"  get-in\tindicates the fields that are required to get the "
268 		"feature\n"
269 		"  set-in\tindicates the fields that are required to set the "
270 		"feature\n"
271 		"  get-out\tindicates the fields the feature outputs\n"
272 		"  set-out\tindicates the fields the feature outputs when "
273 		"setting the feature\n"
274 		"  datalen\tindicates the length of the feature's data "
275 		"payload\n"
276 		"  impl\t\tindicates whether the device implements the "
277 		"feature",
278 		do_list_features, usage_list_features, optparse_list_features,
279 		NVMEADM_C_MULTI
280 	},
281 	{
282 		"get-features",
283 		"get features from controllers and/or namespaces",
284 		NULL,
285 		NULL,
286 		do_get_features, usage_get_features, NULL,
287 		NVMEADM_C_MULTI
288 	},
289 	{
290 		"format",
291 		"format namespace(s) of a controller",
292 		NULL,
293 		NULL,
294 		do_format, usage_format, NULL,
295 		NVMEADM_C_EXCL
296 	},
297 	{
298 		"secure-erase",
299 		"secure erase namespace(s) of a controller",
300 		"  -c  Do a cryptographic erase.",
301 		NULL,
302 		do_secure_erase, usage_secure_erase, optparse_secure_erase,
303 		NVMEADM_C_EXCL
304 	},
305 	{
306 		"detach",
307 		"detach blkdev(4D) from namespace(s) of a controller",
308 		NULL,
309 		NULL,
310 		do_detach, usage_attach_detach, NULL,
311 		NVMEADM_C_EXCL
312 	},
313 	{
314 		"attach",
315 		"attach blkdev(4D) to namespace(s) of a controller",
316 		NULL,
317 		NULL,
318 		do_attach, usage_attach_detach, NULL,
319 		NVMEADM_C_EXCL
320 	},
321 	{
322 		"list-firmware",
323 		"list firmware on a controller",
324 		NULL,
325 		NULL,
326 		do_get_logpage_fwslot, usage_firmware_list, NULL,
327 		0
328 	},
329 	{
330 		"load-firmware",
331 		"load firmware to a controller",
332 		NULL,
333 		NULL,
334 		do_firmware_load, usage_firmware_load, NULL,
335 		NVMEADM_C_EXCL
336 	},
337 	{
338 		"commit-firmware",
339 		"commit downloaded firmware to a slot of a controller",
340 		NULL,
341 		NULL,
342 		do_firmware_commit, usage_firmware_commit, NULL,
343 		NVMEADM_C_EXCL
344 	},
345 	{
346 		"activate-firmware",
347 		"activate a firmware slot of a controller",
348 		NULL,
349 		NULL,
350 		do_firmware_activate, usage_firmware_activate, NULL,
351 		NVMEADM_C_EXCL
352 	},
353 	{
354 		"wdc/e6dump",
355 		"dump WDC e6 diagnostic log",
356 		"  -o output\tspecify output file destination\n",
357 		NULL,
358 		do_wdc_e6dump, usage_wdc_e6dump, optparse_wdc_e6dump,
359 		0
360 	},
361 	{
362 		"wdc/resize",
363 		"change a WDC device's capacity",
364 		"  -g\t\tquery the device's current resized capacity\n"
365 		"  -s size\tset the size of a device to the specified in gb",
366 		NULL,
367 		do_wdc_resize, usage_wdc_resize, optparse_wdc_resize,
368 		/*
369 		 * We do not set NVMEADM_C_EXCL here as that is handled by the
370 		 * vendor unique command logic and operates based on the
371 		 * information we get from vuc discovery.
372 		 */
373 		0
374 	},
375 	{
376 		"wdc/clear-assert",
377 		"clear internal device assertion",
378 		NULL,
379 		NULL,
380 		do_wdc_clear_assert, usage_wdc_clear_assert, NULL
381 	},
382 	{
383 		"wdc/inject-assert",
384 		"inject internal device assertion",
385 		NULL,
386 		NULL,
387 		do_wdc_inject_assert, usage_wdc_inject_assert, NULL
388 	},
389 	{
390 		NULL, NULL, NULL,
391 		NULL, NULL, NULL, 0
392 	}
393 };
394 
395 static const nvmeadm_feature_t features[] = {
396 	{
397 		.f_feature = NVME_FEAT_ARBITRATION,
398 		.f_print = nvme_print_feat_arbitration
399 	}, {
400 		.f_feature = NVME_FEAT_POWER_MGMT,
401 		.f_print = nvme_print_feat_power_mgmt
402 	}, {
403 		.f_feature = NVME_FEAT_LBA_RANGE,
404 		.f_print = nvme_print_feat_lba_range
405 	}, {
406 		.f_feature = NVME_FEAT_TEMPERATURE,
407 		.f_get = do_get_feat_temp_thresh,
408 		.f_print = nvme_print_feat_temperature
409 	}, {
410 		.f_feature = NVME_FEAT_ERROR,
411 		.f_print = nvme_print_feat_error
412 	}, {
413 		.f_feature = NVME_FEAT_WRITE_CACHE,
414 		.f_print = nvme_print_feat_write_cache
415 	}, {
416 		.f_feature = NVME_FEAT_NQUEUES,
417 		.f_print = nvme_print_feat_nqueues
418 	}, {
419 		.f_feature = NVME_FEAT_INTR_COAL,
420 		.f_print = nvme_print_feat_intr_coal
421 	}, {
422 		.f_feature = NVME_FEAT_INTR_VECT,
423 		.f_get = do_get_feat_intr_vect,
424 		.f_print = nvme_print_feat_intr_vect
425 	}, {
426 		.f_feature = NVME_FEAT_WRITE_ATOM,
427 		.f_print = nvme_print_feat_write_atom
428 	}, {
429 		.f_feature = NVME_FEAT_ASYNC_EVENT,
430 		.f_print = nvme_print_feat_async_event
431 	}, {
432 		.f_feature = NVME_FEAT_AUTO_PST,
433 		.f_print = nvme_print_feat_auto_pst
434 	}, {
435 		.f_feature = NVME_FEAT_PROGRESS,
436 		.f_print = nvme_print_feat_progress
437 	}
438 };
439 
440 static void
441 nvmeadm_ctrl_vwarn(const nvme_process_arg_t *npa, const char *fmt, va_list ap)
442 {
443 	nvme_ctrl_t *ctrl = npa->npa_ctrl;
444 
445 	(void) fprintf(stderr, "nvmeadm: ");
446 	(void) vfprintf(stderr, fmt, ap);
447 	(void) fprintf(stderr, ": %s: %s (libnvme: 0x%x, sys: %d)\n",
448 	    nvme_ctrl_errmsg(ctrl), nvme_ctrl_errtostr(npa->npa_ctrl,
449 	    nvme_ctrl_err(ctrl)), nvme_ctrl_err(ctrl), nvme_ctrl_syserr(ctrl));
450 }
451 
452 static void
453 nvmeadm_hdl_vwarn(const nvme_process_arg_t *npa, const char *fmt, va_list ap)
454 {
455 	nvme_t *nvme = npa->npa_nvme;
456 
457 	(void) fprintf(stderr, "nvmeadm: ");
458 	(void) vfprintf(stderr, fmt, ap);
459 	(void) fprintf(stderr, ": %s: %s (libnvme: 0x%x, sys: %d)\n",
460 	    nvme_errmsg(nvme), nvme_errtostr(nvme, nvme_err(nvme)),
461 	    nvme_err(nvme), nvme_syserr(nvme));
462 }
463 
464 static void
465 nvmeadm_ctrl_info_vwarn(const nvme_process_arg_t *npa, const char *fmt,
466     va_list ap)
467 {
468 	nvme_ctrl_info_t *info = npa->npa_ctrl_info;
469 
470 	(void) fprintf(stderr, "nvmeadm: ");
471 	(void) vfprintf(stderr, fmt, ap);
472 	(void) fprintf(stderr, ": %s: %s (libnvme info: 0x%x, sys: %d)\n",
473 	    nvme_ctrl_info_errmsg(info), nvme_ctrl_info_errtostr(info,
474 	    nvme_ctrl_info_err(info)), nvme_ctrl_info_err(info),
475 	    nvme_ctrl_info_syserr(info));
476 }
477 
478 void
479 nvmeadm_warn(const nvme_process_arg_t *npa, const char *fmt, ...)
480 {
481 	va_list ap;
482 
483 	va_start(ap, fmt);
484 	nvmeadm_ctrl_vwarn(npa, fmt, ap);
485 	va_end(ap);
486 }
487 
488 void __NORETURN
489 nvmeadm_fatal(const nvme_process_arg_t *npa, const char *fmt, ...)
490 {
491 	va_list ap;
492 
493 	va_start(ap, fmt);
494 	nvmeadm_ctrl_vwarn(npa, fmt, ap);
495 	va_end(ap);
496 
497 	exit(-1);
498 }
499 
500 void
501 nvmeadm_hdl_warn(const nvme_process_arg_t *npa, const char *fmt, ...)
502 {
503 	va_list ap;
504 
505 	va_start(ap, fmt);
506 	nvmeadm_hdl_vwarn(npa, fmt, ap);
507 	va_end(ap);
508 }
509 
510 void __NORETURN
511 nvmeadm_hdl_fatal(const nvme_process_arg_t *npa, const char *fmt, ...)
512 {
513 	va_list ap;
514 
515 	va_start(ap, fmt);
516 	nvmeadm_hdl_vwarn(npa, fmt, ap);
517 	va_end(ap);
518 
519 	exit(-1);
520 }
521 
522 static void
523 nvmeadm_ctrl_info_warn(const nvme_process_arg_t *npa, const char *fmt, ...)
524 {
525 	va_list ap;
526 
527 	va_start(ap, fmt);
528 	nvmeadm_ctrl_info_vwarn(npa, fmt, ap);
529 	va_end(ap);
530 }
531 
532 static void
533 nvmeadm_ctrl_info_fatal(const nvme_process_arg_t *npa, const char *fmt, ...)
534 {
535 	va_list ap;
536 
537 	va_start(ap, fmt);
538 	nvmeadm_ctrl_info_vwarn(npa, fmt, ap);
539 	va_end(ap);
540 
541 	exit(-1);
542 }
543 
544 boolean_t
545 nvme_version_check(const nvme_process_arg_t *npa, const nvme_version_t *vers)
546 {
547 	return (nvme_vers_atleast(npa->npa_version, vers) ? B_TRUE : B_FALSE);
548 }
549 
550 /*
551  * Because nvmeadm operates on a series of NVMe devices for several commands,
552  * here we need to clean up everything that we allocated for this device so we
553  * can prepare for the next.
554  */
555 static void
556 nvmeadm_cleanup_npa(nvme_process_arg_t *npa)
557 {
558 	npa->npa_idctl = NULL;
559 	npa->npa_version = NULL;
560 
561 	if (npa->npa_excl) {
562 		if (npa->npa_ns != NULL) {
563 			nvme_ns_unlock(npa->npa_ns);
564 		} else if (npa->npa_ctrl != NULL) {
565 			nvme_ctrl_unlock(npa->npa_ctrl);
566 		}
567 	}
568 
569 	if (npa->npa_ns_info != NULL) {
570 		nvme_ns_info_free(npa->npa_ns_info);
571 		npa->npa_ns_info = NULL;
572 	}
573 
574 	if (npa->npa_ctrl_info != NULL) {
575 		nvme_ctrl_info_free(npa->npa_ctrl_info);
576 		npa->npa_ctrl_info = NULL;
577 	}
578 
579 	if (npa->npa_ns != NULL) {
580 		nvme_ns_fini(npa->npa_ns);
581 		npa->npa_ns = NULL;
582 	}
583 
584 	if (npa->npa_ctrl != NULL) {
585 		nvme_ctrl_fini(npa->npa_ctrl);
586 		npa->npa_ctrl = NULL;
587 	}
588 }
589 
590 /*
591  * Determine if a command requires a controller or namespace write lock. If so
592  * we first attempt to grab it non-blocking and then if that fails, we'll warn
593  * that we may be blocking for the lock so that way the user has a chance to do
594  * something and can cancel it.
595  */
596 static void
597 nvmeadm_excl(const nvme_process_arg_t *npa, nvme_lock_level_t level)
598 {
599 	bool ret;
600 	nvme_lock_flags_t flags = NVME_LOCK_F_DONT_BLOCK;
601 
602 	if (npa->npa_ns != NULL) {
603 		ret = nvme_ns_lock(npa->npa_ns, level, flags);
604 	} else {
605 		ret = nvme_ctrl_lock(npa->npa_ctrl, level, flags);
606 	}
607 
608 	if (ret) {
609 		return;
610 	}
611 
612 	if (nvme_ctrl_err(npa->npa_ctrl) != NVME_ERR_LOCK_WOULD_BLOCK) {
613 		nvmeadm_fatal(npa, "failed to acquire lock on %s",
614 		    npa->npa_name);
615 	}
616 
617 	(void) fprintf(stderr, "Waiting on contended %s lock on %s...",
618 	    npa->npa_ns != NULL ? "namespace": "controller", npa->npa_name);
619 	(void) fflush(stderr);
620 
621 	flags &= ~NVME_LOCK_F_DONT_BLOCK;
622 	if (npa->npa_ns != NULL) {
623 		ret = nvme_ns_lock(npa->npa_ns, level, flags);
624 	} else {
625 		ret = nvme_ctrl_lock(npa->npa_ctrl, level, flags);
626 	}
627 
628 	if (!ret) {
629 		nvmeadm_fatal(npa, "failed to acquire lock on %s",
630 		    npa->npa_name);
631 	}
632 
633 	(void) fprintf(stderr, " acquired\n");
634 }
635 
636 /*
637  * Most of nvmeadm was written before the existence of libnvme and always had
638  * things like the identify controller or namespace information sitting around.
639  * As such we try to grab all this in one place for it. Note, regardless if this
640  * succeeds or fails, our callers will still call nvmeadm_cleanup_npa() so we
641  * don't need to clean up the various libnvme objects.
642  */
643 static boolean_t
644 nvmeadm_open_dev(nvme_process_arg_t *npa)
645 {
646 	if (!nvme_ctrl_ns_init(npa->npa_nvme, npa->npa_name, &npa->npa_ctrl,
647 	    &npa->npa_ns)) {
648 		nvmeadm_hdl_warn(npa, "failed to open '%s'", npa->npa_name);
649 		exitcode = -1;
650 		return (B_FALSE);
651 	}
652 
653 	/*
654 	 * Several commands expect to be able to access the controller's
655 	 * information snapshot. Grab that now for it and the namespace if it
656 	 * exists.
657 	 */
658 	if (!nvme_ctrl_info_snap(npa->npa_ctrl, &npa->npa_ctrl_info)) {
659 		nvmeadm_warn(npa, "failed to get controller info for %s",
660 		    npa->npa_ctrl_name);
661 		exitcode = -1;
662 		return (B_FALSE);
663 	}
664 
665 	if (npa->npa_ns != NULL && !nvme_ns_info_snap(npa->npa_ns,
666 	    &npa->npa_ns_info)) {
667 		nvmeadm_warn(npa, "failed to get namespace info for %s",
668 		    npa->npa_name);
669 		exitcode = -1;
670 		return (B_FALSE);
671 	}
672 
673 	/*
674 	 * Snapshot data the rest of the command has fairly ingrained.
675 	 */
676 	npa->npa_version = nvme_ctrl_info_version(npa->npa_ctrl_info);
677 	npa->npa_idctl = nvme_ctrl_info_identify(npa->npa_ctrl_info);
678 
679 	/*
680 	 * If this command has requested exclusive access, proceed to grab that
681 	 * before we continue.
682 	 */
683 	if (npa->npa_excl) {
684 		nvmeadm_excl(npa, NVME_LOCK_L_WRITE);
685 	}
686 
687 	return (B_TRUE);
688 }
689 
690 static bool
691 nvmeadm_ctrl_disc_cb(nvme_t *nvme, const nvme_ctrl_disc_t *disc, void *arg)
692 {
693 	nvme_process_arg_t *npa = arg;
694 	di_node_t di = nvme_ctrl_disc_devi(disc);
695 	char name[128];
696 
697 	(void) snprintf(name, sizeof (name), "%s%d", di_driver_name(di),
698 	    di_instance(di));
699 	npa->npa_name = name;
700 	npa->npa_ctrl_name = name;
701 
702 	if (nvmeadm_open_dev(npa)) {
703 		if (npa->npa_cmd->c_func(npa) != 0) {
704 			exitcode = -1;
705 		}
706 	}
707 
708 	nvmeadm_cleanup_npa(npa);
709 	return (true);
710 }
711 
712 int
713 main(int argc, char **argv)
714 {
715 	int c;
716 	const nvmeadm_cmd_t *cmd;
717 	nvme_process_arg_t npa = { 0 };
718 	int help = 0;
719 	char *ctrl = NULL;
720 
721 	while ((c = getopt(argc, argv, "dhv")) != -1) {
722 		switch (c) {
723 		case 'd':
724 			debug++;
725 			break;
726 
727 		case 'v':
728 			verbose++;
729 			break;
730 
731 		case 'h':
732 			help++;
733 			break;
734 
735 		case '?':
736 			usage(NULL);
737 			exit(-1);
738 		}
739 	}
740 
741 	if (optind == argc) {
742 		usage(NULL);
743 		if (help)
744 			exit(0);
745 		else
746 			exit(-1);
747 	}
748 
749 	/* Look up the specified command in the command table. */
750 	for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++)
751 		if (strcmp(cmd->c_name, argv[optind]) == 0)
752 			break;
753 
754 	if (cmd->c_name == NULL) {
755 		usage(NULL);
756 		exit(-1);
757 	}
758 
759 	if (help) {
760 		usage(cmd);
761 		exit(0);
762 	}
763 
764 	npa.npa_nvme = nvme_init();
765 	if (npa.npa_nvme == NULL) {
766 		err(-1, "failed to initialize libnvme");
767 	}
768 	npa.npa_cmd = cmd;
769 	npa.npa_excl = ((cmd->c_flags & NVMEADM_C_EXCL) != 0);
770 
771 	optind++;
772 
773 	/*
774 	 * Store the remaining arguments for use by the command. Give the
775 	 * command a chance to process the options across the board before going
776 	 * into each controller.
777 	 */
778 	npa.npa_argc = argc - optind;
779 	npa.npa_argv = &argv[optind];
780 
781 	if (cmd->c_optparse != NULL) {
782 		optind = 0;
783 		cmd->c_optparse(&npa);
784 		npa.npa_argc -= optind;
785 		npa.npa_argv += optind;
786 	}
787 
788 	/*
789 	 * All commands but "list" require a ctl/ns argument. However, this
790 	 * should not be passed through to the command in its subsequent
791 	 * arguments.
792 	 */
793 	if (npa.npa_argc == 0 && cmd->c_func != do_list) {
794 		warnx("missing controller/namespace name");
795 		usage(cmd);
796 		exit(-1);
797 	}
798 
799 	if (npa.npa_argc > 0) {
800 		ctrl = npa.npa_argv[0];
801 		npa.npa_argv++;
802 		npa.npa_argc--;
803 	} else {
804 		if (!nvme_ctrl_discover(npa.npa_nvme, nvmeadm_ctrl_disc_cb,
805 		    &npa)) {
806 			nvmeadm_hdl_fatal(&npa, "failed to walk controllers");
807 		}
808 		exit(exitcode);
809 	}
810 
811 	/*
812 	 * Make sure we're not running commands on multiple controllers that
813 	 * aren't allowed to do that.
814 	 */
815 	if (ctrl != NULL && strchr(ctrl, ',') != NULL &&
816 	    (cmd->c_flags & NVMEADM_C_MULTI) == 0) {
817 		warnx("%s not allowed on multiple controllers",
818 		    cmd->c_name);
819 		usage(cmd);
820 		exit(-1);
821 	}
822 
823 	/*
824 	 * Get controller/namespace arguments and run command.
825 	 */
826 	while ((npa.npa_name = strsep(&ctrl, ",")) != NULL) {
827 		char *ctrl_name, *slash;
828 
829 		/*
830 		 * We may be given just a controller as an argument or a
831 		 * controller and a namespace as an argument. Parts of the
832 		 * commands want to know what controller they're referring to
833 		 * even if the overall argument was for a namespace. So we
834 		 * always dup the argument and try to make the controller out of
835 		 * it.
836 		 */
837 		ctrl_name = strdup(npa.npa_name);
838 		if (ctrl_name == NULL) {
839 			err(-1, "failed to duplicate NVMe controller/namespace "
840 			    "name");
841 		}
842 		if ((slash = strchr(ctrl_name, '/')) != NULL)
843 			*slash = '\0';
844 		npa.npa_ctrl_name = ctrl_name;
845 
846 		if (nvmeadm_open_dev(&npa)) {
847 			if (npa.npa_cmd->c_func(&npa) != 0) {
848 				exitcode = -1;
849 			}
850 		}
851 
852 		nvmeadm_cleanup_npa(&npa);
853 		free(ctrl_name);
854 	}
855 
856 	exit(exitcode);
857 }
858 
859 static void
860 nvme_oferr(const char *fmt, ...)
861 {
862 	va_list ap;
863 
864 	va_start(ap, fmt);
865 	verrx(-1, fmt, ap);
866 }
867 
868 static void
869 usage(const nvmeadm_cmd_t *cmd)
870 {
871 	const char *progname = getprogname();
872 
873 	(void) fprintf(stderr, "usage:\n");
874 	(void) fprintf(stderr, "  %s -h %s\n", progname,
875 	    cmd != NULL ? cmd->c_name : "[<command>]");
876 	(void) fprintf(stderr, "  %s [-dv] ", progname);
877 
878 	if (cmd != NULL) {
879 		cmd->c_usage(cmd->c_name);
880 	} else {
881 		(void) fprintf(stderr,
882 		    "<command> <ctl>[/<ns>][,...] [<args>]\n");
883 		(void) fprintf(stderr,
884 		    "\n  Manage NVMe controllers and namespaces.\n");
885 		(void) fprintf(stderr, "\ncommands:\n");
886 
887 		for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++) {
888 			/*
889 			 * The longest nvmeadm subcommand is 19 characters long.
890 			 * The format string needs to be updated every time a
891 			 * longer subcommand is added.
892 			 */
893 			(void) fprintf(stderr, "  %-19s - %s\n",
894 			    cmd->c_name, cmd->c_desc);
895 		}
896 	}
897 	(void) fprintf(stderr, "\n%s flags:\n"
898 	    "  -h\t\tprint usage information\n"
899 	    "  -d\t\tprint information useful for debugging %s\n"
900 	    "  -v\t\tprint verbose information\n",
901 	    progname, progname);
902 
903 	if (cmd != NULL && cmd->c_flagdesc != NULL) {
904 		(void) fprintf(stderr, "\n%s %s flags:\n",
905 		    progname, cmd->c_name);
906 		(void) fprintf(stderr, "%s\n", cmd->c_flagdesc);
907 	}
908 
909 	if (cmd != NULL && cmd->c_fielddesc != NULL) {
910 		(void) fprintf(stderr, "\n%s %s valid fields:\n",
911 		    progname, cmd->c_name);
912 		(void) fprintf(stderr, "%s\n", cmd->c_fielddesc);
913 	}
914 }
915 
916 char *
917 nvme_dskname(di_node_t ctrl, const char *bd_addr)
918 {
919 	di_dim_t dim;
920 	char *diskname = NULL;
921 
922 	dim = di_dim_init();
923 	if (dim == NULL) {
924 		err(-1, "failed to initialize devinfo minor translation");
925 	}
926 
927 	for (di_node_t child = di_child_node(ctrl); child != DI_NODE_NIL;
928 	    child = di_sibling_node(child)) {
929 		char *disk_ctd, *path = NULL;
930 		const char *addr = di_bus_addr(child);
931 		if (addr == NULL)
932 			continue;
933 
934 		if (strcmp(addr, bd_addr) != 0)
935 			continue;
936 
937 		path = di_dim_path_dev(dim, di_driver_name(child),
938 		    di_instance(child), "c");
939 
940 		/*
941 		 * Error out if we didn't get a path, or if it's too short for
942 		 * the following operations to be safe.
943 		 */
944 		if (path == NULL || strlen(path) < 2) {
945 			errx(-1, "failed to get a valid minor path");
946 		}
947 
948 		/* Chop off 's0' and get everything past the last '/' */
949 		path[strlen(path) - 2] = '\0';
950 		disk_ctd = strrchr(path, '/');
951 		if (disk_ctd == NULL) {
952 			errx(-1, "encountered malformed minor path: %s", path);
953 		}
954 
955 		diskname = strdup(++disk_ctd);
956 		if (diskname == NULL) {
957 			err(-1, "failed to duplicate disk path");
958 		}
959 
960 		free(path);
961 		break;
962 	}
963 
964 	di_dim_fini(dim);
965 	return (diskname);
966 }
967 
968 static void
969 usage_list(const char *c_name)
970 {
971 	(void) fprintf(stderr, "%s "
972 	    "[-c] [-p -o field[,...]] [<ctl>[/<ns>][,...]\n\n"
973 	    "  List NVMe controllers and their namespaces. If no "
974 	    "controllers and/or name-\n  spaces are specified, all "
975 	    "controllers and namespaces in the system will be\n  "
976 	    "listed.\n", c_name);
977 }
978 
979 static void
980 optparse_list(nvme_process_arg_t *npa)
981 {
982 	int c;
983 	uint_t oflags = 0;
984 	boolean_t parse = B_FALSE;
985 	const char *fields = NULL;
986 	const ofmt_field_t *ofmt = nvmeadm_list_nsid_ofmt;
987 
988 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":co:p")) != -1) {
989 		switch (c) {
990 		case 'c':
991 			npa->npa_cmdflags |= NVMEADM_O_LS_CTRL;
992 			ofmt = nvmeadm_list_ctrl_ofmt;
993 			break;
994 		case 'o':
995 			fields = optarg;
996 			break;
997 
998 		case 'p':
999 			parse = B_TRUE;
1000 			oflags |= OFMT_PARSABLE;
1001 			break;
1002 
1003 		case '?':
1004 			errx(-1, "unknown option: -%c", optopt);
1005 
1006 		case ':':
1007 			errx(-1, "option -%c requires an argument", optopt);
1008 		}
1009 	}
1010 
1011 	if (fields != NULL && !parse) {
1012 		errx(-1, "-o can only be used when in parsable mode (-p)");
1013 	}
1014 
1015 	if (parse && fields == NULL) {
1016 		errx(-1, "parsable mode (-p) requires one to specify output "
1017 		    "fields with -o");
1018 	}
1019 
1020 	if (parse) {
1021 		ofmt_status_t oferr;
1022 
1023 		oferr = ofmt_open(fields, ofmt, oflags, 0,
1024 		    &npa->npa_ofmt);
1025 		ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);
1026 	}
1027 }
1028 
1029 static void
1030 do_list_nsid(const nvme_process_arg_t *npa, nvme_ctrl_info_t *ctrl,
1031     nvme_ns_info_t *ns)
1032 {
1033 	const char *bd_addr, *disk = NULL;
1034 	char *disk_path = NULL;
1035 	di_node_t ctrl_devi;
1036 
1037 	switch (nvme_ns_info_level(ns)) {
1038 	case NVME_NS_DISC_F_ALL:
1039 		disk = "unallocated";
1040 		break;
1041 	case NVME_NS_DISC_F_ALLOCATED:
1042 		disk = "inactive";
1043 		break;
1044 	case NVME_NS_DISC_F_ACTIVE:
1045 		disk = "ignored";
1046 		break;
1047 	case NVME_NS_DISC_F_NOT_IGNORED:
1048 		disk = "unattached";
1049 		break;
1050 	case NVME_NS_DISC_F_BLKDEV:
1051 		disk = "unknown";
1052 		if (nvme_ns_info_bd_addr(ns, &bd_addr) &&
1053 		    nvme_ctrl_devi(npa->npa_ctrl, &ctrl_devi)) {
1054 			disk_path = nvme_dskname(ctrl_devi, bd_addr);
1055 			disk = disk_path;
1056 		}
1057 		break;
1058 	}
1059 
1060 	if (npa->npa_ofmt != NULL) {
1061 		nvmeadm_list_ofmt_arg_t oarg = { 0 };
1062 
1063 		oarg.nloa_name = npa->npa_ctrl_name;
1064 		oarg.nloa_ctrl = ctrl;
1065 		oarg.nloa_ns = ns;
1066 		oarg.nloa_disk = disk_path;
1067 
1068 		ofmt_print(npa->npa_ofmt, &oarg);
1069 	} else {
1070 		(void) printf("  %s/%u (%s)", npa->npa_ctrl_name,
1071 		    nvme_ns_info_nsid(ns), disk);
1072 		if (nvme_ns_info_level(ns) >= NVME_NS_DISC_F_ACTIVE) {
1073 			(void) printf(": ");
1074 			nvme_print_nsid_summary(ns);
1075 		} else {
1076 			(void) printf("\n");
1077 		}
1078 	}
1079 
1080 	free(disk_path);
1081 }
1082 
1083 static int
1084 do_list(const nvme_process_arg_t *npa)
1085 {
1086 	nvme_ctrl_info_t *info = NULL;
1087 	nvme_ns_iter_t *iter = NULL;
1088 	nvme_iter_t ret;
1089 	const nvme_ns_disc_t *disc;
1090 	nvme_ns_disc_level_t level;
1091 	int rv = -1;
1092 
1093 	if (npa->npa_argc > 0) {
1094 		errx(-1, "%s passed extraneous arguments starting with %s",
1095 		    npa->npa_cmd->c_name, npa->npa_argv[0]);
1096 	}
1097 
1098 	if (!nvme_ctrl_info_snap(npa->npa_ctrl, &info)) {
1099 		nvmeadm_warn(npa, "failed to get controller information for %s",
1100 		    npa->npa_ctrl_name);
1101 		return (-1);
1102 	}
1103 
1104 	if (npa->npa_ofmt == NULL) {
1105 		(void) printf("%s: ", npa->npa_ctrl_name);
1106 		nvme_print_ctrl_summary(info);
1107 	} else if ((npa->npa_cmdflags & NVMEADM_O_LS_CTRL) != 0) {
1108 		nvmeadm_list_ofmt_arg_t oarg = { 0 };
1109 		oarg.nloa_name = npa->npa_ctrl_name;
1110 		oarg.nloa_ctrl = info;
1111 
1112 		ofmt_print(npa->npa_ofmt, &oarg);
1113 	}
1114 
1115 	if ((npa->npa_cmdflags & NVMEADM_O_LS_CTRL) != 0) {
1116 		rv = 0;
1117 		goto out;
1118 	}
1119 
1120 	/*
1121 	 * Check if we were given an explicit namespace as an argument. If so,
1122 	 * we always list it and don't need to do discovery.
1123 	 */
1124 	if (npa->npa_ns != NULL) {
1125 		nvme_ns_info_t *ns_info;
1126 
1127 		if (!nvme_ns_info_snap(npa->npa_ns, &ns_info)) {
1128 			nvmeadm_warn(npa, "failed to get namespace "
1129 			    "information for %s", npa->npa_name);
1130 			goto out;
1131 		}
1132 
1133 		do_list_nsid(npa, info, ns_info);
1134 		nvme_ns_info_free(ns_info);
1135 		rv = 0;
1136 		goto out;
1137 	}
1138 
1139 	if (verbose) {
1140 		level = NVME_NS_DISC_F_ALL;
1141 	} else {
1142 		level = NVME_NS_DISC_F_NOT_IGNORED;
1143 	}
1144 
1145 	if (!nvme_ns_discover_init(npa->npa_ctrl, level, &iter)) {
1146 		nvmeadm_warn(npa, "failed to iterate namespaces on %s",
1147 		    npa->npa_ctrl_name);
1148 		goto out;
1149 	}
1150 
1151 	while ((ret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) {
1152 		nvme_ns_info_t *ns_info;
1153 		uint32_t nsid = nvme_ns_disc_nsid(disc);
1154 
1155 		if (!nvme_ctrl_ns_info_snap(npa->npa_ctrl, nsid, &ns_info)) {
1156 			nvmeadm_warn(npa, "failed to get namespace "
1157 			    "information for %s/%u", npa->npa_ctrl_name, nsid);
1158 			exitcode = -1;
1159 			continue;
1160 		}
1161 
1162 		do_list_nsid(npa, info, ns_info);
1163 		nvme_ns_info_free(ns_info);
1164 	}
1165 
1166 	nvme_ns_discover_fini(iter);
1167 	if (ret == NVME_ITER_ERROR) {
1168 		nvmeadm_warn(npa, "failed to iterate all namespaces on %s",
1169 		    npa->npa_ctrl_name);
1170 	} else {
1171 		rv = 0;
1172 	}
1173 
1174 out:
1175 	nvme_ctrl_info_free(info);
1176 	return (rv);
1177 }
1178 
1179 static void
1180 optparse_identify_ctrl(nvme_process_arg_t *npa)
1181 {
1182 	int c;
1183 
1184 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":Cacn")) != -1) {
1185 		switch (c) {
1186 		case 'C':
1187 			npa->npa_cmdflags |= NVMEADM_O_ID_COMMON_NS;
1188 			break;
1189 
1190 		case 'a':
1191 			npa->npa_cmdflags |= NVMEADM_O_ID_ALLOC_NS;
1192 			break;
1193 
1194 		case 'c':
1195 			npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST;
1196 			break;
1197 
1198 		case 'n':
1199 			npa->npa_cmdflags |= NVMEADM_O_ID_NSID_LIST;
1200 			break;
1201 
1202 		case '?':
1203 			errx(-1, "unknown option: -%c", optopt);
1204 
1205 		case ':':
1206 			errx(-1, "option -%c requires an argument", optopt);
1207 		}
1208 	}
1209 }
1210 
1211 static void
1212 usage_identify_ctrl(const char *c_name)
1213 {
1214 	(void) fprintf(stderr, "%s [-C | -c | [-a] -n] <ctl>[,...]\n\n"
1215 	    "  Print detailed information about the specified NVMe "
1216 	    "controllers.\n", c_name);
1217 }
1218 
1219 static int
1220 do_identify_ctrl(const nvme_process_arg_t *npa)
1221 {
1222 	boolean_t alloc = B_FALSE;
1223 
1224 	if (npa->npa_ns != NULL)
1225 		errx(-1, "identify-controller cannot be used on namespaces");
1226 
1227 	if (npa->npa_argc > 0) {
1228 		errx(-1, "%s passed extraneous arguments starting with %s",
1229 		    npa->npa_cmd->c_name, npa->npa_argv[0]);
1230 	}
1231 
1232 	if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0 &&
1233 	    npa->npa_cmdflags != NVMEADM_O_ID_COMMON_NS) {
1234 		errx(-1, "-C cannot be combined with other flags");
1235 	}
1236 
1237 	if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 &&
1238 	    npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) {
1239 		errx(-1, "-c cannot be combined with other flags");
1240 	}
1241 
1242 	if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0 &&
1243 	    npa->npa_cmdflags !=
1244 	    (NVMEADM_O_ID_ALLOC_NS | NVMEADM_O_ID_NSID_LIST)) {
1245 		errx(-1, "-a can only be used together with -n");
1246 	}
1247 
1248 	if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0) {
1249 		alloc = B_TRUE;
1250 	}
1251 
1252 	if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0) {
1253 		const nvme_identify_nsid_t *idns;
1254 
1255 		if (!nvme_ctrl_info_common_ns(npa->npa_ctrl_info, &idns)) {
1256 			nvmeadm_ctrl_info_warn(npa, "failed to get common "
1257 			    "namespace information for %s", npa->npa_name);
1258 			return (-1);
1259 		}
1260 
1261 		(void) printf("%s: ", npa->npa_name);
1262 		nvme_print_identify_nsid(idns, npa->npa_version);
1263 	} else if ((npa->npa_cmdflags & NVMEADM_O_ID_NSID_LIST) != 0) {
1264 		const char *caption;
1265 		uint32_t cns;
1266 		nvme_identify_nsid_list_t *idnslist;
1267 		nvme_id_req_t *req;
1268 
1269 		if (alloc) {
1270 			caption = "Identify Allocated Namespace List";
1271 			cns = NVME_IDENTIFY_NSID_ALLOC_LIST;
1272 		} else {
1273 			caption = "Identify Active Namespace List";
1274 			cns = NVME_IDENTIFY_NSID_LIST;
1275 		}
1276 
1277 		if ((idnslist = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) {
1278 			err(-1, "failed to allocate identify buffer size");
1279 		}
1280 
1281 		if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM, cns,
1282 		    &req)) {
1283 			nvmeadm_fatal(npa, "failed to initialize %s request",
1284 			    caption);
1285 		}
1286 
1287 		/*
1288 		 * Always set the NSID for these requests to NSID 0 so that way
1289 		 * we can start the list at the beginning. When we encounter
1290 		 * devices with more than 1024 NSIDs then we'll need to issue
1291 		 * additional requests.
1292 		 */
1293 		if (!nvme_id_req_set_nsid(req, 0) ||
1294 		    !nvme_id_req_set_output(req, idnslist,
1295 		    NVME_IDENTIFY_BUFSIZE)) {
1296 			nvmeadm_fatal(npa, "failed to set required fields for "
1297 			    "identify request");
1298 		}
1299 
1300 		if (!nvme_id_req_exec(req)) {
1301 			nvmeadm_fatal(npa, "failed to execute identify "
1302 			    "request");
1303 		}
1304 		nvme_id_req_fini(req);
1305 
1306 		(void) printf("%s: ", npa->npa_name);
1307 
1308 		nvme_print_identify_nsid_list(caption, idnslist);
1309 		free(idnslist);
1310 	} else if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0) {
1311 		nvme_identify_ctrl_list_t *ctlist;
1312 		nvme_id_req_t *req;
1313 
1314 		if ((ctlist = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) {
1315 			err(-1, "failed to allocate identify buffer size");
1316 		}
1317 
1318 		if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM,
1319 		    NVME_IDENTIFY_CTRL_LIST, &req)) {
1320 			nvmeadm_fatal(npa, "failed to initialize identify "
1321 			    "request");
1322 		}
1323 
1324 		if (!nvme_id_req_set_ctrlid(req, 0) ||
1325 		    !nvme_id_req_set_output(req, ctlist,
1326 		    NVME_IDENTIFY_BUFSIZE)) {
1327 			nvmeadm_fatal(npa, "failed to set required fields for "
1328 			    "identify request");
1329 		}
1330 		if (!nvme_id_req_exec(req)) {
1331 			nvmeadm_fatal(npa, "failed to execute identify "
1332 			    "request");
1333 		}
1334 		nvme_id_req_fini(req);
1335 
1336 		(void) printf("%s: ", npa->npa_name);
1337 		nvme_print_identify_ctrl_list("Identify Controller List",
1338 		    ctlist);
1339 		free(ctlist);
1340 	} else {
1341 		uint32_t mpsmin;
1342 
1343 		if (!nvme_ctrl_info_pci_mps_min(npa->npa_ctrl_info,
1344 		    &mpsmin)) {
1345 			nvmeadm_ctrl_info_fatal(npa, "failed to get minimum "
1346 			    "memory page size");
1347 		}
1348 
1349 		(void) printf("%s: ", npa->npa_name);
1350 		nvme_print_identify_ctrl(npa->npa_idctl, mpsmin,
1351 		    npa->npa_version);
1352 	}
1353 
1354 	return (0);
1355 }
1356 
1357 static void
1358 optparse_identify_ns(nvme_process_arg_t *npa)
1359 {
1360 	int c;
1361 
1362 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":cd")) != -1) {
1363 		switch (c) {
1364 		case 'c':
1365 			npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST;
1366 			break;
1367 
1368 		case 'd':
1369 			npa->npa_cmdflags |= NVMEADM_O_ID_DESC_LIST;
1370 			break;
1371 
1372 		case '?':
1373 			errx(-1, "unknown option: -%c", optopt);
1374 
1375 		case ':':
1376 			errx(-1, "option -%c requires an argument", optopt);
1377 		}
1378 	}
1379 }
1380 
1381 static void
1382 usage_identify_ns(const char *c_name)
1383 {
1384 	(void) fprintf(stderr, "%s [-c | -d ] <ctl>/<ns>[,...]\n\n"
1385 	    "  Print detailed information about the specified NVMe "
1386 	    "namespaces.\n", c_name);
1387 }
1388 
1389 static int
1390 do_identify_ns(const nvme_process_arg_t *npa)
1391 {
1392 	uint32_t nsid;
1393 
1394 	if (npa->npa_ns == NULL)
1395 		errx(-1, "identify-namespace cannot be used on controllers");
1396 
1397 	if (npa->npa_argc > 0) {
1398 		errx(-1, "%s passed extraneous arguments starting with %s",
1399 		    npa->npa_cmd->c_name, npa->npa_argv[0]);
1400 	}
1401 
1402 	if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 &&
1403 	    npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) {
1404 		errx(-1, "-c cannot be combined with other flags");
1405 	}
1406 
1407 	if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0 &&
1408 	    npa->npa_cmdflags != NVMEADM_O_ID_DESC_LIST) {
1409 		errx(-1, "-d cannot be combined with other flags");
1410 	}
1411 
1412 	if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0) {
1413 		errx(-1, "-a cannot be used on namespaces");
1414 	}
1415 
1416 	nsid = nvme_ns_info_nsid(npa->npa_ns_info);
1417 
1418 	if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0) {
1419 		nvme_identify_ctrl_list_t *ctlist;
1420 		nvme_id_req_t *req;
1421 
1422 		if ((ctlist = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) {
1423 			err(-1, "failed to allocate identify buffer size");
1424 		}
1425 
1426 		if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM,
1427 		    NVME_IDENTIFY_NSID_CTRL_LIST, &req)) {
1428 			nvmeadm_fatal(npa, "failed to initialize identify "
1429 			    "request");
1430 		}
1431 
1432 		if (!nvme_id_req_set_nsid(req, nsid) ||
1433 		    !nvme_id_req_set_ctrlid(req, 0) ||
1434 		    !nvme_id_req_set_output(req, ctlist,
1435 		    NVME_IDENTIFY_BUFSIZE)) {
1436 			nvmeadm_fatal(npa, "failed to set required fields for "
1437 			    "identify request");
1438 		}
1439 
1440 		if (!nvme_id_req_exec(req)) {
1441 			nvmeadm_fatal(npa, "failed to execute identify "
1442 			    "request");
1443 		}
1444 		nvme_id_req_fini(req);
1445 
1446 		(void) printf("%s: ", npa->npa_name);
1447 		nvme_print_identify_ctrl_list(
1448 		    "Identify Attached Controller List", ctlist);
1449 		free(ctlist);
1450 	} else if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0) {
1451 		nvme_identify_nsid_desc_t *nsdesc;
1452 		nvme_id_req_t *req;
1453 
1454 		if ((nsdesc = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) {
1455 			err(-1, "failed to allocate identify buffer size");
1456 		}
1457 
1458 		if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM,
1459 		    NVME_IDENTIFY_NSID_DESC, &req)) {
1460 			nvmeadm_fatal(npa, "failed to initialize identify "
1461 			    "request");
1462 		}
1463 
1464 		if (!nvme_id_req_set_nsid(req, nsid) ||
1465 		    !nvme_id_req_set_output(req, nsdesc,
1466 		    NVME_IDENTIFY_BUFSIZE)) {
1467 			nvmeadm_fatal(npa, "failed to set required fields for "
1468 			    "identify request");
1469 		}
1470 
1471 		if (!nvme_id_req_exec(req)) {
1472 			nvmeadm_fatal(npa, "failed to execute identify "
1473 			    "request");
1474 		}
1475 		nvme_id_req_fini(req);
1476 
1477 		(void) printf("%s: ", npa->npa_name);
1478 		nvme_print_identify_nsid_desc(nsdesc);
1479 		free(nsdesc);
1480 	} else {
1481 		const nvme_identify_nsid_t *idns;
1482 
1483 		(void) printf("%s: ", npa->npa_name);
1484 		idns = nvme_ns_info_identify(npa->npa_ns_info);
1485 		nvme_print_identify_nsid(idns, npa->npa_version);
1486 	}
1487 
1488 	return (0);
1489 }
1490 
1491 static void
1492 optparse_identify(nvme_process_arg_t *npa)
1493 {
1494 	int c;
1495 
1496 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":Cacdn")) != -1) {
1497 		switch (c) {
1498 		case 'C':
1499 			npa->npa_cmdflags |= NVMEADM_O_ID_COMMON_NS;
1500 			break;
1501 
1502 		case 'a':
1503 			npa->npa_cmdflags |= NVMEADM_O_ID_ALLOC_NS;
1504 			break;
1505 
1506 		case 'c':
1507 			npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST;
1508 			break;
1509 
1510 		case 'd':
1511 			npa->npa_cmdflags |= NVMEADM_O_ID_DESC_LIST;
1512 			break;
1513 
1514 		case 'n':
1515 			npa->npa_cmdflags |= NVMEADM_O_ID_NSID_LIST;
1516 			break;
1517 
1518 		case '?':
1519 			errx(-1, "unknown option: -%c", optopt);
1520 
1521 		case ':':
1522 			errx(-1, "option -%c requires an argument", optopt);
1523 
1524 		}
1525 	}
1526 
1527 	if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0 &&
1528 	    (npa->npa_cmdflags &
1529 	    ~(NVMEADM_O_ID_ALLOC_NS | NVMEADM_O_ID_NSID_LIST)) != 0) {
1530 		errx(-1, "-a can only be used alone or together with -n");
1531 	}
1532 
1533 	if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0 &&
1534 	    npa->npa_cmdflags != NVMEADM_O_ID_COMMON_NS) {
1535 		errx(-1, "-C cannot be combined with other flags");
1536 
1537 	}
1538 
1539 	if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 &&
1540 	    npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) {
1541 		errx(-1, "-c cannot be combined with other flags");
1542 	}
1543 
1544 	if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0 &&
1545 	    npa->npa_cmdflags != NVMEADM_O_ID_DESC_LIST) {
1546 		errx(-1, "-d cannot be combined with other flags");
1547 	}
1548 }
1549 
1550 static void
1551 usage_identify(const char *c_name)
1552 {
1553 	(void) fprintf(stderr,
1554 	    "%s [ -C | -c | -d | [-a] -n ] <ctl>[/<ns>][,...]\n\n"
1555 	    "  Print detailed information about the specified NVMe "
1556 	    "controllers and/or name-\n  spaces.\n", c_name);
1557 }
1558 
1559 static int
1560 do_identify(const nvme_process_arg_t *npa)
1561 {
1562 	if (npa->npa_argc > 0) {
1563 		errx(-1, "%s passed extraneous arguments starting with %s",
1564 		    npa->npa_cmd->c_name, npa->npa_argv[0]);
1565 	}
1566 
1567 	if (npa->npa_ns != NULL) {
1568 		if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0)
1569 			errx(-1, "-C cannot be used on namespaces");
1570 
1571 		if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0)
1572 			errx(-1, "-a cannot be used on namespaces");
1573 
1574 		if ((npa->npa_cmdflags & NVMEADM_O_ID_NSID_LIST) != 0)
1575 			errx(-1, "-n cannot be used on namespaces");
1576 
1577 		return (do_identify_ns(npa));
1578 	} else {
1579 		if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0)
1580 			errx(-1, "-d cannot be used on controllers");
1581 
1582 		return (do_identify_ctrl(npa));
1583 	}
1584 }
1585 
1586 static void
1587 optparse_list_logs(nvme_process_arg_t *npa)
1588 {
1589 	int c;
1590 	uint_t oflags = 0;
1591 	boolean_t parse = B_FALSE;
1592 	const char *fields = NULL;
1593 	char *scope = NULL;
1594 	ofmt_status_t oferr;
1595 	nvmeadm_list_logs_t *nll;
1596 
1597 	if ((nll = calloc(1, sizeof (nvmeadm_list_logs_t))) == NULL) {
1598 		err(-1, "failed to allocate memory to track log information");
1599 	}
1600 
1601 	npa->npa_cmd_arg = nll;
1602 
1603 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":aHo:ps:")) != -1) {
1604 		switch (c) {
1605 		case 'a':
1606 			nll->nll_unimpl = B_TRUE;
1607 			break;
1608 		case 'H':
1609 			oflags |= OFMT_NOHEADER;
1610 			break;
1611 		case 'o':
1612 			fields = optarg;
1613 			break;
1614 		case 'p':
1615 			parse = B_TRUE;
1616 			oflags |= OFMT_PARSABLE;
1617 			break;
1618 		case 's':
1619 			scope = optarg;
1620 			break;
1621 		case '?':
1622 			errx(-1, "unknown option: -%c", optopt);
1623 		case ':':
1624 			errx(-1, "option -%c requires an argument", optopt);
1625 		}
1626 	}
1627 
1628 	if (!parse) {
1629 		oflags |= OFMT_WRAP;
1630 	}
1631 
1632 	if (parse && fields == NULL) {
1633 		errx(-1, "parsable mode (-p) requires fields specified with "
1634 		    "-o");
1635 	}
1636 
1637 	if (fields == NULL) {
1638 		if (nll->nll_unimpl) {
1639 			fields = nvmeadm_list_logs_fields_impl;
1640 		} else {
1641 			fields = nvmeadm_list_logs_fields;
1642 		}
1643 	}
1644 
1645 	if (scope != NULL) {
1646 		const char *str;
1647 
1648 		while ((str = strsep(&scope, ",")) != NULL) {
1649 			if (strcasecmp(str, "nvm") == 0) {
1650 				nll->nll_scope |= NVME_LOG_SCOPE_NVM;
1651 			} else if (strcasecmp(str, "ns") == 0 ||
1652 			    strcasecmp(str, "namespace") == 0) {
1653 				nll->nll_scope |= NVME_LOG_SCOPE_NS;
1654 			} else if (strcasecmp(str, "ctrl") == 0 ||
1655 			    strcasecmp(str, "controller") == 0) {
1656 				nll->nll_scope |= NVME_LOG_SCOPE_CTRL;
1657 			} else {
1658 				errx(-1, "unknown scope string: '%s'; valid "
1659 				    "values are 'nvm', 'namespace', and "
1660 				    "'controller'", str);
1661 			}
1662 		}
1663 	}
1664 
1665 	oferr = ofmt_open(fields, nvmeadm_list_logs_ofmt, oflags, 0,
1666 	    &npa->npa_ofmt);
1667 	ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);
1668 
1669 	if (npa->npa_argc - optind > 1) {
1670 		nll->nll_nfilts = npa->npa_argc - optind - 1;
1671 		nll->nll_filts = npa->npa_argv + optind + 1;
1672 		nll->nll_used = calloc(nll->nll_nfilts, sizeof (boolean_t));
1673 		if (nll->nll_used == NULL) {
1674 			err(-1, "failed to allocate memory for tracking log "
1675 			    "page filters");
1676 		}
1677 	}
1678 }
1679 
1680 static void
1681 usage_list_logs(const char *c_name)
1682 {
1683 	(void) fprintf(stderr, "%s [-H] [-o field,[...] [-p]] [-s scope,[...]] "
1684 	    "[-a]\n\t  [<ctl>[/<ns>][,...] [logpage...]\n\n"
1685 	    "  List log pages supported by controllers or namespaces.\n",
1686 	    c_name);
1687 }
1688 
1689 static boolean_t
1690 do_list_logs_match(const nvme_log_disc_t *disc, nvmeadm_list_logs_t *nll)
1691 {
1692 	if (!nll->nll_unimpl && !nvme_log_disc_impl(disc)) {
1693 		return (B_FALSE);
1694 	}
1695 
1696 	if (nll->nll_nfilts <= 0) {
1697 		return (B_TRUE);
1698 	}
1699 
1700 	for (int i = 0; i < nll->nll_nfilts; i++) {
1701 		if (strcmp(nvme_log_disc_name(disc), nll->nll_filts[i]) == 0) {
1702 			nll->nll_used[i] = B_TRUE;
1703 			return (B_TRUE);
1704 		}
1705 	}
1706 
1707 	return (B_FALSE);
1708 }
1709 
1710 static int
1711 do_list_logs(const nvme_process_arg_t *npa)
1712 {
1713 	nvme_log_disc_scope_t scope;
1714 	nvme_log_iter_t *iter;
1715 	nvme_iter_t ret;
1716 	const nvme_log_disc_t *disc;
1717 	nvmeadm_list_logs_t *nll = npa->npa_cmd_arg;
1718 
1719 	if (nll->nll_scope != 0) {
1720 		scope = nll->nll_scope;
1721 	} else if (npa->npa_ns != NULL) {
1722 		scope = NVME_LOG_SCOPE_NS;
1723 	} else {
1724 		scope = NVME_LOG_SCOPE_CTRL | NVME_LOG_SCOPE_NVM;
1725 	}
1726 
1727 	if (!nvme_log_discover_init(npa->npa_ctrl, scope, 0, &iter)) {
1728 		nvmeadm_warn(npa, "failed to iterate logs on %s",
1729 		    npa->npa_ctrl_name);
1730 		return (-1);
1731 	}
1732 
1733 	while ((ret = nvme_log_discover_step(iter, &disc)) == NVME_ITER_VALID) {
1734 		if (do_list_logs_match(disc, nll)) {
1735 			nvmeadm_list_logs_ofmt_arg_t print;
1736 
1737 			print.nlloa_name = npa->npa_name;
1738 			print.nlloa_disc = disc;
1739 			ofmt_print(npa->npa_ofmt, &print);
1740 			nll->nll_nprint++;
1741 		}
1742 	}
1743 
1744 	nvme_log_discover_fini(iter);
1745 	if (ret == NVME_ITER_ERROR) {
1746 		nvmeadm_warn(npa, "failed to iterate logs on %s",
1747 		    npa->npa_ctrl_name);
1748 		return (-1);
1749 	}
1750 
1751 	for (int i = 0; i < nll->nll_nfilts; i++) {
1752 		if (!nll->nll_used[i]) {
1753 			warnx("log page filter '%s' did match any log pages",
1754 			    nll->nll_filts[i]);
1755 			exitcode = -1;
1756 		}
1757 	}
1758 
1759 	if (nll->nll_nprint == 0) {
1760 		if (nll->nll_nfilts == 0) {
1761 			warnx("no log pages found for %s", npa->npa_name);
1762 		}
1763 		exitcode = -1;
1764 	}
1765 
1766 	return (exitcode);
1767 }
1768 
1769 static void
1770 usage_get_logpage(const char *c_name)
1771 {
1772 	(void) fprintf(stderr, "%s [-O file] <ctl>[/<ns>][,...] <logpage>\n\n"
1773 	    "  Print the specified log page of the specified NVMe "
1774 	    "controllers and/or name-\n  spaces. Run nvmeadm list-logpages "
1775 	    "for supported log pages. All devices\n support error, health, "
1776 	    "and firmware.\n", c_name);
1777 }
1778 
1779 static void
1780 usage_firmware_list(const char *c_name)
1781 {
1782 	(void) fprintf(stderr, "%s <ctl>\n\n"
1783 	    "  Print the log page that contains the list of firmware "
1784 	    "images installed on the specified NVMe controller.\n", c_name);
1785 }
1786 
1787 static uint64_t
1788 do_get_logpage_size(const nvme_process_arg_t *npa, nvme_log_disc_t *disc,
1789     nvme_log_req_t *req)
1790 {
1791 	uint64_t len, ret;
1792 	void *buf;
1793 	nvme_log_size_kind_t kind;
1794 
1795 	kind = nvme_log_disc_size(disc, &len);
1796 	if (kind != NVME_LOG_SIZE_K_VAR) {
1797 		return (len);
1798 	}
1799 
1800 	/*
1801 	 * We have a log with a variable length size. To determine the actual
1802 	 * size we must actually determine the full length of this.
1803 	 */
1804 	if ((buf = malloc(len)) == NULL) {
1805 		errx(-1, "failed to allocate %zu byte buffer to get log "
1806 		    "page size", len);
1807 	}
1808 
1809 	if (!nvme_log_req_set_output(req, buf, len)) {
1810 		nvmeadm_fatal(npa, "failed to set output parameters to "
1811 		    "determine log length");
1812 	}
1813 
1814 	if (!nvme_log_req_exec(req)) {
1815 		nvmeadm_fatal(npa, "failed to execute log request %s to "
1816 		    "determine log length", npa->npa_argv[0]);
1817 	}
1818 
1819 	if (!nvme_log_disc_calc_size(disc, &ret, buf, len)) {
1820 		errx(-1, "failed to determine full %s log length",
1821 		    npa->npa_argv[0]);
1822 	}
1823 
1824 	free(buf);
1825 	return (ret);
1826 }
1827 
1828 static void
1829 do_get_logpage_dump(const void *buf, size_t len, const char *file)
1830 {
1831 	size_t off = 0;
1832 	int fd = open(file, O_WRONLY | O_TRUNC | O_CREAT, 0644);
1833 
1834 	if (fd < 0) {
1835 		err(-1, "failed to create output file %s", file);
1836 	}
1837 
1838 	while (len > 0) {
1839 		ssize_t ret = write(fd, buf + off, len - off);
1840 		if (ret < 0) {
1841 			err(EXIT_FAILURE, "failed to write log data to file %s "
1842 			    "at offset %zu", file, off);
1843 		}
1844 
1845 		off += (size_t)ret;
1846 		len -= (size_t)ret;
1847 	}
1848 
1849 	(void) close(fd);
1850 }
1851 
1852 static int
1853 do_get_logpage_common(const nvme_process_arg_t *npa, const char *page)
1854 {
1855 	int ret = 0;
1856 	nvme_log_disc_t *disc;
1857 	nvme_log_req_t *req;
1858 	nvme_log_disc_scope_t scope;
1859 	void *buf;
1860 	size_t toalloc;
1861 	nvmeadm_get_logpage_t *log = npa->npa_cmd_arg;
1862 
1863 	/*
1864 	 * If we have enough information to identify a log-page via libnvme (or
1865 	 * in the future take enough options to allow us to actually do this
1866 	 * manually), then we will fetch it. If we don't know how to print it,
1867 	 * then we'll just hex dump it for now.
1868 	 */
1869 	if (!nvme_log_req_init_by_name(npa->npa_ctrl, page, 0, &disc, &req)) {
1870 		nvmeadm_fatal(npa, "could not initialize log request for %s",
1871 		    page);
1872 	}
1873 
1874 	if (npa->npa_ns != NULL) {
1875 		scope = NVME_LOG_SCOPE_NS;
1876 	} else {
1877 		scope = NVME_LOG_SCOPE_CTRL | NVME_LOG_SCOPE_NVM;
1878 	}
1879 
1880 	if ((scope & nvme_log_disc_scopes(disc)) == 0) {
1881 		errx(-1, "log page %s does not support operating on %s", page,
1882 		    npa->npa_ns != NULL ? "namespaces" : "controllers");
1883 	}
1884 
1885 	/*
1886 	 * In the future we should add options to allow one to specify and set
1887 	 * the fields for the lsp, lsi, etc. and set them here.
1888 	 */
1889 
1890 	if (npa->npa_ns != NULL) {
1891 		uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info);
1892 
1893 		if (!nvme_log_req_set_nsid(req, nsid)) {
1894 			nvmeadm_fatal(npa, "failed to set log request "
1895 			    "namespace ID to 0x%x", nsid);
1896 		}
1897 	}
1898 
1899 	/*
1900 	 * The output size should be the last thing that we determine as we may
1901 	 * need to issue a log request to figure out how much data we should
1902 	 * actually be reading.
1903 	 */
1904 	toalloc = do_get_logpage_size(npa, disc, req);
1905 	buf = malloc(toalloc);
1906 	if (buf == NULL) {
1907 		err(-1, "failed to allocate %zu bytes for log "
1908 		    "request %s", toalloc, page);
1909 	}
1910 
1911 	if (!nvme_log_req_set_output(req, buf, toalloc)) {
1912 		nvmeadm_fatal(npa, "failed to set output parameters");
1913 	}
1914 
1915 	if (!nvme_log_req_exec(req)) {
1916 		nvmeadm_fatal(npa, "failed to execute log request %s",
1917 		    npa->npa_argv[0]);
1918 	}
1919 
1920 	if (log != NULL && log->ngl_output != NULL) {
1921 		do_get_logpage_dump(buf, toalloc, log->ngl_output);
1922 		goto done;
1923 	}
1924 
1925 	(void) printf("%s: ", npa->npa_name);
1926 	if (strcmp(page, "error") == 0) {
1927 		size_t nlog = toalloc / sizeof (nvme_error_log_entry_t);
1928 		nvme_print_error_log(nlog, buf, npa->npa_version);
1929 	} else if (strcmp(page, "health") == 0) {
1930 		nvme_print_health_log(buf, npa->npa_idctl, npa->npa_version);
1931 	} else if (strcmp(page, "firmware") == 0) {
1932 		nvme_print_fwslot_log(buf, npa->npa_idctl);
1933 	} else {
1934 		(void) printf("%s (%s)\n", nvme_log_disc_desc(disc), page);
1935 		nvmeadm_dump_hex(buf, toalloc);
1936 	}
1937 
1938 done:
1939 	free(buf);
1940 	nvme_log_disc_free(disc);
1941 	nvme_log_req_fini(req);
1942 
1943 	return (ret);
1944 }
1945 
1946 static int
1947 do_get_logpage_fwslot(const nvme_process_arg_t *npa)
1948 {
1949 	if (npa->npa_argc >= 1) {
1950 		warnx("no additional arguments may be specified to %s",
1951 		    npa->npa_cmd->c_name);
1952 		usage(npa->npa_cmd);
1953 		exit(-1);
1954 	}
1955 
1956 	return (do_get_logpage_common(npa, "firmware"));
1957 }
1958 
1959 static void
1960 optparse_get_logpage(nvme_process_arg_t *npa)
1961 {
1962 	int c;
1963 	const char *output = NULL;
1964 	nvmeadm_get_logpage_t *log;
1965 
1966 	if ((log = calloc(1, sizeof (nvmeadm_get_logpage_t))) == NULL) {
1967 		err(-1, "failed to allocate memory to track log page "
1968 		    "information");
1969 	}
1970 
1971 	npa->npa_cmd_arg = log;
1972 
1973 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":O:")) != -1) {
1974 		switch (c) {
1975 		case 'O':
1976 			output = optarg;
1977 			break;
1978 		case '?':
1979 			errx(-1, "unknown option: -%c", optopt);
1980 		case ':':
1981 			errx(-1, "option -%c requires an argument", optopt);
1982 		}
1983 	}
1984 
1985 	log->ngl_output = output;
1986 }
1987 
1988 static int
1989 do_get_logpage(const nvme_process_arg_t *npa)
1990 {
1991 
1992 	if (npa->npa_argc < 1) {
1993 		warnx("missing log page name");
1994 		usage(npa->npa_cmd);
1995 		exit(-1);
1996 	}
1997 
1998 	if (npa->npa_argc > 1) {
1999 		warnx("only a single log page may be specified at a time");
2000 		usage(npa->npa_cmd);
2001 		exit(-1);
2002 	}
2003 
2004 	return (do_get_logpage_common(npa, npa->npa_argv[0]));
2005 }
2006 
2007 static void
2008 optparse_list_features(nvme_process_arg_t *npa)
2009 {
2010 	int c;
2011 	uint_t oflags = 0;
2012 	boolean_t parse = B_FALSE;
2013 	const char *fields = NULL;
2014 	nvmeadm_features_t *feat;
2015 	ofmt_status_t oferr;
2016 
2017 	if ((feat = calloc(1, sizeof (nvmeadm_features_t))) == NULL) {
2018 		err(-1, "failed to allocate memory to track feature "
2019 		    "information");
2020 	}
2021 
2022 	npa->npa_cmd_arg = feat;
2023 
2024 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":aHo:p")) != -1) {
2025 		switch (c) {
2026 		case 'a':
2027 			feat->nf_unimpl = B_TRUE;
2028 			break;
2029 		case 'H':
2030 			oflags |= OFMT_NOHEADER;
2031 			break;
2032 		case 'o':
2033 			fields = optarg;
2034 			break;
2035 		case 'p':
2036 			parse = B_TRUE;
2037 			oflags |= OFMT_PARSABLE;
2038 			break;
2039 		case '?':
2040 			errx(-1, "unknown option: -%c", optopt);
2041 		case ':':
2042 			errx(-1, "option -%c requires an argument", optopt);
2043 		}
2044 	}
2045 
2046 	if (!parse) {
2047 		oflags |= OFMT_WRAP;
2048 	}
2049 
2050 	if (parse && fields == NULL) {
2051 		errx(-1, "parsable mode (-p) requires fields specified with "
2052 		    "-o");
2053 	}
2054 
2055 	if (fields == NULL) {
2056 		fields = nvmeadm_list_features_fields;
2057 	}
2058 
2059 	oferr = ofmt_open(fields, nvmeadm_list_features_ofmt, oflags, 0,
2060 	    &npa->npa_ofmt);
2061 	ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);
2062 
2063 	if (npa->npa_argc - optind > 1) {
2064 		feat->nf_nfilts = (uint32_t)(npa->npa_argc - optind - 1);
2065 		feat->nf_filts = npa->npa_argv + optind + 1;
2066 		feat->nf_used = calloc(feat->nf_nfilts, sizeof (boolean_t));
2067 		if (feat->nf_used == NULL) {
2068 			err(-1, "failed to allocate memory for tracking "
2069 			    "feature filters");
2070 		}
2071 	}
2072 }
2073 
2074 static void
2075 usage_list_features(const char *c_name)
2076 {
2077 	(void) fprintf(stderr, "%s [-a] [-H] [-o field,[...] [-p]] "
2078 	    "<ctl>[/<ns>][,...]\n\t  [feature...]\n\n"
2079 	    "  List features supported by controllers or namespaces.\n",
2080 	    c_name);
2081 }
2082 
2083 static boolean_t
2084 do_features_match(const nvme_feat_disc_t *disc, nvmeadm_features_t *nf)
2085 {
2086 	if (nf->nf_nfilts == 0) {
2087 		return (B_TRUE);
2088 	}
2089 
2090 	for (uint32_t i = 0; i < nf->nf_nfilts; i++) {
2091 		const char *match = nf->nf_filts[i];
2092 		long long fid;
2093 		const char *err;
2094 
2095 		if (strcmp(nvme_feat_disc_short(disc), match) == 0 ||
2096 		    strcasecmp(nvme_feat_disc_spec(disc), match) == 0) {
2097 			nf->nf_used[i] = B_TRUE;
2098 			return (B_TRUE);
2099 		}
2100 
2101 		fid = strtonumx(match, 0, UINT32_MAX, &err, 0);
2102 		if (err == NULL && fid == nvme_feat_disc_fid(disc)) {
2103 			nf->nf_used[i] = B_TRUE;
2104 			return (B_TRUE);
2105 		}
2106 	}
2107 
2108 	return (B_FALSE);
2109 }
2110 
2111 
2112 /*
2113  * This is a common entry point for both list-features and get-features, which
2114  * iterate over all features and take action for each one.
2115  */
2116 typedef void (*do_features_cb_f)(const nvme_process_arg_t *,
2117     const nvme_feat_disc_t *);
2118 static int
2119 do_features(const nvme_process_arg_t *npa, nvmeadm_features_t *nf,
2120     do_features_cb_f func)
2121 {
2122 	nvme_feat_scope_t scope;
2123 	nvme_feat_iter_t *iter;
2124 	nvme_iter_t ret;
2125 	const nvme_feat_disc_t *disc;
2126 
2127 	if (npa->npa_ns != NULL) {
2128 		scope = NVME_FEAT_SCOPE_NS;
2129 	} else {
2130 		scope = NVME_FEAT_SCOPE_CTRL;
2131 	}
2132 
2133 	if (!nvme_feat_discover_init(npa->npa_ctrl, scope, 0, &iter)) {
2134 		nvmeadm_warn(npa, "failed to iterate features on %s",
2135 		    npa->npa_ctrl_name);
2136 		return (-1);
2137 	}
2138 
2139 	while ((ret = nvme_feat_discover_step(iter, &disc)) ==
2140 	    NVME_ITER_VALID) {
2141 		if (do_features_match(disc, nf)) {
2142 			if (!nf->nf_unimpl && nvme_feat_disc_impl(disc) ==
2143 			    NVME_FEAT_IMPL_UNSUPPORTED) {
2144 				continue;
2145 			}
2146 
2147 			func(npa, disc);
2148 			nf->nf_nprint++;
2149 		}
2150 	}
2151 
2152 	nvme_feat_discover_fini(iter);
2153 	if (ret == NVME_ITER_ERROR) {
2154 		nvmeadm_warn(npa, "failed to iterate features on %s",
2155 		    npa->npa_ctrl_name);
2156 		return (-1);
2157 	}
2158 
2159 	for (uint32_t i = 0; i < nf->nf_nfilts; i++) {
2160 		if (!nf->nf_used[i]) {
2161 			warnx("feature filter '%s' did match any features",
2162 			    nf->nf_filts[i]);
2163 			exitcode = -1;
2164 		}
2165 	}
2166 
2167 	if (nf->nf_nprint == 0) {
2168 		if (nf->nf_nfilts == 0) {
2169 			warnx("no features found for %s", npa->npa_name);
2170 		}
2171 		exitcode = -1;
2172 	}
2173 
2174 	return (exitcode);
2175 }
2176 
2177 static void
2178 do_list_features_cb(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc)
2179 {
2180 	nvmeadm_list_features_ofmt_arg_t print;
2181 
2182 	print.nlfoa_name = npa->npa_name;
2183 	print.nlfoa_feat = disc;
2184 	ofmt_print(npa->npa_ofmt, &print);
2185 }
2186 
2187 static int
2188 do_list_features(const nvme_process_arg_t *npa)
2189 {
2190 	nvmeadm_features_t *nf = npa->npa_cmd_arg;
2191 
2192 	return (do_features(npa, nf, do_list_features_cb));
2193 }
2194 
2195 static void
2196 usage_get_features(const char *c_name)
2197 {
2198 	(void) fprintf(stderr, "%s <ctl>[/<ns>][,...] [<feature>[,...]]\n\n"
2199 	    "  Print the specified features of the specified NVMe controllers "
2200 	    "and/or\n  namespaces. Feature support varies on the controller.\n"
2201 	    "Run 'nvmeadm list-features <ctl>' to see supported features.\n",
2202 	    c_name);
2203 }
2204 
2205 /*
2206  * The nvmeadm(8) get-features output has traditionally swallowed certain errors
2207  * for features that it considers unimplemented in tandem with the kernel. With
2208  * the introduction of libnvme and ioctl interface changes, the kernel no longer
2209  * caches information about features that are unimplemented.
2210  *
2211  * There are two cases that we currently swallow errors on and the following
2212  * must all be true:
2213  *
2214  * 1) We have a controller error.
2215  * 2) The system doesn't know whether the feature is implemented or not.
2216  * 3) The controller error indicates that we have an invalid field.
2217  *
2218  * There is one additional wrinkle that we are currently papering over due to
2219  * the history of nvmeadm swallowing errors. The error recovery feature was made
2220  * explicitly namespace-specific in NVMe 1.4. However, various NVMe 1.3 devices
2221  * will error if we ask for it without specifying a namespace. Conversely, older
2222  * devices will be upset if you do ask for a namespace. This case can be removed
2223  * once we better survey devices and come up with a heuristic for how to handle
2224  * this across older generations.
2225  *
2226  * If we add a single feature endpoint that gives flexibility over how the
2227  * feature are listed, then we should not swallow errors.
2228  */
2229 static boolean_t
2230 swallow_get_feat_err(const nvme_process_arg_t *npa,
2231     const nvme_feat_disc_t *disc)
2232 {
2233 	uint32_t sct, sc;
2234 
2235 	if (nvme_ctrl_err(npa->npa_ctrl) != NVME_ERR_CONTROLLER) {
2236 		return (B_FALSE);
2237 	}
2238 
2239 	nvme_ctrl_deverr(npa->npa_ctrl, &sct, &sc);
2240 	if (nvme_feat_disc_impl(disc) == NVME_FEAT_IMPL_UNKNOWN &&
2241 	    sct == NVME_CQE_SCT_GENERIC && sc == NVME_CQE_SC_GEN_INV_FLD) {
2242 		return (B_TRUE);
2243 	}
2244 
2245 	if (nvme_feat_disc_fid(disc) == NVME_FEAT_ERROR &&
2246 	    sct == NVME_CQE_SCT_GENERIC && (sc == NVME_CQE_SC_GEN_INV_FLD ||
2247 	    sc == NVME_CQE_SC_GEN_INV_NS)) {
2248 		return (B_TRUE);
2249 	}
2250 
2251 	return (B_FALSE);
2252 }
2253 
2254 static boolean_t
2255 do_get_feat_common(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc,
2256     uint32_t cdw11, uint32_t *cdw0, void **datap, size_t *lenp)
2257 {
2258 	nvme_get_feat_req_t *req = NULL;
2259 	void *data = NULL;
2260 	uint64_t datalen = 0;
2261 	nvme_get_feat_fields_t fields = nvme_feat_disc_fields_get(disc);
2262 
2263 	if (!nvme_get_feat_req_init_by_disc(npa->npa_ctrl, disc, &req)) {
2264 		nvmeadm_warn(npa, "failed to initialize get feature request "
2265 		    "for feature %s", nvme_feat_disc_short(disc));
2266 		exitcode = -1;
2267 		goto err;
2268 	}
2269 
2270 	if ((fields & NVME_GET_FEAT_F_CDW11) != 0 &&
2271 	    !nvme_get_feat_req_set_cdw11(req, cdw11)) {
2272 		nvmeadm_warn(npa, "failed to set cdw11 to 0x%x for feature %s",
2273 		    cdw11, nvme_feat_disc_short(disc));
2274 		exitcode = -1;
2275 		goto err;
2276 	}
2277 
2278 	if ((fields & NVME_GET_FEAT_F_DATA) != 0) {
2279 		datalen = nvme_feat_disc_data_size(disc);
2280 		VERIFY3U(datalen, !=, 0);
2281 		data = malloc(datalen);
2282 		if (data == NULL) {
2283 			err(-1, "failed to allocate %zu bytes for feature %s "
2284 			    "data buffer", datalen, nvme_feat_disc_short(disc));
2285 		}
2286 
2287 		if (!nvme_get_feat_req_set_output(req, data, datalen)) {
2288 			nvmeadm_warn(npa, "failed to set output data for "
2289 			    "feature %s", nvme_feat_disc_short(disc));
2290 			exitcode = -1;
2291 			goto err;
2292 		}
2293 	}
2294 
2295 	if ((fields & NVME_GET_FEAT_F_NSID) != 0) {
2296 		uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info);
2297 
2298 		if (!nvme_get_feat_req_set_nsid(req, nsid)) {
2299 			nvmeadm_warn(npa, "failed to set nsid to 0x%x for "
2300 			    "feature %s", nsid, nvme_feat_disc_spec(disc));
2301 			exitcode = -1;
2302 			goto err;
2303 		}
2304 	}
2305 
2306 	if (!nvme_get_feat_req_exec(req)) {
2307 		if (!swallow_get_feat_err(npa, disc)) {
2308 			nvmeadm_warn(npa, "failed to get feature %s",
2309 			    nvme_feat_disc_spec(disc));
2310 			exitcode = -1;
2311 		}
2312 
2313 		goto err;
2314 	}
2315 
2316 	if (!nvme_get_feat_req_get_cdw0(req, cdw0)) {
2317 		nvmeadm_warn(npa, "failed to get cdw0 result data for %s",
2318 		    nvme_feat_disc_spec(disc));
2319 		goto err;
2320 	}
2321 
2322 	*datap = data;
2323 	*lenp = datalen;
2324 	nvme_get_feat_req_fini(req);
2325 	return (B_TRUE);
2326 
2327 err:
2328 	free(data);
2329 	nvme_get_feat_req_fini(req);
2330 	return (B_FALSE);
2331 }
2332 
2333 static void
2334 do_get_feat_temp_thresh_one(const nvme_process_arg_t *npa,
2335     const nvme_feat_disc_t *disc, const nvmeadm_feature_t *feat,
2336     const char *label, uint16_t tmpsel, uint16_t thsel)
2337 {
2338 	uint32_t cdw0;
2339 	void *buf = NULL;
2340 	size_t buflen;
2341 	nvme_temp_threshold_t tt;
2342 
2343 	tt.r = 0;
2344 	tt.b.tt_tmpsel = tmpsel;
2345 	tt.b.tt_thsel = thsel;
2346 
2347 	/*
2348 	 * The printing function treats the buffer argument as the label to
2349 	 * print for this threshold.
2350 	 */
2351 	if (!do_get_feat_common(npa, disc, tt.r, &cdw0, &buf, &buflen)) {
2352 		return;
2353 	}
2354 
2355 	feat->f_print(cdw0, (void *)label, 0, npa->npa_idctl,
2356 	    npa->npa_version);
2357 	free(buf);
2358 }
2359 
2360 /*
2361  * In NVMe 1.2, the specification allowed for up to 8 sensors to be on the
2362  * device and changed the main device to have a composite temperature sensor. As
2363  * a result, there is a set of thresholds for each sensor. In addition, they
2364  * added both an over-temperature and under-temperature threshold. Since most
2365  * devices don't actually implement all the sensors, we get the health page and
2366  * see which sensors have a non-zero value to determine how to proceed.
2367  */
2368 static boolean_t
2369 do_get_feat_temp_thresh(const nvme_process_arg_t *npa,
2370     const nvme_feat_disc_t *disc, const nvmeadm_feature_t *feat)
2371 {
2372 	nvme_log_req_t *req = NULL;
2373 	nvme_log_disc_t *log_disc = NULL;
2374 	size_t toalloc;
2375 	void *buf = NULL;
2376 	boolean_t ret = B_FALSE;
2377 	const nvme_health_log_t *hlog;
2378 
2379 	nvme_print(2, nvme_feat_disc_spec(disc), -1, NULL);
2380 	do_get_feat_temp_thresh_one(npa, disc, feat,
2381 	    "Composite Over Temp. Threshold", 0, NVME_TEMP_THRESH_OVER);
2382 
2383 	if (!nvme_version_check(npa, &nvme_vers_1v2)) {
2384 		return (B_TRUE);
2385 	}
2386 
2387 	if (!nvme_log_req_init_by_name(npa->npa_ctrl, "health", 0, &log_disc,
2388 	    &req)) {
2389 		nvmeadm_warn(npa, "failed to initialize health log page "
2390 		    "request");
2391 		return (B_FALSE);
2392 	}
2393 
2394 	toalloc = do_get_logpage_size(npa, log_disc, req);
2395 	buf = malloc(toalloc);
2396 	if (buf == NULL) {
2397 		err(-1, "failed to allocate %zu bytes for health log page",
2398 		    toalloc);
2399 	}
2400 
2401 	if (!nvme_log_req_set_output(req, buf, toalloc)) {
2402 		nvmeadm_warn(npa, "failed to set output parameters for health "
2403 		    "log page");
2404 		goto out;
2405 	}
2406 
2407 	if (!nvme_log_req_exec(req)) {
2408 		nvmeadm_warn(npa, "failed to retrieve the health log page");
2409 		goto out;
2410 	}
2411 
2412 	/* cast required to prove our intentionality to smatch */
2413 	hlog = (const nvme_health_log_t *)buf;
2414 
2415 	do_get_feat_temp_thresh_one(npa, disc, feat,
2416 	    "Composite Under Temp. Threshold", 0, NVME_TEMP_THRESH_UNDER);
2417 	if (hlog->hl_temp_sensor_1 != 0) {
2418 		do_get_feat_temp_thresh_one(npa, disc, feat,
2419 		    "Temp. Sensor 1 Over Temp. Threshold", 1,
2420 		    NVME_TEMP_THRESH_OVER);
2421 		do_get_feat_temp_thresh_one(npa, disc, feat,
2422 		    "Temp. Sensor 1 Under Temp. Threshold", 1,
2423 		    NVME_TEMP_THRESH_UNDER);
2424 	}
2425 
2426 	if (hlog->hl_temp_sensor_2 != 0) {
2427 		do_get_feat_temp_thresh_one(npa, disc, feat,
2428 		    "Temp. Sensor 2 Over Temp. Threshold", 2,
2429 		    NVME_TEMP_THRESH_OVER);
2430 		do_get_feat_temp_thresh_one(npa, disc, feat,
2431 		    "Temp. Sensor 2 Under Temp. Threshold", 2,
2432 		    NVME_TEMP_THRESH_UNDER);
2433 	}
2434 
2435 	if (hlog->hl_temp_sensor_3 != 0) {
2436 		do_get_feat_temp_thresh_one(npa, disc, feat,
2437 		    "Temp. Sensor 3 Over Temp. Threshold", 3,
2438 		    NVME_TEMP_THRESH_OVER);
2439 		do_get_feat_temp_thresh_one(npa, disc, feat,
2440 		    "Temp. Sensor 3 Under Temp. Threshold", 3,
2441 		    NVME_TEMP_THRESH_UNDER);
2442 	}
2443 
2444 	if (hlog->hl_temp_sensor_4 != 0) {
2445 		do_get_feat_temp_thresh_one(npa, disc, feat,
2446 		    "Temp. Sensor 4 Over Temp. Threshold", 4,
2447 		    NVME_TEMP_THRESH_OVER);
2448 		do_get_feat_temp_thresh_one(npa, disc, feat,
2449 		    "Temp. Sensor 4 Under Temp. Threshold", 4,
2450 		    NVME_TEMP_THRESH_UNDER);
2451 	}
2452 
2453 	if (hlog->hl_temp_sensor_5 != 0) {
2454 		do_get_feat_temp_thresh_one(npa, disc, feat,
2455 		    "Temp. Sensor 5 Over Temp. Threshold", 5,
2456 		    NVME_TEMP_THRESH_OVER);
2457 		do_get_feat_temp_thresh_one(npa, disc, feat,
2458 		    "Temp. Sensor 5 Under Temp. Threshold", 5,
2459 		    NVME_TEMP_THRESH_UNDER);
2460 	}
2461 
2462 	if (hlog->hl_temp_sensor_6 != 0) {
2463 		do_get_feat_temp_thresh_one(npa, disc, feat,
2464 		    "Temp. Sensor 6 Over Temp. Threshold", 6,
2465 		    NVME_TEMP_THRESH_OVER);
2466 		do_get_feat_temp_thresh_one(npa, disc, feat,
2467 		    "Temp. Sensor 6 Under Temp. Threshold", 6,
2468 		    NVME_TEMP_THRESH_UNDER);
2469 	}
2470 
2471 	if (hlog->hl_temp_sensor_7 != 0) {
2472 		do_get_feat_temp_thresh_one(npa, disc, feat,
2473 		    "Temp. Sensor 7 Over Temp. Threshold", 7,
2474 		    NVME_TEMP_THRESH_OVER);
2475 		do_get_feat_temp_thresh_one(npa, disc, feat,
2476 		    "Temp. Sensor 7 Under Temp. Threshold", 7,
2477 		    NVME_TEMP_THRESH_UNDER);
2478 	}
2479 
2480 	if (hlog->hl_temp_sensor_8 != 0) {
2481 		do_get_feat_temp_thresh_one(npa, disc, feat,
2482 		    "Temp. Sensor 8 Over Temp. Threshold", 8,
2483 		    NVME_TEMP_THRESH_OVER);
2484 		do_get_feat_temp_thresh_one(npa, disc, feat,
2485 		    "Temp. Sensor 8 Under Temp. Threshold", 8,
2486 		    NVME_TEMP_THRESH_UNDER);
2487 	}
2488 
2489 	ret = B_TRUE;
2490 out:
2491 	nvme_log_req_fini(req);
2492 	free(buf);
2493 	return (ret);
2494 }
2495 
2496 static boolean_t
2497 do_get_feat_intr_vect(const nvme_process_arg_t *npa,
2498     const nvme_feat_disc_t *disc, const nvmeadm_feature_t *feat)
2499 {
2500 	uint32_t nintrs;
2501 	boolean_t ret = B_TRUE;
2502 
2503 	if (!nvme_ctrl_info_pci_nintrs(npa->npa_ctrl_info, &nintrs)) {
2504 		nvmeadm_ctrl_info_warn(npa, "failed to get interrupt count "
2505 		    "from controller %s information snapshot", npa->npa_name);
2506 		return (B_FALSE);
2507 	}
2508 
2509 	nvme_print(2, nvme_feat_disc_spec(disc), -1, NULL);
2510 	for (uint32_t i = 0; i < nintrs; i++) {
2511 		uint32_t cdw0;
2512 		void *buf;
2513 		size_t buflen;
2514 		nvme_intr_vect_t vect;
2515 
2516 		vect.r = 0;
2517 		vect.b.iv_iv = i;
2518 
2519 		if (!do_get_feat_common(npa, disc, vect.r, &cdw0, &buf,
2520 		    &buflen)) {
2521 			ret = B_FALSE;
2522 			continue;
2523 		}
2524 
2525 		feat->f_print(cdw0, buf, buflen, npa->npa_idctl,
2526 		    npa->npa_version);
2527 		free(buf);
2528 	}
2529 
2530 	return (ret);
2531 }
2532 
2533 /*
2534  * We've been asked to print the following feature that the controller probably
2535  * supports. Find our internal feature information for this to see if we know
2536  * how to deal with it.
2537  */
2538 static void
2539 do_get_features_cb(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc)
2540 {
2541 	const nvmeadm_feature_t *feat = NULL;
2542 	uint32_t fid = nvme_feat_disc_fid(disc);
2543 	nvme_get_feat_fields_t fields;
2544 	void *data = NULL;
2545 	size_t datalen = 0;
2546 	uint32_t cdw0;
2547 
2548 	for (size_t i = 0; i < ARRAY_SIZE(features); i++) {
2549 		if (features[i].f_feature == fid) {
2550 			feat = &features[i];
2551 			break;
2552 		}
2553 	}
2554 
2555 	/*
2556 	 * Determine if we have enough logic in here to get and print the
2557 	 * feature. The vast majority of NVMe features only output a single
2558 	 * uint32_t in cdw0 and potentially a data buffer. As long as no input
2559 	 * arguments are required, then we can go ahead and get this and print
2560 	 * the data. If there is, then we will refuse unless we have a
2561 	 * particular function. If we have a specific get function, we expect it
2562 	 * to do all the printing.
2563 	 */
2564 	if (feat != NULL && feat->f_get != NULL) {
2565 		if (!feat->f_get(npa, disc, feat)) {
2566 			exitcode = -1;
2567 		}
2568 		return;
2569 	}
2570 
2571 	fields = nvme_feat_disc_fields_get(disc);
2572 	if ((fields & NVME_GET_FEAT_F_CDW11) != 0) {
2573 		warnx("unable to get feature %s due to missing nvmeadm(8) "
2574 		    "implementation logic", nvme_feat_disc_spec(disc));
2575 		exitcode = -1;
2576 		return;
2577 	}
2578 
2579 	/*
2580 	 * We do not set exitcode on failure here so that way we can swallow
2581 	 * errors from unimplemented features.
2582 	 */
2583 	if (!do_get_feat_common(npa, disc, 0, &cdw0, &data, &datalen)) {
2584 		return;
2585 	}
2586 
2587 	nvme_print(2, nvme_feat_disc_spec(disc), -1, NULL);
2588 	if (feat != NULL && feat->f_print != NULL) {
2589 		feat->f_print(cdw0, data, datalen, npa->npa_idctl,
2590 		    npa->npa_version);
2591 	} else {
2592 		nvme_feat_output_t output = nvme_feat_disc_output_get(disc);
2593 		nvme_print_feat_unknown(output, cdw0, data, datalen);
2594 	}
2595 
2596 	free(data);
2597 }
2598 
2599 /*
2600  * This is an entry point which prints every feature that we know about. We
2601  * often go to lengths to discover all the variable inputs that can be used for
2602  * a given feature that requires an argument in cdw11. Due to the semantics of
2603  * filtering being used for features and the need to print each feature, this is
2604  * not the place to add general field filtering or a means to request a specific
2605  * cdw11 argument or similar. Instead, a new get-feature which requires someone
2606  * to specify the short name for a feature and then allows particular fields to
2607  * be grabbed and arguments should be created instead.
2608  *
2609  * This uses the same general feature logic that underpins do_list_features()
2610  * and therefore we transform filter arguments into the same style used there.
2611  */
2612 static int
2613 do_get_features(const nvme_process_arg_t *npa)
2614 {
2615 	char *fstr = NULL;
2616 	char **filts = NULL;
2617 	boolean_t *used = NULL;
2618 	nvmeadm_features_t nf;
2619 	int ret;
2620 
2621 	if (npa->npa_argc > 1)
2622 		errx(-1, "unexpected arguments");
2623 
2624 	if (npa->npa_ns != NULL && nvme_ns_info_level(npa->npa_ns_info) <
2625 	    NVME_NS_DISC_F_ACTIVE) {
2626 		errx(-1, "cannot get feature: namespace is inactive");
2627 	}
2628 
2629 	/*
2630 	 * We always leave nf_unimpl set to false as we don't want to bother
2631 	 * trying to print a feature that we know the device doesn't support.
2632 	 */
2633 	(void) memset(&nf, 0, sizeof (nvmeadm_features_t));
2634 
2635 	/*
2636 	 * If we've been given a series of features to print, treat those as
2637 	 * filters on the features as we're walking them to determine which to
2638 	 * print or not.
2639 	 */
2640 	if (npa->npa_argc == 1) {
2641 		char *f;
2642 		uint32_t i;
2643 
2644 		nf.nf_nfilts = 1;
2645 		fstr = strdup(npa->npa_argv[0]);
2646 
2647 		if (fstr == NULL) {
2648 			err(-1, "failed to allocate memory to duplicate "
2649 			    "feature string");
2650 		}
2651 
2652 		for (const char *c = strchr(fstr, ','); c != NULL;
2653 		    c = strchr(c + 1, ',')) {
2654 			nf.nf_nfilts++;
2655 		}
2656 
2657 		filts = calloc(nf.nf_nfilts, sizeof (char *));
2658 		if (filts == NULL) {
2659 			err(-1, "failed to allocate memory for filter list");
2660 		}
2661 
2662 		i = 0;
2663 		while ((f = strsep(&fstr, ",")) != NULL) {
2664 			filts[i] = f;
2665 			i++;
2666 		}
2667 		VERIFY3U(i, ==, nf.nf_nfilts);
2668 		nf.nf_filts = filts;
2669 
2670 		used = calloc(nf.nf_nfilts, sizeof (boolean_t));
2671 		if (used == NULL) {
2672 			err(-1, "failed to allocate memory for filter use "
2673 			    "tracking");
2674 		}
2675 		nf.nf_used = used;
2676 	}
2677 
2678 	(void) printf("%s: Get Features\n", npa->npa_name);
2679 	ret = do_features(npa, &nf, do_get_features_cb);
2680 
2681 	free(fstr);
2682 	free(filts);
2683 	free(used);
2684 	return (ret);
2685 }
2686 
2687 static int
2688 do_format_common(const nvme_process_arg_t *npa, uint32_t lbaf,
2689     uint32_t ses)
2690 {
2691 	int ret = 0;
2692 	nvme_format_req_t *req;
2693 
2694 	if (npa->npa_ns != NULL && nvme_ns_info_level(npa->npa_ns_info) <
2695 	    NVME_NS_DISC_F_ACTIVE) {
2696 		errx(-1, "cannot %s: namespace is inactive",
2697 		    npa->npa_cmd->c_name);
2698 	}
2699 
2700 	if (!nvme_format_req_init(npa->npa_ctrl, &req)) {
2701 		nvmeadm_fatal(npa, "failed to initialize format request for "
2702 		    "%s", npa->npa_name);
2703 	}
2704 
2705 	if (npa->npa_ns != NULL) {
2706 		uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info);
2707 
2708 		if (!nvme_format_req_set_nsid(req, nsid)) {
2709 			nvmeadm_fatal(npa, "failed to set format request "
2710 			    "namespace ID to 0x%x", nsid);
2711 		}
2712 	}
2713 
2714 	if (!nvme_format_req_set_lbaf(req, lbaf) ||
2715 	    !nvme_format_req_set_ses(req, ses)) {
2716 		nvmeadm_fatal(npa, "failed to set format request fields for %s",
2717 		    npa->npa_name);
2718 	}
2719 
2720 	if (do_detach(npa) != 0) {
2721 		errx(-1, "cannot %s %s due to namespace detach failure",
2722 		    npa->npa_cmd->c_name, npa->npa_name);
2723 	}
2724 
2725 	if (!nvme_format_req_exec(req)) {
2726 		nvmeadm_warn(npa, "failed to %s %s", npa->npa_cmd->c_name,
2727 		    npa->npa_name);
2728 		ret = -1;
2729 	}
2730 
2731 	if (do_attach(npa) != 0)
2732 		ret = -1;
2733 
2734 	return (ret);
2735 }
2736 
2737 static void
2738 usage_format(const char *c_name)
2739 {
2740 	(void) fprintf(stderr, "%s <ctl>[/<ns>] [<lba-format>]\n\n"
2741 	    "  Format one or all namespaces of the specified NVMe "
2742 	    "controller. Supported LBA\n  formats can be queried with "
2743 	    "the \"%s identify\" command on the namespace\n  to be "
2744 	    "formatted.\n", c_name, getprogname());
2745 }
2746 
2747 static uint32_t
2748 do_format_determine_lbaf(const nvme_process_arg_t *npa)
2749 {
2750 	const nvme_nvm_lba_fmt_t *fmt;
2751 	nvme_ns_info_t *ns_info = NULL;
2752 	uint32_t lbaf;
2753 
2754 	if (npa->npa_argc > 0) {
2755 		unsigned long lba;
2756 		uint32_t nlbaf = nvme_ctrl_info_nformats(npa->npa_ctrl_info);
2757 
2758 		errno = 0;
2759 		lba = strtoul(npa->npa_argv[0], NULL, 10);
2760 		if (errno != 0 || lba >= nlbaf)
2761 			errx(-1, "invalid LBA format %s", npa->npa_argv[0]);
2762 
2763 		if (!nvme_ctrl_info_format(npa->npa_ctrl_info, (uint32_t)lba,
2764 		    &fmt)) {
2765 			nvmeadm_fatal(npa, "failed to get LBA format %lu "
2766 			    "information", lba);
2767 		}
2768 	} else {
2769 		/*
2770 		 * If we have a namespace then we use the current namespace's
2771 		 * LBA format. If we don't have a namespace, then we promised
2772 		 * we'd look at namespace 1 in the manual page.
2773 		 */
2774 		if (npa->npa_ns_info == NULL) {
2775 			if (!nvme_ctrl_ns_info_snap(npa->npa_ctrl, 1,
2776 			    &ns_info)) {
2777 				nvmeadm_fatal(npa, "failed to get namespace 1 "
2778 				    "information, please explicitly specify an "
2779 				    "LBA format");
2780 			}
2781 
2782 			if (!nvme_ns_info_curformat(ns_info, &fmt)) {
2783 				nvmeadm_fatal(npa, "failed to retrieve current "
2784 				    "namespace format from namespace 1");
2785 			}
2786 		} else {
2787 			if (!nvme_ns_info_curformat(npa->npa_ns_info, &fmt)) {
2788 				nvmeadm_fatal(npa, "failed to get the current "
2789 				    "format information from %s",
2790 				    npa->npa_name);
2791 			}
2792 		}
2793 	}
2794 
2795 	if (nvme_nvm_lba_fmt_meta_size(fmt) != 0) {
2796 		errx(-1, "LBA formats with metadata are not supported");
2797 	}
2798 
2799 	lbaf = nvme_nvm_lba_fmt_id(fmt);
2800 	nvme_ns_info_free(ns_info);
2801 	return (lbaf);
2802 }
2803 
2804 static int
2805 do_format(const nvme_process_arg_t *npa)
2806 {
2807 	uint32_t lbaf;
2808 
2809 	if (npa->npa_argc > 1) {
2810 		errx(-1, "%s passed extraneous arguments starting with %s",
2811 		    npa->npa_cmd->c_name, npa->npa_argv[1]);
2812 	}
2813 
2814 	lbaf = do_format_determine_lbaf(npa);
2815 	return (do_format_common(npa, lbaf, 0));
2816 }
2817 
2818 static void
2819 usage_secure_erase(const char *c_name)
2820 {
2821 	(void) fprintf(stderr, "%s [-c] <ctl>[/<ns>]\n\n"
2822 	    "  Secure-Erase one or all namespaces of the specified "
2823 	    "NVMe controller.\n", c_name);
2824 }
2825 
2826 static void
2827 optparse_secure_erase(nvme_process_arg_t *npa)
2828 {
2829 	int c;
2830 
2831 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":c")) != -1) {
2832 		switch (c) {
2833 		case 'c':
2834 			npa->npa_cmdflags |= NVMEADM_O_SE_CRYPTO;
2835 			break;
2836 
2837 		case '?':
2838 			errx(-1, "unknown option: -%c", optopt);
2839 
2840 		case ':':
2841 			errx(-1, "option -%c requires an argument", optopt);
2842 
2843 		}
2844 	}
2845 }
2846 
2847 static int
2848 do_secure_erase(const nvme_process_arg_t *npa)
2849 {
2850 	unsigned long lbaf;
2851 	uint8_t ses = NVME_FRMT_SES_USER;
2852 
2853 	if (npa->npa_argc > 0) {
2854 		errx(-1, "%s passed extraneous arguments starting with %s",
2855 		    npa->npa_cmd->c_name, npa->npa_argv[0]);
2856 	}
2857 
2858 	if ((npa->npa_cmdflags & NVMEADM_O_SE_CRYPTO) != 0)
2859 		ses = NVME_FRMT_SES_CRYPTO;
2860 
2861 	lbaf = do_format_determine_lbaf(npa);
2862 	return (do_format_common(npa, lbaf, ses));
2863 }
2864 
2865 static void
2866 usage_attach_detach(const char *c_name)
2867 {
2868 	(void) fprintf(stderr, "%s <ctl>[/<ns>]\n\n"
2869 	    "  %c%s blkdev(4D) %s one or all namespaces of the "
2870 	    "specified NVMe controller.\n",
2871 	    c_name, toupper(c_name[0]), &c_name[1],
2872 	    c_name[0] == 'd' ? "from" : "to");
2873 }
2874 
2875 static int
2876 do_attach(const nvme_process_arg_t *npa)
2877 {
2878 	int rv;
2879 	nvme_ns_iter_t *iter = NULL;
2880 	nvme_iter_t ret;
2881 	const nvme_ns_disc_t *disc;
2882 
2883 	if (npa->npa_ns != NULL) {
2884 		if (!nvme_ns_bd_attach(npa->npa_ns)) {
2885 			nvmeadm_warn(npa, "faild to attach %s", npa->npa_name);
2886 			return (-1);
2887 		}
2888 		return (0);
2889 	}
2890 
2891 	if (!nvme_ns_discover_init(npa->npa_ctrl, NVME_NS_DISC_F_NOT_IGNORED,
2892 	    &iter))  {
2893 		nvmeadm_fatal(npa, "failed to initialize namespace discovery "
2894 		    "on %s", npa->npa_name);
2895 	}
2896 
2897 	rv = 0;
2898 	while ((ret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) {
2899 		nvme_ns_t *ns;
2900 		uint32_t nsid;
2901 
2902 		if (nvme_ns_disc_level(disc) == NVME_NS_DISC_F_BLKDEV)
2903 			continue;
2904 
2905 		nsid = nvme_ns_disc_nsid(disc);
2906 		if (!nvme_ns_init(npa->npa_ctrl, nsid, &ns)) {
2907 			nvmeadm_warn(npa, "failed to open namespace %s/%u "
2908 			    "handle", npa->npa_name, nsid);
2909 			rv = -1;
2910 			continue;
2911 		}
2912 
2913 		if (!nvme_ns_bd_attach(ns)) {
2914 			nvmeadm_warn(npa, "failed to attach namespace "
2915 			    "%s/%u", npa->npa_name, nsid);
2916 			rv = -1;
2917 		}
2918 		nvme_ns_fini(ns);
2919 	}
2920 
2921 	nvme_ns_discover_fini(iter);
2922 	if (ret == NVME_ITER_ERROR) {
2923 		nvmeadm_warn(npa, "failed to iterate namespaces on %s",
2924 		    npa->npa_name);
2925 		rv = -1;
2926 	}
2927 
2928 	return (rv);
2929 }
2930 
2931 static int
2932 do_detach(const nvme_process_arg_t *npa)
2933 {
2934 	int rv;
2935 	nvme_ns_iter_t *iter = NULL;
2936 	nvme_iter_t ret;
2937 	const nvme_ns_disc_t *disc;
2938 
2939 	if (npa->npa_ns != NULL) {
2940 		if (!nvme_ns_bd_detach(npa->npa_ns)) {
2941 			nvmeadm_warn(npa, "failed to detach %s", npa->npa_name);
2942 			return (-1);
2943 		}
2944 		return (0);
2945 	}
2946 
2947 	if (!nvme_ns_discover_init(npa->npa_ctrl, NVME_NS_DISC_F_BLKDEV,
2948 	    &iter))  {
2949 		nvmeadm_fatal(npa, "failed to initialize namespace discovery "
2950 		    "on %s", npa->npa_name);
2951 	}
2952 
2953 	rv = 0;
2954 	while ((ret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) {
2955 		nvme_ns_t *ns;
2956 		uint32_t nsid = nvme_ns_disc_nsid(disc);
2957 
2958 		if (!nvme_ns_init(npa->npa_ctrl, nsid, &ns)) {
2959 			nvmeadm_warn(npa, "failed to open namespace %s/%u "
2960 			    "handle", npa->npa_name, nsid);
2961 			rv = -1;
2962 			continue;
2963 		}
2964 
2965 		if (!nvme_ns_bd_detach(ns)) {
2966 			nvmeadm_warn(npa, "failed to detach namespace "
2967 			    "%s/%u", npa->npa_name, nsid);
2968 			rv = -1;
2969 		}
2970 		nvme_ns_fini(ns);
2971 	}
2972 
2973 	nvme_ns_discover_fini(iter);
2974 	if (ret == NVME_ITER_ERROR) {
2975 		nvmeadm_warn(npa, "failed to iterate namespaces on %s",
2976 		    npa->npa_name);
2977 		rv = -1;
2978 	}
2979 
2980 	return (rv);
2981 }
2982 
2983 static void
2984 usage_firmware_load(const char *c_name)
2985 {
2986 	(void) fprintf(stderr, "%s <ctl> <image file> [<offset>]\n\n"
2987 	    "  Load firmware <image file> to offset <offset>.\n"
2988 	    "  The firmware needs to be committed to a slot using "
2989 	    "\"nvmeadm commit-firmware\"\n  command.\n", c_name);
2990 }
2991 
2992 /*
2993  * Read exactly len bytes, or until eof.
2994  */
2995 static size_t
2996 read_block(const nvme_process_arg_t *npa, int fd, char *buf, size_t len)
2997 {
2998 	size_t remain;
2999 
3000 	remain = len;
3001 	while (remain > 0) {
3002 		ssize_t bytes = read(fd, buf, remain);
3003 		if (bytes == 0)
3004 			break;
3005 
3006 		if (bytes < 0) {
3007 			if (errno == EINTR)
3008 				continue;
3009 
3010 			err(-1, "Error reading \"%s\"", npa->npa_argv[0]);
3011 		}
3012 
3013 		buf += (size_t)bytes;
3014 		remain -= (size_t)bytes;
3015 	}
3016 
3017 	return (len - remain);
3018 }
3019 
3020 /*
3021  * Convert a string to a valid firmware upload offset (in bytes).
3022  */
3023 static uint64_t
3024 get_fw_offsetb(char *str)
3025 {
3026 	longlong_t offsetb;
3027 	char *valend;
3028 
3029 	errno = 0;
3030 	offsetb = strtoll(str, &valend, 0);
3031 	if (errno != 0 || *valend != '\0' || offsetb < 0 ||
3032 	    offsetb > NVME_FW_OFFSETB_MAX)
3033 		errx(-1, "Offset must be numeric and in the range of 0 to %llu",
3034 		    NVME_FW_OFFSETB_MAX);
3035 
3036 	if ((offsetb & NVME_DWORD_MASK) != 0)
3037 		errx(-1, "Offset must be multiple of %d", NVME_DWORD_SIZE);
3038 
3039 	return ((uint64_t)offsetb);
3040 }
3041 
3042 #define	FIRMWARE_READ_BLKSIZE	(64 * 1024)		/* 64K */
3043 
3044 static int
3045 do_firmware_load(const nvme_process_arg_t *npa)
3046 {
3047 	int fw_fd;
3048 	uint64_t offset = 0;
3049 	size_t size, len;
3050 	char buf[FIRMWARE_READ_BLKSIZE];
3051 
3052 	if (npa->npa_argc > 2)
3053 		errx(-1, "%s passed extraneous arguments starting with %s",
3054 		    npa->npa_cmd->c_name, npa->npa_argv[2]);
3055 
3056 	if (npa->npa_argc == 0)
3057 		errx(-1, "Requires firmware file name, and an "
3058 		    "optional offset");
3059 
3060 	if (npa->npa_ns != NULL)
3061 		errx(-1, "Firmware loading not available on a per-namespace "
3062 		    "basis");
3063 
3064 	if (npa->npa_argc == 2)
3065 		offset = get_fw_offsetb(npa->npa_argv[1]);
3066 
3067 	fw_fd = open(npa->npa_argv[0], O_RDONLY);
3068 	if (fw_fd < 0)
3069 		errx(-1, "Failed to open \"%s\": %s", npa->npa_argv[0],
3070 		    strerror(errno));
3071 
3072 	size = 0;
3073 	do {
3074 		len = read_block(npa, fw_fd, buf, sizeof (buf));
3075 
3076 		if (len == 0)
3077 			break;
3078 
3079 		if (!nvme_fw_load(npa->npa_ctrl, buf, len, offset)) {
3080 			nvmeadm_fatal(npa, "failed to load firmware image "
3081 			    "\"%s\" at offset %" PRIu64, npa->npa_argv[0],
3082 			    offset);
3083 		}
3084 
3085 		offset += len;
3086 		size += len;
3087 	} while (len == sizeof (buf));
3088 
3089 	(void) close(fw_fd);
3090 
3091 	if (verbose)
3092 		(void) printf("%zu bytes downloaded.\n", size);
3093 
3094 	return (0);
3095 }
3096 
3097 /*
3098  * Common firmware commit for nvmeadm commit-firmware and activate-firmware.
3099  */
3100 static void
3101 nvmeadm_firmware_commit(const nvme_process_arg_t *npa, uint32_t slot,
3102     uint32_t act)
3103 {
3104 	nvme_fw_commit_req_t *req;
3105 
3106 	if (!nvme_fw_commit_req_init(npa->npa_ctrl, &req)) {
3107 		nvmeadm_fatal(npa, "failed to initialize firmware commit "
3108 		    "request for %s", npa->npa_name);
3109 	}
3110 
3111 	if (!nvme_fw_commit_req_set_slot(req, slot) ||
3112 	    !nvme_fw_commit_req_set_action(req, act)) {
3113 		nvmeadm_fatal(npa, "failed to set firmware commit fields for "
3114 		    "%s", npa->npa_name);
3115 	}
3116 
3117 	if (!nvme_fw_commit_req_exec(req)) {
3118 		nvmeadm_fatal(npa, "failed to %s firmware on %s",
3119 		    npa->npa_cmd->c_name, npa->npa_name);
3120 	}
3121 
3122 	nvme_fw_commit_req_fini(req);
3123 }
3124 
3125 /*
3126  * Convert str to a valid firmware slot number.
3127  */
3128 static uint32_t
3129 get_slot_number(char *str)
3130 {
3131 	longlong_t slot;
3132 	char *valend;
3133 
3134 	errno = 0;
3135 	slot = strtoll(str, &valend, 0);
3136 	if (errno != 0 || *valend != '\0' ||
3137 	    slot < NVME_FW_SLOT_MIN || slot > NVME_FW_SLOT_MAX)
3138 		errx(-1, "Slot must be numeric and in the range of %u to %u",
3139 		    NVME_FW_SLOT_MIN, NVME_FW_SLOT_MAX);
3140 
3141 	return ((uint32_t)slot);
3142 }
3143 
3144 static void
3145 usage_firmware_commit(const char *c_name)
3146 {
3147 	(void) fprintf(stderr, "%s <ctl> <slot>\n\n"
3148 	    "  Commit previously downloaded firmware to slot <slot>.\n"
3149 	    "  The firmware is only activated after a "
3150 	    "\"nvmeadm activate-firmware\" command.\n", c_name);
3151 }
3152 
3153 static int
3154 do_firmware_commit(const nvme_process_arg_t *npa)
3155 {
3156 	uint32_t slot;
3157 
3158 	if (npa->npa_argc > 1)
3159 		errx(-1, "%s passed extraneous arguments starting with %s",
3160 		    npa->npa_cmd->c_name, npa->npa_argv[1]);
3161 
3162 	if (npa->npa_argc == 0)
3163 		errx(-1, "Firmware slot number is required");
3164 
3165 	if (npa->npa_ns != NULL)
3166 		errx(-1, "Firmware committing not available on a per-namespace "
3167 		    "basis");
3168 
3169 	slot = get_slot_number(npa->npa_argv[0]);
3170 
3171 	if (slot == 1 && npa->npa_idctl->id_frmw.fw_readonly)
3172 		errx(-1, "Cannot commit firmware to slot 1: slot is read-only");
3173 
3174 	nvmeadm_firmware_commit(npa, slot, NVME_FWC_SAVE);
3175 
3176 	if (verbose)
3177 		(void) printf("Firmware committed to slot %u.\n", slot);
3178 
3179 	return (0);
3180 }
3181 
3182 static void
3183 usage_firmware_activate(const char *c_name)
3184 {
3185 	(void) fprintf(stderr, "%s <ctl> <slot>\n\n"
3186 	    "  Activate firmware in slot <slot>.\n"
3187 	    "  The firmware will be in use after the next system reset.\n",
3188 	    c_name);
3189 }
3190 
3191 static int
3192 do_firmware_activate(const nvme_process_arg_t *npa)
3193 {
3194 	uint32_t slot;
3195 
3196 	if (npa->npa_argc > 1)
3197 		errx(-1, "%s passed extraneous arguments starting with %s",
3198 		    npa->npa_cmd->c_name, npa->npa_argv[1]);
3199 
3200 	if (npa->npa_argc == 0)
3201 		errx(-1, "Firmware slot number is required");
3202 
3203 	if (npa->npa_ns != NULL)
3204 		errx(-1, "Firmware activation not available on a per-namespace "
3205 		    "basis");
3206 
3207 	slot = get_slot_number(npa->npa_argv[0]);
3208 
3209 	nvmeadm_firmware_commit(npa, slot, NVME_FWC_ACTIVATE);
3210 
3211 	if (verbose)
3212 		(void) printf("Slot %u successfully activated.\n", slot);
3213 
3214 	return (0);
3215 }
3216 
3217 nvme_vuc_disc_t *
3218 nvmeadm_vuc_init(const nvme_process_arg_t *npa, const char *name)
3219 {
3220 	nvme_vuc_disc_t *vuc;
3221 	nvme_vuc_disc_lock_t lock;
3222 
3223 	if (!nvme_vuc_discover_by_name(npa->npa_ctrl, name, 0, &vuc)) {
3224 		nvmeadm_fatal(npa, "%s does not support operation %s: device "
3225 		    "does not support vendor unique command %s", npa->npa_name,
3226 		    npa->npa_cmd->c_name, name);
3227 	}
3228 
3229 	lock = nvme_vuc_disc_lock(vuc);
3230 	switch (lock) {
3231 	case NVME_VUC_DISC_LOCK_NONE:
3232 		break;
3233 	case NVME_VUC_DISC_LOCK_READ:
3234 		nvmeadm_excl(npa, NVME_LOCK_L_READ);
3235 		break;
3236 	case NVME_VUC_DISC_LOCK_WRITE:
3237 		nvmeadm_excl(npa, NVME_LOCK_L_WRITE);
3238 		break;
3239 	}
3240 
3241 	return (vuc);
3242 }
3243 
3244 void
3245 nvmeadm_vuc_fini(const nvme_process_arg_t *npa, nvme_vuc_disc_t *vuc)
3246 {
3247 	if (nvme_vuc_disc_lock(vuc) != NVME_VUC_DISC_LOCK_NONE) {
3248 		if (npa->npa_ns != NULL) {
3249 			nvme_ns_unlock(npa->npa_ns);
3250 		} else if (npa->npa_ctrl != NULL) {
3251 			nvme_ctrl_unlock(npa->npa_ctrl);
3252 		}
3253 	}
3254 
3255 	nvme_vuc_disc_free(vuc);
3256 }
3257