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 }