Create generic command / arg parsing routines

Create a set of routines and structures to hold the data for the args
for a command. Use them to generate help and to parse args. Convert
all the current commands over to the new format. "comnd" is a hat-tip
to the TOPS-20 %COMND JSYS that (very) loosely inspired much of the
subsequent command line notions in the industry, but this is far
simpler (the %COMND man page is longer than this code) and not in the
kernel... Also, it implements today's de-facto
	command [verb]+ [opts]* [args]*
format rather than the old, archaic TOPS-20 command format :)

This is a snapshot of a work in progress to get the nvme passthru
stuff committed. In time it will become a private library and used
by some other programs in the tree that conform to the above pattern.

Differential Revision: https://reviews.freebsd.org/D19296
This commit is contained in:
Warner Losh 2019-07-16 17:24:03 +00:00
parent dd8bacfd4e
commit f634b4c1be
15 changed files with 1266 additions and 812 deletions

View File

@ -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

326
sbin/nvmecontrol/comnd.c Normal file
View File

@ -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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/ioccom.h>
#include <ctype.h>
#include <dirent.h>
#include <dlfcn.h>
#include <err.h>
#include <fcntl.h>
#include <getopt.h>
#include <libutil.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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 = &top;
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, "=<STRING>", len);
break;
case arg_path:
strlcat(buf, "=<FILE>", len);
break;
default:
strlcat(buf, "=<NUM>", 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, " <args>");
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 = &top;
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)
{
}

102
sbin/nvmecontrol/comnd.h Normal file
View File

@ -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 <sys/queue.h>
#include <sys/linker_set.h>
/*
* 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 */

View File

@ -42,12 +42,24 @@ __FBSDID("$FreeBSD$");
#include <unistd.h>
#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 @@ ns_get_sector_size(struct nvme_namespace_data *nsdata)
}
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 @@ devlist(const struct nvme_function *nf, int argc, char *argv[])
exit(1);
}
NVME_COMMAND(top, devlist, devlist, DEVLIST_USAGE);

View File

@ -50,8 +50,52 @@ __FBSDID("$FreeBSD$");
#include "nvmecontrol.h"
#define FIRMWARE_USAGE \
"firmware [-s slot] [-f path_to_firmware] [-a] <controller id>\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 @@ slot_has_valid_firmware(int fd, int slot)
}
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 @@ activate_firmware(int fd, int slot, int activate_action)
}
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 @@ firmware(const struct nvme_function *nf, int argc, char *argv[])
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 @@ firmware(const struct nvme_function *nf, int argc, char *argv[])
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 @@ firmware(const struct nvme_function *nf, int argc, char *argv[])
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 @@ firmware(const struct nvme_function *nf, int argc, char *argv[])
"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);

View File

@ -35,6 +35,7 @@ __FBSDID("$FreeBSD$");
#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
@ -43,57 +44,104 @@ __FBSDID("$FreeBSD$");
#include "nvmecontrol.h"
#define FORMAT_USAGE \
"format [-f fmt] [-m mset] [-p pi] [-l pil] [-E] [-C] <controller id|namespace id>\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;
int lbaf, ms, pi, pil, ses, fd;
if (argc < 2)
usage(nf);
if (arg_parse(argc, argv, f))
return;
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);
}
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 @@ format(const struct nvme_function *nf, int argc, char *argv[])
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 @@ format(const struct nvme_function *nf, int argc, char *argv[])
& 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 @@ format(const struct nvme_function *nf, int argc, char *argv[])
/* 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 @@ format(const struct nvme_function *nf, int argc, char *argv[])
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 @@ format(const struct nvme_function *nf, int argc, char *argv[])
close(fd);
exit(0);
}
NVME_COMMAND(top, format, format, FORMAT_USAGE);

View File

@ -34,6 +34,7 @@ __FBSDID("$FreeBSD$");
#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
@ -43,8 +44,15 @@ __FBSDID("$FreeBSD$");
#include "nvmecontrol.h"
#include "nvmecontrol_ext.h"
#define IDENTIFY_USAGE \
"identify [-x [-v]] <controller id|namespace id>\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)
@ -160,35 +168,17 @@ print_namespace(struct nvme_namespace_data *nsdata)
}
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,
@ -197,9 +187,9 @@ identify_ctrlr(const struct nvme_function *nf, int argc, char *argv[])
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);
@ -207,37 +197,19 @@ identify_ctrlr(const struct nvme_function *nf, int argc, char *argv[])
}
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);
/*
@ -246,13 +218,13 @@ identify_ns(const struct nvme_function *nf,int argc, char *argv[])
* 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,
@ -261,9 +233,9 @@ identify_ns(const struct nvme_function *nf,int argc, char *argv[])
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);
@ -271,32 +243,42 @@ identify_ns(const struct nvme_function *nf,int argc, char *argv[])
}
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);

View File

@ -48,8 +48,56 @@ __FBSDID("$FreeBSD$");
#include "nvmecontrol.h"
#define LOGPAGE_USAGE \
"logpage <-p page_id> [-b] [-v vendor] [-x] <controller id|namespace id>\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, "<controller id|namespace id>" },
{ 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 @@ logpage_help(void)
}
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 @@ logpage(const struct nvme_function *nf, int argc, char *argv[])
* 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 @@ logpage(const struct nvme_function *nf, int argc, char *argv[])
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 @@ logpage(const struct nvme_function *nf, int argc, char *argv[])
* 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);

View File

@ -41,22 +41,58 @@ __FBSDID("$FreeBSD$");
#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 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 }
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,
};
CMD_SUBCOMMAND(wdc_cmd, cap_diag_cmd);
#define WDC_NVME_TOC_SIZE 8
#define WDC_NVME_CAP_DIAG_OPCODE 0xe6
#define WDC_NVME_CAP_DIAG_CMD 0x0000
static void wdc_cap_diag(const struct nvme_function *nf, int argc, char *argv[]);
#define WDC_CAP_DIAG_USAGE "wdc cap-diag [-o path-template]\n"
NVME_COMMAND(wdc, cap-diag, wdc_cap_diag, WDC_CAP_DIAG_USAGE);
static void
wdc_append_serial_name(int fd, char *buf, size_t len, const char *suffix)
{
@ -153,27 +189,20 @@ wdc_do_dump(int fd, char *tmpl, const char *suffix, uint32_t opcode,
}
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;
char tmpl[MAXPATHLEN];
int 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);
}
if (arg_parse(argc, argv, f))
return;
if (opt.template == NULL) {
fprintf(stderr, "Missing template arg.\n");
arg_help(argc, argv, f);
}
/* Check that a controller was specified. */
if (optind >= argc)
usage(nf);
open_dev(argv[optind], &fd, 1, 1);
wdc_do_dump(fd, path_tmpl, "cap_diag", WDC_NVME_CAP_DIAG_OPCODE,
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 @@ wdc_cap_diag(const struct nvme_function *nf, int argc, char *argv[])
}
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(hgst_info,
NVME_LOGPAGE(wdc_info,
HGST_INFO_LOG, "wdc", "Detailed Health/SMART",
print_hgst_info_log, DEFAULT_SIZE);
NVME_COMMAND(top, wdc, wdc, WDC_USAGE);

View File

@ -41,35 +41,194 @@ __FBSDID("$FreeBSD$");
#include "nvmecontrol.h"
NVME_CMD_DECLARE(ns, struct nvme_function);
/* Tables for command line parsing */
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;
#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 }
static struct cmd ns_cmd = {
.name = "ns", .fn = ns, .descr = "Namespace commands", .ctx_size = 0, .opts = NULL, .args = NULL,
};
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,
};
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
};
static const struct args create_args[] = {
{ arg_string, &create_opt.dev, "controller-id" },
{ arg_none, NULL, NULL },
};
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,
};
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 */
#define NSCREATE_USAGE \
"ns create -s size [-c cap] [-f fmt] [-m mset] [-n nmic] [-p pi] [-l pil] nvmeN\n"
#define NSDELETE_USAGE \
"ns delete -n nsid nvmeN\n"
#define NSATTACH_USAGE \
"ns attach -n nsid [-c ctrlrid] nvmeN \n"
#define NSDETACH_USAGE \
"ns detach -n nsid [-c ctrlrid] nvmeN\n"
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[]);
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);
struct ns_result_str {
uint16_t res;
const char * str;
@ -110,54 +269,25 @@ get_res_str(uint16_t res)
* 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;
int fd, result;
if (optind >= argc)
usage(nf);
if (arg_parse(argc, argv, f))
return;
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);
}
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);
}
if (optind >= argc)
usage(nf);
if (cap == -1)
cap = nsze;
if (nsze == -1 || cap == -1)
usage(nf);
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 @@ nscreate(const struct nvme_function *nf, int argc, char *argv[])
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 @@ nscreate(const struct nvme_function *nf, int argc, char *argv[])
}
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 @@ nsdelete(const struct nvme_function *nf, int argc, char *argv[])
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 @@ nsdelete(const struct nvme_function *nf, int argc, char *argv[])
* 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 @@ nsattach(const struct nvme_function *nf, int argc, char *argv[])
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 @@ nsattach(const struct nvme_function *nf, int argc, char *argv[])
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 @@ nsdetach(const struct nvme_function *nf, int argc, char *argv[])
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 @@ nsdetach(const struct nvme_function *nf, int argc, char *argv[])
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 @@ nsdetach(const struct nvme_function *nf, int argc, char *argv[])
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);

View File

@ -35,7 +35,6 @@ __FBSDID("$FreeBSD$");
#include <ctype.h>
#include <dlfcn.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
@ -49,101 +48,6 @@ __FBSDID("$FreeBSD$");
#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 @@ parse_ns_str(const char *ns_str, char *ctrlr_str, uint32_t *nsid)
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));
cmd_init();
load_dir("/lib/nvmecontrol");
load_dir("/usr/local/lib/nvmecontrol");
cmd_load_dir("/lib/nvmecontrol", NULL, NULL);
cmd_load_dir("/usr/local/lib/nvmecontrol", NULL, NULL);
if (argc < 2)
gen_usage_set(top_begin(), top_limit());
dispatch_set(argc, argv, top_begin(), top_limit());
cmd_dispatch(argc, argv, NULL);
return (0);
}

View File

@ -31,28 +31,8 @@
#ifndef __NVMECONTROL_H__
#define __NVMECONTROL_H__
#include <sys/linker_set.h>
#include <sys/queue.h>
#include <dev/nvme/nvme.h>
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 @@ struct logpage_function {
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 @@ struct kv_name {
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 read_logpage(int fd, uint8_t log_page, uint32_t nsid, void *payload,
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 @@ to128(void *p)
uint64_t le48dec(const void *pp);
char * uint128_to_str(uint128_t u, char *buf, size_t buflen);
#endif

View File

@ -45,11 +45,69 @@ __FBSDID("$FreeBSD$");
#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" \
" <namespace id>\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 @@ print_perftest(struct nvme_io_test *io_test, bool perthread)
}
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);

View File

@ -32,6 +32,7 @@ __FBSDID("$FreeBSD$");
#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
@ -43,8 +44,19 @@ __FBSDID("$FreeBSD$");
_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]] <controller id>\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 @@ power_show(int fd)
}
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;
int fd;
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);
}
}
arg_parse(argc, argv, f);
/* Check that a controller was specified. */
if (optind >= argc)
usage(nf);
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 @@ power(const struct nvme_function *nf, int argc, char *argv[])
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);

View File

@ -41,30 +41,36 @@ __FBSDID("$FreeBSD$");
#include "nvmecontrol.h"
#define RESET_USAGE \
"reset <controller id>\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;
int fd;
while ((ch = getopt(argc, argv, "")) != -1) {
switch ((char)ch) {
default:
usage(nf);
}
}
arg_parse(argc, argv, f);
open_dev(opt.dev, &fd, 1, 1);
/* Check that a controller was specified. */
if (optind >= argc)
usage(nf);
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);