[rtems-libbsd commit] NVMECONTROL(8): Import from FreeBSD

Sebastian Huber sebh at rtems.org
Thu Nov 14 06:05:46 UTC 2019


Module:    rtems-libbsd
Branch:    5-freebsd-12
Commit:    94a7b59e064c3275bde9f132f20e0a962e8fc077
Changeset: http://git.rtems.org/rtems-libbsd/commit/?id=94a7b59e064c3275bde9f132f20e0a962e8fc077

Author:    Sebastian Huber <sebastian.huber at embedded-brains.de>
Date:      Fri Sep 20 07:55:33 2019 +0200

NVMECONTROL(8): Import from FreeBSD

Update #3821.

---

 freebsd/sbin/nvmecontrol/comnd.c               | 345 ++++++++++
 freebsd/sbin/nvmecontrol/comnd.h               | 102 +++
 freebsd/sbin/nvmecontrol/devlist.c             | 131 ++++
 freebsd/sbin/nvmecontrol/firmware.c            | 363 ++++++++++
 freebsd/sbin/nvmecontrol/format.c              | 222 +++++++
 freebsd/sbin/nvmecontrol/identify.c            | 280 ++++++++
 freebsd/sbin/nvmecontrol/identify_ext.c        | 249 +++++++
 freebsd/sbin/nvmecontrol/logpage.c             | 757 +++++++++++++++++++++
 freebsd/sbin/nvmecontrol/modules/intel/intel.c | 197 ++++++
 freebsd/sbin/nvmecontrol/modules/wdc/wdc.c     | 627 +++++++++++++++++
 freebsd/sbin/nvmecontrol/nc_util.c             |  61 ++
 freebsd/sbin/nvmecontrol/ns.c                  | 886 +++++++++++++++++++++++++
 freebsd/sbin/nvmecontrol/nsid.c                |  82 +++
 freebsd/sbin/nvmecontrol/nvmecontrol.c         | 190 ++++++
 freebsd/sbin/nvmecontrol/nvmecontrol.h         | 104 +++
 freebsd/sbin/nvmecontrol/nvmecontrol_ext.h     |  30 +
 freebsd/sbin/nvmecontrol/passthru.c            | 291 ++++++++
 freebsd/sbin/nvmecontrol/perftest.c            | 188 ++++++
 freebsd/sbin/nvmecontrol/power.c               | 203 ++++++
 freebsd/sbin/nvmecontrol/reset.c               |  78 +++
 freebsd/sbin/nvmecontrol/resv.c                | 444 +++++++++++++
 freebsd/sbin/nvmecontrol/sanitize.c            | 224 +++++++
 freebsd/sbin/nvmecontrol/wdc.c                 | 198 ++++++
 23 files changed, 6252 insertions(+)

diff --git a/freebsd/sbin/nvmecontrol/comnd.c b/freebsd/sbin/nvmecontrol/comnd.c
new file mode 100644
index 0000000..3074cfb
--- /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 = ⊤
+
+	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 = ⊤
+	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 0000000..91c97d4
--- /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 0000000..58e1153
--- /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 0000000..482ceb3
--- /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 0000000..95b57cb
--- /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 at 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 0000000..be2fb00
--- /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 at 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 0000000..2ec8f10
--- /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 at 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 0000000..7a36f17
--- /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 at 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 0000000..8c6c256
--- /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 0000000..4a6a90d
--- /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 0000000..443bef2
--- /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 0000000..bb9b001
--- /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 at 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 0000000..74ed06c
--- /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 at 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 0000000..c33d42e
--- /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 0000000..f5dc61f
--- /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 0000000..43042df
--- /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 0000000..979cc87
--- /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 0000000..6f6bacb
--- /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 0000000..e33680a
--- /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 0000000..519594e
--- /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 0000000..5f61594
--- /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 at 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 0000000..cc8e241
--- /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 at 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 0000000..0c7f3c9
--- /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);
+}



More information about the vc mailing list