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 
 543         (void) printf("  %s/%s (%s): ", npa->npa_name,
 544             di_minor_name(npa->npa_minor),
 545             npa->npa_dsk != NULL ? npa->npa_dsk : "unattached");
 546         nvme_print_nsid_summary(npa->npa_idns);
 547 
 548         return (0);
 549 }
 550 
 551 static int
 552 do_list(int fd, const nvme_process_arg_t *npa)
 553 {
 554         _NOTE(ARGUNUSED(fd));
 555 
 556         nvme_process_arg_t ns_npa = { 0 };
 557         nvmeadm_cmd_t cmd = { 0 };
 558         char *name;
 559 
 560         if (asprintf(&name, "%s%d", di_driver_name(npa->npa_node),
 561             di_instance(npa->npa_node)) < 0)
 562                 err(-1, "do_list()");
 563 
 564         (void) printf("%s: ", name);
 565         nvme_print_ctrl_summary(npa->npa_idctl, npa->npa_version);
 566 
 567         ns_npa.npa_name = name;
 568         ns_npa.npa_isns = B_TRUE;
 569         ns_npa.npa_nsid = npa->npa_nsid;
 570         cmd = *(npa->npa_cmd);
 571         cmd.c_func = do_list_nsid;
 572         ns_npa.npa_cmd = &cmd;
 573 
 574         nvme_walk(&ns_npa, npa->npa_node);
 575 
 576         free(name);
 577 
 578         return (exitcode);
 579 }
 580 
 581 static void
 582 usage_identify(const char *c_name)
 583 {
 584         (void) fprintf(stderr, "%s <ctl>[/<ns>][,...]\n\n"
 585             "  Print detailed information about the specified NVMe "
 586             "controllers and/or name-\n  spaces.\n", c_name);
 587 }
 588 
 589 static int
 590 do_identify(int fd, const nvme_process_arg_t *npa)
 591 {
 592         if (npa->npa_nsid == 0) {
 593                 nvme_capabilities_t *cap;
 594 
 595                 cap = nvme_capabilities(fd);
 596                 if (cap == NULL)
 597                         return (-1);
 598 
 599                 (void) printf("%s: ", npa->npa_name);
 600                 nvme_print_identify_ctrl(npa->npa_idctl, cap,
 601                     npa->npa_version);
 602 
 603                 free(cap);
 604         } else {
 605                 (void) printf("%s/%s: ", npa->npa_name,
 606                     di_minor_name(npa->npa_minor));
 607                 nvme_print_identify_nsid(npa->npa_idns,
 608                     npa->npa_version);
 609         }
 610 
 611         return (0);
 612 }
 613 
 614 static void
 615 usage_get_logpage(const char *c_name)
 616 {
 617         (void) fprintf(stderr, "%s <ctl>[/<ns>][,...] <logpage>\n\n"
 618             "  Print the specified log page of the specified NVMe "
 619             "controllers and/or name-\n  spaces. Supported log pages "
 620             "are error, health, and firmware.\n", c_name);
 621 }
 622 
 623 static int
 624 do_get_logpage_error(int fd, const nvme_process_arg_t *npa)
 625 {
 626         int nlog = npa->npa_idctl->id_elpe + 1;
 627         size_t bufsize = sizeof (nvme_error_log_entry_t) * nlog;
 628         nvme_error_log_entry_t *elog;
 629 
 630         if (npa->npa_nsid != 0)
 631                 errx(-1, "Error Log not available on a per-namespace basis");
 632 
 633         elog = nvme_get_logpage(fd, NVME_LOGPAGE_ERROR, &bufsize);
 634 
 635         if (elog == NULL)
 636                 return (-1);
 637 
 638         nlog = bufsize / sizeof (nvme_error_log_entry_t);
 639 
 640         (void) printf("%s: ", npa->npa_name);
 641         nvme_print_error_log(nlog, elog);
 642 
 643         free(elog);
 644 
 645         return (0);
 646 }
 647 
 648 static int
 649 do_get_logpage_health(int fd, const nvme_process_arg_t *npa)
 650 {
 651         size_t bufsize = sizeof (nvme_health_log_t);
 652         nvme_health_log_t *hlog;
 653 
 654         if (npa->npa_nsid != 0) {
 655                 if (npa->npa_idctl->id_lpa.lp_smart == 0)
 656                         errx(-1, "SMART/Health information not available "
 657                             "on a per-namespace basis on this controller");
 658         }
 659 
 660         hlog = nvme_get_logpage(fd, NVME_LOGPAGE_HEALTH, &bufsize);
 661 
 662         if (hlog == NULL)
 663                 return (-1);
 664 
 665         (void) printf("%s: ", npa->npa_name);
 666         nvme_print_health_log(hlog, npa->npa_idctl);
 667 
 668         free(hlog);
 669 
 670         return (0);
 671 }
 672 
 673 static int
 674 do_get_logpage_fwslot(int fd, const nvme_process_arg_t *npa)
 675 {
 676         size_t bufsize = sizeof (nvme_fwslot_log_t);
 677         nvme_fwslot_log_t *fwlog;
 678 
 679         if (npa->npa_nsid != 0)
 680                 errx(-1, "Firmware Slot information not available on a "
 681                     "per-namespace basis");
 682 
 683         fwlog = nvme_get_logpage(fd, NVME_LOGPAGE_FWSLOT, &bufsize);
 684 
 685         if (fwlog == NULL)
 686                 return (-1);
 687 
 688         (void) printf("%s: ", npa->npa_name);
 689         nvme_print_fwslot_log(fwlog);
 690 
 691         free(fwlog);
 692 
 693         return (0);
 694 }
 695 
 696 static int
 697 do_get_logpage(int fd, const nvme_process_arg_t *npa)
 698 {
 699         int ret = 0;
 700         int (*func)(int, const nvme_process_arg_t *);
 701 
 702         if (npa->npa_argc < 1) {
 703                 warnx("missing logpage name");
 704                 usage(npa->npa_cmd);
 705                 exit(-1);
 706         }
 707 
 708         if (strcmp(npa->npa_argv[0], "error") == 0)
 709                 func = do_get_logpage_error;
 710         else if (strcmp(npa->npa_argv[0], "health") == 0)
 711                 func = do_get_logpage_health;
 712         else if (strcmp(npa->npa_argv[0], "firmware") == 0)
 713                 func = do_get_logpage_fwslot;
 714         else
 715                 errx(-1, "invalid log page: %s", npa->npa_argv[0]);
 716 
 717         ret = func(fd, npa);
 718         return (ret);
 719 }
 720 
 721 static void
 722 usage_get_features(const char *c_name)
 723 {
 724         const nvme_feature_t *feat;
 725 
 726         (void) fprintf(stderr, "%s <ctl>[/<ns>][,...] [<feature>[,...]]\n\n"
 727             "  Print the specified features of the specified NVMe controllers "
 728             "and/or\n  namespaces. Supported features are:\n\n", c_name);
 729         (void) fprintf(stderr, "    %-35s %-14s %s\n",
 730             "FEATURE NAME", "SHORT NAME", "CONTROLLER/NAMESPACE");
 731         for (feat = &features[0]; feat->f_feature != 0; feat++) {
 732                 char *type;
 733 
 734                 if ((feat->f_getflags & NVMEADM_BOTH) == NVMEADM_BOTH)
 735                         type = "both";
 736                 else if ((feat->f_getflags & NVMEADM_CTRL) != 0)
 737                         type = "controller only";
 738                 else
 739                         type = "namespace only";
 740 
 741                 (void) fprintf(stderr, "    %-35s %-14s %s\n",
 742                     feat->f_name, feat->f_short, type);
 743         }
 744 
 745 }
 746 
 747 static int
 748 do_get_feat_common(int fd, const nvme_feature_t *feat,
 749     nvme_identify_ctrl_t *idctl)
 750 {
 751         void *buf = NULL;
 752         size_t bufsize = feat->f_bufsize;
 753         uint64_t res;
 754 
 755         if (nvme_get_feature(fd, feat->f_feature, 0, &res, &bufsize, &buf)
 756             == B_FALSE)
 757                 return (EINVAL);
 758 
 759         nvme_print(2, feat->f_name, -1, NULL);
 760         feat->f_print(res, buf, bufsize, idctl);
 761         free(buf);
 762 
 763         return (0);
 764 }
 765 
 766 static int
 767 do_get_feat_intr_vect(int fd, const nvme_feature_t *feat,
 768     nvme_identify_ctrl_t *idctl)
 769 {
 770         uint64_t res;
 771         uint64_t arg;
 772         int intr_cnt;
 773 
 774         intr_cnt = nvme_intr_cnt(fd);
 775 
 776         if (intr_cnt == -1)
 777                 return (EINVAL);
 778 
 779         nvme_print(2, feat->f_name, -1, NULL);
 780 
 781         for (arg = 0; arg < intr_cnt; arg++) {
 782                 if (nvme_get_feature(fd, feat->f_feature, arg, &res, NULL, NULL)
 783                     == B_FALSE)
 784                         return (EINVAL);
 785 
 786                 feat->f_print(res, NULL, 0, idctl);
 787         }
 788 
 789         return (0);
 790 }
 791 
 792 static int
 793 do_get_features(int fd, const nvme_process_arg_t *npa)
 794 {
 795         const nvme_feature_t *feat;
 796         char *f, *flist, *lasts;
 797         boolean_t header_printed = B_FALSE;
 798 
 799         if (npa->npa_argc > 1)
 800                 errx(-1, "unexpected arguments");
 801 
 802         /*
 803          * No feature list given, print all supported features.
 804          */
 805         if (npa->npa_argc == 0) {
 806                 (void) printf("%s: Get Features\n", npa->npa_name);
 807                 for (feat = &features[0]; feat->f_feature != 0; feat++) {
 808                         if ((npa->npa_nsid != 0 &&
 809                             (feat->f_getflags & NVMEADM_NS) == 0) ||
 810                             (npa->npa_nsid == 0 &&
 811                             (feat->f_getflags & NVMEADM_CTRL) == 0))
 812                                 continue;
 813 
 814                         (void) feat->f_get(fd, feat, npa->npa_idctl);
 815                 }
 816 
 817                 return (0);
 818         }
 819 
 820         /*
 821          * Process feature list.
 822          */
 823         flist = strdup(npa->npa_argv[0]);
 824         if (flist == NULL)
 825                 err(-1, "do_get_features");
 826 
 827         for (f = strtok_r(flist, ",", &lasts);
 828             f != NULL;
 829             f = strtok_r(NULL, ",", &lasts)) {
 830                 while (isspace(*f))
 831                         f++;
 832 
 833                 for (feat = &features[0]; feat->f_feature != 0; feat++) {
 834                         if (strncasecmp(feat->f_name, f, strlen(f)) == 0 ||
 835                             strncasecmp(feat->f_short, f, strlen(f)) == 0)
 836                                 break;
 837                 }
 838 
 839                 if (feat->f_feature == 0) {
 840                         warnx("unknown feature %s", f);
 841                         continue;
 842                 }
 843 
 844                 if ((npa->npa_nsid != 0 &&
 845                     (feat->f_getflags & NVMEADM_NS) == 0) ||
 846                     (npa->npa_nsid == 0 &&
 847                     (feat->f_getflags & NVMEADM_CTRL) == 0)) {
 848                         warnx("feature %s %s supported for namespaces",
 849                             feat->f_name, (feat->f_getflags & NVMEADM_NS) != 0 ?
 850                             "only" : "not");
 851                         continue;
 852                 }
 853 
 854                 if (!header_printed) {
 855                         (void) printf("%s: Get Features\n", npa->npa_name);
 856                         header_printed = B_TRUE;
 857                 }
 858 
 859                 if (feat->f_get(fd, feat, npa->npa_idctl) != 0) {
 860                         warnx("unsupported feature: %s", feat->f_name);
 861                         continue;
 862                 }
 863         }
 864 
 865         free(flist);
 866         return (0);
 867 }
 868 
 869 static int
 870 do_format_common(int fd, const nvme_process_arg_t *npa, unsigned long lbaf,
 871     unsigned long ses)
 872 {
 873         nvme_process_arg_t ns_npa = { 0 };
 874         nvmeadm_cmd_t cmd = { 0 };
 875 
 876         cmd = *(npa->npa_cmd);
 877         cmd.c_func = do_attach_detach;
 878         cmd.c_name = "detach";
 879         ns_npa = *npa;
 880         ns_npa.npa_cmd = &cmd;
 881 
 882         if (do_attach_detach(fd, &ns_npa) != 0)
 883                 return (exitcode);
 884         if (nvme_format_nvm(fd, lbaf, ses) == B_FALSE) {
 885                 warn("%s failed", npa->npa_cmd->c_name);
 886                 exitcode += -1;
 887         }
 888         cmd.c_name = "attach";
 889         exitcode += do_attach_detach(fd, &ns_npa);
 890 
 891         return (exitcode);
 892 }
 893 
 894 static void
 895 usage_format(const char *c_name)
 896 {
 897         (void) fprintf(stderr, "%s <ctl>[/<ns>] [<lba-format>]\n\n"
 898             "  Format one or all namespaces of the specified NVMe "
 899             "controller. Supported LBA\n  formats can be queried with "
 900             "the \"%s identify\" command on the namespace\n  to be "
 901             "formatted.\n", c_name, getprogname());
 902 }
 903 
 904 static int
 905 do_format(int fd, const nvme_process_arg_t *npa)
 906 {
 907         unsigned long lbaf;
 908 
 909         if (npa->npa_idctl->id_oacs.oa_format == 0)
 910                 errx(-1, "%s not supported", npa->npa_cmd->c_name);
 911 
 912         if (npa->npa_isns && npa->npa_idctl->id_fna.fn_format != 0)
 913                 errx(-1, "%s not supported on individual namespace",
 914                     npa->npa_cmd->c_name);
 915 
 916 
 917         if (npa->npa_argc > 0) {
 918                 errno = 0;
 919                 lbaf = strtoul(npa->npa_argv[0], NULL, 10);
 920 
 921                 if (errno != 0 || lbaf > NVME_FRMT_MAX_LBAF)
 922                         errx(-1, "invalid LBA format %d", lbaf + 1);
 923 
 924                 if (npa->npa_idns->id_lbaf[lbaf].lbaf_ms != 0)
 925                         errx(-1, "LBA formats with metadata not supported");
 926         } else {
 927                 lbaf = npa->npa_idns->id_flbas.lba_format;
 928         }
 929 
 930         return (do_format_common(fd, npa, lbaf, 0));
 931 }
 932 
 933 static void
 934 usage_secure_erase(const char *c_name)
 935 {
 936         (void) fprintf(stderr, "%s <ctl>[/<ns>] [-c]\n\n"
 937             "  Secure-Erase one or all namespaces of the specified "
 938             "NVMe controller.\n", c_name);
 939 }
 940 
 941 static int
 942 do_secure_erase(int fd, const nvme_process_arg_t *npa)
 943 {
 944         unsigned long lbaf;
 945         uint8_t ses = NVME_FRMT_SES_USER;
 946 
 947         if (npa->npa_idctl->id_oacs.oa_format == 0)
 948                 errx(-1, "%s not supported", npa->npa_cmd->c_name);
 949 
 950         if (npa->npa_isns && npa->npa_idctl->id_fna.fn_sec_erase != 0)
 951                 errx(-1, "%s not supported on individual namespace",
 952                     npa->npa_cmd->c_name);
 953 
 954         if (npa->npa_argc > 0) {
 955                 if (strcmp(npa->npa_argv[0], "-c") == 0)
 956                         ses = NVME_FRMT_SES_CRYPTO;
 957                 else
 958                         usage(npa->npa_cmd);
 959         }
 960 
 961         if (ses == NVME_FRMT_SES_CRYPTO &&
 962             npa->npa_idctl->id_fna.fn_crypt_erase == 0)
 963                 errx(-1, "cryptographic %s not supported",
 964                     npa->npa_cmd->c_name);
 965 
 966         lbaf = npa->npa_idns->id_flbas.lba_format;
 967 
 968         return (do_format_common(fd, npa, lbaf, ses));
 969 }
 970 
 971 static void
 972 usage_attach_detach(const char *c_name)
 973 {
 974         (void) fprintf(stderr, "%s <ctl>[/<ns>]\n\n"
 975             "  %c%s blkdev(7d) %s one or all namespaces of the "
 976             "specified NVMe controller.\n",
 977             c_name, toupper(c_name[0]), &c_name[1],
 978             c_name[0] == 'd' ? "from" : "to");
 979 }
 980 
 981 static int
 982 do_attach_detach(int fd, const nvme_process_arg_t *npa)
 983 {
 984         char *c_name = npa->npa_cmd->c_name;
 985 
 986         if (!npa->npa_isns) {
 987                 nvme_process_arg_t ns_npa = { 0 };
 988 
 989                 ns_npa.npa_name = npa->npa_name;
 990                 ns_npa.npa_isns = B_TRUE;
 991                 ns_npa.npa_cmd = npa->npa_cmd;
 992 
 993                 nvme_walk(&ns_npa, npa->npa_node);
 994 
 995                 return (exitcode);
 996         } else {
 997                 if ((c_name[0] == 'd' ? nvme_detach : nvme_attach)(fd)
 998                     == B_FALSE) {
 999                         warn("%s failed", c_name);
1000                         return (-1);
1001                 }
1002         }
1003 
1004         return (0);
1005 }