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