xref: /illumos-gate/usr/src/cmd/nvmeadm/nvmeadm.c (revision 564eeb7d65973f1d64a5390ff477b689707e9daf)
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 2026 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 #include <sys/stat.h>
49 #include <sys/mman.h>
50 #include <sys/fm/protocol.h>
51 #include <fm/topo_hc.h>
52 #include <getopt.h>
53 
54 #include <sys/nvme.h>
55 
56 #include "nvmeadm.h"
57 
58 /*
59  * Assertions to make sure that we've properly captured various aspects of the
60  * packed structures and haven't broken them during updates.
61  */
62 CTASSERT(sizeof (nvme_identify_ctrl_t) == NVME_IDENTIFY_BUFSIZE);
63 CTASSERT(offsetof(nvme_identify_ctrl_t, id_oacs) == 256);
64 CTASSERT(offsetof(nvme_identify_ctrl_t, id_sqes) == 512);
65 CTASSERT(offsetof(nvme_identify_ctrl_t, id_oncs) == 520);
66 CTASSERT(offsetof(nvme_identify_ctrl_t, id_subnqn) == 768);
67 CTASSERT(offsetof(nvme_identify_ctrl_t, id_nvmof) == 1792);
68 CTASSERT(offsetof(nvme_identify_ctrl_t, id_psd) == 2048);
69 CTASSERT(offsetof(nvme_identify_ctrl_t, id_vs) == 3072);
70 
71 CTASSERT(sizeof (nvme_identify_nsid_t) == NVME_IDENTIFY_BUFSIZE);
72 CTASSERT(offsetof(nvme_identify_nsid_t, id_fpi) == 32);
73 CTASSERT(offsetof(nvme_identify_nsid_t, id_anagrpid) == 92);
74 CTASSERT(offsetof(nvme_identify_nsid_t, id_nguid) == 104);
75 CTASSERT(offsetof(nvme_identify_nsid_t, id_lbaf) == 128);
76 CTASSERT(offsetof(nvme_identify_nsid_t, id_vs) == 384);
77 
78 CTASSERT(sizeof (nvme_identify_nsid_list_t) == NVME_IDENTIFY_BUFSIZE);
79 CTASSERT(sizeof (nvme_identify_ctrl_list_t) == NVME_IDENTIFY_BUFSIZE);
80 
81 CTASSERT(sizeof (nvme_identify_primary_caps_t) == NVME_IDENTIFY_BUFSIZE);
82 CTASSERT(offsetof(nvme_identify_primary_caps_t, nipc_vqfrt) == 32);
83 CTASSERT(offsetof(nvme_identify_primary_caps_t, nipc_vifrt) == 64);
84 
85 CTASSERT(sizeof (nvme_nschange_list_t) == 4096);
86 
87 static void usage(const nvmeadm_cmd_t *);
88 static bool nvmeadm_ctrl_disc_cb(nvme_t *, const nvme_ctrl_disc_t *, void *);
89 
90 static int do_list(const nvme_process_arg_t *);
91 static int do_identify(const nvme_process_arg_t *);
92 static int do_identify_ctrl(const nvme_process_arg_t *);
93 static int do_identify_ns(const nvme_process_arg_t *);
94 static int do_list_logs(const nvme_process_arg_t *);
95 static int do_get_logpage_fwslot(const nvme_process_arg_t *);
96 static int do_get_logpage(const nvme_process_arg_t *);
97 static int do_print_logpage(const nvme_process_arg_t *);
98 static int do_list_features(const nvme_process_arg_t *);
99 static boolean_t do_get_feat_intr_vect(const nvme_process_arg_t *,
100     const nvme_feat_disc_t *, const nvmeadm_feature_t *);
101 static boolean_t do_get_feat_temp_thresh(const nvme_process_arg_t *,
102     const nvme_feat_disc_t *, const nvmeadm_feature_t *);
103 static int do_get_features(const nvme_process_arg_t *);
104 static int do_set_feature(const nvme_process_arg_t *);
105 static int do_format(const nvme_process_arg_t *);
106 static int do_secure_erase(const nvme_process_arg_t *);
107 static int do_attach_bd(const nvme_process_arg_t *);
108 static int do_detach_bd(const nvme_process_arg_t *);
109 static int do_firmware_load(const nvme_process_arg_t *);
110 static int do_firmware_commit(const nvme_process_arg_t *);
111 static int do_firmware_activate(const nvme_process_arg_t *);
112 
113 static void optparse_list(nvme_process_arg_t *);
114 static void optparse_identify(nvme_process_arg_t *);
115 static void optparse_identify_ctrl(nvme_process_arg_t *);
116 static void optparse_identify_ns(nvme_process_arg_t *);
117 static void optparse_list_logs(nvme_process_arg_t *);
118 static void optparse_get_logpage(nvme_process_arg_t *);
119 static void optparse_print_logpage(nvme_process_arg_t *);
120 static void optparse_list_features(nvme_process_arg_t *);
121 static void optparse_set_feature(nvme_process_arg_t *);
122 static void optparse_secure_erase(nvme_process_arg_t *);
123 
124 static void usage_list(const char *);
125 static void usage_identify(const char *);
126 static void usage_identify_ctrl(const char *);
127 static void usage_identify_ns(const char *);
128 static void usage_list_logs(const char *);
129 static void usage_get_logpage(const char *);
130 static void usage_print_logpage(const char *);
131 static void usage_list_features(const char *);
132 static void usage_get_features(const char *);
133 static void usage_set_feature(const char *);
134 static void usage_format(const char *);
135 static void usage_secure_erase(const char *);
136 static void usage_attach_detach_bd(const char *);
137 static void usage_firmware_list(const char *);
138 static void usage_firmware_load(const char *);
139 static void usage_firmware_commit(const char *);
140 static void usage_firmware_activate(const char *);
141 
142 int verbose;
143 int debug;
144 
145 /*
146  * nvmeadm Secure-erase specific options
147  */
148 #define	NVMEADM_O_SE_CRYPTO	0x00000004
149 
150 /*
151  * nvmeadm identify specific options
152  */
153 #define	NVMEADM_O_ID_NSID_LIST	0x00000008
154 #define	NVMEADM_O_ID_COMMON_NS	0x00000010
155 #define	NVMEADM_O_ID_CTRL_LIST	0x00000020
156 #define	NVMEADM_O_ID_DESC_LIST	0x00000040
157 #define	NVMEADM_O_ID_ALLOC_NS	0x00000080
158 
159 static int exitcode;
160 
161 /*
162  * Nvmeadm subcommand definitons.
163  *
164  * When adding a new subcommand, please check that the commands still
165  * line up in the usage() message, and adjust the format string in
166  * usage() below if necessary.
167  */
168 static const nvmeadm_cmd_t nvmeadm_cmds[] = {
169 	{
170 		"list",
171 		"list controllers and namespaces",
172 		"  -c\t\tlist only controllers\n"
173 		"  -L\t\tlist controller location\n"
174 		"  -p\t\tprint parsable output\n"
175 		"  -o field\tselect a field for parsable output\n",
176 		"  model\t\tthe controller's model name\n"
177 		"  serial\tthe controller's serial number\n"
178 		"  fwrev\t\tthe controller's current firmware revision\n"
179 		"  version\tthe controller's NVMe specification version\n"
180 		"  capacity\tthe controller or namespace's capacity in bytes\n"
181 		"  instance\tthe device driver instance (e.g. nvme3)\n"
182 		"  ctrlpath\tthe controller's /devices path\n"
183 		"  unallocated\tthe amount of unallocated NVM in bytes\n"
184 		"  size\t\tthe namespace's logical size in bytes\n"
185 		"  used\t\tthe namespace's bytes used\n"
186 		"  disk\t\tthe name of the namespace's disk device\n"
187 		"  namespace\tthe namespace's numerical value\n"
188 		"  ns-state\tthe namespace's current state\n"
189 		"  format\ta description of the namespace's format\n"
190 		"  fmtid\t\tthe numerical id of the namespace's format\n"
191 		"  fmtds\t\tthe data size of the namespace's format\n"
192 		"  fmtms\t\tthe metadata size of the namespace's format\n"
193 		"  location\tcontroller location (-L)\n"
194 		"  ctlap\t\tcontrolling attachment point (-L)\n",
195 		do_list, usage_list, optparse_list,
196 		NVMEADM_C_MULTI
197 	},
198 	{
199 		"identify",
200 		"identify controllers and/or namespaces",
201 		"  -C\t\tget Common Namespace Identification\n"
202 		"  -a\t\tget only allocated namespace information\n"
203 		"  -c\t\tget controller identifier list\n"
204 		"  -d\t\tget namespace identification descriptors list\n"
205 		"  -n\t\tget namespaces identifier list",
206 		NULL,
207 		do_identify, usage_identify, optparse_identify,
208 		NVMEADM_C_MULTI
209 	},
210 	{
211 		"identify-controller",
212 		"identify controllers",
213 		"  -C\t\tget Common Namespace Identification\n"
214 		"  -a\t\tget only allocated namespace information\n"
215 		"  -c\t\tget controller identifier list\n"
216 		"  -n\t\tget namespaces identifier list",
217 		NULL,
218 		do_identify_ctrl, usage_identify_ctrl, optparse_identify_ctrl,
219 		NVMEADM_C_MULTI
220 	},
221 	{
222 		"identify-namespace",
223 		"identify namespaces",
224 		"  -c\t\tget attached controller identifier list\n"
225 		"  -d\t\tget namespace identification descriptors list",
226 		NULL,
227 		do_identify_ns, usage_identify_ns, optparse_identify_ns,
228 		NVMEADM_C_MULTI
229 	},
230 	{
231 		"list-logpages",
232 		"list a device's supported log pages",
233 		"  -a\t\tprint all log pages, including unimplemented ones\n"
234 		"  -H\t\tomit column headers\n"
235 		"  -o field\tselect a field for parsable output\n"
236 		"  -p\t\tprint parsable output\n"
237 		"  -s scope\tprint logs that match the specified scopes "
238 		"(default is based on\n\t\tdevice)\n",
239 		"  device\tthe name of the controller or namespace\n"
240 		"  name\t\tthe name of the log page\n"
241 		"  desc\t\ta description of the loage page\n"
242 		"  scope\t\tthe valid device scopes for the log page\n"
243 		"  fields\tthe list of fields in the get log request that may "
244 		"be set or required\n\t\t(e.g. lsi, lsp, rae, etc.)\n"
245 		"  csi\t\tthe command set interface the log page belongs to\n"
246 		"  lid\t\tthe log page's numeric ID\n"
247 		"  impl\t\tindicates whether the device implements the log "
248 		"page\n"
249 		"  size\t\tthe size of the log page for fixed size logs\n"
250 		"  minsize\tthe minimum size required to determine the full "
251 		"log page size\n\t\tfor variable-length pages\n"
252 		"  sources\twhere information for this log page came from\n"
253 		"  kind\t\tindicates the kind of log page e.g. standard, "
254 		"vendor-specific,\n\t\tetc.",
255 		do_list_logs, usage_list_logs, optparse_list_logs,
256 		NVMEADM_C_MULTI
257 	},
258 	{
259 		.c_name = "get-logpage",
260 		.c_desc = "get a log page from controllers and/or namespaces",
261 		.c_flagdesc = "  -H\t\tomit column headers\n"
262 		    "  -o field\tselect a field for parsable output\n"
263 		    "  -O file\toutput log raw binary data to a file\n"
264 		    "  -p\t\tprint parsable output\n"
265 		    "  -x\t\tforce hex dump output\n",
266 		.c_fielddesc = "  short\t\tfield's short name\n"
267 		    "  desc\t\tdescription of the field\n"
268 		    "  value\t\tthe raw field value\n"
269 		    "  human\t\tthe human printable form of the value\n"
270 		    "  length\tthe length of the field in bytes\n"
271 		    "  bitlen\tthe number of additional bits long the field "
272 		    "is\n"
273 		    "  offset\tthe offset in bytes to the start of the field\n"
274 		    "  bitoff\tthe additional number of bits to the start of "
275 		    "the field\n",
276 		.c_func = do_get_logpage,
277 		.c_usage = usage_get_logpage,
278 		.c_optparse = optparse_get_logpage,
279 		.c_flags = NVMEADM_C_MULTI
280 	},
281 	{
282 		.c_name = "print-logpage",
283 		.c_desc = "print and filter a log page from disk",
284 		.c_flagdesc = "  -f file\tfile that contains log data\n"
285 		    "  -H\t\tomit column headers\n"
286 		    "  -o field\tselect a field for parsable output\n"
287 		    "  -p\t\tprint parsable output\n"
288 		    "  -x\t\tforce hex dump output\n",
289 		.c_fielddesc = "  short\t\tfield's short name\n"
290 		    "  desc\t\tdescription of the field\n"
291 		    "  value\t\tthe raw field value\n"
292 		    "  human\t\tthe human printable form of the value\n"
293 		    "  offset\tthe length of the field in bytes\n"
294 		    "  bitlen\tthe number of additional bits long the field "
295 		    "is\n"
296 		    "  offset\tthe offset in bytes to the start of the field\n"
297 		    "  bitoff\tthe additional number of bits to the start of "
298 		    "the field\n",
299 		.c_func = do_print_logpage,
300 		.c_usage = usage_print_logpage,
301 		.c_optparse = optparse_print_logpage,
302 		.c_flags = NVMEADM_C_NOCTRL
303 	},
304 	{
305 		"list-features",
306 		"list a device's supported features",
307 		"  -a\t\tprint all features, including unsupported\n"
308 		"  -H\t\tomit column headers\n"
309 		"  -o field\tselect a field for parsable output\n"
310 		"  -p\t\tprint parsable output",
311 		"  device\tthe name of the controller or namespace\n"
312 		"  short\t\tthe short name of the feature\n"
313 		"  spec\t\tthe longer feature description from the NVMe spec\n"
314 		"  fid\t\tthe numeric feature ID\n"
315 		"  scope\t\tthe valid device scopes for the feature\n"
316 		"  kind\t\tindicates the kind of feature e.g. standard, "
317 		"vendor-specific,\n\t\tetc.\n"
318 		"  csi\t\tindicates the features command set interface\n"
319 		"  flags\t\tindicates additional properties of the feature\n"
320 		"  get-in\tindicates the fields that are required to get the "
321 		"feature\n"
322 		"  set-in\tindicates the fields that are required to set the "
323 		"feature\n"
324 		"  get-out\tindicates the fields the feature outputs\n"
325 		"  set-out\tindicates the fields the feature outputs when "
326 		"setting the feature\n"
327 		"  datalen\tindicates the length of the feature's data "
328 		"payload\n"
329 		"  impl\t\tindicates whether the device implements the "
330 		"feature",
331 		do_list_features, usage_list_features, optparse_list_features,
332 		NVMEADM_C_MULTI
333 	},
334 	{
335 		"get-features",
336 		"get features from controllers and/or namespaces",
337 		NULL,
338 		NULL,
339 		do_get_features, usage_get_features, NULL,
340 		NVMEADM_C_MULTI
341 	},
342 	{
343 		.c_name = "set-feature",
344 		.c_desc = "set a feature on a controller and/or namespace",
345 		.c_flagdesc = "  -s\t\tsave the setting persistently\n"
346 		    "  -v value\tset the primary (cdw11) feature value\n"
347 		    "  --cdw12 cdw12\tset the cdw12 32-bit value\n"
348 		    "  --cdw13 cdw13\tset the cdw13 32-bit value\n"
349 		    "  --cdw15 cdw15\tset the cdw15 32-bit value\n"
350 		    "  -i input\tthe command input data file\n"
351 		    "  -l length\tthe command input data size in bytes\n"
352 		    "  -I impact\trequest impact on the system\n",
353 		.c_func = do_set_feature,
354 		.c_usage = usage_set_feature,
355 		.c_optparse = optparse_set_feature,
356 		.c_flags = NVMEADM_C_EXCL
357 	},
358 	{
359 		"format",
360 		"format namespace(s) of a controller",
361 		NULL,
362 		NULL,
363 		do_format, usage_format, NULL,
364 		NVMEADM_C_EXCL
365 	},
366 	{
367 		"secure-erase",
368 		"secure erase namespace(s) of a controller",
369 		"  -c  Do a cryptographic erase.",
370 		NULL,
371 		do_secure_erase, usage_secure_erase, optparse_secure_erase,
372 		NVMEADM_C_EXCL
373 	},
374 	{
375 		"create-namespace",
376 		"create a new namespace",
377 		"  -b block-size\tNamespace format chosen to match the "
378 		"requested block-size\n"
379 		"  -c cap\tSpecifies the namespace capacity in bytes, defaults "
380 		"to the\n\t\tnamespace's size. When the size is greater than "
381 		"the\n\t\tcapacity, the namespace is thin provisioned.\n"
382 		"  -f flbas\tformatted LBA block size index\n"
383 		"  -n nmic\tmulti-path I/O and namespace sharing capabilities, "
384 		"valid values:\n"
385 		"\t\tnone\tno namespace sharing\n"
386 		"\t\tshared\tthe namespace may be attached by two or more "
387 		"controllers\n"
388 		"  -t csi\tspecifies the namespace's command set interface, "
389 		"defaults to\n\t\tnvm\n",
390 		NULL,
391 		do_create_ns, usage_create_ns, optparse_create_ns,
392 		NVMEADM_C_EXCL
393 	},
394 	{
395 		"delete-namespace",
396 		"delete a namespace",
397 		NULL, NULL,
398 		do_delete_ns, usage_delete_ns, NULL,
399 		NVMEADM_C_EXCL
400 	},
401 	{
402 		"attach-namespace",
403 		"attach a namespace to a controller",
404 		NULL, NULL,
405 		do_attach_ns, usage_attach_ns, NULL,
406 		NVMEADM_C_EXCL
407 	},
408 	{
409 		"detach-namespace",
410 		"detach a namespace from a controller",
411 		NULL, NULL,
412 		do_detach_ns, usage_detach_ns, NULL,
413 		NVMEADM_C_EXCL
414 	},
415 
416 	{
417 		"detach",
418 		"detach blkdev(4D) from namespace(s) of a controller",
419 		NULL,
420 		NULL,
421 		do_detach_bd, usage_attach_detach_bd, NULL,
422 		NVMEADM_C_EXCL
423 	},
424 	{
425 		"attach",
426 		"attach blkdev(4D) to namespace(s) of a controller",
427 		NULL,
428 		NULL,
429 		do_attach_bd, usage_attach_detach_bd, NULL,
430 		NVMEADM_C_EXCL
431 	},
432 	{
433 		"list-firmware",
434 		"list firmware on a controller",
435 		NULL,
436 		NULL,
437 		do_get_logpage_fwslot, usage_firmware_list, NULL,
438 		0
439 	},
440 	{
441 		"load-firmware",
442 		"load firmware to a controller",
443 		NULL,
444 		NULL,
445 		do_firmware_load, usage_firmware_load, NULL,
446 		NVMEADM_C_EXCL
447 	},
448 	{
449 		"commit-firmware",
450 		"commit downloaded firmware to a slot of a controller",
451 		NULL,
452 		NULL,
453 		do_firmware_commit, usage_firmware_commit, NULL,
454 		NVMEADM_C_EXCL
455 	},
456 	{
457 		"activate-firmware",
458 		"activate a firmware slot of a controller",
459 		NULL,
460 		NULL,
461 		do_firmware_activate, usage_firmware_activate, NULL,
462 		NVMEADM_C_EXCL
463 	},
464 	{
465 		.c_name = "measure-phyeye",
466 		.c_desc = "measure the physical interface eye",
467 		.c_flagdesc = "  -o output\tthe data output file\n"
468 		    "  -Q quality\tthe quality of the eye measurement (good, "
469 		    "better, or best)\n",
470 		.c_func = do_measure_phyeye_cmd,
471 		.c_usage = usage_measure_phyeye_cmd,
472 		.c_optparse = optparse_measure_phyeye_cmd,
473 		.c_flags = NVMEADM_C_EXCL
474 	},
475 	{
476 		.c_name = "report-phyeye",
477 		.c_desc = "report information about the measured eye",
478 		.c_flagdesc = "  -e eye\toptional eye index to report\n"
479 		    "  -l lane\tprint information only about the specific "
480 		    "lane\n"
481 		    "  -m mode\tdata report mode, either 'print-eye' or "
482 		    "'eye-data'\n",
483 		.c_func = do_report_phyeye_cmd,
484 		.c_usage = usage_report_phyeye_cmd,
485 		.c_optparse = optparse_report_phyeye_cmd,
486 		.c_flags = NVMEADM_C_NOCTRL
487 	},
488 	{
489 		.c_name = "vendor-cmd",
490 		.c_desc = "run an arbitrary vendor-specific command",
491 		.c_flagdesc = "  -O opcode\tcommand opcode\n"
492 		    "  -n nsid\tcommand namespace identifier\n"
493 		    "  --cdw12 cdw12\tcdw12 32-bit value\n"
494 		    "  --cdw13 cdw13\tcdw13 32-bit value\n"
495 		    "  --cdw14 cdw14\tcdw14 32-bit value\n"
496 		    "  --cdw15 cdw15\tcdw15 32-bit value\n"
497 		    "  -l length\tthe command data size in bytes\n"
498 		    "  -i input\tthe command input data file\n"
499 		    "  -o output\tthe command output data file\n"
500 		    "  -L lock\trequest a controller or namespace lock\n"
501 		    "  -I impact\trequest impact on the system\n"
502 		    "  -t timeout\trequest timeout in seconds\n",
503 		.c_func = do_vendor_cmd,
504 		.c_usage = usage_vendor_cmd,
505 		.c_optparse = optparse_vendor_cmd
506 	},
507 	{
508 		.c_name = "ocp/error-inject",
509 		.c_desc = "inject errors onto an NVMe controller",
510 		.c_flagdesc = "  -l lat_us\tset latency duration to lat_us "
511 		    "microseconds\n"
512 		    "  -n nreads\tdelay error injection until nreads read "
513 		    "commands complete\n",
514 		.c_func = do_ocp_errinj,
515 		.c_usage = usage_ocp_errinj,
516 		.c_optparse = optparse_ocp_errinj,
517 		.c_flags = NVMEADM_C_EXCL
518 	},
519 	{
520 		.c_name = "sandisk/hwrev",
521 		.c_desc = "obtain device hardware revision",
522 		.c_func = do_sandisk_hwrev,
523 		.c_usage = usage_sandisk_hwrev,
524 		.c_flags = 0
525 	},
526 	{
527 		.c_name = "sandisk/pci-eye",
528 		.c_desc = "get PCIe receiver eye diagram",
529 		.c_flagdesc = "  -l lane\t\tspecify the PCIe lane (0-3)\n"
530 		    "  -o output\tspecify output file destination\n",
531 		.c_func = do_sandisk_pcieye,
532 		.c_usage = usage_sandisk_pcieye,
533 		.c_optparse = optparse_sandisk_pcieye
534 	},
535 	{
536 		"wdc/e6dump",
537 		"dump WDC e6 diagnostic log",
538 		"  -o output\tspecify output file destination\n",
539 		NULL,
540 		do_wdc_e6dump, usage_wdc_e6dump, optparse_wdc_e6dump,
541 		0
542 	},
543 	{
544 		"wdc/resize",
545 		"change a WDC device's capacity",
546 		"  -g\t\tquery the device's current resized capacity\n"
547 		"  -s size\tset the size of a device to the specified in gb",
548 		NULL,
549 		do_wdc_resize, usage_wdc_resize, optparse_wdc_resize,
550 		/*
551 		 * We do not set NVMEADM_C_EXCL here as that is handled by the
552 		 * vendor unique command logic and operates based on the
553 		 * information we get from vuc discovery.
554 		 */
555 		0
556 	},
557 	{
558 		"wdc/clear-assert",
559 		"clear internal device assertion",
560 		NULL,
561 		NULL,
562 		do_wdc_clear_assert, usage_wdc_clear_assert, NULL
563 	},
564 	{
565 		"wdc/inject-assert",
566 		"inject internal device assertion",
567 		NULL,
568 		NULL,
569 		do_wdc_inject_assert, usage_wdc_inject_assert, NULL
570 	},
571 	{
572 		NULL, NULL, NULL,
573 		NULL, NULL, NULL, 0
574 	}
575 };
576 
577 static const nvmeadm_feature_t features[] = {
578 	{
579 		.f_feature = "arb",
580 		.f_print = nvme_print_feat_arbitration
581 	}, {
582 		.f_feature = "pm",
583 		.f_print = nvme_print_feat_power_mgmt
584 	}, {
585 		.f_feature = "range",
586 		.f_print = nvme_print_feat_lba_range
587 	}, {
588 		.f_feature = "temp",
589 		.f_get = do_get_feat_temp_thresh,
590 		.f_print = nvme_print_feat_temperature
591 	}, {
592 		.f_feature = "errrec",
593 		.f_print = nvme_print_feat_error
594 	}, {
595 		.f_feature = "cache",
596 		.f_print = nvme_print_feat_write_cache
597 	}, {
598 		.f_feature = "queues",
599 		.f_print = nvme_print_feat_nqueues
600 	}, {
601 		.f_feature = "coalescing",
602 		.f_print = nvme_print_feat_intr_coal
603 	}, {
604 		.f_feature = "vector",
605 		.f_get = do_get_feat_intr_vect,
606 		.f_print = nvme_print_feat_intr_vect
607 	}, {
608 		.f_feature = "atomicity",
609 		.f_print = nvme_print_feat_write_atom
610 	}, {
611 		.f_feature = "event",
612 		.f_print = nvme_print_feat_async_event
613 	}, {
614 		.f_feature = "apst",
615 		.f_print = nvme_print_feat_auto_pst
616 	}, {
617 		.f_feature = "progress",
618 		.f_print = nvme_print_feat_progress
619 	}, {
620 		.f_feature = "hostsup",
621 		.f_print = nvme_print_feat_host_behavior
622 	}, {
623 		.f_feature = "ocp/errinj",
624 		.f_print = nvme_print_feat_ocp_err_inj
625 	}, {
626 		.f_feature = "ocp/plpfail",
627 		.f_print = nvme_print_feat_ocp_plp_fail
628 	}, {
629 		.f_feature = "ocp/plphealth",
630 		.f_print = nvme_print_feat_ocp_plp_health
631 	}
632 };
633 
634 static void
nvmeadm_ctrl_vwarn(const nvme_process_arg_t * npa,const char * fmt,va_list ap)635 nvmeadm_ctrl_vwarn(const nvme_process_arg_t *npa, const char *fmt, va_list ap)
636 {
637 	nvme_ctrl_t *ctrl = npa->npa_ctrl;
638 
639 	(void) fprintf(stderr, "nvmeadm: ");
640 	(void) vfprintf(stderr, fmt, ap);
641 	(void) fprintf(stderr, ": %s: %s (libnvme: 0x%x, sys: %d)\n",
642 	    nvme_ctrl_errmsg(ctrl), nvme_ctrl_errtostr(npa->npa_ctrl,
643 	    nvme_ctrl_err(ctrl)), nvme_ctrl_err(ctrl), nvme_ctrl_syserr(ctrl));
644 }
645 
646 static void
nvmeadm_hdl_vwarn(const nvme_process_arg_t * npa,const char * fmt,va_list ap)647 nvmeadm_hdl_vwarn(const nvme_process_arg_t *npa, const char *fmt, va_list ap)
648 {
649 	nvme_t *nvme = npa->npa_nvme;
650 
651 	(void) fprintf(stderr, "nvmeadm: ");
652 	(void) vfprintf(stderr, fmt, ap);
653 	(void) fprintf(stderr, ": %s: %s (libnvme: 0x%x, sys: %d)\n",
654 	    nvme_errmsg(nvme), nvme_errtostr(nvme, nvme_err(nvme)),
655 	    nvme_err(nvme), nvme_syserr(nvme));
656 }
657 
658 static void
nvmeadm_ctrl_info_vwarn(const nvme_process_arg_t * npa,const char * fmt,va_list ap)659 nvmeadm_ctrl_info_vwarn(const nvme_process_arg_t *npa, const char *fmt,
660     va_list ap)
661 {
662 	nvme_ctrl_info_t *info = npa->npa_ctrl_info;
663 
664 	(void) fprintf(stderr, "nvmeadm: ");
665 	(void) vfprintf(stderr, fmt, ap);
666 	(void) fprintf(stderr, ": %s: %s (libnvme info: 0x%x, sys: %d)\n",
667 	    nvme_ctrl_info_errmsg(info), nvme_ctrl_info_errtostr(info,
668 	    nvme_ctrl_info_err(info)), nvme_ctrl_info_err(info),
669 	    nvme_ctrl_info_syserr(info));
670 }
671 
672 void
nvmeadm_warn(const nvme_process_arg_t * npa,const char * fmt,...)673 nvmeadm_warn(const nvme_process_arg_t *npa, const char *fmt, ...)
674 {
675 	va_list ap;
676 
677 	va_start(ap, fmt);
678 	nvmeadm_ctrl_vwarn(npa, fmt, ap);
679 	va_end(ap);
680 }
681 
682 void __NORETURN
nvmeadm_fatal(const nvme_process_arg_t * npa,const char * fmt,...)683 nvmeadm_fatal(const nvme_process_arg_t *npa, const char *fmt, ...)
684 {
685 	va_list ap;
686 
687 	va_start(ap, fmt);
688 	nvmeadm_ctrl_vwarn(npa, fmt, ap);
689 	va_end(ap);
690 
691 	exit(-1);
692 }
693 
694 void
nvmeadm_hdl_warn(const nvme_process_arg_t * npa,const char * fmt,...)695 nvmeadm_hdl_warn(const nvme_process_arg_t *npa, const char *fmt, ...)
696 {
697 	va_list ap;
698 
699 	va_start(ap, fmt);
700 	nvmeadm_hdl_vwarn(npa, fmt, ap);
701 	va_end(ap);
702 }
703 
704 void __NORETURN
nvmeadm_hdl_fatal(const nvme_process_arg_t * npa,const char * fmt,...)705 nvmeadm_hdl_fatal(const nvme_process_arg_t *npa, const char *fmt, ...)
706 {
707 	va_list ap;
708 
709 	va_start(ap, fmt);
710 	nvmeadm_hdl_vwarn(npa, fmt, ap);
711 	va_end(ap);
712 
713 	exit(-1);
714 }
715 
716 static void
nvmeadm_ctrl_info_warn(const nvme_process_arg_t * npa,const char * fmt,...)717 nvmeadm_ctrl_info_warn(const nvme_process_arg_t *npa, const char *fmt, ...)
718 {
719 	va_list ap;
720 
721 	va_start(ap, fmt);
722 	nvmeadm_ctrl_info_vwarn(npa, fmt, ap);
723 	va_end(ap);
724 }
725 
726 static void
nvmeadm_ctrl_info_fatal(const nvme_process_arg_t * npa,const char * fmt,...)727 nvmeadm_ctrl_info_fatal(const nvme_process_arg_t *npa, const char *fmt, ...)
728 {
729 	va_list ap;
730 
731 	va_start(ap, fmt);
732 	nvmeadm_ctrl_info_vwarn(npa, fmt, ap);
733 	va_end(ap);
734 
735 	exit(-1);
736 }
737 
738 boolean_t
nvme_version_check(const nvme_process_arg_t * npa,const nvme_version_t * vers)739 nvme_version_check(const nvme_process_arg_t *npa, const nvme_version_t *vers)
740 {
741 	return (nvme_vers_atleast(npa->npa_version, vers) ? B_TRUE : B_FALSE);
742 }
743 
744 long long
optparse_ui_range(const char * raw,const char * field,uint64_t min,uint64_t max)745 optparse_ui_range(const char *raw, const char *field, uint64_t min,
746     uint64_t max)
747 {
748 	const char *errstr;
749 	long long l;
750 
751 	l = strtonumx(raw, min, max, &errstr, 0);
752 	if (errstr != NULL) {
753 		errx(-1, "failed to parse %s: value %s is %s: valid values "
754 		    "are in the range [0x%" PRIx64 ", 0x%" PRIx64 "]", field,
755 		    raw, errstr, min, max);
756 	}
757 
758 	return (l);
759 }
760 
761 nvme_disc_impact_t
optparse_impact(char * raw)762 optparse_impact(char *raw)
763 {
764 	nvme_disc_impact_t impact = 0;
765 	char *last;
766 
767 	for (char *s = strtok_r(raw, ",", &last); s != NULL;
768 	    s = strtok_r(NULL, ",", &last)) {
769 		if (strcmp(s, "data") == 0) {
770 			impact |= NVME_DISC_IMPACT_DATA;
771 		} else if (strcmp(s, "namespace") == 0) {
772 			impact |= NVME_DISC_IMPACT_NS;
773 		} else {
774 			errx(-1, "invalid impact string: %s", s);
775 		}
776 	}
777 
778 	return (impact);
779 }
780 
781 /*
782  * Because nvmeadm operates on a series of NVMe devices for several commands,
783  * here we need to clean up everything that we allocated for this device so we
784  * can prepare for the next.
785  */
786 static void
nvmeadm_cleanup_npa(nvme_process_arg_t * npa)787 nvmeadm_cleanup_npa(nvme_process_arg_t *npa)
788 {
789 	npa->npa_idctl = NULL;
790 	npa->npa_version = NULL;
791 
792 	if (npa->npa_excl) {
793 		if (npa->npa_ns != NULL) {
794 			nvme_ns_unlock(npa->npa_ns);
795 		} else if (npa->npa_ctrl != NULL) {
796 			nvme_ctrl_unlock(npa->npa_ctrl);
797 		}
798 	}
799 
800 	if (npa->npa_ns_info != NULL) {
801 		nvme_ns_info_free(npa->npa_ns_info);
802 		npa->npa_ns_info = NULL;
803 	}
804 
805 	if (npa->npa_ctrl_info != NULL) {
806 		nvme_ctrl_info_free(npa->npa_ctrl_info);
807 		npa->npa_ctrl_info = NULL;
808 	}
809 
810 	if (npa->npa_ns != NULL) {
811 		nvme_ns_fini(npa->npa_ns);
812 		npa->npa_ns = NULL;
813 	}
814 
815 	if (npa->npa_ctrl != NULL) {
816 		nvme_ctrl_fini(npa->npa_ctrl);
817 		npa->npa_ctrl = NULL;
818 	}
819 }
820 
821 /*
822  * Determine if a command requires a controller or namespace write lock. If so
823  * we first attempt to grab it non-blocking and then if that fails, we'll warn
824  * that we may be blocking for the lock so that way the user has a chance to do
825  * something and can cancel it.
826  */
827 void
nvmeadm_excl(const nvme_process_arg_t * npa,nvme_lock_level_t level)828 nvmeadm_excl(const nvme_process_arg_t *npa, nvme_lock_level_t level)
829 {
830 	bool ret;
831 	nvme_lock_flags_t flags = NVME_LOCK_F_DONT_BLOCK;
832 
833 	if (npa->npa_ns != NULL) {
834 		ret = nvme_ns_lock(npa->npa_ns, level, flags);
835 	} else {
836 		ret = nvme_ctrl_lock(npa->npa_ctrl, level, flags);
837 	}
838 
839 	if (ret) {
840 		return;
841 	}
842 
843 	if (nvme_ctrl_err(npa->npa_ctrl) != NVME_ERR_LOCK_WOULD_BLOCK) {
844 		nvmeadm_fatal(npa, "failed to acquire lock on %s",
845 		    npa->npa_name);
846 	}
847 
848 	(void) fprintf(stderr, "Waiting on contended %s lock on %s...",
849 	    npa->npa_ns != NULL ? "namespace": "controller", npa->npa_name);
850 	(void) fflush(stderr);
851 
852 	flags &= ~NVME_LOCK_F_DONT_BLOCK;
853 	if (npa->npa_ns != NULL) {
854 		ret = nvme_ns_lock(npa->npa_ns, level, flags);
855 	} else {
856 		ret = nvme_ctrl_lock(npa->npa_ctrl, level, flags);
857 	}
858 
859 	if (!ret) {
860 		nvmeadm_fatal(npa, "failed to acquire lock on %s",
861 		    npa->npa_name);
862 	}
863 
864 	(void) fprintf(stderr, " acquired\n");
865 }
866 
867 /*
868  * Most of nvmeadm was written before the existence of libnvme and always had
869  * things like the identify controller or namespace information sitting around.
870  * As such we try to grab all this in one place for it. Note, regardless if this
871  * succeeds or fails, our callers will still call nvmeadm_cleanup_npa() so we
872  * don't need to clean up the various libnvme objects.
873  */
874 static boolean_t
nvmeadm_open_dev(nvme_process_arg_t * npa)875 nvmeadm_open_dev(nvme_process_arg_t *npa)
876 {
877 	if (!nvme_ctrl_ns_init(npa->npa_nvme, npa->npa_name, &npa->npa_ctrl,
878 	    &npa->npa_ns)) {
879 		nvmeadm_hdl_warn(npa, "failed to open '%s'", npa->npa_name);
880 		exitcode = -1;
881 		return (B_FALSE);
882 	}
883 
884 	/*
885 	 * Several commands expect to be able to access the controller's
886 	 * information snapshot. Grab that now for it and the namespace if it
887 	 * exists.
888 	 */
889 	if (!nvme_ctrl_info_snap(npa->npa_ctrl, &npa->npa_ctrl_info)) {
890 		nvmeadm_warn(npa, "failed to get controller info for %s",
891 		    npa->npa_ctrl_name);
892 		exitcode = -1;
893 		return (B_FALSE);
894 	}
895 
896 	if (npa->npa_ns != NULL && !nvme_ns_info_snap(npa->npa_ns,
897 	    &npa->npa_ns_info)) {
898 		nvmeadm_warn(npa, "failed to get namespace info for %s",
899 		    npa->npa_name);
900 		exitcode = -1;
901 		return (B_FALSE);
902 	}
903 
904 	/*
905 	 * Snapshot data the rest of the command has fairly ingrained.
906 	 */
907 	npa->npa_version = nvme_ctrl_info_version(npa->npa_ctrl_info);
908 	npa->npa_idctl = nvme_ctrl_info_identify(npa->npa_ctrl_info);
909 
910 	/*
911 	 * If this command has requested exclusive access, proceed to grab that
912 	 * before we continue.
913 	 */
914 	if (npa->npa_excl) {
915 		nvmeadm_excl(npa, NVME_LOCK_L_WRITE);
916 	}
917 
918 	return (B_TRUE);
919 }
920 
921 static bool
nvmeadm_ctrl_disc_cb(nvme_t * nvme,const nvme_ctrl_disc_t * disc,void * arg)922 nvmeadm_ctrl_disc_cb(nvme_t *nvme, const nvme_ctrl_disc_t *disc, void *arg)
923 {
924 	nvme_process_arg_t *npa = arg;
925 	di_node_t di = nvme_ctrl_disc_devi(disc);
926 	char name[128];
927 
928 	(void) snprintf(name, sizeof (name), "%s%d", di_driver_name(di),
929 	    di_instance(di));
930 	npa->npa_name = name;
931 	npa->npa_ctrl_name = name;
932 
933 	if (nvmeadm_open_dev(npa)) {
934 		if (npa->npa_cmd->c_func(npa) != 0) {
935 			exitcode = -1;
936 		}
937 	}
938 
939 	nvmeadm_cleanup_npa(npa);
940 	return (true);
941 }
942 
943 int
main(int argc,char ** argv)944 main(int argc, char **argv)
945 {
946 	int c;
947 	const nvmeadm_cmd_t *cmd;
948 	nvme_process_arg_t npa = { 0 };
949 	int help = 0;
950 	char *ctrl = NULL;
951 
952 	while ((c = getopt(argc, argv, "dhv")) != -1) {
953 		switch (c) {
954 		case 'd':
955 			debug++;
956 			break;
957 
958 		case 'v':
959 			verbose++;
960 			break;
961 
962 		case 'h':
963 			help++;
964 			break;
965 
966 		case '?':
967 			usage(NULL);
968 			exit(-1);
969 		}
970 	}
971 
972 	if (optind == argc) {
973 		usage(NULL);
974 		if (help)
975 			exit(0);
976 		else
977 			exit(-1);
978 	}
979 
980 	/* Look up the specified command in the command table. */
981 	for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++)
982 		if (strcmp(cmd->c_name, argv[optind]) == 0)
983 			break;
984 
985 	if (cmd->c_name == NULL) {
986 		usage(NULL);
987 		exit(-1);
988 	}
989 
990 	if (help) {
991 		usage(cmd);
992 		exit(0);
993 	}
994 
995 	npa.npa_nvme = nvme_init();
996 	if (npa.npa_nvme == NULL) {
997 		err(-1, "failed to initialize libnvme");
998 	}
999 	npa.npa_cmd = cmd;
1000 	npa.npa_excl = ((npa.npa_cmd->c_flags & NVMEADM_C_EXCL) != 0);
1001 
1002 	optind++;
1003 
1004 	/*
1005 	 * Store the remaining arguments for use by the command. Give the
1006 	 * command a chance to process the options across the board before going
1007 	 * into each controller.
1008 	 */
1009 	npa.npa_argc = argc - optind;
1010 	npa.npa_argv = &argv[optind];
1011 
1012 	if (npa.npa_cmd->c_optparse != NULL) {
1013 		optind = 0;
1014 		npa.npa_cmd->c_optparse(&npa);
1015 		npa.npa_argc -= optind;
1016 		npa.npa_argv += optind;
1017 	}
1018 
1019 	/*
1020 	 * If this command indicates that it does not require a controller, then
1021 	 * don't bother with the rest and just call it immediately.
1022 	 */
1023 	if ((npa.npa_cmd->c_flags & NVMEADM_C_NOCTRL) != 0) {
1024 		if (npa.npa_cmd->c_func(&npa) != 0) {
1025 			exitcode = -1;
1026 		}
1027 		exit(exitcode);
1028 	}
1029 
1030 	/*
1031 	 * All commands but "list" require a ctl/ns argument. However, this
1032 	 * should not be passed through to the command in its subsequent
1033 	 * arguments.
1034 	 */
1035 	if (npa.npa_argc == 0 && cmd->c_func != do_list) {
1036 		warnx("missing controller/namespace name");
1037 		usage(cmd);
1038 		exit(-1);
1039 	}
1040 
1041 	if (npa.npa_argc > 0) {
1042 		ctrl = npa.npa_argv[0];
1043 		npa.npa_argv++;
1044 		npa.npa_argc--;
1045 	} else {
1046 		if (!nvme_ctrl_discover(npa.npa_nvme, nvmeadm_ctrl_disc_cb,
1047 		    &npa)) {
1048 			nvmeadm_hdl_fatal(&npa, "failed to walk controllers");
1049 		}
1050 		exit(exitcode);
1051 	}
1052 
1053 	/*
1054 	 * Make sure we're not running commands on multiple controllers that
1055 	 * aren't allowed to do that.
1056 	 */
1057 	if (ctrl != NULL && strchr(ctrl, ',') != NULL &&
1058 	    (cmd->c_flags & NVMEADM_C_MULTI) == 0) {
1059 		warnx("%s not allowed on multiple controllers",
1060 		    cmd->c_name);
1061 		usage(cmd);
1062 		exit(-1);
1063 	}
1064 
1065 	/*
1066 	 * Get controller/namespace arguments and run command.
1067 	 */
1068 	while ((npa.npa_name = strsep(&ctrl, ",")) != NULL) {
1069 		char *ctrl_name, *slash;
1070 
1071 		/*
1072 		 * We may be given just a controller as an argument or a
1073 		 * controller and a namespace as an argument. Parts of the
1074 		 * commands want to know what controller they're referring to
1075 		 * even if the overall argument was for a namespace. So we
1076 		 * always dup the argument and try to make the controller out of
1077 		 * it.
1078 		 */
1079 		ctrl_name = strdup(npa.npa_name);
1080 		if (ctrl_name == NULL) {
1081 			err(-1, "failed to duplicate NVMe controller/namespace "
1082 			    "name");
1083 		}
1084 		if ((slash = strchr(ctrl_name, '/')) != NULL)
1085 			*slash = '\0';
1086 		npa.npa_ctrl_name = ctrl_name;
1087 
1088 		if (nvmeadm_open_dev(&npa)) {
1089 			if (npa.npa_cmd->c_func(&npa) != 0) {
1090 				exitcode = -1;
1091 			}
1092 		}
1093 
1094 		nvmeadm_cleanup_npa(&npa);
1095 		free(ctrl_name);
1096 	}
1097 
1098 	exit(exitcode);
1099 }
1100 
1101 static void
nvme_oferr(const char * fmt,...)1102 nvme_oferr(const char *fmt, ...)
1103 {
1104 	va_list ap;
1105 
1106 	va_start(ap, fmt);
1107 	verrx(-1, fmt, ap);
1108 }
1109 
1110 static void
usage(const nvmeadm_cmd_t * cmd)1111 usage(const nvmeadm_cmd_t *cmd)
1112 {
1113 	const char *progname = getprogname();
1114 
1115 	(void) fprintf(stderr, "usage:\n");
1116 	(void) fprintf(stderr, "  %s -h %s\n", progname,
1117 	    cmd != NULL ? cmd->c_name : "[<command>]");
1118 	(void) fprintf(stderr, "  %s [-dv] ", progname);
1119 
1120 	if (cmd != NULL) {
1121 		cmd->c_usage(cmd->c_name);
1122 	} else {
1123 		(void) fprintf(stderr,
1124 		    "<command> <ctl>[/<ns>][,...] [<args>]\n");
1125 		(void) fprintf(stderr,
1126 		    "\n  Manage NVMe controllers and namespaces.\n");
1127 		(void) fprintf(stderr, "\ncommands:\n");
1128 
1129 		for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++) {
1130 			/*
1131 			 * The longest nvmeadm subcommand is 19 characters long.
1132 			 * The format string needs to be updated every time a
1133 			 * longer subcommand is added.
1134 			 */
1135 			(void) fprintf(stderr, "  %-19s - %s\n",
1136 			    cmd->c_name, cmd->c_desc);
1137 		}
1138 	}
1139 	(void) fprintf(stderr, "\n%s flags:\n"
1140 	    "  -h\t\tprint usage information\n"
1141 	    "  -d\t\tprint information useful for debugging %s\n"
1142 	    "  -v\t\tprint verbose information\n",
1143 	    progname, progname);
1144 
1145 	if (cmd != NULL && cmd->c_flagdesc != NULL) {
1146 		(void) fprintf(stderr, "\n%s %s flags:\n",
1147 		    progname, cmd->c_name);
1148 		(void) fprintf(stderr, "%s\n", cmd->c_flagdesc);
1149 	}
1150 
1151 	if (cmd != NULL && cmd->c_fielddesc != NULL) {
1152 		(void) fprintf(stderr, "\n%s %s valid fields:\n",
1153 		    progname, cmd->c_name);
1154 		(void) fprintf(stderr, "%s\n", cmd->c_fielddesc);
1155 	}
1156 }
1157 
1158 char *
nvme_dskname(di_node_t ctrl,const char * bd_addr)1159 nvme_dskname(di_node_t ctrl, const char *bd_addr)
1160 {
1161 	di_dim_t dim;
1162 	char *diskname = NULL;
1163 
1164 	dim = di_dim_init();
1165 	if (dim == NULL) {
1166 		err(-1, "failed to initialize devinfo minor translation");
1167 	}
1168 
1169 	for (di_node_t child = di_child_node(ctrl); child != DI_NODE_NIL;
1170 	    child = di_sibling_node(child)) {
1171 		char *disk_ctd, *path = NULL;
1172 		const char *addr = di_bus_addr(child);
1173 		if (addr == NULL)
1174 			continue;
1175 
1176 		if (strcmp(addr, bd_addr) != 0)
1177 			continue;
1178 
1179 		path = di_dim_path_dev(dim, di_driver_name(child),
1180 		    di_instance(child), "c");
1181 
1182 		/*
1183 		 * Error out if we didn't get a path, or if it's too short for
1184 		 * the following operations to be safe.
1185 		 */
1186 		if (path == NULL || strlen(path) < 2) {
1187 			errx(-1, "failed to get a valid minor path");
1188 		}
1189 
1190 		/* Chop off 's0' and get everything past the last '/' */
1191 		path[strlen(path) - 2] = '\0';
1192 		disk_ctd = strrchr(path, '/');
1193 		if (disk_ctd == NULL) {
1194 			errx(-1, "encountered malformed minor path: %s", path);
1195 		}
1196 
1197 		diskname = strdup(++disk_ctd);
1198 		if (diskname == NULL) {
1199 			err(-1, "failed to duplicate disk path");
1200 		}
1201 
1202 		free(path);
1203 		break;
1204 	}
1205 
1206 	di_dim_fini(dim);
1207 	return (diskname);
1208 }
1209 
1210 static void
usage_list(const char * c_name)1211 usage_list(const char *c_name)
1212 {
1213 	(void) fprintf(stderr, "%s "
1214 	    "[-c | -L] [-p -o field[,...]] [<ctl>[/<ns>][,...]\n\n"
1215 	    "  List NVMe controllers and their namespaces. If no "
1216 	    "controllers and/or name-\n  spaces are specified, all "
1217 	    "controllers and namespaces in the system will be\n  "
1218 	    "listed.\n", c_name);
1219 }
1220 
1221 static void
optparse_list(nvme_process_arg_t * npa)1222 optparse_list(nvme_process_arg_t *npa)
1223 {
1224 	int c;
1225 	uint_t oflags = 0;
1226 	boolean_t parse = B_FALSE, cflag = B_FALSE, Lflag = B_FALSE;
1227 	const char *fields = NULL;
1228 	const ofmt_field_t *ofmt = nvmeadm_list_nsid_ofmt;
1229 	nvmeadm_list_t *list = NULL;
1230 
1231 	if ((list = calloc(1, sizeof (nvmeadm_list_t))) == NULL) {
1232 		err(-1, "failed to allocate memory for argument tracking");
1233 	}
1234 	list->list_mode = NVMEADM_LIST_DEFAULT;
1235 
1236 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":cLo:p")) != -1) {
1237 		switch (c) {
1238 		case 'c':
1239 			cflag = B_TRUE;
1240 			list->list_mode = NVMEADM_LIST_CTRL;
1241 			ofmt = nvmeadm_list_ctrl_ofmt;
1242 			break;
1243 		case 'L':
1244 			Lflag = B_TRUE;
1245 			list->list_mode = NVMEADM_LIST_LOC;
1246 			ofmt = nvmeadm_list_loc_ofmt;
1247 			break;
1248 		case 'o':
1249 			fields = optarg;
1250 			break;
1251 
1252 		case 'p':
1253 			parse = B_TRUE;
1254 			oflags |= OFMT_PARSABLE;
1255 			break;
1256 
1257 		case '?':
1258 			errx(-1, "unknown option: -%c", optopt);
1259 
1260 		case ':':
1261 			errx(-1, "option -%c requires an argument", optopt);
1262 		}
1263 	}
1264 
1265 	if (cflag && Lflag) {
1266 		errx(-1, "only one of -c and -L may be selected");
1267 	}
1268 
1269 	if (fields != NULL && !parse) {
1270 		errx(-1, "-o can only be used when in parsable mode (-p)");
1271 	}
1272 
1273 	/*
1274 	 * We use ofmt by default with -L.
1275 	 */
1276 	if (fields == NULL && Lflag) {
1277 		fields = "instance,model,location,ctlap";
1278 	}
1279 
1280 	if (parse && fields == NULL) {
1281 		errx(-1, "parsable mode (-p) requires one to specify output "
1282 		    "fields with -o");
1283 	}
1284 
1285 	if (parse || Lflag) {
1286 		ofmt_status_t oferr;
1287 
1288 		oferr = ofmt_open(fields, ofmt, oflags, 0,
1289 		    &npa->npa_ofmt);
1290 		ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);
1291 	}
1292 
1293 	if (Lflag) {
1294 		int e;
1295 
1296 		list->list_topo = topo_open(TOPO_VERSION, NULL, &e);
1297 		if (list->list_topo == NULL) {
1298 			errx(-1, "location information unavailable: unable to "
1299 			    "obtain topo handle: %s", topo_strerror(e));
1300 		}
1301 
1302 		(void) topo_snap_hold(list->list_topo, NULL, &e);
1303 		if (e != 0) {
1304 			errx(-1, "location information unavailable: unable to "
1305 			    "obtain topo snapshot: %s", topo_strerror(e));
1306 		}
1307 	}
1308 
1309 	npa->npa_cmd_arg = list;
1310 }
1311 
1312 static void
do_list_nsid(const nvme_process_arg_t * npa,nvme_ctrl_info_t * ctrl,nvme_ns_info_t * ns)1313 do_list_nsid(const nvme_process_arg_t *npa, nvme_ctrl_info_t *ctrl,
1314     nvme_ns_info_t *ns)
1315 {
1316 	const char *bd_addr, *disk = NULL, *state = NULL;
1317 	char *disk_path = NULL;
1318 	di_node_t ctrl_devi;
1319 
1320 	switch (nvme_ns_info_level(ns)) {
1321 	case NVME_NS_DISC_F_ALL:
1322 		disk = "unallocated";
1323 		state = "unallocated";
1324 		break;
1325 	case NVME_NS_DISC_F_ALLOCATED:
1326 		disk = "inactive";
1327 		state = "allocated";
1328 		break;
1329 	case NVME_NS_DISC_F_ACTIVE:
1330 		disk = "ignored";
1331 		state = "active";
1332 		break;
1333 	case NVME_NS_DISC_F_NOT_IGNORED:
1334 		disk = "unattached";
1335 		state = "active-usable";
1336 		break;
1337 	case NVME_NS_DISC_F_BLKDEV:
1338 		disk = "unknown";
1339 		state = "blkdev";
1340 		if (nvme_ns_info_bd_addr(ns, &bd_addr) &&
1341 		    nvme_ctrl_devi(npa->npa_ctrl, &ctrl_devi)) {
1342 			disk_path = nvme_dskname(ctrl_devi, bd_addr);
1343 			disk = disk_path;
1344 		}
1345 		break;
1346 	}
1347 
1348 	if (npa->npa_ofmt != NULL) {
1349 		nvmeadm_list_ofmt_arg_t oarg = { 0 };
1350 
1351 		oarg.nloa_name = npa->npa_ctrl_name;
1352 		oarg.nloa_ctrl = ctrl;
1353 		oarg.nloa_ns = ns;
1354 		oarg.nloa_disk = disk_path;
1355 		oarg.nloa_state = state;
1356 		if (!nvme_ctrl_devi(npa->npa_ctrl, &oarg.nloa_dip))
1357 			oarg.nloa_dip = DI_NODE_NIL;
1358 
1359 		ofmt_print(npa->npa_ofmt, &oarg);
1360 	} else {
1361 		(void) printf("  %s/%u (%s)", npa->npa_ctrl_name,
1362 		    nvme_ns_info_nsid(ns), disk);
1363 		if (nvme_ns_info_level(ns) >= NVME_NS_DISC_F_ACTIVE) {
1364 			(void) printf(": ");
1365 			nvme_print_nsid_summary(ns);
1366 		} else {
1367 			(void) printf("\n");
1368 		}
1369 	}
1370 
1371 	free(disk_path);
1372 }
1373 
1374 /*
1375  * Determine a device's controlling AP. As most NVMe devices are PCIe devices
1376  * (except when virtualized) we look at the parent pcieb instance and look for
1377  * its ap-names and slot-names properties.
1378  */
1379 static void
do_list_phys_map_ctlap(nvmeadm_list_ofmt_arg_t * oarg)1380 do_list_phys_map_ctlap(nvmeadm_list_ofmt_arg_t *oarg)
1381 {
1382 	di_node_t pdip;
1383 	int *ap_names, *slot_names;
1384 
1385 	pdip = di_parent_node(oarg->nloa_dip);
1386 	if (pdip == DI_NODE_NIL) {
1387 		return;
1388 	}
1389 
1390 	/*
1391 	 * This probably will need to change a bit when we understand the exact
1392 	 * shape AP's take when ACPI-based PCI hotplug is supported for virtual
1393 	 * machines. In that world, it's not clear where the AP will live.
1394 	 */
1395 	const char *drv = di_driver_name(pdip);
1396 	if (drv == NULL || strcmp(drv, "pcieb") != 0) {
1397 		return;
1398 	}
1399 
1400 	/*
1401 	 * The AP names property is a bitfield that indicates which entry in the
1402 	 * slot-names. A given device can in theory support multiple slots, but
1403 	 * PCIe is always going to be a value of 1.
1404 	 */
1405 	if (di_prop_lookup_ints(DDI_DEV_T_ANY, pdip, "ap-names", &ap_names) !=
1406 	    1 || *ap_names != 1) {
1407 		return;
1408 	}
1409 
1410 	/*
1411 	 * The slot-names property is structured with the first word as a
1412 	 * bitfield mask and then a series of names which are guaranteed to be
1413 	 * NULL terminated.
1414 	 */
1415 	int nu32 = di_prop_lookup_ints(DDI_DEV_T_ANY, pdip, "slot-names",
1416 	    &slot_names);
1417 	if (nu32 <= 1 || slot_names[0] != 1) {
1418 		return;
1419 	}
1420 
1421 	oarg->nloa_ap = calloc(nu32 - 1, sizeof (uint32_t));
1422 	if (oarg->nloa_ap == NULL) {
1423 		err(-1, "failed to allocate memory for attachment point name");
1424 	}
1425 
1426 	(void) memcpy(oarg->nloa_ap, &slot_names[1], sizeof (uint32_t) *
1427 	    (nu32 - 1));
1428 }
1429 
1430 /*
1431  * NVMe devices are always enumerated with an 'nvme' node. See if we can find
1432  * that.
1433  */
1434 static int
do_list_phys_map_loc_cb(topo_hdl_t * hp,tnode_t * tn,void * arg)1435 do_list_phys_map_loc_cb(topo_hdl_t *hp, tnode_t *tn, void *arg)
1436 {
1437 	nvmeadm_list_ofmt_arg_t *oarg = arg;
1438 	char *label = NULL;
1439 	uint32_t tinst;
1440 	int err;
1441 
1442 	if (strcmp(topo_node_name(tn), NVME) != 0) {
1443 		return (TOPO_WALK_NEXT);
1444 	}
1445 
1446 	if (topo_prop_get_uint32(tn, TOPO_PGROUP_IO, TOPO_IO_INSTANCE, &tinst,
1447 	    &err) != 0) {
1448 		return (TOPO_WALK_NEXT);
1449 	}
1450 
1451 	if (tinst != (uint32_t)di_instance(oarg->nloa_dip)) {
1452 		return (TOPO_WALK_NEXT);
1453 	}
1454 
1455 	/*
1456 	 * Walk the node hierarchy looking for something that has a label. We
1457 	 * basically only consider ourselves and our immediate parent if that
1458 	 * parent is a bay or slot. If we go too far then we may end up finding
1459 	 * a label for a device that has no relationship to us.
1460 	 */
1461 	if (topo_prop_get_string(tn, TOPO_PGROUP_PROTOCOL, TOPO_PROP_LABEL,
1462 	    &label, &err) != 0) {
1463 		tnode_t *pn = topo_node_parent(tn);
1464 		const char *pname = topo_node_name(pn);
1465 		if (strcmp(pname, BAY) == 0 || strcmp(pname, SLOT) == 0) {
1466 			if (topo_prop_get_string(pn, TOPO_PGROUP_PROTOCOL,
1467 			    TOPO_PROP_LABEL, &label, &err) != 0) {
1468 				label = NULL;
1469 			}
1470 		}
1471 	}
1472 
1473 	oarg->nloa_loc = label;
1474 	return (TOPO_WALK_TERMINATE);
1475 }
1476 
1477 static void
do_list_phys_map_loc(nvmeadm_list_t * list,nvmeadm_list_ofmt_arg_t * oarg)1478 do_list_phys_map_loc(nvmeadm_list_t *list, nvmeadm_list_ofmt_arg_t *oarg)
1479 {
1480 	topo_walk_t *wp;
1481 	int err;
1482 
1483 	wp = topo_walk_init(list->list_topo, FM_FMRI_SCHEME_HC,
1484 	    do_list_phys_map_loc_cb, oarg, &err);
1485 	if (wp == NULL) {
1486 		warnx("location information not available: failed to "
1487 		    "initialize topo walk: %s", topo_strerror(err));
1488 		exitcode = -1;
1489 		return;
1490 	}
1491 
1492 	err = topo_walk_step(wp, TOPO_WALK_SIBLING);
1493 	if (err == TOPO_WALK_ERR) {
1494 		warnx("location information not available: topo walk failed");
1495 		exitcode = -1;
1496 	}
1497 
1498 	topo_walk_fini(wp);
1499 }
1500 
1501 static int
do_list(const nvme_process_arg_t * npa)1502 do_list(const nvme_process_arg_t *npa)
1503 {
1504 	nvme_ctrl_info_t *info = NULL;
1505 	nvme_ns_iter_t *iter = NULL;
1506 	nvme_iter_t ret;
1507 	const nvme_ns_disc_t *disc;
1508 	nvme_ns_disc_level_t level;
1509 	int rv = -1;
1510 	nvmeadm_list_t *list = npa->npa_cmd_arg;
1511 
1512 	if (npa->npa_argc > 0) {
1513 		errx(-1, "%s passed extraneous arguments starting with %s",
1514 		    npa->npa_cmd->c_name, npa->npa_argv[0]);
1515 	}
1516 
1517 	if (!nvme_ctrl_info_snap(npa->npa_ctrl, &info)) {
1518 		nvmeadm_warn(npa, "failed to get controller information for %s",
1519 		    npa->npa_ctrl_name);
1520 		return (-1);
1521 	}
1522 
1523 	if (npa->npa_ofmt == NULL) {
1524 		(void) printf("%s: ", npa->npa_ctrl_name);
1525 		nvme_print_ctrl_summary(info);
1526 	} else if (list->list_mode != NVMEADM_LIST_DEFAULT) {
1527 		nvmeadm_list_ofmt_arg_t oarg = { 0 };
1528 		oarg.nloa_name = npa->npa_ctrl_name;
1529 		oarg.nloa_ctrl = info;
1530 		if (!nvme_ctrl_devi(npa->npa_ctrl, &oarg.nloa_dip))
1531 			oarg.nloa_dip = DI_NODE_NIL;
1532 
1533 		if (list->list_mode == NVMEADM_LIST_LOC) {
1534 			do_list_phys_map_ctlap(&oarg);
1535 			do_list_phys_map_loc(list, &oarg);
1536 		}
1537 
1538 		ofmt_print(npa->npa_ofmt, &oarg);
1539 		free(oarg.nloa_ap);
1540 		if (list->list_topo != NULL) {
1541 			topo_hdl_strfree(list->list_topo, oarg.nloa_loc);
1542 		}
1543 	}
1544 
1545 	if (list->list_mode != NVMEADM_LIST_DEFAULT) {
1546 		rv = 0;
1547 		goto out;
1548 	}
1549 
1550 	/*
1551 	 * Check if we were given an explicit namespace as an argument. If so,
1552 	 * we always list it and don't need to do discovery.
1553 	 */
1554 	if (npa->npa_ns != NULL) {
1555 		nvme_ns_info_t *ns_info;
1556 
1557 		if (!nvme_ns_info_snap(npa->npa_ns, &ns_info)) {
1558 			nvmeadm_warn(npa, "failed to get namespace "
1559 			    "information for %s", npa->npa_name);
1560 			goto out;
1561 		}
1562 
1563 		do_list_nsid(npa, info, ns_info);
1564 		nvme_ns_info_free(ns_info);
1565 		rv = 0;
1566 		goto out;
1567 	}
1568 
1569 	if (verbose) {
1570 		level = NVME_NS_DISC_F_ALL;
1571 	} else {
1572 		level = NVME_NS_DISC_F_NOT_IGNORED;
1573 	}
1574 
1575 	if (!nvme_ns_discover_init(npa->npa_ctrl, level, &iter)) {
1576 		nvmeadm_warn(npa, "failed to iterate namespaces on %s",
1577 		    npa->npa_ctrl_name);
1578 		goto out;
1579 	}
1580 
1581 	while ((ret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) {
1582 		nvme_ns_info_t *ns_info;
1583 		uint32_t nsid = nvme_ns_disc_nsid(disc);
1584 
1585 		if (!nvme_ctrl_ns_info_snap(npa->npa_ctrl, nsid, &ns_info)) {
1586 			nvmeadm_warn(npa, "failed to get namespace "
1587 			    "information for %s/%u", npa->npa_ctrl_name, nsid);
1588 			exitcode = -1;
1589 			continue;
1590 		}
1591 
1592 		do_list_nsid(npa, info, ns_info);
1593 		nvme_ns_info_free(ns_info);
1594 	}
1595 
1596 	nvme_ns_discover_fini(iter);
1597 	if (ret == NVME_ITER_ERROR) {
1598 		nvmeadm_warn(npa, "failed to iterate all namespaces on %s",
1599 		    npa->npa_ctrl_name);
1600 	} else {
1601 		rv = 0;
1602 	}
1603 
1604 out:
1605 	nvme_ctrl_info_free(info);
1606 	return (rv);
1607 }
1608 
1609 static void
optparse_identify_ctrl(nvme_process_arg_t * npa)1610 optparse_identify_ctrl(nvme_process_arg_t *npa)
1611 {
1612 	int c;
1613 
1614 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":Cacn")) != -1) {
1615 		switch (c) {
1616 		case 'C':
1617 			npa->npa_cmdflags |= NVMEADM_O_ID_COMMON_NS;
1618 			break;
1619 
1620 		case 'a':
1621 			npa->npa_cmdflags |= NVMEADM_O_ID_ALLOC_NS;
1622 			break;
1623 
1624 		case 'c':
1625 			npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST;
1626 			break;
1627 
1628 		case 'n':
1629 			npa->npa_cmdflags |= NVMEADM_O_ID_NSID_LIST;
1630 			break;
1631 
1632 		case '?':
1633 			errx(-1, "unknown option: -%c", optopt);
1634 
1635 		case ':':
1636 			errx(-1, "option -%c requires an argument", optopt);
1637 		}
1638 	}
1639 }
1640 
1641 static void
usage_identify_ctrl(const char * c_name)1642 usage_identify_ctrl(const char *c_name)
1643 {
1644 	(void) fprintf(stderr, "%s [-C | -c | [-a] -n] <ctl>[,...]\n\n"
1645 	    "  Print detailed information about the specified NVMe "
1646 	    "controllers.\n", c_name);
1647 }
1648 
1649 static int
do_identify_ctrl(const nvme_process_arg_t * npa)1650 do_identify_ctrl(const nvme_process_arg_t *npa)
1651 {
1652 	boolean_t alloc = B_FALSE;
1653 
1654 	if (npa->npa_ns != NULL)
1655 		errx(-1, "identify-controller cannot be used on namespaces");
1656 
1657 	if (npa->npa_argc > 0) {
1658 		errx(-1, "%s passed extraneous arguments starting with %s",
1659 		    npa->npa_cmd->c_name, npa->npa_argv[0]);
1660 	}
1661 
1662 	if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0 &&
1663 	    npa->npa_cmdflags != NVMEADM_O_ID_COMMON_NS) {
1664 		errx(-1, "-C cannot be combined with other flags");
1665 	}
1666 
1667 	if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 &&
1668 	    npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) {
1669 		errx(-1, "-c cannot be combined with other flags");
1670 	}
1671 
1672 	if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0 &&
1673 	    npa->npa_cmdflags !=
1674 	    (NVMEADM_O_ID_ALLOC_NS | NVMEADM_O_ID_NSID_LIST)) {
1675 		errx(-1, "-a can only be used together with -n");
1676 	}
1677 
1678 	if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0) {
1679 		alloc = B_TRUE;
1680 	}
1681 
1682 	if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0) {
1683 		const nvme_identify_nsid_t *idns;
1684 
1685 		if (!nvme_ctrl_info_common_ns(npa->npa_ctrl_info, &idns)) {
1686 			nvmeadm_ctrl_info_warn(npa, "failed to get common "
1687 			    "namespace information for %s", npa->npa_name);
1688 			return (-1);
1689 		}
1690 
1691 		(void) printf("%s: ", npa->npa_name);
1692 		nvme_print_identify_nsid(idns, npa->npa_version);
1693 	} else if ((npa->npa_cmdflags & NVMEADM_O_ID_NSID_LIST) != 0) {
1694 		const char *caption;
1695 		uint32_t cns;
1696 		nvme_identify_nsid_list_t *idnslist;
1697 		nvme_id_req_t *req;
1698 
1699 		if (alloc) {
1700 			caption = "Identify Allocated Namespace List";
1701 			cns = NVME_IDENTIFY_NSID_ALLOC_LIST;
1702 		} else {
1703 			caption = "Identify Active Namespace List";
1704 			cns = NVME_IDENTIFY_NSID_LIST;
1705 		}
1706 
1707 		if ((idnslist = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) {
1708 			err(-1, "failed to allocate identify buffer size");
1709 		}
1710 
1711 		if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM, cns,
1712 		    &req)) {
1713 			nvmeadm_fatal(npa, "failed to initialize %s request",
1714 			    caption);
1715 		}
1716 
1717 		/*
1718 		 * Always set the NSID for these requests to NSID 0 so that way
1719 		 * we can start the list at the beginning. When we encounter
1720 		 * devices with more than 1024 NSIDs then we'll need to issue
1721 		 * additional requests.
1722 		 */
1723 		if (!nvme_id_req_set_nsid(req, 0) ||
1724 		    !nvme_id_req_set_output(req, idnslist,
1725 		    NVME_IDENTIFY_BUFSIZE)) {
1726 			nvmeadm_fatal(npa, "failed to set required fields for "
1727 			    "identify request");
1728 		}
1729 
1730 		if (!nvme_id_req_exec(req)) {
1731 			nvmeadm_fatal(npa, "failed to execute identify "
1732 			    "request");
1733 		}
1734 		nvme_id_req_fini(req);
1735 
1736 		(void) printf("%s: ", npa->npa_name);
1737 
1738 		nvme_print_identify_nsid_list(caption, idnslist);
1739 		free(idnslist);
1740 	} else if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0) {
1741 		nvme_identify_ctrl_list_t *ctlist;
1742 		nvme_id_req_t *req;
1743 
1744 		if ((ctlist = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) {
1745 			err(-1, "failed to allocate identify buffer size");
1746 		}
1747 
1748 		if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM,
1749 		    NVME_IDENTIFY_CTRL_LIST, &req)) {
1750 			nvmeadm_fatal(npa, "failed to initialize identify "
1751 			    "request");
1752 		}
1753 
1754 		if (!nvme_id_req_set_ctrlid(req, 0) ||
1755 		    !nvme_id_req_set_output(req, ctlist,
1756 		    NVME_IDENTIFY_BUFSIZE)) {
1757 			nvmeadm_fatal(npa, "failed to set required fields for "
1758 			    "identify request");
1759 		}
1760 		if (!nvme_id_req_exec(req)) {
1761 			nvmeadm_fatal(npa, "failed to execute identify "
1762 			    "request");
1763 		}
1764 		nvme_id_req_fini(req);
1765 
1766 		(void) printf("%s: ", npa->npa_name);
1767 		nvme_print_identify_ctrl_list("Identify Controller List",
1768 		    ctlist);
1769 		free(ctlist);
1770 	} else {
1771 		uint32_t mpsmin;
1772 
1773 		if (!nvme_ctrl_info_pci_mps_min(npa->npa_ctrl_info,
1774 		    &mpsmin)) {
1775 			nvmeadm_ctrl_info_fatal(npa, "failed to get minimum "
1776 			    "memory page size");
1777 		}
1778 
1779 		(void) printf("%s: ", npa->npa_name);
1780 		nvme_print_identify_ctrl(npa->npa_idctl, mpsmin,
1781 		    npa->npa_version);
1782 	}
1783 
1784 	return (0);
1785 }
1786 
1787 static void
optparse_identify_ns(nvme_process_arg_t * npa)1788 optparse_identify_ns(nvme_process_arg_t *npa)
1789 {
1790 	int c;
1791 
1792 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":cd")) != -1) {
1793 		switch (c) {
1794 		case 'c':
1795 			npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST;
1796 			break;
1797 
1798 		case 'd':
1799 			npa->npa_cmdflags |= NVMEADM_O_ID_DESC_LIST;
1800 			break;
1801 
1802 		case '?':
1803 			errx(-1, "unknown option: -%c", optopt);
1804 
1805 		case ':':
1806 			errx(-1, "option -%c requires an argument", optopt);
1807 		}
1808 	}
1809 }
1810 
1811 static void
usage_identify_ns(const char * c_name)1812 usage_identify_ns(const char *c_name)
1813 {
1814 	(void) fprintf(stderr, "%s [-c | -d ] <ctl>/<ns>[,...]\n\n"
1815 	    "  Print detailed information about the specified NVMe "
1816 	    "namespaces.\n", c_name);
1817 }
1818 
1819 static int
do_identify_ns(const nvme_process_arg_t * npa)1820 do_identify_ns(const nvme_process_arg_t *npa)
1821 {
1822 	uint32_t nsid;
1823 
1824 	if (npa->npa_ns == NULL)
1825 		errx(-1, "identify-namespace cannot be used on controllers");
1826 
1827 	if (npa->npa_argc > 0) {
1828 		errx(-1, "%s passed extraneous arguments starting with %s",
1829 		    npa->npa_cmd->c_name, npa->npa_argv[0]);
1830 	}
1831 
1832 	if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 &&
1833 	    npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) {
1834 		errx(-1, "-c cannot be combined with other flags");
1835 	}
1836 
1837 	if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0 &&
1838 	    npa->npa_cmdflags != NVMEADM_O_ID_DESC_LIST) {
1839 		errx(-1, "-d cannot be combined with other flags");
1840 	}
1841 
1842 	if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0) {
1843 		errx(-1, "-a cannot be used on namespaces");
1844 	}
1845 
1846 	nsid = nvme_ns_info_nsid(npa->npa_ns_info);
1847 
1848 	if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0) {
1849 		nvme_identify_ctrl_list_t *ctlist;
1850 		nvme_id_req_t *req;
1851 
1852 		if ((ctlist = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) {
1853 			err(-1, "failed to allocate identify buffer size");
1854 		}
1855 
1856 		if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM,
1857 		    NVME_IDENTIFY_NSID_CTRL_LIST, &req)) {
1858 			nvmeadm_fatal(npa, "failed to initialize identify "
1859 			    "request");
1860 		}
1861 
1862 		if (!nvme_id_req_set_nsid(req, nsid) ||
1863 		    !nvme_id_req_set_ctrlid(req, 0) ||
1864 		    !nvme_id_req_set_output(req, ctlist,
1865 		    NVME_IDENTIFY_BUFSIZE)) {
1866 			nvmeadm_fatal(npa, "failed to set required fields for "
1867 			    "identify request");
1868 		}
1869 
1870 		if (!nvme_id_req_exec(req)) {
1871 			nvmeadm_fatal(npa, "failed to execute identify "
1872 			    "request");
1873 		}
1874 		nvme_id_req_fini(req);
1875 
1876 		(void) printf("%s: ", npa->npa_name);
1877 		nvme_print_identify_ctrl_list(
1878 		    "Identify Attached Controller List", ctlist);
1879 		free(ctlist);
1880 	} else if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0) {
1881 		nvme_identify_nsid_desc_t *nsdesc;
1882 		nvme_id_req_t *req;
1883 
1884 		if ((nsdesc = malloc(NVME_IDENTIFY_BUFSIZE)) == NULL) {
1885 			err(-1, "failed to allocate identify buffer size");
1886 		}
1887 
1888 		if (!nvme_id_req_init_by_cns(npa->npa_ctrl, NVME_CSI_NVM,
1889 		    NVME_IDENTIFY_NSID_DESC, &req)) {
1890 			nvmeadm_fatal(npa, "failed to initialize identify "
1891 			    "request");
1892 		}
1893 
1894 		if (!nvme_id_req_set_nsid(req, nsid) ||
1895 		    !nvme_id_req_set_output(req, nsdesc,
1896 		    NVME_IDENTIFY_BUFSIZE)) {
1897 			nvmeadm_fatal(npa, "failed to set required fields for "
1898 			    "identify request");
1899 		}
1900 
1901 		if (!nvme_id_req_exec(req)) {
1902 			nvmeadm_fatal(npa, "failed to execute identify "
1903 			    "request");
1904 		}
1905 		nvme_id_req_fini(req);
1906 
1907 		(void) printf("%s: ", npa->npa_name);
1908 		nvme_print_identify_nsid_desc(nsdesc);
1909 		free(nsdesc);
1910 	} else {
1911 		const nvme_identify_nsid_t *idns;
1912 
1913 		(void) printf("%s: ", npa->npa_name);
1914 		idns = nvme_ns_info_identify(npa->npa_ns_info);
1915 		nvme_print_identify_nsid(idns, npa->npa_version);
1916 	}
1917 
1918 	return (0);
1919 }
1920 
1921 static void
optparse_identify(nvme_process_arg_t * npa)1922 optparse_identify(nvme_process_arg_t *npa)
1923 {
1924 	int c;
1925 
1926 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":Cacdn")) != -1) {
1927 		switch (c) {
1928 		case 'C':
1929 			npa->npa_cmdflags |= NVMEADM_O_ID_COMMON_NS;
1930 			break;
1931 
1932 		case 'a':
1933 			npa->npa_cmdflags |= NVMEADM_O_ID_ALLOC_NS;
1934 			break;
1935 
1936 		case 'c':
1937 			npa->npa_cmdflags |= NVMEADM_O_ID_CTRL_LIST;
1938 			break;
1939 
1940 		case 'd':
1941 			npa->npa_cmdflags |= NVMEADM_O_ID_DESC_LIST;
1942 			break;
1943 
1944 		case 'n':
1945 			npa->npa_cmdflags |= NVMEADM_O_ID_NSID_LIST;
1946 			break;
1947 
1948 		case '?':
1949 			errx(-1, "unknown option: -%c", optopt);
1950 
1951 		case ':':
1952 			errx(-1, "option -%c requires an argument", optopt);
1953 
1954 		}
1955 	}
1956 
1957 	if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0 &&
1958 	    (npa->npa_cmdflags &
1959 	    ~(NVMEADM_O_ID_ALLOC_NS | NVMEADM_O_ID_NSID_LIST)) != 0) {
1960 		errx(-1, "-a can only be used alone or together with -n");
1961 	}
1962 
1963 	if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0 &&
1964 	    npa->npa_cmdflags != NVMEADM_O_ID_COMMON_NS) {
1965 		errx(-1, "-C cannot be combined with other flags");
1966 
1967 	}
1968 
1969 	if ((npa->npa_cmdflags & NVMEADM_O_ID_CTRL_LIST) != 0 &&
1970 	    npa->npa_cmdflags != NVMEADM_O_ID_CTRL_LIST) {
1971 		errx(-1, "-c cannot be combined with other flags");
1972 	}
1973 
1974 	if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0 &&
1975 	    npa->npa_cmdflags != NVMEADM_O_ID_DESC_LIST) {
1976 		errx(-1, "-d cannot be combined with other flags");
1977 	}
1978 }
1979 
1980 static void
usage_identify(const char * c_name)1981 usage_identify(const char *c_name)
1982 {
1983 	(void) fprintf(stderr,
1984 	    "%s [ -C | -c | -d | [-a] -n ] <ctl>[/<ns>][,...]\n\n"
1985 	    "  Print detailed information about the specified NVMe "
1986 	    "controllers and/or name-\n  spaces.\n", c_name);
1987 }
1988 
1989 static int
do_identify(const nvme_process_arg_t * npa)1990 do_identify(const nvme_process_arg_t *npa)
1991 {
1992 	if (npa->npa_argc > 0) {
1993 		errx(-1, "%s passed extraneous arguments starting with %s",
1994 		    npa->npa_cmd->c_name, npa->npa_argv[0]);
1995 	}
1996 
1997 	if (npa->npa_ns != NULL) {
1998 		if ((npa->npa_cmdflags & NVMEADM_O_ID_COMMON_NS) != 0)
1999 			errx(-1, "-C cannot be used on namespaces");
2000 
2001 		if ((npa->npa_cmdflags & NVMEADM_O_ID_ALLOC_NS) != 0)
2002 			errx(-1, "-a cannot be used on namespaces");
2003 
2004 		if ((npa->npa_cmdflags & NVMEADM_O_ID_NSID_LIST) != 0)
2005 			errx(-1, "-n cannot be used on namespaces");
2006 
2007 		return (do_identify_ns(npa));
2008 	} else {
2009 		if ((npa->npa_cmdflags & NVMEADM_O_ID_DESC_LIST) != 0)
2010 			errx(-1, "-d cannot be used on controllers");
2011 
2012 		return (do_identify_ctrl(npa));
2013 	}
2014 }
2015 
2016 static void
optparse_list_logs(nvme_process_arg_t * npa)2017 optparse_list_logs(nvme_process_arg_t *npa)
2018 {
2019 	int c;
2020 	uint_t oflags = 0;
2021 	boolean_t parse = B_FALSE;
2022 	const char *fields = NULL;
2023 	char *scope = NULL;
2024 	ofmt_status_t oferr;
2025 	nvmeadm_list_logs_t *nll;
2026 
2027 	if ((nll = calloc(1, sizeof (nvmeadm_list_logs_t))) == NULL) {
2028 		err(-1, "failed to allocate memory to track log information");
2029 	}
2030 
2031 	npa->npa_cmd_arg = nll;
2032 
2033 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":aHo:ps:")) != -1) {
2034 		switch (c) {
2035 		case 'a':
2036 			nll->nll_unimpl = B_TRUE;
2037 			break;
2038 		case 'H':
2039 			oflags |= OFMT_NOHEADER;
2040 			break;
2041 		case 'o':
2042 			fields = optarg;
2043 			break;
2044 		case 'p':
2045 			parse = B_TRUE;
2046 			oflags |= OFMT_PARSABLE;
2047 			break;
2048 		case 's':
2049 			scope = optarg;
2050 			break;
2051 		case '?':
2052 			errx(-1, "unknown option: -%c", optopt);
2053 		case ':':
2054 			errx(-1, "option -%c requires an argument", optopt);
2055 		}
2056 	}
2057 
2058 	if (!parse) {
2059 		oflags |= OFMT_WRAP;
2060 	}
2061 
2062 	if (parse && fields == NULL) {
2063 		errx(-1, "parsable mode (-p) requires fields specified with "
2064 		    "-o");
2065 	}
2066 
2067 	if (fields == NULL) {
2068 		if (nll->nll_unimpl) {
2069 			fields = nvmeadm_list_logs_fields_impl;
2070 		} else {
2071 			fields = nvmeadm_list_logs_fields;
2072 		}
2073 	}
2074 
2075 	if (scope != NULL) {
2076 		const char *str;
2077 
2078 		while ((str = strsep(&scope, ",")) != NULL) {
2079 			if (strcasecmp(str, "nvm") == 0) {
2080 				nll->nll_scope |= NVME_LOG_SCOPE_NVM;
2081 			} else if (strcasecmp(str, "ns") == 0 ||
2082 			    strcasecmp(str, "namespace") == 0) {
2083 				nll->nll_scope |= NVME_LOG_SCOPE_NS;
2084 			} else if (strcasecmp(str, "ctrl") == 0 ||
2085 			    strcasecmp(str, "controller") == 0) {
2086 				nll->nll_scope |= NVME_LOG_SCOPE_CTRL;
2087 			} else {
2088 				errx(-1, "unknown scope string: '%s'; valid "
2089 				    "values are 'nvm', 'namespace', and "
2090 				    "'controller'", str);
2091 			}
2092 		}
2093 	}
2094 
2095 	oferr = ofmt_open(fields, nvmeadm_list_logs_ofmt, oflags, 0,
2096 	    &npa->npa_ofmt);
2097 	ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);
2098 
2099 	if (npa->npa_argc - optind > 1) {
2100 		nll->nll_nfilts = npa->npa_argc - optind - 1;
2101 		nll->nll_filts = npa->npa_argv + optind + 1;
2102 		nll->nll_used = calloc(nll->nll_nfilts, sizeof (boolean_t));
2103 		if (nll->nll_used == NULL) {
2104 			err(-1, "failed to allocate memory for tracking log "
2105 			    "page filters");
2106 		}
2107 	}
2108 }
2109 
2110 static void
usage_list_logs(const char * c_name)2111 usage_list_logs(const char *c_name)
2112 {
2113 	(void) fprintf(stderr, "%s [-H] [-o field,[...] [-p]] [-s scope,[...]] "
2114 	    "[-a]\n\t  <ctl>[/<ns>][,...] [logpage...]\n\n"
2115 	    "  List log pages supported by controllers or namespaces.\n",
2116 	    c_name);
2117 }
2118 
2119 static boolean_t
do_list_logs_match(const nvme_log_disc_t * disc,nvmeadm_list_logs_t * nll)2120 do_list_logs_match(const nvme_log_disc_t *disc, nvmeadm_list_logs_t *nll)
2121 {
2122 	if (!nll->nll_unimpl && !nvme_log_disc_impl(disc)) {
2123 		return (B_FALSE);
2124 	}
2125 
2126 	if (nll->nll_nfilts <= 0) {
2127 		return (B_TRUE);
2128 	}
2129 
2130 	/*
2131 	 * Check all filters. If we have multiple specified that would match we
2132 	 * want that to count.
2133 	 */
2134 	boolean_t match = B_FALSE;
2135 	for (int i = 0; i < nll->nll_nfilts; i++) {
2136 		if (strcmp(nvme_log_disc_name(disc), nll->nll_filts[i]) == 0) {
2137 			nll->nll_used[i] = B_TRUE;
2138 			match = B_TRUE;
2139 			continue;
2140 		}
2141 
2142 		size_t naliases = nvme_log_disc_naliases(disc);
2143 		const char *const *aliases = nvme_log_disc_aliases(disc);
2144 		for (size_t a = 0; a < naliases; a++) {
2145 			if (strcmp(aliases[a], nll->nll_filts[i]) == 0) {
2146 				nll->nll_used[i] = B_TRUE;
2147 				match = B_TRUE;
2148 				break;
2149 			}
2150 		}
2151 	}
2152 
2153 	return (match);
2154 }
2155 
2156 static int
do_list_logs(const nvme_process_arg_t * npa)2157 do_list_logs(const nvme_process_arg_t *npa)
2158 {
2159 	nvme_log_disc_scope_t scope;
2160 	nvme_log_iter_t *iter;
2161 	nvme_iter_t ret;
2162 	const nvme_log_disc_t *disc;
2163 	nvmeadm_list_logs_t *nll = npa->npa_cmd_arg;
2164 
2165 	if (nll->nll_scope != 0) {
2166 		scope = nll->nll_scope;
2167 	} else if (npa->npa_ns != NULL) {
2168 		scope = NVME_LOG_SCOPE_NS;
2169 	} else {
2170 		scope = NVME_LOG_SCOPE_CTRL | NVME_LOG_SCOPE_NVM;
2171 	}
2172 
2173 	if (!nvme_log_discover_init(npa->npa_ctrl, scope, 0, &iter)) {
2174 		nvmeadm_warn(npa, "failed to iterate logs on %s",
2175 		    npa->npa_ctrl_name);
2176 		return (-1);
2177 	}
2178 
2179 	while ((ret = nvme_log_discover_step(iter, &disc)) == NVME_ITER_VALID) {
2180 		if (do_list_logs_match(disc, nll)) {
2181 			nvmeadm_list_logs_ofmt_arg_t print;
2182 
2183 			print.nlloa_name = npa->npa_name;
2184 			print.nlloa_disc = disc;
2185 			ofmt_print(npa->npa_ofmt, &print);
2186 			nll->nll_nprint++;
2187 		}
2188 	}
2189 
2190 	nvme_log_discover_fini(iter);
2191 	if (ret == NVME_ITER_ERROR) {
2192 		nvmeadm_warn(npa, "failed to iterate logs on %s",
2193 		    npa->npa_ctrl_name);
2194 		return (-1);
2195 	}
2196 
2197 	for (int i = 0; i < nll->nll_nfilts; i++) {
2198 		if (!nll->nll_used[i]) {
2199 			warnx("log page filter '%s' did match any log pages",
2200 			    nll->nll_filts[i]);
2201 			exitcode = -1;
2202 		}
2203 	}
2204 
2205 	if (nll->nll_nprint == 0) {
2206 		if (nll->nll_nfilts == 0) {
2207 			warnx("no log pages found for %s", npa->npa_name);
2208 		}
2209 		exitcode = -1;
2210 	}
2211 
2212 	return (exitcode);
2213 }
2214 
2215 static void
usage_get_logpage(const char * c_name)2216 usage_get_logpage(const char *c_name)
2217 {
2218 	(void) fprintf(stderr, "%s [-O file | -x | -p -o field,[...] [-H]]\n"
2219 	    "\t  <ctl>[/<ns>][,...] <logpage> [filter...]\n\n"
2220 	    "  Print the specified log page of the specified NVMe "
2221 	    "controllers and/or name-\n  spaces. Run \"nvmeadm list-logpages\" "
2222 	    "for supported log pages. All devices\n  support error, health, "
2223 	    "and firmware.\n", c_name);
2224 }
2225 
2226 static void
usage_firmware_list(const char * c_name)2227 usage_firmware_list(const char *c_name)
2228 {
2229 	(void) fprintf(stderr, "%s <ctl>\n\n"
2230 	    "  Print the log page that contains the list of firmware "
2231 	    "images installed on the specified NVMe controller.\n", c_name);
2232 }
2233 
2234 static uint64_t
do_get_logpage_size(const nvme_process_arg_t * npa,nvme_log_disc_t * disc,nvme_log_req_t * req)2235 do_get_logpage_size(const nvme_process_arg_t *npa, nvme_log_disc_t *disc,
2236     nvme_log_req_t *req)
2237 {
2238 	uint64_t len, ret;
2239 	void *buf;
2240 	nvme_log_size_kind_t kind;
2241 
2242 	kind = nvme_log_disc_size(disc, &len);
2243 	if (kind != NVME_LOG_SIZE_K_VAR) {
2244 		return (len);
2245 	}
2246 
2247 	/*
2248 	 * We have a log with a variable length size. To determine the actual
2249 	 * size we must actually determine the full length of this.
2250 	 */
2251 	if ((buf = malloc(len)) == NULL) {
2252 		errx(-1, "failed to allocate %zu byte buffer to get log "
2253 		    "page size", len);
2254 	}
2255 
2256 	if (!nvme_log_req_set_output(req, buf, len)) {
2257 		nvmeadm_fatal(npa, "failed to set output parameters to "
2258 		    "determine log length");
2259 	}
2260 
2261 	if (!nvme_log_req_exec(req)) {
2262 		nvmeadm_fatal(npa, "failed to execute log request %s to "
2263 		    "determine log length", npa->npa_argv[0]);
2264 	}
2265 
2266 	if (!nvme_log_disc_calc_size(disc, &ret, buf, len)) {
2267 		errx(-1, "failed to determine full %s log length",
2268 		    npa->npa_argv[0]);
2269 	}
2270 
2271 	free(buf);
2272 	return (ret);
2273 }
2274 
2275 static void
do_get_logpage_dump(const void * buf,size_t len,const char * file)2276 do_get_logpage_dump(const void *buf, size_t len, const char *file)
2277 {
2278 	size_t off = 0;
2279 	int fd = open(file, O_WRONLY | O_TRUNC | O_CREAT, 0644);
2280 
2281 	if (fd < 0) {
2282 		err(-1, "failed to create output file %s", file);
2283 	}
2284 
2285 	while (len > 0) {
2286 		ssize_t ret = write(fd, buf + off, len - off);
2287 		if (ret < 0) {
2288 			err(EXIT_FAILURE, "failed to write log data to file %s "
2289 			    "at offset %zu", file, off);
2290 		}
2291 
2292 		off += (size_t)ret;
2293 		len -= (size_t)ret;
2294 	}
2295 
2296 	(void) close(fd);
2297 }
2298 
2299 /*
2300  * Here we need to explicitly attempt to release any context that has previously
2301  * existed for the persistent event log. It is fine if none exists as the
2302  * controller is required not to error. However, if we don't do this and attempt
2303  * to establish a new context, then it will generate an error.
2304  *
2305  * We'll use our existing request, which doesn't ask for data yet and issue the
2306  * get log page request with the LSP in question. After it is completed, we'll
2307  * reset the LSP to establish a context.
2308  */
2309 static void
do_get_logpage_pev_relctx(const nvme_process_arg_t * npa,nvme_log_req_t * req)2310 do_get_logpage_pev_relctx(const nvme_process_arg_t *npa, nvme_log_req_t *req)
2311 {
2312 	uint32_t buf;
2313 
2314 	if (!nvme_log_req_set_lsp(req, NVME_PEV_LSP_REL_CTX)) {
2315 		nvmeadm_fatal(npa, "failed to set lsp to release the "
2316 		    "persistent event log context");
2317 	}
2318 
2319 	/*
2320 	 * In NVMe 2.0 the spec made it explicit that the controller was
2321 	 * supposed to ignore the length and offset for a request to release the
2322 	 * context; however, that wasn't present in NVMe 1.4. The number of
2323 	 * dwords part of the get log page command is a zeros based value,
2324 	 * meaning there is no explicit way to request zero bytes. Rather than
2325 	 * trust that all controllers get this right (especially when it wasn't
2326 	 * exactly specified in NVMe 1.4), we just toss a throwaway buffer here.
2327 	 */
2328 	if (!nvme_log_req_set_output(req, &buf, sizeof (buf))) {
2329 		nvmeadm_fatal(npa, "failed to set zero log length for "
2330 		    "persistent event log release context");
2331 	}
2332 
2333 	if (!nvme_log_req_exec(req)) {
2334 		nvmeadm_fatal(npa, "failed to execute log request %s to "
2335 		    "release the event log context", npa->npa_argv[0]);
2336 	}
2337 
2338 	if (!nvme_log_req_set_lsp(req, NVME_PEV_LSP_EST_CTX_READ)) {
2339 		nvmeadm_fatal(npa, "failed to set lsp to establish the "
2340 		    "persistent event log context");
2341 	}
2342 
2343 	/*
2344 	 * Make sure that our stack buffer is no longer part of the log request.
2345 	 */
2346 	if (!nvme_log_req_clear_output(req)) {
2347 		nvmeadm_fatal(npa, "failed to clear output from persistent "
2348 		    "event log release context");
2349 	}
2350 }
2351 
2352 /*
2353  * Fill in the baseline context for a phyeye request. In this case we always
2354  * perform a normal read command and set the quality to "good" (the
2355  * lowest quality). Configurable use of this is driven through the phyeye
2356  * commands in nvmeadm_phyeye.c. In addition, we must always set the controller
2357  * ID in this request to our own.
2358  */
2359 static void
do_get_logpage_phyeye_ctx(const nvme_process_arg_t * npa,nvme_log_req_t * req)2360 do_get_logpage_phyeye_ctx(const nvme_process_arg_t *npa, nvme_log_req_t *req)
2361 {
2362 	nvme_eom_lsp_t lsp;
2363 
2364 	(void) memset(&lsp, 0, sizeof (lsp));
2365 	lsp.nel_mqual = NVME_EOM_LSP_MQUAL_GOOD;
2366 	lsp.nel_act = NVME_EOM_LSP_READ;
2367 
2368 	if (!nvme_log_req_set_lsp(req, lsp.r)) {
2369 		nvmeadm_fatal(npa, "failed to set lsp for host phyeye");
2370 	}
2371 
2372 	if (!nvme_log_req_set_lsi(req, npa->npa_idctl->id_cntlid)) {
2373 		nvmeadm_fatal(npa, "failed to set lsi for host phyeye");
2374 	}
2375 }
2376 
2377 static int
do_get_logpage_common(const nvme_process_arg_t * npa,const char * page,nvmeadm_field_filt_t * filts,size_t nfilts)2378 do_get_logpage_common(const nvme_process_arg_t *npa, const char *page,
2379     nvmeadm_field_filt_t *filts, size_t nfilts)
2380 {
2381 	int ret = 0;
2382 	nvme_log_disc_t *disc;
2383 	nvme_log_req_t *req;
2384 	nvme_log_disc_scope_t scope;
2385 	void *buf;
2386 	size_t toalloc;
2387 	nvmeadm_get_logpage_t *log = npa->npa_cmd_arg;
2388 
2389 	/*
2390 	 * If we have enough information to identify a log-page via libnvme (or
2391 	 * in the future take enough options to allow us to actually do this
2392 	 * manually), then we will fetch it. If we don't know how to print it,
2393 	 * then we'll just hex dump it for now.
2394 	 */
2395 	if (!nvme_log_req_init_by_name(npa->npa_ctrl, page, 0, &disc, &req)) {
2396 		nvmeadm_fatal(npa, "could not initialize log request for %s",
2397 		    page);
2398 	}
2399 
2400 	if (npa->npa_ns != NULL) {
2401 		scope = NVME_LOG_SCOPE_NS;
2402 	} else {
2403 		scope = NVME_LOG_SCOPE_CTRL | NVME_LOG_SCOPE_NVM;
2404 	}
2405 
2406 	if ((scope & nvme_log_disc_scopes(disc)) == 0) {
2407 		errx(-1, "log page %s does not support operating on %s", page,
2408 		    npa->npa_ns != NULL ? "namespaces" : "controllers");
2409 	}
2410 
2411 	/*
2412 	 * In the future we should add options to allow one to specify and set
2413 	 * the fields for the lsp, lsi, etc. and set them here. Some log pages
2414 	 * need a specific lsp set and special handling related to contexts. Do
2415 	 * that now.
2416 	 */
2417 	switch (nvme_log_disc_lid(disc)) {
2418 	case NVME_LOGPAGE_PEV:
2419 		do_get_logpage_pev_relctx(npa, req);
2420 		break;
2421 	case NVME_LOGPAGE_TELMHOST:
2422 		return (do_get_logpage_telemetry(npa, disc, req));
2423 	case NVME_LOGPAGE_PHYEYE:
2424 		do_get_logpage_phyeye_ctx(npa, req);
2425 		break;
2426 	default:
2427 		break;
2428 	}
2429 
2430 	if (npa->npa_ns != NULL) {
2431 		uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info);
2432 
2433 		if (!nvme_log_req_set_nsid(req, nsid)) {
2434 			nvmeadm_fatal(npa, "failed to set log request "
2435 			    "namespace ID to 0x%x", nsid);
2436 		}
2437 	}
2438 
2439 	/*
2440 	 * The output size should be the last thing that we determine as we may
2441 	 * need to issue a log request to figure out how much data we should
2442 	 * actually be reading.
2443 	 */
2444 	toalloc = do_get_logpage_size(npa, disc, req);
2445 	buf = malloc(toalloc);
2446 	if (buf == NULL) {
2447 		err(-1, "failed to allocate %zu bytes for log "
2448 		    "request %s", toalloc, page);
2449 	}
2450 
2451 	if (!nvme_log_req_set_output(req, buf, toalloc)) {
2452 		nvmeadm_fatal(npa, "failed to set output parameters");
2453 	}
2454 
2455 	/*
2456 	 * Again, we need to potentially adjust specific LSP values here for the
2457 	 * various contexts that exist. Note that we are reusing the existing
2458 	 * request so if the prior values for the initial request are fine, then
2459 	 * we can just leave it there.
2460 	 */
2461 	switch (nvme_log_disc_lid(disc)) {
2462 	case NVME_LOGPAGE_PEV:
2463 		if (!nvme_log_req_set_lsp(req, NVME_PEV_LSP_READ)) {
2464 			nvmeadm_fatal(npa, "failed to set lsp to read the "
2465 			    "persistent event log");
2466 		}
2467 		break;
2468 	default:
2469 		break;
2470 	}
2471 
2472 	if (!nvme_log_req_exec(req)) {
2473 		nvmeadm_fatal(npa, "failed to execute log request %s",
2474 		    npa->npa_argv[0]);
2475 	}
2476 
2477 	if (log != NULL && log->ngl_output != NULL) {
2478 		do_get_logpage_dump(buf, toalloc, log->ngl_output);
2479 		goto done;
2480 	} else if (log != NULL && log->ngl_hex) {
2481 		nvmeadm_dump_hex(buf, toalloc);
2482 		goto done;
2483 	}
2484 
2485 	if (npa->npa_ofmt == NULL) {
2486 		(void) printf("%s: ", npa->npa_name);
2487 	}
2488 	if (strcmp(page, "error") == 0) {
2489 		size_t nlog = toalloc / sizeof (nvme_error_log_entry_t);
2490 		if (nfilts > 0) {
2491 			warnx("log filters are not currently supported with "
2492 			    "the %s log page", page);
2493 			ret = -1;
2494 		}
2495 
2496 		nvme_print_error_log(nlog, buf, npa->npa_version);
2497 	} else if (strcmp(page, "health") == 0) {
2498 		if (nfilts > 0) {
2499 			warnx("log filters are not currently supported with "
2500 			    "the %s log page", page);
2501 			ret = -1;
2502 		}
2503 
2504 		nvme_print_health_log(buf, npa->npa_idctl, npa->npa_version);
2505 	} else if (strcmp(page, "firmware") == 0) {
2506 		if (nfilts > 0) {
2507 			warnx("log filters are not currently supported with "
2508 			    "the %s log page", page);
2509 			ret = -1;
2510 		}
2511 
2512 		nvme_print_fwslot_log(buf, npa->npa_idctl);
2513 	} else {
2514 		if (npa->npa_ofmt == NULL) {
2515 			(void) printf("%s (%s)\n", nvme_log_disc_desc(disc),
2516 			    page);
2517 		}
2518 		if (!nvmeadm_log_page_fields(npa, page, buf, toalloc, filts,
2519 		    nfilts, 0)) {
2520 			ret = -1;
2521 		}
2522 	}
2523 
2524 done:
2525 	free(buf);
2526 	nvme_log_disc_free(disc);
2527 	nvme_log_req_fini(req);
2528 
2529 	return (ret);
2530 }
2531 
2532 static int
do_get_logpage_fwslot(const nvme_process_arg_t * npa)2533 do_get_logpage_fwslot(const nvme_process_arg_t *npa)
2534 {
2535 	if (npa->npa_argc >= 1) {
2536 		warnx("no additional arguments may be specified to %s",
2537 		    npa->npa_cmd->c_name);
2538 		usage(npa->npa_cmd);
2539 		exit(-1);
2540 	}
2541 
2542 	return (do_get_logpage_common(npa, "firmware", NULL, 0));
2543 }
2544 
2545 static void
optparse_get_logpage(nvme_process_arg_t * npa)2546 optparse_get_logpage(nvme_process_arg_t *npa)
2547 {
2548 	int c;
2549 	const char *fields = NULL;
2550 	bool parse = false;
2551 	uint_t oflags = 0;
2552 	nvmeadm_get_logpage_t *log;
2553 	uint_t count = 0;
2554 
2555 	if ((log = calloc(1, sizeof (nvmeadm_get_logpage_t))) == NULL) {
2556 		err(-1, "failed to allocate memory to track log page "
2557 		    "information");
2558 	}
2559 
2560 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":Ho:O:px")) != -1) {
2561 		switch (c) {
2562 		case 'H':
2563 			oflags |= OFMT_NOHEADER;
2564 			break;
2565 		case 'o':
2566 			fields = optarg;
2567 			break;
2568 		case 'O':
2569 			log->ngl_output = optarg;
2570 			count++;
2571 			break;
2572 		case 'p':
2573 			parse = true;
2574 			oflags |= OFMT_PARSABLE;
2575 			count++;
2576 			break;
2577 		case 'x':
2578 			log->ngl_hex = true;
2579 			count++;
2580 			break;
2581 		case '?':
2582 			errx(-1, "unknown option: -%c", optopt);
2583 		case ':':
2584 			errx(-1, "option -%c requires an argument", optopt);
2585 		}
2586 	}
2587 
2588 	if (parse && fields == NULL) {
2589 		errx(-1, "parsable mode (-p) requires fields specified with "
2590 		    "-o");
2591 	}
2592 
2593 	if (!parse && fields != NULL) {
2594 		errx(-1, "output field selection (-o) only usable in "
2595 		    "parsable mode (-p)");
2596 	}
2597 
2598 	if (!parse && (oflags & OFMT_NOHEADER) != 0) {
2599 		errx(-1, "omitting headers (-H) only usable in parsable mode "
2600 		    "(-p)");
2601 	}
2602 
2603 	if (count > 1) {
2604 		errx(-1, "only one of parsable output (-p), outputing to a "
2605 		    "file (-O), and hexadecimal output (-x) may be requested");
2606 	}
2607 
2608 	if (parse) {
2609 		ofmt_status_t oferr = ofmt_open(fields, nvmeadm_field_ofmt,
2610 		    oflags, 0, &npa->npa_ofmt);
2611 		ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);
2612 	}
2613 
2614 	npa->npa_cmd_arg = log;
2615 }
2616 
2617 static void
do_logpage_filts(const nvme_process_arg_t * npa,nvmeadm_field_filt_t ** filtsp,size_t * nfiltsp)2618 do_logpage_filts(const nvme_process_arg_t *npa, nvmeadm_field_filt_t **filtsp,
2619     size_t *nfiltsp)
2620 {
2621 	size_t nfilts;
2622 	nvmeadm_field_filt_t *filts;
2623 
2624 	*filtsp = NULL;
2625 	*nfiltsp = 0;
2626 
2627 	if (npa->npa_argc <= 1)
2628 		return;
2629 
2630 	nfilts = (size_t)npa->npa_argc - 1;
2631 	filts = calloc(nfilts, sizeof (nvmeadm_field_filt_t));
2632 	if (filts == NULL) {
2633 		err(-1, "failed to allocate memory for filter "
2634 		    "tracking");
2635 	}
2636 
2637 	for (size_t i = 0; i < nfilts; i++) {
2638 		filts[i].nff_len = strlen(npa->npa_argv[i + 1]);
2639 		filts[i].nff_str = npa->npa_argv[i + 1];
2640 	}
2641 
2642 	*filtsp = filts;
2643 	*nfiltsp = nfilts;
2644 }
2645 
2646 static int
do_get_logpage(const nvme_process_arg_t * npa)2647 do_get_logpage(const nvme_process_arg_t *npa)
2648 {
2649 	size_t nfilts = 0;
2650 	nvmeadm_field_filt_t *filts = NULL;
2651 	nvmeadm_get_logpage_t *log = npa->npa_cmd_arg;
2652 
2653 	if (npa->npa_argc < 1) {
2654 		warnx("missing log page name");
2655 		usage(npa->npa_cmd);
2656 		exit(-1);
2657 	}
2658 
2659 	do_logpage_filts(npa, &filts, &nfilts);
2660 
2661 	if (log != NULL && log->ngl_hex && nfilts > 0) {
2662 		errx(-1, "hexadecimal output (-x) cannot be used with "
2663 		    "log filter operands");
2664 	}
2665 
2666 	return (do_get_logpage_common(npa, npa->npa_argv[0], filts, nfilts));
2667 }
2668 
2669 static void
usage_print_logpage(const char * c_name)2670 usage_print_logpage(const char *c_name)
2671 {
2672 	(void) fprintf(stderr, "%s -f file [-x | -p -o field,[...] [-H]] "
2673 	    "<logpage>\n\t  [filter...]\n\n"
2674 	    "  Print the specified log page from a file. Optional filters "
2675 	    "may be used to\n  restrict the log fields printed. See nvmeadm(8) "
2676 	    "for a list of all log\n  pages. Run \"nvmeadm list-logpages\" to "
2677 	    "see log pages a specific controller\n  supports.\n", c_name);
2678 }
2679 
2680 typedef struct {
2681 	const char *pl_input;
2682 	bool pl_hex;
2683 } print_logpages_t;
2684 
2685 static void
optparse_print_logpage(nvme_process_arg_t * npa)2686 optparse_print_logpage(nvme_process_arg_t *npa)
2687 {
2688 	int c;
2689 	const char *fields = NULL;
2690 	bool parse = false;
2691 	uint_t oflags = 0;
2692 	print_logpages_t *pl;
2693 
2694 	if ((pl = calloc(1, sizeof (print_logpages_t))) == NULL) {
2695 		err(-1, "failed to allocate memory for option tracking");
2696 	}
2697 
2698 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":f:Ho:px")) != -1) {
2699 		switch (c) {
2700 		case 'f':
2701 			pl->pl_input = optarg;
2702 			break;
2703 		case 'H':
2704 			oflags |= OFMT_NOHEADER;
2705 			break;
2706 		case 'o':
2707 			fields = optarg;
2708 			break;
2709 		case 'p':
2710 			parse = true;
2711 			oflags |= OFMT_PARSABLE;
2712 			break;
2713 		case 'x':
2714 			pl->pl_hex = true;
2715 			break;
2716 		case '?':
2717 			errx(-1, "unknown option: -%c", optopt);
2718 		case ':':
2719 			errx(-1, "option -%c requires an argument", optopt);
2720 		}
2721 	}
2722 
2723 	if (pl->pl_input == NULL) {
2724 		errx(-1, "missing required input file to process (-f)");
2725 	}
2726 
2727 	if (parse && fields == NULL) {
2728 		errx(-1, "parsable mode (-p) requires fields specified with "
2729 		    "-o");
2730 	}
2731 
2732 	if (!parse && fields != NULL) {
2733 		errx(-1, "output field selection (-o) only usable in "
2734 		    "parsable mode (-p)");
2735 	}
2736 
2737 	if (!parse && (oflags & OFMT_NOHEADER) != 0) {
2738 		errx(-1, "omitting headers (-H) only usable in parsable mode "
2739 		    "(-p)");
2740 	}
2741 
2742 	if (pl->pl_hex && parse) {
2743 		errx(-1, "only one of parsable output (-p) and heaxdecimal "
2744 		    "output (-x) may be requested");
2745 	}
2746 
2747 	if (parse) {
2748 		ofmt_status_t oferr = ofmt_open(fields, nvmeadm_field_ofmt,
2749 		    oflags, 0, &npa->npa_ofmt);
2750 		ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);
2751 	}
2752 
2753 	npa->npa_cmd_arg = pl;
2754 }
2755 
2756 static int
do_print_logpage(const nvme_process_arg_t * npa)2757 do_print_logpage(const nvme_process_arg_t *npa)
2758 {
2759 	int fd = -1, ret = 0;
2760 	struct stat st;
2761 	void *data;
2762 	const char *logpage = NULL;
2763 	size_t nfilts = 0;
2764 	nvmeadm_field_filt_t *filts = NULL;
2765 	print_logpages_t *pl = npa->npa_cmd_arg;
2766 	nvmeadm_log_field_flag_t flag = 0;
2767 
2768 	if (pl->pl_hex) {
2769 		if (npa->npa_argc != 0) {
2770 			errx(-1, "log page and filters not supported with -x");
2771 		}
2772 	} else {
2773 		if (npa->npa_argc == 0) {
2774 			errx(-1, "missing required log page");
2775 		}
2776 
2777 		logpage = npa->npa_argv[0];
2778 		do_logpage_filts(npa, &filts, &nfilts);
2779 		flag |= NVMEADM_LFF_CHECK_NAME;
2780 	}
2781 
2782 	if ((fd = open(pl->pl_input, O_RDONLY)) < 0) {
2783 		err(-1, "failed to open input file %s", pl->pl_input);
2784 	}
2785 
2786 	if (fstat(fd, &st) != 0) {
2787 		err(-1, "failed to stat %s", pl->pl_input);
2788 	}
2789 
2790 	if (st.st_size > NVMEADM_MAX_MMAP) {
2791 		errx(-1, "%s file size of 0x%lx exceeds maximum allowed size "
2792 		    "of 0x%llx", pl->pl_input, st.st_size, NVMEADM_MAX_MMAP);
2793 	}
2794 
2795 	data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
2796 	if (data == MAP_FAILED) {
2797 		errx(-1, "failed to mmap %s", pl->pl_input);
2798 	}
2799 
2800 	if (!nvmeadm_log_page_fields(npa, logpage, data, st.st_size,
2801 	    filts, nfilts, flag)) {
2802 		ret = -1;
2803 	}
2804 
2805 	VERIFY0(munmap(data, st.st_size));
2806 	VERIFY0(close(fd));
2807 	free(filts);
2808 
2809 	return (ret);
2810 }
2811 
2812 static void
optparse_list_features(nvme_process_arg_t * npa)2813 optparse_list_features(nvme_process_arg_t *npa)
2814 {
2815 	int c;
2816 	uint_t oflags = 0;
2817 	boolean_t parse = B_FALSE;
2818 	const char *fields = NULL;
2819 	nvmeadm_features_t *feat;
2820 	ofmt_status_t oferr;
2821 
2822 	if ((feat = calloc(1, sizeof (nvmeadm_features_t))) == NULL) {
2823 		err(-1, "failed to allocate memory to track feature "
2824 		    "information");
2825 	}
2826 
2827 	npa->npa_cmd_arg = feat;
2828 
2829 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":aHo:p")) != -1) {
2830 		switch (c) {
2831 		case 'a':
2832 			feat->nf_unimpl = B_TRUE;
2833 			break;
2834 		case 'H':
2835 			oflags |= OFMT_NOHEADER;
2836 			break;
2837 		case 'o':
2838 			fields = optarg;
2839 			break;
2840 		case 'p':
2841 			parse = B_TRUE;
2842 			oflags |= OFMT_PARSABLE;
2843 			break;
2844 		case '?':
2845 			errx(-1, "unknown option: -%c", optopt);
2846 		case ':':
2847 			errx(-1, "option -%c requires an argument", optopt);
2848 		}
2849 	}
2850 
2851 	if (!parse) {
2852 		oflags |= OFMT_WRAP;
2853 	}
2854 
2855 	if (parse && fields == NULL) {
2856 		errx(-1, "parsable mode (-p) requires fields specified with "
2857 		    "-o");
2858 	}
2859 
2860 	if (fields == NULL) {
2861 		fields = nvmeadm_list_features_fields;
2862 	}
2863 
2864 	oferr = ofmt_open(fields, nvmeadm_list_features_ofmt, oflags, 0,
2865 	    &npa->npa_ofmt);
2866 	ofmt_check(oferr, B_TRUE, npa->npa_ofmt, nvme_oferr, warnx);
2867 
2868 	if (npa->npa_argc - optind > 1) {
2869 		feat->nf_nfilts = (uint32_t)(npa->npa_argc - optind - 1);
2870 		feat->nf_filts = npa->npa_argv + optind + 1;
2871 		feat->nf_used = calloc(feat->nf_nfilts, sizeof (boolean_t));
2872 		if (feat->nf_used == NULL) {
2873 			err(-1, "failed to allocate memory for tracking "
2874 			    "feature filters");
2875 		}
2876 	}
2877 }
2878 
2879 static void
usage_list_features(const char * c_name)2880 usage_list_features(const char *c_name)
2881 {
2882 	(void) fprintf(stderr, "%s [-a] [-H] [-o field,[...] [-p]] "
2883 	    "<ctl>[/<ns>][,...]\n\t  [feature...]\n\n"
2884 	    "  List features supported by controllers or namespaces.\n",
2885 	    c_name);
2886 }
2887 
2888 static boolean_t
do_features_match(const nvme_feat_disc_t * disc,nvmeadm_features_t * nf)2889 do_features_match(const nvme_feat_disc_t *disc, nvmeadm_features_t *nf)
2890 {
2891 	if (nf->nf_nfilts == 0) {
2892 		return (B_TRUE);
2893 	}
2894 
2895 	for (uint32_t i = 0; i < nf->nf_nfilts; i++) {
2896 		const char *match = nf->nf_filts[i];
2897 		long long fid;
2898 		const char *err;
2899 
2900 		if (strcmp(nvme_feat_disc_short(disc), match) == 0 ||
2901 		    strcasecmp(nvme_feat_disc_spec(disc), match) == 0) {
2902 			nf->nf_used[i] = B_TRUE;
2903 			return (B_TRUE);
2904 		}
2905 
2906 		fid = strtonumx(match, 0, UINT32_MAX, &err, 0);
2907 		if (err == NULL && fid == nvme_feat_disc_fid(disc)) {
2908 			nf->nf_used[i] = B_TRUE;
2909 			return (B_TRUE);
2910 		}
2911 	}
2912 
2913 	return (B_FALSE);
2914 }
2915 
2916 
2917 /*
2918  * This is a common entry point for both list-features and get-features, which
2919  * iterate over all features and take action for each one.
2920  */
2921 typedef void (*do_features_cb_f)(const nvme_process_arg_t *,
2922     const nvme_feat_disc_t *);
2923 static int
do_features(const nvme_process_arg_t * npa,nvmeadm_features_t * nf,do_features_cb_f func)2924 do_features(const nvme_process_arg_t *npa, nvmeadm_features_t *nf,
2925     do_features_cb_f func)
2926 {
2927 	nvme_feat_scope_t scope;
2928 	nvme_feat_iter_t *iter;
2929 	nvme_iter_t ret;
2930 	const nvme_feat_disc_t *disc;
2931 
2932 	if (npa->npa_ns != NULL) {
2933 		scope = NVME_FEAT_SCOPE_NS;
2934 	} else {
2935 		scope = NVME_FEAT_SCOPE_CTRL | NVME_FEAT_SCOPE_NVM;
2936 	}
2937 
2938 	if (!nvme_feat_discover_init(npa->npa_ctrl, scope, 0, &iter)) {
2939 		nvmeadm_warn(npa, "failed to iterate features on %s",
2940 		    npa->npa_ctrl_name);
2941 		return (-1);
2942 	}
2943 
2944 	while ((ret = nvme_feat_discover_step(iter, &disc)) ==
2945 	    NVME_ITER_VALID) {
2946 		if (do_features_match(disc, nf)) {
2947 			if (!nf->nf_unimpl && nvme_feat_disc_impl(disc) ==
2948 			    NVME_FEAT_IMPL_UNSUPPORTED) {
2949 				continue;
2950 			}
2951 
2952 			func(npa, disc);
2953 			nf->nf_nprint++;
2954 		}
2955 	}
2956 
2957 	nvme_feat_discover_fini(iter);
2958 	if (ret == NVME_ITER_ERROR) {
2959 		nvmeadm_warn(npa, "failed to iterate features on %s",
2960 		    npa->npa_ctrl_name);
2961 		return (-1);
2962 	}
2963 
2964 	for (uint32_t i = 0; i < nf->nf_nfilts; i++) {
2965 		if (!nf->nf_used[i]) {
2966 			warnx("feature filter '%s' did match any features",
2967 			    nf->nf_filts[i]);
2968 			exitcode = -1;
2969 		}
2970 	}
2971 
2972 	if (nf->nf_nprint == 0) {
2973 		if (nf->nf_nfilts == 0) {
2974 			warnx("no features found for %s", npa->npa_name);
2975 		}
2976 		exitcode = -1;
2977 	}
2978 
2979 	return (exitcode);
2980 }
2981 
2982 static void
do_list_features_cb(const nvme_process_arg_t * npa,const nvme_feat_disc_t * disc)2983 do_list_features_cb(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc)
2984 {
2985 	nvmeadm_list_features_ofmt_arg_t print;
2986 
2987 	print.nlfoa_name = npa->npa_name;
2988 	print.nlfoa_feat = disc;
2989 	ofmt_print(npa->npa_ofmt, &print);
2990 }
2991 
2992 static int
do_list_features(const nvme_process_arg_t * npa)2993 do_list_features(const nvme_process_arg_t *npa)
2994 {
2995 	nvmeadm_features_t *nf = npa->npa_cmd_arg;
2996 
2997 	return (do_features(npa, nf, do_list_features_cb));
2998 }
2999 
3000 static void
usage_get_features(const char * c_name)3001 usage_get_features(const char *c_name)
3002 {
3003 	(void) fprintf(stderr, "%s <ctl>[/<ns>][,...] [<feature>[,...]]\n\n"
3004 	    "  Print the specified features of the specified NVMe controllers "
3005 	    "and/or\n  namespaces. Feature support varies on the controller.\n"
3006 	    "Run \"nvmeadm list-features <ctl>\" to see supported features.\n",
3007 	    c_name);
3008 }
3009 
3010 /*
3011  * The nvmeadm(8) get-features output has traditionally swallowed certain errors
3012  * for features that it considers unimplemented in tandem with the kernel. With
3013  * the introduction of libnvme and ioctl interface changes, the kernel no longer
3014  * caches information about features that are unimplemented.
3015  *
3016  * There are two cases that we currently swallow errors on and the following
3017  * must all be true:
3018  *
3019  * 1) We have a controller error.
3020  * 2) The system doesn't know whether the feature is implemented or not.
3021  * 3) The controller error indicates that we have an invalid field.
3022  *
3023  * There is one additional wrinkle that we are currently papering over due to
3024  * the history of nvmeadm swallowing errors. The error recovery feature was made
3025  * explicitly namespace-specific in NVMe 1.4. However, various NVMe 1.3 devices
3026  * will error if we ask for it without specifying a namespace. Conversely, older
3027  * devices will be upset if you do ask for a namespace. This case can be removed
3028  * once we better survey devices and come up with a heuristic for how to handle
3029  * this across older generations.
3030  *
3031  * If we add a single feature endpoint that gives flexibility over how the
3032  * feature are listed, then we should not swallow errors.
3033  */
3034 static boolean_t
swallow_get_feat_err(const nvme_process_arg_t * npa,const nvme_feat_disc_t * disc)3035 swallow_get_feat_err(const nvme_process_arg_t *npa,
3036     const nvme_feat_disc_t *disc)
3037 {
3038 	uint32_t sct, sc;
3039 
3040 	if (nvme_ctrl_err(npa->npa_ctrl) != NVME_ERR_CONTROLLER) {
3041 		return (B_FALSE);
3042 	}
3043 
3044 	nvme_ctrl_deverr(npa->npa_ctrl, &sct, &sc);
3045 	if (nvme_feat_disc_impl(disc) == NVME_FEAT_IMPL_UNKNOWN &&
3046 	    sct == NVME_CQE_SCT_GENERIC && sc == NVME_CQE_SC_GEN_INV_FLD) {
3047 		return (B_TRUE);
3048 	}
3049 
3050 	if (nvme_feat_disc_fid(disc) == NVME_FEAT_ERROR &&
3051 	    sct == NVME_CQE_SCT_GENERIC && (sc == NVME_CQE_SC_GEN_INV_FLD ||
3052 	    sc == NVME_CQE_SC_GEN_INV_NS)) {
3053 		return (B_TRUE);
3054 	}
3055 
3056 	return (B_FALSE);
3057 }
3058 
3059 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)3060 do_get_feat_common(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc,
3061     uint32_t cdw11, uint32_t *cdw0, void **datap, size_t *lenp)
3062 {
3063 	nvme_get_feat_req_t *req = NULL;
3064 	void *data = NULL;
3065 	uint64_t datalen = 0;
3066 	nvme_get_feat_fields_t fields = nvme_feat_disc_fields_get(disc);
3067 
3068 	if (!nvme_get_feat_req_init_by_disc(npa->npa_ctrl, disc, &req)) {
3069 		nvmeadm_warn(npa, "failed to initialize get feature request "
3070 		    "for feature %s", nvme_feat_disc_short(disc));
3071 		exitcode = -1;
3072 		goto err;
3073 	}
3074 
3075 	if ((fields & NVME_GET_FEAT_F_CDW11) != 0 &&
3076 	    !nvme_get_feat_req_set_cdw11(req, cdw11)) {
3077 		nvmeadm_warn(npa, "failed to set cdw11 to 0x%x for feature %s",
3078 		    cdw11, nvme_feat_disc_short(disc));
3079 		exitcode = -1;
3080 		goto err;
3081 	}
3082 
3083 	if ((fields & NVME_GET_FEAT_F_DATA) != 0) {
3084 		datalen = nvme_feat_disc_data_size(disc);
3085 		VERIFY3U(datalen, !=, 0);
3086 		data = malloc(datalen);
3087 		if (data == NULL) {
3088 			err(-1, "failed to allocate %zu bytes for feature %s "
3089 			    "data buffer", datalen, nvme_feat_disc_short(disc));
3090 		}
3091 
3092 		if (!nvme_get_feat_req_set_output(req, data, datalen)) {
3093 			nvmeadm_warn(npa, "failed to set output data for "
3094 			    "feature %s", nvme_feat_disc_short(disc));
3095 			exitcode = -1;
3096 			goto err;
3097 		}
3098 	}
3099 
3100 	if ((fields & NVME_GET_FEAT_F_NSID) != 0) {
3101 		uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info);
3102 
3103 		if (!nvme_get_feat_req_set_nsid(req, nsid)) {
3104 			nvmeadm_warn(npa, "failed to set nsid to 0x%x for "
3105 			    "feature %s", nsid, nvme_feat_disc_spec(disc));
3106 			exitcode = -1;
3107 			goto err;
3108 		}
3109 	}
3110 
3111 	if (!nvme_get_feat_req_exec(req)) {
3112 		if (!swallow_get_feat_err(npa, disc)) {
3113 			nvmeadm_warn(npa, "failed to get feature %s",
3114 			    nvme_feat_disc_spec(disc));
3115 			exitcode = -1;
3116 		}
3117 
3118 		goto err;
3119 	}
3120 
3121 	if (!nvme_get_feat_req_get_cdw0(req, cdw0)) {
3122 		nvmeadm_warn(npa, "failed to get cdw0 result data for %s",
3123 		    nvme_feat_disc_spec(disc));
3124 		goto err;
3125 	}
3126 
3127 	*datap = data;
3128 	*lenp = datalen;
3129 	nvme_get_feat_req_fini(req);
3130 	return (B_TRUE);
3131 
3132 err:
3133 	free(data);
3134 	nvme_get_feat_req_fini(req);
3135 	return (B_FALSE);
3136 }
3137 
3138 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)3139 do_get_feat_temp_thresh_one(const nvme_process_arg_t *npa,
3140     const nvme_feat_disc_t *disc, const nvmeadm_feature_t *feat,
3141     const char *label, uint16_t tmpsel, uint16_t thsel)
3142 {
3143 	uint32_t cdw0;
3144 	void *buf = NULL;
3145 	size_t buflen;
3146 	nvme_temp_threshold_t tt;
3147 
3148 	tt.r = 0;
3149 	tt.b.tt_tmpsel = tmpsel;
3150 	tt.b.tt_thsel = thsel;
3151 
3152 	/*
3153 	 * The printing function treats the buffer argument as the label to
3154 	 * print for this threshold.
3155 	 */
3156 	if (!do_get_feat_common(npa, disc, tt.r, &cdw0, &buf, &buflen)) {
3157 		return;
3158 	}
3159 
3160 	feat->f_print(cdw0, (void *)label, 0, npa->npa_idctl,
3161 	    npa->npa_version);
3162 	free(buf);
3163 }
3164 
3165 /*
3166  * In NVMe 1.2, the specification allowed for up to 8 sensors to be on the
3167  * device and changed the main device to have a composite temperature sensor. As
3168  * a result, there is a set of thresholds for each sensor. In addition, they
3169  * added both an over-temperature and under-temperature threshold. Since most
3170  * devices don't actually implement all the sensors, we get the health page and
3171  * see which sensors have a non-zero value to determine how to proceed.
3172  */
3173 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)3174 do_get_feat_temp_thresh(const nvme_process_arg_t *npa,
3175     const nvme_feat_disc_t *disc, const nvmeadm_feature_t *feat)
3176 {
3177 	nvme_log_req_t *req = NULL;
3178 	nvme_log_disc_t *log_disc = NULL;
3179 	size_t toalloc;
3180 	void *buf = NULL;
3181 	boolean_t ret = B_FALSE;
3182 	const nvme_health_log_t *hlog;
3183 
3184 	nvme_print(2, nvme_feat_disc_spec(disc), -1, NULL);
3185 	do_get_feat_temp_thresh_one(npa, disc, feat,
3186 	    "Composite Over Temp. Threshold", 0, NVME_TEMP_THRESH_OVER);
3187 
3188 	if (!nvme_version_check(npa, &nvme_vers_1v2)) {
3189 		return (B_TRUE);
3190 	}
3191 
3192 	if (!nvme_log_req_init_by_name(npa->npa_ctrl, "health", 0, &log_disc,
3193 	    &req)) {
3194 		nvmeadm_warn(npa, "failed to initialize health log page "
3195 		    "request");
3196 		return (B_FALSE);
3197 	}
3198 
3199 	toalloc = do_get_logpage_size(npa, log_disc, req);
3200 	buf = malloc(toalloc);
3201 	if (buf == NULL) {
3202 		err(-1, "failed to allocate %zu bytes for health log page",
3203 		    toalloc);
3204 	}
3205 
3206 	if (!nvme_log_req_set_output(req, buf, toalloc)) {
3207 		nvmeadm_warn(npa, "failed to set output parameters for health "
3208 		    "log page");
3209 		goto out;
3210 	}
3211 
3212 	if (!nvme_log_req_exec(req)) {
3213 		nvmeadm_warn(npa, "failed to retrieve the health log page");
3214 		goto out;
3215 	}
3216 
3217 	/* cast required to prove our intentionality to smatch */
3218 	hlog = (const nvme_health_log_t *)buf;
3219 
3220 	do_get_feat_temp_thresh_one(npa, disc, feat,
3221 	    "Composite Under Temp. Threshold", 0, NVME_TEMP_THRESH_UNDER);
3222 	if (hlog->hl_temp_sensor_1 != 0) {
3223 		do_get_feat_temp_thresh_one(npa, disc, feat,
3224 		    "Temp. Sensor 1 Over Temp. Threshold", 1,
3225 		    NVME_TEMP_THRESH_OVER);
3226 		do_get_feat_temp_thresh_one(npa, disc, feat,
3227 		    "Temp. Sensor 1 Under Temp. Threshold", 1,
3228 		    NVME_TEMP_THRESH_UNDER);
3229 	}
3230 
3231 	if (hlog->hl_temp_sensor_2 != 0) {
3232 		do_get_feat_temp_thresh_one(npa, disc, feat,
3233 		    "Temp. Sensor 2 Over Temp. Threshold", 2,
3234 		    NVME_TEMP_THRESH_OVER);
3235 		do_get_feat_temp_thresh_one(npa, disc, feat,
3236 		    "Temp. Sensor 2 Under Temp. Threshold", 2,
3237 		    NVME_TEMP_THRESH_UNDER);
3238 	}
3239 
3240 	if (hlog->hl_temp_sensor_3 != 0) {
3241 		do_get_feat_temp_thresh_one(npa, disc, feat,
3242 		    "Temp. Sensor 3 Over Temp. Threshold", 3,
3243 		    NVME_TEMP_THRESH_OVER);
3244 		do_get_feat_temp_thresh_one(npa, disc, feat,
3245 		    "Temp. Sensor 3 Under Temp. Threshold", 3,
3246 		    NVME_TEMP_THRESH_UNDER);
3247 	}
3248 
3249 	if (hlog->hl_temp_sensor_4 != 0) {
3250 		do_get_feat_temp_thresh_one(npa, disc, feat,
3251 		    "Temp. Sensor 4 Over Temp. Threshold", 4,
3252 		    NVME_TEMP_THRESH_OVER);
3253 		do_get_feat_temp_thresh_one(npa, disc, feat,
3254 		    "Temp. Sensor 4 Under Temp. Threshold", 4,
3255 		    NVME_TEMP_THRESH_UNDER);
3256 	}
3257 
3258 	if (hlog->hl_temp_sensor_5 != 0) {
3259 		do_get_feat_temp_thresh_one(npa, disc, feat,
3260 		    "Temp. Sensor 5 Over Temp. Threshold", 5,
3261 		    NVME_TEMP_THRESH_OVER);
3262 		do_get_feat_temp_thresh_one(npa, disc, feat,
3263 		    "Temp. Sensor 5 Under Temp. Threshold", 5,
3264 		    NVME_TEMP_THRESH_UNDER);
3265 	}
3266 
3267 	if (hlog->hl_temp_sensor_6 != 0) {
3268 		do_get_feat_temp_thresh_one(npa, disc, feat,
3269 		    "Temp. Sensor 6 Over Temp. Threshold", 6,
3270 		    NVME_TEMP_THRESH_OVER);
3271 		do_get_feat_temp_thresh_one(npa, disc, feat,
3272 		    "Temp. Sensor 6 Under Temp. Threshold", 6,
3273 		    NVME_TEMP_THRESH_UNDER);
3274 	}
3275 
3276 	if (hlog->hl_temp_sensor_7 != 0) {
3277 		do_get_feat_temp_thresh_one(npa, disc, feat,
3278 		    "Temp. Sensor 7 Over Temp. Threshold", 7,
3279 		    NVME_TEMP_THRESH_OVER);
3280 		do_get_feat_temp_thresh_one(npa, disc, feat,
3281 		    "Temp. Sensor 7 Under Temp. Threshold", 7,
3282 		    NVME_TEMP_THRESH_UNDER);
3283 	}
3284 
3285 	if (hlog->hl_temp_sensor_8 != 0) {
3286 		do_get_feat_temp_thresh_one(npa, disc, feat,
3287 		    "Temp. Sensor 8 Over Temp. Threshold", 8,
3288 		    NVME_TEMP_THRESH_OVER);
3289 		do_get_feat_temp_thresh_one(npa, disc, feat,
3290 		    "Temp. Sensor 8 Under Temp. Threshold", 8,
3291 		    NVME_TEMP_THRESH_UNDER);
3292 	}
3293 
3294 	ret = B_TRUE;
3295 out:
3296 	nvme_log_req_fini(req);
3297 	free(buf);
3298 	return (ret);
3299 }
3300 
3301 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)3302 do_get_feat_intr_vect(const nvme_process_arg_t *npa,
3303     const nvme_feat_disc_t *disc, const nvmeadm_feature_t *feat)
3304 {
3305 	uint32_t nintrs;
3306 	boolean_t ret = B_TRUE;
3307 
3308 	if (!nvme_ctrl_info_pci_nintrs(npa->npa_ctrl_info, &nintrs)) {
3309 		nvmeadm_ctrl_info_warn(npa, "failed to get interrupt count "
3310 		    "from controller %s information snapshot", npa->npa_name);
3311 		return (B_FALSE);
3312 	}
3313 
3314 	nvme_print(2, nvme_feat_disc_spec(disc), -1, NULL);
3315 	for (uint32_t i = 0; i < nintrs; i++) {
3316 		uint32_t cdw0;
3317 		void *buf;
3318 		size_t buflen;
3319 		nvme_intr_vect_t vect;
3320 
3321 		vect.r = 0;
3322 		vect.b.iv_iv = i;
3323 
3324 		if (!do_get_feat_common(npa, disc, vect.r, &cdw0, &buf,
3325 		    &buflen)) {
3326 			ret = B_FALSE;
3327 			continue;
3328 		}
3329 
3330 		feat->f_print(cdw0, buf, buflen, npa->npa_idctl,
3331 		    npa->npa_version);
3332 		free(buf);
3333 	}
3334 
3335 	return (ret);
3336 }
3337 
3338 /*
3339  * We've been asked to print the following feature that the controller probably
3340  * supports. Find our internal feature information for this to see if we know
3341  * how to deal with it.
3342  */
3343 static void
do_get_features_cb(const nvme_process_arg_t * npa,const nvme_feat_disc_t * disc)3344 do_get_features_cb(const nvme_process_arg_t *npa, const nvme_feat_disc_t *disc)
3345 {
3346 	const nvmeadm_feature_t *feat = NULL;
3347 	const char *name = nvme_feat_disc_short(disc);
3348 	nvme_get_feat_fields_t fields;
3349 	void *data = NULL;
3350 	size_t datalen = 0;
3351 	uint32_t cdw0;
3352 
3353 	for (size_t i = 0; i < ARRAY_SIZE(features); i++) {
3354 		if (strcmp(features[i].f_feature, name) == 0) {
3355 			feat = &features[i];
3356 			break;
3357 		}
3358 	}
3359 
3360 	/*
3361 	 * Determine if we have enough logic in here to get and print the
3362 	 * feature. The vast majority of NVMe features only output a single
3363 	 * uint32_t in cdw0 and potentially a data buffer. As long as no input
3364 	 * arguments are required, then we can go ahead and get this and print
3365 	 * the data. If there is, then we will refuse unless we have a
3366 	 * particular function. If we have a specific get function, we expect it
3367 	 * to do all the printing.
3368 	 */
3369 	if (feat != NULL && feat->f_get != NULL) {
3370 		if (!feat->f_get(npa, disc, feat)) {
3371 			exitcode = -1;
3372 		}
3373 		return;
3374 	}
3375 
3376 	fields = nvme_feat_disc_fields_get(disc);
3377 	if ((fields & NVME_GET_FEAT_F_CDW11) != 0) {
3378 		warnx("unable to get feature %s due to missing nvmeadm(8) "
3379 		    "implementation logic", nvme_feat_disc_spec(disc));
3380 		exitcode = -1;
3381 		return;
3382 	}
3383 
3384 	/*
3385 	 * We do not set exitcode on failure here so that way we can swallow
3386 	 * errors from unimplemented features.
3387 	 */
3388 	if (!do_get_feat_common(npa, disc, 0, &cdw0, &data, &datalen)) {
3389 		return;
3390 	}
3391 
3392 	nvme_print(2, nvme_feat_disc_spec(disc), -1, NULL);
3393 	if (feat != NULL && feat->f_print != NULL) {
3394 		feat->f_print(cdw0, data, datalen, npa->npa_idctl,
3395 		    npa->npa_version);
3396 	} else {
3397 		nvme_feat_output_t output = nvme_feat_disc_output_get(disc);
3398 		nvme_print_feat_unknown(output, cdw0, data, datalen);
3399 	}
3400 
3401 	free(data);
3402 }
3403 
3404 /*
3405  * This is an entry point which prints every feature that we know about. We
3406  * often go to lengths to discover all the variable inputs that can be used for
3407  * a given feature that requires an argument in cdw11. Due to the semantics of
3408  * filtering being used for features and the need to print each feature, this is
3409  * not the place to add general field filtering or a means to request a specific
3410  * cdw11 argument or similar. Instead, a new get-feature which requires someone
3411  * to specify the short name for a feature and then allows particular fields to
3412  * be grabbed and arguments should be created instead.
3413  *
3414  * This uses the same general feature logic that underpins do_list_features()
3415  * and therefore we transform filter arguments into the same style used there.
3416  */
3417 static int
do_get_features(const nvme_process_arg_t * npa)3418 do_get_features(const nvme_process_arg_t *npa)
3419 {
3420 	char *fstr = NULL;
3421 	char **filts = NULL;
3422 	boolean_t *used = NULL;
3423 	nvmeadm_features_t nf;
3424 	int ret;
3425 
3426 	if (npa->npa_argc > 1)
3427 		errx(-1, "unexpected arguments");
3428 
3429 	if (npa->npa_ns != NULL && nvme_ns_info_level(npa->npa_ns_info) <
3430 	    NVME_NS_DISC_F_ACTIVE) {
3431 		errx(-1, "cannot get feature: namespace is inactive");
3432 	}
3433 
3434 	/*
3435 	 * We always leave nf_unimpl set to false as we don't want to bother
3436 	 * trying to print a feature that we know the device doesn't support.
3437 	 */
3438 	(void) memset(&nf, 0, sizeof (nvmeadm_features_t));
3439 
3440 	/*
3441 	 * If we've been given a series of features to print, treat those as
3442 	 * filters on the features as we're walking them to determine which to
3443 	 * print or not.
3444 	 */
3445 	if (npa->npa_argc == 1) {
3446 		char *f;
3447 		uint32_t i;
3448 
3449 		nf.nf_nfilts = 1;
3450 		fstr = strdup(npa->npa_argv[0]);
3451 
3452 		if (fstr == NULL) {
3453 			err(-1, "failed to allocate memory to duplicate "
3454 			    "feature string");
3455 		}
3456 
3457 		for (const char *c = strchr(fstr, ','); c != NULL;
3458 		    c = strchr(c + 1, ',')) {
3459 			nf.nf_nfilts++;
3460 		}
3461 
3462 		filts = calloc(nf.nf_nfilts, sizeof (char *));
3463 		if (filts == NULL) {
3464 			err(-1, "failed to allocate memory for filter list");
3465 		}
3466 
3467 		i = 0;
3468 		while ((f = strsep(&fstr, ",")) != NULL) {
3469 			filts[i] = f;
3470 			i++;
3471 		}
3472 		VERIFY3U(i, ==, nf.nf_nfilts);
3473 		nf.nf_filts = filts;
3474 
3475 		used = calloc(nf.nf_nfilts, sizeof (boolean_t));
3476 		if (used == NULL) {
3477 			err(-1, "failed to allocate memory for filter use "
3478 			    "tracking");
3479 		}
3480 		nf.nf_used = used;
3481 	}
3482 
3483 	(void) printf("%s: Get Features\n", npa->npa_name);
3484 	ret = do_features(npa, &nf, do_get_features_cb);
3485 
3486 	free(fstr);
3487 	free(filts);
3488 	free(used);
3489 	return (ret);
3490 }
3491 
3492 typedef struct nvmeadm_set_feat {
3493 	bool nsf_save;
3494 	bool nsf_hcdw11;
3495 	bool nsf_hcdw12;
3496 	bool nsf_hcdw13;
3497 	bool nsf_hcdw15;
3498 	uint32_t nsf_cdw11;
3499 	uint32_t nsf_cdw12;
3500 	uint32_t nsf_cdw13;
3501 	uint32_t nsf_cdw15;
3502 	uint32_t nsf_dlen;
3503 	const char *nsf_input;
3504 	nvme_disc_impact_t nsf_impact;
3505 } nvmeadm_set_feat_t;
3506 
3507 /*
3508  * Like with vendor-specific commands, we opt to allow long options for this
3509  * sub-command so that way the --cdwXX arguments are a little more manageable.
3510  * Traditionally cdw11 was the only field set for a feature aside from data. As
3511  * such, we use -v as the short value here to allow folks that are used to other
3512  * tooling.
3513  */
3514 static const struct option set_feat_lopts[] = {
3515 	{ "save",	no_argument,		NULL,	's' },
3516 	{ "cdw11",	required_argument,	NULL,	'v' },
3517 	{ "cdw12",	required_argument,	NULL,	'2' },
3518 	{ "cdw13",	required_argument,	NULL,	'3' },
3519 	{ "cdw15",	required_argument,	NULL,	'5' },
3520 	{ "input",	required_argument,	NULL,	'i' },
3521 	{ "length",	required_argument,	NULL,	'l' },
3522 	{ "impact",	required_argument,	NULL,	'I' },
3523 	{ NULL, 0, NULL, 0 }
3524 };
3525 
3526 /*
3527  * Use a limit that's a bit larger than the kernels in case someone tries to
3528  * adjust the tunable or we increase it in the future. This would be a good
3529  * thing to ask the kernel about instead.
3530  */
3531 #define	NVMEADM_SET_FEAT_LEN_MAX	(4 * 1024 * 1024)
3532 
3533 static void
optparse_set_feature(nvme_process_arg_t * npa)3534 optparse_set_feature(nvme_process_arg_t *npa)
3535 {
3536 	int c;
3537 	nvmeadm_set_feat_t *feat;
3538 
3539 	if ((feat = calloc(1, sizeof (nvmeadm_set_feat_t))) == NULL) {
3540 		err(-1, "failed to allocate memory for option tracking");
3541 	}
3542 
3543 	/*
3544 	 * Work around getopt_long treating optind=0 as a request to restart
3545 	 * option processing and setting optind = optreset = 1.
3546 	 */
3547 	optind++;
3548 	while ((c = getopt_long(npa->npa_argc + 1, npa->npa_argv - 1,
3549 	    ":sv:2:3:5:i:l:I:", set_feat_lopts, NULL)) != -1) {
3550 		switch (c) {
3551 		case 's':
3552 			feat->nsf_save = true;
3553 			break;
3554 		case 'v':
3555 			feat->nsf_cdw11 = (uint32_t)optparse_ui_range(optarg,
3556 			    "cdw11", 0, UINT32_MAX);
3557 			feat->nsf_hcdw11 = true;
3558 			break;
3559 		case '2':
3560 			feat->nsf_cdw12 = (uint32_t)optparse_ui_range(optarg,
3561 			    "cdw12", 0, UINT32_MAX);
3562 			feat->nsf_hcdw12 = true;
3563 			break;
3564 		case '3':
3565 			feat->nsf_cdw13 = (uint32_t)optparse_ui_range(optarg,
3566 			    "cdw13", 0, UINT32_MAX);
3567 			feat->nsf_hcdw13 = true;
3568 			break;
3569 		case '5':
3570 			feat->nsf_cdw15 = (uint32_t)optparse_ui_range(optarg,
3571 			    "cdw15", 0, UINT32_MAX);
3572 			feat->nsf_hcdw15 = true;
3573 			break;
3574 		case 'i':
3575 			feat->nsf_input = optarg;
3576 			break;
3577 		case 'l':
3578 			feat->nsf_dlen = (uint32_t)optparse_ui_range(optarg,
3579 			    "length", 0, NVMEADM_SET_FEAT_LEN_MAX);
3580 			break;
3581 		case 'I':
3582 			feat->nsf_impact = optparse_impact(optarg);
3583 			break;
3584 		case '?':
3585 			errx(-1, "unknown option: -%c", optopt);
3586 		case ':':
3587 			errx(-1, "option -%c requires an argument", optopt);
3588 		}
3589 	}
3590 
3591 	/*
3592 	 * Undo our lies.
3593 	 */
3594 	optind--;
3595 
3596 	if (feat->nsf_input != NULL && feat->nsf_dlen == 0) {
3597 		errx(-1, "specified data input file (-i) but missing required "
3598 		    "data length (-l)");
3599 	}
3600 
3601 	if (feat->nsf_input == NULL && feat->nsf_dlen != 0) {
3602 		errx(-1, "%u bytes of data transfer requested (-l), but no "
3603 		    "input (-i) specified", feat->nsf_dlen);
3604 	}
3605 
3606 	npa->npa_cmd_arg = feat;
3607 }
3608 
3609 static void
usage_set_feature(const char * c_name)3610 usage_set_feature(const char *c_name)
3611 {
3612 	(void) fprintf(stderr, "%s [-s] [-v value] [--cdw12 cdw12] "
3613 	    "[--cdw13 cdw13]\n\t  [--cdw15 cdw15] [-l length -i input] "
3614 	    "[-I impact] <ctl>[/<ns>]\n\n", c_name);
3615 	(void) fprintf(stderr, "  set a feature on a controller and/or "
3616 	    "namespace\n");
3617 
3618 }
3619 
3620 /*
3621  * Read up to len bytes from file. If file hits EOF before len bytes are read,
3622  * len will be zero-filled.
3623  */
3624 void *
nvmeadm_fill_from_file(const char * file,size_t len)3625 nvmeadm_fill_from_file(const char *file, size_t len)
3626 {
3627 	void *buf = calloc(len, sizeof (uint8_t));
3628 	if (buf == NULL) {
3629 		err(-1, "failed to allocate 0x%zx byte request data buffer",
3630 		    len);
3631 	}
3632 
3633 	int ifd = open(file, O_RDONLY);
3634 	if (ifd < 0) {
3635 		err(-1, "failed to open input file %s", file);
3636 	}
3637 
3638 	size_t rem = len, off = 0;
3639 	while (rem > 0) {
3640 		size_t toread = MIN(16 * 1024, rem);
3641 		ssize_t ret = read(ifd, buf + off, toread);
3642 		if (ret < 0) {
3643 			errx(-1, "failed to read %zu bytes at offset %zu from "
3644 			    "%s", toread, off, file);
3645 		} else if (ret == 0) {
3646 			break;
3647 		}
3648 
3649 		rem -= (size_t)ret;
3650 		off += (size_t)ret;
3651 	}
3652 
3653 	VERIFY0(close(ifd));
3654 	return (buf);
3655 }
3656 
3657 static int
do_set_feature(const nvme_process_arg_t * npa)3658 do_set_feature(const nvme_process_arg_t *npa)
3659 {
3660 	nvmeadm_set_feat_t *feat = npa->npa_cmd_arg;
3661 	nvme_feat_disc_t *disc = NULL;
3662 	nvme_set_feat_req_t *req;
3663 	nvme_ctrl_t *ctrl = npa->npa_ctrl;
3664 	uint8_t *buf = NULL;
3665 
3666 	if (npa->npa_argc < 1) {
3667 		errx(-1, "missing required feature to set");
3668 	} else if (npa->npa_argc > 1) {
3669 		errx(-1, "%s passed extraneous arguments starting with %s",
3670 		    npa->npa_cmd->c_name, npa->npa_argv[1]);
3671 	}
3672 
3673 	if (npa->npa_ns != NULL) {
3674 		errx(-1, "features may only bet set on a controller, not a "
3675 		    "namespace");
3676 	}
3677 
3678 	const char *name = npa->npa_argv[0];
3679 
3680 	/*
3681 	 * First try to parse by name. Otherwise, try to see if this is a
3682 	 * feature identifier we can parse. If it's not a known name, see if
3683 	 * it's a valid feature identifier. If it isn't, we use the original
3684 	 * error message from libnvme.
3685 	 */
3686 	if (!nvme_set_feat_req_init_by_name(ctrl, name, 0, &disc,
3687 	    &req)) {
3688 		if (nvme_ctrl_err(ctrl) != NVME_ERR_FEAT_NAME_UNKNOWN) {
3689 			nvmeadm_fatal(npa, "failed to initialize set feature "
3690 			    "request for %s", name);
3691 		}
3692 
3693 		const char *errstr;
3694 		uint8_t fid = strtonumx(name, 0, NVME_FEAT_MAX_FID, &errstr, 0);
3695 		if (errstr != NULL) {
3696 			nvmeadm_fatal(npa, "failed to initialize set feature "
3697 			    "request for %s", name);
3698 		}
3699 
3700 		if (!nvme_set_feat_req_init(ctrl, &req)) {
3701 			nvmeadm_fatal(npa, "failed to initialize set feature "
3702 			    "request for %s", name);
3703 		}
3704 
3705 		if (!nvme_set_feat_req_set_fid(req, fid)) {
3706 			nvmeadm_fatal(npa, "failed to set feature id to 0x%x",
3707 			    fid);
3708 		}
3709 	}
3710 
3711 	if (npa->npa_ns != NULL) {
3712 		uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info);
3713 
3714 		if (!nvme_set_feat_req_set_nsid(req, nsid)) {
3715 			nvmeadm_fatal(npa, "failed to set feature request "
3716 			    "namespace ID to 0x%x", nsid);
3717 		}
3718 	}
3719 
3720 	if (!nvme_set_feat_req_set_save(req, feat->nsf_save) ||
3721 	    (feat->nsf_hcdw11 && !nvme_set_feat_req_set_cdw11(req,
3722 	    feat->nsf_cdw11)) ||
3723 	    (feat->nsf_hcdw12 && !nvme_set_feat_req_set_cdw12(req,
3724 	    feat->nsf_cdw12)) ||
3725 	    (feat->nsf_hcdw13 && !nvme_set_feat_req_set_cdw13(req,
3726 	    feat->nsf_cdw13)) ||
3727 	    (feat->nsf_hcdw15 && !nvme_set_feat_req_set_cdw15(req,
3728 	    feat->nsf_cdw15)) ||
3729 	    !nvme_set_feat_req_set_impact(req, feat->nsf_impact)) {
3730 		nvmeadm_fatal(npa, "failed to set request fields");
3731 	}
3732 
3733 	if (feat->nsf_dlen > 0) {
3734 		buf = nvmeadm_fill_from_file(feat->nsf_input, feat->nsf_dlen);
3735 		if (!nvme_set_feat_req_set_input(req, buf, feat->nsf_dlen)) {
3736 			nvmeadm_fatal(npa, "failed to set input data buffer");
3737 		}
3738 	}
3739 
3740 	if (!nvme_set_feat_req_exec(req)) {
3741 		nvmeadm_fatal(npa, "failed to execute set feature request");
3742 	}
3743 
3744 	uint32_t cdw0;
3745 	if (nvme_set_feat_req_get_cdw0(req, &cdw0)) {
3746 		(void) printf("Set Feature cdw0: 0x%x\n", cdw0);
3747 	}
3748 
3749 	nvme_set_feat_req_fini(req);
3750 	free(buf);
3751 
3752 	return (0);
3753 }
3754 
3755 static int
do_format_common(const nvme_process_arg_t * npa,uint32_t lbaf,uint32_t ses)3756 do_format_common(const nvme_process_arg_t *npa, uint32_t lbaf,
3757     uint32_t ses)
3758 {
3759 	int ret = 0;
3760 	nvme_format_req_t *req;
3761 
3762 	if (npa->npa_ns != NULL && nvme_ns_info_level(npa->npa_ns_info) <
3763 	    NVME_NS_DISC_F_ACTIVE) {
3764 		errx(-1, "cannot %s: namespace is inactive",
3765 		    npa->npa_cmd->c_name);
3766 	}
3767 
3768 	if (!nvme_format_req_init(npa->npa_ctrl, &req)) {
3769 		nvmeadm_fatal(npa, "failed to initialize format request for "
3770 		    "%s", npa->npa_name);
3771 	}
3772 
3773 	if (npa->npa_ns != NULL) {
3774 		uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info);
3775 
3776 		if (!nvme_format_req_set_nsid(req, nsid)) {
3777 			nvmeadm_fatal(npa, "failed to set format request "
3778 			    "namespace ID to 0x%x", nsid);
3779 		}
3780 	}
3781 
3782 	if (!nvme_format_req_set_lbaf(req, lbaf) ||
3783 	    !nvme_format_req_set_ses(req, ses)) {
3784 		nvmeadm_fatal(npa, "failed to set format request fields for %s",
3785 		    npa->npa_name);
3786 	}
3787 
3788 	if (do_detach_bd(npa) != 0) {
3789 		errx(-1, "cannot %s %s due to namespace detach failure",
3790 		    npa->npa_cmd->c_name, npa->npa_name);
3791 	}
3792 
3793 	if (!nvme_format_req_exec(req)) {
3794 		nvmeadm_warn(npa, "failed to %s %s", npa->npa_cmd->c_name,
3795 		    npa->npa_name);
3796 		ret = -1;
3797 	}
3798 
3799 	if (do_attach_bd(npa) != 0)
3800 		ret = -1;
3801 
3802 	return (ret);
3803 }
3804 
3805 static void
usage_format(const char * c_name)3806 usage_format(const char *c_name)
3807 {
3808 	(void) fprintf(stderr, "%s <ctl>[/<ns>] [<lba-format>]\n\n"
3809 	    "  Format one or all namespaces of the specified NVMe "
3810 	    "controller. Supported LBA\n  formats can be queried with "
3811 	    "the \"%s identify\" command on the namespace\n  to be "
3812 	    "formatted.\n", c_name, getprogname());
3813 }
3814 
3815 static uint32_t
do_format_determine_lbaf(const nvme_process_arg_t * npa)3816 do_format_determine_lbaf(const nvme_process_arg_t *npa)
3817 {
3818 	const nvme_nvm_lba_fmt_t *fmt;
3819 	nvme_ns_info_t *ns_info = NULL;
3820 	uint32_t lbaf;
3821 
3822 	if (npa->npa_argc > 0) {
3823 		unsigned long lba;
3824 		uint32_t nlbaf = nvme_ctrl_info_nformats(npa->npa_ctrl_info);
3825 
3826 		errno = 0;
3827 		lba = strtoul(npa->npa_argv[0], NULL, 10);
3828 		if (errno != 0 || lba >= nlbaf)
3829 			errx(-1, "invalid LBA format %s", npa->npa_argv[0]);
3830 
3831 		if (!nvme_ctrl_info_format(npa->npa_ctrl_info, (uint32_t)lba,
3832 		    &fmt)) {
3833 			nvmeadm_fatal(npa, "failed to get LBA format %lu "
3834 			    "information", lba);
3835 		}
3836 	} else {
3837 		/*
3838 		 * If we have a namespace then we use the current namespace's
3839 		 * LBA format. If we don't have a namespace, then we promised
3840 		 * we'd look at namespace 1 in the manual page.
3841 		 */
3842 		if (npa->npa_ns_info == NULL) {
3843 			if (!nvme_ctrl_ns_info_snap(npa->npa_ctrl, 1,
3844 			    &ns_info)) {
3845 				nvmeadm_fatal(npa, "failed to get namespace 1 "
3846 				    "information, please explicitly specify an "
3847 				    "LBA format");
3848 			}
3849 
3850 			if (!nvme_ns_info_curformat(ns_info, &fmt)) {
3851 				nvmeadm_fatal(npa, "failed to retrieve current "
3852 				    "namespace format from namespace 1");
3853 			}
3854 		} else {
3855 			if (!nvme_ns_info_curformat(npa->npa_ns_info, &fmt)) {
3856 				nvmeadm_fatal(npa, "failed to get the current "
3857 				    "format information from %s",
3858 				    npa->npa_name);
3859 			}
3860 		}
3861 	}
3862 
3863 	if (nvme_nvm_lba_fmt_meta_size(fmt) != 0) {
3864 		errx(-1, "LBA formats with metadata are not supported");
3865 	}
3866 
3867 	lbaf = nvme_nvm_lba_fmt_id(fmt);
3868 	nvme_ns_info_free(ns_info);
3869 	return (lbaf);
3870 }
3871 
3872 static int
do_format(const nvme_process_arg_t * npa)3873 do_format(const nvme_process_arg_t *npa)
3874 {
3875 	uint32_t lbaf;
3876 
3877 	if (npa->npa_argc > 1) {
3878 		errx(-1, "%s passed extraneous arguments starting with %s",
3879 		    npa->npa_cmd->c_name, npa->npa_argv[1]);
3880 	}
3881 
3882 	lbaf = do_format_determine_lbaf(npa);
3883 	return (do_format_common(npa, lbaf, 0));
3884 }
3885 
3886 static void
usage_secure_erase(const char * c_name)3887 usage_secure_erase(const char *c_name)
3888 {
3889 	(void) fprintf(stderr, "%s [-c] <ctl>[/<ns>]\n\n"
3890 	    "  Secure-Erase one or all namespaces of the specified "
3891 	    "NVMe controller.\n", c_name);
3892 }
3893 
3894 static void
optparse_secure_erase(nvme_process_arg_t * npa)3895 optparse_secure_erase(nvme_process_arg_t *npa)
3896 {
3897 	int c;
3898 
3899 	while ((c = getopt(npa->npa_argc, npa->npa_argv, ":c")) != -1) {
3900 		switch (c) {
3901 		case 'c':
3902 			npa->npa_cmdflags |= NVMEADM_O_SE_CRYPTO;
3903 			break;
3904 
3905 		case '?':
3906 			errx(-1, "unknown option: -%c", optopt);
3907 
3908 		case ':':
3909 			errx(-1, "option -%c requires an argument", optopt);
3910 
3911 		}
3912 	}
3913 }
3914 
3915 static int
do_secure_erase(const nvme_process_arg_t * npa)3916 do_secure_erase(const nvme_process_arg_t *npa)
3917 {
3918 	unsigned long lbaf;
3919 	uint8_t ses = NVME_FRMT_SES_USER;
3920 
3921 	if (npa->npa_argc > 0) {
3922 		errx(-1, "%s passed extraneous arguments starting with %s",
3923 		    npa->npa_cmd->c_name, npa->npa_argv[0]);
3924 	}
3925 
3926 	if ((npa->npa_cmdflags & NVMEADM_O_SE_CRYPTO) != 0)
3927 		ses = NVME_FRMT_SES_CRYPTO;
3928 
3929 	lbaf = do_format_determine_lbaf(npa);
3930 	return (do_format_common(npa, lbaf, ses));
3931 }
3932 
3933 static void
usage_attach_detach_bd(const char * c_name)3934 usage_attach_detach_bd(const char *c_name)
3935 {
3936 	(void) fprintf(stderr, "%s <ctl>[/<ns>]\n\n"
3937 	    "  %c%s blkdev(4D) %s one or all namespaces of the "
3938 	    "specified NVMe controller.\n",
3939 	    c_name, toupper(c_name[0]), &c_name[1],
3940 	    c_name[0] == 'd' ? "from" : "to");
3941 }
3942 
3943 /*
3944  * nvmeadm does not generate an error when trying to attach blkdev to something
3945  * that already has it attached. Swallow that here.
3946  */
3947 static boolean_t
swallow_attach_bd_err(const nvme_process_arg_t * npa)3948 swallow_attach_bd_err(const nvme_process_arg_t *npa)
3949 {
3950 	return (nvme_ctrl_err(npa->npa_ctrl) == NVME_ERR_NS_BLKDEV_ATTACH);
3951 }
3952 
3953 static int
do_attach_bd(const nvme_process_arg_t * npa)3954 do_attach_bd(const nvme_process_arg_t *npa)
3955 {
3956 	int rv;
3957 	nvme_ns_iter_t *iter = NULL;
3958 	nvme_iter_t ret;
3959 	const nvme_ns_disc_t *disc;
3960 
3961 	if (npa->npa_ns != NULL) {
3962 		if (!nvme_ns_bd_attach(npa->npa_ns) &&
3963 		    !swallow_attach_bd_err(npa)) {
3964 			nvmeadm_warn(npa, "faild to attach %s", npa->npa_name);
3965 			return (-1);
3966 		}
3967 		return (0);
3968 	}
3969 
3970 	if (!nvme_ns_discover_init(npa->npa_ctrl, NVME_NS_DISC_F_NOT_IGNORED,
3971 	    &iter))  {
3972 		nvmeadm_fatal(npa, "failed to initialize namespace discovery "
3973 		    "on %s", npa->npa_name);
3974 	}
3975 
3976 	rv = 0;
3977 	while ((ret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) {
3978 		nvme_ns_t *ns;
3979 		uint32_t nsid;
3980 
3981 		if (nvme_ns_disc_level(disc) == NVME_NS_DISC_F_BLKDEV)
3982 			continue;
3983 
3984 		nsid = nvme_ns_disc_nsid(disc);
3985 		if (!nvme_ns_init(npa->npa_ctrl, nsid, &ns)) {
3986 			nvmeadm_warn(npa, "failed to open namespace %s/%u "
3987 			    "handle", npa->npa_name, nsid);
3988 			rv = -1;
3989 			continue;
3990 		}
3991 
3992 		/*
3993 		 * nvmeadm has historically swallowed the case where you ask to
3994 		 * attach an already attached namespace.
3995 		 */
3996 		if (!nvme_ns_bd_attach(ns) && !swallow_attach_bd_err(npa)) {
3997 			nvmeadm_warn(npa, "failed to attach namespace "
3998 			    "%s/%u", npa->npa_name, nsid);
3999 			rv = -1;
4000 		}
4001 		nvme_ns_fini(ns);
4002 	}
4003 
4004 	nvme_ns_discover_fini(iter);
4005 	if (ret == NVME_ITER_ERROR) {
4006 		nvmeadm_warn(npa, "failed to iterate namespaces on %s",
4007 		    npa->npa_name);
4008 		rv = -1;
4009 	}
4010 
4011 	return (rv);
4012 }
4013 
4014 /*
4015  * nvmeadm does not generate an error when trying to attach blkdev to something
4016  * that already has it attached. Swallow that here.
4017  */
4018 static boolean_t
swallow_detach_bd_err(const nvme_process_arg_t * npa)4019 swallow_detach_bd_err(const nvme_process_arg_t *npa)
4020 {
4021 	switch (nvme_ctrl_err(npa->npa_ctrl)) {
4022 	case NVME_ERR_NS_UNALLOC:
4023 	case NVME_ERR_NS_CTRL_NOT_ATTACHED:
4024 	case NVME_ERR_NS_CTRL_ATTACHED:
4025 		return (B_TRUE);
4026 	default:
4027 		return (B_FALSE);
4028 	}
4029 }
4030 
4031 static int
do_detach_bd(const nvme_process_arg_t * npa)4032 do_detach_bd(const nvme_process_arg_t *npa)
4033 {
4034 	int rv;
4035 	nvme_ns_iter_t *iter = NULL;
4036 	nvme_iter_t ret;
4037 	const nvme_ns_disc_t *disc;
4038 
4039 	if (npa->npa_ns != NULL) {
4040 		if (!nvme_ns_bd_detach(npa->npa_ns) &&
4041 		    !swallow_detach_bd_err(npa)) {
4042 			nvmeadm_warn(npa, "failed to detach %s", npa->npa_name);
4043 			return (-1);
4044 		}
4045 		return (0);
4046 	}
4047 
4048 	if (!nvme_ns_discover_init(npa->npa_ctrl, NVME_NS_DISC_F_BLKDEV,
4049 	    &iter))  {
4050 		nvmeadm_fatal(npa, "failed to initialize namespace discovery "
4051 		    "on %s", npa->npa_name);
4052 	}
4053 
4054 	rv = 0;
4055 	while ((ret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) {
4056 		nvme_ns_t *ns;
4057 		uint32_t nsid = nvme_ns_disc_nsid(disc);
4058 
4059 		if (!nvme_ns_init(npa->npa_ctrl, nsid, &ns)) {
4060 			nvmeadm_warn(npa, "failed to open namespace %s/%u "
4061 			    "handle", npa->npa_name, nsid);
4062 			rv = -1;
4063 			continue;
4064 		}
4065 
4066 		if (!nvme_ns_bd_detach(ns) && !swallow_detach_bd_err(npa)) {
4067 			nvmeadm_warn(npa, "failed to detach namespace %s/%u",
4068 			    npa->npa_name, nsid);
4069 			rv = -1;
4070 		}
4071 		nvme_ns_fini(ns);
4072 	}
4073 
4074 	nvme_ns_discover_fini(iter);
4075 	if (ret == NVME_ITER_ERROR) {
4076 		nvmeadm_warn(npa, "failed to iterate namespaces on %s",
4077 		    npa->npa_name);
4078 		rv = -1;
4079 	}
4080 
4081 	return (rv);
4082 }
4083 
4084 static void
usage_firmware_load(const char * c_name)4085 usage_firmware_load(const char *c_name)
4086 {
4087 	(void) fprintf(stderr, "%s <ctl> <image file> [<offset>]\n\n"
4088 	    "  Load firmware <image file> to offset <offset>.\n"
4089 	    "  The firmware needs to be committed to a slot using "
4090 	    "\"nvmeadm commit-firmware\"\n  command.\n", c_name);
4091 }
4092 
4093 /*
4094  * Read exactly len bytes, or until eof.
4095  */
4096 static size_t
read_block(const nvme_process_arg_t * npa,int fd,char * buf,size_t len)4097 read_block(const nvme_process_arg_t *npa, int fd, char *buf, size_t len)
4098 {
4099 	size_t remain;
4100 
4101 	remain = len;
4102 	while (remain > 0) {
4103 		ssize_t bytes = read(fd, buf, remain);
4104 		if (bytes == 0)
4105 			break;
4106 
4107 		if (bytes < 0) {
4108 			if (errno == EINTR)
4109 				continue;
4110 
4111 			err(-1, "Error reading \"%s\"", npa->npa_argv[0]);
4112 		}
4113 
4114 		buf += (size_t)bytes;
4115 		remain -= (size_t)bytes;
4116 	}
4117 
4118 	return (len - remain);
4119 }
4120 
4121 /*
4122  * Convert a string to a valid firmware upload offset (in bytes).
4123  */
4124 static uint64_t
get_fw_offsetb(char * str)4125 get_fw_offsetb(char *str)
4126 {
4127 	longlong_t offsetb;
4128 	char *valend;
4129 
4130 	errno = 0;
4131 	offsetb = strtoll(str, &valend, 0);
4132 	if (errno != 0 || *valend != '\0' || offsetb < 0 ||
4133 	    offsetb > NVME_FW_OFFSETB_MAX)
4134 		errx(-1, "Offset must be numeric and in the range of 0 to %llu",
4135 		    NVME_FW_OFFSETB_MAX);
4136 
4137 	if ((offsetb & NVME_DWORD_MASK) != 0)
4138 		errx(-1, "Offset must be multiple of %d", NVME_DWORD_SIZE);
4139 
4140 	return ((uint64_t)offsetb);
4141 }
4142 
4143 #define	FIRMWARE_READ_BLKSIZE	(64 * 1024)		/* 64K */
4144 
4145 static int
do_firmware_load(const nvme_process_arg_t * npa)4146 do_firmware_load(const nvme_process_arg_t *npa)
4147 {
4148 	int fw_fd;
4149 	uint64_t offset = 0;
4150 	size_t size, len;
4151 	char buf[FIRMWARE_READ_BLKSIZE];
4152 
4153 	if (npa->npa_argc > 2)
4154 		errx(-1, "%s passed extraneous arguments starting with %s",
4155 		    npa->npa_cmd->c_name, npa->npa_argv[2]);
4156 
4157 	if (npa->npa_argc == 0)
4158 		errx(-1, "Requires firmware file name, and an "
4159 		    "optional offset");
4160 
4161 	if (npa->npa_ns != NULL)
4162 		errx(-1, "Firmware loading not available on a per-namespace "
4163 		    "basis");
4164 
4165 	if (npa->npa_argc == 2)
4166 		offset = get_fw_offsetb(npa->npa_argv[1]);
4167 
4168 	fw_fd = open(npa->npa_argv[0], O_RDONLY);
4169 	if (fw_fd < 0)
4170 		errx(-1, "Failed to open \"%s\": %s", npa->npa_argv[0],
4171 		    strerror(errno));
4172 
4173 	size = 0;
4174 	do {
4175 		len = read_block(npa, fw_fd, buf, sizeof (buf));
4176 
4177 		if (len == 0)
4178 			break;
4179 
4180 		if (!nvme_fw_load(npa->npa_ctrl, buf, len, offset)) {
4181 			nvmeadm_fatal(npa, "failed to load firmware image "
4182 			    "\"%s\" at offset %" PRIu64, npa->npa_argv[0],
4183 			    offset);
4184 		}
4185 
4186 		offset += len;
4187 		size += len;
4188 	} while (len == sizeof (buf));
4189 
4190 	(void) close(fw_fd);
4191 
4192 	if (verbose)
4193 		(void) printf("%zu bytes downloaded.\n", size);
4194 
4195 	return (0);
4196 }
4197 
4198 /*
4199  * Common firmware commit for nvmeadm commit-firmware and activate-firmware.
4200  */
4201 static void
nvmeadm_firmware_commit(const nvme_process_arg_t * npa,uint32_t slot,uint32_t act)4202 nvmeadm_firmware_commit(const nvme_process_arg_t *npa, uint32_t slot,
4203     uint32_t act)
4204 {
4205 	nvme_fw_commit_req_t *req;
4206 
4207 	if (!nvme_fw_commit_req_init(npa->npa_ctrl, &req)) {
4208 		nvmeadm_fatal(npa, "failed to initialize firmware commit "
4209 		    "request for %s", npa->npa_name);
4210 	}
4211 
4212 	if (!nvme_fw_commit_req_set_slot(req, slot) ||
4213 	    !nvme_fw_commit_req_set_action(req, act)) {
4214 		nvmeadm_fatal(npa, "failed to set firmware commit fields for "
4215 		    "%s", npa->npa_name);
4216 	}
4217 
4218 	if (!nvme_fw_commit_req_exec(req)) {
4219 		/*
4220 		 * A number of command specific status values are informational
4221 		 * and indicate that the operation was successful but that
4222 		 * something else, such as a device reset, is still required
4223 		 * before the new firmware is active.
4224 		 * We distinguish those here and report them as a note rather
4225 		 * than a fatal error.
4226 		 */
4227 		if (nvme_ctrl_err(npa->npa_ctrl) == NVME_ERR_CONTROLLER) {
4228 			uint32_t sct, sc;
4229 
4230 			nvme_ctrl_deverr(npa->npa_ctrl, &sct, &sc);
4231 			if (sct == NVME_CQE_SCT_SPECIFIC && (
4232 			    sc == NVME_CQE_SC_SPC_FW_RESET ||
4233 			    sc == NVME_CQE_SC_SPC_FW_NSSR ||
4234 			    sc == NVME_CQE_SC_SPC_FW_NEXT_RESET)) {
4235 				fprintf(stderr,
4236 				    "nvmeadm: commit successful but %s\n",
4237 				    nvme_sctostr(npa->npa_ctrl, NVME_CSI_NVM,
4238 				    sct, sc));
4239 			} else {
4240 				nvmeadm_fatal(npa, "failed to %s on %s",
4241 				    npa->npa_cmd->c_name, npa->npa_name);
4242 			}
4243 		} else {
4244 			nvmeadm_fatal(npa, "failed to %s on %s",
4245 			    npa->npa_cmd->c_name, npa->npa_name);
4246 		}
4247 	}
4248 
4249 	nvme_fw_commit_req_fini(req);
4250 }
4251 
4252 /*
4253  * Convert str to a valid firmware slot number.
4254  */
4255 static uint32_t
get_slot_number(char * str)4256 get_slot_number(char *str)
4257 {
4258 	longlong_t slot;
4259 	char *valend;
4260 
4261 	errno = 0;
4262 	slot = strtoll(str, &valend, 0);
4263 	if (errno != 0 || *valend != '\0' ||
4264 	    slot < NVME_FW_SLOT_MIN || slot > NVME_FW_SLOT_MAX)
4265 		errx(-1, "Slot must be numeric and in the range of %u to %u",
4266 		    NVME_FW_SLOT_MIN, NVME_FW_SLOT_MAX);
4267 
4268 	return ((uint32_t)slot);
4269 }
4270 
4271 static void
usage_firmware_commit(const char * c_name)4272 usage_firmware_commit(const char *c_name)
4273 {
4274 	(void) fprintf(stderr, "%s <ctl> <slot>\n\n"
4275 	    "  Commit previously downloaded firmware to slot <slot>.\n"
4276 	    "  The firmware is only activated after a "
4277 	    "\"nvmeadm activate-firmware\" command.\n", c_name);
4278 }
4279 
4280 static int
do_firmware_commit(const nvme_process_arg_t * npa)4281 do_firmware_commit(const nvme_process_arg_t *npa)
4282 {
4283 	uint32_t slot;
4284 
4285 	if (npa->npa_argc > 1)
4286 		errx(-1, "%s passed extraneous arguments starting with %s",
4287 		    npa->npa_cmd->c_name, npa->npa_argv[1]);
4288 
4289 	if (npa->npa_argc == 0)
4290 		errx(-1, "Firmware slot number is required");
4291 
4292 	if (npa->npa_ns != NULL)
4293 		errx(-1, "Firmware committing not available on a per-namespace "
4294 		    "basis");
4295 
4296 	slot = get_slot_number(npa->npa_argv[0]);
4297 
4298 	if (slot == 1 && npa->npa_idctl->id_frmw.fw_readonly)
4299 		errx(-1, "Cannot commit firmware to slot 1: slot is read-only");
4300 
4301 	nvmeadm_firmware_commit(npa, slot, NVME_FWC_SAVE);
4302 
4303 	if (verbose)
4304 		(void) printf("Firmware committed to slot %u.\n", slot);
4305 
4306 	return (0);
4307 }
4308 
4309 static void
usage_firmware_activate(const char * c_name)4310 usage_firmware_activate(const char *c_name)
4311 {
4312 	(void) fprintf(stderr, "%s <ctl> <slot>\n\n"
4313 	    "  Activate firmware in slot <slot>.\n"
4314 	    "  The firmware will be in use after the next system reset.\n",
4315 	    c_name);
4316 }
4317 
4318 static int
do_firmware_activate(const nvme_process_arg_t * npa)4319 do_firmware_activate(const nvme_process_arg_t *npa)
4320 {
4321 	uint32_t slot;
4322 
4323 	if (npa->npa_argc > 1)
4324 		errx(-1, "%s passed extraneous arguments starting with %s",
4325 		    npa->npa_cmd->c_name, npa->npa_argv[1]);
4326 
4327 	if (npa->npa_argc == 0)
4328 		errx(-1, "Firmware slot number is required");
4329 
4330 	if (npa->npa_ns != NULL)
4331 		errx(-1, "Firmware activation not available on a per-namespace "
4332 		    "basis");
4333 
4334 	slot = get_slot_number(npa->npa_argv[0]);
4335 
4336 	nvmeadm_firmware_commit(npa, slot, NVME_FWC_ACTIVATE);
4337 
4338 	if (verbose)
4339 		(void) printf("Slot %u successfully activated.\n", slot);
4340 
4341 	return (0);
4342 }
4343