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         char *npa_nsid;
  58         int npa_found;
  59         boolean_t npa_isns;
  60         const nvmeadm_cmd_t *npa_cmd;
  61         di_node_t npa_node;
  62         di_minor_t npa_minor;
  63         char *npa_path;
  64         char *npa_dsk;
  65         nvme_identify_ctrl_t *npa_idctl;
  66         nvme_identify_nsid_t *npa_idns;
  67         nvme_version_t *npa_version;
  68 };
  69 
  70 struct nvme_feature {
  71         char *f_name;
  72         char *f_short;
  73         uint8_t f_feature;
  74         size_t f_bufsize;
  75         uint_t f_getflags;
  76         int (*f_get)(int, const nvme_feature_t *, nvme_identify_ctrl_t *);
  77         void (*f_print)(uint64_t, void *, size_t, nvme_identify_ctrl_t *);
  78 };
  79 
  80 #define NVMEADM_CTRL    1
  81 #define NVMEADM_NS      2
  82 #define NVMEADM_BOTH    (NVMEADM_CTRL | NVMEADM_NS)
  83 
  84 struct nvmeadm_cmd {
  85         char *c_name;
  86         char *c_desc;
  87         char *c_flagdesc;
  88         int (*c_func)(int, const nvme_process_arg_t *);
  89         void (*c_usage)(const char *);
  90         boolean_t c_multi;
  91 };
  92 
  93 
  94 static void usage(const nvmeadm_cmd_t *);
  95 static void nvme_walk(nvme_process_arg_t *, di_node_t);
  96 static boolean_t nvme_match(nvme_process_arg_t *);
  97 
  98 static int nvme_process(di_node_t, di_minor_t, void *);
  99 
 100 static int do_list(int, const nvme_process_arg_t *);
 101 static int do_identify(int, const nvme_process_arg_t *);
 102 static int do_get_logpage_error(int, const nvme_process_arg_t *);
 103 static int do_get_logpage_health(int, const nvme_process_arg_t *);
 104 static int do_get_logpage_fwslot(int, const nvme_process_arg_t *);
 105 static int do_get_logpage(int, const nvme_process_arg_t *);
 106 static int do_get_feat_common(int, const nvme_feature_t *,
 107     nvme_identify_ctrl_t *);
 108 static int do_get_feat_intr_vect(int, const nvme_feature_t *,
 109     nvme_identify_ctrl_t *);
 110 static int do_get_features(int, const nvme_process_arg_t *);
 111 static int do_format(int, const nvme_process_arg_t *);
 112 static int do_secure_erase(int, const nvme_process_arg_t *);
 113 static int do_attach_detach(int, const nvme_process_arg_t *);
 114 
 115 static void usage_list(const char *);
 116 static void usage_identify(const char *);
 117 static void usage_get_logpage(const char *);
 118 static void usage_get_features(const char *);
 119 static void usage_format(const char *);
 120 static void usage_secure_erase(const char *);
 121 static void usage_attach_detach(const char *);
 122 
 123 int verbose;
 124 int debug;
 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                                 *tmp++ = '\0';
 317                                 npa.npa_nsid = tmp;
 318                                 npa.npa_isns = B_TRUE;
 319                         }
 320                 }
 321 
 322                 if ((node = di_init("/", DINFOSUBTREE | DINFOMINOR)) == NULL)
 323                         err(-1, "failed to initialize libdevinfo");
 324                 nvme_walk(&npa, node);
 325                 di_fini(node);
 326 
 327                 if (npa.npa_found == 0) {
 328                         if (npa.npa_name != NULL) {
 329                                 warnx("%s%.*s%.*s: no such controller or "
 330                                     "namespace", npa.npa_name,
 331                                     npa.npa_isns ? -1 : 0, "/",
 332                                     npa.npa_isns ? -1 : 0, npa.npa_nsid);
 333                         } else {
 334                                 warnx("no controllers found");
 335                         }
 336                         exitcode--;
 337                 }
 338                 npa.npa_found = 0;
 339                 npa.npa_name = strtok_r(NULL, ",", &lasts);
 340         } while (npa.npa_name != NULL);
 341 
 342         exit(exitcode);
 343 }
 344 
 345 static void
 346 usage(const nvmeadm_cmd_t *cmd)
 347 {
 348         (void) fprintf(stderr, "usage:\n");
 349         (void) fprintf(stderr, "  %s -h %s\n", getprogname(),
 350             cmd != NULL ? cmd->c_name : "[<command>]");
 351         (void) fprintf(stderr, "  %s [-dv] ", getprogname());
 352 
 353         if (cmd != NULL) {
 354                 cmd->c_usage(cmd->c_name);
 355         } else {
 356                 (void) fprintf(stderr,
 357                     "<command> <ctl>[/<ns>][,...] [<args>]\n");
 358                 (void) fprintf(stderr,
 359                     "\n  Manage NVMe controllers and namespaces.\n");
 360                 (void) fprintf(stderr, "\ncommands:\n");
 361 
 362                 for (cmd = &nvmeadm_cmds[0]; cmd->c_name != NULL; cmd++)
 363                         (void) fprintf(stderr, "  %-15s - %s\n",
 364                             cmd->c_name, cmd->c_desc);
 365         }
 366         (void) fprintf(stderr, "\nflags:\n"
 367             "  -h  print usage information\n"
 368             "  -d  print information useful for debugging %s\n"
 369             "  -v  print verbose information\n", getprogname());
 370         if (cmd != NULL && cmd->c_flagdesc != NULL)
 371                 (void) fprintf(stderr, "%s\n", cmd->c_flagdesc);
 372 }
 373 
 374 static boolean_t
 375 nvme_match(nvme_process_arg_t *npa)
 376 {
 377         char *name;
 378         char *nsid = NULL;
 379 
 380         if (npa->npa_name == NULL)
 381                 return (B_TRUE);
 382 
 383         if (asprintf(&name, "%s%d", di_driver_name(npa->npa_node),
 384             di_instance(npa->npa_node)) < 0)
 385                 err(-1, "nvme_match()");
 386 
 387         if (strcmp(name, npa->npa_name) != 0) {
 388                 free(name);
 389                 return (B_FALSE);
 390         }
 391 
 392         free(name);
 393 
 394         if (npa->npa_isns) {
 395                 if (npa->npa_nsid == NULL)
 396                         return (B_TRUE);
 397 
 398                 nsid = di_minor_name(npa->npa_minor);
 399 
 400                 if (nsid == NULL || strcmp(npa->npa_nsid, nsid) != 0)
 401                         return (B_FALSE);
 402         }
 403 
 404         return (B_TRUE);
 405 }
 406 
 407 char *
 408 nvme_dskname(const nvme_process_arg_t *npa)
 409 {
 410         char *path = NULL;
 411         di_node_t child;
 412         di_dim_t dim;
 413         char *addr;
 414 
 415         dim = di_dim_init();
 416 
 417         for (child = di_child_node(npa->npa_node);
 418             child != DI_NODE_NIL;
 419             child = di_sibling_node(child)) {
 420                 addr = di_bus_addr(child);
 421                 if (addr == NULL)
 422                         continue;
 423 
 424                 if (addr[0] == 'w')
 425                         addr++;
 426 
 427                 if (strncasecmp(addr, di_minor_name(npa->npa_minor),
 428                     strchrnul(addr, ',') - addr) != 0)
 429                         continue;
 430 
 431                 path = di_dim_path_dev(dim, di_driver_name(child),
 432                     di_instance(child), "c");
 433 
 434                 if (path != NULL) {
 435                         path[strlen(path) - 2] = '\0';
 436                         path = strrchr(path, '/') + 1;
 437                         if (path != NULL) {
 438                                 path = strdup(path);
 439                                 if (path == NULL)
 440                                         err(-1, "nvme_dskname");
 441                         }
 442                 }
 443 
 444                 break;
 445         }
 446 
 447         di_dim_fini(dim);
 448         return (path);
 449 }
 450 
 451 static int
 452 nvme_process(di_node_t node, di_minor_t minor, void *arg)
 453 {
 454         nvme_process_arg_t *npa = arg;
 455         int fd;
 456 
 457         npa->npa_node = node;
 458         npa->npa_minor = minor;
 459 
 460         if (!nvme_match(npa))
 461                 return (DI_WALK_CONTINUE);
 462 
 463         if ((fd = nvme_open(minor)) < 0)
 464                 return (DI_WALK_CONTINUE);
 465 
 466         npa->npa_found++;
 467 
 468         npa->npa_path = di_devfs_path(node);
 469         if (npa->npa_path == NULL)
 470                 goto out;
 471 
 472         npa->npa_version = nvme_version(fd);
 473         if (npa->npa_version == NULL)
 474                 goto out;
 475 
 476         npa->npa_idctl = nvme_identify_ctrl(fd);
 477         if (npa->npa_idctl == NULL)
 478                 goto out;
 479 
 480         npa->npa_idns = nvme_identify_nsid(fd);
 481         if (npa->npa_idns == NULL)
 482                 goto out;
 483 
 484         if (npa->npa_isns)
 485                 npa->npa_dsk = nvme_dskname(npa);
 486 
 487         exitcode += npa->npa_cmd->c_func(fd, npa);
 488 
 489 out:
 490         di_devfs_path_free(npa->npa_path);
 491         free(npa->npa_dsk);
 492         free(npa->npa_version);
 493         free(npa->npa_idctl);
 494         free(npa->npa_idns);
 495 
 496         npa->npa_version = NULL;
 497         npa->npa_idctl = NULL;
 498         npa->npa_idns = NULL;
 499 
 500         nvme_close(fd);
 501 
 502         return (DI_WALK_CONTINUE);
 503 }
 504 
 505 static void
 506 nvme_walk(nvme_process_arg_t *npa, di_node_t node)
 507 {
 508         char *minor_nodetype = DDI_NT_NVME_NEXUS;
 509 
 510         if (npa->npa_isns)
 511                 minor_nodetype = DDI_NT_NVME_ATTACHMENT_POINT;
 512 
 513         (void) di_walk_minor(node, minor_nodetype, 0, npa, nvme_process);
 514 }
 515 
 516 static void
 517 usage_list(const char *c_name)
 518 {
 519         (void) fprintf(stderr, "%s [<ctl>[/<ns>][,...]\n\n"
 520             "  List NVMe controllers and their namespaces. If no "
 521             "controllers and/or name-\n  spaces are specified, all "
 522             "controllers and namespaces in the system will be\n  "
 523             "listed.\n", c_name);
 524 }
 525 
 526 static int
 527 do_list_nsid(int fd, const nvme_process_arg_t *npa)
 528 {
 529         _NOTE(ARGUNUSED(fd));
 530 
 531         (void) printf("  %s/%s (%s): ", npa->npa_name,
 532             di_minor_name(npa->npa_minor),
 533             npa->npa_dsk != NULL ? npa->npa_dsk : "unattached");
 534         nvme_print_nsid_summary(npa->npa_idns);
 535 
 536         return (0);
 537 }
 538 
 539 static int
 540 do_list(int fd, const nvme_process_arg_t *npa)
 541 {
 542         _NOTE(ARGUNUSED(fd));
 543 
 544         nvme_process_arg_t ns_npa = { 0 };
 545         nvmeadm_cmd_t cmd = { 0 };
 546         char *name;
 547 
 548         if (asprintf(&name, "%s%d", di_driver_name(npa->npa_node),
 549             di_instance(npa->npa_node)) < 0)
 550                 err(-1, "do_list()");
 551 
 552         (void) printf("%s: ", name);
 553         nvme_print_ctrl_summary(npa->npa_idctl, npa->npa_version);
 554 
 555         ns_npa.npa_name = name;
 556         ns_npa.npa_isns = B_TRUE;
 557         ns_npa.npa_nsid = npa->npa_nsid;
 558         cmd = *(npa->npa_cmd);
 559         cmd.c_func = do_list_nsid;
 560         ns_npa.npa_cmd = &cmd;
 561 
 562         nvme_walk(&ns_npa, npa->npa_node);
 563 
 564         free(name);
 565 
 566         return (exitcode);
 567 }
 568 
 569 static void
 570 usage_identify(const char *c_name)
 571 {
 572         (void) fprintf(stderr, "%s <ctl>[/<ns>][,...]\n\n"
 573             "  Print detailed information about the specified NVMe "
 574             "controllers and/or name-\n  spaces.\n", c_name);
 575 }
 576 
 577 static int
 578 do_identify(int fd, const nvme_process_arg_t *npa)
 579 {
 580         if (!npa->npa_isns) {
 581                 nvme_capabilities_t *cap;
 582 
 583                 cap = nvme_capabilities(fd);
 584                 if (cap == NULL)
 585                         return (-1);
 586 
 587                 (void) printf("%s: ", npa->npa_name);
 588                 nvme_print_identify_ctrl(npa->npa_idctl, cap,
 589                     npa->npa_version);
 590 
 591                 free(cap);
 592         } else {
 593                 (void) printf("%s/%s: ", npa->npa_name,
 594                     di_minor_name(npa->npa_minor));
 595                 nvme_print_identify_nsid(npa->npa_idns,
 596                     npa->npa_version);
 597         }
 598 
 599         return (0);
 600 }
 601 
 602 static void
 603 usage_get_logpage(const char *c_name)
 604 {
 605         (void) fprintf(stderr, "%s <ctl>[/<ns>][,...] <logpage>\n\n"
 606             "  Print the specified log page of the specified NVMe "
 607             "controllers and/or name-\n  spaces. Supported log pages "
 608             "are error, health, and firmware.\n", c_name);
 609 }
 610 
 611 static int
 612 do_get_logpage_error(int fd, const nvme_process_arg_t *npa)
 613 {
 614         int nlog = npa->npa_idctl->id_elpe + 1;
 615         size_t bufsize = sizeof (nvme_error_log_entry_t) * nlog;
 616         nvme_error_log_entry_t *elog;
 617 
 618         if (npa->npa_isns)
 619                 errx(-1, "Error Log not available on a per-namespace basis");
 620 
 621         elog = nvme_get_logpage(fd, NVME_LOGPAGE_ERROR, &bufsize);
 622 
 623         if (elog == NULL)
 624                 return (-1);
 625 
 626         nlog = bufsize / sizeof (nvme_error_log_entry_t);
 627 
 628         (void) printf("%s: ", npa->npa_name);
 629         nvme_print_error_log(nlog, elog);
 630 
 631         free(elog);
 632 
 633         return (0);
 634 }
 635 
 636 static int
 637 do_get_logpage_health(int fd, const nvme_process_arg_t *npa)
 638 {
 639         size_t bufsize = sizeof (nvme_health_log_t);
 640         nvme_health_log_t *hlog;
 641 
 642         if (npa->npa_isns) {
 643                 if (npa->npa_idctl->id_lpa.lp_smart == 0)
 644                         errx(-1, "SMART/Health information not available "
 645                             "on a per-namespace basis on this controller");
 646         }
 647 
 648         hlog = nvme_get_logpage(fd, NVME_LOGPAGE_HEALTH, &bufsize);
 649 
 650         if (hlog == NULL)
 651                 return (-1);
 652 
 653         (void) printf("%s: ", npa->npa_name);
 654         nvme_print_health_log(hlog, npa->npa_idctl);
 655 
 656         free(hlog);
 657 
 658         return (0);
 659 }
 660 
 661 static int
 662 do_get_logpage_fwslot(int fd, const nvme_process_arg_t *npa)
 663 {
 664         size_t bufsize = sizeof (nvme_fwslot_log_t);
 665         nvme_fwslot_log_t *fwlog;
 666 
 667         if (npa->npa_isns)
 668                 errx(-1, "Firmware Slot information not available on a "
 669                     "per-namespace basis");
 670 
 671         fwlog = nvme_get_logpage(fd, NVME_LOGPAGE_FWSLOT, &bufsize);
 672 
 673         if (fwlog == NULL)
 674                 return (-1);
 675 
 676         (void) printf("%s: ", npa->npa_name);
 677         nvme_print_fwslot_log(fwlog);
 678 
 679         free(fwlog);
 680 
 681         return (0);
 682 }
 683 
 684 static int
 685 do_get_logpage(int fd, const nvme_process_arg_t *npa)
 686 {
 687         int ret = 0;
 688         int (*func)(int, const nvme_process_arg_t *);
 689 
 690         if (npa->npa_argc < 1) {
 691                 warnx("missing logpage name");
 692                 usage(npa->npa_cmd);
 693                 exit(-1);
 694         }
 695 
 696         if (strcmp(npa->npa_argv[0], "error") == 0)
 697                 func = do_get_logpage_error;
 698         else if (strcmp(npa->npa_argv[0], "health") == 0)
 699                 func = do_get_logpage_health;
 700         else if (strcmp(npa->npa_argv[0], "firmware") == 0)
 701                 func = do_get_logpage_fwslot;
 702         else
 703                 errx(-1, "invalid log page: %s", npa->npa_argv[0]);
 704 
 705         ret = func(fd, npa);
 706         return (ret);
 707 }
 708 
 709 static void
 710 usage_get_features(const char *c_name)
 711 {
 712         const nvme_feature_t *feat;
 713 
 714         (void) fprintf(stderr, "%s <ctl>[/<ns>][,...] [<feature>[,...]]\n\n"
 715             "  Print the specified features of the specified NVMe controllers "
 716             "and/or\n  namespaces. Supported features are:\n\n", c_name);
 717         (void) fprintf(stderr, "    %-35s %-14s %s\n",
 718             "FEATURE NAME", "SHORT NAME", "CONTROLLER/NAMESPACE");
 719         for (feat = &features[0]; feat->f_feature != 0; feat++) {
 720                 char *type;
 721 
 722                 if ((feat->f_getflags & NVMEADM_BOTH) == NVMEADM_BOTH)
 723                         type = "both";
 724                 else if ((feat->f_getflags & NVMEADM_CTRL) != 0)
 725                         type = "controller only";
 726                 else
 727                         type = "namespace only";
 728 
 729                 (void) fprintf(stderr, "    %-35s %-14s %s\n",
 730                     feat->f_name, feat->f_short, type);
 731         }
 732 
 733 }
 734 
 735 static int
 736 do_get_feat_common(int fd, const nvme_feature_t *feat,
 737     nvme_identify_ctrl_t *idctl)
 738 {
 739         void *buf = NULL;
 740         size_t bufsize = feat->f_bufsize;
 741         uint64_t res;
 742 
 743         if (nvme_get_feature(fd, feat->f_feature, 0, &res, &bufsize, &buf)
 744             == B_FALSE)
 745                 return (EINVAL);
 746 
 747         nvme_print(2, feat->f_name, -1, NULL);
 748         feat->f_print(res, buf, bufsize, idctl);
 749         free(buf);
 750 
 751         return (0);
 752 }
 753 
 754 static int
 755 do_get_feat_intr_vect(int fd, const nvme_feature_t *feat,
 756     nvme_identify_ctrl_t *idctl)
 757 {
 758         uint64_t res;
 759         uint64_t arg;
 760         int intr_cnt;
 761 
 762         intr_cnt = nvme_intr_cnt(fd);
 763 
 764         if (intr_cnt == -1)
 765                 return (EINVAL);
 766 
 767         nvme_print(2, feat->f_name, -1, NULL);
 768 
 769         for (arg = 0; arg < intr_cnt; arg++) {
 770                 if (nvme_get_feature(fd, feat->f_feature, arg, &res, NULL, NULL)
 771                     == B_FALSE)
 772                         return (EINVAL);
 773 
 774                 feat->f_print(res, NULL, 0, idctl);
 775         }
 776 
 777         return (0);
 778 }
 779 
 780 static int
 781 do_get_features(int fd, const nvme_process_arg_t *npa)
 782 {
 783         const nvme_feature_t *feat;
 784         char *f, *flist, *lasts;
 785         boolean_t header_printed = B_FALSE;
 786 
 787         if (npa->npa_argc > 1)
 788                 errx(-1, "unexpected arguments");
 789 
 790         /*
 791          * No feature list given, print all supported features.
 792          */
 793         if (npa->npa_argc == 0) {
 794                 (void) printf("%s: Get Features\n", npa->npa_name);
 795                 for (feat = &features[0]; feat->f_feature != 0; feat++) {
 796                         if ((npa->npa_isns &&
 797                             (feat->f_getflags & NVMEADM_NS) == 0) ||
 798                             (!npa->npa_isns &&
 799                             (feat->f_getflags & NVMEADM_CTRL) == 0))
 800                                 continue;
 801 
 802                         (void) feat->f_get(fd, feat, npa->npa_idctl);
 803                 }
 804 
 805                 return (0);
 806         }
 807 
 808         /*
 809          * Process feature list.
 810          */
 811         flist = strdup(npa->npa_argv[0]);
 812         if (flist == NULL)
 813                 err(-1, "do_get_features");
 814 
 815         for (f = strtok_r(flist, ",", &lasts);
 816             f != NULL;
 817             f = strtok_r(NULL, ",", &lasts)) {
 818                 while (isspace(*f))
 819                         f++;
 820 
 821                 for (feat = &features[0]; feat->f_feature != 0; feat++) {
 822                         if (strncasecmp(feat->f_name, f, strlen(f)) == 0 ||
 823                             strncasecmp(feat->f_short, f, strlen(f)) == 0)
 824                                 break;
 825                 }
 826 
 827                 if (feat->f_feature == 0) {
 828                         warnx("unknown feature %s", f);
 829                         continue;
 830                 }
 831 
 832                 if ((npa->npa_isns &&
 833                     (feat->f_getflags & NVMEADM_NS) == 0) ||
 834                     (!npa->npa_isns &&
 835                     (feat->f_getflags & NVMEADM_CTRL) == 0)) {
 836                         warnx("feature %s %s supported for namespaces",
 837                             feat->f_name, (feat->f_getflags & NVMEADM_NS) != 0 ?
 838                             "only" : "not");
 839                         continue;
 840                 }
 841 
 842                 if (!header_printed) {
 843                         (void) printf("%s: Get Features\n", npa->npa_name);
 844                         header_printed = B_TRUE;
 845                 }
 846 
 847                 if (feat->f_get(fd, feat, npa->npa_idctl) != 0) {
 848                         warnx("unsupported feature: %s", feat->f_name);
 849                         continue;
 850                 }
 851         }
 852 
 853         free(flist);
 854         return (0);
 855 }
 856 
 857 static int
 858 do_format_common(int fd, const nvme_process_arg_t *npa, unsigned long lbaf,
 859     unsigned long ses)
 860 {
 861         nvme_process_arg_t ns_npa = { 0 };
 862         nvmeadm_cmd_t cmd = { 0 };
 863 
 864         cmd = *(npa->npa_cmd);
 865         cmd.c_func = do_attach_detach;
 866         cmd.c_name = "detach";
 867         ns_npa = *npa;
 868         ns_npa.npa_cmd = &cmd;
 869 
 870         if (do_attach_detach(fd, &ns_npa) != 0)
 871                 return (exitcode);
 872         if (nvme_format_nvm(fd, lbaf, ses) == B_FALSE) {
 873                 warn("%s failed", npa->npa_cmd->c_name);
 874                 exitcode += -1;
 875         }
 876         cmd.c_name = "attach";
 877         exitcode += do_attach_detach(fd, &ns_npa);
 878 
 879         return (exitcode);
 880 }
 881 
 882 static void
 883 usage_format(const char *c_name)
 884 {
 885         (void) fprintf(stderr, "%s <ctl>[/<ns>] [<lba-format>]\n\n"
 886             "  Format one or all namespaces of the specified NVMe "
 887             "controller. Supported LBA\n  formats can be queried with "
 888             "the \"%s identify\" command on the namespace\n  to be "
 889             "formatted.\n", c_name, getprogname());
 890 }
 891 
 892 static int
 893 do_format(int fd, const nvme_process_arg_t *npa)
 894 {
 895         unsigned long lbaf;
 896 
 897         if (npa->npa_idctl->id_oacs.oa_format == 0)
 898                 errx(-1, "%s not supported", npa->npa_cmd->c_name);
 899 
 900         if (npa->npa_isns && npa->npa_idctl->id_fna.fn_format != 0)
 901                 errx(-1, "%s not supported on individual namespace",
 902                     npa->npa_cmd->c_name);
 903 
 904 
 905         if (npa->npa_argc > 0) {
 906                 errno = 0;
 907                 lbaf = strtoul(npa->npa_argv[0], NULL, 10);
 908 
 909                 if (errno != 0 || lbaf > NVME_FRMT_MAX_LBAF)
 910                         errx(-1, "invalid LBA format %d", lbaf + 1);
 911 
 912                 if (npa->npa_idns->id_lbaf[lbaf].lbaf_ms != 0)
 913                         errx(-1, "LBA formats with metadata not supported");
 914         } else {
 915                 lbaf = npa->npa_idns->id_flbas.lba_format;
 916         }
 917 
 918         return (do_format_common(fd, npa, lbaf, 0));
 919 }
 920 
 921 static void
 922 usage_secure_erase(const char *c_name)
 923 {
 924         (void) fprintf(stderr, "%s <ctl>[/<ns>] [-c]\n\n"
 925             "  Secure-Erase one or all namespaces of the specified "
 926             "NVMe controller.\n", c_name);
 927 }
 928 
 929 static int
 930 do_secure_erase(int fd, const nvme_process_arg_t *npa)
 931 {
 932         unsigned long lbaf;
 933         uint8_t ses = NVME_FRMT_SES_USER;
 934 
 935         if (npa->npa_idctl->id_oacs.oa_format == 0)
 936                 errx(-1, "%s not supported", npa->npa_cmd->c_name);
 937 
 938         if (npa->npa_isns && npa->npa_idctl->id_fna.fn_sec_erase != 0)
 939                 errx(-1, "%s not supported on individual namespace",
 940                     npa->npa_cmd->c_name);
 941 
 942         if (npa->npa_argc > 0) {
 943                 if (strcmp(npa->npa_argv[0], "-c") == 0)
 944                         ses = NVME_FRMT_SES_CRYPTO;
 945                 else
 946                         usage(npa->npa_cmd);
 947         }
 948 
 949         if (ses == NVME_FRMT_SES_CRYPTO &&
 950             npa->npa_idctl->id_fna.fn_crypt_erase == 0)
 951                 errx(-1, "cryptographic %s not supported",
 952                     npa->npa_cmd->c_name);
 953 
 954         lbaf = npa->npa_idns->id_flbas.lba_format;
 955 
 956         return (do_format_common(fd, npa, lbaf, ses));
 957 }
 958 
 959 static void
 960 usage_attach_detach(const char *c_name)
 961 {
 962         (void) fprintf(stderr, "%s <ctl>[/<ns>]\n\n"
 963             "  %c%s blkdev(7d) %s one or all namespaces of the "
 964             "specified NVMe controller.\n",
 965             c_name, toupper(c_name[0]), &c_name[1],
 966             c_name[0] == 'd' ? "from" : "to");
 967 }
 968 
 969 static int
 970 do_attach_detach(int fd, const nvme_process_arg_t *npa)
 971 {
 972         char *c_name = npa->npa_cmd->c_name;
 973 
 974         if (!npa->npa_isns) {
 975                 nvme_process_arg_t ns_npa = { 0 };
 976 
 977                 ns_npa.npa_name = npa->npa_name;
 978                 ns_npa.npa_isns = B_TRUE;
 979                 ns_npa.npa_cmd = npa->npa_cmd;
 980 
 981                 nvme_walk(&ns_npa, npa->npa_node);
 982 
 983                 return (exitcode);
 984         } else {
 985                 if ((c_name[0] == 'd' ? nvme_detach : nvme_attach)(fd)
 986                     == B_FALSE) {
 987                         warn("%s failed", c_name);
 988                         return (-1);
 989                 }
 990         }
 991 
 992         return (0);
 993 }