diff options
Diffstat (limited to '')
-rw-r--r-- | nvme.c | 4841 | ||||
-rw-r--r-- | nvme.control.in | 9 |
2 files changed, 4850 insertions, 0 deletions
@@ -0,0 +1,4841 @@ +/* + * nvme.c -- NVM-Express command line utility. + * + * Copyright (c) 2014-2015, Intel Corporation. + * + * Written by Keith Busch <kbusch@kernel.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** + * This program uses NVMe IOCTLs to run native nvme commands to a device. + */ + +#include <errno.h> +#include <getopt.h> +#include <fcntl.h> +#include <inttypes.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <math.h> +#include <dirent.h> +#include <libgen.h> + +#ifdef LIBHUGETLBFS +#include <hugetlbfs.h> +#endif + +#include <linux/fs.h> + +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include "common.h" +#include "nvme-print.h" +#include "nvme-ioctl.h" +#include "nvme-status.h" +#include "nvme-lightnvm.h" +#include "plugin.h" + +#include "argconfig.h" +#include "fabrics.h" + +#define CREATE_CMD +#include "nvme-builtin.h" + +static struct stat nvme_stat; +const char *devicename; + +static const char nvme_version_string[] = NVME_VERSION; + +static struct plugin builtin = { + .commands = commands, + .name = NULL, + .desc = NULL, + .next = NULL, + .tail = &builtin, +}; + +static struct program nvme = { + .name = "nvme", + .version = nvme_version_string, + .usage = "<command> [<device>] [<args>]", + .desc = "The '<device>' may be either an NVMe character "\ + "device (ex: /dev/nvme0) or an nvme block device "\ + "(ex: /dev/nvme0n1).", + .extensions = &builtin, +}; + +static const char *output_format = "Output format: normal|json|binary"; +static const char *output_format_no_binary = "Output format: normal|json"; + +static void *__nvme_alloc(size_t len, bool *huge) +{ + void *p; + + if (!posix_memalign(&p, getpagesize(), len)) { + *huge = false; + memset(p, 0, len); + return p; + } + return NULL; +} + +#ifdef LIBHUGETLBFS +#define HUGE_MIN 0x80000 + +static void nvme_free(void *p, bool huge) +{ + if (huge) + free_hugepage_region(p); + else + free(p); +} + +static void *nvme_alloc(size_t len, bool *huge) +{ + void *p; + + if (len < HUGE_MIN) + return __nvme_alloc(len, huge); + + p = get_hugepage_region(len, GHR_DEFAULT); + if (!p) + return __nvme_alloc(len, huge); + + *huge = true; + return p; +} +#else +static void nvme_free(void *p, bool huge) +{ + free(p); +} + +static void *nvme_alloc(size_t len, bool *huge) +{ + return __nvme_alloc(len, huge); +} +#endif + +static int open_dev(char *dev) +{ + int err, fd; + + devicename = basename(dev); + err = open(dev, O_RDONLY); + if (err < 0) + goto perror; + fd = err; + + err = fstat(fd, &nvme_stat); + if (err < 0) { + close(fd); + goto perror; + } + if (!S_ISCHR(nvme_stat.st_mode) && !S_ISBLK(nvme_stat.st_mode)) { + fprintf(stderr, "%s is not a block or character device\n", dev); + close(fd); + return -ENODEV; + } + return fd; +perror: + perror(dev); + return err; +} + +static int check_arg_dev(int argc, char **argv) +{ + if (optind >= argc) { + errno = EINVAL; + perror(argv[0]); + return -EINVAL; + } + return 0; +} + +static int get_dev(int argc, char **argv) +{ + int ret; + + ret = check_arg_dev(argc, argv); + if (ret) + return ret; + + return open_dev(argv[optind]); +} + +int parse_and_open(int argc, char **argv, const char *desc, + const struct argconfig_commandline_options *opts) +{ + int ret; + + ret = argconfig_parse(argc, argv, desc, opts); + if (ret) + return ret; + + ret = get_dev(argc, argv); + if (ret < 0) + argconfig_print_help(desc, opts); + + return ret; +} + +enum nvme_print_flags validate_output_format(char *format) +{ + if (!format) + return -EINVAL; + if (!strcmp(format, "normal")) + return NORMAL; + if (!strcmp(format, "json")) + return JSON; + if (!strcmp(format, "binary")) + return BINARY; + return -EINVAL; +} + +static int get_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct nvme_smart_log smart_log; + const char *desc = "Retrieve SMART log for the given device "\ + "(or optionally a namespace) in either decoded format "\ + "(default) or binary."; + const char *namespace = "(optional) desired namespace"; + const char *raw = "output in binary format"; + const char *human_readable = "show info in readable format"; + enum nvme_print_flags flags; + int err, fd; + + struct config { + __u32 namespace_id; + int raw_binary; + char *output_format; + int human_readable; + }; + + struct config cfg = { + .namespace_id = NVME_NSID_ALL, + .output_format = "normal", + }; + + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_FLAG("human-readable", 'H', &cfg.human_readable, human_readable), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + if (cfg.raw_binary) + flags = BINARY; + if (cfg.human_readable) + flags |= VERBOSE; + + err = nvme_smart_log(fd, cfg.namespace_id, &smart_log); + if (!err) + nvme_show_smart_log(&smart_log, cfg.namespace_id, devicename, + flags); + else if (err > 0) + nvme_show_status(err); + else + perror("smart log"); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int get_ana_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "Retrieve ANA log for the given device in " \ + "decoded format (default), json or binary."; + void *ana_log; + int err, fd; + int groups = 0; /* Right now get all the per ANA group NSIDS */ + size_t ana_log_len; + struct nvme_id_ctrl ctrl; + enum nvme_print_flags flags; + + struct config { + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + + err = nvme_identify_ctrl(fd, &ctrl); + if (err) { + fprintf(stderr, "ERROR : nvme_identify_ctrl() failed 0x%x\n", + err); + goto close_fd; + } + + ana_log_len = sizeof(struct nvme_ana_rsp_hdr) + + le32_to_cpu(ctrl.nanagrpid) * sizeof(struct nvme_ana_group_desc); + if (!(ctrl.anacap & (1 << 6))) + ana_log_len += le32_to_cpu(ctrl.mnan) * sizeof(__le32); + + ana_log = malloc(ana_log_len); + if (!ana_log) { + perror("malloc"); + err = -ENOMEM; + goto close_fd; + } + + err = nvme_ana_log(fd, ana_log, ana_log_len, groups ? NVME_ANA_LOG_RGO : 0); + if (!err) { + nvme_show_ana_log(ana_log, devicename, flags, ana_log_len); + } else if (err > 0) + nvme_show_status(err); + else + perror("ana-log"); + free(ana_log); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int get_telemetry_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Retrieve telemetry log and write to binary file"; + const char *fname = "File name to save raw binary, includes header"; + const char *hgen = "Have the host tell the controller to generate the report"; + const char *cgen = "Gather report generated by the controller."; + const char *dgen = "Pick which telemetry data area to report. Default is all. Valid options are 1, 2, 3."; + const size_t bs = 512; + struct nvme_telemetry_log_page_hdr *hdr; + size_t full_size, offset = bs; + int err = 0, fd, output; + void *page_log; + + struct config { + char *file_name; + __u32 host_gen; + int ctrl_init; + int data_area; + }; + struct config cfg = { + .file_name = NULL, + .host_gen = 1, + .ctrl_init = 0, + .data_area = 3, + }; + + OPT_ARGS(opts) = { + OPT_FILE("output-file", 'o', &cfg.file_name, fname), + OPT_UINT("host-generate", 'g', &cfg.host_gen, hgen), + OPT_FLAG("controller-init", 'c', &cfg.ctrl_init, cgen), + OPT_UINT("data-area", 'd', &cfg.data_area, dgen), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (!cfg.file_name) { + fprintf(stderr, "Please provide an output file!\n"); + err = -EINVAL; + goto close_fd; + } + + cfg.host_gen = !!cfg.host_gen; + hdr = malloc(bs); + page_log = malloc(bs); + if (!hdr || !page_log) { + fprintf(stderr, "Failed to allocate %zu bytes for log: %s\n", + bs, strerror(errno)); + err = -ENOMEM; + goto free_mem; + } + memset(hdr, 0, bs); + + output = open(cfg.file_name, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (output < 0) { + fprintf(stderr, "Failed to open output file %s: %s!\n", + cfg.file_name, strerror(errno)); + err = output; + goto free_mem; + } + + err = nvme_get_telemetry_log(fd, hdr, cfg.host_gen, cfg.ctrl_init, bs, 0); + if (err < 0) + perror("get-telemetry-log"); + else if (err > 0) { + nvme_show_status(err); + fprintf(stderr, "Failed to acquire telemetry header %d!\n", err); + goto close_output; + } + + err = write(output, (void *) hdr, bs); + if (err != bs) { + fprintf(stderr, "Failed to flush all data to file!\n"); + goto close_output; + } + + switch (cfg.data_area) { + case 1: + full_size = (le16_to_cpu(hdr->dalb1) * bs) + offset; + break; + case 2: + full_size = (le16_to_cpu(hdr->dalb2) * bs) + offset; + break; + case 3: + full_size = (le16_to_cpu(hdr->dalb3) * bs) + offset; + break; + default: + fprintf(stderr, "Invalid data area requested\n"); + err = -EINVAL; + goto close_output; + } + + /* + * Continuously pull data until the offset hits the end of the last + * block. + */ + while (offset != full_size) { + err = nvme_get_telemetry_log(fd, page_log, 0, cfg.ctrl_init, bs, offset); + if (err < 0) { + perror("get-telemetry-log"); + break; + } else if (err > 0) { + fprintf(stderr, "Failed to acquire full telemetry log!\n"); + nvme_show_status(err); + break; + } + + err = write(output, (void *) page_log, bs); + if (err != bs) { + fprintf(stderr, "Failed to flush all data to file!\n"); + break; + } + err = 0; + offset += bs; + } + +close_output: + close(output); +free_mem: + free(hdr); + free(page_log); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int get_endurance_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct nvme_endurance_group_log endurance_log; + const char *desc = "Retrieves endurance groups log page and prints the log."; + const char *group_id = "The endurance group identifier"; + enum nvme_print_flags flags; + int err, fd; + + struct config { + char *output_format; + __u16 group_id; + }; + + struct config cfg = { + .output_format = "normal", + .group_id = 0, + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_UINT("group-id", 'g', &cfg.group_id, group_id), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + + err = nvme_endurance_log(fd, cfg.group_id, &endurance_log); + if (!err) + nvme_show_endurance_log(&endurance_log, cfg.group_id, devicename, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("endurance log"); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int get_effects_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Retrieve command effects log page and print the table."; + const char *raw = "show log in binary format"; + const char *human_readable = "show log in readable format"; + struct nvme_effects_log_page effects; + + int err, fd; + enum nvme_print_flags flags; + + struct config { + int raw_binary; + int human_readable; + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("human-readable",'H', &cfg.human_readable, human_readable), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + if (cfg.raw_binary) + flags = BINARY; + if (cfg.human_readable) + flags |= VERBOSE; + + err = nvme_effects_log(fd, &effects); + if (!err) + nvme_show_effects_log(&effects, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("effects log page"); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int get_error_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Retrieve specified number of "\ + "error log entries from a given device "\ + "in either decoded format (default) or binary."; + const char *log_entries = "number of entries to retrieve"; + const char *raw = "dump in binary format"; + struct nvme_error_log_page *err_log; + struct nvme_id_ctrl ctrl; + enum nvme_print_flags flags; + int err, fd; + + struct config { + __u32 log_entries; + int raw_binary; + char *output_format; + }; + + struct config cfg = { + .log_entries = 64, + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_UINT("log-entries", 'e', &cfg.log_entries, log_entries), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + if (cfg.raw_binary) + flags = BINARY; + + if (!cfg.log_entries) { + fprintf(stderr, "non-zero log-entries is required param\n"); + err = -EINVAL; + goto close_fd; + } + + err = nvme_identify_ctrl(fd, &ctrl); + if (err < 0) { + perror("identify controller"); + goto close_fd; + } else if (err) { + fprintf(stderr, "could not identify controller\n"); + err = -ENODEV; + goto close_fd; + } + + cfg.log_entries = min(cfg.log_entries, ctrl.elpe + 1); + err_log = calloc(cfg.log_entries, sizeof(struct nvme_error_log_page)); + if (!err_log) { + fprintf(stderr, "could not alloc buffer for error log\n"); + err = -ENOMEM; + goto close_fd; + } + + err = nvme_error_log(fd, cfg.log_entries, err_log); + if (!err) + nvme_show_error_log(err_log, cfg.log_entries, devicename, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("error log"); + free(err_log); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int get_fw_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Retrieve the firmware log for the "\ + "specified device in either decoded format (default) or binary."; + const char *raw = "use binary output"; + struct nvme_firmware_log_page fw_log; + enum nvme_print_flags flags; + int err, fd; + + struct config { + int raw_binary; + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + if (cfg.raw_binary) + flags = BINARY; + + err = nvme_fw_log(fd, &fw_log); + if (!err) + nvme_show_fw_log(&fw_log, devicename, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("fw log"); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int get_changed_ns_list_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct nvme_changed_ns_list_log changed_ns_list_log; + const char *desc = "Retrieve Changed Namespaces log for the given device "\ + "in either decoded format "\ + "(default) or binary."; + const char *raw = "output in binary format"; + enum nvme_print_flags flags; + int err, fd; + + struct config { + int raw_binary; + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + if (cfg.raw_binary) + flags = BINARY; + + err = nvme_changed_ns_list_log(fd, &changed_ns_list_log); + if (!err) + nvme_show_changed_ns_list_log(&changed_ns_list_log, devicename, + flags); + else if (err > 0) + nvme_show_status(err); + else + perror("changed ns list log"); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int get_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Retrieve desired number of bytes "\ + "from a given log on a specified device in either "\ + "hex-dump (default) or binary format"; + const char *namespace_id = "desired namespace"; + const char *log_id = "identifier of log to retrieve"; + const char *log_len = "how many bytes to retrieve"; + const char *aen = "result of the aen, use to override log id"; + const char *lsp = "log specific field"; + const char *lpo = "log page offset specifies the location within a log page from where to start returning data"; + const char *rae = "retain an asynchronous event"; + const char *raw = "output in raw format"; + const char *uuid_index = "UUID index"; + int err, fd; + + struct config { + __u32 namespace_id; + __u32 log_id; + __u32 log_len; + __u32 aen; + __u64 lpo; + __u8 lsp; + __u8 uuid_index; + int rae; + int raw_binary; + }; + + struct config cfg = { + .namespace_id = NVME_NSID_ALL, + .log_id = 0xffffffff, + .log_len = 0, + .lpo = NVME_NO_LOG_LPO, + .lsp = NVME_NO_LOG_LSP, + .rae = 0, + .uuid_index = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("log-id", 'i', &cfg.log_id, log_id), + OPT_UINT("log-len", 'l', &cfg.log_len, log_len), + OPT_UINT("aen", 'a', &cfg.aen, aen), + OPT_LONG("lpo", 'o', &cfg.lpo, lpo), + OPT_BYTE("lsp", 's', &cfg.lsp, lsp), + OPT_FLAG("rae", 'r', &cfg.rae, rae), + OPT_BYTE("uuid-index", 'U', &cfg.uuid_index, uuid_index), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (cfg.aen) { + cfg.log_len = 4096; + cfg.log_id = (cfg.aen >> 16) & 0xff; + } + + if (cfg.log_id > 0xff) { + fprintf(stderr, "Invalid log identifier: %d. Valid range: 0-255\n", cfg.log_id); + err = -EINVAL; + goto close_fd; + } + + if (!cfg.log_len) { + fprintf(stderr, "non-zero log-len is required param\n"); + err = -EINVAL; + } else { + unsigned char *log; + + log = malloc(cfg.log_len); + if (!log) { + fprintf(stderr, "could not alloc buffer for log: %s\n", + strerror(errno)); + err = -EINVAL; + goto close_fd; + } + + err = nvme_get_log14(fd, cfg.namespace_id, cfg.log_id, + cfg.lsp, cfg.lpo, 0, cfg.rae, + cfg.uuid_index, cfg.log_len, log); + if (!err) { + if (!cfg.raw_binary) { + printf("Device:%s log-id:%d namespace-id:%#x\n", + devicename, cfg.log_id, + cfg.namespace_id); + d(log, cfg.log_len, 16, 1); + } else + d_raw((unsigned char *)log, cfg.log_len); + } else if (err > 0) + nvme_show_status(err); + else + perror("log page"); + free(log); + } +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int sanitize_log(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "Retrieve sanitize log and show it."; + const char *raw = "show log in binary format"; + const char *human_readable = "show log in readable format"; + struct nvme_sanitize_log_page sanitize_log; + enum nvme_print_flags flags; + int fd, err; + + struct config { + int raw_binary; + int human_readable; + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("human-readable",'H', &cfg.human_readable, human_readable), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + if (cfg.raw_binary) + flags = BINARY; + if (cfg.human_readable) + flags |= VERBOSE; + + err = nvme_sanitize_log(fd, &sanitize_log); + if (!err) + nvme_show_sanitize_log(&sanitize_log, devicename, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("sanitize status log"); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int list_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Show controller list information for the subsystem the "\ + "given device is part of, or optionally controllers attached to a specific namespace."; + const char *controller = "controller to display"; + const char *namespace_id = "optional namespace attached to controller"; + int err, i, fd; + struct nvme_controller_list *cntlist; + + struct config { + __u16 cntid; + __u32 namespace_id; + }; + + struct config cfg = { + .cntid = 0, + }; + + OPT_ARGS(opts) = { + OPT_SHRT("cntid", 'c', &cfg.cntid, controller), + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (posix_memalign((void *)&cntlist, getpagesize(), 0x1000)) { + fprintf(stderr, "can not allocate controller list payload\n"); + err = -ENOMEM; + goto close_fd; + } + + err = nvme_identify_ctrl_list(fd, cfg.namespace_id, cfg.cntid, cntlist); + if (!err) { + __u16 num = le16_to_cpu(cntlist->num); + + for (i = 0; i < (min(num, 2048)); i++) + printf("[%4u]:%#x\n", i, le16_to_cpu(cntlist->identifier[i])); + } + else if (err > 0) + nvme_show_status(err); + else + perror("id controller list"); + + free(cntlist); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int list_ns(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "For the specified controller handle, show the "\ + "namespace list in the associated NVMe subsystem, optionally starting with a given nsid."; + const char *namespace_id = "first nsid returned list should start from"; + const char *all = "show all namespaces in the subsystem, whether attached or inactive"; + int err, i, fd; + __le32 ns_list[1024]; + + struct config { + __u32 namespace_id; + int all; + }; + + struct config cfg = { + .namespace_id = 1, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_FLAG("all", 'a', &cfg.all, all), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (!cfg.namespace_id) { + err = -EINVAL; + fprintf(stderr, "invalid nsid parameter\n"); + goto close_fd; + } + + err = nvme_identify_ns_list(fd, cfg.namespace_id - 1, !!cfg.all, + ns_list); + if (!err) { + for (i = 0; i < 1024; i++) + if (ns_list[i]) + printf("[%4u]:%#x\n", i, le32_to_cpu(ns_list[i])); + } else if (err > 0) + nvme_show_status(err); + else + perror("id namespace list"); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int delete_ns(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Delete the given namespace by "\ + "sending a namespace management command to "\ + "the provided device. All controllers should be detached from "\ + "the namespace prior to namespace deletion. A namespace ID "\ + "becomes inactive when that namespace is detached or, if "\ + "the namespace is not already inactive, once deleted."; + const char *namespace_id = "namespace to delete"; + const char *timeout = "timeout value, in milliseconds"; + int err, fd; + + struct config { + __u32 namespace_id; + __u32 timeout; + }; + + struct config cfg = { + .namespace_id = 0, + .timeout = NVME_IOCTL_TIMEOUT, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("timeout", 't', &cfg.timeout, timeout), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (!cfg.namespace_id) { + cfg.namespace_id = nvme_get_nsid(fd); + if (cfg.namespace_id == 0) { + err = -EINVAL; + goto close_fd; + } + else if (cfg.namespace_id < 0) { + err = cfg.namespace_id; + goto close_fd; + } + } + + err = nvme_ns_delete(fd, cfg.namespace_id, cfg.timeout); + if (!err) + printf("%s: Success, deleted nsid:%d\n", cmd->name, + cfg.namespace_id); + else if (err > 0) + nvme_show_status(err); + else + perror("delete namespace"); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int nvme_attach_ns(int argc, char **argv, int attach, const char *desc, struct command *cmd) +{ + int err, num, i, fd, list[2048]; + __u16 ctrlist[2048]; + + const char *namespace_id = "namespace to attach"; + const char *cont = "optional comma-sep controller id list"; + + struct config { + char *cntlist; + __u32 namespace_id; + }; + + struct config cfg = { + .cntlist = "", + .namespace_id = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_LIST("controllers", 'c', &cfg.cntlist, cont), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (!cfg.namespace_id) { + fprintf(stderr, "%s: namespace-id parameter required\n", + cmd->name); + err = -EINVAL; + goto close_fd; + } + + num = argconfig_parse_comma_sep_array(cfg.cntlist, list, 2047); + if (!num) { + fprintf(stderr, "warning: empty controller-id list will result in no actual change in namespace attachment\n"); + } + + if (num == -1) { + fprintf(stderr, "%s: controller id list is malformed\n", + cmd->name); + err = -EINVAL; + goto close_fd; + } + + for (i = 0; i < num; i++) + ctrlist[i] = (uint16_t)list[i]; + + err = nvme_ns_attachment(fd, cfg.namespace_id, num, ctrlist, attach); + + if (!err) + printf("%s: Success, nsid:%d\n", cmd->name, cfg.namespace_id); + else if (err > 0) + nvme_show_status(err); + else + perror(attach ? "attach namespace" : "detach namespace"); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int attach_ns(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Attach the given namespace to the "\ + "given controller or comma-sep list of controllers. ID of the "\ + "given namespace becomes active upon attachment to a "\ + "controller. A namespace must be attached to a controller "\ + "before IO commands may be directed to that namespace."; + return nvme_attach_ns(argc, argv, 1, desc, cmd); +} + +static int detach_ns(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Detach the given namespace from the "\ + "given controller; de-activates the given namespace's ID. A "\ + "namespace must be attached to a controller before IO "\ + "commands may be directed to that namespace."; + return nvme_attach_ns(argc, argv, 0, desc, cmd); +} + +static int create_ns(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Send a namespace management command "\ + "to the specified device to create a namespace with the given "\ + "parameters. The next available namespace ID is used for the "\ + "create operation. Note that create-ns does not attach the "\ + "namespace to a controller, the attach-ns command is needed."; + const char *nsze = "size of ns"; + const char *ncap = "capacity of ns"; + const char *flbas = "FLBA size"; + const char *dps = "data protection capabilities"; + const char *nmic = "multipath and sharing capabilities"; + const char *anagrpid = "ANA Group Identifier"; + const char *nvmsetid = "NVM Set Identifier"; + const char *timeout = "timeout value, in milliseconds"; + const char *bs = "target block size"; + + int err = 0, fd, i; + struct nvme_id_ns ns; + __u32 nsid; + + struct config { + __u64 nsze; + __u64 ncap; + __u8 flbas; + __u8 dps; + __u8 nmic; + __u32 anagrpid; + __u16 nvmsetid; + __u64 bs; + __u32 timeout; + }; + + struct config cfg = { + .flbas = 0xff, + .anagrpid = 0, + .nvmsetid = 0, + .bs = 0x00, + .timeout = NVME_IOCTL_TIMEOUT, + }; + + OPT_ARGS(opts) = { + OPT_SUFFIX("nsze", 's', &cfg.nsze, nsze), + OPT_SUFFIX("ncap", 'c', &cfg.ncap, ncap), + OPT_BYTE("flbas", 'f', &cfg.flbas, flbas), + OPT_BYTE("dps", 'd', &cfg.dps, dps), + OPT_BYTE("nmic", 'm', &cfg.nmic, nmic), + OPT_UINT("anagrp-id", 'a', &cfg.anagrpid, anagrpid), + OPT_UINT("nvmset-id", 'i', &cfg.nvmsetid, nvmsetid), + OPT_SUFFIX("block-size", 'b', &cfg.bs, bs), + OPT_UINT("timeout", 't', &cfg.timeout, timeout), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (cfg.flbas != 0xff && cfg.bs != 0x00) { + fprintf(stderr, + "Invalid specification of both FLBAS and Block Size, please specify only one\n"); + err = -EINVAL; + goto close_fd; + } + if (cfg.bs) { + if ((cfg.bs & (~cfg.bs + 1)) != cfg.bs) { + fprintf(stderr, + "Invalid value for block size (%"PRIu64"). Block size must be a power of two\n", + (uint64_t)cfg.bs); + err = -EINVAL; + goto close_fd; + } + err = nvme_identify_ns(fd, NVME_NSID_ALL, 0, &ns); + if (err) { + if (err < 0) + perror("identify-namespace"); + else { + fprintf(stderr, "identify failed\n"); + nvme_show_status(err); + } + goto close_fd; + } + for (i = 0; i < 16; ++i) { + if ((1 << ns.lbaf[i].ds) == cfg.bs && ns.lbaf[i].ms == 0) { + cfg.flbas = i; + break; + } + } + + } + if (cfg.flbas == 0xff) { + fprintf(stderr, + "FLBAS corresponding to block size %"PRIu64" not found\n", + (uint64_t)cfg.bs); + fprintf(stderr, + "Please correct block size, or specify FLBAS directly\n"); + + err = -EINVAL; + goto close_fd; + } + + err = nvme_ns_create(fd, cfg.nsze, cfg.ncap, cfg.flbas, cfg.dps, cfg.nmic, + cfg.anagrpid, cfg.nvmsetid, cfg.timeout, &nsid); + if (!err) + printf("%s: Success, created nsid:%d\n", cmd->name, nsid); + else if (err > 0) + nvme_show_status(err); + else + perror("create namespace"); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int list_subsys(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + struct nvme_topology t = { }; + enum nvme_print_flags flags; + char *subsysnqn = NULL; + const char *desc = "Retrieve information for subsystems"; + const char *verbose = "Increase output verbosity"; + __u32 ns_instance = 0; + int err; + + struct config { + char *output_format; + int verbose; + }; + + struct config cfg = { + .output_format = "normal", + .verbose = 0, + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format_no_binary), + OPT_FLAG("verbose", 'v', &cfg.verbose, verbose), + OPT_END() + }; + + err = argconfig_parse(argc, argv, desc, opts); + if (err < 0) + goto ret; + + devicename = NULL; + if (optind < argc) { + char path[512]; + int id; + + devicename = basename(argv[optind]); + if (sscanf(devicename, "nvme%dn%d", &id, &ns_instance) != 2) { + fprintf(stderr, "%s is not a NVMe namespace device\n", + argv[optind]); + err = -EINVAL; + goto ret; + } + sprintf(path, "/sys/block/%s/device", devicename); + subsysnqn = get_nvme_subsnqn(path); + if (!subsysnqn) { + fprintf(stderr, "Cannot read subsys NQN from %s\n", + devicename); + err = -EINVAL; + goto ret; + } + optind++; + } + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto free; + if (flags != JSON && flags != NORMAL) { + err = -EINVAL; + goto free; + } + if (cfg.verbose) + flags |= VERBOSE; + + err = scan_subsystems(&t, subsysnqn, ns_instance); + if (err) { + fprintf(stderr, "Failed to scan namespaces\n"); + goto free; + } + nvme_show_subsystem_list(&t, flags); +free: + free_topology(&t); + if (subsysnqn) + free(subsysnqn); +ret: + return nvme_status_to_errno(err, false); +} + +static int list(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Retrieve basic information for all NVMe namespaces"; + const char *verbose = "Increase output verbosity"; + struct nvme_topology t = { }; + enum nvme_print_flags flags; + int err = 0; + + struct config { + char *output_format; + int verbose; + }; + + struct config cfg = { + .output_format = "normal", + .verbose = 0, + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format_no_binary), + OPT_FLAG("verbose", 'v', &cfg.verbose, verbose), + OPT_END() + }; + + err = argconfig_parse(argc, argv, desc, opts); + if (err < 0) + return err; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + return err; + if (flags != JSON && flags != NORMAL) { + fprintf(stderr, "Invalid output format\n"); + return -EINVAL; + } + if (cfg.verbose) + flags |= VERBOSE; + + err = scan_subsystems(&t, NULL, 0); + if (err) { + fprintf(stderr, "Failed to scan namespaces\n"); + return err; + } + + nvme_show_list_items(&t, flags); + free_topology(&t); + return 0; +} + +int __id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin, + void (*vs)(__u8 *vs, struct json_object *root)) +{ + const char *desc = "Send an Identify Controller command to "\ + "the given device and report information about the specified "\ + "controller in human-readable or "\ + "binary format. May also return vendor-specific "\ + "controller attributes in hex-dump if requested."; + const char *vendor_specific = "dump binary vendor field"; + const char *raw = "show identify in binary format"; + const char *human_readable = "show identify in readable format"; + enum nvme_print_flags flags; + struct nvme_id_ctrl ctrl; + int err, fd; + + struct config { + int vendor_specific; + int raw_binary; + int human_readable; + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FLAG("vendor-specific", 'v', &cfg.vendor_specific, vendor_specific), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_FLAG("human-readable", 'H', &cfg.human_readable, human_readable), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + if (cfg.raw_binary) + flags = BINARY; + if (cfg.vendor_specific) + flags |= VS; + if (cfg.human_readable) + flags |= VERBOSE; + + err = nvme_identify_ctrl(fd, &ctrl); + if (!err) + __nvme_show_id_ctrl(&ctrl, flags, vs); + else if (err > 0) + nvme_show_status(err); + else + perror("identify controller"); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return __id_ctrl(argc, argv, cmd, plugin, NULL); +} + +static int ns_descs(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Send Namespace Identification Descriptors command to the "\ + "given device, returns the namespace identification descriptors "\ + "of the specific namespace in either human-readable or binary format."; + const char *raw = "show descriptors in binary format"; + const char *namespace_id = "identifier of desired namespace"; + enum nvme_print_flags flags; + int err, fd; + void *nsdescs; + + struct config { + __u32 namespace_id; + int raw_binary; + char *output_format; + }; + + struct config cfg = { + .namespace_id = 0, + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + if (cfg.raw_binary) + flags = BINARY; + + if (!cfg.namespace_id) { + cfg.namespace_id = nvme_get_nsid(fd); + if (cfg.namespace_id < 0) { + err = cfg.namespace_id; + goto close_fd; + } + } + + if (posix_memalign(&nsdescs, getpagesize(), 0x1000)) { + fprintf(stderr, "can not allocate controller list payload\n"); + err = -ENOMEM; + goto close_fd; + } + + err = nvme_identify_ns_descs(fd, cfg.namespace_id, nsdescs); + if (!err) + nvme_show_id_ns_descs(nsdescs, cfg.namespace_id, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("identify namespace"); + free(nsdescs); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int id_ns(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Send an Identify Namespace command to the "\ + "given device, returns properties of the specified namespace "\ + "in either human-readable or binary format. Can also return "\ + "binary vendor-specific namespace attributes."; + const char *force = "Return this namespace, even if not attaced (1.2 devices only)"; + const char *vendor_specific = "dump binary vendor fields"; + const char *raw = "show identify in binary format"; + const char *human_readable = "show identify in readable format"; + const char *namespace_id = "identifier of desired namespace"; + + enum nvme_print_flags flags; + struct nvme_id_ns ns; + int err, fd; + + struct config { + __u32 namespace_id; + int vendor_specific; + int raw_binary; + int human_readable; + int force; + char *output_format; + }; + + struct config cfg = { + .namespace_id = 0, + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_FLAG("force", 'f', &cfg.force, force), + OPT_FLAG("vendor-specific", 'v', &cfg.vendor_specific, vendor_specific), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("human-readable", 'H', &cfg.human_readable, human_readable), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + if (cfg.raw_binary) + flags = BINARY; + if (cfg.vendor_specific) + flags |= VS; + if (cfg.human_readable) + flags |= VERBOSE; + + if (!cfg.namespace_id) { + cfg.namespace_id = nvme_get_nsid(fd); + if (cfg.namespace_id < 0) { + err = cfg.namespace_id; + goto close_fd; + } + else if (!cfg.namespace_id) { + fprintf(stderr, + "Error: requesting namespace-id from non-block device\n"); + err = -ENOTBLK; + goto close_fd; + } + } + + err = nvme_identify_ns(fd, cfg.namespace_id, cfg.force, &ns); + if (!err) + nvme_show_id_ns(&ns, cfg.namespace_id, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("identify namespace"); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int id_ns_granularity(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Send an Identify Namespace Granularity List command to the "\ + "given device, returns namespace granularity list "\ + "in either human-readable or binary format."; + + struct nvme_id_ns_granularity_list *granularity_list; + enum nvme_print_flags flags; + int err, fd; + + struct config { + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + + if (posix_memalign((void *)&granularity_list, getpagesize(), NVME_IDENTIFY_DATA_SIZE)) { + fprintf(stderr, "can not allocate granularity list payload\n"); + err = -ENOMEM; + goto close_fd; + } + + err = nvme_identify_ns_granularity(fd, granularity_list); + if (!err) + nvme_show_id_ns_granularity_list(granularity_list, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("identify namespace granularity"); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int id_nvmset(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Send an Identify NVM Set List command to the "\ + "given device, returns entries for NVM Set identifiers greater "\ + "than or equal to the value specified CDW11.NVMSETID "\ + "in either binary format or json format"; + const char *nvmset_id = "NVM Set Identify value"; + + struct nvme_id_nvmset nvmset; + enum nvme_print_flags flags; + int err, fd; + + struct config { + __u16 nvmset_id; + char *output_format; + }; + + struct config cfg = { + .nvmset_id = 0, + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_UINT("nvmset_id", 'i', &cfg.nvmset_id, nvmset_id), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + + err = nvme_identify_nvmset(fd, cfg.nvmset_id, &nvmset); + if (!err) + nvme_show_id_nvmset(&nvmset, cfg.nvmset_id, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("identify nvm set list"); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int id_uuid(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Send an Identify UUID List command to the "\ + "given device, returns list of supported Vendor Specific UUIDs "\ + "in either human-readable or binary format."; + const char *raw = "show uuid in binary format"; + const char *human_readable = "show uuid in readable format"; + + struct nvme_id_uuid_list uuid_list; + enum nvme_print_flags flags; + int err, fd; + + struct config { + int raw_binary; + int human_readable; + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_FLAG("human-readable", 'H', &cfg.human_readable, human_readable), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + if (cfg.raw_binary) + flags = BINARY; + if (cfg.human_readable) + flags |= VERBOSE; + + err = nvme_identify_uuid(fd, &uuid_list); + if (!err) + nvme_show_id_uuid_list(&uuid_list, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("identify UUID list"); +close_fd: + close(fd); + return err; +} + +static int get_ns_id(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int err = 0, nsid, fd; + const char *desc = "Get namespce ID of a the block device."; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + nsid = nvme_get_nsid(fd); + if (nsid <= 0) { + perror(devicename); + err = errno; + goto close_fd; + } + err = 0; + printf("%s: namespace-id:%d\n", devicename, nsid); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int virtual_mgmt(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "The Virtualization Management command is supported by primary controllers "\ + "that support the Virtualization Enhancements capability. This command is used for:\n"\ + " 1. Modifying Flexible Resource allocation for the primary controller\n"\ + " 2. Assigning Flexible Resources for secondary controllers\n"\ + " 3. Setting the Online and Offline state for secondary controllers"; + const char *cntlid = "Controller Identifier(CNTLID)"; + const char *rt = "Resource Type(RT): [0,1]\n"\ + "0h: VQ Resources\n"\ + "1h: VI Resources"; + const char *act = "Action(ACT): [1,7,8,9]\n"\ + "1h: Primary Flexible\n"\ + "7h: Secondary Offline\n"\ + "8h: Secondary Assign\n"\ + "9h: Secondary Online"; + const char *nr = "Number of Controller Resources(NR)"; + int fd, err; + __u32 result; + + struct config { + int cntlid; + int rt; + int act; + __u32 cdw10; + __u32 cdw11; + }; + + struct config cfg = { + .cntlid = 0, + .rt = 0, + .act = 0, + .cdw10 = 0, + .cdw11 = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("cntlid", 'c', &cfg.cntlid, cntlid), + OPT_UINT("rt", 'r', &cfg.rt, rt), + OPT_UINT("act", 'a', &cfg.act, act), + OPT_UINT("nr", 'n', &cfg.cdw11, nr), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + cfg.cdw10 = cfg.cntlid << 16; + cfg.cdw10 = cfg.cdw10 | (cfg.rt << 8); + cfg.cdw10 = cfg.cdw10 | cfg.act; + + err = nvme_virtual_mgmt(fd, cfg.cdw10, cfg.cdw11, &result); + if (!err) { + printf("success, Number of Resources allocated:%#x\n", result); + } else if (err > 0) { + nvme_show_status(err); + } else + perror("virt-mgmt"); + + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int list_secondary_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Show secondary controller list associated with the primary controller "\ + "of the given device."; + const char *controller = "lowest controller identifier to display"; + const char *namespace_id = "optional namespace attached to controller"; + const char *num_entries = "number of entries to retrieve"; + + struct nvme_secondary_controllers_list *sc_list; + enum nvme_print_flags flags; + int err, fd; + + struct config { + __u16 cntid; + __u32 num_entries; + __u32 namespace_id; + char *output_format; + }; + + struct config cfg = { + .cntid = 0, + .namespace_id = 0, + .output_format = "normal", + .num_entries = ARRAY_SIZE(sc_list->sc_entry), + }; + + OPT_ARGS(opts) = { + OPT_SHRT("cntid", 'c', &cfg.cntid, controller), + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("num-entries", 'e', &cfg.num_entries, num_entries), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + + if (!cfg.num_entries) { + fprintf(stderr, "non-zero num-entries is required param\n"); + err = -EINVAL; + goto close_fd; + } + + if (posix_memalign((void *)&sc_list, getpagesize(), sizeof(*sc_list))) { + fprintf(stderr, "can not allocate controller list payload\n"); + err = -ENOMEM; + goto close_fd; + } + + err = nvme_identify_secondary_ctrl_list(fd, cfg.namespace_id, cfg.cntid, sc_list); + if (!err) + nvme_show_list_secondary_ctrl(sc_list, cfg.num_entries, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("id secondary controller list"); + + free(sc_list); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int device_self_test(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Implementing the device self-test feature"\ + " which provides the necessary log to determine the state of the device"; + const char *namespace_id = "Indicate the namespace in which the device self-test"\ + " has to be carried out"; + const char * self_test_code = "This field specifies the action taken by the device self-test command : "\ + "\n1h Start a short device self-test operation\n"\ + "2h Start a extended device self-test operation\n"\ + "eh Start a vendor specific device self-test operation\n"\ + "fh abort the device self-test operation\n"; + int fd, err; + + struct config { + __u32 namespace_id; + __u32 cdw10; + }; + + struct config cfg = { + .namespace_id = NVME_NSID_ALL, + .cdw10 = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("self-test-code", 's', &cfg.cdw10, self_test_code), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = nvme_self_test_start(fd, cfg.namespace_id, cfg.cdw10); + if (!err) { + if ((cfg.cdw10 & 0xf) == 0xf) + printf("Aborting device self-test operation\n"); + else + printf("Device self-test started\n"); + } else if (err > 0) { + nvme_show_status(err); + } else + perror("Device self-test"); + + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int self_test_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Retrieve the self-test log for the given device and given test "\ + "(or optionally a namespace) in either decoded format "\ + "(default) or binary."; + const char *namespace_id = "Indicate the namespace from which the self-test "\ + "log has to be obtained"; + const char *verbose = "Increase output verbosity"; + + struct nvme_self_test_log self_test_log; + enum nvme_print_flags flags; + int err, fd; + + struct config { + __u32 namespace_id; + char *output_format; + int verbose; + }; + + struct config cfg = { + .namespace_id = NVME_NSID_ALL, + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("verbose", 'v', &cfg.verbose, verbose), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + if (cfg.verbose) + flags |= VERBOSE; + + err = nvme_self_test_log(fd, cfg.namespace_id, &self_test_log); + if (!err) + nvme_show_self_test_log(&self_test_log, devicename, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("self test log"); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int get_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Read operating parameters of the "\ + "specified controller. Operating parameters are grouped "\ + "and identified by Feature Identifiers; each Feature "\ + "Identifier contains one or more attributes that may affect "\ + "behaviour of the feature. Each Feature has three possible "\ + "settings: default, saveable, and current. If a Feature is "\ + "saveable, it may be modified by set-feature. Default values "\ + "are vendor-specific and not changeable. Use set-feature to "\ + "change saveable Features."; + const char *raw = "show feature in binary format"; + const char *namespace_id = "identifier of desired namespace"; + const char *feature_id = "feature identifier"; + const char *sel = "[0-3]: current/default/saved/supported"; + const char *data_len = "buffer len if data is returned through host memory buffer"; + const char *cdw11 = "dword 11 for interrupt vector config"; + const char *human_readable = "show feature in readable format"; + int err, fd; + __u32 result; + void *buf = NULL; + + struct config { + __u32 namespace_id; + __u32 feature_id; + __u8 sel; + __u32 cdw11; + __u32 data_len; + int raw_binary; + int human_readable; + }; + + struct config cfg = { + .namespace_id = 1, + .feature_id = 0, + .sel = 0, + .cdw11 = 0, + .data_len = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("feature-id", 'f', &cfg.feature_id, feature_id), + OPT_BYTE("sel", 's', &cfg.sel, sel), + OPT_UINT("data-len", 'l', &cfg.data_len, data_len), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_UINT("cdw11", 'c', &cfg.cdw11, cdw11), + OPT_FLAG("human-readable",'H', &cfg.human_readable, human_readable), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (cfg.sel > 7) { + fprintf(stderr, "invalid 'select' param:%d\n", cfg.sel); + err = -EINVAL; + goto close_fd; + } + if (!cfg.feature_id) { + fprintf(stderr, "feature-id required param\n"); + err = -EINVAL; + goto close_fd; + } + + switch (cfg.feature_id) { + case NVME_FEAT_LBA_RANGE: + cfg.data_len = 4096; + break; + case NVME_FEAT_AUTO_PST: + cfg.data_len = 256; + break; + case NVME_FEAT_HOST_MEM_BUF: + cfg.data_len = 4096; + break; + case NVME_FEAT_HOST_ID: + cfg.data_len = 8; + /* check for Extended Host Identifier */ + if (cfg.cdw11 & 0x1) + cfg.data_len = 16; + break; + case NVME_FEAT_PLM_CONFIG: + cfg.data_len = 512; + break; + case NVME_FEAT_TIMESTAMP: + cfg.data_len = 8; + break; + case NVME_FEAT_HOST_BEHAVIOR: + cfg.data_len = 512; + break; + } + + if (cfg.sel == 3) + cfg.data_len = 0; + + if (cfg.data_len) { + if (posix_memalign(&buf, getpagesize(), cfg.data_len)) { + fprintf(stderr, "can not allocate feature payload\n"); + err = -ENOMEM; + goto close_fd; + } + memset(buf, 0, cfg.data_len); + } + + err = nvme_get_feature(fd, cfg.namespace_id, cfg.feature_id, cfg.sel, cfg.cdw11, + cfg.data_len, buf, &result); + if (!err) { + if (!cfg.raw_binary || !buf) { + printf("get-feature:%#02x (%s), %s value:%#08x\n", cfg.feature_id, + nvme_feature_to_string(cfg.feature_id), + nvme_select_to_string(cfg.sel), result); + if (cfg.sel == 3) + nvme_show_select_result(result); + else if (cfg.human_readable) + nvme_feature_show_fields(cfg.feature_id, result, buf); + else if (buf) + d(buf, cfg.data_len, 16, 1); + } else if (buf) + d_raw(buf, cfg.data_len); + } else if (err > 0) { + nvme_show_status(err); + } else + perror("get-feature"); + + if (buf) + free(buf); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int fw_download(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Copy all or part of a firmware image to "\ + "a controller for future update. Optionally, specify how "\ + "many KiB of the firmware to transfer at once. The offset will "\ + "start at 0 and automatically adjust based on xfer size "\ + "unless fw is split across multiple files. May be submitted "\ + "while outstanding commands exist on the Admin and IO "\ + "Submission Queues. Activate downloaded firmware with "\ + "fw-activate, and then reset the device to apply the downloaded firmware."; + const char *fw = "firmware file (required)"; + const char *xfer = "transfer chunksize limit"; + const char *offset = "starting dword offset, default 0"; + int err, fd, fw_fd = -1; + unsigned int fw_size; + struct stat sb; + void *fw_buf, *buf; + bool huge; + + struct config { + char *fw; + __u32 xfer; + __u32 offset; + }; + + struct config cfg = { + .fw = "", + .xfer = 4096, + .offset = 0, + }; + + OPT_ARGS(opts) = { + OPT_FILE("fw", 'f', &cfg.fw, fw), + OPT_UINT("xfer", 'x', &cfg.xfer, xfer), + OPT_UINT("offset", 'o', &cfg.offset, offset), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + fw_fd = open(cfg.fw, O_RDONLY); + cfg.offset <<= 2; + if (fw_fd < 0) { + fprintf(stderr, "Failed to open firmware file %s: %s\n", + cfg.fw, strerror(errno)); + err = -EINVAL; + goto close_fd; + } + + err = fstat(fw_fd, &sb); + if (err < 0) { + perror("fstat"); + goto close_fw_fd; + } + + fw_size = sb.st_size; + if (fw_size & 0x3) { + fprintf(stderr, "Invalid size:%d for f/w image\n", fw_size); + err = -EINVAL; + goto close_fw_fd; + } + + fw_buf = nvme_alloc(fw_size, &huge); + if (!fw_buf) { + fprintf(stderr, "No memory for f/w size:%d\n", fw_size); + err = -ENOMEM; + goto close_fw_fd; + } + + buf = fw_buf; + if (cfg.xfer == 0 || cfg.xfer % 4096) + cfg.xfer = 4096; + if (read(fw_fd, fw_buf, fw_size) != ((ssize_t)(fw_size))) { + err = -errno; + fprintf(stderr, "read :%s :%s\n", cfg.fw, strerror(errno)); + goto free; + } + + while (fw_size > 0) { + cfg.xfer = min(cfg.xfer, fw_size); + + err = nvme_fw_download(fd, cfg.offset, cfg.xfer, fw_buf); + if (err < 0) { + perror("fw-download"); + break; + } else if (err != 0) { + nvme_show_status(err); + break; + } + fw_buf += cfg.xfer; + fw_size -= cfg.xfer; + cfg.offset += cfg.xfer; + } + if (!err) + printf("Firmware download success\n"); + +free: + nvme_free(buf, huge); +close_fw_fd: + close(fw_fd); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static char *nvme_fw_status_reset_type(__u32 status) +{ + switch (status & 0x3ff) { + case NVME_SC_FW_NEEDS_CONV_RESET: return "conventional"; + case NVME_SC_FW_NEEDS_SUBSYS_RESET: return "subsystem"; + case NVME_SC_FW_NEEDS_RESET: return "any controller"; + default: return "unknown"; + } +} + +static int fw_commit(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Verify downloaded firmware image and "\ + "commit to specific firmware slot. Device is not automatically "\ + "reset following firmware activation. A reset may be issued "\ + "with an 'echo 1 > /sys/class/nvme/nvmeX/reset_controller'. "\ + "Ensure nvmeX is the device you just activated before reset."; + const char *slot = "[0-7]: firmware slot for commit action"; + const char *action = "[0-7]: commit action"; + const char *bpid = "[0,1]: boot partition identifier, if applicable (default: 0)"; + int err, fd; + + struct config { + __u8 slot; + __u8 action; + __u8 bpid; + }; + + struct config cfg = { + .slot = 0, + .action = 0, + .bpid = 0, + }; + + OPT_ARGS(opts) = { + OPT_BYTE("slot", 's', &cfg.slot, slot), + OPT_BYTE("action", 'a', &cfg.action, action), + OPT_BYTE("bpid", 'b', &cfg.bpid, bpid), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (cfg.slot > 7) { + fprintf(stderr, "invalid slot:%d\n", cfg.slot); + err = -EINVAL; + goto close_fd; + } + if (cfg.action > 7 || cfg.action == 4 || cfg.action == 5) { + fprintf(stderr, "invalid action:%d\n", cfg.action); + err = -EINVAL; + goto close_fd; + } + if (cfg.bpid > 1) { + fprintf(stderr, "invalid boot partition id:%d\n", cfg.bpid); + err = -EINVAL; + goto close_fd; + } + + err = nvme_fw_commit(fd, cfg.slot, cfg.action, cfg.bpid); + if (err < 0) + perror("fw-commit"); + else if (err != 0) + switch (err & 0x3ff) { + case NVME_SC_FW_NEEDS_CONV_RESET: + case NVME_SC_FW_NEEDS_SUBSYS_RESET: + case NVME_SC_FW_NEEDS_RESET: + printf("Success activating firmware action:%d slot:%d", + cfg.action, cfg.slot); + if (cfg.action == 6 || cfg.action == 7) + printf(" bpid:%d", cfg.bpid); + printf(", but firmware requires %s reset\n", nvme_fw_status_reset_type(err)); + break; + default: + nvme_show_status(err); + break; + } + else { + printf("Success committing firmware action:%d slot:%d", + cfg.action, cfg.slot); + if (cfg.action == 6 || cfg.action == 7) + printf(" bpid:%d", cfg.bpid); + printf("\n"); + } + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int subsystem_reset(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Resets the NVMe subsystem\n"; + int err, fd; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = nvme_subsystem_reset(fd); + if (err < 0) { + if (errno == ENOTTY) + fprintf(stderr, + "Subsystem-reset: NVM Subsystem Reset not supported.\n"); + else + perror("Subsystem-reset"); + } + + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int reset(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Resets the NVMe controller\n"; + int err, fd; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = nvme_reset_controller(fd); + if (err < 0) + perror("Reset"); + + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int ns_rescan(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Rescans the NVMe namespaces\n"; + int err, fd; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = nvme_ns_rescan(fd); + if (err < 0) + perror("Namespace Rescan"); + + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int sanitize(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Send a sanitize command."; + const char *no_dealloc_desc = "No deallocate after sanitize."; + const char *oipbp_desc = "Overwrite invert pattern between passes."; + const char *owpass_desc = "Overwrite pass count."; + const char *ause_desc = "Allow unrestricted sanitize exit."; + const char *sanact_desc = "Sanitize action."; + const char *ovrpat_desc = "Overwrite pattern."; + + int fd, ret; + + struct config { + int no_dealloc; + int oipbp; + __u8 owpass; + int ause; + __u8 sanact; + __u32 ovrpat; + }; + + struct config cfg = { + .no_dealloc = 0, + .oipbp = 0, + .owpass = 0, + .ause = 0, + .sanact = 0, + .ovrpat = 0, + }; + + OPT_ARGS(opts) = { + OPT_FLAG("no-dealloc", 'd', &cfg.no_dealloc, no_dealloc_desc), + OPT_FLAG("oipbp", 'i', &cfg.oipbp, oipbp_desc), + OPT_BYTE("owpass", 'n', &cfg.owpass, owpass_desc), + OPT_FLAG("ause", 'u', &cfg.ause, ause_desc), + OPT_BYTE("sanact", 'a', &cfg.sanact, sanact_desc), + OPT_UINT("ovrpat", 'p', &cfg.ovrpat, ovrpat_desc), + OPT_END() + }; + + ret = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + switch (cfg.sanact) { + case NVME_SANITIZE_ACT_CRYPTO_ERASE: + case NVME_SANITIZE_ACT_BLOCK_ERASE: + case NVME_SANITIZE_ACT_EXIT: + case NVME_SANITIZE_ACT_OVERWRITE: + break; + default: + fprintf(stderr, "Invalid Sanitize Action\n"); + ret = -EINVAL; + goto close_fd; + } + + if (cfg.sanact == NVME_SANITIZE_ACT_EXIT) { + if (cfg.ause || cfg.no_dealloc) { + fprintf(stderr, "SANACT is Exit Failure Mode\n"); + ret = -EINVAL; + goto close_fd; + } + } + + if (cfg.sanact == NVME_SANITIZE_ACT_OVERWRITE) { + if (cfg.owpass > 16) { + fprintf(stderr, "OWPASS out of range [0-16]\n"); + ret = -EINVAL; + goto close_fd; + } + } else { + if (cfg.owpass || cfg.oipbp || cfg.ovrpat) { + fprintf(stderr, "SANACT is not Overwrite\n"); + ret = -EINVAL; + goto close_fd; + } + } + + ret = nvme_sanitize(fd, cfg.sanact, cfg.ause, cfg.owpass, cfg.oipbp, + cfg.no_dealloc, cfg.ovrpat); + if (ret < 0) + perror("sanitize"); + else if (ret > 0) + nvme_show_status(ret); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(ret, false); +} + +static int show_registers(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Reads and shows the defined NVMe controller registers "\ + "in binary or human-readable format"; + const char *human_readable = "show info in readable format in case of "\ + "output_format == normal"; + + enum nvme_print_flags flags; + bool fabrics = true; + int fd, err; + void *bar; + + struct config { + int human_readable; + char *output_format; + }; + + struct config cfg = { + .human_readable = 0, + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("human-readable",'H', &cfg.human_readable, human_readable), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + if (cfg.human_readable) + flags |= VERBOSE; + + err = nvme_get_properties(fd, &bar); + if (err) { + bar = mmap_registers(devicename); + fabrics = false; + if (bar) + err = 0; + } + if (!bar) + goto close_fd; + + nvme_show_ctrl_registers(bar, fabrics, flags); + if (fabrics) + free(bar); + else + munmap(bar, getpagesize()); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int get_property(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Reads and shows the defined NVMe controller property "\ + "for NVMe over Fabric. Property offset must be one of:\n" + "CAP=0x0, VS=0x8, CC=0x14, CSTS=0x1c, NSSR=0x20"; + const char *offset = "offset of the requested property"; + const char *human_readable = "show property in readable format"; + + int fd, err; + uint64_t value; + + struct config { + int offset; + int human_readable; + }; + + struct config cfg = { + .offset = -1, + .human_readable = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("offset", 'o', &cfg.offset, offset), + OPT_FLAG("human-readable",'H', &cfg.human_readable, human_readable), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (cfg.offset == -1) { + fprintf(stderr, "offset required param\n"); + err = -EINVAL; + goto close_fd; + } + + err = nvme_get_property(fd, cfg.offset, &value); + if (err < 0) { + perror("get-property"); + } else if (!err) { + nvme_show_single_property(cfg.offset, value, cfg.human_readable); + } else if (err > 0) { + nvme_show_status(err); + } + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int set_property(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Writes and shows the defined NVMe controller property "\ + "for NVMe ove Fabric"; + const char *offset = "the offset of the property"; + const char *value = "the value of the property to be set"; + int fd, err; + + struct config { + int offset; + int value; + }; + + struct config cfg = { + .offset = -1, + .value = -1, + }; + + OPT_ARGS(opts) = { + OPT_UINT("offset", 'o', &cfg.offset, offset), + OPT_UINT("value", 'v', &cfg.value, value), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (cfg.offset == -1) { + fprintf(stderr, "offset required param\n"); + err = -EINVAL; + goto close_fd; + } + if (cfg.value == -1) { + fprintf(stderr, "value required param\n"); + err = -EINVAL; + goto close_fd; + } + + err = nvme_set_property(fd, cfg.offset, cfg.value); + if (err < 0) { + perror("set-property"); + } else if (!err) { + printf("set-property: %02x (%s), value: %#08x\n", cfg.offset, + nvme_register_to_string(cfg.offset), cfg.value); + } else if (err > 0) { + nvme_show_status(err); + } + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int format(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Re-format a specified namespace on the "\ + "given device. Can erase all data in namespace (user "\ + "data erase) or delete data encryption key if specified. "\ + "Can also be used to change LBAF to change the namespaces reported physical block format."; + const char *namespace_id = "identifier of desired namespace"; + const char *lbaf = "LBA format to apply (required)"; + const char *ses = "[0-2]: secure erase"; + const char *pil = "[0-1]: protection info location last/first 8 bytes of metadata"; + const char *pi = "[0-3]: protection info off/Type 1/Type 2/Type 3"; + const char *ms = "[0-1]: extended format off/on"; + const char *reset = "Automatically reset the controller after successful format"; + const char *timeout = "timeout value, in milliseconds"; + const char *bs = "target block size"; + const char *force = "The \"I know what I'm doing\" flag, skip confirmation before sending command"; + struct nvme_id_ns ns; + struct nvme_id_ctrl ctrl; + int err, fd, i; + __u8 prev_lbaf = 0; + __u8 lbads = 0; + + struct config { + __u32 namespace_id; + __u32 timeout; + __u8 lbaf; + __u8 ses; + __u8 pi; + __u8 pil; + __u8 ms; + __u64 bs; + int reset; + int force; + }; + + struct config cfg = { + .namespace_id = 0, + .timeout = 600000, + .lbaf = 0xff, + .ses = 0, + .pi = 0, + .reset = 0, + .force = 0, + .bs = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("timeout", 't', &cfg.timeout, timeout), + OPT_BYTE("lbaf", 'l', &cfg.lbaf, lbaf), + OPT_BYTE("ses", 's', &cfg.ses, ses), + OPT_BYTE("pi", 'i', &cfg.pi, pi), + OPT_BYTE("pil", 'p', &cfg.pil, pil), + OPT_BYTE("ms", 'm', &cfg.ms, ms), + OPT_FLAG("reset", 'r', &cfg.reset, reset), + OPT_FLAG("force", 'f', &cfg.force, force), + OPT_SUFFIX("block-size", 'b', &cfg.bs, bs), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (cfg.lbaf != 0xff && cfg.bs !=0) { + fprintf(stderr, + "Invalid specification of both LBAF and Block Size, please specify only one\n"); + err = -EINVAL; + goto close_fd; + } + if (cfg.bs) { + if ((cfg.bs & (~cfg.bs + 1)) != cfg.bs) { + fprintf(stderr, + "Invalid value for block size (%"PRIu64"), must be a power of two\n", + (uint64_t) cfg.bs); + err = -EINVAL; + goto close_fd; + } + } + + err = nvme_identify_ctrl(fd, &ctrl); + if (err) { + perror("identify-ctrl"); + goto close_fd; + } + + if ((ctrl.fna & 1) == 1) { + /* + * FNA bit 0 set to 1: all namespaces ... shall be configured with the same + * attributes and a format (excluding secure erase) of any namespace results in a + * format of all namespaces. + */ + cfg.namespace_id = NVME_NSID_ALL; + } else { + if (!cfg.namespace_id) { + cfg.namespace_id = nvme_get_nsid(fd); + if (cfg.namespace_id < 0) { + err = cfg.namespace_id; + goto close_fd; + } + } + } + + if (cfg.namespace_id == 0) { + fprintf(stderr, + "Invalid namespace ID, " + "specify a namespace to format or use '-n 0xffffffff' " + "to format all namespaces on this controller.\n"); + err = -EINVAL; + goto close_fd; + } + + if (cfg.namespace_id != NVME_NSID_ALL) { + err = nvme_identify_ns(fd, cfg.namespace_id, 0, &ns); + if (err) { + if (err < 0) + perror("identify-namespace"); + else { + fprintf(stderr, "identify failed\n"); + nvme_show_status(err); + } + goto close_fd; + } + prev_lbaf = ns.flbas & 0xf; + + if (cfg.bs) { + for (i = 0; i < 16; ++i) { + if ((1ULL << ns.lbaf[i].ds) == cfg.bs && + ns.lbaf[i].ms == 0) { + cfg.lbaf = i; + break; + } + } + if (cfg.lbaf == 0xff) { + fprintf(stderr, + "LBAF corresponding to block size %"PRIu64"(LBAF %u) not found\n", + (uint64_t)cfg.bs, lbads); + fprintf(stderr, + "Please correct block size, or specify LBAF directly\n"); + err = -EINVAL; + goto close_fd; + } + } else if (cfg.lbaf == 0xff) + cfg.lbaf = prev_lbaf; + } + else { + if (cfg.lbaf == 0xff) cfg.lbaf = 0; + } + + /* ses & pi checks set to 7 for forward-compatibility */ + if (cfg.ses > 7) { + fprintf(stderr, "invalid secure erase settings:%d\n", cfg.ses); + err = -EINVAL; + goto close_fd; + } + if (cfg.lbaf > 15) { + fprintf(stderr, "invalid lbaf:%d\n", cfg.lbaf); + err = -EINVAL; + goto close_fd; + } + if (cfg.pi > 7) { + fprintf(stderr, "invalid pi:%d\n", cfg.pi); + err = -EINVAL; + goto close_fd; + } + if (cfg.pil > 1) { + fprintf(stderr, "invalid pil:%d\n", cfg.pil); + err = -EINVAL; + goto close_fd; + } + if (cfg.ms > 1) { + fprintf(stderr, "invalid ms:%d\n", cfg.ms); + err = -EINVAL; + goto close_fd; + } + + if (!cfg.force) { + fprintf(stderr, "You are about to format %s, namespace %#x%s.\n", + devicename, cfg.namespace_id, + cfg.namespace_id == NVME_NSID_ALL ? "(ALL namespaces)" : ""); + nvme_show_relatives(devicename); + fprintf(stderr, "WARNING: Format may irrevocably delete this device's data.\n" + "You have 10 seconds to press Ctrl-C to cancel this operation.\n\n" + "Use the force [--force|-f] option to suppress this warning.\n"); + sleep(10); + fprintf(stderr, "Sending format operation ... \n"); + } + + err = nvme_format(fd, cfg.namespace_id, cfg.lbaf, cfg.ses, cfg.pi, + cfg.pil, cfg.ms, cfg.timeout); + if (err < 0) + perror("format"); + else if (err != 0) + nvme_show_status(err); + else { + printf("Success formatting namespace:%x\n", cfg.namespace_id); + if (cfg.lbaf != prev_lbaf && ioctl(fd, BLKRRPART) < 0) { + fprintf(stderr, "failed to re-read partition table\n"); + err = -errno; + goto close_fd; + } + + if (cfg.reset && S_ISCHR(nvme_stat.st_mode)) + nvme_reset_controller(fd); + } + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int set_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Modify the saveable or changeable "\ + "current operating parameters of the controller. Operating "\ + "parameters are grouped and identified by Feature "\ + "Identifiers. Feature settings can be applied to the entire "\ + "controller and all associated namespaces, or to only a few "\ + "namespace(s) associated with the controller. Default values "\ + "for each Feature are vendor-specific and may not be modified."\ + "Use get-feature to determine which Features are supported by "\ + "the controller and are saveable/changeable."; + const char *namespace_id = "desired namespace"; + const char *feature_id = "feature identifier (required)"; + const char *data_len = "buffer length if data required"; + const char *data = "optional file for feature data (default stdin)"; + const char *value = "new value of feature (required)"; + const char *cdw12 = "feature cdw12, if used"; + const char *save = "specifies that the controller shall save the attribute"; + int err; + __u32 result; + void *buf = NULL; + int fd, ffd = STDIN_FILENO; + + struct config { + char *file; + __u32 namespace_id; + __u32 feature_id; + __u32 value; + __u32 cdw12; + __u32 data_len; + int save; + }; + + struct config cfg = { + .file = "", + .namespace_id = 0, + .feature_id = 0, + .value = 0, + .data_len = 0, + .save = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("feature-id", 'f', &cfg.feature_id, feature_id), + OPT_UINT("value", 'v', &cfg.value, value), + OPT_UINT("cdw12", 'c', &cfg.cdw12, cdw12), + OPT_UINT("data-len", 'l', &cfg.data_len, data_len), + OPT_FILE("data", 'd', &cfg.file, data), + OPT_FLAG("save", 's', &cfg.save, save), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (!cfg.feature_id) { + fprintf(stderr, "feature-id required param\n"); + err = -EINVAL; + goto close_fd; + } + if (cfg.feature_id == NVME_FEAT_LBA_RANGE) + cfg.data_len = 4096; + if (cfg.data_len) { + if (posix_memalign(&buf, getpagesize(), cfg.data_len)) { + fprintf(stderr, "can not allocate feature payload\n"); + err = -ENOMEM; + goto close_fd; + } + memset(buf, 0, cfg.data_len); + } + + if (buf) { + if (strlen(cfg.file)) { + ffd = open(cfg.file, O_RDONLY); + if (ffd <= 0) { + fprintf(stderr, "Failed to open file %s: %s\n", + cfg.file, strerror(errno)); + err = -EINVAL; + goto free; + } + } + err = read(ffd, (void *)buf, cfg.data_len); + if (err < 0) { + err = -errno; + fprintf(stderr, "failed to read data buffer from input" + " file: %s\n", strerror(errno)); + goto close_ffd; + } + } + + err = nvme_set_feature(fd, cfg.namespace_id, cfg.feature_id, cfg.value, + cfg.cdw12, cfg.save, cfg.data_len, buf, &result); + if (err < 0) { + perror("set-feature"); + } else if (!err) { + printf("set-feature:%02x (%s), value:%#08x\n", cfg.feature_id, + nvme_feature_to_string(cfg.feature_id), cfg.value); + if (buf) { + if (cfg.feature_id == NVME_FEAT_LBA_RANGE) + nvme_show_lba_range((struct nvme_lba_range_type *)buf, + result); + else + d(buf, cfg.data_len, 16, 1); + } + } else if (err > 0) + nvme_show_status(err); + +close_ffd: + close(ffd); +free: + if (buf) + free(buf); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int sec_send(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct stat sb; + const char *desc = "Transfer security protocol data to "\ + "a controller. Security Receives for the same protocol should be "\ + "performed after Security Sends. The security protocol field "\ + "associates Security Sends (security-send) and Security Receives "\ + "(security-recv)."; + const char *file = "transfer payload"; + const char *secp = "security protocol (cf. SPC-4)"; + const char *spsp = "security-protocol-specific (cf. SPC-4)"; + const char *tl = "transfer length (cf. SPC-4)"; + const char *namespace_id = "desired namespace"; + const char *nssf = "NVMe Security Specific Field"; + int err, fd, sec_fd = -1; + void *sec_buf; + unsigned int sec_size; + __u32 result; + + struct config { + __u32 namespace_id; + char *file; + __u8 nssf; + __u8 secp; + __u16 spsp; + __u32 tl; + }; + + struct config cfg = { + .file = "", + .secp = 0, + .spsp = 0, + .tl = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_FILE("file", 'f', &cfg.file, file), + OPT_BYTE("nssf", 'N', &cfg.nssf, nssf), + OPT_BYTE("secp", 'p', &cfg.secp, secp), + OPT_SHRT("spsp", 's', &cfg.spsp, spsp), + OPT_UINT("tl", 't', &cfg.tl, tl), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + sec_fd = open(cfg.file, O_RDONLY); + if (sec_fd < 0) { + fprintf(stderr, "Failed to open %s: %s\n", + cfg.file, strerror(errno)); + err = -EINVAL; + goto close_fd; + } + + err = fstat(sec_fd, &sb); + if (err < 0) { + perror("fstat"); + goto close_sec_fd; + } + + sec_size = sb.st_size; + if (posix_memalign(&sec_buf, getpagesize(), sec_size)) { + fprintf(stderr, "No memory for security size:%d\n", sec_size); + err = -ENOMEM; + goto close_sec_fd; + } + + err = read(sec_fd, sec_buf, sec_size); + if (err < 0) { + err = -errno; + fprintf(stderr, "Failed to read data from security file" + " %s with %s\n", cfg.file, strerror(errno)); + goto free; + } + + err = nvme_sec_send(fd, cfg.namespace_id, cfg.nssf, cfg.spsp, cfg.secp, + cfg.tl, sec_size, sec_buf, &result); + if (err < 0) + perror("security-send"); + else if (err != 0) + fprintf(stderr, "NVME Security Send Command Error:%d\n", err); + else + printf("NVME Security Send Command Success:%d\n", result); + +free: + free(sec_buf); +close_sec_fd: + close(sec_fd); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int dir_send(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Set directive parameters of the "\ + "specified directive type."; + const char *raw = "show directive in binary format"; + const char *namespace_id = "identifier of desired namespace"; + const char *data_len = "buffer len (if) data is returned"; + const char *dtype = "directive type"; + const char *dspec = "directive specification associated with directive type"; + const char *doper = "directive operation"; + const char *endir = "directive enable"; + const char *ttype = "target directive type to be enabled/disabled"; + const char *human_readable = "show directive in readable format"; + int err, fd; + __u32 result; + __u32 dw12 = 0; + void *buf = NULL; + int ffd = STDIN_FILENO; + + struct config { + char *file; + __u32 namespace_id; + __u32 data_len; + __u16 dspec; + __u8 dtype; + __u8 doper; + __u16 endir; + __u8 ttype; + int raw_binary; + int human_readable; + }; + + struct config cfg = { + .file = "", + .namespace_id = 1, + .data_len = 0, + .dspec = 0, + .dtype = 0, + .ttype = 0, + .doper = 0, + .endir = 1, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("data-len", 'l', &cfg.data_len, data_len), + OPT_BYTE("dir-type", 'D', &cfg.dtype, dtype), + OPT_BYTE("target-dir", 'T', &cfg.ttype, ttype), + OPT_SHRT("dir-spec", 'S', &cfg.dspec, dspec), + OPT_BYTE("dir-oper", 'O', &cfg.doper, doper), + OPT_SHRT("endir", 'e', &cfg.endir, endir), + OPT_FLAG("human-readable",'H', &cfg.human_readable, human_readable), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + switch (cfg.dtype) { + case NVME_DIR_IDENTIFY: + switch (cfg.doper) { + case NVME_DIR_SND_ID_OP_ENABLE: + if (!cfg.ttype) { + fprintf(stderr, "target-dir required param\n"); + err = -EINVAL; + goto close_fd; + } + dw12 = cfg.ttype << 8 | cfg.endir; + break; + default: + fprintf(stderr, "invalid directive operations for Identify Directives\n"); + err = -EINVAL; + goto close_fd; + } + break; + case NVME_DIR_STREAMS: + switch (cfg.doper) { + case NVME_DIR_SND_ST_OP_REL_ID: + case NVME_DIR_SND_ST_OP_REL_RSC: + break; + default: + fprintf(stderr, "invalid directive operations for Streams Directives\n"); + err = -EINVAL; + goto close_fd; + } + break; + default: + fprintf(stderr, "invalid directive type\n"); + err = -EINVAL; + goto close_fd; + } + + + if (cfg.data_len) { + if (posix_memalign(&buf, getpagesize(), cfg.data_len)) { + err = -ENOMEM; + goto close_fd; + } + memset(buf, 0, cfg.data_len); + } + + if (buf) { + if (strlen(cfg.file)) { + ffd = open(cfg.file, O_RDONLY); + if (ffd <= 0) { + fprintf(stderr, "Failed to open file %s: %s\n", + cfg.file, strerror(errno)); + err = -EINVAL; + goto free; + } + } + err = read(ffd, (void *)buf, cfg.data_len); + if (err < 0) { + err = -errno; + fprintf(stderr, "failed to read data buffer from input" + " file %s\n", strerror(errno)); + goto close_ffd; + } + } + + err = nvme_dir_send(fd, cfg.namespace_id, cfg.dspec, cfg.dtype, cfg.doper, + cfg.data_len, dw12, buf, &result); + if (err < 0) { + perror("dir-send"); + goto close_ffd; + } + if (!err) { + printf("dir-send: type %#x, operation %#x, spec_val %#x, nsid %#x, result %#x \n", + cfg.dtype, cfg.doper, cfg.dspec, cfg.namespace_id, result); + if (buf) { + if (!cfg.raw_binary) + d(buf, cfg.data_len, 16, 1); + else + d_raw(buf, cfg.data_len); + } + } + else if (err > 0) + nvme_show_status(err); + +close_ffd: + close(ffd); +free: + if (buf) + free(buf); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int write_uncor(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int err, fd; + const char *desc = "The Write Uncorrectable command is used to set a "\ + "range of logical blocks to invalid."; + const char *namespace_id = "desired namespace"; + const char *start_block = "64-bit LBA of first block to access"; + const char *block_count = "number of blocks (zeroes based) on device to access"; + + struct config { + __u64 start_block; + __u32 namespace_id; + __u16 block_count; + }; + + struct config cfg = { + .start_block = 0, + .namespace_id = 0, + .block_count = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_SUFFIX("start-block", 's', &cfg.start_block, start_block), + OPT_SHRT("block-count", 'c', &cfg.block_count, block_count), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (!cfg.namespace_id) { + cfg.namespace_id = nvme_get_nsid(fd); + if (cfg.namespace_id < 0) { + err = cfg.namespace_id; + goto close_fd; + } + } + + err = nvme_write_uncorrectable(fd, cfg.namespace_id, cfg.start_block, + cfg.block_count); + if (err < 0) + perror("write uncorrectable"); + else if (err != 0) + nvme_show_status(err); + else + printf("NVME Write Uncorrectable Success\n"); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int write_zeroes(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int err, fd; + __u16 control = 0; + const char *desc = "The Write Zeroes command is used to set a "\ + "range of logical blocks to zero."; + const char *namespace_id = "desired namespace"; + const char *start_block = "64-bit LBA of first block to access"; + const char *block_count = "number of blocks (zeroes based) on device to access"; + const char *limited_retry = "limit media access attempts"; + const char *force = "force device to commit data before command completes"; + const char *prinfo = "PI and check field"; + const char *ref_tag = "reference tag (for end to end PI)"; + const char *app_tag_mask = "app tag mask (for end to end PI)"; + const char *app_tag = "app tag (for end to end PI)"; + const char *deac = "Set DEAC bit, requesting controller to deallocate specified logical blocks"; + + struct config { + __u64 start_block; + __u32 namespace_id; + __u32 ref_tag; + __u16 app_tag; + __u16 app_tag_mask; + __u16 block_count; + __u8 prinfo; + int deac; + int limited_retry; + int force_unit_access; + }; + + struct config cfg = { + .start_block = 0, + .block_count = 0, + .prinfo = 0, + .ref_tag = 0, + .app_tag_mask = 0, + .app_tag = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_SUFFIX("start-block", 's', &cfg.start_block, start_block), + OPT_SHRT("block-count", 'c', &cfg.block_count, block_count), + OPT_FLAG("deac", 'd', &cfg.deac, deac), + OPT_FLAG("limited-retry", 'l', &cfg.limited_retry, limited_retry), + OPT_FLAG("force-unit-access", 'f', &cfg.force_unit_access, force), + OPT_BYTE("prinfo", 'p', &cfg.prinfo, prinfo), + OPT_UINT("ref-tag", 'r', &cfg.ref_tag, ref_tag), + OPT_SHRT("app-tag-mask", 'm', &cfg.app_tag_mask, app_tag_mask), + OPT_SHRT("app-tag", 'a', &cfg.app_tag, app_tag), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (cfg.prinfo > 0xf) { + err = -EINVAL; + goto close_fd; + } + + control |= (cfg.prinfo << 10); + if (cfg.limited_retry) + control |= NVME_RW_LR; + if (cfg.force_unit_access) + control |= NVME_RW_FUA; + if (cfg.deac) + control |= NVME_RW_DEAC; + if (!cfg.namespace_id) { + cfg.namespace_id = nvme_get_nsid(fd); + if (cfg.namespace_id < 0) { + err = cfg.namespace_id; + goto close_fd; + } + } + + err = nvme_write_zeros(fd, cfg.namespace_id, cfg.start_block, cfg.block_count, + control, cfg.ref_tag, cfg.app_tag, cfg.app_tag_mask); + if (err < 0) + perror("write-zeroes"); + else if (err != 0) + nvme_show_status(err); + else + printf("NVME Write Zeroes Success\n"); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int dsm(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "The Dataset Management command is used by the host to "\ + "indicate attributes for ranges of logical blocks. This includes attributes "\ + "for discarding unused blocks, data read and write frequency, access size, and other "\ + "information that may be used to optimize performance and reliability."; + const char *namespace_id = "identifier of desired namespace"; + const char *blocks = "Comma separated list of the number of blocks in each range"; + const char *starting_blocks = "Comma separated list of the starting block in each range"; + const char *context_attrs = "Comma separated list of the context attributes in each range"; + const char *ad = "Attribute Deallocate"; + const char *idw = "Attribute Integral Dataset for Write"; + const char *idr = "Attribute Integral Dataset for Read"; + const char *cdw11 = "All the command DWORD 11 attributes. Use instead of specifying individual attributes"; + + int err, fd; + uint16_t nr, nc, nb, ns; + int ctx_attrs[256] = {0,}; + int nlbs[256] = {0,}; + unsigned long long slbas[256] = {0,}; + struct nvme_dsm_range *dsm; + + struct config { + char *ctx_attrs; + char *blocks; + char *slbas; + int ad; + int idw; + int idr; + __u32 cdw11; + __u32 namespace_id; + }; + + struct config cfg = { + .ctx_attrs = "", + .blocks = "", + .slbas = "", + .namespace_id = 0, + .ad = 0, + .idw = 0, + .idr = 0, + .cdw11 = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_LIST("ctx-attrs", 'a', &cfg.ctx_attrs, context_attrs), + OPT_LIST("blocks", 'b', &cfg.blocks, blocks), + OPT_LIST("slbs", 's', &cfg.slbas, starting_blocks), + OPT_FLAG("ad", 'd', &cfg.ad, ad), + OPT_FLAG("idw", 'w', &cfg.idw, idw), + OPT_FLAG("idr", 'r', &cfg.idr, idr), + OPT_UINT("cdw11", 'c', &cfg.cdw11, cdw11), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + nc = argconfig_parse_comma_sep_array(cfg.ctx_attrs, ctx_attrs, ARRAY_SIZE(ctx_attrs)); + nb = argconfig_parse_comma_sep_array(cfg.blocks, nlbs, ARRAY_SIZE(nlbs)); + ns = argconfig_parse_comma_sep_array_long(cfg.slbas, slbas, ARRAY_SIZE(slbas)); + nr = max(nc, max(nb, ns)); + if (!nr || nr > 256) { + fprintf(stderr, "No range definition provided\n"); + err = -EINVAL; + goto close_fd; + } + + if (!cfg.namespace_id) { + cfg.namespace_id = nvme_get_nsid(fd); + if (cfg.namespace_id < 0) { + err = cfg.namespace_id; + goto close_fd; + } + } + if (!cfg.cdw11) + cfg.cdw11 = (cfg.ad << 2) | (cfg.idw << 1) | (cfg.idr << 0); + + dsm = nvme_setup_dsm_range((__u32 *)ctx_attrs, (__u32 *)nlbs, (__u64 *)slbas, nr); + if (!dsm) { + fprintf(stderr, "failed to allocate data set payload\n"); + err = -ENOMEM; + goto close_fd; + } + + err = nvme_dsm(fd, cfg.namespace_id, cfg.cdw11, dsm, nr); + if (err < 0) + perror("data-set management"); + else if (err != 0) + nvme_show_status(err); + else + printf("NVMe DSM: success\n"); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int flush(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Commit data and metadata associated with "\ + "given namespaces to nonvolatile media. Applies to all commands "\ + "finished before the flush was submitted. Additional data may also be "\ + "flushed by the controller, from any namespace, depending on controller and "\ + "associated namespace status."; + const char *namespace_id = "identifier of desired namespace"; + int err, fd; + + struct config { + __u32 namespace_id; + }; + + struct config cfg = { + .namespace_id = NVME_NSID_ALL, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (!cfg.namespace_id) { + cfg.namespace_id = nvme_get_nsid(fd); + if (cfg.namespace_id < 0) { + err = cfg.namespace_id; + goto close_fd; + } + } + + err = nvme_flush(fd, cfg.namespace_id); + if (err < 0) + perror("flush"); + else if (err != 0) + nvme_show_status(err); + else + printf("NVMe Flush: success\n"); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int resv_acquire(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Obtain a reservation on a given "\ + "namespace. Only one reservation is allowed at a time on a "\ + "given namespace, though multiple controllers may register "\ + "with that namespace. Namespace reservation will abort with "\ + "status Reservation Conflict if the given namespace is "\ + "already reserved."; + const char *namespace_id = "identifier of desired namespace"; + const char *crkey = "current reservation key"; + const char *prkey = "pre-empt reservation key"; + const char *rtype = "reservation type"; + const char *racqa = "reservation acquiry action"; + const char *iekey = "ignore existing res. key"; + int err, fd; + + struct config { + __u32 namespace_id; + __u64 crkey; + __u64 prkey; + __u8 rtype; + __u8 racqa; + int iekey; + }; + + struct config cfg = { + .namespace_id = 0, + .crkey = 0, + .prkey = 0, + .rtype = 0, + .racqa = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_LONG("crkey", 'c', &cfg.crkey, crkey), + OPT_LONG("prkey", 'p', &cfg.prkey, prkey), + OPT_BYTE("rtype", 't', &cfg.rtype, rtype), + OPT_BYTE("racqa", 'a', &cfg.racqa, racqa), + OPT_FLAG("iekey", 'i', &cfg.iekey, iekey), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (!cfg.namespace_id) { + cfg.namespace_id = nvme_get_nsid(fd); + if (cfg.namespace_id < 0) { + err = cfg.namespace_id; + goto close_fd; + } + } + if (cfg.racqa > 7) { + fprintf(stderr, "invalid racqa:%d\n", cfg.racqa); + err = -EINVAL; + goto close_fd; + } + + err = nvme_resv_acquire(fd, cfg.namespace_id, cfg.rtype, cfg.racqa, + !!cfg.iekey, cfg.crkey, cfg.prkey); + if (err < 0) + perror("reservation acquire"); + else if (err != 0) + nvme_show_status(err); + else + printf("NVME Reservation Acquire success\n"); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int resv_register(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Register, de-register, or "\ + "replace a controller's reservation on a given namespace. "\ + "Only one reservation at a time is allowed on any namespace."; + const char *namespace_id = "identifier of desired namespace"; + const char *crkey = "current reservation key"; + const char *iekey = "ignore existing res. key"; + const char *nrkey = "new reservation key"; + const char *rrega = "reservation registration action"; + const char *cptpl = "change persistence through power loss setting"; + int err, fd; + + struct config { + __u32 namespace_id; + __u64 crkey; + __u64 nrkey; + __u8 rrega; + __u8 cptpl; + int iekey; + }; + + struct config cfg = { + .namespace_id = 0, + .crkey = 0, + .nrkey = 0, + .rrega = 0, + .cptpl = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_LONG("crkey", 'c', &cfg.crkey, crkey), + OPT_LONG("nrkey", 'k', &cfg.nrkey, nrkey), + OPT_BYTE("rrega", 'r', &cfg.rrega, rrega), + OPT_BYTE("cptpl", 'p', &cfg.cptpl, cptpl), + OPT_FLAG("iekey", 'i', &cfg.iekey, iekey), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (!cfg.namespace_id) { + cfg.namespace_id = nvme_get_nsid(fd); + if (cfg.namespace_id < 0) { + err = cfg.namespace_id; + goto close_fd; + } + } + if (cfg.cptpl > 3) { + fprintf(stderr, "invalid cptpl:%d\n", cfg.cptpl); + err = -EINVAL; + goto close_fd; + } + + if (cfg.rrega > 7) { + fprintf(stderr, "invalid rrega:%d\n", cfg.rrega); + err = -EINVAL; + goto close_fd; + } + + err = nvme_resv_register(fd, cfg.namespace_id, cfg.rrega, cfg.cptpl, + !!cfg.iekey, cfg.crkey, cfg.nrkey); + if (err < 0) + perror("reservation register"); + else if (err != 0) + nvme_show_status(err); + else + printf("NVME Reservation success\n"); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int resv_release(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Releases reservation held on a "\ + "namespace by the given controller. If rtype != current reservation"\ + "type, release will fails. If the given controller holds no "\ + "reservation on the namespace or is not the namespace's current "\ + "reservation holder, the release command completes with no "\ + "effect. If the reservation type is not Write Exclusive or "\ + "Exclusive Access, all registrants on the namespace except "\ + "the issuing controller are notified."; + const char *namespace_id = "desired namespace"; + const char *crkey = "current reservation key"; + const char *iekey = "ignore existing res. key"; + const char *rtype = "reservation type"; + const char *rrela = "reservation release action"; + int err, fd; + + struct config { + __u32 namespace_id; + __u64 crkey; + __u8 rtype; + __u8 rrela; + __u8 iekey; + }; + + struct config cfg = { + .namespace_id = 0, + .crkey = 0, + .rtype = 0, + .rrela = 0, + .iekey = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_LONG("crkey", 'c', &cfg.crkey, crkey), + OPT_BYTE("rtype", 't', &cfg.rtype, rtype), + OPT_BYTE("rrela", 'a', &cfg.rrela, rrela), + OPT_FLAG("iekey", 'i', &cfg.iekey, iekey), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (!cfg.namespace_id) { + cfg.namespace_id = nvme_get_nsid(fd); + if (cfg.namespace_id < 0) { + err = cfg.namespace_id; + goto close_fd; + } + } + if (cfg.rrela > 7) { + fprintf(stderr, "invalid rrela:%d\n", cfg.rrela); + err = -EINVAL; + goto close_fd; + } + + err = nvme_resv_release(fd, cfg.namespace_id, cfg.rtype, cfg.rrela, + !!cfg.iekey, cfg.crkey); + if (err < 0) + perror("reservation release"); + else if (err != 0) + nvme_show_status(err); + else + printf("NVME Reservation Release success\n"); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int resv_report(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Returns Reservation Status data "\ + "structure describing any existing reservations on and the "\ + "status of a given namespace. Namespace Reservation Status "\ + "depends on the number of controllers registered for that "\ + "namespace."; + const char *namespace_id = "identifier of desired namespace"; + const char *numd = "number of dwords to transfer"; + const char *cdw11 = "command dword 11 value"; + const char *raw = "dump output in binary format"; + + struct nvme_reservation_status *status; + enum nvme_print_flags flags; + int err, fd, size; + + struct config { + __u32 namespace_id; + __u32 numd; + __u32 cdw11; + int raw_binary; + char *output_format; + }; + + struct config cfg = { + .namespace_id = 0, + .numd = 0, + .cdw11 = 0, + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("numd", 'd', &cfg.numd, numd), + OPT_UINT("cdw11", 'c', &cfg.cdw11, cdw11), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + if (cfg.raw_binary) + flags = BINARY; + + if (!cfg.namespace_id) { + cfg.namespace_id = nvme_get_nsid(fd); + if (cfg.namespace_id < 0) { + err = cfg.namespace_id; + goto close_fd; + } + } + + if (!cfg.numd || cfg.numd >= (0x1000 >> 2)) + cfg.numd = (0x1000 >> 2) - 1; + if (cfg.numd < 3) + cfg.numd = 3; + + size = (cfg.numd + 1) << 2; + + if (posix_memalign((void **)&status, getpagesize(), size)) { + fprintf(stderr, "No memory for resv report:%d\n", size); + err = -ENOMEM; + goto close_fd; + } + memset(status, 0, size); + + err = nvme_resv_report(fd, cfg.namespace_id, cfg.numd, cfg.cdw11, status); + if (!err) + nvme_show_resv_report(status, size, cfg.cdw11, flags); + else if (err > 0) + fprintf(stderr, "NVME IO command error:%04x\n", err); + else + perror("reservation report"); + free(status); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static unsigned long long elapsed_utime(struct timeval start_time, + struct timeval end_time) +{ + unsigned long long err = (end_time.tv_sec - start_time.tv_sec) * 1000000 + + (end_time.tv_usec - start_time.tv_usec); + return err; +} + +static int submit_io(int opcode, char *command, const char *desc, + int argc, char **argv) +{ + struct timeval start_time, end_time; + void *buffer, *mbuffer = NULL; + int err = 0; + int dfd, mfd, fd; + int flags = opcode & 1 ? O_RDONLY : O_WRONLY | O_CREAT; + int mode = S_IRUSR | S_IWUSR |S_IRGRP | S_IWGRP| S_IROTH; + __u16 control = 0; + __u32 dsmgmt = 0; + int phys_sector_size = 0; + long long buffer_size = 0; + bool huge; + + const char *start_block = "64-bit addr of first block to access"; + const char *block_count = "number of blocks (zeroes based) on device to access"; + const char *data_size = "size of data in bytes"; + const char *metadata_size = "size of metadata in bytes"; + const char *ref_tag = "reference tag (for end to end PI)"; + const char *data = "data file"; + const char *metadata = "metadata file"; + const char *prinfo = "PI and check field"; + const char *app_tag_mask = "app tag mask (for end to end PI)"; + const char *app_tag = "app tag (for end to end PI)"; + const char *limited_retry = "limit num. media access attempts"; + const char *latency = "output latency statistics"; + const char *force = "force device to commit data before command completes"; + const char *show = "show command before sending"; + const char *dry = "show command instead of sending"; + const char *dtype = "directive type (for write-only)"; + const char *dspec = "directive specific (for write-only)"; + const char *dsm = "dataset management attributes (lower 16 bits)"; + + struct config { + __u64 start_block; + __u16 block_count; + __u64 data_size; + __u64 metadata_size; + __u32 ref_tag; + char *data; + char *metadata; + __u8 prinfo; + __u8 dtype; + __u16 dspec; + __u16 dsmgmt; + __u16 app_tag_mask; + __u16 app_tag; + int limited_retry; + int force_unit_access; + int show; + int dry_run; + int latency; + }; + + struct config cfg = { + .start_block = 0, + .block_count = 0, + .data_size = 0, + .metadata_size = 0, + .ref_tag = 0, + .data = "", + .metadata = "", + .prinfo = 0, + .app_tag_mask = 0, + .app_tag = 0, + }; + + OPT_ARGS(opts) = { + OPT_SUFFIX("start-block", 's', &cfg.start_block, start_block), + OPT_SHRT("block-count", 'c', &cfg.block_count, block_count), + OPT_SUFFIX("data-size", 'z', &cfg.data_size, data_size), + OPT_SUFFIX("metadata-size", 'y', &cfg.metadata_size, metadata_size), + OPT_UINT("ref-tag", 'r', &cfg.ref_tag, ref_tag), + OPT_FILE("data", 'd', &cfg.data, data), + OPT_FILE("metadata", 'M', &cfg.metadata, metadata), + OPT_BYTE("prinfo", 'p', &cfg.prinfo, prinfo), + OPT_SHRT("app-tag-mask", 'm', &cfg.app_tag_mask, app_tag_mask), + OPT_SHRT("app-tag", 'a', &cfg.app_tag, app_tag), + OPT_FLAG("limited-retry", 'l', &cfg.limited_retry, limited_retry), + OPT_FLAG("force-unit-access", 'f', &cfg.force_unit_access, force), + OPT_BYTE("dir-type", 'T', &cfg.dtype, dtype), + OPT_SHRT("dir-spec", 'S', &cfg.dspec, dspec), + OPT_SHRT("dsm", 'D', &cfg.dsmgmt, dsm), + OPT_FLAG("show-command", 'v', &cfg.show, show), + OPT_FLAG("dry-run", 'w', &cfg.dry_run, dry), + OPT_FLAG("latency", 't', &cfg.latency, latency), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + dfd = mfd = opcode & 1 ? STDIN_FILENO : STDOUT_FILENO; + if (cfg.prinfo > 0xf) { + err = -EINVAL; + goto close_fd; + } + + dsmgmt = cfg.dsmgmt; + control |= (cfg.prinfo << 10); + if (cfg.limited_retry) + control |= NVME_RW_LR; + if (cfg.force_unit_access) + control |= NVME_RW_FUA; + if (cfg.dtype) { + if (cfg.dtype > 0xf) { + fprintf(stderr, "Invalid directive type, %x\n", + cfg.dtype); + err = -EINVAL; + goto close_fd; + } + control |= cfg.dtype << 4; + dsmgmt |= ((__u32)cfg.dspec) << 16; + } + + if (strlen(cfg.data)) { + dfd = open(cfg.data, flags, mode); + if (dfd < 0) { + perror(cfg.data); + err = -EINVAL; + goto close_fd; + } + mfd = dfd; + } + if (strlen(cfg.metadata)) { + mfd = open(cfg.metadata, flags, mode); + if (mfd < 0) { + perror(cfg.metadata); + err = -EINVAL; + goto close_dfd; + } + } + + if (!cfg.data_size) { + fprintf(stderr, "data size not provided\n"); + err = -EINVAL; + goto close_mfd; + } + + if (ioctl(fd, BLKPBSZGET, &phys_sector_size) < 0) + goto close_mfd; + + buffer_size = (cfg.block_count + 1) * phys_sector_size; + if (cfg.data_size < buffer_size) { + fprintf(stderr, "Rounding data size to fit block count (%lld bytes)\n", + buffer_size); + } else { + buffer_size = cfg.data_size; + } + + buffer = nvme_alloc(buffer_size, &huge); + if (!buffer) { + fprintf(stderr, "can not allocate io payload\n"); + err = -ENOMEM; + goto close_mfd; + } + + if (cfg.metadata_size) { + mbuffer = malloc(cfg.metadata_size); + if (!mbuffer) { + fprintf(stderr, "can not allocate io metadata " + "payload: %s\n", strerror(errno)); + err = -ENOMEM; + goto free_buffer; + } + memset(mbuffer, 0, cfg.metadata_size); + } + + if ((opcode & 1)) { + err = read(dfd, (void *)buffer, cfg.data_size); + if (err < 0) { + err = -errno; + fprintf(stderr, "failed to read data buffer from input" + " file %s\n", strerror(errno)); + goto free_mbuffer; + } + } + + if ((opcode & 1) && cfg.metadata_size) { + err = read(mfd, (void *)mbuffer, cfg.metadata_size); + if (err < 0) { + err = -errno; + fprintf(stderr, "failed to read meta-data buffer from" + " input file %s\n", strerror(errno)); + goto free_mbuffer; + } + } + + if (cfg.show) { + printf("opcode : %02x\n", opcode); + printf("flags : %02x\n", 0); + printf("control : %04x\n", control); + printf("nblocks : %04x\n", cfg.block_count); + printf("rsvd : %04x\n", 0); + printf("metadata : %"PRIx64"\n", (uint64_t)(uintptr_t)mbuffer); + printf("addr : %"PRIx64"\n", (uint64_t)(uintptr_t)buffer); + printf("slba : %"PRIx64"\n", (uint64_t)cfg.start_block); + printf("dsmgmt : %08x\n", dsmgmt); + printf("reftag : %08x\n", cfg.ref_tag); + printf("apptag : %04x\n", cfg.app_tag); + printf("appmask : %04x\n", cfg.app_tag_mask); + } + if (cfg.dry_run) + goto free_mbuffer; + + gettimeofday(&start_time, NULL); + err = nvme_io(fd, opcode, cfg.start_block, cfg.block_count, control, dsmgmt, + cfg.ref_tag, cfg.app_tag, cfg.app_tag_mask, buffer, mbuffer); + gettimeofday(&end_time, NULL); + if (cfg.latency) + printf(" latency: %s: %llu us\n", + command, elapsed_utime(start_time, end_time)); + if (err < 0) + perror("submit-io"); + else if (err) + nvme_show_status(err); + else { + if (!(opcode & 1) && write(dfd, (void *)buffer, cfg.data_size) < 0) { + fprintf(stderr, "write: %s: failed to write buffer to output file\n", + strerror(errno)); + err = -EINVAL; + } else if (!(opcode & 1) && cfg.metadata_size && + write(mfd, (void *)mbuffer, cfg.metadata_size) < 0) { + fprintf(stderr, "write: %s: failed to write meta-data buffer to output file\n", + strerror(errno)); + err = -EINVAL; + } else + fprintf(stderr, "%s: Success\n", command); + } + +free_mbuffer: + if (cfg.metadata_size) + free(mbuffer); +free_buffer: + nvme_free(buffer, huge); +close_mfd: + if (strlen(cfg.metadata)) + close(mfd); +close_dfd: + close(dfd); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int compare(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Compare specified logical blocks on "\ + "device with specified data buffer; return failure if buffer "\ + "and block(s) are dissimilar"; + return submit_io(nvme_cmd_compare, "compare", desc, argc, argv); +} + +static int read_cmd(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Copy specified logical blocks on the given "\ + "device to specified data buffer (default buffer is stdout)."; + return submit_io(nvme_cmd_read, "read", desc, argc, argv); +} + +static int write_cmd(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Copy from provided data buffer (default "\ + "buffer is stdin) to specified logical blocks on the given "\ + "device."; + return submit_io(nvme_cmd_write, "write", desc, argc, argv); +} + +static int verify_cmd(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int err, fd; + __u16 control = 0; + const char *desc = "Verify specified logical blocks on the given device."; + const char *namespace_id = "desired namespace"; + const char *start_block = "64-bit LBA of first block to access"; + const char *block_count = "number of blocks (zeroes based) on device to access"; + const char *limited_retry = "limit media access attempts"; + const char *force = "force device to commit cached data before performing the verify operation"; + const char *prinfo = "PI and check field"; + const char *ref_tag = "reference tag (for end to end PI)"; + const char *app_tag_mask = "app tag mask (for end to end PI)"; + const char *app_tag = "app tag (for end to end PI)"; + + struct config { + __u64 start_block; + __u32 namespace_id; + __u32 ref_tag; + __u16 app_tag; + __u16 app_tag_mask; + __u16 block_count; + __u8 prinfo; + int limited_retry; + int force_unit_access; + }; + + struct config cfg = { + .namespace_id = 0, + .start_block = 0, + .block_count = 0, + .prinfo = 0, + .ref_tag = 0, + .app_tag = 0, + .app_tag_mask = 0, + .limited_retry = 0, + .force_unit_access = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_SUFFIX("start-block", 's', &cfg.start_block, start_block), + OPT_SHRT("block-count", 'c', &cfg.block_count, block_count), + OPT_FLAG("limited-retry", 'l', &cfg.limited_retry, limited_retry), + OPT_FLAG("force-unit-access", 'f', &cfg.force_unit_access, force), + OPT_BYTE("prinfo", 'p', &cfg.prinfo, prinfo), + OPT_UINT("ref-tag", 'r', &cfg.ref_tag, ref_tag), + OPT_SHRT("app-tag", 'a', &cfg.app_tag, app_tag), + OPT_SHRT("app-tag-mask", 'm', &cfg.app_tag_mask, app_tag_mask), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto err; + + if (cfg.prinfo > 0xf) { + err = EINVAL; + goto close_fd; + } + + control |= (cfg.prinfo << 10); + if (cfg.limited_retry) + control |= NVME_RW_LR; + if (cfg.force_unit_access) + control |= NVME_RW_FUA; + + if (!cfg.namespace_id) { + cfg.namespace_id = nvme_get_nsid(fd); + if (cfg.namespace_id < 0) { + err = cfg.namespace_id; + goto close_fd; + } + } + + err = nvme_verify(fd, cfg.namespace_id, cfg.start_block, cfg.block_count, + control, cfg.ref_tag, cfg.app_tag, cfg.app_tag_mask); + if (err < 0) + perror("verify"); + else if (err != 0) + nvme_show_status(err); + else + printf("NVME Verify Success\n"); + +close_fd: + close(fd); +err: + return err; +} + +static int sec_recv(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Obtain results of one or more "\ + "previously submitted security-sends. Results, and association "\ + "between Security Send and Receive, depend on the security "\ + "protocol field as they are defined by the security protocol "\ + "used. A Security Receive must follow a Security Send made with "\ + "the same security protocol."; + const char *size = "size of buffer (prints to stdout on success)"; + const char *secp = "security protocol (cf. SPC-4)"; + const char *spsp = "security-protocol-specific (cf. SPC-4)"; + const char *al = "allocation length (cf. SPC-4)"; + const char *raw = "dump output in binary format"; + const char *namespace_id = "desired namespace"; + const char *nssf = "NVMe Security Specific Field"; + int err, fd; + void *sec_buf = NULL; + __u32 result; + + struct config { + __u32 namespace_id; + __u32 size; + __u8 secp; + __u8 nssf; + __u16 spsp; + __u32 al; + int raw_binary; + }; + + struct config cfg = { + .size = 0, + .secp = 0, + .spsp = 0, + .al = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("size", 'x', &cfg.size, size), + OPT_BYTE("nssf", 'N', &cfg.nssf, nssf), + OPT_BYTE("secp", 'p', &cfg.secp, secp), + OPT_SHRT("spsp", 's', &cfg.spsp, spsp), + OPT_UINT("al", 't', &cfg.al, al), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (cfg.size) { + if (posix_memalign(&sec_buf, getpagesize(), cfg.size)) { + fprintf(stderr, "No memory for security size:%d\n", + cfg.size); + err = -ENOMEM; + goto close_fd; + } + } + + err = nvme_sec_recv(fd, cfg.namespace_id, cfg.nssf, cfg.spsp, + cfg.secp, cfg.al, cfg.size, sec_buf, &result); + if (err < 0) + perror("security receive"); + else if (err != 0) + fprintf(stderr, "NVME Security Receive Command Error:%d\n", + err); + else { + if (!cfg.raw_binary) { + printf("NVME Security Receive Command Success:%d\n", + result); + d(sec_buf, cfg.size, 16, 1); + } else if (cfg.size) + d_raw((unsigned char *)sec_buf, cfg.size); + } + + free(sec_buf); + +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int get_lba_status(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "Information about potentially unrecoverable LBAs."; + const char *slba = "Starting LBA(SLBA) in 64-bit address of the first"\ + " logical block addressed by this command"; + const char *mndw = "Maximum Number of Dwords(MNDW) specifies maximum"\ + " number of dwords to return"; + const char *atype = "Action Type(ATYPE) specifies the mechanism the"\ + " the controller uses in determining the LBA"\ + " Status Descriptors to return."; + const char *rl = "Range Length(RL) specifies the length of the range"\ + " of contiguous LBAs beginning at SLBA"; + + enum nvme_print_flags flags; + unsigned long buf_len; + int err, fd; + void *buf; + + struct config { + __u64 slba; + __u32 mndw; + __u8 atype; + __u16 rl; + char *output_format; + }; + + struct config cfg = { + .slba = 0, + .mndw = 0, + .atype = 0, + .rl = 0, + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_SUFFIX("start-lba", 's', &cfg.slba, slba), + OPT_UINT("max-dw", 'm', &cfg.mndw, mndw), + OPT_BYTE("action", 'a', &cfg.atype, atype), + OPT_SHRT("range-len", 'l', &cfg.rl, rl), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto err; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + + if (!cfg.atype) { + fprintf(stderr, "action type (--action) has to be given\n"); + err = -EINVAL; + goto close_fd; + } + + buf_len = (cfg.mndw + 1) * 4; + buf = calloc(1, buf_len); + if (!buf) { + err = -ENOMEM; + goto close_fd; + } + + err = nvme_get_lba_status(fd, cfg.slba, cfg.mndw, cfg.atype, cfg.rl, + buf); + if (!err) + nvme_show_lba_status(buf, buf_len, flags); + else if (err > 0) + fprintf(stderr, "NVME command error:%04x\n", err); + else + perror("get lba status"); + free(buf); +close_fd: + close(fd); +err: + return nvme_status_to_errno(err, false); +} + +static int dir_receive(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Read directive parameters of the "\ + "specified directive type."; + const char *raw = "show directive in binary format"; + const char *namespace_id = "identifier of desired namespace"; + const char *data_len = "buffer len (if) data is returned"; + const char *dtype = "directive type"; + const char *dspec = "directive specification associated with directive type"; + const char *doper = "directive operation"; + const char *nsr = "namespace stream requested"; + const char *human_readable = "show directive in readable format"; + + enum nvme_print_flags flags = NORMAL; + int err, fd; + __u32 result; + __u32 dw12 = 0; + void *buf = NULL; + + struct config { + __u32 namespace_id; + __u32 data_len; + __u16 dspec; + __u8 dtype; + __u8 doper; + __u16 nsr; /* dw12 for NVME_DIR_ST_RCVOP_STATUS */ + int raw_binary; + int human_readable; + }; + + struct config cfg = { + .namespace_id = 1, + .data_len = 0, + .dspec = 0, + .dtype = 0, + .doper = 0, + .nsr = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("data-len", 'l', &cfg.data_len, data_len), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_BYTE("dir-type", 'D', &cfg.dtype, dtype), + OPT_SHRT("dir-spec", 'S', &cfg.dspec, dspec), + OPT_BYTE("dir-oper", 'O', &cfg.doper, doper), + OPT_SHRT("req-resource", 'r', &cfg.nsr, nsr), + OPT_FLAG("human-readable",'H', &cfg.human_readable, human_readable), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (cfg.human_readable) + flags |= VERBOSE; + if (cfg.raw_binary) + flags = BINARY; + + switch (cfg.dtype) { + case NVME_DIR_IDENTIFY: + switch (cfg.doper) { + case NVME_DIR_RCV_ID_OP_PARAM: + if (!cfg.data_len) + cfg.data_len = 4096; + break; + default: + fprintf(stderr, "invalid directive operations for Identify Directives\n"); + err = -EINVAL; + goto close_fd; + } + break; + case NVME_DIR_STREAMS: + switch (cfg.doper) { + case NVME_DIR_RCV_ST_OP_PARAM: + if (!cfg.data_len) + cfg.data_len = 32; + break; + case NVME_DIR_RCV_ST_OP_STATUS: + if (!cfg.data_len) + cfg.data_len = 128 * 1024; + break; + case NVME_DIR_RCV_ST_OP_RESOURCE: + dw12 = cfg.nsr; + break; + default: + fprintf(stderr, "invalid directive operations for Streams Directives\n"); + err = -EINVAL; + goto close_fd; + } + break; + default: + fprintf(stderr, "invalid directive type\n"); + err = -EINVAL; + goto close_fd; + } + + if (cfg.data_len) { + if (posix_memalign(&buf, getpagesize(), cfg.data_len)) { + err = -ENOMEM; + goto close_fd; + } + memset(buf, 0, cfg.data_len); + } + + err = nvme_dir_recv(fd, cfg.namespace_id, cfg.dspec, cfg.dtype, cfg.doper, + cfg.data_len, dw12, buf, &result); + if (!err) + nvme_directive_show(cfg.dtype, cfg.doper, cfg.dspec, + cfg.namespace_id, result, buf, cfg.data_len, + flags); + else if (err > 0) + nvme_show_status(err); + else if (err < 0) + perror("dir-receive"); + + if (cfg.data_len) + free(buf); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int passthru(int argc, char **argv, int ioctl_cmd, const char *desc, struct command *cmd) +{ + void *data = NULL, *metadata = NULL; + int err = 0, wfd = STDIN_FILENO, fd; + __u32 result; + bool huge; + + struct config { + __u8 opcode; + __u8 flags; + __u16 rsvd; + __u32 namespace_id; + __u32 data_len; + __u32 metadata_len; + __u32 timeout; + __u32 cdw2; + __u32 cdw3; + __u32 cdw10; + __u32 cdw11; + __u32 cdw12; + __u32 cdw13; + __u32 cdw14; + __u32 cdw15; + char *input_file; + int raw_binary; + int show_command; + int dry_run; + int read; + int write; + __u8 prefill; + }; + + struct config cfg = { + .opcode = 0, + .flags = 0, + .rsvd = 0, + .namespace_id = 0, + .data_len = 0, + .metadata_len = 0, + .timeout = 0, + .cdw2 = 0, + .cdw3 = 0, + .cdw10 = 0, + .cdw11 = 0, + .cdw12 = 0, + .cdw13 = 0, + .cdw14 = 0, + .cdw15 = 0, + .input_file = "", + .prefill = 0, + }; + + const char *opcode = "opcode (required)"; + const char *flags = "command flags"; + const char *rsvd = "value for reserved field"; + const char *namespace_id = "desired namespace"; + const char *data_len = "data I/O length (bytes)"; + const char *metadata_len = "metadata seg. length (bytes)"; + const char *timeout = "timeout value, in milliseconds"; + const char *cdw2 = "command dword 2 value"; + const char *cdw3 = "command dword 3 value"; + const char *cdw10 = "command dword 10 value"; + const char *cdw11 = "command dword 11 value"; + const char *cdw12 = "command dword 12 value"; + const char *cdw13 = "command dword 13 value"; + const char *cdw14 = "command dword 14 value"; + const char *cdw15 = "command dword 15 value"; + const char *input = "write/send file (default stdin)"; + const char *raw_binary = "dump output in binary format"; + const char *show = "print command before sending"; + const char *dry = "show command instead of sending"; + const char *re = "set dataflow direction to receive"; + const char *wr = "set dataflow direction to send"; + const char *prefill = "prefill buffers with known byte-value, default 0"; + + OPT_ARGS(opts) = { + OPT_BYTE("opcode", 'o', &cfg.opcode, opcode), + OPT_BYTE("flags", 'f', &cfg.flags, flags), + OPT_BYTE("prefill", 'p', &cfg.prefill, prefill), + OPT_SHRT("rsvd", 'R', &cfg.rsvd, rsvd), + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("data-len", 'l', &cfg.data_len, data_len), + OPT_UINT("metadata-len", 'm', &cfg.metadata_len, metadata_len), + OPT_UINT("timeout", 't', &cfg.timeout, timeout), + OPT_UINT("cdw2", '2', &cfg.cdw2, cdw2), + OPT_UINT("cdw3", '3', &cfg.cdw3, cdw3), + OPT_UINT("cdw10", '4', &cfg.cdw10, cdw10), + OPT_UINT("cdw11", '5', &cfg.cdw11, cdw11), + OPT_UINT("cdw12", '6', &cfg.cdw12, cdw12), + OPT_UINT("cdw13", '7', &cfg.cdw13, cdw13), + OPT_UINT("cdw14", '8', &cfg.cdw14, cdw14), + OPT_UINT("cdw15", '9', &cfg.cdw15, cdw15), + OPT_FILE("input-file", 'i', &cfg.input_file, input), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw_binary), + OPT_FLAG("show-command", 's', &cfg.show_command, show), + OPT_FLAG("dry-run", 'd', &cfg.dry_run, dry), + OPT_FLAG("read", 'r', &cfg.read, re), + OPT_FLAG("write", 'w', &cfg.write, wr), + OPT_END() + }; + + err = fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + goto ret; + + if (strlen(cfg.input_file)){ + wfd = open(cfg.input_file, O_RDONLY, + S_IRUSR | S_IRGRP | S_IROTH); + if (wfd < 0) { + perror(cfg.input_file); + err = -EINVAL; + goto close_fd; + } + } + + if (cfg.metadata_len) { + metadata = malloc(cfg.metadata_len); + if (!metadata) { + fprintf(stderr, "can not allocate metadata " + "payload: %s\n", strerror(errno)); + err = -ENOMEM; + goto close_wfd; + } + memset(metadata, cfg.prefill, cfg.metadata_len); + } + if (cfg.data_len) { + data = nvme_alloc(cfg.data_len, &huge); + if (!data) { + fprintf(stderr, "can not allocate data payload\n"); + err = -ENOMEM; + goto free_metadata; + } + + if (cfg.write && !(cfg.opcode & 0x01)) { + fprintf(stderr, "warning: write flag set but write direction bit is not set in the opcode\n"); + } + + if (cfg.read && !(cfg.opcode & 0x02)) { + fprintf(stderr, "warning: read flag set but read direction bit is not set in the opcode\n"); + } + + memset(data, cfg.prefill, cfg.data_len); + if (!cfg.read && !cfg.write) { + fprintf(stderr, "data direction not given\n"); + err = -EINVAL; + goto free_data; + } else if (cfg.write) { + if (read(wfd, data, cfg.data_len) < 0) { + err = -errno; + fprintf(stderr, "failed to read write buffer " + "%s\n", strerror(errno)); + goto free_data; + } + } + } + + if (cfg.show_command) { + printf("opcode : %02x\n", cfg.opcode); + printf("flags : %02x\n", cfg.flags); + printf("rsvd1 : %04x\n", cfg.rsvd); + printf("nsid : %08x\n", cfg.namespace_id); + printf("cdw2 : %08x\n", cfg.cdw2); + printf("cdw3 : %08x\n", cfg.cdw3); + printf("data_len : %08x\n", cfg.data_len); + printf("metadata_len : %08x\n", cfg.metadata_len); + printf("addr : %"PRIx64"\n", (uint64_t)(uintptr_t)data); + printf("metadata : %"PRIx64"\n", (uint64_t)(uintptr_t)metadata); + printf("cdw10 : %08x\n", cfg.cdw10); + printf("cdw11 : %08x\n", cfg.cdw11); + printf("cdw12 : %08x\n", cfg.cdw12); + printf("cdw13 : %08x\n", cfg.cdw13); + printf("cdw14 : %08x\n", cfg.cdw14); + printf("cdw15 : %08x\n", cfg.cdw15); + printf("timeout_ms : %08x\n", cfg.timeout); + } + if (cfg.dry_run) + goto free_data; + + err = nvme_passthru(fd, ioctl_cmd, cfg.opcode, cfg.flags, cfg.rsvd, + cfg.namespace_id, cfg.cdw2, cfg.cdw3, cfg.cdw10, + cfg.cdw11, cfg.cdw12, cfg.cdw13, cfg.cdw14, cfg.cdw15, + cfg.data_len, data, cfg.metadata_len, metadata, + cfg.timeout, &result); + if (err < 0) + perror("passthru"); + else if (err) + nvme_show_status(err); + else { + if (!cfg.raw_binary) { + fprintf(stderr, "NVMe command result:%08x\n", result); + if (data && cfg.read && !err) + d((unsigned char *)data, cfg.data_len, 16, 1); + } else if (data && cfg.read) + d_raw((unsigned char *)data, cfg.data_len); + } +free_data: + if (cfg.data_len) + nvme_free(data, huge); +free_metadata: + if (cfg.metadata_len) + free(metadata); +close_wfd: + if (strlen(cfg.input_file)) + close(wfd); +close_fd: + close(fd); +ret: + return nvme_status_to_errno(err, false); +} + +static int io_passthru(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Send a user-defined IO command to the specified "\ + "device via IOCTL passthrough, return results."; + return passthru(argc, argv, NVME_IOCTL_IO_CMD, desc, cmd); +} + +static int admin_passthru(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Send a user-defined Admin command to the specified "\ + "device via IOCTL passthrough, return results."; + return passthru(argc, argv, NVME_IOCTL_ADMIN_CMD, desc, cmd); +} + +#ifdef LIBUUID +static int gen_hostnqn_cmd(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + uuid_t uuid; + char uuid_str[37]; /* e.g. 1b4e28ba-2fa1-11d2-883f-0016d3cca427 + \0 */ + + uuid_generate_random(uuid); + uuid_unparse_lower(uuid, uuid_str); + printf("nqn.2014-08.org.nvmexpress:uuid:%s\n", uuid_str); + return 0; +} +#else +static int gen_hostnqn_cmd(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + fprintf(stderr, "\"%s\" not supported. Install lib uuid and rebuild.\n", + command->name); + return -ENOTSUP; +} +#endif + +static int show_hostnqn_cmd(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + char *hostnqn; + + hostnqn = hostnqn_read(); + if (hostnqn) { + fputs(hostnqn, stdout); + free(hostnqn); + return 0; + } else { + fprintf(stderr, "hostnqn is not available -- use nvme gen-hostnqn\n"); + return -ENOENT; + } +} + +static int discover_cmd(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "Send Get Log Page request to Discovery Controller."; + return fabrics_discover(desc, argc, argv, false); +} + +static int connect_all_cmd(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "Discover NVMeoF subsystems and connect to them"; + return fabrics_discover(desc, argc, argv, true); +} + +static int connect_cmd(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "Connect to NVMeoF subsystem"; + return fabrics_connect(desc, argc, argv); +} + +static int disconnect_cmd(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "Disconnect from NVMeoF subsystem"; + return fabrics_disconnect(desc, argc, argv); +} + +static int disconnect_all_cmd(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "Disconnect from all connected NVMeoF subsystems"; + return fabrics_disconnect_all(desc, argc, argv); +} + +void register_extension(struct plugin *plugin) +{ + plugin->parent = &nvme; + nvme.extensions->tail->next = plugin; + nvme.extensions->tail = plugin; +} + +int main(int argc, char **argv) +{ + int err; + + nvme.extensions->parent = &nvme; + if (argc < 2) { + general_help(&builtin); + return 0; + } + setlocale(LC_ALL, ""); + + err = handle_plugin(argc - 1, &argv[1], nvme.extensions); + if (err == -ENOTTY) + general_help(&builtin); + + return err; +} diff --git a/nvme.control.in b/nvme.control.in new file mode 100644 index 0000000..46714d4 --- /dev/null +++ b/nvme.control.in @@ -0,0 +1,9 @@ +Package: nvme +Version: @@VERSION@@ +Section: base +Priority: optional +Architecture: amd64 +Depends: @@DEPENDS@@ +Maintainer: Keith Busch <kbusch@kernel.org> +Description: NVM-Express Command Line Interface + The nvme management tool |