NVMECONTROL(8): Import from FreeBSD

Update #3821.
This commit is contained in:
Sebastian Huber 2019-09-20 07:55:33 +02:00
parent e7291f7ac3
commit 94a7b59e06
23 changed files with 6252 additions and 0 deletions

View File

@ -0,0 +1,345 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* 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_tree(const struct cmd *f)
{
if (f->parent != NULL)
print_tree(f->parent);
if (f->name != NULL)
fprintf(stderr, " %s", f->name);
}
static void
print_usage(const struct cmd *f)
{
fprintf(stderr, " %s", getprogname());
print_tree(f->parent);
fprintf(stderr, " %-15s - %s\n", 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", getprogname());
print_tree(f);
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);
free(lopts);
free(shortopts);
return (1);
}
*(char **)args->ptr = argv[optind++];
args++;
}
}
free(lopts);
free(shortopts);
return (0);
bad_arg:
fprintf(stderr, "Bad value to --%s: %s\n", opts[idx].long_arg, optarg);
free(lopts);
free(shortopts);
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)
{
}

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

@ -0,0 +1,131 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nvmecontrol.h"
#include "comnd.h"
/* 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 = "List 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)
{
uint8_t flbas_fmt, lbads;
flbas_fmt = (nsdata->flbas >> NVME_NS_DATA_FLBAS_FORMAT_SHIFT) &
NVME_NS_DATA_FLBAS_FORMAT_MASK;
lbads = (nsdata->lbaf[flbas_fmt] >> NVME_NS_DATA_LBAF_LBADS_SHIFT) &
NVME_NS_DATA_LBAF_LBADS_MASK;
return (1 << lbads);
}
static void
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 ctrlr, fd, found, ret;
if (arg_parse(argc, argv, f))
return;
ctrlr = -1;
found = 0;
while (ctrlr < NVME_MAX_UNIT) {
ctrlr++;
sprintf(name, "%s%d", NVME_CTRLR_PREFIX, ctrlr);
ret = open_dev(name, &fd, 0, 0);
if (ret == EACCES) {
warnx("could not open "_PATH_DEV"%s\n", name);
continue;
} else if (ret != 0)
continue;
found++;
read_controller_data(fd, &cdata);
nvme_strvis(mn, cdata.mn, sizeof(mn), NVME_MODEL_NUMBER_LENGTH);
printf("%6s: %s\n", name, mn);
for (i = 0; i < cdata.nn; i++) {
read_namespace_data(fd, i + 1, &nsdata);
if (nsdata.nsze == 0)
continue;
sprintf(name, "%s%d%s%d", NVME_CTRLR_PREFIX, ctrlr,
NVME_NS_PREFIX, i + 1);
printf(" %10s (%lldMB)\n",
name,
nsdata.nsze *
(long long)ns_get_sector_size(&nsdata) /
1024 / 1024);
}
close(fd);
}
if (found == 0)
printf("No NVMe controllers found.\n");
exit(1);
}

View File

@ -0,0 +1,363 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2013 EMC Corp.
* All rights reserved.
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/ioccom.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nvmecontrol.h"
/* 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)
{
struct nvme_firmware_page fw;
int has_fw = false;
read_logpage(fd, NVME_LOG_FIRMWARE_SLOT,
NVME_GLOBAL_NAMESPACE_TAG, 0, 0, 0, &fw, sizeof(fw));
if (fw.revision[slot-1] != 0LLU)
has_fw = true;
return (has_fw);
}
static void
read_image_file(const char *path, void **buf, int32_t *size)
{
struct stat sb;
int32_t filesize;
int fd;
*size = 0;
*buf = NULL;
if ((fd = open(path, O_RDONLY)) < 0)
err(1, "unable to open '%s'", path);
if (fstat(fd, &sb) < 0)
err(1, "unable to stat '%s'", path);
/*
* The NVMe spec does not explicitly state a maximum firmware image
* size, although one can be inferred from the dword size limitation
* for the size and offset fields in the Firmware Image Download
* command.
*
* Technically, the max is UINT32_MAX * sizeof(uint32_t), since the
* size and offsets are specified in terms of dwords (not bytes), but
* realistically INT32_MAX is sufficient here and simplifies matters
* a bit.
*/
if (sb.st_size > INT32_MAX)
errx(1, "size of file '%s' is too large (%jd bytes)",
path, (intmax_t)sb.st_size);
filesize = (int32_t)sb.st_size;
if ((*buf = malloc(filesize)) == NULL)
errx(1, "unable to malloc %d bytes", filesize);
if ((*size = read(fd, *buf, filesize)) < 0)
err(1, "error reading '%s'", path);
/* XXX assuming no short reads */
if (*size != filesize)
errx(1,
"error reading '%s' (read %d bytes, requested %d bytes)",
path, *size, filesize);
}
static void
update_firmware(int fd, uint8_t *payload, int32_t payload_size)
{
struct nvme_pt_command pt;
int32_t off, resid, size;
void *chunk;
off = 0;
resid = payload_size;
if ((chunk = aligned_alloc(PAGE_SIZE, NVME_MAX_XFER_SIZE)) == NULL)
errx(1, "unable to malloc %d bytes", NVME_MAX_XFER_SIZE);
while (resid > 0) {
size = (resid >= NVME_MAX_XFER_SIZE) ?
NVME_MAX_XFER_SIZE : resid;
memcpy(chunk, payload + off, size);
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_FIRMWARE_IMAGE_DOWNLOAD;
pt.cmd.cdw10 = htole32((size / sizeof(uint32_t)) - 1);
pt.cmd.cdw11 = htole32(off / sizeof(uint32_t));
pt.buf = chunk;
pt.len = size;
pt.is_read = 0;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "firmware download request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "firmware download request returned error");
resid -= size;
off += size;
}
}
static int
activate_firmware(int fd, int slot, int activate_action)
{
struct nvme_pt_command pt;
uint16_t sct, sc;
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_FIRMWARE_ACTIVATE;
pt.cmd.cdw10 = htole32((activate_action << 3) | slot);
pt.is_read = 0;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "firmware activate request failed");
sct = NVME_STATUS_GET_SCT(pt.cpl.status);
sc = NVME_STATUS_GET_SC(pt.cpl.status);
if (sct == NVME_SCT_COMMAND_SPECIFIC &&
sc == NVME_SC_FIRMWARE_REQUIRES_RESET)
return 1;
if (nvme_completion_is_error(&pt.cpl))
errx(1, "firmware activate request returned error");
return 0;
}
static void
firmware(const struct cmd *f, int argc, char *argv[])
{
int fd = -1;
int activate_action, reboot_required;
char prompt[64];
void *buf = NULL;
int32_t size = 0, nsid;
uint16_t oacs_fw;
uint8_t fw_slot1_ro, fw_num_slots;
struct nvme_controller_data cdata;
if (arg_parse(argc, argv, f))
return;
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);
}
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");
arg_help(argc, argv, f);
}
if (opt.activate && opt.fw_img == NULL && opt.slot == 0) {
fprintf(stderr,
"Slot number to activate not specified.\n");
arg_help(argc, argv, f);
}
open_dev(opt.dev, &fd, 1, 1);
/* Check that a controller (and not a namespace) was specified. */
get_nsid(fd, NULL, &nsid);
if (nsid != 0) {
close(fd);
arg_help(argc, argv, f);
}
read_controller_data(fd, &cdata);
oacs_fw = (cdata.oacs >> NVME_CTRLR_DATA_OACS_FIRMWARE_SHIFT) &
NVME_CTRLR_DATA_OACS_FIRMWARE_MASK;
if (oacs_fw == 0)
errx(1,
"controller does not support firmware activate/download");
fw_slot1_ro = (cdata.frmw >> NVME_CTRLR_DATA_FRMW_SLOT1_RO_SHIFT) &
NVME_CTRLR_DATA_FRMW_SLOT1_RO_MASK;
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 (opt.slot > fw_num_slots)
errx(1,
"slot %d specified but controller only supports %d slots",
opt.slot, fw_num_slots);
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",
opt.slot, opt.dev);
if (opt.fw_img)
read_image_file(opt.fw_img, &buf, &size);
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",
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",
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",
opt.fw_img, opt.dev);
printf("Are you sure you want to continue? (yes/no) ");
while (1) {
fgets(prompt, sizeof(prompt), stdin);
if (strncasecmp(prompt, "yes", 3) == 0)
break;
if (strncasecmp(prompt, "no", 2) == 0)
exit(1);
printf("Please answer \"yes\" or \"no\". ");
}
if (opt.fw_img != NULL) {
update_firmware(fd, buf, size);
if (opt.activate)
activate_action = NVME_AA_REPLACE_ACTIVATE;
else
activate_action = NVME_AA_REPLACE_NO_ACTIVATE;
} else {
activate_action = NVME_AA_ACTIVATE;
}
reboot_required = activate_firmware(fd, opt.slot, activate_action);
if (opt.activate) {
if (reboot_required) {
printf("New firmware image activated but requires "
"conventional reset (i.e. reboot) to "
"complete activation.\n");
} else {
printf("New firmware image activated and will take "
"effect after next controller reset.\n"
"Controller reset can be initiated via "
"'nvmecontrol reset %s'\n",
opt.dev);
}
}
close(fd);
exit(0);
}

View File

@ -0,0 +1,222 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (C) 2018-2019 Alexander Motin <mav@FreeBSD.org>
*
* 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 <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nvmecontrol.h"
#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,
"Crptographic erase"),
OPT("erase", 'E', arg_none, opt, Eflag,
"User data erase"),
OPT("lbaf", 'f', arg_uint32, opt, lbaf,
"LBA Format to apply to the media"),
OPT("ms", 'm', arg_uint32, opt, ms,
"Metadata settings"),
OPT("pi", 'p', arg_uint32, opt, pi,
"Protective information"),
OPT("pil", 'l', arg_uint32, opt, pil,
"Protective information location"),
OPT("ses", 's', arg_uint32, opt, ses,
"Secure erase settings"),
{ 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 cmd *f, int argc, char *argv[])
{
struct nvme_controller_data cd;
struct nvme_namespace_data nsd;
struct nvme_pt_command pt;
char *path;
const char *target;
uint32_t nsid;
int lbaf, ms, pi, pil, ses, fd;
if (arg_parse(argc, argv, f))
return;
if ((int)opt.Eflag + opt.Cflag + (opt.ses != SES_NONE) > 1) {
fprintf(stderr,
"Only one of -E, -C or -s may be specified\n");
arg_help(argc, argv, f);
}
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;
open_dev(target, &fd, 1, 1);
get_nsid(fd, &path, &nsid);
if (nsid == 0) {
nsid = NVME_GLOBAL_NAMESPACE_TAG;
} else {
/*
* We send FORMAT commands to the controller, not the namespace,
* since it is an admin cmd. The namespace ID will be specified
* in the command itself. So parse the namespace's device node
* string to get the controller substring and namespace ID.
*/
close(fd);
open_dev(path, &fd, 1, 1);
}
free(path);
/* Check that controller can execute this command. */
read_controller_data(fd, &cd);
if (((cd.oacs >> NVME_CTRLR_DATA_OACS_FORMAT_SHIFT) &
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 == 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 == 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 != SES_NONE)
errx(1, "controller does not support per-NS erase");
/* Try to keep previous namespace parameters. */
read_namespace_data(fd, nsid, &nsd);
if (lbaf < 0)
lbaf = (nsd.flbas >> NVME_NS_DATA_FLBAS_FORMAT_SHIFT)
& NVME_NS_DATA_FLBAS_FORMAT_MASK;
if (lbaf > nsd.nlbaf)
errx(1, "LBA format is out of range");
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)
& NVME_NS_DATA_DPS_MD_START_MASK;
if (pil < 0)
pil = (nsd.dps >> NVME_NS_DATA_DPS_PIT_SHIFT)
& NVME_NS_DATA_DPS_PIT_MASK;
} else {
/* We have no previous parameters, so default to zeroes. */
if (lbaf < 0)
lbaf = 0;
if (ms < 0)
ms = 0;
if (pi < 0)
pi = 0;
if (pil < 0)
pil = 0;
}
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_FORMAT_NVM;
pt.cmd.nsid = htole32(nsid);
pt.cmd.cdw10 = htole32((ses << 9) + (pil << 8) + (pi << 5) +
(ms << 4) + lbaf);
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "format request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "format request returned error");
close(fd);
exit(0);
}

View File

@ -0,0 +1,280 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (C) 2012-2013 Intel Corporation
* All rights reserved.
* Copyright (C) 2018-2019 Alexander Motin <mav@FreeBSD.org>
*
* 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 <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nvmecontrol.h"
#include "nvmecontrol_ext.h"
#define NONE 0xfffffffeu
static struct options {
bool hex;
bool verbose;
const char *dev;
uint32_t nsid;
} opt = {
.hex = false,
.verbose = false,
.dev = NULL,
.nsid = NONE,
};
void
print_namespace(struct nvme_namespace_data *nsdata)
{
uint32_t i;
uint32_t lbaf, lbads, ms, rp;
uint8_t thin_prov, ptype;
uint8_t flbas_fmt, t;
thin_prov = (nsdata->nsfeat >> NVME_NS_DATA_NSFEAT_THIN_PROV_SHIFT) &
NVME_NS_DATA_NSFEAT_THIN_PROV_MASK;
flbas_fmt = (nsdata->flbas >> NVME_NS_DATA_FLBAS_FORMAT_SHIFT) &
NVME_NS_DATA_FLBAS_FORMAT_MASK;
printf("Size (in LBAs): %lld (%lldM)\n",
(long long)nsdata->nsze,
(long long)nsdata->nsze / 1024 / 1024);
printf("Capacity (in LBAs): %lld (%lldM)\n",
(long long)nsdata->ncap,
(long long)nsdata->ncap / 1024 / 1024);
printf("Utilization (in LBAs): %lld (%lldM)\n",
(long long)nsdata->nuse,
(long long)nsdata->nuse / 1024 / 1024);
printf("Thin Provisioning: %s\n",
thin_prov ? "Supported" : "Not Supported");
printf("Number of LBA Formats: %d\n", nsdata->nlbaf+1);
printf("Current LBA Format: LBA Format #%02d\n", flbas_fmt);
printf("Data Protection Caps: %s%s%s%s%s%s\n",
(nsdata->dpc == 0) ? "Not Supported" : "",
((nsdata->dpc >> NVME_NS_DATA_DPC_MD_END_SHIFT) &
NVME_NS_DATA_DPC_MD_END_MASK) ? "Last Bytes, " : "",
((nsdata->dpc >> NVME_NS_DATA_DPC_MD_START_SHIFT) &
NVME_NS_DATA_DPC_MD_START_MASK) ? "First Bytes, " : "",
((nsdata->dpc >> NVME_NS_DATA_DPC_PIT3_SHIFT) &
NVME_NS_DATA_DPC_PIT3_MASK) ? "Type 3, " : "",
((nsdata->dpc >> NVME_NS_DATA_DPC_PIT2_SHIFT) &
NVME_NS_DATA_DPC_PIT2_MASK) ? "Type 2, " : "",
((nsdata->dpc >> NVME_NS_DATA_DPC_PIT2_MASK) &
NVME_NS_DATA_DPC_PIT1_MASK) ? "Type 1" : "");
printf("Data Protection Settings: ");
ptype = (nsdata->dps >> NVME_NS_DATA_DPS_PIT_SHIFT) &
NVME_NS_DATA_DPS_PIT_MASK;
if (ptype) {
printf("Type %d, %s Bytes\n", ptype,
((nsdata->dps >> NVME_NS_DATA_DPS_MD_START_SHIFT) &
NVME_NS_DATA_DPS_MD_START_MASK) ? "First" : "Last");
} else {
printf("Not Enabled\n");
}
printf("Multi-Path I/O Capabilities: %s%s\n",
(nsdata->nmic == 0) ? "Not Supported" : "",
((nsdata->nmic >> NVME_NS_DATA_NMIC_MAY_BE_SHARED_SHIFT) &
NVME_NS_DATA_NMIC_MAY_BE_SHARED_MASK) ? "May be shared" : "");
printf("Reservation Capabilities: %s%s%s%s%s%s%s%s%s\n",
(nsdata->rescap == 0) ? "Not Supported" : "",
((nsdata->rescap >> NVME_NS_DATA_RESCAP_IEKEY13_SHIFT) &
NVME_NS_DATA_RESCAP_IEKEY13_MASK) ? "IEKEY13, " : "",
((nsdata->rescap >> NVME_NS_DATA_RESCAP_EX_AC_AR_SHIFT) &
NVME_NS_DATA_RESCAP_EX_AC_AR_MASK) ? "EX_AC_AR, " : "",
((nsdata->rescap >> NVME_NS_DATA_RESCAP_WR_EX_AR_SHIFT) &
NVME_NS_DATA_RESCAP_WR_EX_AR_MASK) ? "WR_EX_AR, " : "",
((nsdata->rescap >> NVME_NS_DATA_RESCAP_EX_AC_RO_SHIFT) &
NVME_NS_DATA_RESCAP_EX_AC_RO_MASK) ? "EX_AC_RO, " : "",
((nsdata->rescap >> NVME_NS_DATA_RESCAP_WR_EX_RO_SHIFT) &
NVME_NS_DATA_RESCAP_WR_EX_RO_MASK) ? "WR_EX_RO, " : "",
((nsdata->rescap >> NVME_NS_DATA_RESCAP_EX_AC_SHIFT) &
NVME_NS_DATA_RESCAP_EX_AC_MASK) ? "EX_AC, " : "",
((nsdata->rescap >> NVME_NS_DATA_RESCAP_WR_EX_SHIFT) &
NVME_NS_DATA_RESCAP_WR_EX_MASK) ? "WR_EX, " : "",
((nsdata->rescap >> NVME_NS_DATA_RESCAP_PTPL_SHIFT) &
NVME_NS_DATA_RESCAP_PTPL_MASK) ? "PTPL" : "");
printf("Format Progress Indicator: ");
if ((nsdata->fpi >> NVME_NS_DATA_FPI_SUPP_SHIFT) &
NVME_NS_DATA_FPI_SUPP_MASK) {
printf("%u%% remains\n",
(nsdata->fpi >> NVME_NS_DATA_FPI_PERC_SHIFT) &
NVME_NS_DATA_FPI_PERC_MASK);
} else
printf("Not Supported\n");
t = (nsdata->dlfeat >> NVME_NS_DATA_DLFEAT_READ_SHIFT) &
NVME_NS_DATA_DLFEAT_READ_MASK;
printf("Deallocate Logical Block: Read %s%s%s\n",
(t == NVME_NS_DATA_DLFEAT_READ_NR) ? "Not Reported" :
(t == NVME_NS_DATA_DLFEAT_READ_00) ? "00h" :
(t == NVME_NS_DATA_DLFEAT_READ_FF) ? "FFh" : "Unknown",
(nsdata->dlfeat >> NVME_NS_DATA_DLFEAT_DWZ_SHIFT) &
NVME_NS_DATA_DLFEAT_DWZ_MASK ? ", Write Zero" : "",
(nsdata->dlfeat >> NVME_NS_DATA_DLFEAT_GCRC_SHIFT) &
NVME_NS_DATA_DLFEAT_GCRC_MASK ? ", Guard CRC" : "");
printf("Optimal I/O Boundary (LBAs): %u\n", nsdata->noiob);
printf("Globally Unique Identifier: ");
for (i = 0; i < sizeof(nsdata->nguid); i++)
printf("%02x", nsdata->nguid[i]);
printf("\n");
printf("IEEE EUI64: ");
for (i = 0; i < sizeof(nsdata->eui64); i++)
printf("%02x", nsdata->eui64[i]);
printf("\n");
for (i = 0; i <= nsdata->nlbaf; i++) {
lbaf = nsdata->lbaf[i];
lbads = (lbaf >> NVME_NS_DATA_LBAF_LBADS_SHIFT) &
NVME_NS_DATA_LBAF_LBADS_MASK;
ms = (lbaf >> NVME_NS_DATA_LBAF_MS_SHIFT) &
NVME_NS_DATA_LBAF_MS_MASK;
rp = (lbaf >> NVME_NS_DATA_LBAF_RP_SHIFT) &
NVME_NS_DATA_LBAF_RP_MASK;
printf("LBA Format #%02d: Data Size: %5d Metadata Size: %5d"
" Performance: %s\n",
i, 1 << lbads, ms, (rp == 0) ? "Best" :
(rp == 1) ? "Better" : (rp == 2) ? "Good" : "Degraded");
}
}
static void
identify_ctrlr(int fd)
{
struct nvme_controller_data cdata;
int hexlength;
read_controller_data(fd, &cdata);
close(fd);
if (opt.hex) {
if (opt.verbose)
hexlength = sizeof(struct nvme_controller_data);
else
hexlength = offsetof(struct nvme_controller_data,
reserved8);
print_hex(&cdata, hexlength);
exit(0);
}
nvme_print_controller(&cdata);
exit(0);
}
static void
identify_ns(int fd, uint32_t nsid)
{
struct nvme_namespace_data nsdata;
int hexlength;
read_namespace_data(fd, nsid, &nsdata);
close(fd);
if (opt.hex) {
if (opt.verbose)
hexlength = sizeof(struct nvme_namespace_data);
else
hexlength = offsetof(struct nvme_namespace_data,
reserved6);
print_hex(&nsdata, hexlength);
exit(0);
}
print_namespace(&nsdata);
exit(0);
}
static void
identify(const struct cmd *f, int argc, char *argv[])
{
char *path;
int fd;
uint32_t nsid;
arg_parse(argc, argv, f);
open_dev(opt.dev, &fd, 1, 1);
get_nsid(fd, &path, &nsid);
if (nsid != 0) {
/*
* We got namespace device, but we need to send IDENTIFY
* commands to the controller, not the namespace, since it
* is an admin cmd. The namespace ID will be specified in
* the IDENTIFY command itself.
*/
close(fd);
open_dev(path, &fd, 1, 1);
}
free(path);
if (opt.nsid != NONE)
nsid = opt.nsid;
if (nsid == 0)
identify_ctrlr(fd);
else
identify_ns(fd, nsid);
}
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"),
OPT("nsid", 'n', arg_uint32, opt, nsid,
"Namespace ID to use if not in device name"),
{ 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 summary of the IDENTIFY information",
.ctx_size = sizeof(opt),
.opts = identify_opts,
.args = identify_args,
};
CMD_COMMAND(identify_cmd);

View File

@ -0,0 +1,249 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (C) 2012-2013 Intel Corporation
* All rights reserved.
* Copyright (C) 2018-2019 Alexander Motin <mav@FreeBSD.org>
*
* 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 <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nvmecontrol.h"
#include "nvmecontrol_ext.h"
void
nvme_print_controller(struct nvme_controller_data *cdata)
{
uint8_t str[128];
char cbuf[UINT128_DIG + 1];
uint16_t oncs, oacs;
uint8_t compare, write_unc, dsm, t;
uint8_t security, fmt, fw, nsmgmt;
uint8_t fw_slot1_ro, fw_num_slots;
uint8_t ns_smart;
uint8_t sqes_max, sqes_min;
uint8_t cqes_max, cqes_min;
oncs = cdata->oncs;
compare = (oncs >> NVME_CTRLR_DATA_ONCS_COMPARE_SHIFT) &
NVME_CTRLR_DATA_ONCS_COMPARE_MASK;
write_unc = (oncs >> NVME_CTRLR_DATA_ONCS_WRITE_UNC_SHIFT) &
NVME_CTRLR_DATA_ONCS_WRITE_UNC_MASK;
dsm = (oncs >> NVME_CTRLR_DATA_ONCS_DSM_SHIFT) &
NVME_CTRLR_DATA_ONCS_DSM_MASK;
oacs = cdata->oacs;
security = (oacs >> NVME_CTRLR_DATA_OACS_SECURITY_SHIFT) &
NVME_CTRLR_DATA_OACS_SECURITY_MASK;
fmt = (oacs >> NVME_CTRLR_DATA_OACS_FORMAT_SHIFT) &
NVME_CTRLR_DATA_OACS_FORMAT_MASK;
fw = (oacs >> NVME_CTRLR_DATA_OACS_FIRMWARE_SHIFT) &
NVME_CTRLR_DATA_OACS_FIRMWARE_MASK;
nsmgmt = (oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) &
NVME_CTRLR_DATA_OACS_NSMGMT_MASK;
fw_num_slots = (cdata->frmw >> NVME_CTRLR_DATA_FRMW_NUM_SLOTS_SHIFT) &
NVME_CTRLR_DATA_FRMW_NUM_SLOTS_MASK;
fw_slot1_ro = (cdata->frmw >> NVME_CTRLR_DATA_FRMW_SLOT1_RO_SHIFT) &
NVME_CTRLR_DATA_FRMW_SLOT1_RO_MASK;
ns_smart = (cdata->lpa >> NVME_CTRLR_DATA_LPA_NS_SMART_SHIFT) &
NVME_CTRLR_DATA_LPA_NS_SMART_MASK;
sqes_min = (cdata->sqes >> NVME_CTRLR_DATA_SQES_MIN_SHIFT) &
NVME_CTRLR_DATA_SQES_MIN_MASK;
sqes_max = (cdata->sqes >> NVME_CTRLR_DATA_SQES_MAX_SHIFT) &
NVME_CTRLR_DATA_SQES_MAX_MASK;
cqes_min = (cdata->cqes >> NVME_CTRLR_DATA_CQES_MIN_SHIFT) &
NVME_CTRLR_DATA_CQES_MIN_MASK;
cqes_max = (cdata->cqes >> NVME_CTRLR_DATA_CQES_MAX_SHIFT) &
NVME_CTRLR_DATA_CQES_MAX_MASK;
printf("Controller Capabilities/Features\n");
printf("================================\n");
printf("Vendor ID: %04x\n", cdata->vid);
printf("Subsystem Vendor ID: %04x\n", cdata->ssvid);
nvme_strvis(str, cdata->sn, sizeof(str), NVME_SERIAL_NUMBER_LENGTH);
printf("Serial Number: %s\n", str);
nvme_strvis(str, cdata->mn, sizeof(str), NVME_MODEL_NUMBER_LENGTH);
printf("Model Number: %s\n", str);
nvme_strvis(str, cdata->fr, sizeof(str), NVME_FIRMWARE_REVISION_LENGTH);
printf("Firmware Version: %s\n", str);
printf("Recommended Arb Burst: %d\n", cdata->rab);
printf("IEEE OUI Identifier: %02x %02x %02x\n",
cdata->ieee[0], cdata->ieee[1], cdata->ieee[2]);
printf("Multi-Path I/O Capabilities: %s%s%s%s%s\n",
(cdata->mic == 0) ? "Not Supported" : "",
((cdata->mic >> NVME_CTRLR_DATA_MIC_ANAR_SHIFT) &
NVME_CTRLR_DATA_MIC_SRIOVVF_MASK) ? "Asymmetric, " : "",
((cdata->mic >> NVME_CTRLR_DATA_MIC_SRIOVVF_SHIFT) &
NVME_CTRLR_DATA_MIC_SRIOVVF_MASK) ? "SR-IOV VF, " : "",
((cdata->mic >> NVME_CTRLR_DATA_MIC_MCTRLRS_SHIFT) &
NVME_CTRLR_DATA_MIC_MCTRLRS_MASK) ? "Multiple controllers, " : "",
((cdata->mic >> NVME_CTRLR_DATA_MIC_MPORTS_SHIFT) &
NVME_CTRLR_DATA_MIC_MPORTS_MASK) ? "Multiple ports" : "");
/* TODO: Use CAP.MPSMIN to determine true memory page size. */
printf("Max Data Transfer Size: ");
if (cdata->mdts == 0)
printf("Unlimited\n");
else
printf("%ld\n", PAGE_SIZE * (1L << cdata->mdts));
printf("Controller ID: 0x%04x\n", cdata->ctrlr_id);
printf("Version: %d.%d.%d\n",
(cdata->ver >> 16) & 0xffff, (cdata->ver >> 8) & 0xff,
cdata->ver & 0xff);
printf("\n");
printf("Admin Command Set Attributes\n");
printf("============================\n");
printf("Security Send/Receive: %s\n",
security ? "Supported" : "Not Supported");
printf("Format NVM: %s\n",
fmt ? "Supported" : "Not Supported");
printf("Firmware Activate/Download: %s\n",
fw ? "Supported" : "Not Supported");
printf("Namespace Managment: %s\n",
nsmgmt ? "Supported" : "Not Supported");
printf("Device Self-test: %sSupported\n",
((oacs >> NVME_CTRLR_DATA_OACS_SELFTEST_SHIFT) &
NVME_CTRLR_DATA_OACS_SELFTEST_MASK) ? "" : "Not ");
printf("Directives: %sSupported\n",
((oacs >> NVME_CTRLR_DATA_OACS_DIRECTIVES_SHIFT) &
NVME_CTRLR_DATA_OACS_DIRECTIVES_MASK) ? "" : "Not ");
printf("NVMe-MI Send/Receive: %sSupported\n",
((oacs >> NVME_CTRLR_DATA_OACS_NVMEMI_SHIFT) &
NVME_CTRLR_DATA_OACS_NVMEMI_MASK) ? "" : "Not ");
printf("Virtualization Management: %sSupported\n",
((oacs >> NVME_CTRLR_DATA_OACS_VM_SHIFT) &
NVME_CTRLR_DATA_OACS_VM_MASK) ? "" : "Not ");
printf("Doorbell Buffer Config: %sSupported\n",
((oacs >> NVME_CTRLR_DATA_OACS_DBBUFFER_SHIFT) &
NVME_CTRLR_DATA_OACS_DBBUFFER_MASK) ? "" : "Not ");
printf("Get LBA Status: %sSupported\n",
((oacs >> NVME_CTRLR_DATA_OACS_GETLBA_SHIFT) &
NVME_CTRLR_DATA_OACS_GETLBA_MASK) ? "" : "Not ");
printf("Sanitize: ");
if (cdata->sanicap != 0) {
printf("%s%s%s\n",
((cdata->sanicap >> NVME_CTRLR_DATA_SANICAP_CES_SHIFT) &
NVME_CTRLR_DATA_SANICAP_CES_MASK) ? "crypto, " : "",
((cdata->sanicap >> NVME_CTRLR_DATA_SANICAP_BES_SHIFT) &
NVME_CTRLR_DATA_SANICAP_BES_MASK) ? "block, " : "",
((cdata->sanicap >> NVME_CTRLR_DATA_SANICAP_OWS_SHIFT) &
NVME_CTRLR_DATA_SANICAP_OWS_MASK) ? "overwrite" : "");
} else {
printf("Not Supported\n");
}
printf("Abort Command Limit: %d\n", cdata->acl+1);
printf("Async Event Request Limit: %d\n", cdata->aerl+1);
printf("Number of Firmware Slots: ");
if (fw != 0)
printf("%d\n", fw_num_slots);
else
printf("N/A\n");
printf("Firmware Slot 1 Read-Only: ");
if (fw != 0)
printf("%s\n", fw_slot1_ro ? "Yes" : "No");
else
printf("N/A\n");
printf("Per-Namespace SMART Log: %s\n",
ns_smart ? "Yes" : "No");
printf("Error Log Page Entries: %d\n", cdata->elpe+1);
printf("Number of Power States: %d\n", cdata->npss+1);
printf("\n");
printf("NVM Command Set Attributes\n");
printf("==========================\n");
printf("Submission Queue Entry Size\n");
printf(" Max: %d\n", 1 << sqes_max);
printf(" Min: %d\n", 1 << sqes_min);
printf("Completion Queue Entry Size\n");
printf(" Max: %d\n", 1 << cqes_max);
printf(" Min: %d\n", 1 << cqes_min);
printf("Number of Namespaces: %d\n", cdata->nn);
printf("Compare Command: %s\n",
compare ? "Supported" : "Not Supported");
printf("Write Uncorrectable Command: %s\n",
write_unc ? "Supported" : "Not Supported");
printf("Dataset Management Command: %s\n",
dsm ? "Supported" : "Not Supported");
printf("Write Zeroes Command: %sSupported\n",
((oncs >> NVME_CTRLR_DATA_ONCS_WRZERO_SHIFT) &
NVME_CTRLR_DATA_ONCS_WRZERO_MASK) ? "" : "Not ");
printf("Save Features: %sSupported\n",
((oncs >> NVME_CTRLR_DATA_ONCS_SAVEFEAT_SHIFT) &
NVME_CTRLR_DATA_ONCS_SAVEFEAT_MASK) ? "" : "Not ");
printf("Reservations: %sSupported\n",
((oncs >> NVME_CTRLR_DATA_ONCS_RESERV_SHIFT) &
NVME_CTRLR_DATA_ONCS_RESERV_MASK) ? "" : "Not ");
printf("Timestamp feature: %sSupported\n",
((oncs >> NVME_CTRLR_DATA_ONCS_TIMESTAMP_SHIFT) &
NVME_CTRLR_DATA_ONCS_TIMESTAMP_MASK) ? "" : "Not ");
printf("Verify feature: %sSupported\n",
((oncs >> NVME_CTRLR_DATA_ONCS_VERIFY_SHIFT) &
NVME_CTRLR_DATA_ONCS_VERIFY_MASK) ? "" : "Not ");
printf("Fused Operation Support: %s%s\n",
(cdata->fuses == 0) ? "Not Supported" : "",
((cdata->fuses >> NVME_CTRLR_DATA_FUSES_CNW_SHIFT) &
NVME_CTRLR_DATA_FUSES_CNW_MASK) ? "Compare and Write" : "");
printf("Format NVM Attributes: %s%s Erase, %s Format\n",
((cdata->fna >> NVME_CTRLR_DATA_FNA_CRYPTO_ERASE_SHIFT) &
NVME_CTRLR_DATA_FNA_CRYPTO_ERASE_MASK) ? "Crypto Erase, " : "",
((cdata->fna >> NVME_CTRLR_DATA_FNA_ERASE_ALL_SHIFT) &
NVME_CTRLR_DATA_FNA_ERASE_ALL_MASK) ? "All-NVM" : "Per-NS",
((cdata->fna >> NVME_CTRLR_DATA_FNA_FORMAT_ALL_SHIFT) &
NVME_CTRLR_DATA_FNA_FORMAT_ALL_MASK) ? "All-NVM" : "Per-NS");
t = (cdata->vwc >> NVME_CTRLR_DATA_VWC_ALL_SHIFT) &
NVME_CTRLR_DATA_VWC_ALL_MASK;
printf("Volatile Write Cache: %s%s\n",
((cdata->vwc >> NVME_CTRLR_DATA_VWC_PRESENT_SHIFT) &
NVME_CTRLR_DATA_VWC_PRESENT_MASK) ? "Present" : "Not Present",
(t == NVME_CTRLR_DATA_VWC_ALL_NO) ? ", no flush all" :
(t == NVME_CTRLR_DATA_VWC_ALL_YES) ? ", flush all" : "");
if (nsmgmt) {
printf("\n");
printf("Namespace Drive Attributes\n");
printf("==========================\n");
printf("NVM total cap: %s\n",
uint128_to_str(to128(cdata->untncap.tnvmcap), cbuf, sizeof(cbuf)));
printf("NVM unallocated cap: %s\n",
uint128_to_str(to128(cdata->untncap.unvmcap), cbuf, sizeof(cbuf)));
}
}

View File

@ -0,0 +1,757 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2013 EMC Corp.
* All rights reserved.
*
* Copyright (C) 2012-2013 Intel Corporation
* All rights reserved.
* Copyright (C) 2018-2019 Alexander Motin <mav@FreeBSD.org>
*
* 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 <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/endian.h>
#include "nvmecontrol.h"
/* Tables for command line parsing */
static cmd_fn_t logpage;
#define NONE 0xffffffffu
static struct options {
bool binary;
bool hex;
uint32_t page;
uint8_t lsp;
uint16_t lsi;
bool rae;
const char *vendor;
const char *dev;
} opt = {
.binary = false,
.hex = false,
.page = NONE,
.lsp = 0,
.lsi = 0,
.rae = false,
.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("lsp", 'f', arg_uint8, opt, lsp,
"Log Specific Field"),
OPT("lsi", 'i', arg_uint16, opt, lsp,
"Log Specific Identifier"),
OPT("rae", 'r', arg_none, opt, rae,
"Retain Asynchronous Event"),
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)
static SLIST_HEAD(,logpage_function) logpages;
static int
logpage_compare(struct logpage_function *a, struct logpage_function *b)
{
int c;
if ((a->vendor == NULL) != (b->vendor == NULL))
return (a->vendor == NULL ? -1 : 1);
if (a->vendor != NULL) {
c = strcmp(a->vendor, b->vendor);
if (c != 0)
return (c);
}
return ((int)a->log_page - (int)b->log_page);
}
void
logpage_register(struct logpage_function *p)
{
struct logpage_function *l, *a;
a = NULL;
l = SLIST_FIRST(&logpages);
while (l != NULL) {
if (logpage_compare(l, p) > 0)
break;
a = l;
l = SLIST_NEXT(l, link);
}
if (a == NULL)
SLIST_INSERT_HEAD(&logpages, p, link);
else
SLIST_INSERT_AFTER(a, p, link);
}
const char *
kv_lookup(const struct kv_name *kv, size_t kv_count, uint32_t key)
{
static char bad[32];
size_t i;
for (i = 0; i < kv_count; i++, kv++)
if (kv->key == key)
return kv->name;
snprintf(bad, sizeof(bad), "Attribute %#x", key);
return bad;
}
static void
print_log_hex(const struct nvme_controller_data *cdata __unused, void *data, uint32_t length)
{
print_hex(data, length);
}
static void
print_bin(const struct nvme_controller_data *cdata __unused, void *data, uint32_t length)
{
write(STDOUT_FILENO, data, length);
}
static void *
get_log_buffer(uint32_t size)
{
void *buf;
if ((buf = malloc(size)) == NULL)
errx(1, "unable to malloc %u bytes", size);
memset(buf, 0, size);
return (buf);
}
void
read_logpage(int fd, uint8_t log_page, uint32_t nsid, uint8_t lsp,
uint16_t lsi, uint8_t rae, void *payload, uint32_t payload_size)
{
struct nvme_pt_command pt;
struct nvme_error_information_entry *err_entry;
u_int i, err_pages, numd;
numd = payload_size / sizeof(uint32_t) - 1;
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_GET_LOG_PAGE;
pt.cmd.nsid = htole32(nsid);
pt.cmd.cdw10 = htole32(
(numd << 16) | /* NUMDL */
(rae << 15) | /* RAE */
(lsp << 8) | /* LSP */
log_page); /* LID */
pt.cmd.cdw11 = htole32(
((uint32_t)lsi << 16) | /* LSI */
(numd >> 16)); /* NUMDU */
pt.cmd.cdw12 = 0; /* LPOL */
pt.cmd.cdw13 = 0; /* LPOU */
pt.cmd.cdw14 = 0; /* UUID Index */
pt.buf = payload;
pt.len = payload_size;
pt.is_read = 1;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "get log page request failed");
/* Convert data to host endian */
switch (log_page) {
case NVME_LOG_ERROR:
err_entry = (struct nvme_error_information_entry *)payload;
err_pages = payload_size / sizeof(struct nvme_error_information_entry);
for (i = 0; i < err_pages; i++)
nvme_error_information_entry_swapbytes(err_entry++);
break;
case NVME_LOG_HEALTH_INFORMATION:
nvme_health_information_page_swapbytes(
(struct nvme_health_information_page *)payload);
break;
case NVME_LOG_FIRMWARE_SLOT:
nvme_firmware_page_swapbytes(
(struct nvme_firmware_page *)payload);
break;
case NVME_LOG_CHANGED_NAMESPACE:
nvme_ns_list_swapbytes((struct nvme_ns_list *)payload);
break;
case NVME_LOG_COMMAND_EFFECT:
nvme_command_effects_page_swapbytes(
(struct nvme_command_effects_page *)payload);
break;
case NVME_LOG_RES_NOTIFICATION:
nvme_res_notification_page_swapbytes(
(struct nvme_res_notification_page *)payload);
break;
case NVME_LOG_SANITIZE_STATUS:
nvme_sanitize_status_page_swapbytes(
(struct nvme_sanitize_status_page *)payload);
break;
case INTEL_LOG_TEMP_STATS:
intel_log_temp_stats_swapbytes(
(struct intel_log_temp_stats *)payload);
break;
default:
break;
}
if (nvme_completion_is_error(&pt.cpl))
errx(1, "get log page request returned error");
}
static void
print_log_error(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size)
{
int i, nentries;
uint16_t status;
uint8_t p, sc, sct, m, dnr;
struct nvme_error_information_entry *entry = buf;
printf("Error Information Log\n");
printf("=====================\n");
if (entry->error_count == 0) {
printf("No error entries found\n");
return;
}
nentries = size/sizeof(struct nvme_error_information_entry);
for (i = 0; i < nentries; i++, entry++) {
if (entry->error_count == 0)
break;
status = entry->status;
p = NVME_STATUS_GET_P(status);
sc = NVME_STATUS_GET_SC(status);
sct = NVME_STATUS_GET_SCT(status);
m = NVME_STATUS_GET_M(status);
dnr = NVME_STATUS_GET_DNR(status);
printf("Entry %02d\n", i + 1);
printf("=========\n");
printf(" Error count: %ju\n", entry->error_count);
printf(" Submission queue ID: %u\n", entry->sqid);
printf(" Command ID: %u\n", entry->cid);
/* TODO: Export nvme_status_string structures from kernel? */
printf(" Status:\n");
printf(" Phase tag: %d\n", p);
printf(" Status code: %d\n", sc);
printf(" Status code type: %d\n", sct);
printf(" More: %d\n", m);
printf(" DNR: %d\n", dnr);
printf(" Error location: %u\n", entry->error_location);
printf(" LBA: %ju\n", entry->lba);
printf(" Namespace ID: %u\n", entry->nsid);
printf(" Vendor specific info: %u\n", entry->vendor_specific);
printf(" Transport type: %u\n", entry->trtype);
printf(" Command specific info:%ju\n", entry->csi);
printf(" Transport specific: %u\n", entry->ttsi);
}
}
void
print_temp(uint16_t t)
{
printf("%u K, %2.2f C, %3.2f F\n", t, (float)t - 273.15, (float)t * 9 / 5 - 459.67);
}
static void
print_log_health(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused)
{
struct nvme_health_information_page *health = buf;
char cbuf[UINT128_DIG + 1];
uint8_t warning;
int i;
warning = health->critical_warning;
printf("SMART/Health Information Log\n");
printf("============================\n");
printf("Critical Warning State: 0x%02x\n", warning);
printf(" Available spare: %d\n",
!!(warning & NVME_CRIT_WARN_ST_AVAILABLE_SPARE));
printf(" Temperature: %d\n",
!!(warning & NVME_CRIT_WARN_ST_TEMPERATURE));
printf(" Device reliability: %d\n",
!!(warning & NVME_CRIT_WARN_ST_DEVICE_RELIABILITY));
printf(" Read only: %d\n",
!!(warning & NVME_CRIT_WARN_ST_READ_ONLY));
printf(" Volatile memory backup: %d\n",
!!(warning & NVME_CRIT_WARN_ST_VOLATILE_MEMORY_BACKUP));
printf("Temperature: ");
print_temp(health->temperature);
printf("Available spare: %u\n",
health->available_spare);
printf("Available spare threshold: %u\n",
health->available_spare_threshold);
printf("Percentage used: %u\n",
health->percentage_used);
printf("Data units (512,000 byte) read: %s\n",
uint128_to_str(to128(health->data_units_read), cbuf, sizeof(cbuf)));
printf("Data units written: %s\n",
uint128_to_str(to128(health->data_units_written), cbuf, sizeof(cbuf)));
printf("Host read commands: %s\n",
uint128_to_str(to128(health->host_read_commands), cbuf, sizeof(cbuf)));
printf("Host write commands: %s\n",
uint128_to_str(to128(health->host_write_commands), cbuf, sizeof(cbuf)));
printf("Controller busy time (minutes): %s\n",
uint128_to_str(to128(health->controller_busy_time), cbuf, sizeof(cbuf)));
printf("Power cycles: %s\n",
uint128_to_str(to128(health->power_cycles), cbuf, sizeof(cbuf)));
printf("Power on hours: %s\n",
uint128_to_str(to128(health->power_on_hours), cbuf, sizeof(cbuf)));
printf("Unsafe shutdowns: %s\n",
uint128_to_str(to128(health->unsafe_shutdowns), cbuf, sizeof(cbuf)));
printf("Media errors: %s\n",
uint128_to_str(to128(health->media_errors), cbuf, sizeof(cbuf)));
printf("No. error info log entries: %s\n",
uint128_to_str(to128(health->num_error_info_log_entries), cbuf, sizeof(cbuf)));
printf("Warning Temp Composite Time: %d\n", health->warning_temp_time);
printf("Error Temp Composite Time: %d\n", health->error_temp_time);
for (i = 0; i < 8; i++) {
if (health->temp_sensor[i] == 0)
continue;
printf("Temperature Sensor %d: ", i + 1);
print_temp(health->temp_sensor[i]);
}
printf("Temperature 1 Transition Count: %d\n", health->tmt1tc);
printf("Temperature 2 Transition Count: %d\n", health->tmt2tc);
printf("Total Time For Temperature 1: %d\n", health->ttftmt1);
printf("Total Time For Temperature 2: %d\n", health->ttftmt2);
}
static void
print_log_firmware(const struct nvme_controller_data *cdata, void *buf, uint32_t size __unused)
{
int i, slots;
const char *status;
struct nvme_firmware_page *fw = buf;
uint8_t afi_slot;
uint16_t oacs_fw;
uint8_t fw_num_slots;
afi_slot = fw->afi >> NVME_FIRMWARE_PAGE_AFI_SLOT_SHIFT;
afi_slot &= NVME_FIRMWARE_PAGE_AFI_SLOT_MASK;
oacs_fw = (cdata->oacs >> NVME_CTRLR_DATA_OACS_FIRMWARE_SHIFT) &
NVME_CTRLR_DATA_OACS_FIRMWARE_MASK;
fw_num_slots = (cdata->frmw >> NVME_CTRLR_DATA_FRMW_NUM_SLOTS_SHIFT) &
NVME_CTRLR_DATA_FRMW_NUM_SLOTS_MASK;
printf("Firmware Slot Log\n");
printf("=================\n");
if (oacs_fw == 0)
slots = 1;
else
slots = MIN(fw_num_slots, MAX_FW_SLOTS);
for (i = 0; i < slots; i++) {
printf("Slot %d: ", i + 1);
if (afi_slot == i + 1)
status = " Active";
else
status = "Inactive";
if (fw->revision[i] == 0LLU)
printf("Empty\n");
else
if (isprint(*(char *)&fw->revision[i]))
printf("[%s] %.8s\n", status,
(char *)&fw->revision[i]);
else
printf("[%s] %016jx\n", status,
fw->revision[i]);
}
}
static void
print_log_ns(const struct nvme_controller_data *cdata __unused, void *buf,
uint32_t size __unused)
{
struct nvme_ns_list *nsl;
u_int i;
nsl = (struct nvme_ns_list *)buf;
printf("Changed Namespace List\n");
printf("======================\n");
for (i = 0; i < nitems(nsl->ns) && nsl->ns[i] != 0; i++) {
printf("%08x\n", nsl->ns[i]);
}
}
static void
print_log_command_effects(const struct nvme_controller_data *cdata __unused,
void *buf, uint32_t size __unused)
{
struct nvme_command_effects_page *ce;
u_int i;
uint32_t s;
ce = (struct nvme_command_effects_page *)buf;
printf("Commands Supported and Effects\n");
printf("==============================\n");
printf(" Command\tLBCC\tNCC\tNIC\tCCC\tCSE\tUUID\n");
for (i = 0; i < 255; i++) {
s = ce->acs[i];
if (((s >> NVME_CE_PAGE_CSUP_SHIFT) &
NVME_CE_PAGE_CSUP_MASK) == 0)
continue;
printf("Admin\t%02x\t%s\t%s\t%s\t%s\t%u\t%s\n", i,
((s >> NVME_CE_PAGE_LBCC_SHIFT) &
NVME_CE_PAGE_LBCC_MASK) ? "Yes" : "No",
((s >> NVME_CE_PAGE_NCC_SHIFT) &
NVME_CE_PAGE_NCC_MASK) ? "Yes" : "No",
((s >> NVME_CE_PAGE_NIC_SHIFT) &
NVME_CE_PAGE_NIC_MASK) ? "Yes" : "No",
((s >> NVME_CE_PAGE_CCC_SHIFT) &
NVME_CE_PAGE_CCC_MASK) ? "Yes" : "No",
((s >> NVME_CE_PAGE_CSE_SHIFT) &
NVME_CE_PAGE_CSE_MASK),
((s >> NVME_CE_PAGE_UUID_SHIFT) &
NVME_CE_PAGE_UUID_MASK) ? "Yes" : "No");
}
for (i = 0; i < 255; i++) {
s = ce->iocs[i];
if (((s >> NVME_CE_PAGE_CSUP_SHIFT) &
NVME_CE_PAGE_CSUP_MASK) == 0)
continue;
printf("I/O\t%02x\t%s\t%s\t%s\t%s\t%u\t%s\n", i,
((s >> NVME_CE_PAGE_LBCC_SHIFT) &
NVME_CE_PAGE_LBCC_MASK) ? "Yes" : "No",
((s >> NVME_CE_PAGE_NCC_SHIFT) &
NVME_CE_PAGE_NCC_MASK) ? "Yes" : "No",
((s >> NVME_CE_PAGE_NIC_SHIFT) &
NVME_CE_PAGE_NIC_MASK) ? "Yes" : "No",
((s >> NVME_CE_PAGE_CCC_SHIFT) &
NVME_CE_PAGE_CCC_MASK) ? "Yes" : "No",
((s >> NVME_CE_PAGE_CSE_SHIFT) &
NVME_CE_PAGE_CSE_MASK),
((s >> NVME_CE_PAGE_UUID_SHIFT) &
NVME_CE_PAGE_UUID_MASK) ? "Yes" : "No");
}
}
static void
print_log_res_notification(const struct nvme_controller_data *cdata __unused,
void *buf, uint32_t size __unused)
{
struct nvme_res_notification_page *rn;
rn = (struct nvme_res_notification_page *)buf;
printf("Reservation Notification\n");
printf("========================\n");
printf("Log Page Count: %ju\n", rn->log_page_count);
printf("Log Page Type: ");
switch (rn->log_page_type) {
case 0:
printf("Empty Log Page\n");
break;
case 1:
printf("Registration Preempted\n");
break;
case 2:
printf("Reservation Released\n");
break;
case 3:
printf("Reservation Preempted\n");
break;
default:
printf("Unknown %x\n", rn->log_page_type);
break;
};
printf("Number of Available Log Pages: %d\n", rn->available_log_pages);
printf("Namespace ID: 0x%x\n", rn->nsid);
}
static void
print_log_sanitize_status(const struct nvme_controller_data *cdata __unused,
void *buf, uint32_t size __unused)
{
struct nvme_sanitize_status_page *ss;
u_int p;
ss = (struct nvme_sanitize_status_page *)buf;
printf("Sanitize Status\n");
printf("===============\n");
printf("Sanitize Progress: %u%% (%u/65535)\n",
(ss->sprog * 100 + 32768) / 65536, ss->sprog);
printf("Sanitize Status: ");
switch ((ss->sstat >> NVME_SS_PAGE_SSTAT_STATUS_SHIFT) &
NVME_SS_PAGE_SSTAT_STATUS_MASK) {
case NVME_SS_PAGE_SSTAT_STATUS_NEVER:
printf("Never sanitized");
break;
case NVME_SS_PAGE_SSTAT_STATUS_COMPLETED:
printf("Completed");
break;
case NVME_SS_PAGE_SSTAT_STATUS_INPROG:
printf("In Progress");
break;
case NVME_SS_PAGE_SSTAT_STATUS_FAILED:
printf("Failed");
break;
case NVME_SS_PAGE_SSTAT_STATUS_COMPLETEDWD:
printf("Completed with deallocation");
break;
default:
printf("Unknown");
break;
}
p = (ss->sstat & NVME_SS_PAGE_SSTAT_PASSES_SHIFT) >>
NVME_SS_PAGE_SSTAT_PASSES_MASK;
if (p > 0)
printf(", %d passes", p);
if ((ss->sstat & NVME_SS_PAGE_SSTAT_GDE_SHIFT) >>
NVME_SS_PAGE_SSTAT_GDE_MASK)
printf(", Global Data Erased");
printf("\n");
printf("Sanitize Command Dword 10: 0x%x\n", ss->scdw10);
printf("Time For Overwrite: %u sec\n", ss->etfo);
printf("Time For Block Erase: %u sec\n", ss->etfbe);
printf("Time For Crypto Erase: %u sec\n", ss->etfce);
printf("Time For Overwrite No-Deallocate: %u sec\n", ss->etfownd);
printf("Time For Block Erase No-Deallocate: %u sec\n", ss->etfbewnd);
printf("Time For Crypto Erase No-Deallocate: %u sec\n", ss->etfcewnd);
}
/*
* Table of log page printer / sizing.
*
* Make sure you keep all the pages of one vendor together so -v help
* lists all the vendors pages.
*/
NVME_LOGPAGE(error,
NVME_LOG_ERROR, NULL, "Drive Error Log",
print_log_error, 0);
NVME_LOGPAGE(health,
NVME_LOG_HEALTH_INFORMATION, NULL, "Health/SMART Data",
print_log_health, sizeof(struct nvme_health_information_page));
NVME_LOGPAGE(fw,
NVME_LOG_FIRMWARE_SLOT, NULL, "Firmware Information",
print_log_firmware, sizeof(struct nvme_firmware_page));
NVME_LOGPAGE(ns,
NVME_LOG_CHANGED_NAMESPACE, NULL, "Changed Namespace List",
print_log_ns, sizeof(struct nvme_ns_list));
NVME_LOGPAGE(ce,
NVME_LOG_COMMAND_EFFECT, NULL, "Commands Supported and Effects",
print_log_command_effects, sizeof(struct nvme_command_effects_page));
NVME_LOGPAGE(dst,
NVME_LOG_DEVICE_SELF_TEST, NULL, "Device Self-test",
NULL, 564);
NVME_LOGPAGE(thi,
NVME_LOG_TELEMETRY_HOST_INITIATED, NULL, "Telemetry Host-Initiated",
NULL, DEFAULT_SIZE);
NVME_LOGPAGE(tci,
NVME_LOG_TELEMETRY_CONTROLLER_INITIATED, NULL, "Telemetry Controller-Initiated",
NULL, DEFAULT_SIZE);
NVME_LOGPAGE(egi,
NVME_LOG_ENDURANCE_GROUP_INFORMATION, NULL, "Endurance Group Information",
NULL, DEFAULT_SIZE);
NVME_LOGPAGE(plpns,
NVME_LOG_PREDICTABLE_LATENCY_PER_NVM_SET, NULL, "Predictable Latency Per NVM Set",
NULL, DEFAULT_SIZE);
NVME_LOGPAGE(ple,
NVME_LOG_PREDICTABLE_LATENCY_EVENT_AGGREGATE, NULL, "Predictable Latency Event Aggregate",
NULL, DEFAULT_SIZE);
NVME_LOGPAGE(ana,
NVME_LOG_ASYMMETRIC_NAMESPAVE_ACCESS, NULL, "Asymmetric Namespace Access",
NULL, DEFAULT_SIZE);
NVME_LOGPAGE(pel,
NVME_LOG_PERSISTENT_EVENT_LOG, NULL, "Persistent Event Log",
NULL, DEFAULT_SIZE);
NVME_LOGPAGE(lbasi,
NVME_LOG_LBA_STATUS_INFORMATION, NULL, "LBA Status Information",
NULL, DEFAULT_SIZE);
NVME_LOGPAGE(egea,
NVME_LOG_ENDURANCE_GROUP_EVENT_AGGREGATE, NULL, "Endurance Group Event Aggregate",
NULL, DEFAULT_SIZE);
NVME_LOGPAGE(res_notification,
NVME_LOG_RES_NOTIFICATION, NULL, "Reservation Notification",
print_log_res_notification, sizeof(struct nvme_res_notification_page));
NVME_LOGPAGE(sanitize_status,
NVME_LOG_SANITIZE_STATUS, NULL, "Sanitize Status",
print_log_sanitize_status, sizeof(struct nvme_sanitize_status_page));
static void
logpage_help(void)
{
const struct logpage_function *f;
const char *v;
fprintf(stderr, "\n");
fprintf(stderr, "%-8s %-10s %s\n", "Page", "Vendor","Page Name");
fprintf(stderr, "-------- ---------- ----------\n");
SLIST_FOREACH(f, &logpages, link) {
v = f->vendor == NULL ? "-" : f->vendor;
fprintf(stderr, "0x%02x %-10s %s\n", f->log_page, v, f->name);
}
exit(1);
}
static void
logpage(const struct cmd *f, int argc, char *argv[])
{
int fd;
char *path;
uint32_t nsid, size;
void *buf;
const struct logpage_function *lpf;
struct nvme_controller_data cdata;
print_fn_t print_fn;
uint8_t ns_smart;
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 (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);
}
open_dev(opt.dev, &fd, 1, 1);
get_nsid(fd, &path, &nsid);
if (nsid == 0) {
nsid = NVME_GLOBAL_NAMESPACE_TAG;
} else {
close(fd);
open_dev(path, &fd, 1, 1);
}
free(path);
read_controller_data(fd, &cdata);
ns_smart = (cdata.lpa >> NVME_CTRLR_DATA_LPA_NS_SMART_SHIFT) &
NVME_CTRLR_DATA_LPA_NS_SMART_MASK;
/*
* The log page attribtues indicate whether or not the controller
* supports the SMART/Health information log page on a per
* namespace basis.
*/
if (nsid != NVME_GLOBAL_NAMESPACE_TAG) {
if (opt.page != NVME_LOG_HEALTH_INFORMATION)
errx(1, "log page %d valid only at controller level",
opt.page);
if (ns_smart == 0)
errx(1,
"controller does not support per namespace "
"smart/health information");
}
print_fn = print_log_hex;
size = DEFAULT_SIZE;
if (opt.binary)
print_fn = print_bin;
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
* (print_hex). If there was a vendor specified by the user, and
* the page is vendor specific, don't match the print function
* unless the vendors match.
*/
SLIST_FOREACH(lpf, &logpages, link) {
if (lpf->vendor != NULL && opt.vendor != NULL &&
strcmp(lpf->vendor, opt.vendor) != 0)
continue;
if (opt.page != lpf->log_page)
continue;
if (lpf->print_fn != NULL)
print_fn = lpf->print_fn;
size = lpf->size;
break;
}
}
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, opt.page, nsid, opt.lsp, opt.lsi, opt.rae, buf, size);
print_fn(&cdata, buf, size);
close(fd);
exit(0);
}

View File

@ -0,0 +1,197 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2013 EMC Corp.
* All rights reserved.
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/ioccom.h>
#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/endian.h>
#include "nvmecontrol.h"
/*
* Intel specific log pages from
* http://www.intel.com/content/dam/www/public/us/en/documents/product-specifications/ssd-dc-p3700-spec.pdf
*
* Though the version as of this date has a typo for the size of log page 0xca,
* offset 147: it is only 1 byte, not 6.
*/
static void
print_intel_temp_stats(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused)
{
struct intel_log_temp_stats *temp = buf;
printf("Intel Temperature Log\n");
printf("=====================\n");
printf("Current: ");
print_temp(temp->current);
printf("Overtemp Last Flags %#jx\n", (uintmax_t)temp->overtemp_flag_last);
printf("Overtemp Lifetime Flags %#jx\n", (uintmax_t)temp->overtemp_flag_life);
printf("Max Temperature ");
print_temp(temp->max_temp);
printf("Min Temperature ");
print_temp(temp->min_temp);
printf("Max Operating Temperature ");
print_temp(temp->max_oper_temp);
printf("Min Operating Temperature ");
print_temp(temp->min_oper_temp);
printf("Estimated Temperature Offset: %ju C/K\n", (uintmax_t)temp->est_offset);
}
/*
* Format from Table 22, section 5.7 IO Command Latency Statistics.
* Read and write stats pages have identical encoding.
*/
static void
print_intel_read_write_lat_log(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused)
{
const char *walker = buf;
int i;
printf("Major: %d\n", le16dec(walker + 0));
printf("Minor: %d\n", le16dec(walker + 2));
for (i = 0; i < 32; i++)
printf("%4dus-%4dus: %ju\n", i * 32, (i + 1) * 32, (uintmax_t)le32dec(walker + 4 + i * 4));
for (i = 1; i < 32; i++)
printf("%4dms-%4dms: %ju\n", i, i + 1, (uintmax_t)le32dec(walker + 132 + i * 4));
for (i = 1; i < 32; i++)
printf("%4dms-%4dms: %ju\n", i * 32, (i + 1) * 32, (uintmax_t)le32dec(walker + 256 + i * 4));
}
static void
print_intel_read_lat_log(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size)
{
printf("Intel Read Latency Log\n");
printf("======================\n");
print_intel_read_write_lat_log(cdata, buf, size);
}
static void
print_intel_write_lat_log(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size)
{
printf("Intel Write Latency Log\n");
printf("=======================\n");
print_intel_read_write_lat_log(cdata, buf, size);
}
/*
* Table 19. 5.4 SMART Attributes. Others also implement this and some extra data not documented.
*/
void
print_intel_add_smart(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused)
{
uint8_t *walker = buf;
uint8_t *end = walker + 150;
const char *name;
uint64_t raw;
uint8_t normalized;
static struct kv_name kv[] =
{
{ 0xab, "Program Fail Count" },
{ 0xac, "Erase Fail Count" },
{ 0xad, "Wear Leveling Count" },
{ 0xb8, "End to End Error Count" },
{ 0xc7, "CRC Error Count" },
{ 0xe2, "Timed: Media Wear" },
{ 0xe3, "Timed: Host Read %" },
{ 0xe4, "Timed: Elapsed Time" },
{ 0xea, "Thermal Throttle Status" },
{ 0xf0, "Retry Buffer Overflows" },
{ 0xf3, "PLL Lock Loss Count" },
{ 0xf4, "NAND Bytes Written" },
{ 0xf5, "Host Bytes Written" },
};
printf("Additional SMART Data Log\n");
printf("=========================\n");
/*
* walker[0] = Key
* walker[1,2] = reserved
* walker[3] = Normalized Value
* walker[4] = reserved
* walker[5..10] = Little Endian Raw value
* (or other represenations)
* walker[11] = reserved
*/
while (walker < end) {
name = kv_lookup(kv, nitems(kv), *walker);
normalized = walker[3];
raw = le48dec(walker + 5);
switch (*walker){
case 0:
break;
case 0xad:
printf("%-32s: %3d min: %u max: %u ave: %u\n", name, normalized,
le16dec(walker + 5), le16dec(walker + 7), le16dec(walker + 9));
break;
case 0xe2:
printf("%-32s: %3d %.3f%%\n", name, normalized, raw / 1024.0);
break;
case 0xea:
printf("%-32s: %3d %d%% %d times\n", name, normalized, walker[5], le32dec(walker+6));
break;
default:
printf("%-32s: %3d %ju\n", name, normalized, (uintmax_t)raw);
break;
}
walker += 12;
}
}
NVME_LOGPAGE(intel_temp,
INTEL_LOG_TEMP_STATS, "intel", "Temperature Stats",
print_intel_temp_stats, sizeof(struct intel_log_temp_stats));
NVME_LOGPAGE(intel_rlat,
INTEL_LOG_READ_LAT_LOG, "intel", "Read Latencies",
print_intel_read_lat_log, DEFAULT_SIZE);
NVME_LOGPAGE(intel_wlat,
INTEL_LOG_WRITE_LAT_LOG, "intel", "Write Latencies",
print_intel_write_lat_log, DEFAULT_SIZE);
NVME_LOGPAGE(intel_smart,
INTEL_LOG_ADD_SMART, "intel", "Extra Health/SMART Data",
print_intel_add_smart, DEFAULT_SIZE);

View File

@ -0,0 +1,627 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* Copyright (c) 2017 Netflix, Inc
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/ioccom.h>
#include <sys/endian.h>
#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nvmecontrol.h"
/* Tables for command line parsing */
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_append_serial_name(int fd, char *buf, size_t len, const char *suffix)
{
struct nvme_controller_data cdata;
char sn[NVME_SERIAL_NUMBER_LENGTH + 1];
char *walker;
len -= strlen(buf);
buf += strlen(buf);
read_controller_data(fd, &cdata);
memcpy(sn, cdata.sn, NVME_SERIAL_NUMBER_LENGTH);
walker = sn + NVME_SERIAL_NUMBER_LENGTH - 1;
while (walker > sn && *walker == ' ')
walker--;
*++walker = '\0';
snprintf(buf, len, "%s%s.bin", sn, suffix);
}
static void
wdc_get_data(int fd, uint32_t opcode, uint32_t len, uint32_t off, uint32_t cmd,
uint8_t *buffer, size_t buflen)
{
struct nvme_pt_command pt;
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = opcode;
pt.cmd.cdw10 = htole32(len / sizeof(uint32_t)); /* - 1 like all the others ??? */
pt.cmd.cdw11 = htole32(off / sizeof(uint32_t));
pt.cmd.cdw12 = htole32(cmd);
pt.buf = buffer;
pt.len = buflen;
pt.is_read = 1;
// printf("opcode %#x cdw10(len) %#x cdw11(offset?) %#x cdw12(cmd/sub) %#x buflen %zd\n",
// (int)opcode, (int)cdw10, (int)cdw11, (int)cdw12, buflen);
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "wdc_get_data request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "wdc_get_data request returned error");
}
static void
wdc_do_dump(int fd, char *tmpl, const char *suffix, uint32_t opcode,
uint32_t cmd, int len_off)
{
int first;
int fd2;
uint8_t *buf;
uint32_t len, offset;
size_t resid;
wdc_append_serial_name(fd, tmpl, MAXPATHLEN, suffix);
/* XXX overwrite protection? */
fd2 = open(tmpl, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd2 < 0)
err(1, "open %s", tmpl);
buf = aligned_alloc(PAGE_SIZE, NVME_MAX_XFER_SIZE);
if (buf == NULL)
errx(1, "Can't get buffer to read dump");
offset = 0;
len = NVME_MAX_XFER_SIZE;
first = 1;
do {
resid = len > NVME_MAX_XFER_SIZE ? NVME_MAX_XFER_SIZE : len;
wdc_get_data(fd, opcode, resid, offset, cmd, buf, resid);
if (first) {
len = be32dec(buf + len_off);
if (len == 0)
errx(1, "No data for %s", suffix);
if (memcmp("E6LG", buf, 4) != 0)
printf("Expected header of E6LG, found '%4.4s' instead\n",
buf);
printf("Dumping %d bytes of version %d.%d log to %s\n", len,
buf[8], buf[9], tmpl);
/*
* Adjust amount to dump if total dump < 1MB,
* though it likely doesn't matter to the WDC
* analysis tools.
*/
if (resid > len)
resid = len;
first = 0;
}
if (write(fd2, buf, resid) != (ssize_t)resid)
err(1, "write");
offset += resid;
len -= resid;
} while (len > 0);
free(buf);
close(fd2);
}
static void
wdc_cap_diag(const struct cmd *f, int argc, char *argv[])
{
char tmpl[MAXPATHLEN];
int fd;
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);
exit(1);
}
static void
wdc(const struct cmd *nf __unused, int argc, char *argv[])
{
cmd_dispatch(argc, argv, &wdc_cmd);
}
/*
* HGST's 0xc1 page. This is a grab bag of additional data. Please see
* https://www.hgst.com/sites/default/files/resources/US_SN150_ProdManual.pdf
* https://www.hgst.com/sites/default/files/resources/US_SN100_ProdManual.pdf
* Appendix A for details
*/
typedef void (*subprint_fn_t)(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
struct subpage_print
{
uint16_t key;
subprint_fn_t fn;
};
static void print_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
static void print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
static void print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
static void print_hgst_info_self_test(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
static void print_hgst_info_background_scan(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
static void print_hgst_info_erase_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
static void print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
static void print_hgst_info_temp_history(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
static void print_hgst_info_ssd_perf(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
static void print_hgst_info_firmware_load(void *buf, uint16_t subtype, uint8_t res, uint32_t size);
static struct subpage_print hgst_subpage[] = {
{ 0x02, print_hgst_info_write_errors },
{ 0x03, print_hgst_info_read_errors },
{ 0x05, print_hgst_info_verify_errors },
{ 0x10, print_hgst_info_self_test },
{ 0x15, print_hgst_info_background_scan },
{ 0x30, print_hgst_info_erase_errors },
{ 0x31, print_hgst_info_erase_counts },
{ 0x32, print_hgst_info_temp_history },
{ 0x37, print_hgst_info_ssd_perf },
{ 0x38, print_hgst_info_firmware_load },
};
/* Print a subpage that is basically just key value pairs */
static void
print_hgst_info_subpage_gen(void *buf, uint16_t subtype __unused, uint32_t size,
const struct kv_name *kv, size_t kv_count)
{
uint8_t *wsp, *esp;
uint16_t ptype;
uint8_t plen;
uint64_t param;
int i;
wsp = buf;
esp = wsp + size;
while (wsp < esp) {
ptype = le16dec(wsp);
wsp += 2;
wsp++; /* Flags, just ignore */
plen = *wsp++;
param = 0;
for (i = 0; i < plen; i++)
param |= (uint64_t)*wsp++ << (i * 8);
printf(" %-30s: %jd\n", kv_lookup(kv, kv_count, ptype), (uintmax_t)param);
}
}
static void
print_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size)
{
static struct kv_name kv[] =
{
{ 0x0000, "Corrected Without Delay" },
{ 0x0001, "Corrected Maybe Delayed" },
{ 0x0002, "Re-Writes" },
{ 0x0003, "Errors Corrected" },
{ 0x0004, "Correct Algorithm Used" },
{ 0x0005, "Bytes Processed" },
{ 0x0006, "Uncorrected Errors" },
{ 0x8000, "Flash Write Commands" },
{ 0x8001, "HGST Special" },
};
printf("Write Errors Subpage:\n");
print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv));
}
static void
print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size)
{
static struct kv_name kv[] =
{
{ 0x0000, "Corrected Without Delay" },
{ 0x0001, "Corrected Maybe Delayed" },
{ 0x0002, "Re-Reads" },
{ 0x0003, "Errors Corrected" },
{ 0x0004, "Correct Algorithm Used" },
{ 0x0005, "Bytes Processed" },
{ 0x0006, "Uncorrected Errors" },
{ 0x8000, "Flash Read Commands" },
{ 0x8001, "XOR Recovered" },
{ 0x8002, "Total Corrected Bits" },
};
printf("Read Errors Subpage:\n");
print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv));
}
static void
print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size)
{
static struct kv_name kv[] =
{
{ 0x0000, "Corrected Without Delay" },
{ 0x0001, "Corrected Maybe Delayed" },
{ 0x0002, "Re-Reads" },
{ 0x0003, "Errors Corrected" },
{ 0x0004, "Correct Algorithm Used" },
{ 0x0005, "Bytes Processed" },
{ 0x0006, "Uncorrected Errors" },
{ 0x8000, "Commands Processed" },
};
printf("Verify Errors Subpage:\n");
print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv));
}
static void
print_hgst_info_self_test(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size)
{
size_t i;
uint8_t *walker = buf;
uint16_t code, hrs;
uint32_t lba;
printf("Self Test Subpage:\n");
for (i = 0; i < size / 20; i++) { /* Each entry is 20 bytes */
code = le16dec(walker);
walker += 2;
walker++; /* Ignore fixed flags */
if (*walker == 0) /* Last entry is zero length */
break;
if (*walker++ != 0x10) {
printf("Bad length for self test report\n");
return;
}
printf(" %-30s: %d\n", "Recent Test", code);
printf(" %-28s: %#x\n", "Self-Test Results", *walker & 0xf);
printf(" %-28s: %#x\n", "Self-Test Code", (*walker >> 5) & 0x7);
walker++;
printf(" %-28s: %#x\n", "Self-Test Number", *walker++);
hrs = le16dec(walker);
walker += 2;
lba = le32dec(walker);
walker += 4;
printf(" %-28s: %u\n", "Total Power On Hrs", hrs);
printf(" %-28s: %#jx (%jd)\n", "LBA", (uintmax_t)lba, (uintmax_t)lba);
printf(" %-28s: %#x\n", "Sense Key", *walker++ & 0xf);
printf(" %-28s: %#x\n", "Additional Sense Code", *walker++);
printf(" %-28s: %#x\n", "Additional Sense Qualifier", *walker++);
printf(" %-28s: %#x\n", "Vendor Specific Detail", *walker++);
}
}
static void
print_hgst_info_background_scan(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size)
{
uint8_t *walker = buf;
uint8_t status;
uint16_t code, nscan, progress;
uint32_t pom, nand;
printf("Background Media Scan Subpage:\n");
/* Decode the header */
code = le16dec(walker);
walker += 2;
walker++; /* Ignore fixed flags */
if (*walker++ != 0x10) {
printf("Bad length for background scan header\n");
return;
}
if (code != 0) {
printf("Expceted code 0, found code %#x\n", code);
return;
}
pom = le32dec(walker);
walker += 4;
walker++; /* Reserved */
status = *walker++;
nscan = le16dec(walker);
walker += 2;
progress = le16dec(walker);
walker += 2;
walker += 6; /* Reserved */
printf(" %-30s: %d\n", "Power On Minutes", pom);
printf(" %-30s: %x (%s)\n", "BMS Status", status,
status == 0 ? "idle" : (status == 1 ? "active" : (status == 8 ? "suspended" : "unknown")));
printf(" %-30s: %d\n", "Number of BMS", nscan);
printf(" %-30s: %d\n", "Progress Current BMS", progress);
/* Report retirements */
if (walker - (uint8_t *)buf != 20) {
printf("Coding error, offset not 20\n");
return;
}
size -= 20;
printf(" %-30s: %d\n", "BMS retirements", size / 0x18);
while (size > 0) {
code = le16dec(walker);
walker += 2;
walker++;
if (*walker++ != 0x14) {
printf("Bad length parameter\n");
return;
}
pom = le32dec(walker);
walker += 4;
/*
* Spec sheet says the following are hard coded, if true, just
* print the NAND retirement.
*/
if (walker[0] == 0x41 &&
walker[1] == 0x0b &&
walker[2] == 0x01 &&
walker[3] == 0x00 &&
walker[4] == 0x00 &&
walker[5] == 0x00 &&
walker[6] == 0x00 &&
walker[7] == 0x00) {
walker += 8;
walker += 4; /* Skip reserved */
nand = le32dec(walker);
walker += 4;
printf(" %-30s: %d\n", "Retirement number", code);
printf(" %-28s: %#x\n", "NAND (C/T)BBBPPP", nand);
} else {
printf("Parameter %#x entry corrupt\n", code);
walker += 16;
}
}
}
static void
print_hgst_info_erase_errors(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size)
{
static struct kv_name kv[] =
{
{ 0x0000, "Corrected Without Delay" },
{ 0x0001, "Corrected Maybe Delayed" },
{ 0x0002, "Re-Erase" },
{ 0x0003, "Errors Corrected" },
{ 0x0004, "Correct Algorithm Used" },
{ 0x0005, "Bytes Processed" },
{ 0x0006, "Uncorrected Errors" },
{ 0x8000, "Flash Erase Commands" },
{ 0x8001, "Mfg Defect Count" },
{ 0x8002, "Grown Defect Count" },
{ 0x8003, "Erase Count -- User" },
{ 0x8004, "Erase Count -- System" },
};
printf("Erase Errors Subpage:\n");
print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv));
}
static void
print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size)
{
/* My drive doesn't export this -- so not coding up */
printf("XXX: Erase counts subpage: %p, %#x %d\n", buf, subtype, size);
}
static void
print_hgst_info_temp_history(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused)
{
uint8_t *walker = buf;
uint32_t min;
printf("Temperature History:\n");
printf(" %-30s: %d C\n", "Current Temperature", *walker++);
printf(" %-30s: %d C\n", "Reference Temperature", *walker++);
printf(" %-30s: %d C\n", "Maximum Temperature", *walker++);
printf(" %-30s: %d C\n", "Minimum Temperature", *walker++);
min = le32dec(walker);
walker += 4;
printf(" %-30s: %d:%02d:00\n", "Max Temperature Time", min / 60, min % 60);
min = le32dec(walker);
walker += 4;
printf(" %-30s: %d:%02d:00\n", "Over Temperature Duration", min / 60, min % 60);
min = le32dec(walker);
walker += 4;
printf(" %-30s: %d:%02d:00\n", "Min Temperature Time", min / 60, min % 60);
}
static void
print_hgst_info_ssd_perf(void *buf, uint16_t subtype __unused, uint8_t res, uint32_t size __unused)
{
uint8_t *walker = buf;
uint64_t val;
printf("SSD Performance Subpage Type %d:\n", res);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "Host Read Commands", val);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "Host Read Blocks", val);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "Host Cache Read Hits Commands", val);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "Host Cache Read Hits Blocks", val);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "Host Read Commands Stalled", val);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "Host Write Commands", val);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "Host Write Blocks", val);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "Host Write Odd Start Commands", val);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "Host Write Odd End Commands", val);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "Host Write Commands Stalled", val);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "NAND Read Commands", val);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "NAND Read Blocks", val);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "NAND Write Commands", val);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "NAND Write Blocks", val);
val = le64dec(walker);
walker += 8;
printf(" %-30s: %ju\n", "NAND Read Before Writes", val);
}
static void
print_hgst_info_firmware_load(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused)
{
uint8_t *walker = buf;
printf("Firmware Load Subpage:\n");
printf(" %-30s: %d\n", "Firmware Downloads", le32dec(walker));
}
static void
kv_indirect(void *buf, uint32_t subtype, uint8_t res, uint32_t size, struct subpage_print *sp, size_t nsp)
{
size_t i;
for (i = 0; i < nsp; i++, sp++) {
if (sp->key == subtype) {
sp->fn(buf, subtype, res, size);
return;
}
}
printf("No handler for page type %x\n", subtype);
}
static void
print_hgst_info_log(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused)
{
uint8_t *walker, *end, *subpage;
int pages;
uint16_t len;
uint8_t subtype, res;
printf("HGST Extra Info Log\n");
printf("===================\n");
walker = buf;
pages = *walker++;
walker++;
len = le16dec(walker);
walker += 2;
end = walker + len; /* Length is exclusive of this header */
while (walker < end) {
subpage = walker + 4;
subtype = *walker++ & 0x3f; /* subtype */
res = *walker++; /* Reserved */
len = le16dec(walker);
walker += len + 2; /* Length, not incl header */
if (walker > end) {
printf("Ooops! Off the end of the list\n");
break;
}
kv_indirect(subpage, subtype, res, len, hgst_subpage, nitems(hgst_subpage));
}
}
NVME_LOGPAGE(hgst_info,
HGST_INFO_LOG, "hgst", "Detailed Health/SMART",
print_hgst_info_log, DEFAULT_SIZE);
NVME_LOGPAGE(wdc_info,
HGST_INFO_LOG, "wdc", "Detailed Health/SMART",
print_hgst_info_log, DEFAULT_SIZE);

View File

@ -0,0 +1,61 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* Copyright (c) 2017 Netflix, Inc
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/endian.h>
#include "nvmecontrol.h"
char *
uint128_to_str(uint128_t u, char *buf, size_t buflen)
{
char *end = buf + buflen - 1;
*end-- = '\0';
if (u == 0)
*end-- = '0';
while (u && end >= buf) {
*end-- = u % 10 + '0';
u /= 10;
}
end++;
if (u != 0)
return NULL;
return end;
}
/* "Missing" from endian.h */
uint64_t
le48dec(const void *pp)
{
uint8_t const *p = (uint8_t const *)pp;
return (((uint64_t)le16dec(p + 4) << 32) | le32dec(p));
}

View File

@ -0,0 +1,886 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2017 Netflix, Inc
* Copyright (C) 2018-2019 Alexander Motin <mav@FreeBSD.org>
*
* 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,
* without modification, immediately at the beginning of the file.
* 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 ``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 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 <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nvmecontrol.h"
/* Tables for command line parsing */
static cmd_fn_t ns;
static cmd_fn_t nsactive;
static cmd_fn_t nsallocated;
static cmd_fn_t nscontrollers;
static cmd_fn_t nscreate;
static cmd_fn_t nsdelete;
static cmd_fn_t nsattach;
static cmd_fn_t nsdetach;
static cmd_fn_t nsattached;
static cmd_fn_t nsidentify;
#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 management commands",
.ctx_size = 0,
.opts = NULL,
.args = NULL,
};
CMD_COMMAND(ns_cmd);
static struct active_options {
const char *dev;
} active_opt = {
.dev = NULL,
};
static const struct args active_args[] = {
{ arg_string, &active_opt.dev, "controller-id" },
{ arg_none, NULL, NULL },
};
static struct cmd active_cmd = {
.name = "active",
.fn = nsactive,
.descr = "List active (attached) namespaces",
.ctx_size = sizeof(active_opt),
.opts = NULL,
.args = active_args,
};
CMD_SUBCOMMAND(ns_cmd, active_cmd);
static struct cmd allocated_cmd = {
.name = "allocated",
.fn = nsallocated,
.descr = "List allocated (created) namespaces",
.ctx_size = sizeof(active_opt),
.opts = NULL,
.args = active_args,
};
CMD_SUBCOMMAND(ns_cmd, allocated_cmd);
static struct controllers_options {
const char *dev;
} controllers_opt = {
.dev = NULL,
};
static const struct args controllers_args[] = {
{ arg_string, &controllers_opt.dev, "controller-id" },
{ arg_none, NULL, NULL },
};
static struct cmd controllers_cmd = {
.name = "controllers",
.fn = nscontrollers,
.descr = "List all controllers in NVM subsystem",
.ctx_size = sizeof(controllers_opt),
.opts = NULL,
.args = controllers_args,
};
CMD_SUBCOMMAND(ns_cmd, controllers_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 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 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, ctrlrid,
"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 controller to a namespace",
.ctx_size = sizeof(attach_opt),
.opts = attach_opts,
.args = attach_args,
};
CMD_SUBCOMMAND(ns_cmd, attach_cmd);
static struct attached_options {
uint32_t nsid;
const char *dev;
} attached_opt = {
.nsid = NONE,
.dev = NULL,
};
static const struct opts attached_opts[] = {
OPT("namespace-id", 'n', arg_uint32, attached_opt, nsid,
"The namespace ID to request attached controllers"),
OPT_END
};
static const struct args attached_args[] = {
{ arg_string, &attached_opt.dev, "controller-id" },
{ arg_none, NULL, NULL },
};
static struct cmd attached_cmd = {
.name = "attached",
.fn = nsattached,
.descr = "List controllers attached to a namespace",
.ctx_size = sizeof(attached_opt),
.opts = attached_opts,
.args = attached_args,
};
CMD_SUBCOMMAND(ns_cmd, attached_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, ctrlrid,
"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 controller from a namespace",
.ctx_size = sizeof(detach_opt),
.opts = detach_opts,
.args = detach_args,
};
CMD_SUBCOMMAND(ns_cmd, detach_cmd);
static struct identify_options {
bool hex;
bool verbose;
const char *dev;
uint32_t nsid;
} identify_opt = {
.hex = false,
.verbose = false,
.dev = NULL,
.nsid = NONE,
};
static const struct opts identify_opts[] = {
OPT("hex", 'x', arg_none, identify_opt, hex,
"Print identiy information in hex"),
OPT("verbose", 'v', arg_none, identify_opt, verbose,
"More verbosity: print entire identify table"),
OPT("nsid", 'n', arg_uint32, identify_opt, nsid,
"The namespace ID to print IDENTIFY for"),
{ NULL, 0, arg_none, NULL, NULL }
};
static const struct args identify_args[] = {
{ arg_string, &identify_opt.dev, "controller-id" },
{ arg_none, NULL, NULL },
};
static struct cmd identify_cmd = {
.name = "identify",
.fn = nsidentify,
.descr = "Print IDENTIFY for allocated namespace",
.ctx_size = sizeof(identify_opt),
.opts = identify_opts,
.args = identify_args,
};
CMD_SUBCOMMAND(ns_cmd, identify_cmd);
/* handles NVME_OPC_NAMESPACE_MANAGEMENT and ATTACHMENT admin cmds */
struct ns_result_str {
uint16_t res;
const char * str;
};
static struct ns_result_str ns_result[] = {
{ 0x2, "Invalid Field"},
{ 0xa, "Invalid Format"},
{ 0xb, "Invalid Namespace or format"},
{ 0x15, "Namespace insufficent capacity"},
{ 0x16, "Namespace ID unavaliable"},
{ 0x18, "Namespace already attached"},
{ 0x19, "Namespace is private"},
{ 0x1a, "Namespace is not attached"},
{ 0x1b, "Thin provisioning not supported"},
{ 0x1c, "Controller list invalid"},
{ 0x24, "ANA Group Identifier Invalid"},
{ 0x25, "ANA Attach Failed"},
{ 0xFFFF, "Unknown"}
};
static const char *
get_res_str(uint16_t res)
{
struct ns_result_str *t = ns_result;
while (t->res != 0xFFFF) {
if (t->res == res)
return (t->str);
t++;
}
return t->str;
}
static void
nsactive(const struct cmd *f, int argc, char *argv[])
{
struct nvme_pt_command pt;
int fd, i;
uint32_t list[1024];
if (arg_parse(argc, argv, f))
return;
open_dev(active_opt.dev, &fd, 1, 1);
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_IDENTIFY;
pt.cmd.nsid = htole32(0);
pt.cmd.cdw10 = htole32(0x02);
pt.buf = list;
pt.len = sizeof(list);
pt.is_read = 1;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "identify request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "identify request returned error");
printf("Active namespaces:\n");
for (i = 0; list[i] != 0; i++)
printf("%10d\n", le32toh(list[i]));
exit(0);
}
static void
nsallocated(const struct cmd *f, int argc, char *argv[])
{
struct nvme_pt_command pt;
struct nvme_controller_data cd;
int fd, i;
uint32_t list[1024];
if (arg_parse(argc, argv, f))
return;
open_dev(active_opt.dev, &fd, 1, 1);
read_controller_data(fd, &cd);
/* Check that controller can execute this command. */
if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) &
NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0)
errx(1, "controller does not support namespace management");
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_IDENTIFY;
pt.cmd.nsid = htole32(0);
pt.cmd.cdw10 = htole32(0x10);
pt.buf = list;
pt.len = sizeof(list);
pt.is_read = 1;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "identify request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "identify request returned error");
printf("Allocated namespaces:\n");
for (i = 0; list[i] != 0; i++)
printf("%10d\n", le32toh(list[i]));
exit(0);
}
static void
nscontrollers(const struct cmd *f, int argc, char *argv[])
{
struct nvme_pt_command pt;
struct nvme_controller_data cd;
int fd, i, n;
uint16_t clist[2048];
if (arg_parse(argc, argv, f))
return;
open_dev(controllers_opt.dev, &fd, 1, 1);
read_controller_data(fd, &cd);
/* Check that controller can execute this command. */
if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) &
NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0)
errx(1, "controller does not support namespace management");
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_IDENTIFY;
pt.cmd.cdw10 = htole32(0x13);
pt.buf = clist;
pt.len = sizeof(clist);
pt.is_read = 1;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "identify request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "identify request returned error");
n = le16toh(clist[0]);
printf("NVM subsystem includes %d controller(s):\n", n);
for (i = 0; i < n; i++)
printf(" 0x%04x\n", le16toh(clist[i + 1]));
exit(0);
}
/*
* NS MGMT Command specific status values:
* 0xa = Invalid Format
* 0x15 = Namespace Insuffience capacity
* 0x16 = Namespace ID unavailable (number namespaces exceeded)
* 0xb = Thin Provisioning Not supported
*/
static void
nscreate(const struct cmd *f, int argc, char *argv[])
{
struct nvme_pt_command pt;
struct nvme_controller_data cd;
struct nvme_namespace_data nsdata;
int fd, result;
if (arg_parse(argc, argv, f))
return;
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(create_opt.dev, &fd, 1, 1);
read_controller_data(fd, &cd);
/* Check that controller can execute this command. */
if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) &
NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0)
errx(1, "controller does not support namespace management");
/* Allow namespaces sharing if Multi-Path I/O is supported. */
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 = 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));
pt.cmd.opc = NVME_OPC_NAMESPACE_MANAGEMENT;
pt.cmd.cdw10 = htole32(0); /* create */
pt.buf = &nsdata;
pt.len = sizeof(struct nvme_namespace_data);
pt.is_read = 0; /* passthrough writes data to ctrlr */
if ((result = ioctl(fd, NVME_PASSTHROUGH_CMD, &pt)) < 0)
errx(1, "ioctl request to %s failed: %d", argv[optind], result);
if (nvme_completion_is_error(&pt.cpl)) {
errx(1, "namespace creation failed: %s",
get_res_str((pt.cpl.status >> NVME_STATUS_SC_SHIFT) &
NVME_STATUS_SC_MASK));
}
printf("namespace %d created\n", pt.cpl.cdw0);
exit(0);
}
static void
nsdelete(const struct cmd *f, int argc, char *argv[])
{
struct nvme_pt_command pt;
struct nvme_controller_data cd;
int fd, result;
char buf[2];
if (arg_parse(argc, argv, f))
return;
if (delete_opt.nsid == NONE) {
fprintf(stderr,
"No NSID specified");
arg_help(argc, argv, f);
}
open_dev(delete_opt.dev, &fd, 1, 1);
read_controller_data(fd, &cd);
/* Check that controller can execute this command. */
if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) &
NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0)
errx(1, "controller does not support namespace management");
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_NAMESPACE_MANAGEMENT;
pt.cmd.cdw10 = htole32(1); /* delete */
pt.buf = buf;
pt.len = sizeof(buf);
pt.is_read = 1;
pt.cmd.nsid = delete_opt.nsid;
if ((result = ioctl(fd, NVME_PASSTHROUGH_CMD, &pt)) < 0)
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", delete_opt.nsid);
exit(0);
}
/*
* Attach and Detach use Dword 10, and a controller list (section 4.9)
* This struct is 4096 bytes in size.
* 0h = attach
* 1h = detach
*
* Result values for both attach/detach:
*
* Completion 18h = Already attached
* 19h = NS is private and already attached to a controller
* 1Ah = Not attached, request could not be completed
* 1Ch = Controller list invalid.
*
* 0x2 Invalid Field can occur if ctrlrid d.n.e in system.
*/
static void
nsattach(const struct cmd *f, int argc, char *argv[])
{
struct nvme_pt_command pt;
struct nvme_controller_data cd;
int fd, result;
uint16_t clist[2048];
if (arg_parse(argc, argv, f))
return;
if (attach_opt.nsid == NONE) {
fprintf(stderr, "No valid NSID specified\n");
arg_help(argc, argv, f);
}
open_dev(attach_opt.dev, &fd, 1, 1);
read_controller_data(fd, &cd);
/* Check that controller can execute this command. */
if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) &
NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0)
errx(1, "controller does not support namespace management");
if (attach_opt.ctrlrid == NONE) {
/* Get full list of controllers to attach to. */
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_IDENTIFY;
pt.cmd.cdw10 = htole32(0x13);
pt.buf = clist;
pt.len = sizeof(clist);
pt.is_read = 1;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "identify request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "identify request returned error");
} else {
/* By default attach to this controller. */
if (attach_opt.ctrlrid == NONE - 1)
attach_opt.ctrlrid = cd.ctrlr_id;
memset(&clist, 0, sizeof(clist));
clist[0] = htole16(1);
clist[1] = htole16(attach_opt.ctrlrid);
}
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_NAMESPACE_ATTACHMENT;
pt.cmd.cdw10 = htole32(0); /* attach */
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", 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", attach_opt.nsid);
exit(0);
}
static void
nsdetach(const struct cmd *f, int argc, char *argv[])
{
struct nvme_pt_command pt;
struct nvme_controller_data cd;
int fd, result;
uint16_t clist[2048];
if (arg_parse(argc, argv, f))
return;
if (detach_opt.nsid == NONE) {
fprintf(stderr, "No valid NSID specified\n");
arg_help(argc, argv, f);
}
open_dev(detach_opt.dev, &fd, 1, 1);
read_controller_data(fd, &cd);
/* Check that controller can execute this command. */
if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) &
NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0)
errx(1, "controller does not support namespace management");
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(detach_opt.nsid);
pt.cmd.cdw10 = htole32(0x12);
pt.buf = clist;
pt.len = sizeof(clist);
pt.is_read = 1;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "identify request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "identify request returned error");
if (clist[0] == 0) {
detach_opt.ctrlrid = cd.ctrlr_id;
memset(&clist, 0, sizeof(clist));
clist[0] = htole16(1);
clist[1] = htole16(detach_opt.ctrlrid);
}
} else {
/* By default detach from this controller. */
if (detach_opt.ctrlrid == NONE - 1)
detach_opt.ctrlrid = cd.ctrlr_id;
memset(&clist, 0, sizeof(clist));
clist[0] = htole16(1);
clist[1] = htole16(detach_opt.ctrlrid);
}
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_NAMESPACE_ATTACHMENT;
pt.cmd.cdw10 = htole32(1); /* detach */
pt.cmd.nsid = detach_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);
if (nvme_completion_is_error(&pt.cpl)) {
errx(1, "namespace detach failed: %s",
get_res_str((pt.cpl.status >> NVME_STATUS_SC_SHIFT) &
NVME_STATUS_SC_MASK));
}
printf("namespace %d detached\n", detach_opt.nsid);
exit(0);
}
static void
nsattached(const struct cmd *f, int argc, char *argv[])
{
struct nvme_pt_command pt;
struct nvme_controller_data cd;
int fd, i, n;
uint16_t clist[2048];
if (arg_parse(argc, argv, f))
return;
if (attached_opt.nsid == NONE) {
fprintf(stderr, "No valid NSID specified\n");
arg_help(argc, argv, f);
}
open_dev(attached_opt.dev, &fd, 1, 1);
read_controller_data(fd, &cd);
/* Check that controller can execute this command. */
if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) &
NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0)
errx(1, "controller does not support namespace management");
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_IDENTIFY;
pt.cmd.nsid = htole32(attached_opt.nsid);
pt.cmd.cdw10 = htole32(0x12);
pt.buf = clist;
pt.len = sizeof(clist);
pt.is_read = 1;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "identify request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "identify request returned error");
n = le16toh(clist[0]);
printf("Attached %d controller(s):\n", n);
for (i = 0; i < n; i++)
printf(" 0x%04x\n", le16toh(clist[i + 1]));
exit(0);
}
static void
nsidentify(const struct cmd *f, int argc, char *argv[])
{
struct nvme_pt_command pt;
struct nvme_controller_data cd;
struct nvme_namespace_data nsdata;
uint8_t *data;
int fd;
u_int i;
if (arg_parse(argc, argv, f))
return;
if (identify_opt.nsid == NONE) {
fprintf(stderr, "No valid NSID specified\n");
arg_help(argc, argv, f);
}
open_dev(identify_opt.dev, &fd, 1, 1);
read_controller_data(fd, &cd);
/* Check that controller can execute this command. */
if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) &
NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0)
errx(1, "controller does not support namespace management");
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_IDENTIFY;
pt.cmd.nsid = htole32(identify_opt.nsid);
pt.cmd.cdw10 = htole32(0x11);
pt.buf = &nsdata;
pt.len = sizeof(nsdata);
pt.is_read = 1;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "identify request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "identify request returned error");
close(fd);
data = (uint8_t *)&nsdata;
for (i = 0; i < sizeof(nsdata); i++) {
if (data[i] != 0)
break;
}
if (i == sizeof(nsdata))
errx(1, "namespace %d is not allocated", identify_opt.nsid);
/* Convert data to host endian */
nvme_namespace_data_swapbytes(&nsdata);
if (identify_opt.hex) {
i = sizeof(struct nvme_namespace_data);
if (!identify_opt.verbose) {
for (; i > 384; i--) {
if (data[i - 1] != 0)
break;
}
}
print_hex(&nsdata, i);
exit(0);
}
print_namespace(&nsdata);
exit(0);
}
static void
ns(const struct cmd *nf __unused, int argc, char *argv[])
{
cmd_dispatch(argc, argv, &ns_cmd);
}

View File

@ -0,0 +1,82 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (C) 2019 Alexander Motin <mav@FreeBSD.org>
*
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "nvmecontrol.h"
#include "comnd.h"
/* Tables for command line parsing */
static cmd_fn_t gnsid;
static struct nsid_options {
const char *dev;
} nsid_opt = {
.dev = NULL,
};
static const struct args nsid_args[] = {
{ arg_string, &nsid_opt.dev, "namespace-id" },
{ arg_none, NULL, NULL },
};
static struct cmd nsid_cmd = {
.name = "nsid",
.fn = gnsid,
.descr = "Get controller and NSID for namespace",
.ctx_size = sizeof(nsid_opt),
.opts = NULL,
.args = nsid_args,
};
CMD_COMMAND(nsid_cmd);
static void
gnsid(const struct cmd *f, int argc, char *argv[])
{
char *path;
int fd;
uint32_t nsid;
arg_parse(argc, argv, f);
open_dev(nsid_opt.dev, &fd, 1, 1);
get_nsid(fd, &path, &nsid);
close(fd);
printf("%s\t%u\n", path, nsid);
free(path);
}

View File

@ -0,0 +1,190 @@
#include <machine/rtems-bsd-kernel-space.h>
/*-
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/ioccom.h>
#include <sys/stat.h>
#include <ctype.h>
#include <dlfcn.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nvmecontrol.h"
static void
print_bytes(void *data, uint32_t length)
{
uint32_t i, j;
uint8_t *p, *end;
end = (uint8_t *)data + length;
for (i = 0; i < length; i++) {
p = (uint8_t *)data + (i*16);
printf("%03x: ", i*16);
for (j = 0; j < 16 && p < end; j++)
printf("%02x ", *p++);
if (p >= end)
break;
printf("\n");
}
printf("\n");
}
static void
print_dwords(void *data, uint32_t length)
{
uint32_t *p;
uint32_t i, j;
p = (uint32_t *)data;
length /= sizeof(uint32_t);
for (i = 0; i < length; i+=8) {
printf("%03x: ", i*4);
for (j = 0; j < 8; j++)
printf("%08x ", p[i+j]);
printf("\n");
}
printf("\n");
}
void
print_hex(void *data, uint32_t length)
{
if (length >= sizeof(uint32_t) || length % sizeof(uint32_t) == 0)
print_dwords(data, length);
else
print_bytes(data, length);
}
void
read_controller_data(int fd, struct nvme_controller_data *cdata)
{
struct nvme_pt_command pt;
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_IDENTIFY;
pt.cmd.cdw10 = htole32(1);
pt.buf = cdata;
pt.len = sizeof(*cdata);
pt.is_read = 1;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "identify request failed");
/* Convert data to host endian */
nvme_controller_data_swapbytes(cdata);
if (nvme_completion_is_error(&pt.cpl))
errx(1, "identify request returned error");
}
void
read_namespace_data(int fd, uint32_t nsid, struct nvme_namespace_data *nsdata)
{
struct nvme_pt_command pt;
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_IDENTIFY;
pt.cmd.nsid = htole32(nsid);
pt.cmd.cdw10 = htole32(0);
pt.buf = nsdata;
pt.len = sizeof(*nsdata);
pt.is_read = 1;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "identify request failed");
/* Convert data to host endian */
nvme_namespace_data_swapbytes(nsdata);
if (nvme_completion_is_error(&pt.cpl))
errx(1, "identify request returned error");
}
int
open_dev(const char *str, int *fd, int show_error, int exit_on_error)
{
char full_path[64];
snprintf(full_path, sizeof(full_path), _PATH_DEV"%s", str);
*fd = open(full_path, O_RDWR);
if (*fd < 0) {
if (show_error)
warn("could not open %s", full_path);
if (exit_on_error)
exit(1);
else
return (errno);
}
return (0);
}
void
get_nsid(int fd, char **ctrlr_str, uint32_t *nsid)
{
struct nvme_get_nsid gnsid;
if (ioctl(fd, NVME_GET_NSID, &gnsid) < 0)
err(1, "NVME_GET_NSID ioctl failed");
if (ctrlr_str != NULL)
*ctrlr_str = strndup(gnsid.cdev, sizeof(gnsid.cdev));
if (nsid != NULL)
*nsid = gnsid.nsid;
}
int
main(int argc, char *argv[])
{
cmd_init();
cmd_load_dir("/lib/nvmecontrol", NULL, NULL);
cmd_load_dir("/usr/local/lib/nvmecontrol", NULL, NULL);
cmd_dispatch(argc, argv, NULL);
return (0);
}

View File

@ -0,0 +1,104 @@
/*-
* 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.
*
* $FreeBSD$
*/
#ifndef __NVMECONTROL_H__
#define __NVMECONTROL_H__
#include <dev/nvme/nvme.h>
#include "comnd.h"
typedef void (*print_fn_t)(const struct nvme_controller_data *cdata, void *buf, uint32_t size);
struct logpage_function {
SLIST_ENTRY(logpage_function) link;
uint8_t log_page;
const char *vendor;
const char *name;
print_fn_t print_fn;
size_t size;
};
#define NVME_LOGPAGE(unique, lp, vend, nam, fn, sz) \
static struct logpage_function unique ## _lpf = { \
.log_page = lp, \
.vendor = vend, \
.name = nam, \
.print_fn = fn, \
.size = sz, \
} ; \
static void logpage_reg_##unique(void) __attribute__((constructor)); \
static void logpage_reg_##unique(void) { logpage_register(&unique##_lpf); }
#define DEFAULT_SIZE (4096)
struct kv_name {
uint32_t key;
const char *name;
};
const char *kv_lookup(const struct kv_name *kv, size_t kv_count, uint32_t key);
void logpage_register(struct logpage_function *p);
#define NVME_CTRLR_PREFIX "nvme"
#define NVME_NS_PREFIX "ns"
int open_dev(const char *str, int *fd, int show_error, int exit_on_error);
void get_nsid(int fd, char **ctrlr_str, uint32_t *nsid);
void read_controller_data(int fd, struct nvme_controller_data *cdata);
void read_namespace_data(int fd, uint32_t nsid, struct nvme_namespace_data *nsdata);
void print_hex(void *data, uint32_t length);
void print_namespace(struct nvme_namespace_data *nsdata);
void read_logpage(int fd, uint8_t log_page, uint32_t nsid, uint8_t lsp,
uint16_t lsi, uint8_t rae, void *payload, uint32_t payload_size);
void print_temp(uint16_t t);
void print_intel_add_smart(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused);
/* Utility Routines */
/*
* 128-bit integer augments to standard values. On i386 this
* doesn't exist, so we use 64-bit values. So, on 32-bit i386,
* you'll get truncated values until someone implement 128bit
* ints in sofware.
*/
#define UINT128_DIG 39
#ifdef __i386__
typedef uint64_t uint128_t;
#else
typedef __uint128_t uint128_t;
#endif
static __inline uint128_t
to128(void *p)
{
return *(uint128_t *)p;
}
uint64_t le48dec(const void *pp);
char * uint128_to_str(uint128_t u, char *buf, size_t buflen);
#endif

View File

@ -0,0 +1,30 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (C) 2018 Netflix
*
* 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$
*/
void nvme_print_controller(struct nvme_controller_data *cdata);

View File

@ -0,0 +1,291 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/ioccom.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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 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);
}
static struct cmd admin_pass_cmd = {
.name = "admin-passthru",
.fn = admin_passthru,
.ctx_size = sizeof(struct options),
.opts = opts,
.args = args,
.descr = "Send a pass through Admin command to the specified device",
};
static struct cmd io_pass_cmd = {
.name = "io-passthru",
.fn = io_passthru,
.ctx_size = sizeof(struct options),
.opts = opts,
.args = args,
.descr = "Send a pass through Admin command to the specified device",
};
CMD_COMMAND(admin_pass_cmd);
CMD_COMMAND(io_pass_cmd);

View File

@ -0,0 +1,188 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/ioccom.h>
#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nvmecontrol.h"
/* 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 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)
{
uint64_t io_completed = 0, iops, mbps;
uint32_t i;
for (i = 0; i < io_test->num_threads; i++)
io_completed += io_test->io_completed[i];
iops = io_completed/io_test->time;
mbps = iops * io_test->size / (1024*1024);
printf("Threads: %2d Size: %6d %5s Time: %3d IO/s: %7ju MB/s: %4ju\n",
io_test->num_threads, io_test->size,
io_test->opc == NVME_OPC_READ ? "READ" : "WRITE",
io_test->time, (uintmax_t)iops, (uintmax_t)mbps);
if (perthread)
for (i = 0; i < io_test->num_threads; i++)
printf("\t%3d: %8ju IO/s\n", i,
(uintmax_t)io_test->io_completed[i]/io_test->time);
}
static void
perftest(const struct cmd *f, int argc, char *argv[])
{
struct nvme_io_test io_test;
int fd;
u_long ioctl_cmd = NVME_IO_TEST;
memset(&io_test, 0, sizeof(io_test));
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 (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, opt.perthread);
exit(0);
}

View File

@ -0,0 +1,203 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* Copyright (c) 2016 Netflix, Inc
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/ioccom.h>
#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nvmecontrol.h"
_Static_assert(sizeof(struct nvme_power_state) == 256 / NBBY,
"nvme_power_state size wrong");
#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)
{
int mpower, apower, ipower;
uint8_t mps, nops, aps, apw;
mps = (nps->mps_nops >> NVME_PWR_ST_MPS_SHIFT) &
NVME_PWR_ST_MPS_MASK;
nops = (nps->mps_nops >> NVME_PWR_ST_NOPS_SHIFT) &
NVME_PWR_ST_NOPS_MASK;
apw = (nps->apw_aps >> NVME_PWR_ST_APW_SHIFT) &
NVME_PWR_ST_APW_MASK;
aps = (nps->apw_aps >> NVME_PWR_ST_APS_SHIFT) &
NVME_PWR_ST_APS_MASK;
mpower = nps->mp;
if (mps == 0)
mpower *= 100;
ipower = nps->idlp;
if (nps->ips == 1)
ipower *= 100;
apower = nps->actp;
if (aps == 1)
apower *= 100;
printf("%2d: %2d.%04dW%c %3d.%03dms %3d.%03dms %2d %2d %2d %2d %2d.%04dW %2d.%04dW %d\n",
i, mpower / 10000, mpower % 10000,
nops ? '*' : ' ', nps->enlat / 1000, nps->enlat % 1000,
nps->exlat / 1000, nps->exlat % 1000, nps->rrt, nps->rrl,
nps->rwt, nps->rwl, ipower / 10000, ipower % 10000,
apower / 10000, apower % 10000, apw);
}
static void
power_list(struct nvme_controller_data *cdata)
{
int i;
printf("\nPower States Supported: %d\n\n", cdata->npss + 1);
printf(" # Max pwr Enter Lat Exit Lat RT RL WT WL Idle Pwr Act Pwr Workloadd\n");
printf("-- -------- --------- --------- -- -- -- -- -------- -------- --\n");
for (i = 0; i <= cdata->npss; i++)
power_list_one(i, &cdata->power_state[i]);
}
static void
power_set(int fd, int power_val, int workload, int perm)
{
struct nvme_pt_command pt;
uint32_t p;
p = perm ? (1u << 31) : 0;
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_SET_FEATURES;
pt.cmd.cdw10 = htole32(NVME_FEAT_POWER_MANAGEMENT | p);
pt.cmd.cdw11 = htole32(power_val | (workload << 5));
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "set feature power mgmt request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "set feature power mgmt request returned error");
}
static void
power_show(int fd)
{
struct nvme_pt_command pt;
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_GET_FEATURES;
pt.cmd.cdw10 = htole32(NVME_FEAT_POWER_MANAGEMENT);
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "set feature power mgmt request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "set feature power mgmt request returned error");
printf("Current Power Mode is %d\n", pt.cpl.cdw0);
}
static void
power(const struct cmd *f, int argc, char *argv[])
{
struct nvme_controller_data cdata;
int fd;
arg_parse(argc, argv, f);
if (opt.list && opt.power != POWER_NONE) {
fprintf(stderr, "Can't set power and list power states\n");
arg_help(argc, argv, f);
}
open_dev(opt.dev, &fd, 1, 1);
if (opt.list) {
read_controller_data(fd, &cdata);
power_list(&cdata);
goto out;
}
if (opt.power != POWER_NONE) {
power_set(fd, opt.power, opt.workload, 0);
goto out;
}
power_show(fd);
out:
close(fd);
exit(0);
}
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

@ -0,0 +1,78 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/ioccom.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nvmecontrol.h"
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 cmd *f, int argc, char *argv[])
{
int fd;
arg_parse(argc, argv, f);
open_dev(opt.dev, &fd, 1, 1);
if (ioctl(fd, NVME_RESET_CONTROLLER) < 0)
err(1, "reset request to %s failed", argv[optind]);
exit(0);
}
static struct cmd reset_cmd = {
.name = "reset",
.fn = reset,
.descr = "Perform a controller-level reset",
.args = args,
};
CMD_COMMAND(reset_cmd);

View File

@ -0,0 +1,444 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (C) 2019 Alexander Motin <mav@FreeBSD.org>
*
* 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,
* without modification, immediately at the beginning of the file.
* 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 ``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 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 <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nvmecontrol.h"
/* Tables for command line parsing */
static cmd_fn_t resv;
static cmd_fn_t resvacquire;
static cmd_fn_t resvregister;
static cmd_fn_t resvrelease;
static cmd_fn_t resvreport;
#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 resv_cmd = {
.name = "resv",
.fn = resv,
.descr = "Reservation commands",
.ctx_size = 0,
.opts = NULL,
.args = NULL,
};
CMD_COMMAND(resv_cmd);
static struct acquire_options {
uint64_t crkey;
uint64_t prkey;
uint8_t rtype;
uint8_t racqa;
const char *dev;
} acquire_opt = {
.crkey = 0,
.prkey = 0,
.rtype = 0,
.racqa = 0,
.dev = NULL,
};
static const struct opts acquire_opts[] = {
OPT("crkey", 'c', arg_uint64, acquire_opt, crkey,
"Current Reservation Key"),
OPT("prkey", 'p', arg_uint64, acquire_opt, prkey,
"Preempt Reservation Key"),
OPT("rtype", 't', arg_uint8, acquire_opt, rtype,
"Reservation Type"),
OPT("racqa", 'a', arg_uint8, acquire_opt, racqa,
"Acquire Action (0=acq, 1=pre, 2=pre+ab)"),
{ NULL, 0, arg_none, NULL, NULL }
};
static const struct args acquire_args[] = {
{ arg_string, &acquire_opt.dev, "namespace-id" },
{ arg_none, NULL, NULL },
};
static struct cmd acquire_cmd = {
.name = "acquire",
.fn = resvacquire,
.descr = "Acquire/preempt reservation",
.ctx_size = sizeof(acquire_opt),
.opts = acquire_opts,
.args = acquire_args,
};
CMD_SUBCOMMAND(resv_cmd, acquire_cmd);
static struct register_options {
uint64_t crkey;
uint64_t nrkey;
uint8_t rrega;
bool iekey;
uint8_t cptpl;
const char *dev;
} register_opt = {
.crkey = 0,
.nrkey = 0,
.rrega = 0,
.iekey = false,
.cptpl = 0,
.dev = NULL,
};
static const struct opts register_opts[] = {
OPT("crkey", 'c', arg_uint64, register_opt, crkey,
"Current Reservation Key"),
OPT("nrkey", 'k', arg_uint64, register_opt, nrkey,
"New Reservation Key"),
OPT("rrega", 'r', arg_uint8, register_opt, rrega,
"Register Action (0=reg, 1=unreg, 2=replace)"),
OPT("iekey", 'i', arg_none, register_opt, iekey,
"Ignore Existing Key"),
OPT("cptpl", 'p', arg_uint8, register_opt, cptpl,
"Change Persist Through Power Loss State"),
{ NULL, 0, arg_none, NULL, NULL }
};
static const struct args register_args[] = {
{ arg_string, &register_opt.dev, "namespace-id" },
{ arg_none, NULL, NULL },
};
static struct cmd register_cmd = {
.name = "register",
.fn = resvregister,
.descr = "Register/unregister reservation",
.ctx_size = sizeof(register_opt),
.opts = register_opts,
.args = register_args,
};
CMD_SUBCOMMAND(resv_cmd, register_cmd);
static struct release_options {
uint64_t crkey;
uint8_t rtype;
uint8_t rrela;
const char *dev;
} release_opt = {
.crkey = 0,
.rtype = 0,
.rrela = 0,
.dev = NULL,
};
static const struct opts release_opts[] = {
OPT("crkey", 'c', arg_uint64, release_opt, crkey,
"Current Reservation Key"),
OPT("rtype", 't', arg_uint8, release_opt, rtype,
"Reservation Type"),
OPT("rrela", 'a', arg_uint8, release_opt, rrela,
"Release Action (0=release, 1=clear)"),
{ NULL, 0, arg_none, NULL, NULL }
};
static const struct args release_args[] = {
{ arg_string, &release_opt.dev, "namespace-id" },
{ arg_none, NULL, NULL },
};
static struct cmd release_cmd = {
.name = "release",
.fn = resvrelease,
.descr = "Release/clear reservation",
.ctx_size = sizeof(release_opt),
.opts = release_opts,
.args = release_args,
};
CMD_SUBCOMMAND(resv_cmd, release_cmd);
static struct report_options {
bool hex;
bool verbose;
bool eds;
const char *dev;
} report_opt = {
.hex = false,
.verbose = false,
.eds = false,
.dev = NULL,
};
static const struct opts report_opts[] = {
OPT("hex", 'x', arg_none, report_opt, hex,
"Print reservation status in hex"),
OPT("verbose", 'v', arg_none, report_opt, verbose,
"More verbosity"),
OPT("eds", 'e', arg_none, report_opt, eds,
"Extended Data Structure"),
{ NULL, 0, arg_none, NULL, NULL }
};
static const struct args report_args[] = {
{ arg_string, &report_opt.dev, "namespace-id" },
{ arg_none, NULL, NULL },
};
static struct cmd report_cmd = {
.name = "report",
.fn = resvreport,
.descr = "Print reservation status",
.ctx_size = sizeof(report_opt),
.opts = report_opts,
.args = report_args,
};
CMD_SUBCOMMAND(resv_cmd, report_cmd);
/* handles NVME_OPC_RESERVATION_* NVM commands */
static void
resvacquire(const struct cmd *f, int argc, char *argv[])
{
struct nvme_pt_command pt;
uint64_t data[2];
int fd;
uint32_t nsid;
if (arg_parse(argc, argv, f))
return;
open_dev(acquire_opt.dev, &fd, 1, 1);
get_nsid(fd, NULL, &nsid);
if (nsid == 0) {
fprintf(stderr, "This command require namespace-id\n");
arg_help(argc, argv, f);
}
data[0] = htole64(acquire_opt.crkey);
data[1] = htole64(acquire_opt.prkey);
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_RESERVATION_ACQUIRE;
pt.cmd.cdw10 = htole32((acquire_opt.racqa & 7) |
(acquire_opt.rtype << 8));
pt.buf = &data;
pt.len = sizeof(data);
pt.is_read = 0;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "acquire request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "acquire request returned error");
close(fd);
exit(0);
}
static void
resvregister(const struct cmd *f, int argc, char *argv[])
{
struct nvme_pt_command pt;
uint64_t data[2];
int fd;
uint32_t nsid;
if (arg_parse(argc, argv, f))
return;
open_dev(register_opt.dev, &fd, 1, 1);
get_nsid(fd, NULL, &nsid);
if (nsid == 0) {
fprintf(stderr, "This command require namespace-id\n");
arg_help(argc, argv, f);
}
data[0] = htole64(register_opt.crkey);
data[1] = htole64(register_opt.nrkey);
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_RESERVATION_REGISTER;
pt.cmd.cdw10 = htole32((register_opt.rrega & 7) |
(register_opt.iekey << 3) | (register_opt.cptpl << 30));
pt.buf = &data;
pt.len = sizeof(data);
pt.is_read = 0;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "register request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "register request returned error");
close(fd);
exit(0);
}
static void
resvrelease(const struct cmd *f, int argc, char *argv[])
{
struct nvme_pt_command pt;
uint64_t data[1];
int fd;
uint32_t nsid;
if (arg_parse(argc, argv, f))
return;
open_dev(release_opt.dev, &fd, 1, 1);
get_nsid(fd, NULL, &nsid);
if (nsid == 0) {
fprintf(stderr, "This command require namespace-id\n");
arg_help(argc, argv, f);
}
data[0] = htole64(release_opt.crkey);
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_RESERVATION_RELEASE;
pt.cmd.cdw10 = htole32((release_opt.rrela & 7) |
(release_opt.rtype << 8));
pt.buf = &data;
pt.len = sizeof(data);
pt.is_read = 0;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "release request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "release request returned error");
close(fd);
exit(0);
}
static void
resvreport(const struct cmd *f, int argc, char *argv[])
{
struct nvme_pt_command pt;
struct nvme_resv_status *s;
struct nvme_resv_status_ext *e;
uint8_t data[4096] __aligned(4);
int fd;
u_int i, n;
uint32_t nsid;
if (arg_parse(argc, argv, f))
return;
open_dev(report_opt.dev, &fd, 1, 1);
get_nsid(fd, NULL, &nsid);
if (nsid == 0) {
fprintf(stderr, "This command require namespace-id\n");
arg_help(argc, argv, f);
}
bzero(data, sizeof(data));
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_RESERVATION_REPORT;
pt.cmd.cdw10 = htole32(sizeof(data) / 4 - 1);
pt.cmd.cdw11 = htole32(report_opt.eds); /* EDS */
pt.buf = &data;
pt.len = sizeof(data);
pt.is_read = 1;
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "report request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "report request returned error");
close(fd);
if (report_opt.eds)
nvme_resv_status_ext_swapbytes((void *)data, sizeof(data));
else
nvme_resv_status_swapbytes((void *)data, sizeof(data));
if (report_opt.hex) {
i = sizeof(data);
if (!report_opt.verbose) {
for (; i > 64; i--) {
if (data[i - 1] != 0)
break;
}
}
print_hex(&data, i);
exit(0);
}
s = (struct nvme_resv_status *)data;
n = (s->regctl[1] << 8) | s->regctl[0];
printf("Generation: %u\n", s->gen);
printf("Reservation Type: %u\n", s->rtype);
printf("Number of Registered Controllers: %u\n", n);
printf("Persist Through Power Loss State: %u\n", s->ptpls);
if (report_opt.eds) {
e = (struct nvme_resv_status_ext *)data;
n = MIN(n, (sizeof(data) - sizeof(e)) / sizeof(e->ctrlr[0]));
for (i = 0; i < n; i++) {
printf("Controller ID: 0x%04x\n",
e->ctrlr[i].ctrlr_id);
printf(" Reservation Status: %u\n",
e->ctrlr[i].rcsts);
printf(" Reservation Key: 0x%08jx\n",
e->ctrlr[i].rkey);
printf(" Host Identifier: 0x%08jx%08jx\n",
e->ctrlr[i].hostid[0], e->ctrlr[i].hostid[1]);
}
} else {
n = MIN(n, (sizeof(data) - sizeof(s)) / sizeof(s->ctrlr[0]));
for (i = 0; i < n; i++) {
printf("Controller ID: 0x%04x\n",
s->ctrlr[i].ctrlr_id);
printf(" Reservation Status: %u\n",
s->ctrlr[i].rcsts);
printf(" Host Identifier: 0x%08jx\n",
s->ctrlr[i].hostid);
printf(" Reservation Key: 0x%08jx\n",
s->ctrlr[i].rkey);
}
}
exit(0);
}
static void
resv(const struct cmd *nf __unused, int argc, char *argv[])
{
cmd_dispatch(argc, argv, &resv_cmd);
}

View File

@ -0,0 +1,224 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (C) 2019 Alexander Motin <mav@FreeBSD.org>
*
* 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 <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nvmecontrol.h"
/* Tables for command line parsing */
static cmd_fn_t sanitize;
static struct options {
bool ause;
bool ndas;
bool oipbp;
bool reportonly;
uint8_t owpass;
uint32_t ovrpat;
const char *sanact;
const char *dev;
} opt = {
.ause = false,
.ndas = false,
.oipbp = false,
.reportonly = false,
.owpass = 1,
.ovrpat = 0,
.sanact = NULL,
.dev = NULL,
};
static const struct opts sanitize_opts[] = {
#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc }
OPT("ause", 'U', arg_none, opt, ause,
"Allow Unrestricted Sanitize Exit"),
OPT("ndas", 'd', arg_none, opt, ndas,
"No Deallocate After Sanitize"),
OPT("oipbp", 'I', arg_none, opt, oipbp,
"Overwrite Invert Pattern Between Passes"),
OPT("reportonly", 'r', arg_none, opt, reportonly,
"Report previous sanitize status"),
OPT("owpass", 'c', arg_uint8, opt, owpass,
"Overwrite Pass Count"),
OPT("ovrpat", 'p', arg_uint32, opt, ovrpat,
"Overwrite Pattern"),
OPT("sanact", 'a', arg_string, opt, sanact,
"Sanitize Action (block, overwrite, crypto)"),
{ NULL, 0, arg_none, NULL, NULL }
};
#undef OPT
static const struct args sanitize_args[] = {
{ arg_string, &opt.dev, "controller-id" },
{ arg_none, NULL, NULL },
};
static struct cmd sanitize_cmd = {
.name = "sanitize",
.fn = sanitize,
.descr = "Sanitize NVM subsystem",
.ctx_size = sizeof(opt),
.opts = sanitize_opts,
.args = sanitize_args,
};
CMD_COMMAND(sanitize_cmd);
/* End of tables for command line parsing */
static void
sanitize(const struct cmd *f, int argc, char *argv[])
{
struct nvme_controller_data cd;
struct nvme_pt_command pt;
struct nvme_sanitize_status_page ss;
char *path;
uint32_t nsid;
int sanact = 0, fd, delay = 1;
if (arg_parse(argc, argv, f))
return;
if (opt.sanact == NULL) {
if (!opt.reportonly) {
fprintf(stderr, "Sanitize Action is not specified\n");
arg_help(argc, argv, f);
}
} else {
if (strcmp(opt.sanact, "exitfailure") == 0)
sanact = 1;
else if (strcmp(opt.sanact, "block") == 0)
sanact = 2;
else if (strcmp(opt.sanact, "overwrite") == 0)
sanact = 3;
else if (strcmp(opt.sanact, "crypto") == 0)
sanact = 4;
else {
fprintf(stderr, "Incorrect Sanitize Action value\n");
arg_help(argc, argv, f);
}
}
if (opt.owpass == 0 || opt.owpass > 16) {
fprintf(stderr, "Incorrect Overwrite Pass Count value\n");
arg_help(argc, argv, f);
}
open_dev(opt.dev, &fd, 1, 1);
get_nsid(fd, &path, &nsid);
if (nsid != 0) {
close(fd);
open_dev(path, &fd, 1, 1);
}
free(path);
if (opt.reportonly)
goto wait;
/* Check that controller can execute this command. */
read_controller_data(fd, &cd);
if (((cd.sanicap >> NVME_CTRLR_DATA_SANICAP_BES_SHIFT) &
NVME_CTRLR_DATA_SANICAP_BES_MASK) == 0 && sanact == 2)
errx(1, "controller does not support Block Erase");
if (((cd.sanicap >> NVME_CTRLR_DATA_SANICAP_OWS_SHIFT) &
NVME_CTRLR_DATA_SANICAP_OWS_MASK) == 0 && sanact == 3)
errx(1, "controller does not support Overwrite");
if (((cd.sanicap >> NVME_CTRLR_DATA_SANICAP_CES_SHIFT) &
NVME_CTRLR_DATA_SANICAP_CES_MASK) == 0 && sanact == 4)
errx(1, "controller does not support Crypto Erase");
/*
* If controller supports only one namespace, we may sanitize it.
* If there can be more, make user explicit in his commands.
*/
if (nsid != 0 && cd.nn > 1)
errx(1, "can't sanitize one of namespaces, specify controller");
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_SANITIZE;
pt.cmd.cdw10 = htole32((opt.ndas << 9) | (opt.oipbp << 8) |
((opt.owpass & 0xf) << 4) | (opt.ause << 3) | sanact);
pt.cmd.cdw11 = htole32(opt.ovrpat);
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "sanitize request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "sanitize request returned error");
wait:
read_logpage(fd, NVME_LOG_SANITIZE_STATUS,
NVME_GLOBAL_NAMESPACE_TAG, 0, 0, 0, &ss, sizeof(ss));
switch ((ss.sstat >> NVME_SS_PAGE_SSTAT_STATUS_SHIFT) &
NVME_SS_PAGE_SSTAT_STATUS_MASK) {
case NVME_SS_PAGE_SSTAT_STATUS_NEVER:
printf("Never sanitized");
break;
case NVME_SS_PAGE_SSTAT_STATUS_COMPLETED:
printf("Sanitize completed");
break;
case NVME_SS_PAGE_SSTAT_STATUS_INPROG:
printf("Sanitize in progress: %u%% (%u/65535)\r",
(ss.sprog * 100 + 32768) / 65536, ss.sprog);
fflush(stdout);
if (delay < 16)
delay++;
sleep(delay);
goto wait;
case NVME_SS_PAGE_SSTAT_STATUS_FAILED:
printf("Sanitize failed");
break;
case NVME_SS_PAGE_SSTAT_STATUS_COMPLETEDWD:
printf("Sanitize completed with deallocation");
break;
default:
printf("Sanitize status unknown");
break;
}
if (delay > 1)
printf(" ");
printf("\n");
close(fd);
exit(0);
}

View File

@ -0,0 +1,198 @@
#include <machine/rtems-bsd-user-space.h>
/*-
* Copyright (c) 2017 Netflix, Inc
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/ioccom.h>
#include <sys/endian.h>
#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nvmecontrol.h"
#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(int argc, char *argv[]);
#define WDC_CAP_DIAG_USAGE "\tnvmecontrol wdc cap-diag [-o path-template]\n"
static struct nvme_function wdc_funcs[] = {
{"cap-diag", wdc_cap_diag, WDC_CAP_DIAG_USAGE},
{NULL, NULL, NULL},
};
static void
wdc_append_serial_name(int fd, char *buf, size_t len, const char *suffix)
{
struct nvme_controller_data cdata;
char sn[NVME_SERIAL_NUMBER_LENGTH + 1];
char *walker;
len -= strlen(buf);
buf += strlen(buf);
read_controller_data(fd, &cdata);
memcpy(sn, cdata.sn, NVME_SERIAL_NUMBER_LENGTH);
walker = sn + NVME_SERIAL_NUMBER_LENGTH - 1;
while (walker > sn && *walker == ' ')
walker--;
*++walker = '\0';
snprintf(buf, len, "%s%s.bin", sn, suffix);
}
static void
wdc_get_data(int fd, uint32_t opcode, uint32_t len, uint32_t off, uint32_t cmd,
uint8_t *buffer, size_t buflen)
{
struct nvme_pt_command pt;
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = opcode;
pt.cmd.cdw10 = htole32(len / sizeof(uint32_t)); /* - 1 like all the others ??? */
pt.cmd.cdw11 = htole32(off / sizeof(uint32_t));
pt.cmd.cdw12 = htole32(cmd);
pt.buf = buffer;
pt.len = buflen;
pt.is_read = 1;
// printf("opcode %#x cdw10(len) %#x cdw11(offset?) %#x cdw12(cmd/sub) %#x buflen %zd\n",
// (int)opcode, (int)cdw10, (int)cdw11, (int)cdw12, buflen);
if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0)
err(1, "wdc_get_data request failed");
if (nvme_completion_is_error(&pt.cpl))
errx(1, "wdc_get_data request returned error");
}
static void
wdc_do_dump(int fd, char *tmpl, const char *suffix, uint32_t opcode,
uint32_t cmd, int len_off)
{
int first;
int fd2;
uint8_t *buf;
uint32_t len, offset;
size_t resid;
wdc_append_serial_name(fd, tmpl, MAXPATHLEN, suffix);
/* XXX overwrite protection? */
fd2 = open(tmpl, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd2 < 0)
err(1, "open %s", tmpl);
buf = aligned_alloc(PAGE_SIZE, NVME_MAX_XFER_SIZE);
if (buf == NULL)
errx(1, "Can't get buffer to read dump");
offset = 0;
len = NVME_MAX_XFER_SIZE;
first = 1;
do {
resid = len > NVME_MAX_XFER_SIZE ? NVME_MAX_XFER_SIZE : len;
wdc_get_data(fd, opcode, resid, offset, cmd, buf, resid);
if (first) {
len = be32dec(buf + len_off);
if (len == 0)
errx(1, "No data for %s", suffix);
if (memcmp("E6LG", buf, 4) != 0)
printf("Expected header of E6LG, found '%4.4s' instead\n",
buf);
printf("Dumping %d bytes of version %d.%d log to %s\n", len,
buf[8], buf[9], tmpl);
/*
* Adjust amount to dump if total dump < 1MB,
* though it likely doesn't matter to the WDC
* analysis tools.
*/
if (resid > len)
resid = len;
first = 0;
}
if (write(fd2, buf, resid) != (ssize_t)resid)
err(1, "write");
offset += resid;
len -= resid;
} while (len > 0);
free(buf);
close(fd2);
}
static void
wdc_cap_diag_usage(void)
{
fprintf(stderr, "usage:\n");
fprintf(stderr, WDC_CAP_DIAG_USAGE);
exit(1);
}
static void
wdc_cap_diag(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:
wdc_cap_diag_usage();
}
}
/* Check that a controller was specified. */
if (optind >= argc)
wdc_cap_diag_usage();
open_dev(argv[optind], &fd, 1, 1);
wdc_do_dump(fd, path_tmpl, "cap_diag", WDC_NVME_CAP_DIAG_OPCODE,
WDC_NVME_CAP_DIAG_CMD, 4);
close(fd);
exit(1);
}
void
wdc(int argc, char *argv[])
{
dispatch(argc, argv, wdc_funcs);
}