summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastian Huber <sebastian.huber@embedded-brains.de>2019-09-20 07:55:33 +0200
committerSebastian Huber <sebastian.huber@embedded-brains.de>2019-11-13 13:22:25 +0100
commit94a7b59e064c3275bde9f132f20e0a962e8fc077 (patch)
treedf84e5928ac45c9004b51aa97337daf6873b3238
parentNVD(4): Add RTEMS-specific NVMe disk driver (diff)
downloadrtems-libbsd-94a7b59e064c3275bde9f132f20e0a962e8fc077.tar.bz2
NVMECONTROL(8): Import from FreeBSD
Update #3821.
-rw-r--r--freebsd/sbin/nvmecontrol/comnd.c345
-rw-r--r--freebsd/sbin/nvmecontrol/comnd.h102
-rw-r--r--freebsd/sbin/nvmecontrol/devlist.c131
-rw-r--r--freebsd/sbin/nvmecontrol/firmware.c363
-rw-r--r--freebsd/sbin/nvmecontrol/format.c222
-rw-r--r--freebsd/sbin/nvmecontrol/identify.c280
-rw-r--r--freebsd/sbin/nvmecontrol/identify_ext.c249
-rw-r--r--freebsd/sbin/nvmecontrol/logpage.c757
-rw-r--r--freebsd/sbin/nvmecontrol/modules/intel/intel.c197
-rw-r--r--freebsd/sbin/nvmecontrol/modules/wdc/wdc.c627
-rw-r--r--freebsd/sbin/nvmecontrol/nc_util.c61
-rw-r--r--freebsd/sbin/nvmecontrol/ns.c886
-rw-r--r--freebsd/sbin/nvmecontrol/nsid.c82
-rw-r--r--freebsd/sbin/nvmecontrol/nvmecontrol.c190
-rw-r--r--freebsd/sbin/nvmecontrol/nvmecontrol.h104
-rw-r--r--freebsd/sbin/nvmecontrol/nvmecontrol_ext.h30
-rw-r--r--freebsd/sbin/nvmecontrol/passthru.c291
-rw-r--r--freebsd/sbin/nvmecontrol/perftest.c188
-rw-r--r--freebsd/sbin/nvmecontrol/power.c203
-rw-r--r--freebsd/sbin/nvmecontrol/reset.c78
-rw-r--r--freebsd/sbin/nvmecontrol/resv.c444
-rw-r--r--freebsd/sbin/nvmecontrol/sanitize.c224
-rw-r--r--freebsd/sbin/nvmecontrol/wdc.c198
23 files changed, 6252 insertions, 0 deletions
diff --git a/freebsd/sbin/nvmecontrol/comnd.c b/freebsd/sbin/nvmecontrol/comnd.c
new file mode 100644
index 00000000..3074cfb9
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/comnd.c
@@ -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)
+{
+
+}
diff --git a/freebsd/sbin/nvmecontrol/comnd.h b/freebsd/sbin/nvmecontrol/comnd.h
new file mode 100644
index 00000000..91c97d4a
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/comnd.h
@@ -0,0 +1,102 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2019 Netflix, Inc
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef COMND_H
+#define COMND_H
+
+#include <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 */
diff --git a/freebsd/sbin/nvmecontrol/devlist.c b/freebsd/sbin/nvmecontrol/devlist.c
new file mode 100644
index 00000000..58e1153b
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/devlist.c
@@ -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);
+}
diff --git a/freebsd/sbin/nvmecontrol/firmware.c b/freebsd/sbin/nvmecontrol/firmware.c
new file mode 100644
index 00000000..482ceb3c
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/firmware.c
@@ -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);
+}
diff --git a/freebsd/sbin/nvmecontrol/format.c b/freebsd/sbin/nvmecontrol/format.c
new file mode 100644
index 00000000..95b57cb4
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/format.c
@@ -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);
+}
diff --git a/freebsd/sbin/nvmecontrol/identify.c b/freebsd/sbin/nvmecontrol/identify.c
new file mode 100644
index 00000000..be2fb00c
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/identify.c
@@ -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);
diff --git a/freebsd/sbin/nvmecontrol/identify_ext.c b/freebsd/sbin/nvmecontrol/identify_ext.c
new file mode 100644
index 00000000..2ec8f100
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/identify_ext.c
@@ -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)));
+ }
+}
diff --git a/freebsd/sbin/nvmecontrol/logpage.c b/freebsd/sbin/nvmecontrol/logpage.c
new file mode 100644
index 00000000..7a36f17a
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/logpage.c
@@ -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);
+}
diff --git a/freebsd/sbin/nvmecontrol/modules/intel/intel.c b/freebsd/sbin/nvmecontrol/modules/intel/intel.c
new file mode 100644
index 00000000..8c6c2564
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/modules/intel/intel.c
@@ -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);
diff --git a/freebsd/sbin/nvmecontrol/modules/wdc/wdc.c b/freebsd/sbin/nvmecontrol/modules/wdc/wdc.c
new file mode 100644
index 00000000..4a6a90dc
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/modules/wdc/wdc.c
@@ -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);
diff --git a/freebsd/sbin/nvmecontrol/nc_util.c b/freebsd/sbin/nvmecontrol/nc_util.c
new file mode 100644
index 00000000..443bef24
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/nc_util.c
@@ -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));
+}
diff --git a/freebsd/sbin/nvmecontrol/ns.c b/freebsd/sbin/nvmecontrol/ns.c
new file mode 100644
index 00000000..bb9b0011
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/ns.c
@@ -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);
+}
diff --git a/freebsd/sbin/nvmecontrol/nsid.c b/freebsd/sbin/nvmecontrol/nsid.c
new file mode 100644
index 00000000..74ed06c8
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/nsid.c
@@ -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);
+}
diff --git a/freebsd/sbin/nvmecontrol/nvmecontrol.c b/freebsd/sbin/nvmecontrol/nvmecontrol.c
new file mode 100644
index 00000000..c33d42ef
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/nvmecontrol.c
@@ -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);
+}
diff --git a/freebsd/sbin/nvmecontrol/nvmecontrol.h b/freebsd/sbin/nvmecontrol/nvmecontrol.h
new file mode 100644
index 00000000..f5dc61f2
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/nvmecontrol.h
@@ -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
diff --git a/freebsd/sbin/nvmecontrol/nvmecontrol_ext.h b/freebsd/sbin/nvmecontrol/nvmecontrol_ext.h
new file mode 100644
index 00000000..43042dfa
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/nvmecontrol_ext.h
@@ -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);
diff --git a/freebsd/sbin/nvmecontrol/passthru.c b/freebsd/sbin/nvmecontrol/passthru.c
new file mode 100644
index 00000000..979cc873
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/passthru.c
@@ -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);
diff --git a/freebsd/sbin/nvmecontrol/perftest.c b/freebsd/sbin/nvmecontrol/perftest.c
new file mode 100644
index 00000000..6f6bacbf
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/perftest.c
@@ -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);
+}
diff --git a/freebsd/sbin/nvmecontrol/power.c b/freebsd/sbin/nvmecontrol/power.c
new file mode 100644
index 00000000..e33680a0
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/power.c
@@ -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);
diff --git a/freebsd/sbin/nvmecontrol/reset.c b/freebsd/sbin/nvmecontrol/reset.c
new file mode 100644
index 00000000..519594e3
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/reset.c
@@ -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);
diff --git a/freebsd/sbin/nvmecontrol/resv.c b/freebsd/sbin/nvmecontrol/resv.c
new file mode 100644
index 00000000..5f615941
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/resv.c
@@ -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);
+}
diff --git a/freebsd/sbin/nvmecontrol/sanitize.c b/freebsd/sbin/nvmecontrol/sanitize.c
new file mode 100644
index 00000000..cc8e2417
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/sanitize.c
@@ -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);
+}
diff --git a/freebsd/sbin/nvmecontrol/wdc.c b/freebsd/sbin/nvmecontrol/wdc.c
new file mode 100644
index 00000000..0c7f3c90
--- /dev/null
+++ b/freebsd/sbin/nvmecontrol/wdc.c
@@ -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);
+}