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