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