Index: sbin/nvmecontrol/Makefile =================================================================== --- sbin/nvmecontrol/Makefile +++ sbin/nvmecontrol/Makefile @@ -2,10 +2,13 @@ PACKAGE=runtime PROG= nvmecontrol -SRCS= nvmecontrol.c devlist.c firmware.c format.c identify.c identify_ext.c logpage.c \ - perftest.c reset.c ns.c nvme_util.c power.c nc_util.c +SRCS= comnd.c nvmecontrol.c +SRCS+= devlist.c firmware.c format.c identify.c logpage.c ns.c perftest.c power.c reset.c +#SRCS+= passthru.c +SRCS+= identify_ext.c nvme_util.c nc_util.c MAN= nvmecontrol.8 LDFLAGS+= -rdynamic +LIBADD+= util SUBDIR= modules .PATH: ${SRCTOP}/sys/dev/nvme Index: sbin/nvmecontrol/comnd.h =================================================================== --- /dev/null +++ sbin/nvmecontrol/comnd.h @@ -0,0 +1,102 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Netflix, Inc + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef COMND_H +#define COMND_H + +#include +#include + +/* + * Regularized parsing of simple arguments built on top of getopt_long. + */ + +typedef enum arg_type { + arg_none = 0, + arg_uint8, + arg_uint16, + arg_uint32, + arg_uint64, + arg_size, + arg_string, + arg_path, +} arg_type; + +// XXX need to change to offsetof for opts and args. +// we then need to allocate ctx and pass that into the cmd +// stuff. this will be a little tricky and we may need to expand +// arg_type stuff. + +struct opts { + const char *long_arg; + int short_arg; + arg_type at; + void *ptr; // XXXX change to offset of + const char *descr; +}; + +// XXX TDB: subcommand vs actual argument. maybe with subcmd? +// XXX TBD: do we need parsing callback functions? +struct args { + arg_type at; + void *ptr; // XXXX change to offset of + const char *descr; +}; + +typedef void (cmd_load_cb_t)(void *, void *); +struct cmd; +typedef void (cmd_fn_t)(const struct cmd *nf, int argc, char *argv[]); + +struct cmd { + SLIST_ENTRY(cmd) link; + const char *name; + cmd_fn_t *fn; + size_t ctx_size; + const struct opts *opts; + const struct args *args; + const char *descr; + SLIST_HEAD(,cmd) subcmd; + struct cmd *parent; +}; + +void cmd_register(struct cmd *, struct cmd *); +#define CMD_COMMAND(c) \ + static void cmd_register_##c(void) __attribute__((constructor)); \ + static void cmd_register_##c(void) { cmd_register(NULL, &c); } +#define CMD_SUBCOMMAND(c,sc) \ + static void cmd_register_##c_##sc(void) __attribute__((constructor)); \ + static void cmd_register_##c_##sc(void) { cmd_register(&c, &sc); } + +int arg_parse(int argc, char * const *argv, const struct cmd *f); +void arg_help(int argc, char * const *argv, const struct cmd *f); +void cmd_init(void); +void cmd_load_dir(const char *dir, cmd_load_cb_t *cb, void *argp); +int cmd_dispatch(int argc, char *argv[], const struct cmd *); + +#endif /* COMND_H */ Index: sbin/nvmecontrol/comnd.c =================================================================== --- /dev/null +++ sbin/nvmecontrol/comnd.c @@ -0,0 +1,326 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (C) 2019 Netflix, Inc + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "comnd.h" + +static struct cmd top; + +static void +print_usage(const struct cmd *f) +{ + + fprintf(stderr, " %s %-15s - %s\n", getprogname(), f->name, f->descr); +} + +static void +gen_usage(const struct cmd *t) +{ + struct cmd *walker; + + fprintf(stderr, "usage:\n"); + SLIST_FOREACH(walker, &t->subcmd, link) { + print_usage(walker); + } + exit(1); +} + +int +cmd_dispatch(int argc, char *argv[], const struct cmd *t) +{ + struct cmd *walker; + + if (t == NULL) + t = ⊤ + + if (argv[1] == NULL) { + gen_usage(t); + return (1); + } + SLIST_FOREACH(walker, &t->subcmd, link) { + if (strcmp(argv[1], walker->name) == 0) { + walker->fn(walker, argc-1, &argv[1]); + return (0); + } + } + fprintf(stderr, "Unknown command: %s\n", argv[1]); + gen_usage(t); + return (1); +} + +static void +arg_suffix(char *buf, size_t len, arg_type at) +{ + switch (at) { + case arg_none: + break; + case arg_string: + strlcat(buf, "=", len); + break; + case arg_path: + strlcat(buf, "=", len); + break; + default: + strlcat(buf, "=", len); + break; + } +} + +void +arg_help(int argc __unused, char * const *argv, const struct cmd *f) +{ + int i; + char buf[31]; + const struct opts *opts = f->opts; + const struct args *args = f->args; + + // XXX walk up the cmd list... + if (argv[optind]) + fprintf(stderr, "Unknown argument: %s\n", argv[optind]); + fprintf(stderr, "Usage:\n %s %s", getprogname(), argv[0]); + if (opts) + fprintf(stderr, " "); + if (args) { + while (args->descr != NULL) { + fprintf(stderr, " %s", args->descr); + args++; + } + } + fprintf(stderr, "\n\n%s\n", f->descr); + if (opts != NULL) { + fprintf(stderr, "Options:\n"); + for (i = 0; opts[i].long_arg != NULL; i++) { + *buf = '\0'; + if (isprint(opts[i].short_arg)) { + snprintf(buf, sizeof(buf), " -%c, ", opts[i].short_arg); + } else { + strlcpy(buf, " ", sizeof(buf)); + } + strlcat(buf, "--", sizeof(buf)); + strlcat(buf, opts[i].long_arg, sizeof(buf)); + arg_suffix(buf, sizeof(buf), opts[i].at); + fprintf(stderr, "%-30.30s - %s\n", buf, opts[i].descr); + } + } + exit(1); +} + +static int +find_long(struct option *lopts, int ch) +{ + int i; + + for (i = 0; lopts[i].val != ch && lopts[i].name != NULL; i++) + continue; + return i; +} + +int +arg_parse(int argc, char * const * argv, const struct cmd *f) +{ + int i, n, idx, ch; + uint64_t v; + struct option *lopts; + char *shortopts, *p; + const struct opts *opts = f->opts; + const struct args *args = f->args; + + if (opts == NULL) + n = 0; + else + for (n = 0; opts[n].long_arg != NULL;) + n++; + lopts = malloc((n + 2) * sizeof(struct option)); + if (lopts == NULL) + err(1, "option memory"); + p = shortopts = malloc((n + 3) * sizeof(char)); + if (shortopts == NULL) + err(1, "shortopts memory"); + idx = 0; + for (i = 0; i < n; i++) { + lopts[i].name = opts[i].long_arg; + lopts[i].has_arg = opts[i].at == arg_none ? no_argument : required_argument; + lopts[i].flag = NULL; + lopts[i].val = opts[i].short_arg; + if (isprint(opts[i].short_arg)) { + *p++ = opts[i].short_arg; + if (lopts[i].has_arg) + *p++ = ':'; + } + } + lopts[n].name = "help"; + lopts[n].has_arg = no_argument; + lopts[n].flag = NULL; + lopts[n].val = '?'; + *p++ = '?'; + *p++ = '\0'; + memset(lopts + n + 1, 0, sizeof(struct option)); + while ((ch = getopt_long(argc, argv, shortopts, lopts, &idx)) != -1) { + /* + * If ch != 0, we've found a short option, and we have to + * look it up lopts table. Otherwise idx is valid. + */ + if (ch != 0) + idx = find_long(lopts, ch); + if (idx == n) + arg_help(argc, argv, f); + switch (opts[idx].at) { + case arg_none: + *(bool *)opts[idx].ptr = true; + break; + case arg_string: + case arg_path: + *(const char **)opts[idx].ptr = optarg; + break; + case arg_uint8: + v = strtoul(optarg, NULL, 0); + if (v > 0xff) + goto bad_arg; + *(uint8_t *)opts[idx].ptr = v; + break; + case arg_uint16: + v = strtoul(optarg, NULL, 0); + if (v > 0xffff) + goto bad_arg; + *(uint16_t *)opts[idx].ptr = v; + break; + case arg_uint32: + v = strtoul(optarg, NULL, 0); + if (v > 0xffffffffu) + goto bad_arg; + *(uint32_t *)opts[idx].ptr = v; + break; + case arg_uint64: + v = strtoul(optarg, NULL, 0); + if (v > 0xffffffffffffffffull) + goto bad_arg; + *(uint64_t *)opts[idx].ptr = v; + break; + case arg_size: + if (expand_number(optarg, &v) < 0) + goto bad_arg; + *(uint64_t *)opts[idx].ptr = v; + break; + } + } + if (args) { + while (args->descr) { + if (optind >= argc) { + fprintf(stderr, "Missing arg %s\n", args->descr); + arg_help(argc, argv, f); + return (1); + } + *(char **)args->ptr = argv[optind++]; + args++; + } + } + free(lopts); + return (0); +bad_arg: + fprintf(stderr, "Bad value to --%s: %s\n", opts[idx].long_arg, optarg); + free(lopts); + exit(1); +} + +/* + * Loads all the .so's from the specified directory. + */ +void +cmd_load_dir(const char *dir __unused, cmd_load_cb_t cb __unused, void *argp __unused) +{ + DIR *d; + struct dirent *dent; + char *path = NULL; + void *h; + + d = opendir(dir); + if (d == NULL) + return; + for (dent = readdir(d); dent != NULL; dent = readdir(d)) { + if (strcmp(".so", dent->d_name + dent->d_namlen - 3) != 0) + continue; + asprintf(&path, "%s/%s", dir, dent->d_name); + if (path == NULL) + err(1, "Can't malloc for path, giving up."); + if ((h = dlopen(path, RTLD_NOW | RTLD_GLOBAL)) == NULL) + warnx("Can't load %s: %s", path, dlerror()); + else { + if (cb != NULL) + cb(argp, h); + } + free(path); + path = NULL; + } + closedir(d); +} + +void +cmd_register(struct cmd *up, struct cmd *cmd) +{ + struct cmd *walker, *last; + + if (up == NULL) + up = ⊤ + SLIST_INIT(&cmd->subcmd); + cmd->parent = up; + last = NULL; + SLIST_FOREACH(walker, &up->subcmd, link) { + if (strcmp(walker->name, cmd->name) > 0) + break; + last = walker; + } + if (last == NULL) { + SLIST_INSERT_HEAD(&up->subcmd, cmd, link); + } else { + SLIST_INSERT_AFTER(last, cmd, link); + } +} + +void +cmd_init(void) +{ + +} Index: sbin/nvmecontrol/devlist.c =================================================================== --- sbin/nvmecontrol/devlist.c +++ sbin/nvmecontrol/devlist.c @@ -42,12 +42,24 @@ #include #include "nvmecontrol.h" +#include "comnd.h" -#define DEVLIST_USAGE \ - "devlist\n" +/* Tables for command line parsing */ #define NVME_MAX_UNIT 256 +static cmd_fn_t devlist; + +static struct cmd devlist_cmd = { + .name = "devlist", + .fn = devlist, + .descr = "Display a list of NVMe controllers and namespaces." +}; + +CMD_COMMAND(devlist_cmd); + +/* End of tables for command line parsing */ + static inline uint32_t ns_get_sector_size(struct nvme_namespace_data *nsdata) { @@ -62,21 +74,17 @@ } static void -devlist(const struct nvme_function *nf, int argc, char *argv[]) +devlist(const struct cmd *f, int argc, char *argv[]) { struct nvme_controller_data cdata; struct nvme_namespace_data nsdata; char name[64]; uint8_t mn[64]; uint32_t i; - int ch, ctrlr, fd, found, ret; + int ctrlr, fd, found, ret; - while ((ch = getopt(argc, argv, "")) != -1) { - switch ((char)ch) { - default: - usage(nf); - } - } + if (arg_parse(argc, argv, f)) + return; ctrlr = -1; found = 0; @@ -119,5 +127,3 @@ exit(1); } - -NVME_COMMAND(top, devlist, devlist, DEVLIST_USAGE); Index: sbin/nvmecontrol/firmware.c =================================================================== --- sbin/nvmecontrol/firmware.c +++ sbin/nvmecontrol/firmware.c @@ -50,8 +50,52 @@ #include "nvmecontrol.h" -#define FIRMWARE_USAGE \ - "firmware [-s slot] [-f path_to_firmware] [-a] \n" +/* Tables for command line parsing */ + +static cmd_fn_t firmware; + +#define NONE 0xffffffffu +static struct options { + bool activate; + uint32_t slot; + const char *fw_img; + const char *dev; +} opt = { + .activate = false, + .slot = NONE, + .fw_img = NULL, + .dev = NULL, +}; + +static const struct opts firmware_opts[] = { +#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } + OPT("activate", 'a', arg_none, opt, activate, + "Attempt to activate firmware"), + OPT("slot", 's', arg_uint32, opt, slot, + "Slot to activate and/or download firmware to"), + OPT("firmware", 'f', arg_path, opt, fw_img, + "Firmware image to download"), + { NULL, 0, arg_none, NULL, NULL } +}; +#undef OPT + +static const struct args firmware_args[] = { + { arg_string, &opt.dev, "controller-id" }, + { arg_none, NULL, NULL }, +}; + +static struct cmd firmware_cmd = { + .name = "firmware", + .fn = firmware, + .descr = "Download firmware image to controller.", + .ctx_size = sizeof(opt), + .opts = firmware_opts, + .args = firmware_args, +}; + +CMD_COMMAND(firmware_cmd); + +/* End of tables for command line parsing */ static int slot_has_valid_firmware(int fd, int slot) @@ -69,7 +113,7 @@ } static void -read_image_file(char *path, void **buf, int32_t *size) +read_image_file(const char *path, void **buf, int32_t *size) { struct stat sb; int32_t filesize; @@ -174,74 +218,52 @@ } static void -firmware(const struct nvme_function *nf, int argc, char *argv[]) +firmware(const struct cmd *f, int argc, char *argv[]) { - int fd = -1, slot = 0; - int a_flag, f_flag; + int fd = -1; int activate_action, reboot_required; - int opt; - char *p, *image = NULL; - char *controller = NULL, prompt[64]; + char prompt[64]; void *buf = NULL; int32_t size = 0; uint16_t oacs_fw; uint8_t fw_slot1_ro, fw_num_slots; struct nvme_controller_data cdata; - a_flag = f_flag = false; + if (arg_parse(argc, argv, f)) + return; - while ((opt = getopt(argc, argv, "af:s:")) != -1) { - switch (opt) { - case 'a': - a_flag = true; - break; - case 's': - slot = strtol(optarg, &p, 0); - if (p != NULL && *p != '\0') { - fprintf(stderr, - "\"%s\" not valid slot.\n", - optarg); - usage(nf); - } else if (slot == 0) { - fprintf(stderr, - "0 is not a valid slot number. " - "Slot numbers start at 1.\n"); - usage(nf); - } else if (slot > 7) { - fprintf(stderr, - "Slot number %s specified which is " - "greater than max allowed slot number of " - "7.\n", optarg); - usage(nf); - } - break; - case 'f': - image = optarg; - f_flag = true; - break; - } + if (opt.slot == 0) { + fprintf(stderr, + "0 is not a valid slot number. " + "Slot numbers start at 1.\n"); + arg_help(argc, argv, f); + } else if (opt.slot > 7 && opt.slot != NONE) { + fprintf(stderr, + "Slot number %s specified which is " + "greater than max allowed slot number of " + "7.\n", optarg); + arg_help(argc, argv, f); } - /* Check that a controller (and not a namespace) was specified. */ - if (optind >= argc || strstr(argv[optind], NVME_NS_PREFIX) != NULL) - usage(nf); - - if (!f_flag && !a_flag) { + if (!opt.activate && opt.fw_img == NULL) { fprintf(stderr, "Neither a replace ([-f path_to_firmware]) nor " "activate ([-a]) firmware image action\n" "was specified.\n"); - usage(nf); + arg_help(argc, argv, f); } - if (!f_flag && a_flag && slot == 0) { + /* Check that a controller (and not a namespace) was specified. */ + if (strstr(opt.dev, NVME_NS_PREFIX) != NULL) + arg_help(argc, argv, f); + + if (opt.activate && opt.fw_img == NULL && opt.slot == 0) { fprintf(stderr, "Slot number to activate not specified.\n"); - usage(nf); + arg_help(argc, argv, f); } - controller = argv[optind]; - open_dev(controller, &fd, 1, 1); + open_dev(opt.dev, &fd, 1, 1); read_controller_data(fd, &cdata); oacs_fw = (cdata.oacs >> NVME_CTRLR_DATA_OACS_FIRMWARE_SHIFT) & @@ -254,44 +276,45 @@ fw_slot1_ro = (cdata.frmw >> NVME_CTRLR_DATA_FRMW_SLOT1_RO_SHIFT) & NVME_CTRLR_DATA_FRMW_SLOT1_RO_MASK; - if (f_flag && slot == 1 && fw_slot1_ro) - errx(1, "slot %d is marked as read only", slot); + if (opt.fw_img && opt.slot == 1 && fw_slot1_ro) + errx(1, "slot %d is marked as read only", opt.slot); fw_num_slots = (cdata.frmw >> NVME_CTRLR_DATA_FRMW_NUM_SLOTS_SHIFT) & NVME_CTRLR_DATA_FRMW_NUM_SLOTS_MASK; - if (slot > fw_num_slots) + if (opt.slot > fw_num_slots) errx(1, "slot %d specified but controller only supports %d slots", - slot, fw_num_slots); + opt.slot, fw_num_slots); - if (a_flag && !f_flag && !slot_has_valid_firmware(fd, slot)) + if (opt.activate && opt.fw_img == NULL && + !slot_has_valid_firmware(fd, opt.slot)) errx(1, "slot %d does not contain valid firmware,\n" "try 'nvmecontrol logpage -p 3 %s' to get a list " "of available images\n", - slot, controller); + opt.slot, opt.dev); - if (f_flag) - read_image_file(image, &buf, &size); + if (opt.fw_img) + read_image_file(opt.fw_img, &buf, &size); - if (f_flag && a_flag) + if (opt.fw_img != NULL&& opt.activate) printf("You are about to download and activate " "firmware image (%s) to controller %s.\n" "This may damage your controller and/or " "overwrite an existing firmware image.\n", - image, controller); - else if (a_flag) + opt.fw_img, opt.dev); + else if (opt.activate) printf("You are about to activate a new firmware " "image on controller %s.\n" "This may damage your controller.\n", - controller); - else if (f_flag) + opt.dev); + else if (opt.fw_img != NULL) printf("You are about to download firmware image " "(%s) to controller %s.\n" "This may damage your controller and/or " "overwrite an existing firmware image.\n", - image, controller); + opt.fw_img, opt.dev); printf("Are you sure you want to continue? (yes/no) "); while (1) { @@ -303,9 +326,9 @@ printf("Please answer \"yes\" or \"no\". "); } - if (f_flag) { + if (opt.fw_img != NULL) { update_firmware(fd, buf, size); - if (a_flag) + if (opt.activate) activate_action = NVME_AA_REPLACE_ACTIVATE; else activate_action = NVME_AA_REPLACE_NO_ACTIVATE; @@ -313,9 +336,9 @@ activate_action = NVME_AA_ACTIVATE; } - reboot_required = activate_firmware(fd, slot, activate_action); + reboot_required = activate_firmware(fd, opt.slot, activate_action); - if (a_flag) { + if (opt.activate) { if (reboot_required) { printf("New firmware image activated but requires " "conventional reset (i.e. reboot) to " @@ -325,12 +348,10 @@ "effect after next controller reset.\n" "Controller reset can be initiated via " "'nvmecontrol reset %s'\n", - controller); + opt.dev); } } close(fd); exit(0); } - -NVME_COMMAND(top, firmware, firmware, FIRMWARE_USAGE); Index: sbin/nvmecontrol/format.c =================================================================== --- sbin/nvmecontrol/format.c +++ sbin/nvmecontrol/format.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -43,57 +44,104 @@ #include "nvmecontrol.h" -#define FORMAT_USAGE \ - "format [-f fmt] [-m mset] [-p pi] [-l pil] [-E] [-C] \n" +#define NONE 0xffffffffu +#define SES_NONE 0 +#define SES_USER 1 +#define SES_CRYPTO 2 + +/* Tables for command line parsing */ + +static cmd_fn_t format; + +static struct options { + uint32_t lbaf; + uint32_t ms; + uint32_t pi; + uint32_t pil; + uint32_t ses; + bool Eflag; + bool Cflag; + const char *dev; +} opt = { + .lbaf = NONE, + .ms = NONE, + .pi = NONE, + .pil = NONE, + .ses = SES_NONE, + .Eflag = false, + .Cflag = false, + .dev = NULL, +}; + +static const struct opts format_opts[] = { +#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } + OPT("crypto", 'C', arg_none, opt, Cflag, + "Crptographically erase user data by forgetting key"), + OPT("erase", 'E', arg_none, opt, Eflag, + "Erase user data"), + OPT("lbaf", 'f', arg_uint32, opt, lbaf, + "Set the LBA Format to apply to the media"), + OPT("ms", 'm', arg_uint32, opt, ms, + "Slot to activate and/or download format to"), + OPT("pi", 'p', arg_uint32, opt, pi, + "Slot to activate and/or download format to"), + OPT("pil", 'l', arg_uint32, opt, pil, + "Slot to activate and/or download format to"), + OPT("ses", 's', arg_uint32, opt, ses, + "Slot to activate and/or download format to"), + { NULL, 0, arg_none, NULL, NULL } +}; +#undef OPT + +static const struct args format_args[] = { + { arg_string, &opt.dev, "controller-id|namespace-id" }, + { arg_none, NULL, NULL }, +}; + +static struct cmd format_cmd = { + .name = "format", + .fn = format, + .descr = "Format/erase one or all the namespaces.", + .ctx_size = sizeof(opt), + .opts = format_opts, + .args = format_args, +}; + +CMD_COMMAND(format_cmd); + +/* End of tables for command line parsing */ static void -format(const struct nvme_function *nf, int argc, char *argv[]) +format(const struct cmd *f, int argc, char *argv[]) { struct nvme_controller_data cd; struct nvme_namespace_data nsd; struct nvme_pt_command pt; char path[64]; - char *target; + const char *target; uint32_t nsid; - int ch, fd; - int lbaf = -1, mset = -1, pi = -1, pil = -1, ses = 0; - - if (argc < 2) - usage(nf); - - while ((ch = getopt(argc, argv, "f:m:p:l:EC")) != -1) { - switch ((char)ch) { - case 'f': - lbaf = strtol(optarg, NULL, 0); - break; - case 'm': - mset = strtol(optarg, NULL, 0); - break; - case 'p': - pi = strtol(optarg, NULL, 0); - break; - case 'l': - pil = strtol(optarg, NULL, 0); - break; - case 'E': - if (ses == 2) - errx(1, "-E and -C are mutually exclusive"); - ses = 1; - break; - case 'C': - if (ses == 1) - errx(1, "-E and -C are mutually exclusive"); - ses = 2; - break; - default: - usage(nf); - } + int lbaf, ms, pi, pil, ses, fd; + + if (arg_parse(argc, argv, f)) + return; + + if (opt.Eflag || opt.Cflag || opt.ses != SES_NONE) { + fprintf(stderr, + "Only one of -E, -C or -s may be specified\n"); + arg_help(argc, argv, f); } - /* Check that a controller or namespace was specified. */ - if (optind >= argc) - usage(nf); - target = argv[optind]; + target = opt.dev; + lbaf = opt.lbaf; + ms = opt.ms; + pi = opt.pi; + pil = opt.pil; + if (opt.Eflag) + ses = SES_USER; + else if (opt.Cflag) + ses = SES_CRYPTO; + else + ses = opt.ses; /* * Check if the specified device node exists before continuing. @@ -126,15 +174,15 @@ NVME_CTRLR_DATA_OACS_FORMAT_MASK) == 0) errx(1, "controller does not support format"); if (((cd.fna >> NVME_CTRLR_DATA_FNA_CRYPTO_ERASE_SHIFT) & - NVME_CTRLR_DATA_FNA_CRYPTO_ERASE_MASK) == 0 && ses == 2) + NVME_CTRLR_DATA_FNA_CRYPTO_ERASE_MASK) == 0 && ses == SES_CRYPTO) errx(1, "controller does not support cryptographic erase"); if (nsid != NVME_GLOBAL_NAMESPACE_TAG) { if (((cd.fna >> NVME_CTRLR_DATA_FNA_FORMAT_ALL_SHIFT) & - NVME_CTRLR_DATA_FNA_FORMAT_ALL_MASK) && ses == 0) + NVME_CTRLR_DATA_FNA_FORMAT_ALL_MASK) && ses == SES_NONE) errx(1, "controller does not support per-NS format"); if (((cd.fna >> NVME_CTRLR_DATA_FNA_ERASE_ALL_SHIFT) & - NVME_CTRLR_DATA_FNA_ERASE_ALL_MASK) && ses != 0) + NVME_CTRLR_DATA_FNA_ERASE_ALL_MASK) && ses != SES_NONE) errx(1, "controller does not support per-NS erase"); /* Try to keep previous namespace parameters. */ @@ -144,8 +192,8 @@ & NVME_NS_DATA_FLBAS_FORMAT_MASK; if (lbaf > nsd.nlbaf) errx(1, "LBA format is out of range"); - if (mset < 0) - mset = (nsd.flbas >> NVME_NS_DATA_FLBAS_EXTENDED_SHIFT) + if (ms < 0) + ms = (nsd.flbas >> NVME_NS_DATA_FLBAS_EXTENDED_SHIFT) & NVME_NS_DATA_FLBAS_EXTENDED_MASK; if (pi < 0) pi = (nsd.dps >> NVME_NS_DATA_DPS_MD_START_SHIFT) @@ -158,8 +206,8 @@ /* We have no previous parameters, so default to zeroes. */ if (lbaf < 0) lbaf = 0; - if (mset < 0) - mset = 0; + if (ms < 0) + ms = 0; if (pi < 0) pi = 0; if (pil < 0) @@ -170,7 +218,7 @@ pt.cmd.opc = NVME_OPC_FORMAT_NVM; pt.cmd.nsid = htole32(nsid); pt.cmd.cdw10 = htole32((ses << 9) + (pil << 8) + (pi << 5) + - (mset << 4) + lbaf); + (ms << 4) + lbaf); if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) err(1, "format request failed"); @@ -180,5 +228,3 @@ close(fd); exit(0); } - -NVME_COMMAND(top, format, format, FORMAT_USAGE); Index: sbin/nvmecontrol/identify.c =================================================================== --- sbin/nvmecontrol/identify.c +++ sbin/nvmecontrol/identify.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -43,8 +44,15 @@ #include "nvmecontrol.h" #include "nvmecontrol_ext.h" -#define IDENTIFY_USAGE \ - "identify [-x [-v]] \n" +static struct options { + bool hex; + bool verbose; + const char *dev; +} opt = { + .hex = false, + .verbose = false, + .dev = NULL, +}; static void print_namespace(struct nvme_namespace_data *nsdata) @@ -150,35 +158,17 @@ } static void -identify_ctrlr(const struct nvme_function *nf, int argc, char *argv[]) +identify_ctrlr(const struct cmd *f, int argc, char *argv[]) { struct nvme_controller_data cdata; - int ch, fd, hexflag = 0, hexlength; - int verboseflag = 0; + int fd, hexlength; - while ((ch = getopt(argc, argv, "vx")) != -1) { - switch ((char)ch) { - case 'v': - verboseflag = 1; - break; - case 'x': - hexflag = 1; - break; - default: - usage(nf); - } - } - - /* Check that a controller was specified. */ - if (optind >= argc) - usage(nf); - - open_dev(argv[optind], &fd, 1, 1); + open_dev(opt.dev, &fd, 1, 1); read_controller_data(fd, &cdata); close(fd); - if (hexflag == 1) { - if (verboseflag == 1) + if (opt.hex) { + if (opt.verbose) hexlength = sizeof(struct nvme_controller_data); else hexlength = offsetof(struct nvme_controller_data, @@ -187,9 +177,9 @@ exit(0); } - if (verboseflag == 1) { + if (opt.verbose) { fprintf(stderr, "-v not currently supported without -x\n"); - usage(nf); + arg_help(argc, argv, f); } nvme_print_controller(&cdata); @@ -197,37 +187,19 @@ } static void -identify_ns(const struct nvme_function *nf,int argc, char *argv[]) +identify_ns(const struct cmd *f, int argc, char *argv[]) { struct nvme_namespace_data nsdata; char path[64]; - int ch, fd, hexflag = 0, hexlength; - int verboseflag = 0; + int fd, hexlength; uint32_t nsid; - while ((ch = getopt(argc, argv, "vx")) != -1) { - switch ((char)ch) { - case 'v': - verboseflag = 1; - break; - case 'x': - hexflag = 1; - break; - default: - usage(nf); - } - } - - /* Check that a namespace was specified. */ - if (optind >= argc) - usage(nf); - /* * Check if the specified device node exists before continuing. * This is a cleaner check for cases where the correct controller * is specified, but an invalid namespace on that controller. */ - open_dev(argv[optind], &fd, 1, 1); + open_dev(opt.dev, &fd, 1, 1); close(fd); /* @@ -236,13 +208,13 @@ * the IDENTIFY command itself. So parse the namespace's device node * string to get the controller substring and namespace ID. */ - parse_ns_str(argv[optind], path, &nsid); + parse_ns_str(opt.dev, path, &nsid); open_dev(path, &fd, 1, 1); read_namespace_data(fd, nsid, &nsdata); close(fd); - if (hexflag == 1) { - if (verboseflag == 1) + if (opt.hex) { + if (opt.verbose) hexlength = sizeof(struct nvme_namespace_data); else hexlength = offsetof(struct nvme_namespace_data, @@ -251,9 +223,9 @@ exit(0); } - if (verboseflag == 1) { + if (opt.verbose) { fprintf(stderr, "-v not currently supported without -x\n"); - usage(nf); + arg_help(argc, argv, f); } print_namespace(&nsdata); @@ -261,32 +233,42 @@ } static void -identify(const struct nvme_function *nf, int argc, char *argv[]) +identify(const struct cmd *f, int argc, char *argv[]) { - char *target; - - if (argc < 2) - usage(nf); - - while (getopt(argc, argv, "vx") != -1) ; - - /* Check that a controller or namespace was specified. */ - if (optind >= argc) - usage(nf); - - target = argv[optind]; - - optreset = 1; - optind = 1; + arg_parse(argc, argv, f); /* * If device node contains "ns", we consider it a namespace, * otherwise, consider it a controller. */ - if (strstr(target, NVME_NS_PREFIX) == NULL) - identify_ctrlr(nf, argc, argv); + if (strstr(opt.dev, NVME_NS_PREFIX) == NULL) + identify_ctrlr(f, argc, argv); else - identify_ns(nf, argc, argv); + identify_ns(f, argc, argv); } -NVME_COMMAND(top, identify, identify, IDENTIFY_USAGE); +static const struct opts identify_opts[] = { +#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } + OPT("hex", 'x', arg_none, opt, hex, + "Print identiy information in hex"), + OPT("verbose", 'v', arg_none, opt, verbose, + "More verbosity: print entire identify table"), + { NULL, 0, arg_none, NULL, NULL } +}; +#undef OPT + +static const struct args identify_args[] = { + { arg_string, &opt.dev, "controller-id|namespace-id" }, + { arg_none, NULL, NULL }, +}; + +static struct cmd identify_cmd = { + .name = "identify", + .fn = identify, + .descr = "Print a human-readable summary of the IDENTIFY information", + .ctx_size = sizeof(opt), + .opts = identify_opts, + .args = identify_args, +}; + +CMD_COMMAND(identify_cmd); Index: sbin/nvmecontrol/logpage.c =================================================================== --- sbin/nvmecontrol/logpage.c +++ sbin/nvmecontrol/logpage.c @@ -48,8 +48,56 @@ #include "nvmecontrol.h" -#define LOGPAGE_USAGE \ - "logpage <-p page_id> [-b] [-v vendor] [-x] \n" \ +/* Tables for command line parsing */ + +static cmd_fn_t logpage; + +#define NONE 0xffffffffu +static struct options { + bool binary; + bool hex; + uint32_t page; + const char *vendor; + const char *dev; +} opt = { + .binary = false, + .hex = false, + .page = NONE, + .vendor = NULL, + .dev = NULL, +}; + +static const struct opts logpage_opts[] = { +#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } + OPT("binary", 'b', arg_none, opt, binary, + "Dump the log page as binary"), + OPT("hex", 'x', arg_none, opt, hex, + "Dump the log page as hex"), + OPT("page", 'p', arg_uint32, opt, page, + "Page to dump"), + OPT("vendor", 'v', arg_string, opt, vendor, + "Vendor specific formatting"), + { NULL, 0, arg_none, NULL, NULL } +}; +#undef OPT + +static const struct args logpage_args[] = { + { arg_string, &opt.dev, "" }, + { arg_none, NULL, NULL }, +}; + +static struct cmd logpage_cmd = { + .name = "logpage", + .fn = logpage, + .descr = "Print logpages in human-readable form", + .ctx_size = sizeof(opt), + .opts = logpage_opts, + .args = logpage_args, +}; + +CMD_COMMAND(logpage_cmd); + +/* End of tables for command line parsing */ #define MAX_FW_SLOTS (7) @@ -348,69 +396,40 @@ } static void -logpage(const struct nvme_function *nf, int argc, char *argv[]) +logpage(const struct cmd *f, int argc, char *argv[]) { int fd; - int log_page = 0, pageflag = false; - int binflag = false, hexflag = false, ns_specified; - int opt; - char *p; + bool ns_specified; char cname[64]; uint32_t nsid, size; void *buf; const char *vendor = NULL; - const struct logpage_function *f; + const struct logpage_function *lpf; struct nvme_controller_data cdata; print_fn_t print_fn; uint8_t ns_smart; - while ((opt = getopt(argc, argv, "bp:xv:")) != -1) { - switch (opt) { - case 'b': - binflag = true; - break; - case 'p': - if (strcmp(optarg, "help") == 0) - logpage_help(); - - /* TODO: Add human-readable ASCII page IDs */ - log_page = strtol(optarg, &p, 0); - if (p != NULL && *p != '\0') { - fprintf(stderr, - "\"%s\" not valid log page id.\n", - optarg); - usage(nf); - } - pageflag = true; - break; - case 'x': - hexflag = true; - break; - case 'v': - if (strcmp(optarg, "help") == 0) - logpage_help(); - vendor = optarg; - break; - } + if (arg_parse(argc, argv, f)) + return; + if (opt.hex && opt.binary) { + fprintf(stderr, + "Can't specify both binary and hex\n"); + arg_help(argc, argv, f); } - - if (!pageflag) { - printf("Missing page_id (-p).\n"); - usage(nf); + if (opt.vendor != NULL && strcmp(opt.vendor, "help") == 0) + logpage_help(); + if (opt.page == NONE) { + fprintf(stderr, "Missing page_id (-p).\n"); + arg_help(argc, argv, f); } - - /* Check that a controller and/or namespace was specified. */ - if (optind >= argc) - usage(nf); - - if (strstr(argv[optind], NVME_NS_PREFIX) != NULL) { + if (strstr(opt.dev, NVME_NS_PREFIX) != NULL) { ns_specified = true; - parse_ns_str(argv[optind], cname, &nsid); + parse_ns_str(opt.dev, cname, &nsid); open_dev(cname, &fd, 1, 1); } else { ns_specified = false; nsid = NVME_GLOBAL_NAMESPACE_TAG; - open_dev(argv[optind], &fd, 1, 1); + open_dev(opt.dev, &fd, 1, 1); } read_controller_data(fd, &cdata); @@ -424,9 +443,9 @@ * namespace basis. */ if (ns_specified) { - if (log_page != NVME_LOG_HEALTH_INFORMATION) + if (opt.page != NVME_LOG_HEALTH_INFORMATION) errx(1, "log page %d valid only at controller level", - log_page); + opt.page); if (ns_smart == 0) errx(1, "controller does not support per namespace " @@ -435,9 +454,9 @@ print_fn = print_log_hex; size = DEFAULT_SIZE; - if (binflag) + if (opt.binary) print_fn = print_bin; - if (!binflag && !hexflag) { + if (!opt.binary && !opt.hex) { /* * See if there is a pretty print function for the specified log * page. If one isn't found, we just revert to the default @@ -445,30 +464,28 @@ * the page is vendor specific, don't match the print function * unless the vendors match. */ - SLIST_FOREACH(f, &logpages, link) { - if (f->vendor != NULL && vendor != NULL && - strcmp(f->vendor, vendor) != 0) + SLIST_FOREACH(lpf, &logpages, link) { + if (lpf->vendor != NULL && vendor != NULL && + strcmp(lpf->vendor, vendor) != 0) continue; - if (log_page != f->log_page) + if (opt.page != lpf->log_page) continue; - print_fn = f->print_fn; - size = f->size; + print_fn = lpf->print_fn; + size = lpf->size; break; } } - if (log_page == NVME_LOG_ERROR) { + if (opt.page == NVME_LOG_ERROR) { size = sizeof(struct nvme_error_information_entry); size *= (cdata.elpe + 1); } /* Read the log page */ buf = get_log_buffer(size); - read_logpage(fd, log_page, nsid, buf, size); + read_logpage(fd, opt.page, nsid, buf, size); print_fn(&cdata, buf, size); close(fd); exit(0); } - -NVME_COMMAND(top, logpage, logpage, LOGPAGE_USAGE); Index: sbin/nvmecontrol/modules/wdc/wdc.c =================================================================== --- sbin/nvmecontrol/modules/wdc/wdc.c +++ sbin/nvmecontrol/modules/wdc/wdc.c @@ -41,21 +41,57 @@ #include "nvmecontrol.h" -#define WDC_USAGE \ - "wdc (cap-diag)\n" +/* Tables for command line parsing */ -NVME_CMD_DECLARE(wdc, struct nvme_function); +static cmd_fn_t wdc; +static cmd_fn_t wdc_cap_diag; -#define WDC_NVME_TOC_SIZE 8 +#define NONE 0xffffffffu +#define NONE64 0xffffffffffffffffull +#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } +#define OPT_END { NULL, 0, arg_none, NULL, NULL } -#define WDC_NVME_CAP_DIAG_OPCODE 0xe6 -#define WDC_NVME_CAP_DIAG_CMD 0x0000 +static struct cmd wdc_cmd = { + .name = "wdc", .fn = wdc, .descr = "wdc vendor specific commands", .ctx_size = 0, .opts = NULL, .args = NULL, +}; + +CMD_COMMAND(wdc_cmd); + +static struct options +{ + const char *template; + const char *dev; +} opt = { + .template = NULL, + .dev = NULL, +}; + +static const struct opts opts[] = { + OPT("template", 'o', arg_string, opt, template, + "Template for paths to use for different logs"), + OPT_END +}; + +static const struct args args[] = { + { arg_string, &opt.dev, "controller-id" }, + { arg_none, NULL, NULL }, +}; + +static struct cmd cap_diag_cmd = { + .name = "cap-diag", + .fn = wdc_cap_diag, + .descr = "Retrieve the cap-diag logs from the drive", + .ctx_size = sizeof(struct options), + .opts = opts, + .args = args, +}; -static void wdc_cap_diag(const struct nvme_function *nf, int argc, char *argv[]); +CMD_SUBCOMMAND(wdc_cmd, cap_diag_cmd); -#define WDC_CAP_DIAG_USAGE "wdc cap-diag [-o path-template]\n" +#define WDC_NVME_TOC_SIZE 8 -NVME_COMMAND(wdc, cap-diag, wdc_cap_diag, WDC_CAP_DIAG_USAGE); +#define WDC_NVME_CAP_DIAG_OPCODE 0xe6 +#define WDC_NVME_CAP_DIAG_CMD 0x0000 static void wdc_append_serial_name(int fd, char *buf, size_t len, const char *suffix) @@ -153,27 +189,20 @@ } static void -wdc_cap_diag(const struct nvme_function *nf, int argc, char *argv[]) +wdc_cap_diag(const struct cmd *f, int argc, char *argv[]) { - char path_tmpl[MAXPATHLEN]; - int ch, fd; - - path_tmpl[0] = '\0'; - while ((ch = getopt(argc, argv, "o:")) != -1) { - switch ((char)ch) { - case 'o': - strlcpy(path_tmpl, optarg, MAXPATHLEN); - break; - default: - usage(nf); - } - } - /* Check that a controller was specified. */ - if (optind >= argc) - usage(nf); - open_dev(argv[optind], &fd, 1, 1); + char tmpl[MAXPATHLEN]; + int fd; - wdc_do_dump(fd, path_tmpl, "cap_diag", WDC_NVME_CAP_DIAG_OPCODE, + if (arg_parse(argc, argv, f)) + return; + if (opt.template == NULL) { + fprintf(stderr, "Missing template arg.\n"); + arg_help(argc, argv, f); + } + strlcpy(tmpl, opt.template, sizeof(tmpl)); + open_dev(opt.dev, &fd, 1, 1); + wdc_do_dump(fd, tmpl, "cap_diag", WDC_NVME_CAP_DIAG_OPCODE, WDC_NVME_CAP_DIAG_CMD, 4); close(fd); @@ -182,10 +211,10 @@ } static void -wdc(const struct nvme_function *nf __unused, int argc, char *argv[]) +wdc(const struct cmd *nf __unused, int argc, char *argv[]) { - DISPATCH(argc, argv, wdc); + cmd_dispatch(argc, argv, &wdc_cmd); } /* @@ -593,4 +622,3 @@ NVME_LOGPAGE(wdc_info, HGST_INFO_LOG, "wdc", "Detailed Health/SMART", print_hgst_info_log, DEFAULT_SIZE); -NVME_COMMAND(top, wdc, wdc, WDC_USAGE); Index: sbin/nvmecontrol/ns.c =================================================================== --- sbin/nvmecontrol/ns.c +++ sbin/nvmecontrol/ns.c @@ -41,34 +41,193 @@ #include "nvmecontrol.h" -NVME_CMD_DECLARE(ns, struct nvme_function); +/* Tables for command line parsing */ -#define NS_USAGE \ - "ns (create|delete|attach|detach)\n" +static cmd_fn_t ns; +static cmd_fn_t nscreate; +static cmd_fn_t nsdelete; +static cmd_fn_t nsattach; +static cmd_fn_t nsdetach; -/* handles NVME_OPC_NAMESPACE_MANAGEMENT and ATTACHMENT admin cmds */ +#define NONE 0xffffffffu +#define NONE64 0xffffffffffffffffull +#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } +#define OPT_END { NULL, 0, arg_none, NULL, NULL } -#define NSCREATE_USAGE \ - "ns create -s size [-c cap] [-f fmt] [-m mset] [-n nmic] [-p pi] [-l pil] nvmeN\n" +static struct cmd ns_cmd = { + .name = "ns", .fn = ns, .descr = "Namespace commands", .ctx_size = 0, .opts = NULL, .args = NULL, +}; -#define NSDELETE_USAGE \ - "ns delete -n nsid nvmeN\n" +CMD_COMMAND(ns_cmd); + +static struct create_options { + uint64_t nsze; + uint64_t cap; + uint32_t lbaf; + uint32_t mset; + uint32_t nmic; + uint32_t pi; + uint32_t pil; + uint32_t flbas; + uint32_t dps; +// uint32_t block_size; + const char *dev; +} create_opt = { + .nsze = NONE64, + .cap = NONE64, + .lbaf = NONE, + .mset = NONE, + .nmic = NONE, + .pi = NONE, + .pil = NONE, + .flbas = NONE, + .dps = NONE, + .dev = NULL, +// .block_size = NONE, +}; -#define NSATTACH_USAGE \ - "ns attach -n nsid [-c ctrlrid] nvmeN \n" +static const struct opts create_opts[] = { + OPT("nsze", 's', arg_uint64, create_opt, nsze, + "The namespace size"), + OPT("ncap", 'c', arg_uint64, create_opt, cap, + "The capacity of the namespace (<= ns size)"), + OPT("lbaf", 'f', arg_uint32, create_opt, lbaf, + "The FMT field of the FLBAS"), + OPT("mset", 'm', arg_uint32, create_opt, mset, + "The MSET field of the FLBAS"), + OPT("nmic", 'n', arg_uint32, create_opt, nmic, + "Namespace multipath and sharing capabilities"), + OPT("pi", 'p', arg_uint32, create_opt, pi, + "PI field of FLBAS"), + OPT("pil", 'l', arg_uint32, create_opt, pil, + "PIL field of FLBAS"), + OPT("flbas", 'l', arg_uint32, create_opt, flbas, + "Namespace formatted logical block size setting"), + OPT("dps", 'd', arg_uint32, create_opt, dps, + "Data protection settings"), +// OPT("block-size", 'b', arg_uint32, create_opt, block_size, +// "Blocksize of the namespace"), + OPT_END +}; -#define NSDETACH_USAGE \ - "ns detach -n nsid [-c ctrlrid] nvmeN\n" +static const struct args create_args[] = { + { arg_string, &create_opt.dev, "controller-id" }, + { arg_none, NULL, NULL }, +}; -static void nscreate(const struct nvme_function *nf, int argc, char *argv[]); -static void nsdelete(const struct nvme_function *nf, int argc, char *argv[]); -static void nsattach(const struct nvme_function *nf, int argc, char *argv[]); -static void nsdetach(const struct nvme_function *nf, int argc, char *argv[]); +static struct cmd create_cmd = { + .name = "create", + .fn = nscreate, + .descr = "Create a new namespace", + .ctx_size = sizeof(create_opt), + .opts = create_opts, + .args = create_args, +}; -NVME_COMMAND(ns, create, nscreate, NSCREATE_USAGE); -NVME_COMMAND(ns, delete, nsdelete, NSDELETE_USAGE); -NVME_COMMAND(ns, attach, nsattach, NSATTACH_USAGE); -NVME_COMMAND(ns, detach, nsdetach, NSDETACH_USAGE); +CMD_SUBCOMMAND(ns_cmd, create_cmd); + +static struct delete_options { + uint32_t nsid; + const char *dev; +} delete_opt = { + .nsid = NONE, + .dev = NULL, +}; + +static const struct opts delete_opts[] = { + OPT("namespace-id", 'n', arg_uint32, delete_opt, nsid, + "The namespace ID to delete"), + OPT_END +}; + +static const struct args delete_args[] = { + { arg_string, &delete_opt.dev, "controller-id" }, + { arg_none, NULL, NULL }, +}; + +static struct cmd delete_cmd = { + .name = "delete", + .fn = nsdelete, + .descr = "Delete a new namespace", + .ctx_size = sizeof(delete_opt), + .opts = delete_opts, + .args = delete_args, +}; + +CMD_SUBCOMMAND(ns_cmd, delete_cmd); + +static struct attach_options { + uint32_t nsid; + uint32_t ctrlrid; + const char *dev; +} attach_opt = { + .nsid = NONE, + .ctrlrid = NONE - 1, + .dev = NULL, +}; + +static const struct opts attach_opts[] = { + OPT("namespace-id", 'n', arg_uint32, attach_opt, nsid, + "The namespace ID to attach"), + OPT("controller", 'c', arg_uint32, attach_opt, nsid, + "The controller ID to attach"), + OPT_END +}; + +static const struct args attach_args[] = { + { arg_string, &attach_opt.dev, "controller-id" }, + { arg_none, NULL, NULL }, +}; + +static struct cmd attach_cmd = { + .name = "attach", + .fn = nsattach, + .descr = "Attach a new namespace", + .ctx_size = sizeof(attach_opt), + .opts = attach_opts, + .args = attach_args, +}; + +CMD_SUBCOMMAND(ns_cmd, attach_cmd); + +static struct detach_options { + uint32_t nsid; + uint32_t ctrlrid; + const char *dev; +} detach_opt = { + .nsid = NONE, + .ctrlrid = NONE - 1, + .dev = NULL, +}; + +static const struct opts detach_opts[] = { + OPT("namespace-id", 'n', arg_uint32, detach_opt, nsid, + "The namespace ID to detach"), + OPT("controller", 'c', arg_uint32, detach_opt, nsid, + "The controller ID to detach"), + OPT_END +}; + +static const struct args detach_args[] = { + { arg_string, &detach_opt.dev, "controller-id" }, + { arg_none, NULL, NULL }, +}; + +static struct cmd detach_cmd = { + .name = "detach", + .fn = nsdetach, + .descr = "Detach a new namespace", + .ctx_size = sizeof(detach_opt), + .opts = detach_opts, + .args = detach_args, +}; + +CMD_SUBCOMMAND(ns_cmd, detach_cmd); + +#define NS_USAGE \ + "ns (create|delete|attach|detach)\n" + +/* handles NVME_OPC_NAMESPACE_MANAGEMENT and ATTACHMENT admin cmds */ struct ns_result_str { uint16_t res; @@ -110,54 +269,25 @@ * 0xb = Thin Provisioning Not supported */ static void -nscreate(const struct nvme_function *nf, int argc, char *argv[]) +nscreate(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; struct nvme_namespace_data nsdata; - int64_t nsze = -1, cap = -1; - int ch, fd, result, lbaf = 0, mset = 0, nmic = -1, pi = 0, pil = 0; - - if (optind >= argc) - usage(nf); - - while ((ch = getopt(argc, argv, "s:c:f:m:n:p:l:")) != -1) { - switch (ch) { - case 's': - nsze = strtol(optarg, (char **)NULL, 0); - break; - case 'c': - cap = strtol(optarg, (char **)NULL, 0); - break; - case 'f': - lbaf = strtol(optarg, (char **)NULL, 0); - break; - case 'm': - mset = strtol(optarg, NULL, 0); - break; - case 'n': - nmic = strtol(optarg, NULL, 0); - break; - case 'p': - pi = strtol(optarg, NULL, 0); - break; - case 'l': - pil = strtol(optarg, NULL, 0); - break; - default: - usage(nf); - } - } + int fd, result; - if (optind >= argc) - usage(nf); + if (arg_parse(argc, argv, f)) + return; - if (cap == -1) - cap = nsze; - if (nsze == -1 || cap == -1) - usage(nf); + if (create_opt.cap == NONE64) + create_opt.cap = create_opt.nsze; + if (create_opt.nsze == NONE64) { + fprintf(stderr, + "Size not specified\n"); + arg_help(argc, argv, f); + } - open_dev(argv[optind], &fd, 1, 1); + open_dev(create_opt.dev, &fd, 1, 1); read_controller_data(fd, &cd); /* Check that controller can execute this command. */ @@ -166,23 +296,29 @@ errx(1, "controller does not support namespace management"); /* Allow namespaces sharing if Multi-Path I/O is supported. */ - if (nmic == -1) { - nmic = cd.mic ? (NVME_NS_DATA_NMIC_MAY_BE_SHARED_MASK << + if (create_opt.nmic == NONE) { + create_opt.nmic = cd.mic ? (NVME_NS_DATA_NMIC_MAY_BE_SHARED_MASK << NVME_NS_DATA_NMIC_MAY_BE_SHARED_SHIFT) : 0; } memset(&nsdata, 0, sizeof(nsdata)); - nsdata.nsze = (uint64_t)nsze; - nsdata.ncap = (uint64_t)cap; - nsdata.flbas = ((lbaf & NVME_NS_DATA_FLBAS_FORMAT_MASK) - << NVME_NS_DATA_FLBAS_FORMAT_SHIFT) | - ((mset & NVME_NS_DATA_FLBAS_EXTENDED_MASK) - << NVME_NS_DATA_FLBAS_EXTENDED_SHIFT); - nsdata.dps = ((pi & NVME_NS_DATA_DPS_MD_START_MASK) - << NVME_NS_DATA_DPS_MD_START_SHIFT) | - ((pil & NVME_NS_DATA_DPS_PIT_MASK) - << NVME_NS_DATA_DPS_PIT_SHIFT); - nsdata.nmic = nmic; + nsdata.nsze = create_opt.nsze; + nsdata.ncap = create_opt.cap; + if (create_opt.flbas == NONE) + nsdata.flbas = ((create_opt.lbaf & NVME_NS_DATA_FLBAS_FORMAT_MASK) + << NVME_NS_DATA_FLBAS_FORMAT_SHIFT) | + ((create_opt.mset & NVME_NS_DATA_FLBAS_EXTENDED_MASK) + << NVME_NS_DATA_FLBAS_EXTENDED_SHIFT); + else + nsdata.flbas = create_opt.flbas; + if (create_opt.dps == NONE) + nsdata.dps = ((create_opt.pi & NVME_NS_DATA_DPS_MD_START_MASK) + << NVME_NS_DATA_DPS_MD_START_SHIFT) | + ((create_opt.pil & NVME_NS_DATA_DPS_PIT_MASK) + << NVME_NS_DATA_DPS_PIT_SHIFT); + else + nsdata.dps = create_opt.dps; + nsdata.nmic = create_opt.nmic; nvme_namespace_data_swapbytes(&nsdata); memset(&pt, 0, sizeof(pt)); @@ -205,30 +341,22 @@ } static void -nsdelete(const struct nvme_function *nf, int argc, char *argv[]) +nsdelete(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; - int ch, fd, result, nsid = -2; + int fd, result; char buf[2]; - if (optind >= argc) - usage(nf); - - while ((ch = getopt(argc, argv, "n:")) != -1) { - switch ((char)ch) { - case 'n': - nsid = strtol(optarg, (char **)NULL, 0); - break; - default: - usage(nf); - } + if (arg_parse(argc, argv, f)) + return; + if (delete_opt.nsid == NONE) { + fprintf(stderr, + "No NSID specified"); + arg_help(argc, argv, f); } - if (optind >= argc || nsid == -2) - usage(nf); - - open_dev(argv[optind], &fd, 1, 1); + open_dev(delete_opt.dev, &fd, 1, 1); read_controller_data(fd, &cd); /* Check that controller can execute this command. */ @@ -242,17 +370,17 @@ pt.buf = buf; pt.len = sizeof(buf); pt.is_read = 1; - pt.cmd.nsid = (uint32_t)nsid; + pt.cmd.nsid = delete_opt.nsid; if ((result = ioctl(fd, NVME_PASSTHROUGH_CMD, &pt)) < 0) - errx(1, "ioctl request to %s failed: %d", argv[optind], result); + errx(1, "ioctl request to %s failed: %d", delete_opt.dev, result); if (nvme_completion_is_error(&pt.cpl)) { errx(1, "namespace deletion failed: %s", get_res_str((pt.cpl.status >> NVME_STATUS_SC_SHIFT) & NVME_STATUS_SC_MASK)); } - printf("namespace %d deleted\n", nsid); + printf("namespace %d deleted\n", delete_opt.nsid); exit(0); } @@ -272,37 +400,20 @@ * 0x2 Invalid Field can occur if ctrlrid d.n.e in system. */ static void -nsattach(const struct nvme_function *nf, int argc, char *argv[]) +nsattach(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; - int ctrlrid = -2; - int fd, ch, result, nsid = -1; + int fd, result; uint16_t clist[2048]; - if (optind >= argc) - usage(nf); - - while ((ch = getopt(argc, argv, "n:c:")) != -1) { - switch (ch) { - case 'n': - nsid = strtol(optarg, (char **)NULL, 0); - break; - case 'c': - ctrlrid = strtol(optarg, (char **)NULL, 0); - break; - default: - usage(nf); - } + if (arg_parse(argc, argv, f)) + return; + if (attach_opt.nsid == NONE) { + fprintf(stderr, "No valid NSID specified\n"); + arg_help(argc, argv, f); } - - if (optind >= argc) - usage(nf); - - if (nsid == -1 ) - usage(nf); - - open_dev(argv[optind], &fd, 1, 1); + open_dev(attach_opt.dev, &fd, 1, 1); read_controller_data(fd, &cd); /* Check that controller can execute this command. */ @@ -310,7 +421,7 @@ NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0) errx(1, "controller does not support namespace management"); - if (ctrlrid == -1) { + if (attach_opt.ctrlrid == NONE) { /* Get full list of controllers to attach to. */ memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_IDENTIFY; @@ -324,64 +435,47 @@ errx(1, "identify request returned error"); } else { /* By default attach to this controller. */ - if (ctrlrid == -2) - ctrlrid = cd.ctrlr_id; + if (attach_opt.ctrlrid == NONE - 1) + attach_opt.ctrlrid = cd.ctrlr_id; memset(&clist, 0, sizeof(clist)); clist[0] = htole16(1); - clist[1] = htole16(ctrlrid); + clist[1] = htole16(attach_opt.ctrlrid); } memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_NAMESPACE_ATTACHMENT; pt.cmd.cdw10 = 0; /* attach */ - pt.cmd.nsid = (uint32_t)nsid; + pt.cmd.nsid = attach_opt.nsid; pt.buf = &clist; pt.len = sizeof(clist); if ((result = ioctl(fd, NVME_PASSTHROUGH_CMD, &pt)) < 0) - errx(1, "ioctl request to %s failed: %d", argv[optind], result); + errx(1, "ioctl request to %s failed: %d", attach_opt.dev, result); if (nvme_completion_is_error(&pt.cpl)) { errx(1, "namespace attach failed: %s", get_res_str((pt.cpl.status >> NVME_STATUS_SC_SHIFT) & NVME_STATUS_SC_MASK)); } - printf("namespace %d attached\n", nsid); + printf("namespace %d attached\n", attach_opt.nsid); exit(0); } static void -nsdetach(const struct nvme_function *nf, int argc, char *argv[]) +nsdetach(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; - int ctrlrid = -2; - int fd, ch, result, nsid = -1; + int fd, result; uint16_t clist[2048]; - if (optind >= argc) - usage(nf); - - while ((ch = getopt(argc, argv, "n:c:")) != -1) { - switch (ch) { - case 'n': - nsid = strtol(optarg, (char **)NULL, 0); - break; - case 'c': - ctrlrid = strtol(optarg, (char **)NULL, 0); - break; - default: - usage(nf); - } + if (arg_parse(argc, argv, f)) + return; + if (attach_opt.nsid == NONE) { + fprintf(stderr, "No valid NSID specified\n"); + arg_help(argc, argv, f); } - - if (optind >= argc) - usage(nf); - - if (nsid == -1) - usage(nf); - - open_dev(argv[optind], &fd, 1, 1); + open_dev(attach_opt.dev, &fd, 1, 1); read_controller_data(fd, &cd); /* Check that controller can execute this command. */ @@ -389,11 +483,11 @@ NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0) errx(1, "controller does not support namespace management"); - if (ctrlrid == -1) { + if (detach_opt.ctrlrid == NONE) { /* Get list of controllers this namespace attached to. */ memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_IDENTIFY; - pt.cmd.nsid = htole32(nsid); + pt.cmd.nsid = htole32(detach_opt.nsid); pt.cmd.cdw10 = htole32(0x12); pt.buf = clist; pt.len = sizeof(clist); @@ -403,24 +497,24 @@ if (nvme_completion_is_error(&pt.cpl)) errx(1, "identify request returned error"); if (clist[0] == 0) { - ctrlrid = cd.ctrlr_id; + detach_opt.ctrlrid = cd.ctrlr_id; memset(&clist, 0, sizeof(clist)); clist[0] = htole16(1); - clist[1] = htole16(ctrlrid); + clist[1] = htole16(detach_opt.ctrlrid); } } else { /* By default detach from this controller. */ - if (ctrlrid == -2) - ctrlrid = cd.ctrlr_id; + if (detach_opt.ctrlrid == NONE - 1) + detach_opt.ctrlrid = cd.ctrlr_id; memset(&clist, 0, sizeof(clist)); clist[0] = htole16(1); - clist[1] = htole16(ctrlrid); + clist[1] = htole16(detach_opt.ctrlrid); } memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_NAMESPACE_ATTACHMENT; pt.cmd.cdw10 = 1; /* detach */ - pt.cmd.nsid = (uint32_t)nsid; + pt.cmd.nsid = detach_opt.nsid; pt.buf = &clist; pt.len = sizeof(clist); @@ -432,15 +526,13 @@ get_res_str((pt.cpl.status >> NVME_STATUS_SC_SHIFT) & NVME_STATUS_SC_MASK)); } - printf("namespace %d detached\n", nsid); + printf("namespace %d detached\n", detach_opt.nsid); exit(0); } static void -ns(const struct nvme_function *nf __unused, int argc, char *argv[]) +ns(const struct cmd *nf __unused, int argc, char *argv[]) { - DISPATCH(argc, argv, ns); + cmd_dispatch(argc, argv, &ns_cmd); } - -NVME_COMMAND(top, ns, ns, NS_USAGE); Index: sbin/nvmecontrol/nvmecontrol.h =================================================================== --- sbin/nvmecontrol/nvmecontrol.h +++ sbin/nvmecontrol/nvmecontrol.h @@ -31,28 +31,8 @@ #ifndef __NVMECONTROL_H__ #define __NVMECONTROL_H__ -#include -#include #include - -struct nvme_function; -typedef void (*nvme_fn_t)(const struct nvme_function *nf, int argc, char *argv[]); - -struct nvme_function { - const char *name; - nvme_fn_t fn; - const char *usage; -}; - -#define NVME_SETNAME(set) set -#define NVME_CMDSET(set, sym) DATA_SET(NVME_SETNAME(set), sym) -#define NVME_COMMAND(set, nam, function, usage_str) \ - static struct nvme_function function ## _nvme_cmd = \ - { .name = #nam, .fn = function, .usage = usage_str }; \ - NVME_CMDSET(set, function ## _nvme_cmd) -#define NVME_CMD_BEGIN(set) SET_BEGIN(NVME_SETNAME(set)) -#define NVME_CMD_LIMIT(set) SET_LIMIT(NVME_SETNAME(set)) -#define NVME_CMD_DECLARE(set, t) SET_DECLARE(NVME_SETNAME(set), t) +#include "comnd.h" typedef void (*print_fn_t)(const struct nvme_controller_data *cdata, void *buf, uint32_t size); @@ -65,7 +45,6 @@ size_t size; }; - #define NVME_LOGPAGE(unique, lp, vend, nam, fn, sz) \ static struct logpage_function unique ## _lpf = { \ .log_page = lp, \ @@ -85,26 +64,7 @@ const char *kv_lookup(const struct kv_name *kv, size_t kv_count, uint32_t key); -NVME_CMD_DECLARE(top, struct nvme_function); void logpage_register(struct logpage_function *p); - -struct set_concat { - void **begin; - void **limit; -}; -void set_concat_add(struct set_concat *m, void *begin, void *end); -#define SET_CONCAT_DEF(set, t) \ -static struct set_concat set ## _concat; \ -static inline const t * const *set ## _begin(void) { return ((const t * const *)set ## _concat.begin); } \ -static inline const t * const *set ## _limit(void) { return ((const t * const *)set ## _concat.limit); } \ -void add_to_ ## set(t **b, t **e) \ -{ \ - set_concat_add(&set ## _concat, b, e); \ -} -#define SET_CONCAT_DECL(set, t) \ - void add_to_ ## set(t **b, t **e) -SET_CONCAT_DECL(top, struct nvme_function); - #define NVME_CTRLR_PREFIX "nvme" #define NVME_NS_PREFIX "ns" @@ -118,15 +78,6 @@ void print_temp(uint16_t t); void print_intel_add_smart(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused); -void usage(const struct nvme_function *f); -void dispatch_set(int argc, char *argv[], const struct nvme_function * const *tbl, - const struct nvme_function * const *tbl_limit); - -#define DISPATCH(argc, argv, set) \ - dispatch_set(argc, argv, \ - (const struct nvme_function * const *)NVME_CMD_BEGIN(set), \ - (const struct nvme_function * const *)NVME_CMD_LIMIT(set)) \ - /* Utility Routines */ /* * 128-bit integer augments to standard values. On i386 this @@ -149,5 +100,4 @@ uint64_t le48dec(const void *pp); char * uint128_to_str(uint128_t u, char *buf, size_t buflen); - #endif Index: sbin/nvmecontrol/nvmecontrol.c =================================================================== --- sbin/nvmecontrol/nvmecontrol.c +++ sbin/nvmecontrol/nvmecontrol.c @@ -35,7 +35,6 @@ #include #include -#include #include #include #include @@ -49,101 +48,6 @@ #include "nvmecontrol.h" -SET_CONCAT_DEF(top, struct nvme_function); - -static void -print_usage(const struct nvme_function *f) -{ - const char *cp; - char ch; - bool need_prefix = true; - - cp = f->usage; - while (*cp) { - ch = *cp++; - if (need_prefix) { - if (ch != ' ') - fputs(" nvmecontrol ", stderr); - else - fputs(" ", stderr); - } - fputc(ch, stderr); - need_prefix = (ch == '\n'); - } - if (!need_prefix) - fputc('\n', stderr); -} - -static void -gen_usage_set(const struct nvme_function * const *f, const struct nvme_function * const *flimit) -{ - - fprintf(stderr, "usage:\n"); - while (f < flimit) { - print_usage(*f); - f++; - } - exit(1); -} - -void -usage(const struct nvme_function *f) -{ - - fprintf(stderr, "usage:\n"); - print_usage(f); - exit(1); -} - -void -dispatch_set(int argc, char *argv[], const struct nvme_function * const *tbl, - const struct nvme_function * const *tbl_limit) -{ - const struct nvme_function * const *f = tbl; - - if (argv[1] == NULL) { - gen_usage_set(tbl, tbl_limit); - return; - } - - while (f < tbl_limit) { - if (strcmp(argv[1], (*f)->name) == 0) { - (*f)->fn(*f, argc-1, &argv[1]); - return; - } - f++; - } - - fprintf(stderr, "Unknown command: %s\n", argv[1]); - gen_usage_set(tbl, tbl_limit); -} - -void -set_concat_add(struct set_concat *m, void *b, void *e) -{ - void **bp, **ep; - int add_n, cur_n; - - if (b == NULL) - return; - /* - * Args are really pointers to arrays of pointers, but C's - * casting rules kinda suck since you can't directly cast - * struct foo ** to a void **. - */ - bp = (void **)b; - ep = (void **)e; - add_n = ep - bp; - cur_n = 0; - if (m->begin != NULL) - cur_n = m->limit - m->begin; - m->begin = reallocarray(m->begin, cur_n + add_n, sizeof(void *)); - if (m->begin == NULL) - err(1, "expanding concat set"); - memcpy(m->begin + cur_n, bp, add_n * sizeof(void *)); - m->limit = m->begin + cur_n + add_n; -} - static void print_bytes(void *data, uint32_t length) { @@ -288,61 +192,16 @@ snprintf(ctrlr_str, nsloc - ns_str + 1, "%s", ns_str); } -/* - * Loads all the .so's from the specified directory. - */ -static void -load_dir(const char *dir) -{ - DIR *d; - struct dirent *dent; - char *path = NULL; - void *h; - - d = opendir(dir); - if (d == NULL) - return; - for (dent = readdir(d); dent != NULL; dent = readdir(d)) { - if (strcmp(".so", dent->d_name + dent->d_namlen - 3) != 0) - continue; - asprintf(&path, "%s/%s", dir, dent->d_name); - if (path == NULL) - err(1, "Can't malloc for path, giving up."); - if ((h = dlopen(path, RTLD_NOW | RTLD_GLOBAL)) == NULL) - warnx("Can't load %s: %s", path, dlerror()); - else { - /* - * Add in the top (for cli commands)linker sets. We have - * to do this by hand because linker sets aren't - * automatically merged. - */ - void *begin, *limit; - - begin = dlsym(h, "__start_set_top"); - limit = dlsym(h, "__stop_set_top"); - if (begin) - add_to_top(begin, limit); - /* log pages use constructors so are done on load */ - } - free(path); - path = NULL; - } - closedir(d); -} - int main(int argc, char *argv[]) { - add_to_top(NVME_CMD_BEGIN(top), NVME_CMD_LIMIT(top)); - - load_dir("/lib/nvmecontrol"); - load_dir("/usr/local/lib/nvmecontrol"); + cmd_init(); - if (argc < 2) - gen_usage_set(top_begin(), top_limit()); + cmd_load_dir("/lib/nvmecontrol", NULL, NULL); + cmd_load_dir("/usr/local/lib/nvmecontrol", NULL, NULL); - dispatch_set(argc, argv, top_begin(), top_limit()); + cmd_dispatch(argc, argv, NULL); return (0); } Index: sbin/nvmecontrol/passthru.c =================================================================== --- /dev/null +++ sbin/nvmecontrol/passthru.c @@ -0,0 +1,273 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (C) 2012-2013 Intel Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nvmecontrol.h" +#include "comnd.h" + +static struct options { + uint8_t opcode; + uint8_t flags; + uint16_t rsvd; + uint32_t nsid; + uint32_t data_len; + uint32_t metadata_len; + uint32_t timeout; + uint32_t cdw2; + uint32_t cdw3; + uint32_t cdw10; + uint32_t cdw11; + uint32_t cdw12; + uint32_t cdw13; + uint32_t cdw14; + uint32_t cdw15; + const char *ifn; + bool binary; + bool show_command; + bool dry_run; + bool read; + bool write; + uint8_t prefill; + const char *dev; +} opt = { + .binary = false, + .cdw10 = 0, + .cdw11 = 0, + .cdw12 = 0, + .cdw13 = 0, + .cdw14 = 0, + .cdw15 = 0, + .cdw2 = 0, + .cdw3 = 0, + .data_len = 0, + .dry_run = false, + .flags = 0, + .ifn = "", + .metadata_len = 0, + .nsid = 0, + .opcode = 0, + .prefill = 0, + .read = false, + .rsvd = 0, + .show_command = false, + .timeout = 0, + .write = false, + .dev = NULL, +}; + +/* + * Argument names and short names selected to match the nvme-cli program + * so vendor-siupplied formulas work out of the box on FreeBSD with a simple + * s/nvme/nvmecontrol/. + */ +#define ARG(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } + +static const struct opts opts[] = { + ARG("opcode", 'o', arg_uint8, opt, opcode, + "NVMe command opcode (required)"), + ARG("cdw2", '2', arg_uint32, opt, cdw2, + "Command dword 2 value"), + ARG("cdw3", '3', arg_uint32, opt, cdw3, + "Command dword 3 value"), + ARG("cdw10", '4', arg_uint32, opt, cdw10, + "Command dword 10 value"), + ARG("cdw11", '5', arg_uint32, opt, cdw11, + "Command dword 11 value"), + ARG("cdw12", '6', arg_uint32, opt, cdw12, + "Command dword 12 value"), + ARG("cdw13", '7', arg_uint32, opt, cdw13, + "Command dword 13 value"), + ARG("cdw14", '8', arg_uint32, opt, cdw14, + "Command dword 14 value"), + ARG("cdw15", '9', arg_uint32, opt, cdw15, + "Command dword 15 value"), + ARG("data-len", 'l', arg_uint32, opt, data_len, + "Length of data for I/O (bytes)"), + ARG("metadata-len", 'm', arg_uint32, opt, metadata_len, + "Length of metadata segment (bytes) (igored)"), + ARG("flags", 'f', arg_uint8, opt, flags, + "NVMe command flags"), + ARG("input-file", 'i', arg_path, opt, ifn, + "Input file to send (default stdin)"), + ARG("namespace-id", 'n', arg_uint32, opt, nsid, + "Namespace id (ignored on FreeBSD)"), + ARG("prefill", 'p', arg_uint8, opt, prefill, + "Value to prefill payload with"), + ARG("rsvd", 'R', arg_uint16, opt, rsvd, + "Reserved field value"), + ARG("timeout", 't', arg_uint32, opt, timeout, + "Command timeout (ms)"), + ARG("raw-binary", 'b', arg_none, opt, binary, + "Output in binary format"), + ARG("dry-run", 'd', arg_none, opt, dry_run, + "Don't actually execute the command"), + ARG("read", 'r', arg_none, opt, read, + "Command reads data from device"), + ARG("show-command", 's', arg_none, opt, show_command, + "Show all the command values on stdout"), + ARG("write", 'w', arg_none, opt, write, + "Command writes data to device"), + { NULL, 0, arg_none, NULL, NULL } +}; + +static const struct args args[] = { + { arg_string, &opt.dev, "controller-id|namespace-id" }, + { arg_none, NULL, NULL }, +}; + +static void +passthru(const struct cmd *f, int argc, char *argv[]) +{ + int fd = -1, ifd = -1; + void *data = NULL, *metadata = NULL; + struct nvme_pt_command pt; + + arg_parse(argc, argv, f); + open_dev(argv[optind], &fd, 1, 1); + + if (opt.read && opt.write) + errx(1, "need exactly one of --read or --write"); + if (opt.data_len != 0 && !opt.read && !opt.write) + errx(1, "need exactly one of --read or --write"); + if (*opt.ifn && (ifd = open(opt.ifn, O_RDONLY)) == -1) { + warn("open %s", opt.ifn); + goto cleanup; + } +#if notyet /* No support in kernel for this */ + if (opt.metadata_len != 0) { + if (posix_memalign(&metadata, getpagesize(), opt.metadata_len)) { + warn("can't allocate %d bytes for metadata", metadata_len); + goto cleanup; + } + } +#else + if (opt.metadata_len != 0) + errx(1, "metadata not supported on FreeBSD"); +#endif + if (opt.data_len) { + if (posix_memalign(&data, getpagesize(), opt.data_len)) { + warn("can't allocate %d bytes for data", opt.data_len); + goto cleanup; + } + memset(data, opt.prefill, opt.data_len); + if (opt.write && read(ifd, data, opt.data_len) < 0) { + warn("read %s", *opt.ifn ? opt.ifn : "stdin"); + goto cleanup; + } + } + if (opt.show_command) { + fprintf(stderr, "opcode : %#02x\n", opt.opcode); + fprintf(stderr, "flags : %#02x\n", opt.flags); + fprintf(stderr, "rsvd1 : %#04x\n", opt.rsvd); + fprintf(stderr, "nsid : %#04x\n", opt.nsid); + fprintf(stderr, "cdw2 : %#08x\n", opt.cdw2); + fprintf(stderr, "cdw3 : %#08x\n", opt.cdw3); + fprintf(stderr, "data_len : %#08x\n", opt.data_len); + fprintf(stderr, "metadata_len : %#08x\n", opt.metadata_len); + fprintf(stderr, "data : %p\n", data); + fprintf(stderr, "metadata : %p\n", metadata); + fprintf(stderr, "cdw10 : %#08x\n", opt.cdw10); + fprintf(stderr, "cdw11 : %#08x\n", opt.cdw11); + fprintf(stderr, "cdw12 : %#08x\n", opt.cdw12); + fprintf(stderr, "cdw13 : %#08x\n", opt.cdw13); + fprintf(stderr, "cdw14 : %#08x\n", opt.cdw14); + fprintf(stderr, "cdw15 : %#08x\n", opt.cdw15); + fprintf(stderr, "timeout_ms : %d\n", opt.timeout); + } + if (opt.dry_run) { + errno = 0; + warn("Doing a dry-run, no actual I/O"); + goto cleanup; + } + + memset(&pt, 0, sizeof(pt)); + pt.cmd.opc = opt.opcode; + pt.cmd.fuse = opt.flags; + pt.cmd.cid = htole16(opt.rsvd); + pt.cmd.nsid = opt.nsid; /* XXX note: kernel overrides this */ + pt.cmd.rsvd2 = htole32(opt.cdw2); + pt.cmd.rsvd3 = htole32(opt.cdw3); + pt.cmd.cdw10 = htole32(opt.cdw10); + pt.cmd.cdw11 = htole32(opt.cdw11); + pt.cmd.cdw12 = htole32(opt.cdw12); + pt.cmd.cdw13 = htole32(opt.cdw13); + pt.cmd.cdw14 = htole32(opt.cdw14); + pt.cmd.cdw15 = htole32(opt.cdw15); + pt.buf = data; + pt.len = opt.data_len; + pt.is_read = opt.read; + + errno = 0; + if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) + err(1, "passthrough request failed"); + /* XXX report status */ + if (opt.read) { + if (opt.binary) + write(STDOUT_FILENO, data, opt.data_len); + else { + /* print status here */ + print_hex(data, opt.data_len); + } + } +cleanup: + if (errno) + exit(1); +} + +static void +admin_passthru(const struct cmd *nf, int argc, char *argv[]) +{ + + passthru(nf, argc, argv); +} + +static void +io_passthru(const struct cmd *nf, int argc, char *argv[]) +{ + + passthru(nf, argc, argv); +} + +CMD_COMMAND(top, admin-passthru, admin_passthru, sizeof(struct options), opts, args, + "Send a pass through Admin command to the specified device"); +CMD_COMMAND(top, io-passthru, io_passthru, sizeof(struct options), opts, args, + "Send a pass through I/O command to the specified device"); Index: sbin/nvmecontrol/perftest.c =================================================================== --- sbin/nvmecontrol/perftest.c +++ sbin/nvmecontrol/perftest.c @@ -45,11 +45,69 @@ #include "nvmecontrol.h" -#define PERFTEST_USAGE \ - "perftest <-n num_threads> <-o read|write>\n" \ - " <-s size_in_bytes> <-t time_in_seconds>\n" \ - " <-i intr|wait> [-f refthread] [-p]\n" \ - " \n" +/* Tables for command line parsing */ + +static cmd_fn_t perftest; + +#define NONE 0xffffffffu +static struct options { + bool perthread; + uint32_t threads; + uint32_t size; + uint32_t time; + const char *op; + const char *intr; + const char *flags; + const char *dev; +} opt = { + .perthread = false, + .threads = 0, + .size = 0, + .time = 0, + .op = NULL, + .intr = NULL, + .flags = NULL, + .dev = NULL, +}; + + +static const struct opts perftest_opts[] = { +#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } + OPT("perthread", 'p', arg_none, opt, perthread, + "Report per-thread results"), + OPT("threads", 'n', arg_uint32, opt, threads, + "Number of threads to run"), + OPT("size", 's', arg_uint32, opt, size, + "Size of the test"), + OPT("time", 't', arg_uint32, opt, time, + "How long to run the test in seconds"), + OPT("operation", 'o', arg_string, opt, op, + "Operation type: 'read' or 'write'"), + OPT("interrupt", 'i', arg_string, opt, intr, + "Interrupt mode: 'intr' or 'wait'"), + OPT("flags", 'f', arg_string, opt, flags, + "Turn on testing flags: refthread"), + { NULL, 0, arg_none, NULL, NULL } +}; +#undef OPT + +static const struct args perftest_args[] = { + { arg_string, &opt.dev, "namespace-id" }, + { arg_none, NULL, NULL }, +}; + +static struct cmd perftest_cmd = { + .name = "perftest", + .fn = perftest, + .descr = "Perform low-level driver performance testing.", + .ctx_size = sizeof(opt), + .opts = perftest_opts, + .args = perftest_args, +}; + +CMD_COMMAND(perftest_cmd); + +/* End of tables for command line parsing */ static void print_perftest(struct nvme_io_test *io_test, bool perthread) @@ -75,105 +133,54 @@ } static void -perftest(const struct nvme_function *nf, int argc, char *argv[]) +perftest(const struct cmd *f, int argc, char *argv[]) { struct nvme_io_test io_test; int fd; - int opt; - char *p; u_long ioctl_cmd = NVME_IO_TEST; - bool nflag, oflag, sflag, tflag; - int perthread = 0; - - nflag = oflag = sflag = tflag = false; memset(&io_test, 0, sizeof(io_test)); - - while ((opt = getopt(argc, argv, "f:i:n:o:ps:t:")) != -1) { - switch (opt) { - case 'f': - if (!strcmp(optarg, "refthread")) - io_test.flags |= NVME_TEST_FLAG_REFTHREAD; - break; - case 'i': - if (!strcmp(optarg, "bio") || - !strcmp(optarg, "wait")) - ioctl_cmd = NVME_BIO_TEST; - else if (!strcmp(optarg, "io") || - !strcmp(optarg, "intr")) - ioctl_cmd = NVME_IO_TEST; - break; - case 'n': - nflag = true; - io_test.num_threads = strtoul(optarg, &p, 0); - if (p != NULL && *p != '\0') { - fprintf(stderr, - "\"%s\" not valid number of threads.\n", - optarg); - usage(nf); - } else if (io_test.num_threads == 0 || - io_test.num_threads > 128) { - fprintf(stderr, - "\"%s\" not valid number of threads.\n", - optarg); - usage(nf); - } - break; - case 'o': - oflag = true; - if (!strcmp(optarg, "read") || !strcmp(optarg, "READ")) - io_test.opc = NVME_OPC_READ; - else if (!strcmp(optarg, "write") || - !strcmp(optarg, "WRITE")) - io_test.opc = NVME_OPC_WRITE; - else { - fprintf(stderr, "\"%s\" not valid opcode.\n", - optarg); - usage(nf); - } - break; - case 'p': - perthread = 1; - break; - case 's': - sflag = true; - io_test.size = strtoul(optarg, &p, 0); - if (p == NULL || *p == '\0' || toupper(*p) == 'B') { - // do nothing - } else if (toupper(*p) == 'K') { - io_test.size *= 1024; - } else if (toupper(*p) == 'M') { - io_test.size *= 1024 * 1024; - } else { - fprintf(stderr, "\"%s\" not valid size.\n", - optarg); - usage(nf); - } - break; - case 't': - tflag = true; - io_test.time = strtoul(optarg, &p, 0); - if (p != NULL && *p != '\0') { - fprintf(stderr, - "\"%s\" not valid time duration.\n", - optarg); - usage(nf); - } - break; + if (arg_parse(argc, argv, f)) + return; + + if (opt.flags == NULL || opt.op == NULL) + arg_help(argc, argv, f); + if (strcmp(opt.flags, "refthread") == 0) + io_test.flags |= NVME_TEST_FLAG_REFTHREAD; + if (opt.intr != NULL) { + if (strcmp(opt.intr, "bio") == 0 || + strcmp(opt.intr, "wait") == 0) + ioctl_cmd = NVME_BIO_TEST; + else if (strcmp(opt.intr, "io") == 0 || + strcmp(opt.intr, "intr") == 0) + ioctl_cmd = NVME_IO_TEST; + else { + fprintf(stderr, "Unknown interrupt test type %s\n", opt.intr); + arg_help(argc, argv, f); } } - - if (!nflag || !oflag || !sflag || !tflag || optind >= argc) - usage(nf); - - - open_dev(argv[optind], &fd, 1, 1); + if (opt.threads <= 0 || opt.threads > 128) { + fprintf(stderr, "Bad number of threads %d\n", opt.threads); + arg_help(argc, argv, f); + } + if (strcasecmp(opt.op, "read") == 0) + io_test.opc = NVME_OPC_READ; + else if (strcasecmp(opt.op, "write") == 0) + io_test.opc = NVME_OPC_WRITE; + else { + fprintf(stderr, "\"%s\" not valid opcode.\n", opt.op); + arg_help(argc, argv, f); + } + if (opt.time == 0) { + fprintf(stderr, "No time speciifed\n"); + arg_help(argc, argv, f); + } + io_test.time = opt.time; + open_dev(opt.dev, &fd, 1, 1); if (ioctl(fd, ioctl_cmd, &io_test) < 0) err(1, "ioctl NVME_IO_TEST failed"); close(fd); - print_perftest(&io_test, perthread); + print_perftest(&io_test, opt.perthread); exit(0); } - -NVME_COMMAND(top, perftest, perftest, PERFTEST_USAGE); Index: sbin/nvmecontrol/power.c =================================================================== --- sbin/nvmecontrol/power.c +++ sbin/nvmecontrol/power.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -43,8 +44,19 @@ _Static_assert(sizeof(struct nvme_power_state) == 256 / NBBY, "nvme_power_state size wrong"); -#define POWER_USAGE \ - "power [-l] [-p new-state [-w workload-hint]] \n" +#define POWER_NONE 0xffffffffu + +static struct options { + bool list; + uint32_t power; + uint32_t workload; + const char *dev; +} opt = { + .list = false, + .power = POWER_NONE, + .workload = 0, + .dev = NULL, +}; static void power_list_one(int i, struct nvme_power_state *nps) @@ -128,57 +140,28 @@ } static void -power(const struct nvme_function *nf, int argc, char *argv[]) +power(const struct cmd *f, int argc, char *argv[]) { struct nvme_controller_data cdata; - int ch, listflag = 0, powerflag = 0, power_val = 0, fd; - int workload = 0; - char *end; - - while ((ch = getopt(argc, argv, "lp:w:")) != -1) { - switch ((char)ch) { - case 'l': - listflag = 1; - break; - case 'p': - powerflag = 1; - power_val = strtol(optarg, &end, 0); - if (*end != '\0') { - fprintf(stderr, "Invalid power state number: %s\n", optarg); - usage(nf); - } - break; - case 'w': - workload = strtol(optarg, &end, 0); - if (*end != '\0') { - fprintf(stderr, "Invalid workload hint: %s\n", optarg); - usage(nf); - } - break; - default: - usage(nf); - } - } + int fd; - /* Check that a controller was specified. */ - if (optind >= argc) - usage(nf); + arg_parse(argc, argv, f); - if (listflag && powerflag) { + if (opt.list && opt.power != POWER_NONE) { fprintf(stderr, "Can't set power and list power states\n"); - usage(nf); + arg_help(argc, argv, f); } - open_dev(argv[optind], &fd, 1, 1); - read_controller_data(fd, &cdata); + open_dev(opt.dev, &fd, 1, 1); - if (listflag) { + if (opt.list) { + read_controller_data(fd, &cdata); power_list(&cdata); goto out; } - if (powerflag) { - power_set(fd, power_val, workload, 0); + if (opt.power != POWER_NONE) { + power_set(fd, opt.power, opt.workload, 0); goto out; } power_show(fd); @@ -188,4 +171,30 @@ exit(0); } -NVME_COMMAND(top, power, power, POWER_USAGE); +static const struct opts power_opts[] = { +#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } + OPT("list", 'l', arg_none, opt, list, + "List the valid power states"), + OPT("power", 'p', arg_uint32, opt, power, + "Set the power state"), + OPT("workload", 'w', arg_uint32, opt, workload, + "Set the workload"), + { NULL, 0, arg_none, NULL, NULL } +}; +#undef OPT + +static const struct args power_args[] = { + { arg_string, &opt.dev, "controller-id" }, + { arg_none, NULL, NULL }, +}; + +static struct cmd power_cmd = { + .name = "power", + .fn = power, + .descr = "Manage power states for the drive", + .ctx_size = sizeof(opt), + .opts = power_opts, + .args = power_args, +}; + +CMD_COMMAND(power_cmd); Index: sbin/nvmecontrol/reset.c =================================================================== --- sbin/nvmecontrol/reset.c +++ sbin/nvmecontrol/reset.c @@ -41,30 +41,36 @@ #include "nvmecontrol.h" -#define RESET_USAGE \ - "reset \n" +static struct options { + const char *dev; +} opt = { + .dev = NULL +}; + +static const struct args args[] = { + { arg_string, &opt.dev, "controller-id" }, + { arg_none, NULL, NULL }, +}; static void -reset(const struct nvme_function *nf, int argc, char *argv[]) +reset(const struct cmd *f, int argc, char *argv[]) { - int ch, fd; - - while ((ch = getopt(argc, argv, "")) != -1) { - switch ((char)ch) { - default: - usage(nf); - } - } + int fd; - /* Check that a controller was specified. */ - if (optind >= argc) - usage(nf); + arg_parse(argc, argv, f); + open_dev(opt.dev, &fd, 1, 1); - open_dev(argv[optind], &fd, 1, 1); if (ioctl(fd, NVME_RESET_CONTROLLER) < 0) err(1, "reset request to %s failed", argv[optind]); exit(0); } -NVME_COMMAND(top, reset, reset, RESET_USAGE); +static struct cmd reset_cmd = { + .name = "reset", + .fn = reset, + .descr = "Perform a controller-level reset.", + .args = args, +}; + +CMD_COMMAND(reset_cmd);