diff options
Diffstat (limited to '')
-rw-r--r-- | nvme.c | 1118 |
1 files changed, 889 insertions, 229 deletions
@@ -52,9 +52,10 @@ #include <sys/random.h> #endif +#include <libnvme.h> + #include "common.h" #include "nvme.h" -#include "libnvme.h" #include "nvme-print.h" #include "plugin.h" #include "util/base64.h" @@ -62,6 +63,7 @@ #include "nvme-wrap.h" #include "util/argconfig.h" #include "util/suffix.h" +#include "util/logging.h" #include "fabrics.h" #define CREATE_CMD #include "nvme-builtin.h" @@ -106,9 +108,64 @@ struct passthru_config { bool latency; }; +struct get_reg_config { + int offset; + bool human_readable; + bool cap; + bool vs; + bool intms; + bool intmc; + bool cc; + bool csts; + bool nssr; + bool aqa; + bool asq; + bool acq; + bool cmbloc; + bool cmbsz; + bool bpinfo; + bool bprsel; + bool bpmbl; + bool cmbmsc; + bool cmbsts; + bool cmbebs; + bool cmbswtp; + bool nssd; + bool crto; + bool pmrcap; + bool pmrctl; + bool pmrsts; + bool pmrebs; + bool pmrswtp; + bool pmrmscl; + bool pmrmscu; + bool fabrics; +}; + +struct set_reg_config { + int offset; + bool mmio32; + __u64 value; + __u32 intms; + __u32 intmc; + __u32 cc; + __u32 csts; + __u32 nssr; + __u32 aqa; + __u64 asq; + __u64 acq; + __u32 bprsel; + __u64 bpmbl; + __u64 cmbmsc; + __u32 nssd; + __u32 pmrctl; + __u32 pmrmscl; + __u32 pmrmscu; +}; + #define NVME_ARGS(n, ...) \ struct argconfig_commandline_options n[] = { \ - OPT_FLAG("verbose", 'v', NULL, verbose), \ + OPT_INCR("verbose", 'v', &verbose_level, verbose), \ OPT_FMT("output-format", 'o', &output_format_val, output_format), \ ##__VA_ARGS__, \ OPT_END() \ @@ -184,10 +241,27 @@ static const char *uuid_index_specify = "specify uuid index"; static const char *verbose = "Increase output verbosity"; static const char dash[51] = {[0 ... 49] = '=', '\0'}; static const char space[51] = {[0 ... 49] = ' ', '\0'}; +static const char *offset = "offset of the requested register"; +static const char *intms = "INTMS=0xc register offset"; +static const char *intmc = "INTMC=0x10 register offset"; +static const char *cc = "CC=0x14 register offset"; +static const char *csts = "CSTS=0x1c register offset"; +static const char *nssr = "NSSR=0x20 register offset"; +static const char *aqa = "AQA=0x24 register offset"; +static const char *asq = "ASQ=0x28 register offset"; +static const char *acq = "ACQ=0x30 register offset"; +static const char *bprsel = "BPRSEL=0x44 register offset"; +static const char *bpmbl = "BPMBL=0x48 register offset"; +static const char *cmbmsc = "CMBMSC=0x50 register offset"; +static const char *nssd = "NSSD=0x64 register offset"; +static const char *pmrctl = "PMRCTL=0xe04 register offset"; +static const char *pmrmscl = "PMRMSCL=0xe14 register offset"; +static const char *pmrmscu = "PMRMSCU=0xe18 register offset"; static char *output_format_val = "normal"; +int verbose_level; -static void *mmap_registers(nvme_root_t r, struct nvme_dev *dev); +static void *mmap_registers(struct nvme_dev *dev, bool writable); const char *nvme_strerror(int errnum) { @@ -196,36 +270,6 @@ const char *nvme_strerror(int errnum) return strerror(errnum); } -int map_log_level(int verbose, bool quiet) -{ - int log_level; - - /* - * LOG_NOTICE is unused thus the user has to provide two 'v' for getting - * any feedback at all. Thus skip this level - */ - verbose++; - - switch (verbose) { - case 0: - log_level = LOG_WARNING; - break; - case 1: - log_level = LOG_NOTICE; - break; - case 2: - log_level = LOG_INFO; - break; - default: - log_level = LOG_DEBUG; - break; - } - if (quiet) - log_level = LOG_ERR; - - return log_level; -} - static ssize_t getrandom_bytes(void *buf, size_t buflen) { #if HAVE_SYS_RANDOM @@ -281,7 +325,8 @@ static int open_dev_direct(struct nvme_dev **devp, char *devstr, int flags) } if (!is_chardev(dev) && !is_blkdev(dev)) { nvme_show_error("%s is not a block or character device", devstr); - err = -ENODEV; + errno = ENODEV; + err = -1; goto err_close; } *devp = dev; @@ -400,8 +445,8 @@ int parse_and_open(struct nvme_dev **dev, int argc, char **argv, ret = get_dev(dev, argc, argv, O_RDONLY); if (ret < 0) argconfig_print_help(desc, opts); - else if (argconfig_parse_seen(opts, "verbose")) - nvme_cli_set_debug(*dev, true); + else + log_level = map_log_level(verbose_level, false); return ret; } @@ -531,7 +576,7 @@ static int get_ana_log(int argc, char **argv, struct command *cmd, "decoded format (default), json or binary."; const char *groups = "Return ANA groups only."; - _cleanup_nvme_dev_ struct nvme_dev *dev= NULL; + _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; _cleanup_free_ struct nvme_id_ctrl *ctrl = NULL; _cleanup_free_ void *ana_log = NULL; size_t ana_log_len; @@ -868,7 +913,9 @@ static int get_telemetry_log(int argc, char **argv, struct command *cmd, while (data_remaining) { data_written = write(output, data_ptr, data_remaining); if (data_written < 0) { - data_remaining = data_written; + err = -errno; + nvme_show_error("ERROR: %s: : write failed with error : %s", + __func__, strerror(errno)); break; } else if (data_written <= data_remaining) { data_remaining -= data_written; @@ -877,6 +924,7 @@ static int get_telemetry_log(int argc, char **argv, struct command *cmd, /* Unexpected overwrite */ fprintf(stderr, "Failure: Unexpected telemetry log overwrite - data_remaining = 0x%x, data_written = 0x%x\n", data_remaining, data_written); + err = -1; break; } } @@ -1007,12 +1055,9 @@ static int get_effects_log(int argc, char **argv, struct command *cmd, struct pl list_head_init(&log_pages); if (cfg.csi < 0) { - nvme_root_t r; __u64 cap; - r = nvme_scan(NULL); - bar = mmap_registers(r, dev); - nvme_free_tree(r); + bar = mmap_registers(dev, false); if (bar) { cap = mmio_read64(bar + NVME_REG_CAP); @@ -1172,7 +1217,7 @@ static int get_fw_log(int argc, char **argv, struct command *cmd, struct plugin const char *desc = "Retrieve the firmware log for the " "specified device in either decoded format (default) or binary."; - _cleanup_free_ struct nvme_firmware_slot *fw_log = NULL;; + _cleanup_free_ struct nvme_firmware_slot *fw_log = NULL; _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; enum nvme_print_flags flags; int err; @@ -3251,7 +3296,8 @@ static int list_subsys(int argc, char **argv, struct command *cmd, if (argconfig_parse_seen(opts, "verbose")) flags |= VERBOSE; - r = nvme_create_root(stderr, map_log_level(!!(flags & VERBOSE), false)); + + r = nvme_create_root(stderr, log_level); if (!r) { if (devname) nvme_show_error("Failed to scan nvme subsystem for %s", devname); @@ -3274,7 +3320,8 @@ static int list_subsys(int argc, char **argv, struct command *cmd, err = nvme_scan_topology(r, filter, (void *)devname); if (err) { - nvme_show_error("Failed to scan topology: %s", nvme_strerror(errno)); + if (errno != ENOENT) + nvme_show_error("Failed to scan topology: %s", nvme_strerror(errno)); goto ret; } @@ -3308,14 +3355,15 @@ static int list(int argc, char **argv, struct command *cmd, struct plugin *plugi if (argconfig_parse_seen(opts, "verbose")) flags |= VERBOSE; - r = nvme_create_root(stderr, map_log_level(!!(flags & VERBOSE), false)); + r = nvme_create_root(stderr, log_level); if (!r) { nvme_show_error("Failed to create topology root: %s", nvme_strerror(errno)); return -errno; } err = nvme_scan_topology(r, NULL, NULL); if (err < 0) { - nvme_show_error("Failed to scan topology: %s", nvme_strerror(errno)); + if (errno != ENOENT) + nvme_show_error("Failed to scan topology: %s", nvme_strerror(errno)); nvme_free_tree(r); return err; } @@ -3584,7 +3632,7 @@ static int ns_descs(int argc, char **argv, struct command *cmd, struct plugin *p const char *raw = "show descriptors in binary format"; _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; - _cleanup_free_ void *nsdescs = NULL;; + _cleanup_free_ void *nsdescs = NULL; enum nvme_print_flags flags; int err; @@ -3615,6 +3663,9 @@ static int ns_descs(int argc, char **argv, struct command *cmd, struct plugin *p if (cfg.raw_binary) flags = BINARY; + if (argconfig_parse_seen(opts, "verbose")) + flags |= VERBOSE; + if (!cfg.namespace_id) { err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); if (err < 0) { @@ -4824,7 +4875,7 @@ static int fw_download(int argc, char **argv, struct command *cmd, struct plugin int err; struct stat sb; void *fw_buf; - struct nvme_id_ctrl ctrl; + struct nvme_id_ctrl ctrl = { 0 }; struct config { char *fw; @@ -4836,7 +4887,7 @@ static int fw_download(int argc, char **argv, struct command *cmd, struct plugin struct config cfg = { .fw = "", - .xfer = 4096, + .xfer = 0, .offset = 0, .progress = false, .ignore_ovr = false, @@ -4885,6 +4936,10 @@ static int fw_download(int argc, char **argv, struct command *cmd, struct plugin } else if (cfg.xfer % 4096) cfg.xfer = 4096; + if (ctrl.fwug && ctrl.fwug != 0xff && fw_size % cfg.xfer) + nvme_show_error("WARNING: firmware file size %u not conform to FWUG alignment %lu", + fw_size, cfg.xfer); + fw_buf = nvme_alloc_huge(fw_size, &mh); if (!fw_buf) return -ENOMEM; @@ -5119,7 +5174,7 @@ static int ns_rescan(int argc, char **argv, struct command *cmd, struct plugin * err = nvme_ns_rescan(dev_fd(dev)); if (err < 0) - nvme_show_error("Namespace Rescan"); + nvme_show_error("Namespace Rescan: %s\n", nvme_strerror(errno)); return err; } @@ -5224,42 +5279,62 @@ static int sanitize_cmd(int argc, char **argv, struct command *cmd, struct plugi return err; } -static int nvme_get_properties(int fd, void **pbar) +static int nvme_get_single_property(int fd, struct get_reg_config *cfg, __u64 *value) +{ + int err; + struct nvme_get_property_args args = { + .args_size = sizeof(args), + .fd = fd, + .offset = cfg->offset, + .value = value, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + }; + + err = nvme_get_property(&args); + if (!err) + return 0; + + if (!cfg->fabrics && + nvme_status_equals(err, NVME_STATUS_TYPE_NVME, NVME_SC_INVALID_FIELD)) { + *value = -1; + return 0; + } + + if (cfg->fabrics && err > 0) + nvme_show_status(err); + else + nvme_show_error("get-property: %s", nvme_strerror(errno)); + + return err; +} + +static int nvme_get_properties(int fd, void **pbar, struct get_reg_config *cfg) { - int offset, err, size = getpagesize(); + int err, size = getpagesize(); + bool is_64bit = false; __u64 value; - void *bar = malloc(size); + void *bar; + int offset; + bar = malloc(size); if (!bar) { nvme_show_error("malloc: %s", strerror(errno)); - return -1; + return -errno; } memset(bar, 0xff, size); - for (offset = NVME_REG_CAP; offset <= NVME_REG_CMBSZ;) { - struct nvme_get_property_args args = { - .args_size = sizeof(args), - .fd = fd, - .offset = offset, - .value = &value, - .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, - }; - - err = nvme_get_property(&args); - if (nvme_status_equals(err, NVME_STATUS_TYPE_NVME, NVME_SC_INVALID_FIELD)) { - err = 0; - value = -1; - } else if (err) { - nvme_show_error("get-property: %s", nvme_strerror(errno)); + for (offset = NVME_REG_CAP; offset <= NVME_REG_CMBSZ; + offset += is_64bit ? sizeof(uint64_t) : sizeof(uint32_t)) { + cfg->offset = offset; + err = nvme_get_single_property(fd, cfg, &value); + if (err) break; - } - if (nvme_is_64bit_reg(offset)) { - *(uint64_t *)(bar + offset) = value; - offset += 8; - } else { - *(uint32_t *)(bar + offset) = value; - offset += 4; - } + + is_64bit = nvme_is_64bit_reg(cfg->offset); + if (is_64bit) + *(uint64_t *)(bar + cfg->offset) = value; + else + *(uint32_t *)(bar + cfg->offset) = value; } if (err) @@ -5270,42 +5345,28 @@ static int nvme_get_properties(int fd, void **pbar) return err; } -static void *mmap_registers(nvme_root_t r, struct nvme_dev *dev) +static void *mmap_registers(struct nvme_dev *dev, bool writable) { - nvme_ctrl_t c = NULL; - nvme_ns_t n = NULL; - char path[512]; void *membase; int fd; + int prot = PROT_READ; - c = nvme_scan_ctrl(r, dev->name); - if (c) { - snprintf(path, sizeof(path), "%s/device/resource0", - nvme_ctrl_get_sysfs_dir(c)); - nvme_free_ctrl(c); - } else { - n = nvme_scan_namespace(dev->name); - if (!n) { - nvme_show_error("Unable to find %s", dev->name); - return NULL; - } - snprintf(path, sizeof(path), "%s/device/device/resource0", - nvme_ns_get_sysfs_dir(n)); - nvme_free_ns(n); - } + if (writable) + prot |= PROT_WRITE; - fd = open(path, O_RDONLY); + sprintf(path, "/sys/class/nvme/%s/device/resource0", dev->name); + fd = open(path, writable ? O_RDWR : O_RDONLY); if (fd < 0) { - if (map_log_level(0, false) >= LOG_DEBUG) + if (log_level >= LOG_DEBUG) nvme_show_error("%s did not find a pci resource, open failed %s", dev->name, strerror(errno)); return NULL; } - membase = mmap(NULL, getpagesize(), PROT_READ, MAP_SHARED, fd, 0); + membase = mmap(NULL, getpagesize(), prot, MAP_SHARED, fd, 0); if (membase == MAP_FAILED) { - if (map_log_level(0, false) >= LOG_DEBUG) { + if (log_level >= LOG_DEBUG) { fprintf(stderr, "%s failed to map. ", dev->name); fprintf(stderr, "Did your kernel enable CONFIG_IO_STRICT_DEVMEM?\n"); } @@ -5326,15 +5387,10 @@ static int show_registers(int argc, char **argv, struct command *cmd, struct plu _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; enum nvme_print_flags flags; bool fabrics = false; - nvme_root_t r; void *bar; int err; - struct config { - bool human_readable; - }; - - struct config cfg = { + struct get_reg_config cfg = { .human_readable = false, }; @@ -5345,21 +5401,20 @@ static int show_registers(int argc, char **argv, struct command *cmd, struct plu if (err) return err; - r = nvme_scan(NULL); err = validate_output_format(output_format_val, &flags); if (err < 0) { nvme_show_error("Invalid output format"); - goto free_tree; + return err; } if (cfg.human_readable) flags |= VERBOSE; - bar = mmap_registers(r, dev); + bar = mmap_registers(dev, false); if (!bar) { - err = nvme_get_properties(dev_fd(dev), &bar); + err = nvme_get_properties(dev_fd(dev), &bar, &cfg); if (err) - goto free_tree; + return err; fabrics = true; } @@ -5368,8 +5423,575 @@ static int show_registers(int argc, char **argv, struct command *cmd, struct plu free(bar); else munmap(bar, getpagesize()); -free_tree: - nvme_free_tree(r); + + return 0; +} + +int get_reg_size(int offset) +{ + return nvme_is_64bit_reg(offset) ? sizeof(uint64_t) : sizeof(uint32_t); +} + +static bool is_reg_selected(struct get_reg_config *cfg, int offset) +{ + switch (offset) { + case NVME_REG_CAP: + return cfg->cap; + case NVME_REG_VS: + return cfg->vs; + case NVME_REG_INTMS: + return cfg->intms; + case NVME_REG_INTMC: + return cfg->intmc; + case NVME_REG_CC: + return cfg->cc; + case NVME_REG_CSTS: + return cfg->csts; + case NVME_REG_NSSR: + return cfg->nssr; + case NVME_REG_AQA: + return cfg->aqa; + case NVME_REG_ASQ: + return cfg->asq; + case NVME_REG_ACQ: + return cfg->acq; + case NVME_REG_CMBLOC: + return cfg->cmbloc; + case NVME_REG_CMBSZ: + return cfg->cmbsz; + case NVME_REG_BPINFO: + return cfg->bpinfo; + case NVME_REG_BPRSEL: + return cfg->bprsel; + case NVME_REG_BPMBL: + return cfg->bpmbl; + case NVME_REG_CMBMSC: + return cfg->cmbmsc; + case NVME_REG_CMBSTS: + return cfg->cmbsts; + case NVME_REG_CMBEBS: + return cfg->cmbebs; + case NVME_REG_CMBSWTP: + return cfg->cmbswtp; + case NVME_REG_NSSD: + return cfg->nssd; + case NVME_REG_CRTO: + return cfg->crto; + case NVME_REG_PMRCAP: + return cfg->pmrcap; + case NVME_REG_PMRCTL: + return cfg->pmrctl; + case NVME_REG_PMRSTS: + return cfg->pmrsts; + case NVME_REG_PMREBS: + return cfg->pmrebs; + case NVME_REG_PMRSWTP: + return cfg->pmrswtp; + case NVME_REG_PMRMSCL: + return cfg->pmrmscl; + case NVME_REG_PMRMSCU: + return cfg->pmrmscu; + default: + break; + } + + return false; +} + +static int get_register_properties(int fd, void **pbar, struct get_reg_config *cfg) +{ + int offset = NVME_REG_CRTO; + __u64 value; + int size; + int err; + void *bar; + struct nvme_get_property_args args = { + .args_size = sizeof(args), + .fd = fd, + .value = &value, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + }; + + size = offset + get_reg_size(offset); + bar = malloc(size); + if (!bar) { + nvme_show_error("malloc: %s", strerror(errno)); + return -1; + } + + for (offset = NVME_REG_CAP; offset <= NVME_REG_CRTO; offset += get_reg_size(offset)) { + if ((cfg->offset != offset && !is_reg_selected(cfg, offset)) || + !nvme_is_fabrics_reg(offset)) + continue; + + args.offset = offset; + err = nvme_get_property(&args); + if (nvme_status_equals(err, NVME_STATUS_TYPE_NVME, NVME_SC_INVALID_FIELD)) { + value = -1; + } else if (err) { + nvme_show_error("get-property: %s", nvme_strerror(errno)); + free(bar); + return err; + } + + if (nvme_is_64bit_reg(offset)) + *(uint64_t *)(bar + offset) = value; + else + *(uint32_t *)(bar + offset) = value; + } + + *pbar = bar; + + return 0; +} + +bool nvme_is_ctrl_reg(int offset) +{ + switch (offset) { + case NVME_REG_CAP: + case NVME_REG_VS: + case NVME_REG_INTMS: + case NVME_REG_INTMC: + case NVME_REG_CC: + case NVME_REG_CSTS: + case NVME_REG_NSSR: + case NVME_REG_AQA: + case NVME_REG_ASQ: + case NVME_REG_ACQ: + case NVME_REG_CMBLOC: + case NVME_REG_CMBSZ: + case NVME_REG_BPINFO: + case NVME_REG_BPRSEL: + case NVME_REG_BPMBL: + case NVME_REG_CMBMSC: + case NVME_REG_CMBSTS: + case NVME_REG_CMBEBS: + case NVME_REG_CMBSWTP: + case NVME_REG_NSSD: + case NVME_REG_CRTO: + case NVME_REG_PMRCAP: + case NVME_REG_PMRCTL: + case NVME_REG_PMRSTS: + case NVME_REG_PMREBS: + case NVME_REG_PMRSWTP: + case NVME_REG_PMRMSCL: + case NVME_REG_PMRMSCU: + return true; + default: + break; + } + + return false; +} + +static bool get_register_offset(void *bar, bool fabrics, struct get_reg_config *cfg, + enum nvme_print_flags flags) +{ + bool offset_matched = cfg->offset >= 0; + int offset; + + if (offset_matched) + nvme_show_ctrl_register(bar, fabrics, cfg->offset, flags); + + for (offset = NVME_REG_CAP; offset <= NVME_REG_PMRMSCU; offset += get_reg_size(offset)) { + if (!nvme_is_ctrl_reg(offset) || offset == cfg->offset || !is_reg_selected(cfg, offset)) + continue; + nvme_show_ctrl_register(bar, fabrics, offset, flags); + if (!offset_matched) + offset_matched = true; + } + + return offset_matched; +} + +static int get_register(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Reads and shows the defined NVMe controller register.\n" + "Register offset must be one of:\n" + "CAP=0x0, VS=0x8, INTMS=0xc, INTMC=0x10, CC=0x14, CSTS=0x1c,\n" + "NSSR=0x20, AQA=0x24, ASQ=0x28, ACQ=0x30, CMBLOC=0x38,\n" + "CMBSZ=0x3c, BPINFO=0x40, BPRSEL=0x44, BPMBL=0x48, CMBMSC=0x50,\n" + "CMBSTS=0x58, CRTO=0x68, PMRCAP=0xe00, PMRCTL=0xe04,\n" + "PMRSTS=0xe08, PMREBS=0xe0c, PMRSWTP=0xe10, PMRMSCL=0xe14, PMRMSCU=0xe18"; + const char *human_readable = "show register in readable format"; + const char *cap = "CAP=0x0 register offset"; + const char *vs = "VS=0x8 register offset"; + const char *cmbloc = "CMBLOC=0x38 register offset"; + const char *cmbsz = "CMBSZ=0x3c register offset"; + const char *bpinfo = "BPINFO=0x40 register offset"; + const char *cmbsts = "CMBSTS=0x58 register offset"; + const char *cmbebs = "CMBEBS=0x5c register offset"; + const char *cmbswtp = "CMBSWTP=0x60 register offset"; + const char *crto = "CRTO=0x68 register offset"; + const char *pmrcap = "PMRCAP=0xe00 register offset"; + const char *pmrsts = "PMRSTS=0xe08 register offset"; + const char *pmrebs = "PMREBS=0xe0c register offset"; + const char *pmrswtp = "PMRSWTP=0xe10 register offset"; + const char *pmrmscl = "PMRMSCL=0xe14 register offset"; + const char *pmrmscu = "PMRMSCU=0xe18 register offset"; + + _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; + int err; + enum nvme_print_flags flags; + bool fabrics = false; + + void *bar; + + struct get_reg_config cfg = { + .offset = -1, + }; + + NVME_ARGS(opts, + OPT_UINT("offset", 'O', &cfg.offset, offset), + OPT_FLAG("human-readable", 'H', &cfg.human_readable, human_readable), + OPT_FLAG("cap", 0, &cfg.cap, cap), + OPT_FLAG("vs", 0, &cfg.vs, vs), + OPT_FLAG("cmbloc", 0, &cfg.cmbloc, cmbloc), + OPT_FLAG("cmbsz", 0, &cfg.cmbsz, cmbsz), + OPT_FLAG("bpinfo", 0, &cfg.bpinfo, bpinfo), + OPT_FLAG("cmbsts", 0, &cfg.cmbsts, cmbsts), + OPT_FLAG("cmbebs", 0, &cfg.cmbebs, cmbebs), + OPT_FLAG("cmbswtp", 0, &cfg.cmbswtp, cmbswtp), + OPT_FLAG("crto", 0, &cfg.crto, crto), + OPT_FLAG("pmrcap", 0, &cfg.pmrcap, pmrcap), + OPT_FLAG("pmrsts", 0, &cfg.pmrsts, pmrsts), + OPT_FLAG("pmrebs", 0, &cfg.pmrebs, pmrebs), + OPT_FLAG("pmrswtp", 0, &cfg.pmrswtp, pmrswtp), + OPT_FLAG("intms", 0, &cfg.intms, intms), + OPT_FLAG("intmc", 0, &cfg.intmc, intmc), + OPT_FLAG("cc", 0, &cfg.cc, cc), + OPT_FLAG("csts", 0, &cfg.csts, csts), + OPT_FLAG("nssr", 0, &cfg.nssr, nssr), + OPT_FLAG("aqa", 0, &cfg.aqa, aqa), + OPT_FLAG("asq", 0, &cfg.asq, asq), + OPT_FLAG("acq", 0, &cfg.acq, acq), + OPT_FLAG("bprsel", 0, &cfg.bprsel, bprsel), + OPT_FLAG("bpmbl", 0, &cfg.bpmbl, bpmbl), + OPT_FLAG("cmbmsc", 0, &cfg.cmbmsc, cmbmsc), + OPT_FLAG("nssd", 0, &cfg.nssd, nssd), + OPT_FLAG("pmrctl", 0, &cfg.pmrctl, pmrctl), + OPT_FLAG("pmrmscl", 0, &cfg.pmrmscl, pmrmscl), + OPT_FLAG("pmrmscu", 0, &cfg.pmrmscu, pmrmscu)); + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = validate_output_format(output_format_val, &flags); + if (err < 0) { + nvme_show_error("Invalid output format"); + return err; + } + + if (cfg.human_readable) + flags |= VERBOSE; + + bar = mmap_registers(dev, false); + if (!bar) { + err = get_register_properties(dev_fd(dev), &bar, &cfg); + if (err) + return err; + fabrics = true; + } + + nvme_show_init(); + + if (!get_register_offset(bar, fabrics, &cfg, flags)) { + nvme_show_error("offset required param"); + err = -EINVAL; + } + + nvme_show_finish(); + + if (fabrics) + free(bar); + else + munmap(bar, getpagesize()); + + return err; +} + +static int nvme_set_single_property(int fd, int offset, uint64_t value) +{ + struct nvme_set_property_args args = { + .args_size = sizeof(args), + .fd = fd, + .offset = offset, + .value = value, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + int err = nvme_set_property(&args); + + if (err < 0) + nvme_show_error("set-property: %s", nvme_strerror(errno)); + else if (!err) + printf("set-property: %#02x (%s), value: %#"PRIx64"\n", offset, + nvme_register_to_string(offset), value); + else if (err > 0) + nvme_show_status(err); + + return err; +} + +static int set_register_property(int fd, int offset, uint64_t value) +{ + if (!nvme_is_fabrics_reg(offset)) { + printf("register: %#04x (%s) not fabrics\n", offset, + nvme_register_to_string(offset)); + return -EINVAL; + } + + return nvme_set_single_property(fd, offset, value); +} + +static int nvme_set_register(int fd, void *bar, int offset, uint64_t value, bool mmio32) +{ + if (!bar) + return set_register_property(fd, offset, value); + + if (nvme_is_64bit_reg(offset)) + mmio_write64(bar + offset, value, mmio32); + else + mmio_write32(bar + offset, value); + + printf("set-register: %#02x (%s), value: %#"PRIx64"\n", offset, + nvme_register_to_string(offset), value); + + return 0; +} + +static inline int set_register_names_check(struct argconfig_commandline_options *opts, int offset) +{ + switch (offset) { + case NVME_REG_INTMS: + if (argconfig_parse_seen(opts, "intms")) + return -EINVAL; + break; + case NVME_REG_INTMC: + if (argconfig_parse_seen(opts, "intmc")) + return -EINVAL; + break; + case NVME_REG_CC: + if (argconfig_parse_seen(opts, "cc")) + return -EINVAL; + break; + case NVME_REG_CSTS: + if (argconfig_parse_seen(opts, "csts")) + return -EINVAL; + break; + case NVME_REG_NSSR: + if (argconfig_parse_seen(opts, "nssr")) + return -EINVAL; + break; + case NVME_REG_AQA: + if (argconfig_parse_seen(opts, "aqa")) + return -EINVAL; + break; + case NVME_REG_ASQ: + if (argconfig_parse_seen(opts, "asq")) + return -EINVAL; + break; + case NVME_REG_ACQ: + if (argconfig_parse_seen(opts, "acq")) + return -EINVAL; + break; + case NVME_REG_BPRSEL: + if (argconfig_parse_seen(opts, "bprsel")) + return -EINVAL; + break; + case NVME_REG_CMBMSC: + if (argconfig_parse_seen(opts, "cmbmsc")) + return -EINVAL; + break; + case NVME_REG_NSSD: + if (argconfig_parse_seen(opts, "nssd")) + return -EINVAL; + break; + case NVME_REG_PMRCTL: + if (argconfig_parse_seen(opts, "pmrctl")) + return -EINVAL; + break; + case NVME_REG_PMRMSCL: + if (argconfig_parse_seen(opts, "pmrmscl")) + return -EINVAL; + break; + case NVME_REG_PMRMSCU: + if (argconfig_parse_seen(opts, "pmrmscu")) + return -EINVAL; + break; + default: + break; + } + + return 0; +} + +static int set_register_offset(int fd, void *bar, struct argconfig_commandline_options *opts, + struct set_reg_config *cfg) +{ + int err; + + if (!argconfig_parse_seen(opts, "value")) { + nvme_show_error("value required param"); + return -EINVAL; + } + + err = set_register_names_check(opts, cfg->offset); + if (err) { + nvme_show_error("offset duplicated param"); + return err; + } + + err = nvme_set_register(fd, bar, cfg->offset, cfg->value, cfg->mmio32); + if (err) + return err; + + return 0; +} + +static int set_register_names(int fd, void *bar, struct argconfig_commandline_options *opts, + struct set_reg_config *cfg) +{ + int err; + + if (argconfig_parse_seen(opts, "intms")) { + err = nvme_set_register(fd, bar, NVME_REG_INTMS, cfg->intms, cfg->mmio32); + if (err) + return err; + } + + if (argconfig_parse_seen(opts, "intmc")) { + err = nvme_set_register(fd, bar, NVME_REG_INTMC, cfg->intmc, cfg->mmio32); + if (err) + return err; + } + + if (argconfig_parse_seen(opts, "cc")) { + err = nvme_set_register(fd, bar, NVME_REG_CC, cfg->cc, cfg->mmio32); + if (err) + return err; + } + + if (argconfig_parse_seen(opts, "csts")) { + err = nvme_set_register(fd, bar, NVME_REG_CSTS, cfg->csts, cfg->mmio32); + if (err) + return err; + } + + if (argconfig_parse_seen(opts, "nssr")) { + err = nvme_set_register(fd, bar, NVME_REG_NSSR, cfg->nssr, cfg->mmio32); + if (err) + return err; + } + + if (argconfig_parse_seen(opts, "aqa")) { + err = nvme_set_register(fd, bar, NVME_REG_AQA, cfg->aqa, cfg->mmio32); + if (err) + return err; + } + + if (argconfig_parse_seen(opts, "asq")) { + err = nvme_set_register(fd, bar, NVME_REG_ASQ, cfg->asq, cfg->mmio32); + if (err) + return err; + } + + if (argconfig_parse_seen(opts, "acq")) { + err = nvme_set_register(fd, bar, NVME_REG_ACQ, cfg->acq, cfg->mmio32); + if (err) + return err; + } + + if (argconfig_parse_seen(opts, "bprsel")) { + err = nvme_set_register(fd, bar, NVME_REG_BPRSEL, cfg->bprsel, cfg->mmio32); + if (err) + return err; + } + + if (argconfig_parse_seen(opts, "cmbmsc")) { + err = nvme_set_register(fd, bar, NVME_REG_CMBMSC, cfg->cmbmsc, cfg->mmio32); + if (err) + return err; + } + + if (argconfig_parse_seen(opts, "nssd")) { + err = nvme_set_register(fd, bar, NVME_REG_NSSD, cfg->nssd, cfg->mmio32); + if (err) + return err; + } + + if (argconfig_parse_seen(opts, "pmrctl")) { + err = nvme_set_register(fd, bar, NVME_REG_PMRCTL, cfg->pmrctl, cfg->mmio32); + if (err) + return err; + } + + if (argconfig_parse_seen(opts, "pmrmscl")) { + err = nvme_set_register(fd, bar, NVME_REG_PMRMSCL, cfg->pmrmscl, cfg->mmio32); + if (err) + return err; + } + + if (argconfig_parse_seen(opts, "pmrmscu")) { + err = nvme_set_register(fd, bar, NVME_REG_PMRMSCU, cfg->pmrmscu, cfg->mmio32); + if (err) + return err; + } + + return 0; +} + +static int set_register(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Writes and shows the defined NVMe controller register"; + const char *value = "the value of the register to be set"; + const char *mmio32 = "Access 64-bit registers as 2 32-bit"; + + _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; + int err; + + void *bar; + + struct set_reg_config cfg = { + .offset = -1, + }; + + NVME_ARGS(opts, + OPT_UINT("offset", 'O', &cfg.offset, offset), + OPT_SUFFIX("value", 'V', &cfg.value, value), + OPT_FLAG("mmio32", 'm', &cfg.mmio32, mmio32), + OPT_UINT("intms", 0, &cfg.intms, intms), + OPT_UINT("intmc", 0, &cfg.intmc, intmc), + OPT_UINT("cc", 0, &cfg.cc, cc), + OPT_UINT("csts", 0, &cfg.csts, csts), + OPT_UINT("nssr", 0, &cfg.nssr, nssr), + OPT_UINT("aqa", 0, &cfg.aqa, aqa), + OPT_SUFFIX("asq", 0, &cfg.asq, asq), + OPT_SUFFIX("acq", 0, &cfg.acq, acq), + OPT_UINT("bprsel", 0, &cfg.bprsel, bprsel), + OPT_SUFFIX("bpmbl", 0, &cfg.bpmbl, bpmbl), + OPT_SUFFIX("cmbmsc", 0, &cfg.cmbmsc, cmbmsc), + OPT_UINT("nssd", 0, &cfg.nssd, nssd), + OPT_UINT("pmrctl", 0, &cfg.pmrctl, pmrctl), + OPT_UINT("pmrmscl", 0, &cfg.pmrmscl, pmrmscl), + OPT_UINT("pmrmscu", 0, &cfg.pmrmscu, pmrmscu)); + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + bar = mmap_registers(dev, true); + + if (argconfig_parse_seen(opts, "offset")) + err = set_register_offset(dev_fd(dev), bar, opts, &cfg); + + if (!err) + err = set_register_names(dev_fd(dev), bar, opts, &cfg); + + if (bar) + munmap(bar, getpagesize()); + return err; } @@ -5377,7 +5999,7 @@ static int get_property(int argc, char **argv, struct command *cmd, struct plugi { const char *desc = "Reads and shows the defined NVMe controller property\n" "for NVMe over Fabric. Property offset must be one of:\n" - "CAP=0x0, VS=0x8, CC=0x14, CSTS=0x1c, NSSR=0x20"; + "CAP=0x0, VS=0x8, CC=0x14, CSTS=0x1c, NSSR=0x20, NSSD=0x64, CRTO=0x68"; const char *offset = "offset of the requested property"; const char *human_readable = "show property in readable format"; @@ -5385,14 +6007,10 @@ static int get_property(int argc, char **argv, struct command *cmd, struct plugi __u64 value; int err; - struct config { - int offset; - bool human_readable; - }; - - struct config cfg = { + struct get_reg_config cfg = { .offset = -1, .human_readable = false, + .fabrics = true, }; NVME_ARGS(opts, @@ -5408,20 +6026,9 @@ static int get_property(int argc, char **argv, struct command *cmd, struct plugi return -EINVAL; } - struct nvme_get_property_args args = { - .args_size = sizeof(args), - .fd = dev_fd(dev), - .offset = cfg.offset, - .value = &value, - .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, - }; - err = nvme_get_property(&args); - if (err < 0) - nvme_show_error("get-property: %s", nvme_strerror(errno)); - else if (!err) + err = nvme_get_single_property(dev_fd(dev), &cfg, &value); + if (!err) nvme_show_single_property(cfg.offset, value, cfg.human_readable); - else if (err > 0) - nvme_show_status(err); return err; } @@ -5436,12 +6043,7 @@ static int set_property(int argc, char **argv, struct command *cmd, struct plugi _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; int err; - struct config { - int offset; - int value; - }; - - struct config cfg = { + struct set_reg_config cfg = { .offset = -1, .value = -1, }; @@ -5463,24 +6065,7 @@ static int set_property(int argc, char **argv, struct command *cmd, struct plugi return -EINVAL; } - struct nvme_set_property_args args = { - .args_size = sizeof(args), - .fd = dev_fd(dev), - .offset = cfg.offset, - .value = cfg.value, - .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, - .result = NULL, - }; - err = nvme_set_property(&args); - if (err < 0) - nvme_show_error("set-property: %s", nvme_strerror(errno)); - 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); - - return err; + return nvme_set_single_property(dev_fd(dev), cfg.offset, cfg.value); } static int format_cmd(int argc, char **argv, struct command *cmd, struct plugin *plugin) @@ -7644,7 +8229,7 @@ static int sec_recv(int argc, char **argv, struct command *cmd, struct plugin *p return err; if (cfg.size) { - sec_buf = nvme_alloc(sizeof(*sec_buf)); + sec_buf = nvme_alloc(cfg.size); if (!sec_buf) return -ENOMEM; } @@ -8363,7 +8948,8 @@ static int gen_dhchap_key(int argc, char **argv, struct command *command, struct "HMAC function to use for key transformation (0 = none, 1 = SHA-256, 2 = SHA-384, 3 = SHA-512)."; const char *nqn = "Host NQN to use for key transformation."; - unsigned char *raw_secret; + _cleanup_free_ unsigned char *raw_secret = NULL; + _cleanup_free_ char *hnqn = NULL; unsigned char key[68]; char encoded_key[128]; unsigned long crc = crc32(0L, NULL, 0); @@ -8458,7 +9044,7 @@ static int gen_dhchap_key(int argc, char **argv, struct command *command, struct } if (!cfg.nqn) { - cfg.nqn = nvmf_hostnqn_from_file(); + cfg.nqn = hnqn = nvmf_hostnqn_from_file(); if (!cfg.nqn) { nvme_show_error("Could not read host NQN"); return -ENOENT; @@ -8581,12 +9167,12 @@ static int gen_tls_key(int argc, char **argv, struct command *command, struct pl const char *subsysnqn = "Subsystem NQN for the retained key."; const char *keyring = "Keyring for the retained key."; const char *keytype = "Key type of the retained key."; - const char *insert = "Insert only, do not print the retained key."; + const char *insert = "Insert retained key into the keyring."; - unsigned char *raw_secret; - char encoded_key[128]; + _cleanup_free_ unsigned char *raw_secret = NULL; + _cleanup_free_ char *encoded_key = NULL; + _cleanup_free_ char *hnqn = NULL; int key_len = 32; - unsigned long crc = crc32(0L, NULL, 0); int err; long tls_key; @@ -8640,7 +9226,7 @@ static int gen_tls_key(int argc, char **argv, struct command *command, struct pl return -EINVAL; } if (!cfg.hostnqn) { - cfg.hostnqn = nvmf_hostnqn_from_file(); + cfg.hostnqn = hnqn = nvmf_hostnqn_from_file(); if (!cfg.hostnqn) { nvme_show_error("Failed to read host NQN"); return -EINVAL; @@ -8677,6 +9263,9 @@ static int gen_tls_key(int argc, char **argv, struct command *command, struct pl } } + encoded_key = nvme_export_tls_key(raw_secret, key_len); + printf("%s\n", encoded_key); + if (cfg.insert) { tls_key = nvme_insert_tls_key_versioned(cfg.keyring, cfg.keytype, cfg.hostnqn, @@ -8688,18 +9277,7 @@ static int gen_tls_key(int argc, char **argv, struct command *command, struct pl } printf("Inserted TLS key %08x\n", (unsigned int)tls_key); - return 0; } - crc = crc32(crc, raw_secret, key_len); - raw_secret[key_len++] = crc & 0xff; - raw_secret[key_len++] = (crc >> 8) & 0xff; - raw_secret[key_len++] = (crc >> 16) & 0xff; - raw_secret[key_len++] = (crc >> 24) & 0xff; - - memset(encoded_key, 0, sizeof(encoded_key)); - base64_encode(raw_secret, key_len, encoded_key); - - printf("NVMeTLSkey-1:%02x:%s:\n", cfg.hmac, encoded_key); return 0; } @@ -8714,11 +9292,10 @@ static int check_tls_key(int argc, char **argv, struct command *command, struct const char *keytype = "Key type of the retained key."; const char *insert = "Insert retained key into the keyring."; - unsigned char decoded_key[128]; - unsigned int decoded_len; - u_int32_t crc = crc32(0L, NULL, 0); - u_int32_t key_crc; - int err = 0, hmac; + _cleanup_free_ unsigned char *decoded_key = NULL; + _cleanup_free_ char *hnqn = NULL; + int decoded_len, err = 0; + unsigned int hmac; long tls_key; struct config { char *keyring; @@ -8763,60 +9340,25 @@ static int check_tls_key(int argc, char **argv, struct command *command, struct return -EINVAL; } - if (sscanf(cfg.keydata, "NVMeTLSkey-1:%02x:*s", &hmac) != 1) { - nvme_show_error("Invalid key '%s'", cfg.keydata); - return -EINVAL; - } - switch (hmac) { - case 1: - if (strlen(cfg.keydata) != 65) { - nvme_show_error("Invalid key length %zu for SHA(256)", strlen(cfg.keydata)); - return -EINVAL; - } - break; - case 2: - if (strlen(cfg.keydata) != 89) { - nvme_show_error("Invalid key length %zu for SHA(384)", strlen(cfg.keydata)); - return -EINVAL; - } - break; - default: - nvme_show_error("Invalid HMAC identifier %d", hmac); - return -EINVAL; + decoded_key = nvme_import_tls_key(cfg.keydata, &decoded_len, &hmac); + if (!decoded_key) { + nvme_show_error("Key decoding failed, error %d\n", errno); + return -errno; } if (cfg.subsysnqn) { - if (cfg.insert && !cfg.hostnqn) { - cfg.hostnqn = nvmf_hostnqn_from_file(); + if (!cfg.hostnqn) { + cfg.hostnqn = hnqn = nvmf_hostnqn_from_file(); if (!cfg.hostnqn) { nvme_show_error("Failed to read host NQN"); return -EINVAL; } } - } else if (cfg.insert || cfg.identity == 1) { + } else { nvme_show_error("Need to specify a subsystem NQN"); return -EINVAL; } - err = base64_decode(cfg.keydata + 16, strlen(cfg.keydata) - 17, decoded_key); - if (err < 0) { - nvme_show_error("Base64 decoding failed (%s, error %d)", cfg.keydata + 16, err); - return err; - } - decoded_len = err; - decoded_len -= 4; - if (decoded_len != 32 && decoded_len != 48) { - nvme_show_error("Invalid key length %d", decoded_len); - return -EINVAL; - } - crc = crc32(crc, decoded_key, decoded_len); - key_crc = ((u_int32_t)decoded_key[decoded_len]) | - ((u_int32_t)decoded_key[decoded_len + 1] << 8) | - ((u_int32_t)decoded_key[decoded_len + 2] << 16) | - ((u_int32_t)decoded_key[decoded_len + 3] << 24); - if (key_crc != crc) { - nvme_show_error("CRC mismatch (key %08x, crc %08x)", key_crc, crc); - return -EINVAL; - } + if (cfg.insert) { tls_key = nvme_insert_tls_key_versioned(cfg.keyring, cfg.keytype, cfg.hostnqn, @@ -8828,7 +9370,7 @@ static int check_tls_key(int argc, char **argv, struct command *command, struct } printf("Inserted TLS key %08x\n", (unsigned int)tls_key); } else { - char *tls_id; + _cleanup_free_ char *tls_id = NULL; tls_id = nvme_generate_tls_key_identity(cfg.hostnqn, cfg.subsysnqn, cfg.identity, @@ -8839,11 +9381,128 @@ static int check_tls_key(int argc, char **argv, struct command *command, struct return -errno; } printf("%s\n", tls_id); - free(tls_id); } return 0; } +static void __scan_tls_key(long keyring_id, long key_id, + char *desc, int desc_len, void *data) +{ + FILE *fd = data; + _cleanup_free_ const unsigned char *key_data = NULL; + _cleanup_free_ char *encoded_key = NULL; + int key_len; + + key_data = nvme_read_key(keyring_id, key_id, &key_len); + if (!key_data) + return; + encoded_key = nvme_export_tls_key(key_data, key_len); + if (!encoded_key) + return; + fprintf(fd, "%s %s\n", desc, encoded_key); +} + +static int tls_key(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "Manipulation of TLS keys.\n"; + const char *keyring = "Keyring for the retained key."; + const char *keytype = "Key type of the retained key."; + const char *keyfile = "File for list of keys."; + const char *import = "Import all keys into the keyring."; + const char *export = "Export all keys from the keyring."; + + FILE *fd; + int err = 0; + + struct config { + char *keyring; + char *keytype; + char *keyfile; + bool import; + bool export; + }; + + struct config cfg = { + .keyring = ".nvme", + .keytype = "psk", + .keyfile = NULL, + .import = false, + .export = false, + }; + + NVME_ARGS(opts, + OPT_STR("keyring", 'k', &cfg.keyring, keyring), + OPT_STR("keytype", 't', &cfg.keytype, keytype), + OPT_STR("keyfile", 'f', &cfg.keyfile, keyfile), + OPT_FLAG("import", 'i', &cfg.import, import), + OPT_FLAG("export", 'e', &cfg.export, export)); + + err = argconfig_parse(argc, argv, desc, opts); + if (err) + return err; + + if (cfg.keyfile) { + fd = fopen(cfg.keyfile, "r"); + if (!fd) { + nvme_show_error("Cannot open keyfile %s, error %d\n", + cfg.keyfile, errno); + return -errno; + } + } else + fd = stdin; + + if (cfg.export && cfg.import) { + nvme_show_error("Cannot specify both --import and --export"); + err = -EINVAL; + } else if (cfg.export) { + nvme_scan_tls_keys(cfg.keyring, __scan_tls_key, fd); + } else if (cfg.import) { + long keyring_id; + char tls_str[512]; + char *tls_key; + unsigned char *psk; + unsigned int hmac; + int linenum = -1, key_len; + + keyring_id = nvme_lookup_keyring(cfg.keyring); + if (!keyring_id) { + nvme_show_error("Invalid keyring '%s'", cfg.keyring); + err = -ENOKEY; + goto out; + } + + while (fgets(tls_str, 512, fd)) { + linenum++; + tls_key = strrchr(tls_str, ' '); + if (!tls_key) { + nvme_show_error("Parse error in line %d", + linenum); + continue; + } + *tls_key = '\0'; + tls_key++; + psk = nvme_import_tls_key(tls_key, &key_len, &hmac); + if (!psk) { + nvme_show_error("Failed to import key in line %d", + linenum); + continue; + } + nvme_update_key(keyring_id, "psk", tls_str, + psk, key_len); + free(psk); + } + } else { + nvme_show_error("Must specify either --import or --export"); + err = -EINVAL; + } + +out: + if (cfg.keyfile) + fclose(fd); + + return err; +} + static int show_topology_cmd(int argc, char **argv, struct command *command, struct plugin *plugin) { const char *desc = "Show the topology\n"; @@ -8886,7 +9545,7 @@ static int show_topology_cmd(int argc, char **argv, struct command *command, str return -EINVAL; } - r = nvme_create_root(stderr, map_log_level(!!(flags & VERBOSE), false)); + r = nvme_create_root(stderr, log_level); if (!r) { nvme_show_error("Failed to create topology root: %s", nvme_strerror(errno)); return -errno; @@ -8894,7 +9553,8 @@ static int show_topology_cmd(int argc, char **argv, struct command *command, str err = nvme_scan_topology(r, NULL, NULL); if (err < 0) { - nvme_show_error("Failed to scan topology: %s", nvme_strerror(errno)); + if (errno != ENOENT) + nvme_show_error("Failed to scan topology: %s", nvme_strerror(errno)); nvme_free_tree(r); return err; } |