Feature-complete NVMe Namespace Management.

This adds several previously missed but important subcommands to list
namespaces and controllers.  It also fixes few previously added but
just found with real testing to be broken subcommands.

Also while there, add possibility to explicitly specify nsid for
`nvmecontrol identify` subcommand.  It may be useful to specify nsids
not having own devices, for example 0xffffffff, or just newly created
ones.

MFC after:	2 weeks
Relnotes:	yes
Sponsored by:	iXsystems, Inc.
This commit is contained in:
Alexander Motin 2019-07-31 18:44:20 +00:00
parent 4d7486c30f
commit 3b3dd3f770
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=350477
6 changed files with 442 additions and 39 deletions

View File

@ -48,13 +48,15 @@ static struct options {
bool hex;
bool verbose;
const char *dev;
uint32_t nsid;
} opt = {
.hex = false,
.verbose = false,
.dev = NULL,
.nsid = 0,
};
static void
void
print_namespace(struct nvme_namespace_data *nsdata)
{
uint32_t i;
@ -204,22 +206,24 @@ identify_ns(const struct cmd *f, int argc, char *argv[])
int fd, hexlength;
uint32_t nsid;
/*
* Check if the specified device node exists before continuing.
* This is a cleaner check for cases where the correct controller
* is specified, but an invalid namespace on that controller.
*/
open_dev(opt.dev, &fd, 1, 1);
close(fd);
if (strstr(opt.dev, NVME_NS_PREFIX) != NULL) {
/*
* We 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. So parse the namespace's device node
* string to get the controller substring and namespace ID.
* Now we know that provided device name is valid, that is
* good for error reporting if specified controller name is
* valid, but namespace ID is not. But we 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. So parse the namespace's
* device node string to get the controller device substring
* and namespace ID.
*/
close(fd);
parse_ns_str(opt.dev, path, &nsid);
open_dev(path, &fd, 1, 1);
} else {
nsid = opt.nsid;
}
read_namespace_data(fd, nsid, &nsdata);
close(fd);
@ -248,10 +252,10 @@ identify(const struct cmd *f, int argc, char *argv[])
arg_parse(argc, argv, f);
/*
* If device node contains "ns", we consider it a namespace,
* otherwise, consider it a controller.
* If device node contains "ns" or nsid is specified, we consider
* it a namespace request, otherwise, consider it a controller.
*/
if (strstr(opt.dev, NVME_NS_PREFIX) == NULL)
if (strstr(opt.dev, NVME_NS_PREFIX) == NULL && opt.nsid == 0)
identify_ctrlr(f, argc, argv);
else
identify_ns(f, argc, argv);
@ -263,6 +267,8 @@ static const struct opts identify_opts[] = {
"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
@ -275,7 +281,7 @@ static const struct args identify_args[] = {
static struct cmd identify_cmd = {
.name = "identify",
.fn = identify,
.descr = "Print a human-readable summary of the IDENTIFY information",
.descr = "Print summary of the IDENTIFY information",
.ctx_size = sizeof(opt),
.opts = identify_opts,
.args = identify_args,

View File

@ -121,7 +121,7 @@ nvme_print_controller(struct nvme_controller_data *cdata)
printf("Unlimited\n");
else
printf("%ld\n", PAGE_SIZE * (1L << cdata->mdts));
printf("Controller ID: 0x%02x\n", cdata->ctrlr_id);
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);

View File

@ -34,6 +34,8 @@ __FBSDID("$FreeBSD$");
#include <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -44,10 +46,15 @@ __FBSDID("$FreeBSD$");
/* 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
@ -55,11 +62,71 @@ static cmd_fn_t nsdetach;
#define OPT_END { NULL, 0, arg_none, NULL, NULL }
static struct cmd ns_cmd = {
.name = "ns", .fn = ns, .descr = "Namespace commands", .ctx_size = 0, .opts = NULL, .args = NULL,
.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;
@ -101,7 +168,7 @@ static const struct opts create_opts[] = {
"PI field of FLBAS"),
OPT("pil", 'l', arg_uint32, create_opt, pil,
"PIL field of FLBAS"),
OPT("flbas", 'l', arg_uint32, create_opt, 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"),
@ -118,7 +185,7 @@ static const struct args create_args[] = {
static struct cmd create_cmd = {
.name = "create",
.fn = nscreate,
.descr = "Create a new namespace",
.descr = "Create a namespace",
.ctx_size = sizeof(create_opt),
.opts = create_opts,
.args = create_args,
@ -148,7 +215,7 @@ static const struct args delete_args[] = {
static struct cmd delete_cmd = {
.name = "delete",
.fn = nsdelete,
.descr = "Delete a new namespace",
.descr = "Delete a namespace",
.ctx_size = sizeof(delete_opt),
.opts = delete_opts,
.args = delete_args,
@ -169,7 +236,7 @@ static struct attach_options {
static const struct opts attach_opts[] = {
OPT("namespace-id", 'n', arg_uint32, attach_opt, nsid,
"The namespace ID to attach"),
OPT("controller", 'c', arg_uint32, attach_opt, nsid,
OPT("controller", 'c', arg_uint32, attach_opt, ctrlrid,
"The controller ID to attach"),
OPT_END
};
@ -182,7 +249,7 @@ static const struct args attach_args[] = {
static struct cmd attach_cmd = {
.name = "attach",
.fn = nsattach,
.descr = "Attach a new namespace",
.descr = "Attach a controller to a namespace",
.ctx_size = sizeof(attach_opt),
.opts = attach_opts,
.args = attach_args,
@ -190,6 +257,36 @@ static struct cmd attach_cmd = {
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;
@ -203,7 +300,7 @@ static struct detach_options {
static const struct opts detach_opts[] = {
OPT("namespace-id", 'n', arg_uint32, detach_opt, nsid,
"The namespace ID to detach"),
OPT("controller", 'c', arg_uint32, detach_opt, nsid,
OPT("controller", 'c', arg_uint32, detach_opt, ctrlrid,
"The controller ID to detach"),
OPT_END
};
@ -216,7 +313,7 @@ static const struct args detach_args[] = {
static struct cmd detach_cmd = {
.name = "detach",
.fn = nsdetach,
.descr = "Detach a new namespace",
.descr = "Detach a controller from a namespace",
.ctx_size = sizeof(detach_opt),
.opts = detach_opts,
.args = detach_args,
@ -224,8 +321,43 @@ static struct cmd detach_cmd = {
CMD_SUBCOMMAND(ns_cmd, detach_cmd);
#define NS_USAGE \
"ns (create|delete|attach|detach)\n"
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 */
@ -245,6 +377,8 @@ static struct ns_result_str ns_result[] = {
{ 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"}
};
@ -261,6 +395,110 @@ get_res_str(uint16_t res)
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
@ -323,8 +561,7 @@ nscreate(const struct cmd *f, int argc, char *argv[])
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_NAMESPACE_MANAGEMENT;
pt.cmd.cdw10 = 0; /* create */
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 */
@ -366,7 +603,7 @@ nsdelete(const struct cmd *f, int argc, char *argv[])
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_NAMESPACE_MANAGEMENT;
pt.cmd.cdw10 = 1; /* delete */
pt.cmd.cdw10 = htole32(1); /* delete */
pt.buf = buf;
pt.len = sizeof(buf);
pt.is_read = 1;
@ -444,7 +681,7 @@ nsattach(const struct cmd *f, int argc, char *argv[])
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_NAMESPACE_ATTACHMENT;
pt.cmd.cdw10 = 0; /* attach */
pt.cmd.cdw10 = htole32(0); /* attach */
pt.cmd.nsid = attach_opt.nsid;
pt.buf = &clist;
pt.len = sizeof(clist);
@ -471,11 +708,11 @@ nsdetach(const struct cmd *f, int argc, char *argv[])
if (arg_parse(argc, argv, f))
return;
if (attach_opt.nsid == NONE) {
if (detach_opt.nsid == NONE) {
fprintf(stderr, "No valid NSID specified\n");
arg_help(argc, argv, f);
}
open_dev(attach_opt.dev, &fd, 1, 1);
open_dev(detach_opt.dev, &fd, 1, 1);
read_controller_data(fd, &cd);
/* Check that controller can execute this command. */
@ -513,7 +750,7 @@ nsdetach(const struct cmd *f, int argc, char *argv[])
memset(&pt, 0, sizeof(pt));
pt.cmd.opc = NVME_OPC_NAMESPACE_ATTACHMENT;
pt.cmd.cdw10 = 1; /* detach */
pt.cmd.cdw10 = htole32(1); /* detach */
pt.cmd.nsid = detach_opt.nsid;
pt.buf = &clist;
pt.len = sizeof(clist);
@ -530,6 +767,115 @@ nsdetach(const struct cmd *f, int argc, char *argv[])
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[])
{

View File

@ -34,7 +34,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd December 7, 2018
.Dd July 31, 2019
.Dt NVMECONTROL 8
.Os
.Sh NAME
@ -69,6 +69,51 @@
.Aq device id
.Aq namespace id
.Nm
.Ic ns active
.Aq device id
.Nm
.Ic ns allocated
.Aq device id
.Nm
.Ic ns attach
.Aq Fl n Ar nsid
.Aq Fl c Ar cntid
.Aq device id
.Nm
.Ic ns attached
.Aq Fl n Ar nsid
.Aq device id
.Nm
.Ic ns controllers
.Aq device id
.Nm
.Ic ns create
.Aq Fl s Ar nsze
.Op Fl c Ar ncap
.Op Fl f Ar lbaf
.Op Fl m Ar mset
.Op Fl n Ar nmic
.Op Fl p Ar pi
.Op Fl l Ar pil
.Op Fl L Ar flbas
.Op Fl d Ar dps
.Aq device id
.Nm
.Ic ns delete
.Aq Fl n Ar nsid
.Aq device id
.Nm
.Ic ns detach
.Aq Fl n Ar nsid
.Aq Fl c Ar cntid
.Aq device id
.Nm
.Ic ns identify
.Op Fl v
.Op Fl x
.Aq Fl n Ar nsid
.Aq device id
.Nm
.Ic firmware
.Op Fl s Ar slot
.Op Fl f Ar path_to_firmware
@ -143,6 +188,10 @@ will list all valid vendors and pages.
will print the page as hex.
.Fl b
will print the binary data for the page.
.Ss ns
Various namespace management commands.
If namespace management is supported by device, allow list, create and delete
namespaces, list, attach and detach controllers to namespaces.
.Ss format
Format either specified namespace, or all namespaces of specified controller,
using specified parameters:

View File

@ -126,6 +126,7 @@ read_namespace_data(int fd, uint32_t nsid, struct nvme_namespace_data *nsdata)
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;

View File

@ -73,6 +73,7 @@ void parse_ns_str(const char *ns_str, 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, void *payload,
uint32_t payload_size);
void print_temp(uint16_t t);