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 2016 Nexenta Systems, Inc.
14 */
15
16 /*
17 * nvmeadm -- NVMe administration utility
18 *
19 * nvmeadm [-v] [-d] [-h] <command> [<ctl>[/<ns>][,...]] [args]
20 * commands: list
21 * identify
22 * get-logpage <logpage name>
23 * get-features <feature>[,...]
24 * format ...
25 * secure-erase ...
26 * detach ...
27 * attach ...
28 * get-param ...
29 * set-param ...
30 * load-firmware ...
31 * activate-firmware ...
32 * write-uncorrectable ...
33 * compare ...
34 * compare-and-write ...
35 */
36
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <strings.h>
40 #include <ctype.h>
41 #include <err.h>
42 #include <sys/sunddi.h>
43 #include <libdevinfo.h>
44
45 #include <sys/nvme.h>
46
47 #include "nvmeadm.h"
48
49 typedef struct nvme_process_arg nvme_process_arg_t;
50 typedef struct nvme_feature nvme_feature_t;
51 typedef struct nvmeadm_cmd nvmeadm_cmd_t;
52
53 struct nvme_process_arg {
54 int npa_argc;
55 char **npa_argv;
56 char *npa_name;
57 uint32_t npa_nsid;
58 boolean_t npa_isns;
59 const nvmeadm_cmd_t *npa_cmd;
60 di_node_t npa_node;
61 di_minor_t npa_minor;
62 char *npa_path;
63 char *npa_dsk;
64 nvme_identify_ctrl_t *npa_idctl;
65 nvme_identify_nsid_t *npa_idns;
66 nvme_version_t *npa_version;
67 };
68
69 struct nvme_feature {
70 char *f_name;
71 char *f_short;
72 uint8_t f_feature;
73 size_t f_bufsize;
74 uint_t f_getflags;
75 int (*f_get)(int, const nvme_feature_t *, nvme_identify_ctrl_t *);
76 void (*f_print)(uint64_t, void *, size_t, nvme_identify_ctrl_t *);
77 };
78
79 #define NVMEADM_CTRL 1
80 #define NVMEADM_NS 2
81 #define NVMEADM_BOTH (NVMEADM_CTRL | NVMEADM_NS)
82
83 struct nvmeadm_cmd {
84 char *c_name;
85 char *c_desc;
86 char *c_flagdesc;
87 int (*c_func)(int, const nvme_process_arg_t *);
88 void (*c_usage)(const char *);
89 boolean_t c_multi;
90 };
91
92
93 static void usage(const nvmeadm_cmd_t *);
94 static void nvme_walk(nvme_process_arg_t *, di_node_t);
95 static boolean_t nvme_match(nvme_process_arg_t *);
96
97 static int nvme_process(di_node_t, di_minor_t, void *);
98
99 static int do_list(int, const nvme_process_arg_t *);
100 static int do_identify(int, const nvme_process_arg_t *);
101 static int do_get_logpage_error(int, const nvme_process_arg_t *);
102 static int do_get_logpage_health(int, const nvme_process_arg_t *);
103 static int do_get_logpage_fwslot(int, const nvme_process_arg_t *);
104 static int do_get_logpage(int, const nvme_process_arg_t *);
105 static int do_get_feat_common(int, const nvme_feature_t *,
106 nvme_identify_ctrl_t *);
107 static int do_get_feat_intr_vect(int, const nvme_feature_t *,
108 nvme_identify_ctrl_t *);
109 static int do_get_features(int, const nvme_process_arg_t *);
110 static int do_format(int, const nvme_process_arg_t *);
111 static int do_secure_erase(int, const nvme_process_arg_t *);
112 static int do_attach_detach(int, const nvme_process_arg_t *);
113
114 static void usage_list(const char *);
115 static void usage_identify(const char *);
116 static void usage_get_logpage(const char *);
117 static void usage_get_features(const char *);
118 static void usage_format(const char *);
119 static void usage_secure_erase(const char *);
120 static void usage_attach_detach(const char *);
121
122 int verbose;
123 int debug;
124 int found;
125 static int exitcode;
126
127 static const nvmeadm_cmd_t nvmeadm_cmds[] = {
128 {
129 "list",
130 "list controllers and namespaces",
131 NULL,
132 do_list, usage_list, B_TRUE
133 },
134 {
135 "identify",
136 "identify controllers and/or namespaces",
137 NULL,
138 do_identify, usage_identify, B_TRUE
139 },
140 {
141 "get-logpage",
142 "get a log page from controllers and/or namespaces",
143 NULL,
144 do_get_logpage, usage_get_logpage, B_TRUE
145 },
146 {
147 "get-features",
148 "get features from controllers and/or namespaces",
149 NULL,
150 do_get_features, usage_get_features, B_TRUE
151 },
152 {
153 "format",
154 "format namespace(s) of a controller",
155 NULL,
156 do_format, usage_format, B_FALSE
157 },
158 {
159 "secure-erase",
160 "secure erase namespace(s) of a controller",
161 " -c Do a cryptographic erase.",
162 do_secure_erase, usage_secure_erase, B_FALSE
163 },
164 {
165 "detach",
166 "detach blkdev(7d) from namespace(s) of a controller",
167 NULL,
168 do_attach_detach, usage_attach_detach, B_FALSE
169 },
170 {
171 "attach",
172 "attach blkdev(7d) to namespace(s) of a controller",
173 NULL,
174 do_attach_detach, usage_attach_detach, B_FALSE
175 },
176 {
177 NULL, NULL, NULL,
178 NULL, NULL, B_FALSE
179 }
180 };
181
182 static const nvme_feature_t features[] = {
183 { "Arbitration", "",
184 NVME_FEAT_ARBITRATION, 0, NVMEADM_CTRL,
185 do_get_feat_common, nvme_print_feat_arbitration },
186 { "Power Management", "",
187 NVME_FEAT_POWER_MGMT, 0, NVMEADM_CTRL,
188 do_get_feat_common, nvme_print_feat_power_mgmt },
189 { "LBA Range Type", "range",
190 NVME_FEAT_LBA_RANGE, NVME_LBA_RANGE_BUFSIZE, NVMEADM_NS,
191 do_get_feat_common, nvme_print_feat_lba_range },
192 { "Temperature Threshold", "",
193 NVME_FEAT_TEMPERATURE, 0, NVMEADM_CTRL,
194 do_get_feat_common, nvme_print_feat_temperature },
195 { "Error Recovery", "",
196 NVME_FEAT_ERROR, 0, NVMEADM_CTRL,
197 do_get_feat_common, nvme_print_feat_error },
198 { "Volatile Write Cache", "cache",
199 NVME_FEAT_WRITE_CACHE, 0, NVMEADM_CTRL,
200 do_get_feat_common, nvme_print_feat_write_cache },
201 { "Number of Queues", "queues",
202 NVME_FEAT_NQUEUES, 0, NVMEADM_CTRL,
203 do_get_feat_common, nvme_print_feat_nqueues },
204 { "Interrupt Coalescing", "coalescing",
205 NVME_FEAT_INTR_COAL, 0, NVMEADM_CTRL,
206 do_get_feat_common, nvme_print_feat_intr_coal },
207 { "Interrupt Vector Configuration", "vector",
208 NVME_FEAT_INTR_VECT, 0, NVMEADM_CTRL,
209 do_get_feat_intr_vect, nvme_print_feat_intr_vect },
210 { "Write Atomicity", "atomicity",
211 NVME_FEAT_WRITE_ATOM, 0, NVMEADM_CTRL,
212 do_get_feat_common, nvme_print_feat_write_atom },
213 { "Asynchronous Event Configuration", "event",
214 NVME_FEAT_ASYNC_EVENT, 0, NVMEADM_CTRL,
215 do_get_feat_common, nvme_print_feat_async_event },
216 { "Autonomous Power State Transition", "",
217 NVME_FEAT_AUTO_PST, NVME_AUTO_PST_BUFSIZE, NVMEADM_CTRL,
218 do_get_feat_common, nvme_print_feat_auto_pst },
219 { "Software Progress Marker", "progress",
220 NVME_FEAT_PROGRESS, 0, NVMEADM_CTRL,
221 do_get_feat_common, nvme_print_feat_progress },
222 { NULL, NULL, 0, 0, B_FALSE, NULL }
223 };
224
225
226 int
227 main(int argc, char **argv)
228 {
229 int c;
230 extern int optind;
231 const nvmeadm_cmd_t *cmd;
232 di_node_t node;
233 nvme_process_arg_t npa = { 0 };
234 int help = 0;
235 char *tmp, *lasts = NULL;
236
237 while ((c = getopt(argc, argv, "dhv")) != -1) {
238 switch (c) {
239 case 'd':
240 debug++;
241 break;
242 case 'v':
243 verbose++;
244 break;
245 case 'h':
246 help++;
247 break;
248 case '?':
249 usage(NULL);
250 exit(-1);
251 }
252 }
253
254 if (optind == argc) {
255 usage(NULL);
256 if (help)
257 exit(0);
258 else
259 exit(-1);
260 }
261
262 /* Look up the specified command in the command table. */
263 for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++)
264 if (strcmp(cmd->c_name, argv[optind]) == 0)
265 break;
266
267 if (cmd->c_name == NULL) {
268 usage(NULL);
269 exit(-1);
270 }
271
272 if (help) {
273 usage(cmd);
274 exit(0);
275 }
276
277 npa.npa_cmd = cmd;
278
279 optind++;
280
281 /*
282 * All commands but "list" require a ctl/ns argument.
283 */
284 if ((optind == argc || (strncmp(argv[optind], "nvme", 4) != 0)) &&
285 cmd->c_func != do_list) {
286 warnx("missing controller/namespace name");
287 usage(cmd);
288 exit(-1);
289 }
290
291
292 /* Store the remaining arguments for use by the command. */
293 npa.npa_argc = argc - optind - 1;
294 npa.npa_argv = &argv[optind + 1];
295
296 /*
297 * Make sure we're not running commands on multiple controllers that
298 * aren't allowed to do that.
299 */
300 if (argv[optind] != NULL && strchr(argv[optind], ',') != NULL &&
301 cmd->c_multi == B_FALSE) {
302 warnx("%s not allowed on multiple controllers",
303 cmd->c_name);
304 usage(cmd);
305 exit(-1);
306 }
307
308 /*
309 * Get controller/namespace arguments and run command.
310 */
311 npa.npa_name = strtok_r(argv[optind], ",", &lasts);
312 do {
313 if (npa.npa_name != NULL) {
314 tmp = strchr(npa.npa_name, '/');
315 if (tmp != NULL) {
316 unsigned long nsid;
317 *tmp++ = '\0';
318 errno = 0;
319 nsid = strtoul(tmp, NULL, 10);
320 if (nsid >= UINT32_MAX || errno != 0) {
321 warn("invalid namespace %s", tmp);
322 exitcode--;
323 continue;
324 }
325 if (nsid == 0) {
326 warnx("invalid namespace %s", tmp);
327 exitcode--;
328 continue;
329 }
330 npa.npa_nsid = nsid;
331 npa.npa_isns = B_TRUE;
332 }
333 }
334
335 if ((node = di_init("/", DINFOSUBTREE | DINFOMINOR)) == NULL)
336 err(-1, "failed to initialize libdevinfo");
337 nvme_walk(&npa, node);
338 di_fini(node);
339
340 if (found == 0) {
341 if (npa.npa_name != NULL) {
342 warnx("%s%.*s%.*d: no such controller or "
343 "namespace", npa.npa_name,
344 npa.npa_nsid > 0 ? -1 : 0, "/",
345 npa.npa_nsid > 0 ? -1 : 0, npa.npa_nsid);
346 } else {
347 warnx("no controllers found");
348 }
349 exitcode--;
350 }
351 found = 0;
352 npa.npa_name = strtok_r(NULL, ",", &lasts);
353 } while (npa.npa_name != NULL);
354
355 exit(exitcode);
356 }
357
358 static void
359 usage(const nvmeadm_cmd_t *cmd)
360 {
361 (void) fprintf(stderr, "usage:\n");
362 (void) fprintf(stderr, " %s -h %s\n", getprogname(),
363 cmd != NULL ? cmd->c_name : "[<command>]");
364 (void) fprintf(stderr, " %s [-dv] ", getprogname());
365
366 if (cmd != NULL) {
367 cmd->c_usage(cmd->c_name);
368 } else {
369 (void) fprintf(stderr,
370 "<command> <ctl>[/<ns>][,...] [<args>]\n");
371 (void) fprintf(stderr,
372 "\n Manage NVMe controllers and namespaces.\n");
373 (void) fprintf(stderr, "\ncommands:\n");
374
375 for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++)
376 (void) fprintf(stderr, " %-15s - %s\n",
377 cmd->c_name, cmd->c_desc);
378 }
379 (void) fprintf(stderr, "\nflags:\n"
380 " -h print usage information\n"
381 " -d print information useful for debugging %s\n"
382 " -v print verbose information\n", getprogname());
383 if (cmd != NULL && cmd->c_flagdesc != NULL)
384 (void) fprintf(stderr, "%s\n", cmd->c_flagdesc);
385 }
386
387 static boolean_t
388 nvme_match(nvme_process_arg_t *npa)
389 {
390 char *name;
391 uint32_t nsid = 0;
392
393 if (npa->npa_name == NULL)
394 return (B_TRUE);
395
396 if (asprintf(&name, "%s%d", di_driver_name(npa->npa_node),
397 di_instance(npa->npa_node)) < 0)
398 err(-1, "nvme_match()");
399
400 if (strcmp(name, npa->npa_name) != 0) {
401 free(name);
402 return (B_FALSE);
403 }
404
405 free(name);
406
407 if (npa->npa_isns) {
408 if (npa->npa_nsid == 0)
409 return (B_TRUE);
410 nsid = strtoul(di_minor_name(npa->npa_minor), NULL, 10);
411 }
412
413 if (npa->npa_isns && npa->npa_nsid != nsid)
414 return (B_FALSE);
415
416 return (B_TRUE);
417 }
418
419 char *
420 nvme_dskname(const nvme_process_arg_t *npa)
421 {
422 char *path = NULL;
423 di_node_t child;
424 di_dim_t dim;
425 char *addr;
426
427 dim = di_dim_init();
428
429 for (child = di_child_node(npa->npa_node);
430 child != DI_NODE_NIL;
431 child = di_sibling_node(child)) {
432 addr = di_bus_addr(child);
433 if (addr == NULL)
434 continue;
435
436 if (addr[0] == 'w')
437 addr++;
438
439 if (strncasecmp(addr, di_minor_name(npa->npa_minor),
440 strchrnul(addr, ',') - addr) != 0)
441 continue;
442
443 path = di_dim_path_dev(dim, di_driver_name(child),
444 di_instance(child), "c");
445
446 if (path != NULL) {
447 path[strlen(path) - 2] = '\0';
448 path = strrchr(path, '/') + 1;
449 if (path != NULL) {
450 path = strdup(path);
451 if (path == NULL)
452 err(-1, "nvme_dskname");
453 }
454 }
455
456 break;
457 }
458
459 di_dim_fini(dim);
460 return (path);
461 }
462
463 static int
464 nvme_process(di_node_t node, di_minor_t minor, void *arg)
465 {
466 nvme_process_arg_t *npa = arg;
467 int fd;
468
469 npa->npa_node = node;
470 npa->npa_minor = minor;
471
472 if (!nvme_match(npa))
473 return (DI_WALK_CONTINUE);
474
475 if ((fd = nvme_open(minor)) < 0)
476 return (DI_WALK_CONTINUE);
477
478 found++;
479
480 npa->npa_path = di_devfs_path(node);
481 if (npa->npa_path == NULL)
482 goto out;
483
484 npa->npa_version = nvme_version(fd);
485 if (npa->npa_version == NULL)
486 goto out;
487
488 npa->npa_idctl = nvme_identify_ctrl(fd);
489 if (npa->npa_idctl == NULL)
490 goto out;
491
492 npa->npa_idns = nvme_identify_nsid(fd);
493 if (npa->npa_idns == NULL)
494 goto out;
495
496 if (npa->npa_isns)
497 npa->npa_dsk = nvme_dskname(npa);
498
499 exitcode += npa->npa_cmd->c_func(fd, npa);
500
501 out:
502 di_devfs_path_free(npa->npa_path);
503 free(npa->npa_dsk);
504 free(npa->npa_version);
505 free(npa->npa_idctl);
506 free(npa->npa_idns);
507
508 npa->npa_version = NULL;
509 npa->npa_idctl = NULL;
510 npa->npa_idns = NULL;
511
512 nvme_close(fd);
513
514 return (DI_WALK_CONTINUE);
515 }
516
517 static void
518 nvme_walk(nvme_process_arg_t *npa, di_node_t node)
519 {
520 char *minor_nodetype = DDI_NT_NVME_NEXUS;
521
522 if (npa->npa_isns)
523 minor_nodetype = DDI_NT_NVME_ATTACHMENT_POINT;
524
525 (void) di_walk_minor(node, minor_nodetype, 0, npa, nvme_process);
526 }
527
528 static void
529 usage_list(const char *c_name)
530 {
531 (void) fprintf(stderr, "%s [<ctl>[/<ns>][,...]\n\n"
532 " List NVMe controllers and their namespaces. If no "
533 "controllers and/or name-\n spaces are specified, all "
534 "controllers and namespaces in the system will be\n "
535 "listed.\n", c_name);
536 }
537
538 static int
539 do_list_nsid(int fd, const nvme_process_arg_t *npa)
540 {
541 _NOTE(ARGUNUSED(fd));
542 const uint_t format = npa->npa_idns->id_flbas.lba_format;
543 const uint_t bshift = npa->npa_idns->id_lbaf[format].lbaf_lbads;
544
545 /*
546 * Some devices have extra namespaces with illegal block sizes and
547 * zero blocks. Don't list them when verbose operation isn't requested.
548 */
549 if ((bshift < 9 || npa->npa_idns->id_nsize == 0) && verbose == 0)
550 return (0);
551
552 (void) printf(" %s/%s (%s): ", npa->npa_name,
553 di_minor_name(npa->npa_minor),
554 npa->npa_dsk != NULL ? npa->npa_dsk : "unattached");
555 nvme_print_nsid_summary(npa->npa_idns);
556
557 return (0);
558 }
559
560 static int
561 do_list(int fd, const nvme_process_arg_t *npa)
562 {
563 _NOTE(ARGUNUSED(fd));
564
565 nvme_process_arg_t ns_npa = { 0 };
566 nvmeadm_cmd_t cmd = { 0 };
567 char *name;
568
569 if (asprintf(&name, "%s%d", di_driver_name(npa->npa_node),
570 di_instance(npa->npa_node)) < 0)
571 err(-1, "do_list()");
572
573 (void) printf("%s: ", name);
574 nvme_print_ctrl_summary(npa->npa_idctl, npa->npa_version);
575
576 ns_npa.npa_name = name;
577 ns_npa.npa_isns = B_TRUE;
578 ns_npa.npa_nsid = npa->npa_nsid;
579 cmd = *(npa->npa_cmd);
580 cmd.c_func = do_list_nsid;
581 ns_npa.npa_cmd = &cmd;
582
583 nvme_walk(&ns_npa, npa->npa_node);
584
585 free(name);
586
587 return (exitcode);
588 }
589
590 static void
591 usage_identify(const char *c_name)
592 {
593 (void) fprintf(stderr, "%s <ctl>[/<ns>][,...]\n\n"
594 " Print detailed information about the specified NVMe "
595 "controllers and/or name-\n spaces.\n", c_name);
596 }
597
598 static int
599 do_identify(int fd, const nvme_process_arg_t *npa)
600 {
601 if (npa->npa_nsid == 0) {
602 nvme_capabilities_t *cap;
603
604 cap = nvme_capabilities(fd);
605 if (cap == NULL)
606 return (-1);
607
608 (void) printf("%s: ", npa->npa_name);
609 nvme_print_identify_ctrl(npa->npa_idctl, cap,
610 npa->npa_version);
611
612 free(cap);
613 } else {
614 (void) printf("%s/%s: ", npa->npa_name,
615 di_minor_name(npa->npa_minor));
616 nvme_print_identify_nsid(npa->npa_idns,
617 npa->npa_version);
618 }
619
620 return (0);
621 }
622
623 static void
624 usage_get_logpage(const char *c_name)
625 {
626 (void) fprintf(stderr, "%s <ctl>[/<ns>][,...] <logpage>\n\n"
627 " Print the specified log page of the specified NVMe "
628 "controllers and/or name-\n spaces. Supported log pages "
629 "are error, health, and firmware.\n", c_name);
630 }
631
632 static int
633 do_get_logpage_error(int fd, const nvme_process_arg_t *npa)
634 {
635 int nlog = npa->npa_idctl->id_elpe + 1;
636 size_t bufsize = sizeof (nvme_error_log_entry_t) * nlog;
637 nvme_error_log_entry_t *elog;
638
639 if (npa->npa_nsid != 0)
640 errx(-1, "Error Log not available on a per-namespace basis");
641
642 elog = nvme_get_logpage(fd, NVME_LOGPAGE_ERROR, &bufsize);
643
644 if (elog == NULL)
645 return (-1);
646
647 nlog = bufsize / sizeof (nvme_error_log_entry_t);
648
649 (void) printf("%s: ", npa->npa_name);
650 nvme_print_error_log(nlog, elog);
651
652 free(elog);
653
654 return (0);
655 }
656
657 static int
658 do_get_logpage_health(int fd, const nvme_process_arg_t *npa)
659 {
660 size_t bufsize = sizeof (nvme_health_log_t);
661 nvme_health_log_t *hlog;
662
663 if (npa->npa_nsid != 0) {
664 if (npa->npa_idctl->id_lpa.lp_smart == 0)
665 errx(-1, "SMART/Health information not available "
666 "on a per-namespace basis on this controller");
667 }
668
669 hlog = nvme_get_logpage(fd, NVME_LOGPAGE_HEALTH, &bufsize);
670
671 if (hlog == NULL)
672 return (-1);
673
674 (void) printf("%s: ", npa->npa_name);
675 nvme_print_health_log(hlog, npa->npa_idctl);
676
677 free(hlog);
678
679 return (0);
680 }
681
682 static int
683 do_get_logpage_fwslot(int fd, const nvme_process_arg_t *npa)
684 {
685 size_t bufsize = sizeof (nvme_fwslot_log_t);
686 nvme_fwslot_log_t *fwlog;
687
688 if (npa->npa_nsid != 0)
689 errx(-1, "Firmware Slot information not available on a "
690 "per-namespace basis");
691
692 fwlog = nvme_get_logpage(fd, NVME_LOGPAGE_FWSLOT, &bufsize);
693
694 if (fwlog == NULL)
695 return (-1);
696
697 (void) printf("%s: ", npa->npa_name);
698 nvme_print_fwslot_log(fwlog);
699
700 free(fwlog);
701
702 return (0);
703 }
704
705 static int
706 do_get_logpage(int fd, const nvme_process_arg_t *npa)
707 {
708 int ret = 0;
709 int (*func)(int, const nvme_process_arg_t *);
710
711 if (npa->npa_argc < 1) {
712 warnx("missing logpage name");
713 usage(npa->npa_cmd);
714 exit(-1);
715 }
716
717 if (strcmp(npa->npa_argv[0], "error") == 0)
718 func = do_get_logpage_error;
719 else if (strcmp(npa->npa_argv[0], "health") == 0)
720 func = do_get_logpage_health;
721 else if (strcmp(npa->npa_argv[0], "firmware") == 0)
722 func = do_get_logpage_fwslot;
723 else
724 errx(-1, "invalid log page: %s", npa->npa_argv[0]);
725
726 ret = func(fd, npa);
727 return (ret);
728 }
729
730 static void
731 usage_get_features(const char *c_name)
732 {
733 const nvme_feature_t *feat;
734
735 (void) fprintf(stderr, "%s <ctl>[/<ns>][,...] [<feature>[,...]]\n\n"
736 " Print the specified features of the specified NVMe controllers "
737 "and/or\n namespaces. Supported features are:\n\n", c_name);
738 (void) fprintf(stderr, " %-35s %-14s %s\n",
739 "FEATURE NAME", "SHORT NAME", "CONTROLLER/NAMESPACE");
740 for (feat = &features[0]; feat->f_feature != 0; feat++) {
741 char *type;
742
743 if ((feat->f_getflags & NVMEADM_BOTH) == NVMEADM_BOTH)
744 type = "both";
745 else if ((feat->f_getflags & NVMEADM_CTRL) != 0)
746 type = "controller only";
747 else
748 type = "namespace only";
749
750 (void) fprintf(stderr, " %-35s %-14s %s\n",
751 feat->f_name, feat->f_short, type);
752 }
753
754 }
755
756 static int
757 do_get_feat_common(int fd, const nvme_feature_t *feat,
758 nvme_identify_ctrl_t *idctl)
759 {
760 void *buf = NULL;
761 size_t bufsize = feat->f_bufsize;
762 uint64_t res;
763
764 if (nvme_get_feature(fd, feat->f_feature, 0, &res, &bufsize, &buf)
765 == B_FALSE)
766 return (EINVAL);
767
768 nvme_print(2, feat->f_name, -1, NULL);
769 feat->f_print(res, buf, bufsize, idctl);
770 free(buf);
771
772 return (0);
773 }
774
775 static int
776 do_get_feat_intr_vect(int fd, const nvme_feature_t *feat,
777 nvme_identify_ctrl_t *idctl)
778 {
779 uint64_t res;
780 uint64_t arg;
781 int intr_cnt;
782
783 intr_cnt = nvme_intr_cnt(fd);
784
785 if (intr_cnt == -1)
786 return (EINVAL);
787
788 nvme_print(2, feat->f_name, -1, NULL);
789
790 for (arg = 0; arg < intr_cnt; arg++) {
791 if (nvme_get_feature(fd, feat->f_feature, arg, &res, NULL, NULL)
792 == B_FALSE)
793 return (EINVAL);
794
795 feat->f_print(res, NULL, 0, idctl);
796 }
797
798 return (0);
799 }
800
801 static int
802 do_get_features(int fd, const nvme_process_arg_t *npa)
803 {
804 const nvme_feature_t *feat;
805 char *f, *flist, *lasts;
806 boolean_t header_printed = B_FALSE;
807
808 if (npa->npa_argc > 1)
809 errx(-1, "unexpected arguments");
810
811 /*
812 * No feature list given, print all supported features.
813 */
814 if (npa->npa_argc == 0) {
815 (void) printf("%s: Get Features\n", npa->npa_name);
816 for (feat = &features[0]; feat->f_feature != 0; feat++) {
817 if ((npa->npa_nsid != 0 &&
818 (feat->f_getflags & NVMEADM_NS) == 0) ||
819 (npa->npa_nsid == 0 &&
820 (feat->f_getflags & NVMEADM_CTRL) == 0))
821 continue;
822
823 (void) feat->f_get(fd, feat, npa->npa_idctl);
824 }
825
826 return (0);
827 }
828
829 /*
830 * Process feature list.
831 */
832 flist = strdup(npa->npa_argv[0]);
833 if (flist == NULL)
834 err(-1, "do_get_features");
835
836 for (f = strtok_r(flist, ",", &lasts);
837 f != NULL;
838 f = strtok_r(NULL, ",", &lasts)) {
839 while (isspace(*f))
840 f++;
841
842 for (feat = &features[0]; feat->f_feature != 0; feat++) {
843 if (strncasecmp(feat->f_name, f, strlen(f)) == 0 ||
844 strncasecmp(feat->f_short, f, strlen(f)) == 0)
845 break;
846 }
847
848 if (feat->f_feature == 0) {
849 warnx("unknown feature %s", f);
850 continue;
851 }
852
853 if ((npa->npa_nsid != 0 &&
854 (feat->f_getflags & NVMEADM_NS) == 0) ||
855 (npa->npa_nsid == 0 &&
856 (feat->f_getflags & NVMEADM_CTRL) == 0)) {
857 warnx("feature %s %s supported for namespaces",
858 feat->f_name, (feat->f_getflags & NVMEADM_NS) != 0 ?
859 "only" : "not");
860 continue;
861 }
862
863 if (!header_printed) {
864 (void) printf("%s: Get Features\n", npa->npa_name);
865 header_printed = B_TRUE;
866 }
867
868 if (feat->f_get(fd, feat, npa->npa_idctl) != 0) {
869 warnx("unsupported feature: %s", feat->f_name);
870 continue;
871 }
872 }
873
874 free(flist);
875 return (0);
876 }
877
878 static int
879 do_format_common(int fd, const nvme_process_arg_t *npa, unsigned long lbaf,
880 unsigned long ses)
881 {
882 nvme_process_arg_t ns_npa = { 0 };
883 nvmeadm_cmd_t cmd = { 0 };
884
885 cmd = *(npa->npa_cmd);
886 cmd.c_func = do_attach_detach;
887 cmd.c_name = "detach";
888 ns_npa = *npa;
889 ns_npa.npa_cmd = &cmd;
890
891 if (do_attach_detach(fd, &ns_npa) != 0)
892 return (exitcode);
893 if (nvme_format_nvm(fd, lbaf, ses) == B_FALSE) {
894 warn("%s failed", npa->npa_cmd->c_name);
895 exitcode += -1;
896 }
897 cmd.c_name = "attach";
898 exitcode += do_attach_detach(fd, &ns_npa);
899
900 return (exitcode);
901 }
902
903 static void
904 usage_format(const char *c_name)
905 {
906 (void) fprintf(stderr, "%s <ctl>[/<ns>] [<lba-format>]\n\n"
907 " Format one or all namespaces of the specified NVMe "
908 "controller. Supported LBA\n formats can be queried with "
909 "the \"%s identify\" command on the namespace\n to be "
910 "formatted.\n", c_name, getprogname());
911 }
912
913 static int
914 do_format(int fd, const nvme_process_arg_t *npa)
915 {
916 unsigned long lbaf;
917
918 if (npa->npa_idctl->id_oacs.oa_format == 0)
919 errx(-1, "%s not supported", npa->npa_cmd->c_name);
920
921 if (npa->npa_isns && npa->npa_idctl->id_fna.fn_format != 0)
922 errx(-1, "%s not supported on individual namespace",
923 npa->npa_cmd->c_name);
924
925
926 if (npa->npa_argc > 0) {
927 errno = 0;
928 lbaf = strtoul(npa->npa_argv[0], NULL, 10);
929
930 if (errno != 0 || lbaf > NVME_FRMT_MAX_LBAF)
931 errx(-1, "invalid LBA format %d", lbaf + 1);
932
933 if (npa->npa_idns->id_lbaf[lbaf].lbaf_ms != 0)
934 errx(-1, "LBA formats with metadata not supported");
935 } else {
936 lbaf = npa->npa_idns->id_flbas.lba_format;
937 }
938
939 return (do_format_common(fd, npa, lbaf, 0));
940 }
941
942 static void
943 usage_secure_erase(const char *c_name)
944 {
945 (void) fprintf(stderr, "%s <ctl>[/<ns>] [-c]\n\n"
946 " Secure-Erase one or all namespaces of the specified "
947 "NVMe controller.\n", c_name);
948 }
949
950 static int
951 do_secure_erase(int fd, const nvme_process_arg_t *npa)
952 {
953 unsigned long lbaf;
954 uint8_t ses = NVME_FRMT_SES_USER;
955
956 if (npa->npa_idctl->id_oacs.oa_format == 0)
957 errx(-1, "%s not supported", npa->npa_cmd->c_name);
958
959 if (npa->npa_isns && npa->npa_idctl->id_fna.fn_sec_erase != 0)
960 errx(-1, "%s not supported on individual namespace",
961 npa->npa_cmd->c_name);
962
963 if (npa->npa_argc > 0) {
964 if (strcmp(npa->npa_argv[0], "-c") == 0)
965 ses = NVME_FRMT_SES_CRYPTO;
966 else
967 usage(npa->npa_cmd);
968 }
969
970 if (ses == NVME_FRMT_SES_CRYPTO &&
971 npa->npa_idctl->id_fna.fn_crypt_erase == 0)
972 errx(-1, "cryptographic %s not supported",
973 npa->npa_cmd->c_name);
974
975 lbaf = npa->npa_idns->id_flbas.lba_format;
976
977 return (do_format_common(fd, npa, lbaf, ses));
978 }
979
980 static void
981 usage_attach_detach(const char *c_name)
982 {
983 (void) fprintf(stderr, "%s <ctl>[/<ns>]\n\n"
984 " %c%s blkdev(7d) %s one or all namespaces of the "
985 "specified NVMe controller.\n",
986 c_name, toupper(c_name[0]), &c_name[1],
987 c_name[0] == 'd' ? "from" : "to");
988 }
989
990 static int
991 do_attach_detach(int fd, const nvme_process_arg_t *npa)
992 {
993 char *c_name = npa->npa_cmd->c_name;
994
995 if (!npa->npa_isns) {
996 nvme_process_arg_t ns_npa = { 0 };
997
998 ns_npa.npa_name = npa->npa_name;
999 ns_npa.npa_isns = B_TRUE;
1000 ns_npa.npa_cmd = npa->npa_cmd;
1001
1002 nvme_walk(&ns_npa, npa->npa_node);
1003
1004 return (exitcode);
1005 } else {
1006 if ((c_name[0] == 'd' ? nvme_detach : nvme_attach)(fd)
1007 == B_FALSE) {
1008 warn("%s failed", c_name);
1009 return (-1);
1010 }
1011 }
1012
1013 return (0);
1014 }