Index: head/sbin/nvmecontrol/comnd.c =================================================================== --- head/sbin/nvmecontrol/comnd.c (revision 367629) +++ head/sbin/nvmecontrol/comnd.c (revision 367630) @@ -1,343 +1,344 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2019 Netflix, Inc * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include "comnd.h" static struct cmd top; static void print_tree(const struct cmd *f) { if (f->parent != NULL) print_tree(f->parent); if (f->name != NULL) fprintf(stderr, " %s", f->name); } static void print_usage(const struct cmd *f) { fprintf(stderr, " %s", getprogname()); print_tree(f->parent); fprintf(stderr, " %-15s - %s\n", f->name, f->descr); } static void gen_usage(const struct cmd *t) { struct cmd *walker; fprintf(stderr, "usage:\n"); SLIST_FOREACH(walker, &t->subcmd, link) { print_usage(walker); } - exit(1); + exit(EX_USAGE); } int cmd_dispatch(int argc, char *argv[], const struct cmd *t) { struct cmd *walker; if (t == NULL) t = ⊤ if (argv[1] == NULL) { gen_usage(t); return (1); } SLIST_FOREACH(walker, &t->subcmd, link) { if (strcmp(argv[1], walker->name) == 0) { walker->fn(walker, argc-1, &argv[1]); return (0); } } fprintf(stderr, "Unknown command: %s\n", argv[1]); gen_usage(t); return (1); } static void arg_suffix(char *buf, size_t len, arg_type at) { switch (at) { case arg_none: break; case arg_string: strlcat(buf, "=", len); break; case arg_path: strlcat(buf, "=", len); break; default: strlcat(buf, "=", len); break; } } void arg_help(int argc __unused, char * const *argv, const struct cmd *f) { int i; char buf[31]; const struct opts *opts = f->opts; const struct args *args = f->args; // XXX walk up the cmd list... if (argv[optind]) fprintf(stderr, "Unknown argument: %s\n", argv[optind]); fprintf(stderr, "Usage:\n %s", getprogname()); print_tree(f); if (opts) fprintf(stderr, " "); if (args) { while (args->descr != NULL) { fprintf(stderr, " %s", args->descr); args++; } } fprintf(stderr, "\n\n%s\n", f->descr); if (opts != NULL) { fprintf(stderr, "Options:\n"); for (i = 0; opts[i].long_arg != NULL; i++) { *buf = '\0'; if (isprint(opts[i].short_arg)) { snprintf(buf, sizeof(buf), " -%c, ", opts[i].short_arg); } else { strlcpy(buf, " ", sizeof(buf)); } strlcat(buf, "--", sizeof(buf)); strlcat(buf, opts[i].long_arg, sizeof(buf)); arg_suffix(buf, sizeof(buf), opts[i].at); fprintf(stderr, "%-30.30s - %s\n", buf, opts[i].descr); } } - exit(1); + exit(EX_USAGE); } static int find_long(struct option *lopts, int ch) { int i; for (i = 0; lopts[i].val != ch && lopts[i].name != NULL; i++) continue; return (i); } int arg_parse(int argc, char * const * argv, const struct cmd *f) { int i, n, idx, ch; uint64_t v; struct option *lopts; char *shortopts, *p; const struct opts *opts = f->opts; const struct args *args = f->args; if (opts == NULL) n = 0; else for (n = 0; opts[n].long_arg != NULL;) n++; lopts = malloc((n + 2) * sizeof(struct option)); if (lopts == NULL) - err(1, "option memory"); + err(EX_OSERR, "option memory"); p = shortopts = malloc((2 * n + 3) * sizeof(char)); if (shortopts == NULL) - err(1, "shortopts memory"); + err(EX_OSERR, "shortopts memory"); idx = 0; for (i = 0; i < n; i++) { lopts[i].name = opts[i].long_arg; lopts[i].has_arg = opts[i].at == arg_none ? no_argument : required_argument; lopts[i].flag = NULL; lopts[i].val = opts[i].short_arg; if (isprint(opts[i].short_arg)) { *p++ = opts[i].short_arg; if (lopts[i].has_arg) *p++ = ':'; } } lopts[n].name = "help"; lopts[n].has_arg = no_argument; lopts[n].flag = NULL; lopts[n].val = '?'; *p++ = '?'; *p++ = '\0'; memset(lopts + n + 1, 0, sizeof(struct option)); while ((ch = getopt_long(argc, argv, shortopts, lopts, &idx)) != -1) { /* * If ch != 0, we've found a short option, and we have to * look it up lopts table. Otherwise idx is valid. */ if (ch != 0) idx = find_long(lopts, ch); if (idx == n) arg_help(argc, argv, f); switch (opts[idx].at) { case arg_none: *(bool *)opts[idx].ptr = true; break; case arg_string: case arg_path: *(const char **)opts[idx].ptr = optarg; break; case arg_uint8: v = strtoul(optarg, NULL, 0); if (v > 0xff) goto bad_arg; *(uint8_t *)opts[idx].ptr = v; break; case arg_uint16: v = strtoul(optarg, NULL, 0); if (v > 0xffff) goto bad_arg; *(uint16_t *)opts[idx].ptr = v; break; case arg_uint32: v = strtoul(optarg, NULL, 0); if (v > 0xffffffffu) goto bad_arg; *(uint32_t *)opts[idx].ptr = v; break; case arg_uint64: v = strtoul(optarg, NULL, 0); if (v > 0xffffffffffffffffull) goto bad_arg; *(uint64_t *)opts[idx].ptr = v; break; case arg_size: if (expand_number(optarg, &v) < 0) goto bad_arg; *(uint64_t *)opts[idx].ptr = v; break; } } if (args) { while (args->descr) { if (optind >= argc) { fprintf(stderr, "Missing arg %s\n", args->descr); arg_help(argc, argv, f); free(lopts); free(shortopts); return (1); } *(char **)args->ptr = argv[optind++]; args++; } } free(lopts); free(shortopts); return (0); bad_arg: fprintf(stderr, "Bad value to --%s: %s\n", opts[idx].long_arg, optarg); free(lopts); free(shortopts); - exit(1); + exit(EX_USAGE); } /* * Loads all the .so's from the specified directory. */ void cmd_load_dir(const char *dir __unused, cmd_load_cb_t cb __unused, void *argp __unused) { DIR *d; struct dirent *dent; char *path = NULL; void *h; d = opendir(dir); if (d == NULL) return; for (dent = readdir(d); dent != NULL; dent = readdir(d)) { if (strcmp(".so", dent->d_name + dent->d_namlen - 3) != 0) continue; asprintf(&path, "%s/%s", dir, dent->d_name); if (path == NULL) - err(1, "Can't malloc for path, giving up."); + err(EX_OSERR, "Can't malloc for path, giving up."); if ((h = dlopen(path, RTLD_NOW | RTLD_GLOBAL)) == NULL) warnx("Can't load %s: %s", path, dlerror()); else { if (cb != NULL) cb(argp, h); } free(path); path = NULL; } closedir(d); } void cmd_register(struct cmd *up, struct cmd *cmd) { struct cmd *walker, *last; if (up == NULL) up = ⊤ SLIST_INIT(&cmd->subcmd); cmd->parent = up; last = NULL; SLIST_FOREACH(walker, &up->subcmd, link) { if (strcmp(walker->name, cmd->name) > 0) break; last = walker; } if (last == NULL) { SLIST_INSERT_HEAD(&up->subcmd, cmd, link); } else { SLIST_INSERT_AFTER(last, cmd, link); } } void cmd_init(void) { } Index: head/sbin/nvmecontrol/devlist.c =================================================================== --- head/sbin/nvmecontrol/devlist.c (revision 367629) +++ head/sbin/nvmecontrol/devlist.c (revision 367630) @@ -1,131 +1,134 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2012-2013 Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include +#include #include #include "nvmecontrol.h" #include "comnd.h" /* Tables for command line parsing */ #define NVME_MAX_UNIT 256 static cmd_fn_t devlist; static struct cmd devlist_cmd = { .name = "devlist", .fn = devlist, .descr = "List NVMe controllers and namespaces" }; CMD_COMMAND(devlist_cmd); /* End of tables for command line parsing */ static inline uint32_t ns_get_sector_size(struct nvme_namespace_data *nsdata) { uint8_t flbas_fmt, lbads; flbas_fmt = (nsdata->flbas >> NVME_NS_DATA_FLBAS_FORMAT_SHIFT) & NVME_NS_DATA_FLBAS_FORMAT_MASK; lbads = (nsdata->lbaf[flbas_fmt] >> NVME_NS_DATA_LBAF_LBADS_SHIFT) & NVME_NS_DATA_LBAF_LBADS_MASK; return (1 << lbads); } static void devlist(const struct cmd *f, int argc, char *argv[]) { struct nvme_controller_data cdata; struct nvme_namespace_data nsdata; char name[64]; uint8_t mn[64]; uint32_t i; int ctrlr, fd, found, ret; if (arg_parse(argc, argv, f)) return; ctrlr = -1; found = 0; while (ctrlr < NVME_MAX_UNIT) { ctrlr++; sprintf(name, "%s%d", NVME_CTRLR_PREFIX, ctrlr); ret = open_dev(name, &fd, 0, 0); if (ret == EACCES) { warnx("could not open "_PATH_DEV"%s\n", name); continue; } else if (ret != 0) continue; found++; - read_controller_data(fd, &cdata); + if (read_controller_data(fd, &cdata)) + continue; nvme_strvis(mn, cdata.mn, sizeof(mn), NVME_MODEL_NUMBER_LENGTH); printf("%6s: %s\n", name, mn); for (i = 0; i < cdata.nn; i++) { - read_namespace_data(fd, i + 1, &nsdata); + if (read_namespace_data(fd, i + 1, &nsdata)) + continue; if (nsdata.nsze == 0) continue; sprintf(name, "%s%d%s%d", NVME_CTRLR_PREFIX, ctrlr, NVME_NS_PREFIX, i + 1); printf(" %10s (%lldMB)\n", name, nsdata.nsze * (long long)ns_get_sector_size(&nsdata) / 1024 / 1024); } close(fd); } if (found == 0) { printf("No NVMe controllers found.\n"); - exit(1); + exit(EX_UNAVAILABLE); } exit(0); } Index: head/sbin/nvmecontrol/firmware.c =================================================================== --- head/sbin/nvmecontrol/firmware.c (revision 367629) +++ head/sbin/nvmecontrol/firmware.c (revision 367630) @@ -1,371 +1,373 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2013 EMC Corp. * All rights reserved. * * Copyright (C) 2012-2013 Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include "nvmecontrol.h" /* Tables for command line parsing */ static cmd_fn_t firmware; #define NONE 0xffffffffu static struct options { bool activate; uint32_t slot; const char *fw_img; const char *dev; } opt = { .activate = false, .slot = NONE, .fw_img = NULL, .dev = NULL, }; static const struct opts firmware_opts[] = { #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } OPT("activate", 'a', arg_none, opt, activate, "Attempt to activate firmware"), OPT("slot", 's', arg_uint32, opt, slot, "Slot to activate and/or download firmware to"), OPT("firmware", 'f', arg_path, opt, fw_img, "Firmware image to download"), { NULL, 0, arg_none, NULL, NULL } }; #undef OPT static const struct args firmware_args[] = { { arg_string, &opt.dev, "controller-id|namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd firmware_cmd = { .name = "firmware", .fn = firmware, .descr = "Download firmware image to controller", .ctx_size = sizeof(opt), .opts = firmware_opts, .args = firmware_args, }; CMD_COMMAND(firmware_cmd); /* End of tables for command line parsing */ static int slot_has_valid_firmware(int fd, int slot) { struct nvme_firmware_page fw; int has_fw = false; read_logpage(fd, NVME_LOG_FIRMWARE_SLOT, NVME_GLOBAL_NAMESPACE_TAG, 0, 0, 0, &fw, sizeof(fw)); if (fw.revision[slot-1] != 0LLU) has_fw = true; return (has_fw); } static void read_image_file(const char *path, void **buf, int32_t *size) { struct stat sb; int32_t filesize; int fd; *size = 0; *buf = NULL; if ((fd = open(path, O_RDONLY)) < 0) - err(1, "unable to open '%s'", path); + err(EX_NOINPUT, "unable to open '%s'", path); if (fstat(fd, &sb) < 0) - err(1, "unable to stat '%s'", path); + err(EX_NOINPUT, "unable to stat '%s'", path); /* * The NVMe spec does not explicitly state a maximum firmware image * size, although one can be inferred from the dword size limitation * for the size and offset fields in the Firmware Image Download * command. * * Technically, the max is UINT32_MAX * sizeof(uint32_t), since the * size and offsets are specified in terms of dwords (not bytes), but * realistically INT32_MAX is sufficient here and simplifies matters * a bit. */ if (sb.st_size > INT32_MAX) - errx(1, "size of file '%s' is too large (%jd bytes)", + errx(EX_USAGE, "size of file '%s' is too large (%jd bytes)", path, (intmax_t)sb.st_size); filesize = (int32_t)sb.st_size; if ((*buf = malloc(filesize)) == NULL) - errx(1, "unable to malloc %d bytes", filesize); + errx(EX_OSERR, "unable to malloc %d bytes", filesize); if ((*size = read(fd, *buf, filesize)) < 0) - err(1, "error reading '%s'", path); + err(EX_IOERR, "error reading '%s'", path); /* XXX assuming no short reads */ if (*size != filesize) - errx(1, + errx(EX_IOERR, "error reading '%s' (read %d bytes, requested %d bytes)", path, *size, filesize); close(fd); } static void update_firmware(int fd, uint8_t *payload, int32_t payload_size, uint8_t fwug) { struct nvme_pt_command pt; uint64_t max_xfer_size; int32_t off, resid, size; void *chunk; off = 0; resid = payload_size; if (fwug != 0 && fwug != 0xFF) max_xfer_size = ((uint64_t)fwug << 12); else if (ioctl(fd, NVME_GET_MAX_XFER_SIZE, &max_xfer_size) < 0) - err(1, "query max transfer size failed"); + err(EX_IOERR, "query max transfer size failed"); if (max_xfer_size > NVME_MAX_XFER_SIZE) max_xfer_size = NVME_MAX_XFER_SIZE; if ((chunk = aligned_alloc(PAGE_SIZE, max_xfer_size)) == NULL) - errx(1, "unable to malloc %zd bytes", (size_t)max_xfer_size); + errx(EX_OSERR, "unable to malloc %zd bytes", (size_t)max_xfer_size); while (resid > 0) { size = (resid >= (int32_t)max_xfer_size) ? max_xfer_size : resid; memcpy(chunk, payload + off, size); memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_FIRMWARE_IMAGE_DOWNLOAD; pt.cmd.cdw10 = htole32((size / sizeof(uint32_t)) - 1); pt.cmd.cdw11 = htole32(off / sizeof(uint32_t)); pt.buf = chunk; pt.len = size; pt.is_read = 0; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "firmware download request failed"); + err(EX_IOERR, "firmware download request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "firmware download request returned error"); + errx(EX_IOERR, "firmware download request returned error"); resid -= size; off += size; } free(chunk); } static int activate_firmware(int fd, int slot, int activate_action) { struct nvme_pt_command pt; uint16_t sct, sc; memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_FIRMWARE_ACTIVATE; pt.cmd.cdw10 = htole32((activate_action << 3) | slot); pt.is_read = 0; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "firmware activate request failed"); + err(EX_IOERR, "firmware activate request failed"); sct = NVME_STATUS_GET_SCT(pt.cpl.status); sc = NVME_STATUS_GET_SC(pt.cpl.status); if (sct == NVME_SCT_COMMAND_SPECIFIC && sc == NVME_SC_FIRMWARE_REQUIRES_RESET) return 1; if (nvme_completion_is_error(&pt.cpl)) - errx(1, "firmware activate request returned error"); + errx(EX_IOERR, "firmware activate request returned error"); return 0; } static void firmware(const struct cmd *f, int argc, char *argv[]) { int fd = -1; int activate_action, reboot_required; char prompt[64]; void *buf = NULL; char *path; int32_t size = 0, nsid; uint16_t oacs_fw; uint8_t fw_slot1_ro, fw_num_slots; struct nvme_controller_data cdata; if (arg_parse(argc, argv, f)) return; if (opt.slot == 0) { fprintf(stderr, "0 is not a valid slot number. " "Slot numbers start at 1.\n"); arg_help(argc, argv, f); } else if (opt.slot > 7 && opt.slot != NONE) { fprintf(stderr, "Slot number %s specified which is " "greater than max allowed slot number of " "7.\n", optarg); arg_help(argc, argv, f); } if (!opt.activate && opt.fw_img == NULL) { fprintf(stderr, "Neither a replace ([-f path_to_firmware]) nor " "activate ([-a]) firmware image action\n" "was specified.\n"); arg_help(argc, argv, f); } if (opt.activate && opt.fw_img == NULL && opt.slot == 0) { fprintf(stderr, "Slot number to activate not specified.\n"); arg_help(argc, argv, f); } open_dev(opt.dev, &fd, 1, 1); get_nsid(fd, &path, &nsid); if (nsid != 0) { close(fd); open_dev(path, &fd, 1, 1); } free(path); - read_controller_data(fd, &cdata); + if (read_controller_data(fd, &cdata)) + errx(EX_IOERR, "Identify request failed"); oacs_fw = (cdata.oacs >> NVME_CTRLR_DATA_OACS_FIRMWARE_SHIFT) & NVME_CTRLR_DATA_OACS_FIRMWARE_MASK; if (oacs_fw == 0) - errx(1, + errx(EX_UNAVAILABLE, "controller does not support firmware activate/download"); fw_slot1_ro = (cdata.frmw >> NVME_CTRLR_DATA_FRMW_SLOT1_RO_SHIFT) & NVME_CTRLR_DATA_FRMW_SLOT1_RO_MASK; if (opt.fw_img && opt.slot == 1 && fw_slot1_ro) - errx(1, "slot %d is marked as read only", opt.slot); + errx(EX_UNAVAILABLE, "slot %d is marked as read only", opt.slot); fw_num_slots = (cdata.frmw >> NVME_CTRLR_DATA_FRMW_NUM_SLOTS_SHIFT) & NVME_CTRLR_DATA_FRMW_NUM_SLOTS_MASK; if (opt.slot > fw_num_slots) - errx(1, + errx(EX_UNAVAILABLE, "slot %d specified but controller only supports %d slots", opt.slot, fw_num_slots); if (opt.activate && opt.fw_img == NULL && !slot_has_valid_firmware(fd, opt.slot)) - errx(1, + errx(EX_UNAVAILABLE, "slot %d does not contain valid firmware,\n" "try 'nvmecontrol logpage -p 3 %s' to get a list " "of available images\n", opt.slot, opt.dev); if (opt.fw_img) read_image_file(opt.fw_img, &buf, &size); if (opt.fw_img != NULL&& opt.activate) printf("You are about to download and activate " "firmware image (%s) to controller %s.\n" "This may damage your controller and/or " "overwrite an existing firmware image.\n", opt.fw_img, opt.dev); else if (opt.activate) printf("You are about to activate a new firmware " "image on controller %s.\n" "This may damage your controller.\n", opt.dev); else if (opt.fw_img != NULL) printf("You are about to download firmware image " "(%s) to controller %s.\n" "This may damage your controller and/or " "overwrite an existing firmware image.\n", opt.fw_img, opt.dev); printf("Are you sure you want to continue? (yes/no) "); while (1) { fgets(prompt, sizeof(prompt), stdin); if (strncasecmp(prompt, "yes", 3) == 0) break; if (strncasecmp(prompt, "no", 2) == 0) - exit(1); + exit(EX_DATAERR); printf("Please answer \"yes\" or \"no\". "); } if (opt.fw_img != NULL) { update_firmware(fd, buf, size, cdata.fwug); if (opt.activate) activate_action = NVME_AA_REPLACE_ACTIVATE; else activate_action = NVME_AA_REPLACE_NO_ACTIVATE; } else { activate_action = NVME_AA_ACTIVATE; } reboot_required = activate_firmware(fd, opt.slot, activate_action); if (opt.activate) { if (reboot_required) { printf("New firmware image activated but requires " "conventional reset (i.e. reboot) to " "complete activation.\n"); } else { printf("New firmware image activated and will take " "effect after next controller reset.\n" "Controller reset can be initiated via " "'nvmecontrol reset %s'\n", opt.dev); } } close(fd); exit(0); } Index: head/sbin/nvmecontrol/format.c =================================================================== --- head/sbin/nvmecontrol/format.c (revision 367629) +++ head/sbin/nvmecontrol/format.c (revision 367630) @@ -1,220 +1,223 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2018-2019 Alexander Motin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include +#include #include #include "nvmecontrol.h" #define NONE 0xffffffffu #define SES_NONE 0 #define SES_USER 1 #define SES_CRYPTO 2 /* Tables for command line parsing */ static cmd_fn_t format; static struct options { uint32_t lbaf; uint32_t ms; uint32_t pi; uint32_t pil; uint32_t ses; bool Eflag; bool Cflag; const char *dev; } opt = { .lbaf = NONE, .ms = NONE, .pi = NONE, .pil = NONE, .ses = SES_NONE, .Eflag = false, .Cflag = false, .dev = NULL, }; static const struct opts format_opts[] = { #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } OPT("crypto", 'C', arg_none, opt, Cflag, "Crptographic erase"), OPT("erase", 'E', arg_none, opt, Eflag, "User data erase"), OPT("lbaf", 'f', arg_uint32, opt, lbaf, "LBA Format to apply to the media"), OPT("ms", 'm', arg_uint32, opt, ms, "Metadata settings"), OPT("pi", 'p', arg_uint32, opt, pi, "Protective information"), OPT("pil", 'l', arg_uint32, opt, pil, "Protective information location"), OPT("ses", 's', arg_uint32, opt, ses, "Secure erase settings"), { NULL, 0, arg_none, NULL, NULL } }; #undef OPT static const struct args format_args[] = { { arg_string, &opt.dev, "controller-id|namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd format_cmd = { .name = "format", .fn = format, .descr = "Format/erase one or all the namespaces", .ctx_size = sizeof(opt), .opts = format_opts, .args = format_args, }; CMD_COMMAND(format_cmd); /* End of tables for command line parsing */ static void format(const struct cmd *f, int argc, char *argv[]) { struct nvme_controller_data cd; struct nvme_namespace_data nsd; struct nvme_pt_command pt; char *path; const char *target; uint32_t nsid; int lbaf, ms, pi, pil, ses, fd; if (arg_parse(argc, argv, f)) return; if ((int)opt.Eflag + opt.Cflag + (opt.ses != SES_NONE) > 1) { fprintf(stderr, "Only one of -E, -C or -s may be specified\n"); arg_help(argc, argv, f); } target = opt.dev; lbaf = opt.lbaf; ms = opt.ms; pi = opt.pi; pil = opt.pil; if (opt.Eflag) ses = SES_USER; else if (opt.Cflag) ses = SES_CRYPTO; else ses = opt.ses; open_dev(target, &fd, 1, 1); get_nsid(fd, &path, &nsid); if (nsid == 0) { nsid = NVME_GLOBAL_NAMESPACE_TAG; } else { /* * We send FORMAT commands to the controller, not the namespace, * since it is an admin cmd. The namespace ID will be specified * in the command itself. So parse the namespace's device node * string to get the controller substring and namespace ID. */ close(fd); open_dev(path, &fd, 1, 1); } free(path); /* Check that controller can execute this command. */ - read_controller_data(fd, &cd); + if (read_controller_data(fd, &cd)) + errx(EX_IOERR, "Identify request failed"); if (((cd.oacs >> NVME_CTRLR_DATA_OACS_FORMAT_SHIFT) & NVME_CTRLR_DATA_OACS_FORMAT_MASK) == 0) - errx(1, "controller does not support format"); + errx(EX_UNAVAILABLE, "controller does not support format"); if (((cd.fna >> NVME_CTRLR_DATA_FNA_CRYPTO_ERASE_SHIFT) & NVME_CTRLR_DATA_FNA_CRYPTO_ERASE_MASK) == 0 && ses == SES_CRYPTO) - errx(1, "controller does not support cryptographic erase"); + errx(EX_UNAVAILABLE, "controller does not support cryptographic erase"); if (nsid != NVME_GLOBAL_NAMESPACE_TAG) { if (((cd.fna >> NVME_CTRLR_DATA_FNA_FORMAT_ALL_SHIFT) & NVME_CTRLR_DATA_FNA_FORMAT_ALL_MASK) && ses == SES_NONE) - errx(1, "controller does not support per-NS format"); + errx(EX_UNAVAILABLE, "controller does not support per-NS format"); if (((cd.fna >> NVME_CTRLR_DATA_FNA_ERASE_ALL_SHIFT) & NVME_CTRLR_DATA_FNA_ERASE_ALL_MASK) && ses != SES_NONE) - errx(1, "controller does not support per-NS erase"); + errx(EX_UNAVAILABLE, "controller does not support per-NS erase"); /* Try to keep previous namespace parameters. */ - read_namespace_data(fd, nsid, &nsd); + if (read_namespace_data(fd, nsid, &nsd)) + errx(EX_IOERR, "Identify request failed"); if (lbaf < 0) lbaf = (nsd.flbas >> NVME_NS_DATA_FLBAS_FORMAT_SHIFT) & NVME_NS_DATA_FLBAS_FORMAT_MASK; if (lbaf > nsd.nlbaf) - errx(1, "LBA format is out of range"); + errx(EX_USAGE, "LBA format is out of range"); if (ms < 0) ms = (nsd.flbas >> NVME_NS_DATA_FLBAS_EXTENDED_SHIFT) & NVME_NS_DATA_FLBAS_EXTENDED_MASK; if (pi < 0) pi = (nsd.dps >> NVME_NS_DATA_DPS_MD_START_SHIFT) & NVME_NS_DATA_DPS_MD_START_MASK; if (pil < 0) pil = (nsd.dps >> NVME_NS_DATA_DPS_PIT_SHIFT) & NVME_NS_DATA_DPS_PIT_MASK; } else { /* We have no previous parameters, so default to zeroes. */ if (lbaf < 0) lbaf = 0; if (ms < 0) ms = 0; if (pi < 0) pi = 0; if (pil < 0) pil = 0; } memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_FORMAT_NVM; pt.cmd.nsid = htole32(nsid); pt.cmd.cdw10 = htole32((ses << 9) + (pil << 8) + (pi << 5) + (ms << 4) + lbaf); if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "format request failed"); + err(EX_IOERR, "format request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "format request returned error"); + errx(EX_IOERR, "format request returned error"); close(fd); exit(0); } Index: head/sbin/nvmecontrol/identify.c =================================================================== --- head/sbin/nvmecontrol/identify.c (revision 367629) +++ head/sbin/nvmecontrol/identify.c (revision 367630) @@ -1,292 +1,295 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2012-2013 Intel Corporation * All rights reserved. * Copyright (C) 2018-2019 Alexander Motin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include +#include #include #include "nvmecontrol.h" #include "nvmecontrol_ext.h" #define NONE 0xfffffffeu static struct options { bool hex; bool verbose; const char *dev; uint32_t nsid; } opt = { .hex = false, .verbose = false, .dev = NULL, .nsid = NONE, }; void print_namespace(struct nvme_namespace_data *nsdata) { char cbuf[UINT128_DIG + 1]; uint32_t i; uint32_t lbaf, lbads, ms, rp; uint8_t thin_prov, ptype; uint8_t flbas_fmt, t; thin_prov = (nsdata->nsfeat >> NVME_NS_DATA_NSFEAT_THIN_PROV_SHIFT) & NVME_NS_DATA_NSFEAT_THIN_PROV_MASK; flbas_fmt = (nsdata->flbas >> NVME_NS_DATA_FLBAS_FORMAT_SHIFT) & NVME_NS_DATA_FLBAS_FORMAT_MASK; printf("Size: %lld blocks\n", (long long)nsdata->nsze); printf("Capacity: %lld blocks\n", (long long)nsdata->ncap); printf("Utilization: %lld blocks\n", (long long)nsdata->nuse); printf("Thin Provisioning: %s\n", thin_prov ? "Supported" : "Not Supported"); printf("Number of LBA Formats: %d\n", nsdata->nlbaf+1); printf("Current LBA Format: LBA Format #%02d\n", flbas_fmt); printf("Data Protection Caps: %s%s%s%s%s%s\n", (nsdata->dpc == 0) ? "Not Supported" : "", ((nsdata->dpc >> NVME_NS_DATA_DPC_MD_END_SHIFT) & NVME_NS_DATA_DPC_MD_END_MASK) ? "Last Bytes, " : "", ((nsdata->dpc >> NVME_NS_DATA_DPC_MD_START_SHIFT) & NVME_NS_DATA_DPC_MD_START_MASK) ? "First Bytes, " : "", ((nsdata->dpc >> NVME_NS_DATA_DPC_PIT3_SHIFT) & NVME_NS_DATA_DPC_PIT3_MASK) ? "Type 3, " : "", ((nsdata->dpc >> NVME_NS_DATA_DPC_PIT2_SHIFT) & NVME_NS_DATA_DPC_PIT2_MASK) ? "Type 2, " : "", ((nsdata->dpc >> NVME_NS_DATA_DPC_PIT1_SHIFT) & NVME_NS_DATA_DPC_PIT1_MASK) ? "Type 1" : ""); printf("Data Protection Settings: "); ptype = (nsdata->dps >> NVME_NS_DATA_DPS_PIT_SHIFT) & NVME_NS_DATA_DPS_PIT_MASK; if (ptype) { printf("Type %d, %s Bytes\n", ptype, ((nsdata->dps >> NVME_NS_DATA_DPS_MD_START_SHIFT) & NVME_NS_DATA_DPS_MD_START_MASK) ? "First" : "Last"); } else { printf("Not Enabled\n"); } printf("Multi-Path I/O Capabilities: %s%s\n", (nsdata->nmic == 0) ? "Not Supported" : "", ((nsdata->nmic >> NVME_NS_DATA_NMIC_MAY_BE_SHARED_SHIFT) & NVME_NS_DATA_NMIC_MAY_BE_SHARED_MASK) ? "May be shared" : ""); printf("Reservation Capabilities: %s%s%s%s%s%s%s%s%s\n", (nsdata->rescap == 0) ? "Not Supported" : "", ((nsdata->rescap >> NVME_NS_DATA_RESCAP_IEKEY13_SHIFT) & NVME_NS_DATA_RESCAP_IEKEY13_MASK) ? "IEKEY13, " : "", ((nsdata->rescap >> NVME_NS_DATA_RESCAP_EX_AC_AR_SHIFT) & NVME_NS_DATA_RESCAP_EX_AC_AR_MASK) ? "EX_AC_AR, " : "", ((nsdata->rescap >> NVME_NS_DATA_RESCAP_WR_EX_AR_SHIFT) & NVME_NS_DATA_RESCAP_WR_EX_AR_MASK) ? "WR_EX_AR, " : "", ((nsdata->rescap >> NVME_NS_DATA_RESCAP_EX_AC_RO_SHIFT) & NVME_NS_DATA_RESCAP_EX_AC_RO_MASK) ? "EX_AC_RO, " : "", ((nsdata->rescap >> NVME_NS_DATA_RESCAP_WR_EX_RO_SHIFT) & NVME_NS_DATA_RESCAP_WR_EX_RO_MASK) ? "WR_EX_RO, " : "", ((nsdata->rescap >> NVME_NS_DATA_RESCAP_EX_AC_SHIFT) & NVME_NS_DATA_RESCAP_EX_AC_MASK) ? "EX_AC, " : "", ((nsdata->rescap >> NVME_NS_DATA_RESCAP_WR_EX_SHIFT) & NVME_NS_DATA_RESCAP_WR_EX_MASK) ? "WR_EX, " : "", ((nsdata->rescap >> NVME_NS_DATA_RESCAP_PTPL_SHIFT) & NVME_NS_DATA_RESCAP_PTPL_MASK) ? "PTPL" : ""); printf("Format Progress Indicator: "); if ((nsdata->fpi >> NVME_NS_DATA_FPI_SUPP_SHIFT) & NVME_NS_DATA_FPI_SUPP_MASK) { printf("%u%% remains\n", (nsdata->fpi >> NVME_NS_DATA_FPI_PERC_SHIFT) & NVME_NS_DATA_FPI_PERC_MASK); } else printf("Not Supported\n"); t = (nsdata->dlfeat >> NVME_NS_DATA_DLFEAT_READ_SHIFT) & NVME_NS_DATA_DLFEAT_READ_MASK; printf("Deallocate Logical Block: Read %s%s%s\n", (t == NVME_NS_DATA_DLFEAT_READ_NR) ? "Not Reported" : (t == NVME_NS_DATA_DLFEAT_READ_00) ? "00h" : (t == NVME_NS_DATA_DLFEAT_READ_FF) ? "FFh" : "Unknown", (nsdata->dlfeat >> NVME_NS_DATA_DLFEAT_DWZ_SHIFT) & NVME_NS_DATA_DLFEAT_DWZ_MASK ? ", Write Zero" : "", (nsdata->dlfeat >> NVME_NS_DATA_DLFEAT_GCRC_SHIFT) & NVME_NS_DATA_DLFEAT_GCRC_MASK ? ", Guard CRC" : ""); printf("Optimal I/O Boundary: %u blocks\n", nsdata->noiob); printf("NVM Capacity: %s bytes\n", uint128_to_str(to128(nsdata->nvmcap), cbuf, sizeof(cbuf))); if ((nsdata->nsfeat >> NVME_NS_DATA_NSFEAT_NPVALID_SHIFT) & NVME_NS_DATA_NSFEAT_NPVALID_MASK) { printf("Preferred Write Granularity: %u blocks\n", nsdata->npwg + 1); printf("Preferred Write Alignment: %u blocks\n", nsdata->npwa + 1); printf("Preferred Deallocate Granul: %u blocks\n", nsdata->npdg + 1); printf("Preferred Deallocate Align: %u blocks\n", nsdata->npda + 1); printf("Optimal Write Size: %u blocks\n", nsdata->nows + 1); } printf("Globally Unique Identifier: "); for (i = 0; i < sizeof(nsdata->nguid); i++) printf("%02x", nsdata->nguid[i]); printf("\n"); printf("IEEE EUI64: "); for (i = 0; i < sizeof(nsdata->eui64); i++) printf("%02x", nsdata->eui64[i]); printf("\n"); for (i = 0; i <= nsdata->nlbaf; i++) { lbaf = nsdata->lbaf[i]; lbads = (lbaf >> NVME_NS_DATA_LBAF_LBADS_SHIFT) & NVME_NS_DATA_LBAF_LBADS_MASK; ms = (lbaf >> NVME_NS_DATA_LBAF_MS_SHIFT) & NVME_NS_DATA_LBAF_MS_MASK; rp = (lbaf >> NVME_NS_DATA_LBAF_RP_SHIFT) & NVME_NS_DATA_LBAF_RP_MASK; printf("LBA Format #%02d: Data Size: %5d Metadata Size: %5d" " Performance: %s\n", i, 1 << lbads, ms, (rp == 0) ? "Best" : (rp == 1) ? "Better" : (rp == 2) ? "Good" : "Degraded"); } } static void identify_ctrlr(int fd) { struct nvme_controller_data cdata; int hexlength; - read_controller_data(fd, &cdata); + if (read_controller_data(fd, &cdata)) + errx(EX_IOERR, "Identify request failed"); close(fd); if (opt.hex) { if (opt.verbose) hexlength = sizeof(struct nvme_controller_data); else hexlength = offsetof(struct nvme_controller_data, reserved8); print_hex(&cdata, hexlength); exit(0); } nvme_print_controller(&cdata); exit(0); } static void identify_ns(int fd, uint32_t nsid) { struct nvme_namespace_data nsdata; int hexlength; - read_namespace_data(fd, nsid, &nsdata); + if (read_namespace_data(fd, nsid, &nsdata)) + errx(EX_IOERR, "Identify request failed"); close(fd); if (opt.hex) { if (opt.verbose) hexlength = sizeof(struct nvme_namespace_data); else hexlength = offsetof(struct nvme_namespace_data, reserved6); print_hex(&nsdata, hexlength); exit(0); } print_namespace(&nsdata); exit(0); } static void identify(const struct cmd *f, int argc, char *argv[]) { char *path; int fd; uint32_t nsid; if (arg_parse(argc, argv, f)) return; open_dev(opt.dev, &fd, 0, 1); get_nsid(fd, &path, &nsid); if (nsid != 0) { /* * We got namespace device, but we need to send IDENTIFY * commands to the controller, not the namespace, since it * is an admin cmd. The namespace ID will be specified in * the IDENTIFY command itself. */ close(fd); open_dev(path, &fd, 0, 1); } free(path); if (opt.nsid != NONE) nsid = opt.nsid; if (nsid == 0) identify_ctrlr(fd); else identify_ns(fd, nsid); } static const struct opts identify_opts[] = { #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } OPT("hex", 'x', arg_none, opt, hex, "Print identiy information in hex"), OPT("verbose", 'v', arg_none, opt, verbose, "More verbosity: print entire identify table"), OPT("nsid", 'n', arg_uint32, opt, nsid, "Namespace ID to use if not in device name"), { NULL, 0, arg_none, NULL, NULL } }; #undef OPT static const struct args identify_args[] = { { arg_string, &opt.dev, "controller-id|namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd identify_cmd = { .name = "identify", .fn = identify, .descr = "Print summary of the IDENTIFY information", .ctx_size = sizeof(opt), .opts = identify_opts, .args = identify_args, }; CMD_COMMAND(identify_cmd); Index: head/sbin/nvmecontrol/logpage.c =================================================================== --- head/sbin/nvmecontrol/logpage.c (revision 367629) +++ head/sbin/nvmecontrol/logpage.c (revision 367630) @@ -1,757 +1,759 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2013 EMC Corp. * All rights reserved. * * Copyright (C) 2012-2013 Intel Corporation * All rights reserved. * Copyright (C) 2018-2019 Alexander Motin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include +#include #include #include #include "nvmecontrol.h" /* Tables for command line parsing */ static cmd_fn_t logpage; #define NONE 0xffffffffu static struct options { bool binary; bool hex; uint32_t page; uint8_t lsp; uint16_t lsi; bool rae; const char *vendor; const char *dev; } opt = { .binary = false, .hex = false, .page = NONE, .lsp = 0, .lsi = 0, .rae = false, .vendor = NULL, .dev = NULL, }; static const struct opts logpage_opts[] = { #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } OPT("binary", 'b', arg_none, opt, binary, "Dump the log page as binary"), OPT("hex", 'x', arg_none, opt, hex, "Dump the log page as hex"), OPT("page", 'p', arg_uint32, opt, page, "Page to dump"), OPT("lsp", 'f', arg_uint8, opt, lsp, "Log Specific Field"), OPT("lsi", 'i', arg_uint16, opt, lsi, "Log Specific Identifier"), OPT("rae", 'r', arg_none, opt, rae, "Retain Asynchronous Event"), OPT("vendor", 'v', arg_string, opt, vendor, "Vendor specific formatting"), { NULL, 0, arg_none, NULL, NULL } }; #undef OPT static const struct args logpage_args[] = { { arg_string, &opt.dev, "" }, { arg_none, NULL, NULL }, }; static struct cmd logpage_cmd = { .name = "logpage", .fn = logpage, .descr = "Print logpages in human-readable form", .ctx_size = sizeof(opt), .opts = logpage_opts, .args = logpage_args, }; CMD_COMMAND(logpage_cmd); /* End of tables for command line parsing */ #define MAX_FW_SLOTS (7) static SLIST_HEAD(,logpage_function) logpages; static int logpage_compare(struct logpage_function *a, struct logpage_function *b) { int c; if ((a->vendor == NULL) != (b->vendor == NULL)) return (a->vendor == NULL ? -1 : 1); if (a->vendor != NULL) { c = strcmp(a->vendor, b->vendor); if (c != 0) return (c); } return ((int)a->log_page - (int)b->log_page); } void logpage_register(struct logpage_function *p) { struct logpage_function *l, *a; a = NULL; l = SLIST_FIRST(&logpages); while (l != NULL) { if (logpage_compare(l, p) > 0) break; a = l; l = SLIST_NEXT(l, link); } if (a == NULL) SLIST_INSERT_HEAD(&logpages, p, link); else SLIST_INSERT_AFTER(a, p, link); } const char * kv_lookup(const struct kv_name *kv, size_t kv_count, uint32_t key) { static char bad[32]; size_t i; for (i = 0; i < kv_count; i++, kv++) if (kv->key == key) return kv->name; snprintf(bad, sizeof(bad), "Attribute %#x", key); return bad; } static void print_log_hex(const struct nvme_controller_data *cdata __unused, void *data, uint32_t length) { print_hex(data, length); } static void print_bin(const struct nvme_controller_data *cdata __unused, void *data, uint32_t length) { write(STDOUT_FILENO, data, length); } static void * get_log_buffer(uint32_t size) { void *buf; if ((buf = malloc(size)) == NULL) - errx(1, "unable to malloc %u bytes", size); + errx(EX_OSERR, "unable to malloc %u bytes", size); memset(buf, 0, size); return (buf); } void read_logpage(int fd, uint8_t log_page, uint32_t nsid, uint8_t lsp, uint16_t lsi, uint8_t rae, void *payload, uint32_t payload_size) { struct nvme_pt_command pt; struct nvme_error_information_entry *err_entry; u_int i, err_pages, numd; numd = payload_size / sizeof(uint32_t) - 1; memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_GET_LOG_PAGE; pt.cmd.nsid = htole32(nsid); pt.cmd.cdw10 = htole32( (numd << 16) | /* NUMDL */ (rae << 15) | /* RAE */ (lsp << 8) | /* LSP */ log_page); /* LID */ pt.cmd.cdw11 = htole32( ((uint32_t)lsi << 16) | /* LSI */ (numd >> 16)); /* NUMDU */ pt.cmd.cdw12 = 0; /* LPOL */ pt.cmd.cdw13 = 0; /* LPOU */ pt.cmd.cdw14 = 0; /* UUID Index */ pt.buf = payload; pt.len = payload_size; pt.is_read = 1; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "get log page request failed"); + err(EX_IOERR, "get log page request failed"); /* Convert data to host endian */ switch (log_page) { case NVME_LOG_ERROR: err_entry = (struct nvme_error_information_entry *)payload; err_pages = payload_size / sizeof(struct nvme_error_information_entry); for (i = 0; i < err_pages; i++) nvme_error_information_entry_swapbytes(err_entry++); break; case NVME_LOG_HEALTH_INFORMATION: nvme_health_information_page_swapbytes( (struct nvme_health_information_page *)payload); break; case NVME_LOG_FIRMWARE_SLOT: nvme_firmware_page_swapbytes( (struct nvme_firmware_page *)payload); break; case NVME_LOG_CHANGED_NAMESPACE: nvme_ns_list_swapbytes((struct nvme_ns_list *)payload); break; case NVME_LOG_COMMAND_EFFECT: nvme_command_effects_page_swapbytes( (struct nvme_command_effects_page *)payload); break; case NVME_LOG_RES_NOTIFICATION: nvme_res_notification_page_swapbytes( (struct nvme_res_notification_page *)payload); break; case NVME_LOG_SANITIZE_STATUS: nvme_sanitize_status_page_swapbytes( (struct nvme_sanitize_status_page *)payload); break; case INTEL_LOG_TEMP_STATS: intel_log_temp_stats_swapbytes( (struct intel_log_temp_stats *)payload); break; default: break; } if (nvme_completion_is_error(&pt.cpl)) - errx(1, "get log page request returned error"); + errx(EX_IOERR, "get log page request returned error"); } static void print_log_error(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size) { int i, nentries; uint16_t status; uint8_t p, sc, sct, m, dnr; struct nvme_error_information_entry *entry = buf; printf("Error Information Log\n"); printf("=====================\n"); if (entry->error_count == 0) { printf("No error entries found\n"); return; } nentries = size/sizeof(struct nvme_error_information_entry); for (i = 0; i < nentries; i++, entry++) { if (entry->error_count == 0) break; status = entry->status; p = NVME_STATUS_GET_P(status); sc = NVME_STATUS_GET_SC(status); sct = NVME_STATUS_GET_SCT(status); m = NVME_STATUS_GET_M(status); dnr = NVME_STATUS_GET_DNR(status); printf("Entry %02d\n", i + 1); printf("=========\n"); printf(" Error count: %ju\n", entry->error_count); printf(" Submission queue ID: %u\n", entry->sqid); printf(" Command ID: %u\n", entry->cid); /* TODO: Export nvme_status_string structures from kernel? */ printf(" Status:\n"); printf(" Phase tag: %d\n", p); printf(" Status code: %d\n", sc); printf(" Status code type: %d\n", sct); printf(" More: %d\n", m); printf(" DNR: %d\n", dnr); printf(" Error location: %u\n", entry->error_location); printf(" LBA: %ju\n", entry->lba); printf(" Namespace ID: %u\n", entry->nsid); printf(" Vendor specific info: %u\n", entry->vendor_specific); printf(" Transport type: %u\n", entry->trtype); printf(" Command specific info:%ju\n", entry->csi); printf(" Transport specific: %u\n", entry->ttsi); } } void print_temp(uint16_t t) { printf("%u K, %2.2f C, %3.2f F\n", t, (float)t - 273.15, (float)t * 9 / 5 - 459.67); } static void print_log_health(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused) { struct nvme_health_information_page *health = buf; char cbuf[UINT128_DIG + 1]; uint8_t warning; int i; warning = health->critical_warning; printf("SMART/Health Information Log\n"); printf("============================\n"); printf("Critical Warning State: 0x%02x\n", warning); printf(" Available spare: %d\n", !!(warning & NVME_CRIT_WARN_ST_AVAILABLE_SPARE)); printf(" Temperature: %d\n", !!(warning & NVME_CRIT_WARN_ST_TEMPERATURE)); printf(" Device reliability: %d\n", !!(warning & NVME_CRIT_WARN_ST_DEVICE_RELIABILITY)); printf(" Read only: %d\n", !!(warning & NVME_CRIT_WARN_ST_READ_ONLY)); printf(" Volatile memory backup: %d\n", !!(warning & NVME_CRIT_WARN_ST_VOLATILE_MEMORY_BACKUP)); printf("Temperature: "); print_temp(health->temperature); printf("Available spare: %u\n", health->available_spare); printf("Available spare threshold: %u\n", health->available_spare_threshold); printf("Percentage used: %u\n", health->percentage_used); printf("Data units (512,000 byte) read: %s\n", uint128_to_str(to128(health->data_units_read), cbuf, sizeof(cbuf))); printf("Data units written: %s\n", uint128_to_str(to128(health->data_units_written), cbuf, sizeof(cbuf))); printf("Host read commands: %s\n", uint128_to_str(to128(health->host_read_commands), cbuf, sizeof(cbuf))); printf("Host write commands: %s\n", uint128_to_str(to128(health->host_write_commands), cbuf, sizeof(cbuf))); printf("Controller busy time (minutes): %s\n", uint128_to_str(to128(health->controller_busy_time), cbuf, sizeof(cbuf))); printf("Power cycles: %s\n", uint128_to_str(to128(health->power_cycles), cbuf, sizeof(cbuf))); printf("Power on hours: %s\n", uint128_to_str(to128(health->power_on_hours), cbuf, sizeof(cbuf))); printf("Unsafe shutdowns: %s\n", uint128_to_str(to128(health->unsafe_shutdowns), cbuf, sizeof(cbuf))); printf("Media errors: %s\n", uint128_to_str(to128(health->media_errors), cbuf, sizeof(cbuf))); printf("No. error info log entries: %s\n", uint128_to_str(to128(health->num_error_info_log_entries), cbuf, sizeof(cbuf))); printf("Warning Temp Composite Time: %d\n", health->warning_temp_time); printf("Error Temp Composite Time: %d\n", health->error_temp_time); for (i = 0; i < 8; i++) { if (health->temp_sensor[i] == 0) continue; printf("Temperature Sensor %d: ", i + 1); print_temp(health->temp_sensor[i]); } printf("Temperature 1 Transition Count: %d\n", health->tmt1tc); printf("Temperature 2 Transition Count: %d\n", health->tmt2tc); printf("Total Time For Temperature 1: %d\n", health->ttftmt1); printf("Total Time For Temperature 2: %d\n", health->ttftmt2); } static void print_log_firmware(const struct nvme_controller_data *cdata, void *buf, uint32_t size __unused) { int i, slots; const char *status; struct nvme_firmware_page *fw = buf; uint8_t afi_slot; uint16_t oacs_fw; uint8_t fw_num_slots; afi_slot = fw->afi >> NVME_FIRMWARE_PAGE_AFI_SLOT_SHIFT; afi_slot &= NVME_FIRMWARE_PAGE_AFI_SLOT_MASK; oacs_fw = (cdata->oacs >> NVME_CTRLR_DATA_OACS_FIRMWARE_SHIFT) & NVME_CTRLR_DATA_OACS_FIRMWARE_MASK; fw_num_slots = (cdata->frmw >> NVME_CTRLR_DATA_FRMW_NUM_SLOTS_SHIFT) & NVME_CTRLR_DATA_FRMW_NUM_SLOTS_MASK; printf("Firmware Slot Log\n"); printf("=================\n"); if (oacs_fw == 0) slots = 1; else slots = MIN(fw_num_slots, MAX_FW_SLOTS); for (i = 0; i < slots; i++) { printf("Slot %d: ", i + 1); if (afi_slot == i + 1) status = " Active"; else status = "Inactive"; if (fw->revision[i] == 0LLU) printf("Empty\n"); else if (isprint(*(char *)&fw->revision[i])) printf("[%s] %.8s\n", status, (char *)&fw->revision[i]); else printf("[%s] %016jx\n", status, fw->revision[i]); } } static void print_log_ns(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused) { struct nvme_ns_list *nsl; u_int i; nsl = (struct nvme_ns_list *)buf; printf("Changed Namespace List\n"); printf("======================\n"); for (i = 0; i < nitems(nsl->ns) && nsl->ns[i] != 0; i++) { printf("%08x\n", nsl->ns[i]); } } static void print_log_command_effects(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused) { struct nvme_command_effects_page *ce; u_int i; uint32_t s; ce = (struct nvme_command_effects_page *)buf; printf("Commands Supported and Effects\n"); printf("==============================\n"); printf(" Command\tLBCC\tNCC\tNIC\tCCC\tCSE\tUUID\n"); for (i = 0; i < 255; i++) { s = ce->acs[i]; if (((s >> NVME_CE_PAGE_CSUP_SHIFT) & NVME_CE_PAGE_CSUP_MASK) == 0) continue; printf("Admin\t%02x\t%s\t%s\t%s\t%s\t%u\t%s\n", i, ((s >> NVME_CE_PAGE_LBCC_SHIFT) & NVME_CE_PAGE_LBCC_MASK) ? "Yes" : "No", ((s >> NVME_CE_PAGE_NCC_SHIFT) & NVME_CE_PAGE_NCC_MASK) ? "Yes" : "No", ((s >> NVME_CE_PAGE_NIC_SHIFT) & NVME_CE_PAGE_NIC_MASK) ? "Yes" : "No", ((s >> NVME_CE_PAGE_CCC_SHIFT) & NVME_CE_PAGE_CCC_MASK) ? "Yes" : "No", ((s >> NVME_CE_PAGE_CSE_SHIFT) & NVME_CE_PAGE_CSE_MASK), ((s >> NVME_CE_PAGE_UUID_SHIFT) & NVME_CE_PAGE_UUID_MASK) ? "Yes" : "No"); } for (i = 0; i < 255; i++) { s = ce->iocs[i]; if (((s >> NVME_CE_PAGE_CSUP_SHIFT) & NVME_CE_PAGE_CSUP_MASK) == 0) continue; printf("I/O\t%02x\t%s\t%s\t%s\t%s\t%u\t%s\n", i, ((s >> NVME_CE_PAGE_LBCC_SHIFT) & NVME_CE_PAGE_LBCC_MASK) ? "Yes" : "No", ((s >> NVME_CE_PAGE_NCC_SHIFT) & NVME_CE_PAGE_NCC_MASK) ? "Yes" : "No", ((s >> NVME_CE_PAGE_NIC_SHIFT) & NVME_CE_PAGE_NIC_MASK) ? "Yes" : "No", ((s >> NVME_CE_PAGE_CCC_SHIFT) & NVME_CE_PAGE_CCC_MASK) ? "Yes" : "No", ((s >> NVME_CE_PAGE_CSE_SHIFT) & NVME_CE_PAGE_CSE_MASK), ((s >> NVME_CE_PAGE_UUID_SHIFT) & NVME_CE_PAGE_UUID_MASK) ? "Yes" : "No"); } } static void print_log_res_notification(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused) { struct nvme_res_notification_page *rn; rn = (struct nvme_res_notification_page *)buf; printf("Reservation Notification\n"); printf("========================\n"); printf("Log Page Count: %ju\n", rn->log_page_count); printf("Log Page Type: "); switch (rn->log_page_type) { case 0: printf("Empty Log Page\n"); break; case 1: printf("Registration Preempted\n"); break; case 2: printf("Reservation Released\n"); break; case 3: printf("Reservation Preempted\n"); break; default: printf("Unknown %x\n", rn->log_page_type); break; }; printf("Number of Available Log Pages: %d\n", rn->available_log_pages); printf("Namespace ID: 0x%x\n", rn->nsid); } static void print_log_sanitize_status(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused) { struct nvme_sanitize_status_page *ss; u_int p; ss = (struct nvme_sanitize_status_page *)buf; printf("Sanitize Status\n"); printf("===============\n"); printf("Sanitize Progress: %u%% (%u/65535)\n", (ss->sprog * 100 + 32768) / 65536, ss->sprog); printf("Sanitize Status: "); switch ((ss->sstat >> NVME_SS_PAGE_SSTAT_STATUS_SHIFT) & NVME_SS_PAGE_SSTAT_STATUS_MASK) { case NVME_SS_PAGE_SSTAT_STATUS_NEVER: printf("Never sanitized"); break; case NVME_SS_PAGE_SSTAT_STATUS_COMPLETED: printf("Completed"); break; case NVME_SS_PAGE_SSTAT_STATUS_INPROG: printf("In Progress"); break; case NVME_SS_PAGE_SSTAT_STATUS_FAILED: printf("Failed"); break; case NVME_SS_PAGE_SSTAT_STATUS_COMPLETEDWD: printf("Completed with deallocation"); break; default: printf("Unknown"); break; } p = (ss->sstat >> NVME_SS_PAGE_SSTAT_PASSES_SHIFT) & NVME_SS_PAGE_SSTAT_PASSES_MASK; if (p > 0) printf(", %d passes", p); if ((ss->sstat >> NVME_SS_PAGE_SSTAT_GDE_SHIFT) & NVME_SS_PAGE_SSTAT_GDE_MASK) printf(", Global Data Erased"); printf("\n"); printf("Sanitize Command Dword 10: 0x%x\n", ss->scdw10); printf("Time For Overwrite: %u sec\n", ss->etfo); printf("Time For Block Erase: %u sec\n", ss->etfbe); printf("Time For Crypto Erase: %u sec\n", ss->etfce); printf("Time For Overwrite No-Deallocate: %u sec\n", ss->etfownd); printf("Time For Block Erase No-Deallocate: %u sec\n", ss->etfbewnd); printf("Time For Crypto Erase No-Deallocate: %u sec\n", ss->etfcewnd); } /* * Table of log page printer / sizing. * * Make sure you keep all the pages of one vendor together so -v help * lists all the vendors pages. */ NVME_LOGPAGE(error, NVME_LOG_ERROR, NULL, "Drive Error Log", print_log_error, 0); NVME_LOGPAGE(health, NVME_LOG_HEALTH_INFORMATION, NULL, "Health/SMART Data", print_log_health, sizeof(struct nvme_health_information_page)); NVME_LOGPAGE(fw, NVME_LOG_FIRMWARE_SLOT, NULL, "Firmware Information", print_log_firmware, sizeof(struct nvme_firmware_page)); NVME_LOGPAGE(ns, NVME_LOG_CHANGED_NAMESPACE, NULL, "Changed Namespace List", print_log_ns, sizeof(struct nvme_ns_list)); NVME_LOGPAGE(ce, NVME_LOG_COMMAND_EFFECT, NULL, "Commands Supported and Effects", print_log_command_effects, sizeof(struct nvme_command_effects_page)); NVME_LOGPAGE(dst, NVME_LOG_DEVICE_SELF_TEST, NULL, "Device Self-test", NULL, 564); NVME_LOGPAGE(thi, NVME_LOG_TELEMETRY_HOST_INITIATED, NULL, "Telemetry Host-Initiated", NULL, DEFAULT_SIZE); NVME_LOGPAGE(tci, NVME_LOG_TELEMETRY_CONTROLLER_INITIATED, NULL, "Telemetry Controller-Initiated", NULL, DEFAULT_SIZE); NVME_LOGPAGE(egi, NVME_LOG_ENDURANCE_GROUP_INFORMATION, NULL, "Endurance Group Information", NULL, DEFAULT_SIZE); NVME_LOGPAGE(plpns, NVME_LOG_PREDICTABLE_LATENCY_PER_NVM_SET, NULL, "Predictable Latency Per NVM Set", NULL, DEFAULT_SIZE); NVME_LOGPAGE(ple, NVME_LOG_PREDICTABLE_LATENCY_EVENT_AGGREGATE, NULL, "Predictable Latency Event Aggregate", NULL, DEFAULT_SIZE); NVME_LOGPAGE(ana, NVME_LOG_ASYMMETRIC_NAMESPAVE_ACCESS, NULL, "Asymmetric Namespace Access", NULL, DEFAULT_SIZE); NVME_LOGPAGE(pel, NVME_LOG_PERSISTENT_EVENT_LOG, NULL, "Persistent Event Log", NULL, DEFAULT_SIZE); NVME_LOGPAGE(lbasi, NVME_LOG_LBA_STATUS_INFORMATION, NULL, "LBA Status Information", NULL, DEFAULT_SIZE); NVME_LOGPAGE(egea, NVME_LOG_ENDURANCE_GROUP_EVENT_AGGREGATE, NULL, "Endurance Group Event Aggregate", NULL, DEFAULT_SIZE); NVME_LOGPAGE(res_notification, NVME_LOG_RES_NOTIFICATION, NULL, "Reservation Notification", print_log_res_notification, sizeof(struct nvme_res_notification_page)); NVME_LOGPAGE(sanitize_status, NVME_LOG_SANITIZE_STATUS, NULL, "Sanitize Status", print_log_sanitize_status, sizeof(struct nvme_sanitize_status_page)); static void logpage_help(void) { const struct logpage_function *f; const char *v; fprintf(stderr, "\n"); fprintf(stderr, "%-8s %-10s %s\n", "Page", "Vendor","Page Name"); fprintf(stderr, "-------- ---------- ----------\n"); SLIST_FOREACH(f, &logpages, link) { v = f->vendor == NULL ? "-" : f->vendor; fprintf(stderr, "0x%02x %-10s %s\n", f->log_page, v, f->name); } - exit(1); + exit(EX_USAGE); } static void logpage(const struct cmd *f, int argc, char *argv[]) { int fd; char *path; uint32_t nsid, size; void *buf; const struct logpage_function *lpf; struct nvme_controller_data cdata; print_fn_t print_fn; uint8_t ns_smart; if (arg_parse(argc, argv, f)) return; if (opt.hex && opt.binary) { fprintf(stderr, "Can't specify both binary and hex\n"); arg_help(argc, argv, f); } if (opt.vendor != NULL && strcmp(opt.vendor, "help") == 0) logpage_help(); if (opt.page == NONE) { fprintf(stderr, "Missing page_id (-p).\n"); arg_help(argc, argv, f); } open_dev(opt.dev, &fd, 0, 1); get_nsid(fd, &path, &nsid); if (nsid == 0) { nsid = NVME_GLOBAL_NAMESPACE_TAG; } else { close(fd); open_dev(path, &fd, 0, 1); } free(path); - read_controller_data(fd, &cdata); + if (read_controller_data(fd, &cdata)) + errx(EX_IOERR, "Identify request failed"); ns_smart = (cdata.lpa >> NVME_CTRLR_DATA_LPA_NS_SMART_SHIFT) & NVME_CTRLR_DATA_LPA_NS_SMART_MASK; /* * The log page attribtues indicate whether or not the controller * supports the SMART/Health information log page on a per * namespace basis. */ if (nsid != NVME_GLOBAL_NAMESPACE_TAG) { if (opt.page != NVME_LOG_HEALTH_INFORMATION) - errx(1, "log page %d valid only at controller level", + errx(EX_USAGE, "log page %d valid only at controller level", opt.page); if (ns_smart == 0) - errx(1, + errx(EX_UNAVAILABLE, "controller does not support per namespace " "smart/health information"); } print_fn = print_log_hex; size = DEFAULT_SIZE; if (opt.binary) print_fn = print_bin; if (!opt.binary && !opt.hex) { /* * See if there is a pretty print function for the specified log * page. If one isn't found, we just revert to the default * (print_hex). If there was a vendor specified by the user, and * the page is vendor specific, don't match the print function * unless the vendors match. */ SLIST_FOREACH(lpf, &logpages, link) { if (lpf->vendor != NULL && opt.vendor != NULL && strcmp(lpf->vendor, opt.vendor) != 0) continue; if (opt.page != lpf->log_page) continue; if (lpf->print_fn != NULL) print_fn = lpf->print_fn; size = lpf->size; break; } } if (opt.page == NVME_LOG_ERROR) { size = sizeof(struct nvme_error_information_entry); size *= (cdata.elpe + 1); } /* Read the log page */ buf = get_log_buffer(size); read_logpage(fd, opt.page, nsid, opt.lsp, opt.lsi, opt.rae, buf, size); print_fn(&cdata, buf, size); close(fd); exit(0); } Index: head/sbin/nvmecontrol/modules/wdc/wdc.c =================================================================== --- head/sbin/nvmecontrol/modules/wdc/wdc.c (revision 367629) +++ head/sbin/nvmecontrol/modules/wdc/wdc.c (revision 367630) @@ -1,811 +1,813 @@ /*- * Copyright (c) 2017 Netflix, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include +#include #include #include #include "nvmecontrol.h" /* Tables for command line parsing */ static cmd_fn_t wdc; static cmd_fn_t wdc_cap_diag; #define NONE 0xffffffffu #define NONE64 0xffffffffffffffffull #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } #define OPT_END { NULL, 0, arg_none, NULL, NULL } static struct cmd wdc_cmd = { .name = "wdc", .fn = wdc, .descr = "wdc vendor specific commands", .ctx_size = 0, .opts = NULL, .args = NULL, }; CMD_COMMAND(wdc_cmd); static struct options { const char *template; const char *dev; uint8_t data_area; } opt = { .template = NULL, .dev = NULL, .data_area = 0, }; static const struct opts opts[] = { OPT("template", 'o', arg_string, opt, template, "Template for paths to use for different logs"), OPT("data-area", 'd', arg_uint8, opt, data_area, "Data-area to retrieve up to"), OPT_END }; static const struct args args[] = { { arg_string, &opt.dev, "controller-id" }, { arg_none, NULL, NULL }, }; static struct cmd cap_diag_cmd = { .name = "cap-diag", .fn = wdc_cap_diag, .descr = "Retrieve the cap-diag logs from the drive", .ctx_size = sizeof(struct options), .opts = opts, .args = args, }; CMD_SUBCOMMAND(wdc_cmd, cap_diag_cmd); #define WDC_NVME_VID 0x1c58 #define WDC_NVME_VID_2 0x1b96 #define WDC_NVME_VID_3 0x15b7 #define WDC_NVME_TOC_SIZE 0x8 #define WDC_NVME_LOG_SIZE_HDR_LEN 0x8 #define WDC_NVME_CAP_DIAG_OPCODE_E6 0xe6 #define WDC_NVME_CAP_DIAG_CMD 0x0000 #define WDC_NVME_CAP_DIAG_OPCODE_FA 0xfa #define WDC_NVME_DUI_MAX_SECTIONS_V0 0x3c #define WDC_NVME_DUI_MAX_SECTIONS_V1 0x3a #define WDC_NVME_DUI_MAX_SECTIONS_V2 0x26 #define WDC_NVME_DUI_MAX_SECTIONS_V3 0x23 typedef enum wdc_dui_header { WDC_DUI_HEADER_VER_0 = 0, WDC_DUI_HEADER_VER_1, WDC_DUI_HEADER_VER_2, WDC_DUI_HEADER_VER_3, } wdc_dui_header; static void wdc_append_serial_name(int fd, char *buf, size_t len, const char *suffix) { struct nvme_controller_data cdata; char sn[NVME_SERIAL_NUMBER_LENGTH + 1]; char *walker; len -= strlen(buf); buf += strlen(buf); - read_controller_data(fd, &cdata); + if (read_controller_data(fd, &cdata)) + errx(EX_IOERR, "Identify request failed"); memcpy(sn, cdata.sn, NVME_SERIAL_NUMBER_LENGTH); walker = sn + NVME_SERIAL_NUMBER_LENGTH - 1; while (walker > sn && *walker == ' ') walker--; *++walker = '\0'; snprintf(buf, len, "_%s_%s.bin", sn, suffix); } static void wdc_get_data(int fd, uint32_t opcode, uint32_t len, uint32_t off, uint32_t cmd, uint8_t *buffer, size_t buflen, bool e6lg_flag) { struct nvme_pt_command pt; memset(&pt, 0, sizeof(pt)); pt.cmd.opc = opcode; pt.cmd.cdw10 = htole32(len / sizeof(uint32_t)); pt.cmd.cdw12 = htole32(cmd); if (e6lg_flag) pt.cmd.cdw11 = htole32(off / sizeof(uint32_t)); else pt.cmd.cdw13 = htole32(off / sizeof(uint32_t)); pt.buf = buffer; pt.len = buflen; pt.is_read = 1; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "wdc_get_data request failed"); + err(EX_IOERR, "wdc_get_data request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "wdc_get_data request returned error"); + errx(EX_IOERR, "wdc_get_data request returned error"); } static void wdc_do_dump_e6(int fd, char *tmpl, const char *suffix, uint32_t opcode, uint32_t cmd, int len_off) { int first; int fd2; uint8_t *buf, *hdr; uint32_t len, offset; size_t resid; bool e6lg_flag = false; wdc_append_serial_name(fd, tmpl, MAXPATHLEN, suffix); /* Read Log Dump header */ len = WDC_NVME_LOG_SIZE_HDR_LEN; offset = 0; hdr = malloc(len); if (hdr == NULL) - errx(1, "Can't get buffer to read dump"); + errx(EX_OSERR, "Can't get buffer to read dump"); wdc_get_data(fd, opcode, len, offset, cmd, hdr, len, false); if (memcmp("E6LG", hdr, 4) == 0) { e6lg_flag = true; } /* XXX overwrite protection? */ fd2 = open(tmpl, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd2 < 0) - err(1, "open %s", tmpl); + err(EX_CANTCREAT, "open %s", tmpl); buf = aligned_alloc(PAGE_SIZE, NVME_MAX_XFER_SIZE); if (buf == NULL) - errx(1, "Can't get buffer to read dump"); + errx(EX_OSERR, "Can't get buffer to read dump"); offset = 0; len = NVME_MAX_XFER_SIZE; first = 1; do { resid = len > NVME_MAX_XFER_SIZE ? NVME_MAX_XFER_SIZE : len; wdc_get_data(fd, opcode, resid, offset, cmd, buf, resid, e6lg_flag); if (first) { len = be32dec(buf + len_off); if (len == 0) - errx(1, "No data for %s", suffix); + errx(EX_PROTOCOL, "No data for %s", suffix); printf("Dumping %d bytes of version %d.%d log to %s\n", len, buf[8], buf[9], tmpl); /* * Adjust amount to dump if total dump < 1MB, * though it likely doesn't matter to the WDC * analysis tools. */ if (resid > len) resid = len; first = 0; } if (write(fd2, buf, resid) != (ssize_t)resid) - err(1, "write"); + err(EX_IOERR, "write"); offset += resid; len -= resid; } while (len > 0); free(hdr); free(buf); close(fd2); } static void wdc_get_data_dui(int fd, uint32_t opcode, uint32_t len, uint64_t off, uint8_t *buffer, size_t buflen) { struct nvme_pt_command pt; memset(&pt, 0, sizeof(pt)); pt.cmd.opc = opcode; pt.cmd.nsid = NONE; pt.cmd.cdw10 = htole32((len / sizeof(uint32_t)) - 1) ; pt.cmd.cdw12 = htole32(off & 0xFFFFFFFFu); pt.cmd.cdw13 = htole32(off >> 32); pt.buf = buffer; pt.len = buflen; pt.is_read = 1; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "wdc_get_data_dui request failed"); + err(EX_IOERR, "wdc_get_data_dui request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "wdc_get_data_dui request returned error"); + errx(EX_IOERR, "wdc_get_data_dui request returned error"); } static uint8_t wdc_get_dui_max_sections(uint16_t header_ver) { switch (header_ver) { case WDC_DUI_HEADER_VER_0: return WDC_NVME_DUI_MAX_SECTIONS_V0; case WDC_DUI_HEADER_VER_1: return WDC_NVME_DUI_MAX_SECTIONS_V1; case WDC_DUI_HEADER_VER_2: return WDC_NVME_DUI_MAX_SECTIONS_V2; case WDC_DUI_HEADER_VER_3: return WDC_NVME_DUI_MAX_SECTIONS_V3; } return 0; } static void wdc_get_dui_log_size(int fd, uint32_t opcode, uint8_t data_area, uint64_t *log_size, int len_off) { uint8_t *hdr; uint8_t max_sections; int i, j; uint16_t hdr_ver; uint16_t len; uint64_t dui_size; dui_size = 0; len = 1024; hdr = (uint8_t*)malloc(len); if (hdr == NULL) - errx(1, "Can't get buffer to read header"); + errx(EX_OSERR, "Can't get buffer to read header"); wdc_get_data_dui(fd, opcode, len, 0, hdr, len); hdr += len_off; hdr_ver = ((*hdr & 0xF) != 0)? *hdr : le16dec(hdr); max_sections = wdc_get_dui_max_sections(hdr_ver); if (hdr_ver == 0 || hdr_ver == 1) { dui_size = (uint64_t)le32dec(hdr + 4); if (dui_size == 0) { hdr += 8; for (i = 0, j = 0; i < (int)max_sections; i++, j+=8) dui_size += (uint64_t)le32dec(hdr + j + 4); } } else if (hdr_ver == 2 || hdr_ver == 3) { if (data_area == 0) { dui_size = le64dec(hdr + 4); if (dui_size == 0) { hdr += 12; for (i = 0, j = 0 ; i < (int)max_sections; i++, j+=12) dui_size += le64dec(hdr + j + 4); } } else { hdr += 12; for (i = 0, j = 0; i < (int)max_sections; i++, j+=12) { if (le16dec(hdr + j + 2) <= data_area) dui_size += le64dec(hdr + j + 4); else break; } } } else - errx(1, "ERROR : No valid header "); + errx(EX_PROTOCOL, "ERROR : No valid header "); *log_size = dui_size; free(hdr); } static void wdc_do_dump_dui(int fd, char *tmpl, uint8_t data_area, const char *suffix, uint32_t opcode, int len_off) { int fd2, first; uint8_t *buf; uint16_t hdr_ver; uint64_t log_len, offset; size_t resid; wdc_append_serial_name(fd, tmpl, MAXPATHLEN, suffix); wdc_get_dui_log_size(fd, opcode, data_area, &log_len, len_off); if (log_len == 0) - errx(1, "No data for %s", suffix); + errx(EX_PROTOCOL, "No data for %s", suffix); fd2 = open(tmpl, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd2 < 0) - err(1, "open %s", tmpl); + err(EX_CANTCREAT, "open %s", tmpl); buf = aligned_alloc(PAGE_SIZE, NVME_MAX_XFER_SIZE); if (buf == NULL) - errx(1, "Can't get buffer to read dump"); + errx(EX_OSERR, "Can't get buffer to read dump"); offset = 0; first = 1; while (log_len > 0) { resid = log_len > NVME_MAX_XFER_SIZE ? NVME_MAX_XFER_SIZE : log_len; wdc_get_data_dui(fd, opcode, resid, offset, buf, resid); if (first) { hdr_ver = ((buf[len_off] & 0xF) != 0) ? (buf[len_off]) : (le16dec(buf + len_off)); printf("Dumping %jd bytes of version %d log to %s\n", (uintmax_t)log_len, hdr_ver, tmpl); first = 0; } if (write(fd2, buf, resid) != (ssize_t)resid) - err(1, "write"); + err(EX_IOERR, "write"); offset += resid; log_len -= resid; } free(buf); close(fd2); } static void wdc_cap_diag(const struct cmd *f, int argc, char *argv[]) { char tmpl[MAXPATHLEN]; int fd; struct nvme_controller_data cdata; uint32_t vid; if (arg_parse(argc, argv, f)) return; if (opt.template == NULL) { fprintf(stderr, "Missing template arg.\n"); arg_help(argc, argv, f); } if (opt.data_area > 4) { fprintf(stderr, "Data area range 1-4, supplied %d.\n", opt.data_area); arg_help(argc, argv, f); } strlcpy(tmpl, opt.template, sizeof(tmpl)); open_dev(opt.dev, &fd, 1, 1); - read_controller_data(fd, &cdata); + if (read_controller_data(fd, &cdata)) + errx(EX_IOERR, "Identify request failed"); vid = cdata.vid; switch (vid) { case WDC_NVME_VID : case WDC_NVME_VID_2 : wdc_do_dump_e6(fd, tmpl, "cap_diag", WDC_NVME_CAP_DIAG_OPCODE_E6, WDC_NVME_CAP_DIAG_CMD, 4); break; case WDC_NVME_VID_3 : wdc_do_dump_dui(fd, tmpl, opt.data_area, "cap_diag", WDC_NVME_CAP_DIAG_OPCODE_FA, 512); break; default: - errx(1, "ERROR : WDC: unsupported device (%#x) for this command", vid); + errx(EX_UNAVAILABLE, "ERROR : WDC: unsupported device (%#x) for this command", vid); } close(fd); - - exit(1); + exit(0); } static void wdc(const struct cmd *nf __unused, int argc, char *argv[]) { cmd_dispatch(argc, argv, &wdc_cmd); } /* * HGST's 0xc1 page. This is a grab bag of additional data. Please see * https://www.hgst.com/sites/default/files/resources/US_SN150_ProdManual.pdf * https://www.hgst.com/sites/default/files/resources/US_SN100_ProdManual.pdf * Appendix A for details */ typedef void (*subprint_fn_t)(void *buf, uint16_t subtype, uint8_t res, uint32_t size); struct subpage_print { uint16_t key; subprint_fn_t fn; }; static void print_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); static void print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); static void print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); static void print_hgst_info_self_test(void *buf, uint16_t subtype, uint8_t res, uint32_t size); static void print_hgst_info_background_scan(void *buf, uint16_t subtype, uint8_t res, uint32_t size); static void print_hgst_info_erase_errors(void *buf, uint16_t subtype, uint8_t res, uint32_t size); static void print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res, uint32_t size); static void print_hgst_info_temp_history(void *buf, uint16_t subtype, uint8_t res, uint32_t size); static void print_hgst_info_ssd_perf(void *buf, uint16_t subtype, uint8_t res, uint32_t size); static void print_hgst_info_firmware_load(void *buf, uint16_t subtype, uint8_t res, uint32_t size); static struct subpage_print hgst_subpage[] = { { 0x02, print_hgst_info_write_errors }, { 0x03, print_hgst_info_read_errors }, { 0x05, print_hgst_info_verify_errors }, { 0x10, print_hgst_info_self_test }, { 0x15, print_hgst_info_background_scan }, { 0x30, print_hgst_info_erase_errors }, { 0x31, print_hgst_info_erase_counts }, { 0x32, print_hgst_info_temp_history }, { 0x37, print_hgst_info_ssd_perf }, { 0x38, print_hgst_info_firmware_load }, }; /* Print a subpage that is basically just key value pairs */ static void print_hgst_info_subpage_gen(void *buf, uint16_t subtype __unused, uint32_t size, const struct kv_name *kv, size_t kv_count) { uint8_t *wsp, *esp; uint16_t ptype; uint8_t plen; uint64_t param; int i; wsp = buf; esp = wsp + size; while (wsp < esp) { ptype = le16dec(wsp); wsp += 2; wsp++; /* Flags, just ignore */ plen = *wsp++; param = 0; for (i = 0; i < plen && wsp < esp; i++) param |= (uint64_t)*wsp++ << (i * 8); printf(" %-30s: %jd\n", kv_lookup(kv, kv_count, ptype), (uintmax_t)param); } } static void print_hgst_info_write_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) { static struct kv_name kv[] = { { 0x0000, "Corrected Without Delay" }, { 0x0001, "Corrected Maybe Delayed" }, { 0x0002, "Re-Writes" }, { 0x0003, "Errors Corrected" }, { 0x0004, "Correct Algorithm Used" }, { 0x0005, "Bytes Processed" }, { 0x0006, "Uncorrected Errors" }, { 0x8000, "Flash Write Commands" }, { 0x8001, "HGST Special" }, }; printf("Write Errors Subpage:\n"); print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); } static void print_hgst_info_read_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) { static struct kv_name kv[] = { { 0x0000, "Corrected Without Delay" }, { 0x0001, "Corrected Maybe Delayed" }, { 0x0002, "Re-Reads" }, { 0x0003, "Errors Corrected" }, { 0x0004, "Correct Algorithm Used" }, { 0x0005, "Bytes Processed" }, { 0x0006, "Uncorrected Errors" }, { 0x8000, "Flash Read Commands" }, { 0x8001, "XOR Recovered" }, { 0x8002, "Total Corrected Bits" }, }; printf("Read Errors Subpage:\n"); print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); } static void print_hgst_info_verify_errors(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) { static struct kv_name kv[] = { { 0x0000, "Corrected Without Delay" }, { 0x0001, "Corrected Maybe Delayed" }, { 0x0002, "Re-Reads" }, { 0x0003, "Errors Corrected" }, { 0x0004, "Correct Algorithm Used" }, { 0x0005, "Bytes Processed" }, { 0x0006, "Uncorrected Errors" }, { 0x8000, "Commands Processed" }, }; printf("Verify Errors Subpage:\n"); print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); } static void print_hgst_info_self_test(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) { size_t i; uint8_t *walker = buf; uint16_t code, hrs; uint32_t lba; printf("Self Test Subpage:\n"); for (i = 0; i < size / 20; i++) { /* Each entry is 20 bytes */ code = le16dec(walker); walker += 2; walker++; /* Ignore fixed flags */ if (*walker == 0) /* Last entry is zero length */ break; if (*walker++ != 0x10) { printf("Bad length for self test report\n"); return; } printf(" %-30s: %d\n", "Recent Test", code); printf(" %-28s: %#x\n", "Self-Test Results", *walker & 0xf); printf(" %-28s: %#x\n", "Self-Test Code", (*walker >> 5) & 0x7); walker++; printf(" %-28s: %#x\n", "Self-Test Number", *walker++); hrs = le16dec(walker); walker += 2; lba = le32dec(walker); walker += 4; printf(" %-28s: %u\n", "Total Power On Hrs", hrs); printf(" %-28s: %#jx (%jd)\n", "LBA", (uintmax_t)lba, (uintmax_t)lba); printf(" %-28s: %#x\n", "Sense Key", *walker++ & 0xf); printf(" %-28s: %#x\n", "Additional Sense Code", *walker++); printf(" %-28s: %#x\n", "Additional Sense Qualifier", *walker++); printf(" %-28s: %#x\n", "Vendor Specific Detail", *walker++); } } static void print_hgst_info_background_scan(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) { uint8_t *walker = buf; uint8_t status; uint16_t code, nscan, progress; uint32_t pom, nand; printf("Background Media Scan Subpage:\n"); /* Decode the header */ code = le16dec(walker); walker += 2; walker++; /* Ignore fixed flags */ if (*walker++ != 0x10) { printf("Bad length for background scan header\n"); return; } if (code != 0) { printf("Expceted code 0, found code %#x\n", code); return; } pom = le32dec(walker); walker += 4; walker++; /* Reserved */ status = *walker++; nscan = le16dec(walker); walker += 2; progress = le16dec(walker); walker += 2; walker += 6; /* Reserved */ printf(" %-30s: %d\n", "Power On Minutes", pom); printf(" %-30s: %x (%s)\n", "BMS Status", status, status == 0 ? "idle" : (status == 1 ? "active" : (status == 8 ? "suspended" : "unknown"))); printf(" %-30s: %d\n", "Number of BMS", nscan); printf(" %-30s: %d\n", "Progress Current BMS", progress); /* Report retirements */ if (walker - (uint8_t *)buf != 20) { printf("Coding error, offset not 20\n"); return; } size -= 20; printf(" %-30s: %d\n", "BMS retirements", size / 0x18); while (size > 0) { code = le16dec(walker); walker += 2; walker++; if (*walker++ != 0x14) { printf("Bad length parameter\n"); return; } pom = le32dec(walker); walker += 4; /* * Spec sheet says the following are hard coded, if true, just * print the NAND retirement. */ if (walker[0] == 0x41 && walker[1] == 0x0b && walker[2] == 0x01 && walker[3] == 0x00 && walker[4] == 0x00 && walker[5] == 0x00 && walker[6] == 0x00 && walker[7] == 0x00) { walker += 8; walker += 4; /* Skip reserved */ nand = le32dec(walker); walker += 4; printf(" %-30s: %d\n", "Retirement number", code); printf(" %-28s: %#x\n", "NAND (C/T)BBBPPP", nand); } else { printf("Parameter %#x entry corrupt\n", code); walker += 16; } } } static void print_hgst_info_erase_errors(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size) { static struct kv_name kv[] = { { 0x0000, "Corrected Without Delay" }, { 0x0001, "Corrected Maybe Delayed" }, { 0x0002, "Re-Erase" }, { 0x0003, "Errors Corrected" }, { 0x0004, "Correct Algorithm Used" }, { 0x0005, "Bytes Processed" }, { 0x0006, "Uncorrected Errors" }, { 0x8000, "Flash Erase Commands" }, { 0x8001, "Mfg Defect Count" }, { 0x8002, "Grown Defect Count" }, { 0x8003, "Erase Count -- User" }, { 0x8004, "Erase Count -- System" }, }; printf("Erase Errors Subpage:\n"); print_hgst_info_subpage_gen(buf, subtype, size, kv, nitems(kv)); } static void print_hgst_info_erase_counts(void *buf, uint16_t subtype, uint8_t res __unused, uint32_t size) { /* My drive doesn't export this -- so not coding up */ printf("XXX: Erase counts subpage: %p, %#x %d\n", buf, subtype, size); } static void print_hgst_info_temp_history(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused) { uint8_t *walker = buf; uint32_t min; printf("Temperature History:\n"); printf(" %-30s: %d C\n", "Current Temperature", *walker++); printf(" %-30s: %d C\n", "Reference Temperature", *walker++); printf(" %-30s: %d C\n", "Maximum Temperature", *walker++); printf(" %-30s: %d C\n", "Minimum Temperature", *walker++); min = le32dec(walker); walker += 4; printf(" %-30s: %d:%02d:00\n", "Max Temperature Time", min / 60, min % 60); min = le32dec(walker); walker += 4; printf(" %-30s: %d:%02d:00\n", "Over Temperature Duration", min / 60, min % 60); min = le32dec(walker); walker += 4; printf(" %-30s: %d:%02d:00\n", "Min Temperature Time", min / 60, min % 60); } static void print_hgst_info_ssd_perf(void *buf, uint16_t subtype __unused, uint8_t res, uint32_t size __unused) { uint8_t *walker = buf; uint64_t val; printf("SSD Performance Subpage Type %d:\n", res); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "Host Read Commands", val); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "Host Read Blocks", val); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "Host Cache Read Hits Commands", val); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "Host Cache Read Hits Blocks", val); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "Host Read Commands Stalled", val); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "Host Write Commands", val); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "Host Write Blocks", val); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "Host Write Odd Start Commands", val); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "Host Write Odd End Commands", val); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "Host Write Commands Stalled", val); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "NAND Read Commands", val); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "NAND Read Blocks", val); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "NAND Write Commands", val); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "NAND Write Blocks", val); val = le64dec(walker); walker += 8; printf(" %-30s: %ju\n", "NAND Read Before Writes", val); } static void print_hgst_info_firmware_load(void *buf, uint16_t subtype __unused, uint8_t res __unused, uint32_t size __unused) { uint8_t *walker = buf; printf("Firmware Load Subpage:\n"); printf(" %-30s: %d\n", "Firmware Downloads", le32dec(walker)); } static void kv_indirect(void *buf, uint32_t subtype, uint8_t res, uint32_t size, struct subpage_print *sp, size_t nsp) { size_t i; for (i = 0; i < nsp; i++, sp++) { if (sp->key == subtype) { sp->fn(buf, subtype, res, size); return; } } printf("No handler for page type %x\n", subtype); } static void print_hgst_info_log(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused) { uint8_t *walker, *end, *subpage; int pages; uint16_t len; uint8_t subtype, res; printf("HGST Extra Info Log\n"); printf("===================\n"); walker = buf; pages = *walker++; walker++; len = le16dec(walker); walker += 2; end = walker + len; /* Length is exclusive of this header */ while (walker < end) { subpage = walker + 4; subtype = *walker++ & 0x3f; /* subtype */ res = *walker++; /* Reserved */ len = le16dec(walker); walker += len + 2; /* Length, not incl header */ if (walker > end) { printf("Ooops! Off the end of the list\n"); break; } kv_indirect(subpage, subtype, res, len, hgst_subpage, nitems(hgst_subpage)); } } NVME_LOGPAGE(hgst_info, HGST_INFO_LOG, "hgst", "Detailed Health/SMART", print_hgst_info_log, DEFAULT_SIZE); NVME_LOGPAGE(wdc_info, HGST_INFO_LOG, "wdc", "Detailed Health/SMART", print_hgst_info_log, DEFAULT_SIZE); Index: head/sbin/nvmecontrol/ns.c =================================================================== --- head/sbin/nvmecontrol/ns.c (revision 367629) +++ head/sbin/nvmecontrol/ns.c (revision 367630) @@ -1,968 +1,978 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2017 Netflix, Inc. * Copyright (C) 2018-2019 Alexander Motin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include +#include #include #include "nvmecontrol.h" /* Tables for command line parsing */ static cmd_fn_t ns; static cmd_fn_t nsactive; static cmd_fn_t nsallocated; static cmd_fn_t nscontrollers; static cmd_fn_t nscreate; static cmd_fn_t nsdelete; static cmd_fn_t nsattach; static cmd_fn_t nsdetach; static cmd_fn_t nsattached; static cmd_fn_t nsidentify; #define NONE 0xffffffffu #define NONE64 0xffffffffffffffffull #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } #define OPT_END { NULL, 0, arg_none, NULL, NULL } static struct cmd ns_cmd = { .name = "ns", .fn = ns, .descr = "Namespace management commands", .ctx_size = 0, .opts = NULL, .args = NULL, }; CMD_COMMAND(ns_cmd); static struct active_options { const char *dev; } active_opt = { .dev = NULL, }; static const struct args active_args[] = { { arg_string, &active_opt.dev, "controller-id|namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd active_cmd = { .name = "active", .fn = nsactive, .descr = "List active (attached) namespaces", .ctx_size = sizeof(active_opt), .opts = NULL, .args = active_args, }; CMD_SUBCOMMAND(ns_cmd, active_cmd); static struct cmd allocated_cmd = { .name = "allocated", .fn = nsallocated, .descr = "List allocated (created) namespaces", .ctx_size = sizeof(active_opt), .opts = NULL, .args = active_args, }; CMD_SUBCOMMAND(ns_cmd, allocated_cmd); static struct controllers_options { const char *dev; } controllers_opt = { .dev = NULL, }; static const struct args controllers_args[] = { { arg_string, &controllers_opt.dev, "controller-id|namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd controllers_cmd = { .name = "controllers", .fn = nscontrollers, .descr = "List all controllers in NVM subsystem", .ctx_size = sizeof(controllers_opt), .opts = NULL, .args = controllers_args, }; CMD_SUBCOMMAND(ns_cmd, controllers_cmd); static struct create_options { uint64_t nsze; uint64_t cap; uint32_t lbaf; uint32_t mset; uint32_t nmic; uint32_t pi; uint32_t pil; uint32_t flbas; uint32_t dps; // uint32_t block_size; const char *dev; } create_opt = { .nsze = NONE64, .cap = NONE64, .lbaf = NONE, .mset = NONE, .nmic = NONE, .pi = NONE, .pil = NONE, .flbas = NONE, .dps = NONE, .dev = NULL, // .block_size = NONE, }; static const struct opts create_opts[] = { OPT("nsze", 's', arg_uint64, create_opt, nsze, "The namespace size"), OPT("ncap", 'c', arg_uint64, create_opt, cap, "The capacity of the namespace (<= ns size)"), OPT("lbaf", 'f', arg_uint32, create_opt, lbaf, "The FMT field of the FLBAS"), OPT("mset", 'm', arg_uint32, create_opt, mset, "The MSET field of the FLBAS"), OPT("nmic", 'n', arg_uint32, create_opt, nmic, "Namespace multipath and sharing capabilities"), OPT("pi", 'p', arg_uint32, create_opt, pi, "PI field of FLBAS"), OPT("pil", 'l', arg_uint32, create_opt, pil, "PIL field of FLBAS"), OPT("flbas", 'L', arg_uint32, create_opt, flbas, "Namespace formatted logical block size setting"), OPT("dps", 'd', arg_uint32, create_opt, dps, "Data protection settings"), // OPT("block-size", 'b', arg_uint32, create_opt, block_size, // "Blocksize of the namespace"), OPT_END }; static const struct args create_args[] = { { arg_string, &create_opt.dev, "controller-id|namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd create_cmd = { .name = "create", .fn = nscreate, .descr = "Create a namespace", .ctx_size = sizeof(create_opt), .opts = create_opts, .args = create_args, }; CMD_SUBCOMMAND(ns_cmd, create_cmd); static struct delete_options { uint32_t nsid; const char *dev; } delete_opt = { .nsid = NONE, .dev = NULL, }; static const struct opts delete_opts[] = { OPT("namespace-id", 'n', arg_uint32, delete_opt, nsid, "The namespace ID to delete"), OPT_END }; static const struct args delete_args[] = { { arg_string, &delete_opt.dev, "controller-id|namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd delete_cmd = { .name = "delete", .fn = nsdelete, .descr = "Delete a namespace", .ctx_size = sizeof(delete_opt), .opts = delete_opts, .args = delete_args, }; CMD_SUBCOMMAND(ns_cmd, delete_cmd); static struct attach_options { uint32_t nsid; uint32_t ctrlrid; const char *dev; } attach_opt = { .nsid = NONE, .ctrlrid = NONE - 1, .dev = NULL, }; static const struct opts attach_opts[] = { OPT("namespace-id", 'n', arg_uint32, attach_opt, nsid, "The namespace ID to attach"), OPT("controller", 'c', arg_uint32, attach_opt, ctrlrid, "The controller ID to attach"), OPT_END }; static const struct args attach_args[] = { { arg_string, &attach_opt.dev, "controller-id|namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd attach_cmd = { .name = "attach", .fn = nsattach, .descr = "Attach a controller to a namespace", .ctx_size = sizeof(attach_opt), .opts = attach_opts, .args = attach_args, }; CMD_SUBCOMMAND(ns_cmd, attach_cmd); static struct attached_options { uint32_t nsid; const char *dev; } attached_opt = { .nsid = NONE, .dev = NULL, }; static const struct opts attached_opts[] = { OPT("namespace-id", 'n', arg_uint32, attached_opt, nsid, "The namespace ID to request attached controllers"), OPT_END }; static const struct args attached_args[] = { { arg_string, &attached_opt.dev, "controller-id|namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd attached_cmd = { .name = "attached", .fn = nsattached, .descr = "List controllers attached to a namespace", .ctx_size = sizeof(attached_opt), .opts = attached_opts, .args = attached_args, }; CMD_SUBCOMMAND(ns_cmd, attached_cmd); static struct detach_options { uint32_t nsid; uint32_t ctrlrid; const char *dev; } detach_opt = { .nsid = NONE, .ctrlrid = NONE - 1, .dev = NULL, }; static const struct opts detach_opts[] = { OPT("namespace-id", 'n', arg_uint32, detach_opt, nsid, "The namespace ID to detach"), OPT("controller", 'c', arg_uint32, detach_opt, ctrlrid, "The controller ID to detach"), OPT_END }; static const struct args detach_args[] = { { arg_string, &detach_opt.dev, "controller-id|namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd detach_cmd = { .name = "detach", .fn = nsdetach, .descr = "Detach a controller from a namespace", .ctx_size = sizeof(detach_opt), .opts = detach_opts, .args = detach_args, }; CMD_SUBCOMMAND(ns_cmd, detach_cmd); static struct identify_options { bool hex; bool verbose; const char *dev; uint32_t nsid; } identify_opt = { .hex = false, .verbose = false, .dev = NULL, .nsid = NONE, }; static const struct opts identify_opts[] = { OPT("hex", 'x', arg_none, identify_opt, hex, "Print identiy information in hex"), OPT("verbose", 'v', arg_none, identify_opt, verbose, "More verbosity: print entire identify table"), OPT("nsid", 'n', arg_uint32, identify_opt, nsid, "The namespace ID to print IDENTIFY for"), { NULL, 0, arg_none, NULL, NULL } }; static const struct args identify_args[] = { { arg_string, &identify_opt.dev, "controller-id|namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd identify_cmd = { .name = "identify", .fn = nsidentify, .descr = "Print IDENTIFY for allocated namespace", .ctx_size = sizeof(identify_opt), .opts = identify_opts, .args = identify_args, }; CMD_SUBCOMMAND(ns_cmd, identify_cmd); /* handles NVME_OPC_NAMESPACE_MANAGEMENT and ATTACHMENT admin cmds */ struct ns_result_str { uint16_t res; const char * str; }; static struct ns_result_str ns_result[] = { { 0x2, "Invalid Field"}, { 0xa, "Invalid Format"}, { 0xb, "Invalid Namespace or format"}, { 0x15, "Namespace insufficent capacity"}, { 0x16, "Namespace ID unavaliable"}, { 0x18, "Namespace already attached"}, { 0x19, "Namespace is private"}, { 0x1a, "Namespace is not attached"}, { 0x1b, "Thin provisioning not supported"}, { 0x1c, "Controller list invalid"}, { 0x24, "ANA Group Identifier Invalid"}, { 0x25, "ANA Attach Failed"}, { 0xFFFF, "Unknown"} }; static const char * get_res_str(uint16_t res) { struct ns_result_str *t = ns_result; while (t->res != 0xFFFF) { if (t->res == res) return (t->str); t++; } return t->str; } static void nsactive(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; int fd, i; char *path; uint32_t nsid; uint32_t list[1024]; if (arg_parse(argc, argv, f)) return; open_dev(active_opt.dev, &fd, 0, 1); get_nsid(fd, &path, &nsid); if (nsid != 0) { close(fd); open_dev(path, &fd, 0, 1); } free(path); - read_controller_data(fd, &cd); + if (read_controller_data(fd, &cd)) + errx(EX_IOERR, "Identify request failed"); /* Check that controller can execute this command. */ if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) & NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0) - errx(1, "controller does not support namespace management"); + errx(EX_UNAVAILABLE, "controller does not support namespace management"); memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_IDENTIFY; pt.cmd.nsid = htole32(0); pt.cmd.cdw10 = htole32(0x02); pt.buf = list; pt.len = sizeof(list); pt.is_read = 1; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "identify request failed"); + err(EX_IOERR, "identify request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "identify request returned error"); + errx(EX_IOERR, "identify request returned error"); printf("Active namespaces:\n"); for (i = 0; list[i] != 0; i++) printf("%10d\n", le32toh(list[i])); exit(0); } static void nsallocated(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; int fd, i; char *path; uint32_t nsid; uint32_t list[1024]; if (arg_parse(argc, argv, f)) return; open_dev(active_opt.dev, &fd, 0, 1); get_nsid(fd, &path, &nsid); if (nsid != 0) { close(fd); open_dev(path, &fd, 0, 1); } free(path); - read_controller_data(fd, &cd); + if (read_controller_data(fd, &cd)) + errx(EX_IOERR, "Identify request failed"); /* Check that controller can execute this command. */ if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) & NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0) - errx(1, "controller does not support namespace management"); + errx(EX_UNAVAILABLE, "controller does not support namespace management"); memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_IDENTIFY; pt.cmd.nsid = htole32(0); pt.cmd.cdw10 = htole32(0x10); pt.buf = list; pt.len = sizeof(list); pt.is_read = 1; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "identify request failed"); + err(EX_IOERR, "identify request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "identify request returned error"); + errx(EX_IOERR, "identify request returned error"); printf("Allocated namespaces:\n"); for (i = 0; list[i] != 0; i++) printf("%10d\n", le32toh(list[i])); exit(0); } static void nscontrollers(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; int fd, i, n; char *path; uint32_t nsid; uint16_t clist[2048]; if (arg_parse(argc, argv, f)) return; open_dev(controllers_opt.dev, &fd, 0, 1); get_nsid(fd, &path, &nsid); if (nsid != 0) { close(fd); open_dev(path, &fd, 0, 1); } free(path); - read_controller_data(fd, &cd); + if (read_controller_data(fd, &cd)) + errx(EX_IOERR, "Identify request failed"); /* Check that controller can execute this command. */ if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) & NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0) - errx(1, "controller does not support namespace management"); + errx(EX_UNAVAILABLE, "controller does not support namespace management"); memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_IDENTIFY; pt.cmd.cdw10 = htole32(0x13); pt.buf = clist; pt.len = sizeof(clist); pt.is_read = 1; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "identify request failed"); + err(EX_IOERR, "identify request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "identify request returned error"); + errx(EX_IOERR, "identify request returned error"); n = le16toh(clist[0]); printf("NVM subsystem includes %d controller(s):\n", n); for (i = 0; i < n; i++) printf(" 0x%04x\n", le16toh(clist[i + 1])); exit(0); } /* * NS MGMT Command specific status values: * 0xa = Invalid Format * 0x15 = Namespace Insuffience capacity * 0x16 = Namespace ID unavailable (number namespaces exceeded) * 0xb = Thin Provisioning Not supported */ static void nscreate(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; struct nvme_namespace_data nsdata; int fd, result; char *path; uint32_t nsid; if (arg_parse(argc, argv, f)) return; if (create_opt.cap == NONE64) create_opt.cap = create_opt.nsze; if (create_opt.nsze == NONE64) { fprintf(stderr, "Size not specified\n"); arg_help(argc, argv, f); } open_dev(create_opt.dev, &fd, 1, 1); get_nsid(fd, &path, &nsid); if (nsid != 0) { close(fd); open_dev(path, &fd, 1, 1); } free(path); - read_controller_data(fd, &cd); + if (read_controller_data(fd, &cd)) + errx(EX_IOERR, "Identify request failed"); /* Check that controller can execute this command. */ if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) & NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0) - errx(1, "controller does not support namespace management"); + errx(EX_UNAVAILABLE, "controller does not support namespace management"); /* Allow namespaces sharing if Multi-Path I/O is supported. */ if (create_opt.nmic == NONE) { create_opt.nmic = cd.mic ? (NVME_NS_DATA_NMIC_MAY_BE_SHARED_MASK << NVME_NS_DATA_NMIC_MAY_BE_SHARED_SHIFT) : 0; } memset(&nsdata, 0, sizeof(nsdata)); nsdata.nsze = create_opt.nsze; nsdata.ncap = create_opt.cap; if (create_opt.flbas == NONE) nsdata.flbas = ((create_opt.lbaf & NVME_NS_DATA_FLBAS_FORMAT_MASK) << NVME_NS_DATA_FLBAS_FORMAT_SHIFT) | ((create_opt.mset & NVME_NS_DATA_FLBAS_EXTENDED_MASK) << NVME_NS_DATA_FLBAS_EXTENDED_SHIFT); else nsdata.flbas = create_opt.flbas; if (create_opt.dps == NONE) nsdata.dps = ((create_opt.pi & NVME_NS_DATA_DPS_MD_START_MASK) << NVME_NS_DATA_DPS_MD_START_SHIFT) | ((create_opt.pil & NVME_NS_DATA_DPS_PIT_MASK) << NVME_NS_DATA_DPS_PIT_SHIFT); else nsdata.dps = create_opt.dps; nsdata.nmic = create_opt.nmic; nvme_namespace_data_swapbytes(&nsdata); memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_NAMESPACE_MANAGEMENT; pt.cmd.cdw10 = htole32(0); /* create */ pt.buf = &nsdata; pt.len = sizeof(struct nvme_namespace_data); pt.is_read = 0; /* passthrough writes data to ctrlr */ if ((result = ioctl(fd, NVME_PASSTHROUGH_CMD, &pt)) < 0) - errx(1, "ioctl request to %s failed: %d", create_opt.dev, result); + errx(EX_IOERR, "ioctl request to %s failed: %d", create_opt.dev, result); if (nvme_completion_is_error(&pt.cpl)) { - errx(1, "namespace creation failed: %s", + errx(EX_IOERR, "namespace creation failed: %s", get_res_str((pt.cpl.status >> NVME_STATUS_SC_SHIFT) & NVME_STATUS_SC_MASK)); } printf("namespace %d created\n", pt.cpl.cdw0); exit(0); } static void nsdelete(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; int fd, result; char *path; uint32_t nsid; char buf[2]; if (arg_parse(argc, argv, f)) return; open_dev(delete_opt.dev, &fd, 1, 1); get_nsid(fd, &path, &nsid); if (nsid != 0) { close(fd); open_dev(path, &fd, 1, 1); } else if (delete_opt.nsid == NONE) { close(fd); fprintf(stderr, "No NSID specified"); arg_help(argc, argv, f); } if (delete_opt.nsid != NONE) nsid = delete_opt.nsid; free(path); - read_controller_data(fd, &cd); + if (read_controller_data(fd, &cd)) + errx(EX_IOERR, "Identify request failed"); /* Check that controller can execute this command. */ if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) & NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0) - errx(1, "controller does not support namespace management"); + errx(EX_UNAVAILABLE, "controller does not support namespace management"); memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_NAMESPACE_MANAGEMENT; pt.cmd.cdw10 = htole32(1); /* delete */ pt.buf = buf; pt.len = sizeof(buf); pt.is_read = 1; pt.cmd.nsid = nsid; if ((result = ioctl(fd, NVME_PASSTHROUGH_CMD, &pt)) < 0) - errx(1, "ioctl request to %s failed: %d", delete_opt.dev, result); + errx(EX_IOERR, "ioctl request to %s failed: %d", delete_opt.dev, result); if (nvme_completion_is_error(&pt.cpl)) { - errx(1, "namespace deletion failed: %s", + errx(EX_IOERR, "namespace deletion failed: %s", get_res_str((pt.cpl.status >> NVME_STATUS_SC_SHIFT) & NVME_STATUS_SC_MASK)); } printf("namespace %d deleted\n", nsid); exit(0); } /* * Attach and Detach use Dword 10, and a controller list (section 4.9) * This struct is 4096 bytes in size. * 0h = attach * 1h = detach * * Result values for both attach/detach: * * Completion 18h = Already attached * 19h = NS is private and already attached to a controller * 1Ah = Not attached, request could not be completed * 1Ch = Controller list invalid. * * 0x2 Invalid Field can occur if ctrlrid d.n.e in system. */ static void nsattach(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; int fd, result; char *path; uint32_t nsid; uint16_t clist[2048]; if (arg_parse(argc, argv, f)) return; open_dev(attach_opt.dev, &fd, 1, 1); get_nsid(fd, &path, &nsid); if (nsid != 0) { close(fd); open_dev(path, &fd, 1, 1); } else if (attach_opt.nsid == NONE) { close(fd); fprintf(stderr, "No NSID specified"); arg_help(argc, argv, f); } if (attach_opt.nsid != NONE) nsid = attach_opt.nsid; - read_controller_data(fd, &cd); + if (read_controller_data(fd, &cd)) + errx(EX_IOERR, "Identify request failed"); /* Check that controller can execute this command. */ if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) & NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0) - errx(1, "controller does not support namespace management"); + errx(EX_UNAVAILABLE, "controller does not support namespace management"); if (attach_opt.ctrlrid == NONE) { /* Get full list of controllers to attach to. */ memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_IDENTIFY; pt.cmd.cdw10 = htole32(0x13); pt.buf = clist; pt.len = sizeof(clist); pt.is_read = 1; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "identify request failed"); + err(EX_IOERR, "identify request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "identify request returned error"); + errx(EX_IOERR, "identify request returned error"); } else { /* By default attach to this controller. */ if (attach_opt.ctrlrid == NONE - 1) attach_opt.ctrlrid = cd.ctrlr_id; memset(&clist, 0, sizeof(clist)); clist[0] = htole16(1); clist[1] = htole16(attach_opt.ctrlrid); } memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_NAMESPACE_ATTACHMENT; pt.cmd.cdw10 = htole32(0); /* attach */ pt.cmd.nsid = nsid; pt.buf = &clist; pt.len = sizeof(clist); if ((result = ioctl(fd, NVME_PASSTHROUGH_CMD, &pt)) < 0) - errx(1, "ioctl request to %s failed: %d", attach_opt.dev, result); + errx(EX_IOERR, "ioctl request to %s failed: %d", attach_opt.dev, result); if (nvme_completion_is_error(&pt.cpl)) { - errx(1, "namespace attach failed: %s", + errx(EX_IOERR, "namespace attach failed: %s", get_res_str((pt.cpl.status >> NVME_STATUS_SC_SHIFT) & NVME_STATUS_SC_MASK)); } printf("namespace %d attached\n", nsid); exit(0); } static void nsdetach(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; int fd, result; char *path; uint32_t nsid; uint16_t clist[2048]; if (arg_parse(argc, argv, f)) return; open_dev(detach_opt.dev, &fd, 1, 1); get_nsid(fd, &path, &nsid); if (nsid != 0) { close(fd); open_dev(path, &fd, 1, 1); } else if (detach_opt.nsid == NONE) { close(fd); fprintf(stderr, "No NSID specified"); arg_help(argc, argv, f); } if (detach_opt.nsid != NONE) nsid = detach_opt.nsid; - read_controller_data(fd, &cd); + if (read_controller_data(fd, &cd)) + errx(EX_IOERR, "Identify request failed"); /* Check that controller can execute this command. */ if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) & NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0) - errx(1, "controller does not support namespace management"); + errx(EX_UNAVAILABLE, "controller does not support namespace management"); if (detach_opt.ctrlrid == NONE) { /* Get list of controllers this namespace attached to. */ memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_IDENTIFY; pt.cmd.nsid = htole32(nsid); pt.cmd.cdw10 = htole32(0x12); pt.buf = clist; pt.len = sizeof(clist); pt.is_read = 1; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "identify request failed"); + err(EX_IOERR, "identify request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "identify request returned error"); + errx(EX_IOERR, "identify request returned error"); if (clist[0] == 0) { detach_opt.ctrlrid = cd.ctrlr_id; memset(&clist, 0, sizeof(clist)); clist[0] = htole16(1); clist[1] = htole16(detach_opt.ctrlrid); } } else { /* By default detach from this controller. */ if (detach_opt.ctrlrid == NONE - 1) detach_opt.ctrlrid = cd.ctrlr_id; memset(&clist, 0, sizeof(clist)); clist[0] = htole16(1); clist[1] = htole16(detach_opt.ctrlrid); } memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_NAMESPACE_ATTACHMENT; pt.cmd.cdw10 = htole32(1); /* detach */ pt.cmd.nsid = nsid; pt.buf = &clist; pt.len = sizeof(clist); if ((result = ioctl(fd, NVME_PASSTHROUGH_CMD, &pt)) < 0) - errx(1, "ioctl request to %s failed: %d", detach_opt.dev, result); + errx(EX_IOERR, "ioctl request to %s failed: %d", detach_opt.dev, result); if (nvme_completion_is_error(&pt.cpl)) { - errx(1, "namespace detach failed: %s", + errx(EX_IOERR, "namespace detach failed: %s", get_res_str((pt.cpl.status >> NVME_STATUS_SC_SHIFT) & NVME_STATUS_SC_MASK)); } printf("namespace %d detached\n", nsid); exit(0); } static void nsattached(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; int fd, i, n; char *path; uint32_t nsid; uint16_t clist[2048]; if (arg_parse(argc, argv, f)) return; open_dev(attached_opt.dev, &fd, 0, 1); get_nsid(fd, &path, &nsid); if (nsid != 0) { close(fd); open_dev(path, &fd, 1, 1); } else if (attached_opt.nsid == NONE) { close(fd); fprintf(stderr, "No NSID specified"); arg_help(argc, argv, f); } if (attached_opt.nsid != NONE) nsid = attached_opt.nsid; - read_controller_data(fd, &cd); + if (read_controller_data(fd, &cd)) + errx(EX_IOERR, "Identify request failed"); /* Check that controller can execute this command. */ if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) & NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0) - errx(1, "controller does not support namespace management"); + errx(EX_UNAVAILABLE, "controller does not support namespace management"); memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_IDENTIFY; pt.cmd.nsid = htole32(nsid); pt.cmd.cdw10 = htole32(0x12); pt.buf = clist; pt.len = sizeof(clist); pt.is_read = 1; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "identify request failed"); + err(EX_IOERR, "identify request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "identify request returned error"); + errx(EX_IOERR, "identify request returned error"); n = le16toh(clist[0]); printf("Attached %d controller(s):\n", n); for (i = 0; i < n; i++) printf(" 0x%04x\n", le16toh(clist[i + 1])); exit(0); } static void nsidentify(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_controller_data cd; struct nvme_namespace_data nsdata; uint8_t *data; int fd; char *path; uint32_t nsid; u_int i; if (arg_parse(argc, argv, f)) return; open_dev(identify_opt.dev, &fd, 0, 1); get_nsid(fd, &path, &nsid); if (nsid != 0) { close(fd); open_dev(path, &fd, 1, 1); } else if (identify_opt.nsid == NONE) { close(fd); fprintf(stderr, "No NSID specified"); arg_help(argc, argv, f); } if (identify_opt.nsid != NONE) nsid = identify_opt.nsid; - read_controller_data(fd, &cd); + if (read_controller_data(fd, &cd)) + errx(EX_IOERR, "Identify request failed"); /* Check that controller can execute this command. */ if (((cd.oacs >> NVME_CTRLR_DATA_OACS_NSMGMT_SHIFT) & NVME_CTRLR_DATA_OACS_NSMGMT_MASK) == 0) - errx(1, "controller does not support namespace management"); + errx(EX_UNAVAILABLE, "controller does not support namespace management"); memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_IDENTIFY; pt.cmd.nsid = htole32(nsid); pt.cmd.cdw10 = htole32(0x11); pt.buf = &nsdata; pt.len = sizeof(nsdata); pt.is_read = 1; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "identify request failed"); + err(EX_IOERR, "identify request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "identify request returned error"); + errx(EX_IOERR, "identify request returned error"); close(fd); data = (uint8_t *)&nsdata; for (i = 0; i < sizeof(nsdata); i++) { if (data[i] != 0) break; } if (i == sizeof(nsdata)) - errx(1, "namespace %d is not allocated", nsid); + errx(EX_UNAVAILABLE, "namespace %d is not allocated", nsid); /* Convert data to host endian */ nvme_namespace_data_swapbytes(&nsdata); if (identify_opt.hex) { i = sizeof(struct nvme_namespace_data); if (!identify_opt.verbose) { for (; i > 384; i--) { if (data[i - 1] != 0) break; } } print_hex(&nsdata, i); exit(0); } print_namespace(&nsdata); exit(0); } static void ns(const struct cmd *nf __unused, int argc, char *argv[]) { cmd_dispatch(argc, argv, &ns_cmd); } Index: head/sbin/nvmecontrol/nvmecontrol.c =================================================================== --- head/sbin/nvmecontrol/nvmecontrol.c (revision 367629) +++ head/sbin/nvmecontrol/nvmecontrol.c (revision 367630) @@ -1,187 +1,190 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2012-2013 Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include "nvmecontrol.h" static void print_bytes(void *data, uint32_t length) { uint32_t i, j; uint8_t *p, *end; end = (uint8_t *)data + length; for (i = 0; i < length; i++) { p = (uint8_t *)data + (i*16); printf("%03x: ", i*16); for (j = 0; j < 16 && p < end; j++) printf("%02x ", *p++); if (p >= end) break; printf("\n"); } printf("\n"); } static void print_dwords(void *data, uint32_t length) { uint32_t *p; uint32_t i, j; p = (uint32_t *)data; length /= sizeof(uint32_t); for (i = 0; i < length; i+=8) { printf("%03x: ", i*4); for (j = 0; j < 8; j++) printf("%08x ", p[i+j]); printf("\n"); } printf("\n"); } void print_hex(void *data, uint32_t length) { if (length >= sizeof(uint32_t) || length % sizeof(uint32_t) == 0) print_dwords(data, length); else print_bytes(data, length); } -void +int read_controller_data(int fd, struct nvme_controller_data *cdata) { struct nvme_pt_command pt; memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_IDENTIFY; pt.cmd.cdw10 = htole32(1); pt.buf = cdata; pt.len = sizeof(*cdata); pt.is_read = 1; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "identify request failed"); + return (errno); /* Convert data to host endian */ nvme_controller_data_swapbytes(cdata); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "identify request returned error"); + return (EIO); + return (0); } -void +int read_namespace_data(int fd, uint32_t nsid, struct nvme_namespace_data *nsdata) { struct nvme_pt_command pt; memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_IDENTIFY; pt.cmd.nsid = htole32(nsid); pt.cmd.cdw10 = htole32(0); pt.buf = nsdata; pt.len = sizeof(*nsdata); pt.is_read = 1; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "identify request failed"); + return (errno); /* Convert data to host endian */ nvme_namespace_data_swapbytes(nsdata); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "identify request returned error"); + return (EIO); + return (0); } int open_dev(const char *str, int *fd, int write, int exit_on_error) { char full_path[64]; snprintf(full_path, sizeof(full_path), _PATH_DEV"%s", str); *fd = open(full_path, write ? O_RDWR : O_RDONLY); if (*fd < 0) { if (exit_on_error) { - err(1, "could not open %s%s", full_path, + err(EX_OSFILE, "could not open %s%s", full_path, write ? " for write" : ""); } else return (errno); } return (0); } void get_nsid(int fd, char **ctrlr_str, uint32_t *nsid) { struct nvme_get_nsid gnsid; if (ioctl(fd, NVME_GET_NSID, &gnsid) < 0) - err(1, "NVME_GET_NSID ioctl failed"); + err(EX_OSERR, "NVME_GET_NSID ioctl failed"); if (ctrlr_str != NULL) *ctrlr_str = strndup(gnsid.cdev, sizeof(gnsid.cdev)); if (nsid != NULL) *nsid = gnsid.nsid; } int main(int argc, char *argv[]) { cmd_init(); cmd_load_dir("/lib/nvmecontrol", NULL, NULL); cmd_load_dir(_PATH_LOCALBASE "/lib/nvmecontrol", NULL, NULL); cmd_dispatch(argc, argv, NULL); return (0); } Index: head/sbin/nvmecontrol/nvmecontrol.h =================================================================== --- head/sbin/nvmecontrol/nvmecontrol.h (revision 367629) +++ head/sbin/nvmecontrol/nvmecontrol.h (revision 367630) @@ -1,104 +1,104 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2012-2013 Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef __NVMECONTROL_H__ #define __NVMECONTROL_H__ #include #include "comnd.h" typedef void (*print_fn_t)(const struct nvme_controller_data *cdata, void *buf, uint32_t size); struct logpage_function { SLIST_ENTRY(logpage_function) link; uint8_t log_page; const char *vendor; const char *name; print_fn_t print_fn; size_t size; }; #define NVME_LOGPAGE(unique, lp, vend, nam, fn, sz) \ static struct logpage_function unique ## _lpf = { \ .log_page = lp, \ .vendor = vend, \ .name = nam, \ .print_fn = fn, \ .size = sz, \ } ; \ static void logpage_reg_##unique(void) __attribute__((constructor)); \ static void logpage_reg_##unique(void) { logpage_register(&unique##_lpf); } #define DEFAULT_SIZE (4096) struct kv_name { uint32_t key; const char *name; }; const char *kv_lookup(const struct kv_name *kv, size_t kv_count, uint32_t key); void logpage_register(struct logpage_function *p); #define NVME_CTRLR_PREFIX "nvme" #define NVME_NS_PREFIX "ns" int open_dev(const char *str, int *fd, int write, int exit_on_error); void get_nsid(int fd, char **ctrlr_str, uint32_t *nsid); -void read_controller_data(int fd, struct nvme_controller_data *cdata); -void read_namespace_data(int fd, uint32_t nsid, struct nvme_namespace_data *nsdata); +int read_controller_data(int fd, struct nvme_controller_data *cdata); +int read_namespace_data(int fd, uint32_t nsid, struct nvme_namespace_data *nsdata); void print_hex(void *data, uint32_t length); void print_namespace(struct nvme_namespace_data *nsdata); void read_logpage(int fd, uint8_t log_page, uint32_t nsid, uint8_t lsp, uint16_t lsi, uint8_t rae, void *payload, uint32_t payload_size); void print_temp(uint16_t t); void print_intel_add_smart(const struct nvme_controller_data *cdata __unused, void *buf, uint32_t size __unused); /* Utility Routines */ /* * 128-bit integer augments to standard values. On i386 this * doesn't exist, so we use 64-bit values. So, on 32-bit i386, * you'll get truncated values until someone implement 128bit * ints in sofware. */ #define UINT128_DIG 39 #ifdef __i386__ typedef uint64_t uint128_t; #else typedef __uint128_t uint128_t; #endif static __inline uint128_t to128(void *p) { return *(uint128_t *)p; } uint64_t le48dec(const void *pp); char * uint128_to_str(uint128_t u, char *buf, size_t buflen); #endif Index: head/sbin/nvmecontrol/passthru.c =================================================================== --- head/sbin/nvmecontrol/passthru.c (revision 367629) +++ head/sbin/nvmecontrol/passthru.c (revision 367630) @@ -1,299 +1,300 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2012-2013 Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include +#include #include #include "nvmecontrol.h" #include "comnd.h" static struct options { uint8_t opcode; uint8_t flags; uint16_t rsvd; uint32_t nsid; uint32_t data_len; uint32_t metadata_len; uint32_t timeout; uint32_t cdw2; uint32_t cdw3; uint32_t cdw10; uint32_t cdw11; uint32_t cdw12; uint32_t cdw13; uint32_t cdw14; uint32_t cdw15; const char *ifn; bool binary; bool show_command; bool dry_run; bool read; bool write; uint8_t prefill; const char *dev; } opt = { .binary = false, .cdw10 = 0, .cdw11 = 0, .cdw12 = 0, .cdw13 = 0, .cdw14 = 0, .cdw15 = 0, .cdw2 = 0, .cdw3 = 0, .data_len = 0, .dry_run = false, .flags = 0, .ifn = "", .metadata_len = 0, .nsid = 0, .opcode = 0, .prefill = 0, .read = false, .rsvd = 0, .show_command = false, .timeout = 0, .write = false, .dev = NULL, }; /* * Argument names and short names selected to match the nvme-cli program * so vendor-siupplied formulas work out of the box on FreeBSD with a simple * s/nvme/nvmecontrol/. */ #define ARG(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } static struct opts opts[] = { ARG("opcode", 'o', arg_uint8, opt, opcode, "NVMe command opcode (required)"), ARG("cdw2", '2', arg_uint32, opt, cdw2, "Command dword 2 value"), ARG("cdw3", '3', arg_uint32, opt, cdw3, "Command dword 3 value"), ARG("cdw10", '4', arg_uint32, opt, cdw10, "Command dword 10 value"), ARG("cdw11", '5', arg_uint32, opt, cdw11, "Command dword 11 value"), ARG("cdw12", '6', arg_uint32, opt, cdw12, "Command dword 12 value"), ARG("cdw13", '7', arg_uint32, opt, cdw13, "Command dword 13 value"), ARG("cdw14", '8', arg_uint32, opt, cdw14, "Command dword 14 value"), ARG("cdw15", '9', arg_uint32, opt, cdw15, "Command dword 15 value"), ARG("data-len", 'l', arg_uint32, opt, data_len, "Length of data for I/O (bytes)"), ARG("metadata-len", 'm', arg_uint32, opt, metadata_len, "Length of metadata segment (bytes) (igored)"), ARG("flags", 'f', arg_uint8, opt, flags, "NVMe command flags"), ARG("input-file", 'i', arg_path, opt, ifn, "Input file to send (default stdin)"), ARG("namespace-id", 'n', arg_uint32, opt, nsid, "Namespace id (ignored on FreeBSD)"), ARG("prefill", 'p', arg_uint8, opt, prefill, "Value to prefill payload with"), ARG("rsvd", 'R', arg_uint16, opt, rsvd, "Reserved field value"), ARG("timeout", 't', arg_uint32, opt, timeout, "Command timeout (ms)"), ARG("raw-binary", 'b', arg_none, opt, binary, "Output in binary format"), ARG("dry-run", 'd', arg_none, opt, dry_run, "Don't actually execute the command"), ARG("read", 'r', arg_none, opt, read, "Command reads data from device"), ARG("show-command", 's', arg_none, opt, show_command, "Show all the command values on stdout"), ARG("write", 'w', arg_none, opt, write, "Command writes data to device"), { NULL, 0, arg_none, NULL, NULL } }; static const struct args args[] = { { arg_string, &opt.dev, "controller-id|namespace-id" }, { arg_none, NULL, NULL }, }; static void passthru(const struct cmd *f, int argc, char *argv[]) { int fd = -1, ifd = -1; size_t bytes_read; void *data = NULL, *metadata = NULL; struct nvme_pt_command pt; if (arg_parse(argc, argv, f)) return; open_dev(opt.dev, &fd, 1, 1); if (opt.read && opt.write) - errx(1, "need exactly one of --read or --write"); + errx(EX_USAGE, "need exactly one of --read or --write"); if (opt.data_len != 0 && !opt.read && !opt.write) - errx(1, "need exactly one of --read or --write"); + errx(EX_USAGE, "need exactly one of --read or --write"); if (*opt.ifn && (ifd = open(opt.ifn, O_RDONLY)) == -1) { warn("open %s", opt.ifn); goto cleanup; } #if notyet /* No support in kernel for this */ if (opt.metadata_len != 0) { if (posix_memalign(&metadata, getpagesize(), opt.metadata_len)) { warn("can't allocate %d bytes for metadata", metadata_len); goto cleanup; } } #else if (opt.metadata_len != 0) - errx(1, "metadata not supported on FreeBSD"); + errx(EX_UNAVAILABLE, "metadata not supported on FreeBSD"); #endif if (opt.data_len) { if (posix_memalign(&data, getpagesize(), opt.data_len)) { warn("can't allocate %d bytes for data", opt.data_len); goto cleanup; } memset(data, opt.prefill, opt.data_len); if (opt.write && (bytes_read = read(ifd, data, opt.data_len)) != opt.data_len) { warn("read %s; expected %u bytes; got %zd", *opt.ifn ? opt.ifn : "stdin", opt.data_len, bytes_read); goto cleanup; } } if (opt.show_command) { fprintf(stderr, "opcode : %#02x\n", opt.opcode); fprintf(stderr, "flags : %#02x\n", opt.flags); fprintf(stderr, "rsvd1 : %#04x\n", opt.rsvd); fprintf(stderr, "nsid : %#04x\n", opt.nsid); fprintf(stderr, "cdw2 : %#08x\n", opt.cdw2); fprintf(stderr, "cdw3 : %#08x\n", opt.cdw3); fprintf(stderr, "data_len : %#08x\n", opt.data_len); fprintf(stderr, "metadata_len : %#08x\n", opt.metadata_len); fprintf(stderr, "data : %p\n", data); fprintf(stderr, "metadata : %p\n", metadata); fprintf(stderr, "cdw10 : %#08x\n", opt.cdw10); fprintf(stderr, "cdw11 : %#08x\n", opt.cdw11); fprintf(stderr, "cdw12 : %#08x\n", opt.cdw12); fprintf(stderr, "cdw13 : %#08x\n", opt.cdw13); fprintf(stderr, "cdw14 : %#08x\n", opt.cdw14); fprintf(stderr, "cdw15 : %#08x\n", opt.cdw15); fprintf(stderr, "timeout_ms : %d\n", opt.timeout); } if (opt.dry_run) { errno = 0; warn("Doing a dry-run, no actual I/O"); goto cleanup; } memset(&pt, 0, sizeof(pt)); pt.cmd.opc = opt.opcode; pt.cmd.fuse = opt.flags; pt.cmd.cid = htole16(opt.rsvd); pt.cmd.nsid = opt.nsid; /* XXX note: kernel overrides this */ pt.cmd.rsvd2 = htole32(opt.cdw2); pt.cmd.rsvd3 = htole32(opt.cdw3); pt.cmd.cdw10 = htole32(opt.cdw10); pt.cmd.cdw11 = htole32(opt.cdw11); pt.cmd.cdw12 = htole32(opt.cdw12); pt.cmd.cdw13 = htole32(opt.cdw13); pt.cmd.cdw14 = htole32(opt.cdw14); pt.cmd.cdw15 = htole32(opt.cdw15); pt.buf = data; pt.len = opt.data_len; pt.is_read = opt.read; errno = 0; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "passthrough request failed"); + err(EX_IOERR, "passthrough request failed"); /* XXX report status */ if (opt.read) { if (opt.binary) write(STDOUT_FILENO, data, opt.data_len); else { /* print status here */ print_hex(data, opt.data_len); } } cleanup: free(data); close(fd); if (ifd > -1) close(ifd); if (errno) - exit(1); + exit(EX_IOERR); } static void admin_passthru(const struct cmd *nf, int argc, char *argv[]) { passthru(nf, argc, argv); } static void io_passthru(const struct cmd *nf, int argc, char *argv[]) { passthru(nf, argc, argv); } static struct cmd admin_pass_cmd = { .name = "admin-passthru", .fn = admin_passthru, .ctx_size = sizeof(struct options), .opts = opts, .args = args, .descr = "Send a pass through Admin command to the specified device", }; static struct cmd io_pass_cmd = { .name = "io-passthru", .fn = io_passthru, .ctx_size = sizeof(struct options), .opts = opts, .args = args, .descr = "Send a pass through I/O command to the specified device", }; CMD_COMMAND(admin_pass_cmd); CMD_COMMAND(io_pass_cmd); Index: head/sbin/nvmecontrol/perftest.c =================================================================== --- head/sbin/nvmecontrol/perftest.c (revision 367629) +++ head/sbin/nvmecontrol/perftest.c (revision 367630) @@ -1,188 +1,189 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2012-2013 Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include +#include #include #include "nvmecontrol.h" /* Tables for command line parsing */ static cmd_fn_t perftest; #define NONE 0xffffffffu static struct options { bool perthread; uint32_t threads; uint32_t size; uint32_t time; const char *op; const char *intr; const char *flags; const char *dev; } opt = { .perthread = false, .threads = 0, .size = 0, .time = 0, .op = NULL, .intr = NULL, .flags = NULL, .dev = NULL, }; static const struct opts perftest_opts[] = { #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } OPT("perthread", 'p', arg_none, opt, perthread, "Report per-thread results"), OPT("threads", 'n', arg_uint32, opt, threads, "Number of threads to run"), OPT("size", 's', arg_uint32, opt, size, "Size of the test"), OPT("time", 't', arg_uint32, opt, time, "How long to run the test in seconds"), OPT("operation", 'o', arg_string, opt, op, "Operation type: 'read' or 'write'"), OPT("interrupt", 'i', arg_string, opt, intr, "Interrupt mode: 'intr' or 'wait'"), OPT("flags", 'f', arg_string, opt, flags, "Turn on testing flags: refthread"), { NULL, 0, arg_none, NULL, NULL } }; #undef OPT static const struct args perftest_args[] = { { arg_string, &opt.dev, "namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd perftest_cmd = { .name = "perftest", .fn = perftest, .descr = "Perform low-level performance testing", .ctx_size = sizeof(opt), .opts = perftest_opts, .args = perftest_args, }; CMD_COMMAND(perftest_cmd); /* End of tables for command line parsing */ static void print_perftest(struct nvme_io_test *io_test, bool perthread) { uint64_t io_completed = 0, iops, mbps; uint32_t i; for (i = 0; i < io_test->num_threads; i++) io_completed += io_test->io_completed[i]; iops = io_completed/io_test->time; mbps = iops * io_test->size / (1024*1024); printf("Threads: %2d Size: %6d %5s Time: %3d IO/s: %7ju MB/s: %4ju\n", io_test->num_threads, io_test->size, io_test->opc == NVME_OPC_READ ? "READ" : "WRITE", io_test->time, (uintmax_t)iops, (uintmax_t)mbps); if (perthread) for (i = 0; i < io_test->num_threads; i++) printf("\t%3d: %8ju IO/s\n", i, (uintmax_t)io_test->io_completed[i]/io_test->time); } static void perftest(const struct cmd *f, int argc, char *argv[]) { struct nvme_io_test io_test; int fd; u_long ioctl_cmd = NVME_IO_TEST; memset(&io_test, 0, sizeof(io_test)); if (arg_parse(argc, argv, f)) return; if (opt.op == NULL) arg_help(argc, argv, f); if (opt.flags != NULL && strcmp(opt.flags, "refthread") == 0) io_test.flags |= NVME_TEST_FLAG_REFTHREAD; if (opt.intr != NULL) { if (strcmp(opt.intr, "bio") == 0 || strcmp(opt.intr, "wait") == 0) ioctl_cmd = NVME_BIO_TEST; else if (strcmp(opt.intr, "io") == 0 || strcmp(opt.intr, "intr") == 0) ioctl_cmd = NVME_IO_TEST; else { fprintf(stderr, "Unknown interrupt test type %s\n", opt.intr); arg_help(argc, argv, f); } } if (opt.threads <= 0 || opt.threads > 128) { fprintf(stderr, "Bad number of threads %d\n", opt.threads); arg_help(argc, argv, f); } io_test.num_threads = opt.threads; if (strcasecmp(opt.op, "read") == 0) io_test.opc = NVME_OPC_READ; else if (strcasecmp(opt.op, "write") == 0) io_test.opc = NVME_OPC_WRITE; else { fprintf(stderr, "\"%s\" not valid opcode.\n", opt.op); arg_help(argc, argv, f); } if (opt.time == 0) { fprintf(stderr, "No time speciifed\n"); arg_help(argc, argv, f); } io_test.time = opt.time; io_test.size = opt.size; open_dev(opt.dev, &fd, 1, 1); if (ioctl(fd, ioctl_cmd, &io_test) < 0) - err(1, "ioctl NVME_IO_TEST failed"); + err(EX_IOERR, "ioctl NVME_IO_TEST failed"); close(fd); print_perftest(&io_test, opt.perthread); exit(0); } Index: head/sbin/nvmecontrol/power.c =================================================================== --- head/sbin/nvmecontrol/power.c (revision 367629) +++ head/sbin/nvmecontrol/power.c (revision 367630) @@ -1,209 +1,211 @@ /*- * Copyright (c) 2016 Netflix, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include +#include #include #include "nvmecontrol.h" _Static_assert(sizeof(struct nvme_power_state) == 256 / NBBY, "nvme_power_state size wrong"); #define POWER_NONE 0xffffffffu static struct options { bool list; uint32_t power; uint32_t workload; const char *dev; } opt = { .list = false, .power = POWER_NONE, .workload = 0, .dev = NULL, }; static void power_list_one(int i, struct nvme_power_state *nps) { int mpower, apower, ipower; uint8_t mps, nops, aps, apw; mps = (nps->mps_nops >> NVME_PWR_ST_MPS_SHIFT) & NVME_PWR_ST_MPS_MASK; nops = (nps->mps_nops >> NVME_PWR_ST_NOPS_SHIFT) & NVME_PWR_ST_NOPS_MASK; apw = (nps->apw_aps >> NVME_PWR_ST_APW_SHIFT) & NVME_PWR_ST_APW_MASK; aps = (nps->apw_aps >> NVME_PWR_ST_APS_SHIFT) & NVME_PWR_ST_APS_MASK; mpower = nps->mp; if (mps == 0) mpower *= 100; ipower = nps->idlp; if (nps->ips == 1) ipower *= 100; apower = nps->actp; if (aps == 1) apower *= 100; printf("%2d: %2d.%04dW%c %3d.%03dms %3d.%03dms %2d %2d %2d %2d %2d.%04dW %2d.%04dW %d\n", i, mpower / 10000, mpower % 10000, nops ? '*' : ' ', nps->enlat / 1000, nps->enlat % 1000, nps->exlat / 1000, nps->exlat % 1000, nps->rrt, nps->rrl, nps->rwt, nps->rwl, ipower / 10000, ipower % 10000, apower / 10000, apower % 10000, apw); } static void power_list(struct nvme_controller_data *cdata) { int i; printf("\nPower States Supported: %d\n\n", cdata->npss + 1); printf(" # Max pwr Enter Lat Exit Lat RT RL WT WL Idle Pwr Act Pwr Workloadd\n"); printf("-- -------- --------- --------- -- -- -- -- -------- -------- --\n"); for (i = 0; i <= cdata->npss; i++) power_list_one(i, &cdata->power_state[i]); } static void power_set(int fd, int power_val, int workload, int perm) { struct nvme_pt_command pt; uint32_t p; p = perm ? (1u << 31) : 0; memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_SET_FEATURES; pt.cmd.cdw10 = htole32(NVME_FEAT_POWER_MANAGEMENT | p); pt.cmd.cdw11 = htole32(power_val | (workload << 5)); if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "set feature power mgmt request failed"); + err(EX_IOERR, "set feature power mgmt request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "set feature power mgmt request returned error"); + errx(EX_IOERR, "set feature power mgmt request returned error"); } static void power_show(int fd) { struct nvme_pt_command pt; memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_GET_FEATURES; pt.cmd.cdw10 = htole32(NVME_FEAT_POWER_MANAGEMENT); if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "set feature power mgmt request failed"); + err(EX_IOERR, "set feature power mgmt request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "set feature power mgmt request returned error"); + errx(EX_IOERR, "set feature power mgmt request returned error"); printf("Current Power Mode is %d\n", pt.cpl.cdw0); } static void power(const struct cmd *f, int argc, char *argv[]) { struct nvme_controller_data cdata; int fd; char *path; uint32_t nsid; if (arg_parse(argc, argv, f)) return; if (opt.list && opt.power != POWER_NONE) { fprintf(stderr, "Can't set power and list power states\n"); arg_help(argc, argv, f); } open_dev(opt.dev, &fd, 1, 1); get_nsid(fd, &path, &nsid); if (nsid != 0) { close(fd); open_dev(path, &fd, 1, 1); } free(path); if (opt.list) { - read_controller_data(fd, &cdata); + if (read_controller_data(fd, &cdata)) + errx(EX_IOERR, "Identify request failed"); power_list(&cdata); goto out; } if (opt.power != POWER_NONE) { power_set(fd, opt.power, opt.workload, 0); goto out; } power_show(fd); out: close(fd); exit(0); } static const struct opts power_opts[] = { #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } OPT("list", 'l', arg_none, opt, list, "List the valid power states"), OPT("power", 'p', arg_uint32, opt, power, "Set the power state"), OPT("workload", 'w', arg_uint32, opt, workload, "Set the workload"), { NULL, 0, arg_none, NULL, NULL } }; #undef OPT static const struct args power_args[] = { { arg_string, &opt.dev, "controller-id|namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd power_cmd = { .name = "power", .fn = power, .descr = "Manage power states for the drive", .ctx_size = sizeof(opt), .opts = power_opts, .args = power_args, }; CMD_COMMAND(power_cmd); Index: head/sbin/nvmecontrol/reset.c =================================================================== --- head/sbin/nvmecontrol/reset.c (revision 367629) +++ head/sbin/nvmecontrol/reset.c (revision 367630) @@ -1,85 +1,86 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2012-2013 Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include +#include #include #include "nvmecontrol.h" static struct options { const char *dev; } opt = { .dev = NULL }; static const struct args args[] = { { arg_string, &opt.dev, "controller-id|namespace-id" }, { arg_none, NULL, NULL }, }; static void reset(const struct cmd *f, int argc, char *argv[]) { int fd; char *path; uint32_t nsid; if (arg_parse(argc, argv, f)) return; open_dev(opt.dev, &fd, 1, 1); get_nsid(fd, &path, &nsid); if (nsid != 0) { close(fd); open_dev(path, &fd, 1, 1); } free(path); if (ioctl(fd, NVME_RESET_CONTROLLER) < 0) - err(1, "reset request to %s failed", opt.dev); + err(EX_IOERR, "reset request to %s failed", opt.dev); exit(0); } static struct cmd reset_cmd = { .name = "reset", .fn = reset, .descr = "Perform a controller-level reset", .args = args, }; CMD_COMMAND(reset_cmd); Index: head/sbin/nvmecontrol/resv.c =================================================================== --- head/sbin/nvmecontrol/resv.c (revision 367629) +++ head/sbin/nvmecontrol/resv.c (revision 367630) @@ -1,442 +1,443 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2019 Alexander Motin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include +#include #include #include "nvmecontrol.h" /* Tables for command line parsing */ static cmd_fn_t resv; static cmd_fn_t resvacquire; static cmd_fn_t resvregister; static cmd_fn_t resvrelease; static cmd_fn_t resvreport; #define NONE 0xffffffffu #define NONE64 0xffffffffffffffffull #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } #define OPT_END { NULL, 0, arg_none, NULL, NULL } static struct cmd resv_cmd = { .name = "resv", .fn = resv, .descr = "Reservation commands", .ctx_size = 0, .opts = NULL, .args = NULL, }; CMD_COMMAND(resv_cmd); static struct acquire_options { uint64_t crkey; uint64_t prkey; uint8_t rtype; uint8_t racqa; const char *dev; } acquire_opt = { .crkey = 0, .prkey = 0, .rtype = 0, .racqa = 0, .dev = NULL, }; static const struct opts acquire_opts[] = { OPT("crkey", 'c', arg_uint64, acquire_opt, crkey, "Current Reservation Key"), OPT("prkey", 'p', arg_uint64, acquire_opt, prkey, "Preempt Reservation Key"), OPT("rtype", 't', arg_uint8, acquire_opt, rtype, "Reservation Type"), OPT("racqa", 'a', arg_uint8, acquire_opt, racqa, "Acquire Action (0=acq, 1=pre, 2=pre+ab)"), { NULL, 0, arg_none, NULL, NULL } }; static const struct args acquire_args[] = { { arg_string, &acquire_opt.dev, "namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd acquire_cmd = { .name = "acquire", .fn = resvacquire, .descr = "Acquire/preempt reservation", .ctx_size = sizeof(acquire_opt), .opts = acquire_opts, .args = acquire_args, }; CMD_SUBCOMMAND(resv_cmd, acquire_cmd); static struct register_options { uint64_t crkey; uint64_t nrkey; uint8_t rrega; bool iekey; uint8_t cptpl; const char *dev; } register_opt = { .crkey = 0, .nrkey = 0, .rrega = 0, .iekey = false, .cptpl = 0, .dev = NULL, }; static const struct opts register_opts[] = { OPT("crkey", 'c', arg_uint64, register_opt, crkey, "Current Reservation Key"), OPT("nrkey", 'k', arg_uint64, register_opt, nrkey, "New Reservation Key"), OPT("rrega", 'r', arg_uint8, register_opt, rrega, "Register Action (0=reg, 1=unreg, 2=replace)"), OPT("iekey", 'i', arg_none, register_opt, iekey, "Ignore Existing Key"), OPT("cptpl", 'p', arg_uint8, register_opt, cptpl, "Change Persist Through Power Loss State"), { NULL, 0, arg_none, NULL, NULL } }; static const struct args register_args[] = { { arg_string, ®ister_opt.dev, "namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd register_cmd = { .name = "register", .fn = resvregister, .descr = "Register/unregister reservation", .ctx_size = sizeof(register_opt), .opts = register_opts, .args = register_args, }; CMD_SUBCOMMAND(resv_cmd, register_cmd); static struct release_options { uint64_t crkey; uint8_t rtype; uint8_t rrela; const char *dev; } release_opt = { .crkey = 0, .rtype = 0, .rrela = 0, .dev = NULL, }; static const struct opts release_opts[] = { OPT("crkey", 'c', arg_uint64, release_opt, crkey, "Current Reservation Key"), OPT("rtype", 't', arg_uint8, release_opt, rtype, "Reservation Type"), OPT("rrela", 'a', arg_uint8, release_opt, rrela, "Release Action (0=release, 1=clear)"), { NULL, 0, arg_none, NULL, NULL } }; static const struct args release_args[] = { { arg_string, &release_opt.dev, "namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd release_cmd = { .name = "release", .fn = resvrelease, .descr = "Release/clear reservation", .ctx_size = sizeof(release_opt), .opts = release_opts, .args = release_args, }; CMD_SUBCOMMAND(resv_cmd, release_cmd); static struct report_options { bool hex; bool verbose; bool eds; const char *dev; } report_opt = { .hex = false, .verbose = false, .eds = false, .dev = NULL, }; static const struct opts report_opts[] = { OPT("hex", 'x', arg_none, report_opt, hex, "Print reservation status in hex"), OPT("verbose", 'v', arg_none, report_opt, verbose, "More verbosity"), OPT("eds", 'e', arg_none, report_opt, eds, "Extended Data Structure"), { NULL, 0, arg_none, NULL, NULL } }; static const struct args report_args[] = { { arg_string, &report_opt.dev, "namespace-id" }, { arg_none, NULL, NULL }, }; static struct cmd report_cmd = { .name = "report", .fn = resvreport, .descr = "Print reservation status", .ctx_size = sizeof(report_opt), .opts = report_opts, .args = report_args, }; CMD_SUBCOMMAND(resv_cmd, report_cmd); /* handles NVME_OPC_RESERVATION_* NVM commands */ static void resvacquire(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; uint64_t data[2]; int fd; uint32_t nsid; if (arg_parse(argc, argv, f)) return; open_dev(acquire_opt.dev, &fd, 0, 1); get_nsid(fd, NULL, &nsid); if (nsid == 0) { fprintf(stderr, "This command require namespace-id\n"); arg_help(argc, argv, f); } data[0] = htole64(acquire_opt.crkey); data[1] = htole64(acquire_opt.prkey); memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_RESERVATION_ACQUIRE; pt.cmd.cdw10 = htole32((acquire_opt.racqa & 7) | (acquire_opt.rtype << 8)); pt.buf = &data; pt.len = sizeof(data); pt.is_read = 0; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "acquire request failed"); + err(EX_IOERR, "acquire request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "acquire request returned error"); + errx(EX_IOERR, "acquire request returned error"); close(fd); exit(0); } static void resvregister(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; uint64_t data[2]; int fd; uint32_t nsid; if (arg_parse(argc, argv, f)) return; open_dev(register_opt.dev, &fd, 0, 1); get_nsid(fd, NULL, &nsid); if (nsid == 0) { fprintf(stderr, "This command require namespace-id\n"); arg_help(argc, argv, f); } data[0] = htole64(register_opt.crkey); data[1] = htole64(register_opt.nrkey); memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_RESERVATION_REGISTER; pt.cmd.cdw10 = htole32((register_opt.rrega & 7) | (register_opt.iekey << 3) | (register_opt.cptpl << 30)); pt.buf = &data; pt.len = sizeof(data); pt.is_read = 0; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "register request failed"); + err(EX_IOERR, "register request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "register request returned error"); + errx(EX_IOERR, "register request returned error"); close(fd); exit(0); } static void resvrelease(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; uint64_t data[1]; int fd; uint32_t nsid; if (arg_parse(argc, argv, f)) return; open_dev(release_opt.dev, &fd, 0, 1); get_nsid(fd, NULL, &nsid); if (nsid == 0) { fprintf(stderr, "This command require namespace-id\n"); arg_help(argc, argv, f); } data[0] = htole64(release_opt.crkey); memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_RESERVATION_RELEASE; pt.cmd.cdw10 = htole32((release_opt.rrela & 7) | (release_opt.rtype << 8)); pt.buf = &data; pt.len = sizeof(data); pt.is_read = 0; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "release request failed"); + err(EX_IOERR, "release request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "release request returned error"); + errx(EX_IOERR, "release request returned error"); close(fd); exit(0); } static void resvreport(const struct cmd *f, int argc, char *argv[]) { struct nvme_pt_command pt; struct nvme_resv_status *s; struct nvme_resv_status_ext *e; uint8_t data[4096] __aligned(4); int fd; u_int i, n; uint32_t nsid; if (arg_parse(argc, argv, f)) return; open_dev(report_opt.dev, &fd, 0, 1); get_nsid(fd, NULL, &nsid); if (nsid == 0) { fprintf(stderr, "This command require namespace-id\n"); arg_help(argc, argv, f); } bzero(data, sizeof(data)); memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_RESERVATION_REPORT; pt.cmd.cdw10 = htole32(sizeof(data) / 4 - 1); pt.cmd.cdw11 = htole32(report_opt.eds); /* EDS */ pt.buf = &data; pt.len = sizeof(data); pt.is_read = 1; if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "report request failed"); + err(EX_IOERR, "report request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "report request returned error"); + errx(EX_IOERR, "report request returned error"); close(fd); if (report_opt.eds) nvme_resv_status_ext_swapbytes((void *)data, sizeof(data)); else nvme_resv_status_swapbytes((void *)data, sizeof(data)); if (report_opt.hex) { i = sizeof(data); if (!report_opt.verbose) { for (; i > 64; i--) { if (data[i - 1] != 0) break; } } print_hex(&data, i); exit(0); } s = (struct nvme_resv_status *)data; n = (s->regctl[1] << 8) | s->regctl[0]; printf("Generation: %u\n", s->gen); printf("Reservation Type: %u\n", s->rtype); printf("Number of Registered Controllers: %u\n", n); printf("Persist Through Power Loss State: %u\n", s->ptpls); if (report_opt.eds) { e = (struct nvme_resv_status_ext *)data; n = MIN(n, (sizeof(data) - sizeof(e)) / sizeof(e->ctrlr[0])); for (i = 0; i < n; i++) { printf("Controller ID: 0x%04x\n", e->ctrlr[i].ctrlr_id); printf(" Reservation Status: %u\n", e->ctrlr[i].rcsts); printf(" Reservation Key: 0x%08jx\n", e->ctrlr[i].rkey); printf(" Host Identifier: 0x%08jx%08jx\n", e->ctrlr[i].hostid[0], e->ctrlr[i].hostid[1]); } } else { n = MIN(n, (sizeof(data) - sizeof(s)) / sizeof(s->ctrlr[0])); for (i = 0; i < n; i++) { printf("Controller ID: 0x%04x\n", s->ctrlr[i].ctrlr_id); printf(" Reservation Status: %u\n", s->ctrlr[i].rcsts); printf(" Host Identifier: 0x%08jx\n", s->ctrlr[i].hostid); printf(" Reservation Key: 0x%08jx\n", s->ctrlr[i].rkey); } } exit(0); } static void resv(const struct cmd *nf __unused, int argc, char *argv[]) { cmd_dispatch(argc, argv, &resv_cmd); } Index: head/sbin/nvmecontrol/sanitize.c =================================================================== --- head/sbin/nvmecontrol/sanitize.c (revision 367629) +++ head/sbin/nvmecontrol/sanitize.c (revision 367630) @@ -1,222 +1,224 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2019 Alexander Motin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include +#include #include #include "nvmecontrol.h" /* Tables for command line parsing */ static cmd_fn_t sanitize; static struct options { bool ause; bool ndas; bool oipbp; bool reportonly; uint8_t owpass; uint32_t ovrpat; const char *sanact; const char *dev; } opt = { .ause = false, .ndas = false, .oipbp = false, .reportonly = false, .owpass = 1, .ovrpat = 0, .sanact = NULL, .dev = NULL, }; static const struct opts sanitize_opts[] = { #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } OPT("ause", 'U', arg_none, opt, ause, "Allow Unrestricted Sanitize Exit"), OPT("ndas", 'd', arg_none, opt, ndas, "No Deallocate After Sanitize"), OPT("oipbp", 'I', arg_none, opt, oipbp, "Overwrite Invert Pattern Between Passes"), OPT("reportonly", 'r', arg_none, opt, reportonly, "Report previous sanitize status"), OPT("owpass", 'c', arg_uint8, opt, owpass, "Overwrite Pass Count"), OPT("ovrpat", 'p', arg_uint32, opt, ovrpat, "Overwrite Pattern"), OPT("sanact", 'a', arg_string, opt, sanact, "Sanitize Action (block, overwrite, crypto)"), { NULL, 0, arg_none, NULL, NULL } }; #undef OPT static const struct args sanitize_args[] = { { arg_string, &opt.dev, "controller-id" }, { arg_none, NULL, NULL }, }; static struct cmd sanitize_cmd = { .name = "sanitize", .fn = sanitize, .descr = "Sanitize NVM subsystem", .ctx_size = sizeof(opt), .opts = sanitize_opts, .args = sanitize_args, }; CMD_COMMAND(sanitize_cmd); /* End of tables for command line parsing */ static void sanitize(const struct cmd *f, int argc, char *argv[]) { struct nvme_controller_data cd; struct nvme_pt_command pt; struct nvme_sanitize_status_page ss; char *path; uint32_t nsid; int sanact = 0, fd, delay = 1; if (arg_parse(argc, argv, f)) return; if (opt.sanact == NULL) { if (!opt.reportonly) { fprintf(stderr, "Sanitize Action is not specified\n"); arg_help(argc, argv, f); } } else { if (strcmp(opt.sanact, "exitfailure") == 0) sanact = 1; else if (strcmp(opt.sanact, "block") == 0) sanact = 2; else if (strcmp(opt.sanact, "overwrite") == 0) sanact = 3; else if (strcmp(opt.sanact, "crypto") == 0) sanact = 4; else { fprintf(stderr, "Incorrect Sanitize Action value\n"); arg_help(argc, argv, f); } } if (opt.owpass == 0 || opt.owpass > 16) { fprintf(stderr, "Incorrect Overwrite Pass Count value\n"); arg_help(argc, argv, f); } open_dev(opt.dev, &fd, 1, 1); get_nsid(fd, &path, &nsid); if (nsid != 0) { close(fd); open_dev(path, &fd, 1, 1); } free(path); if (opt.reportonly) goto wait; /* Check that controller can execute this command. */ - read_controller_data(fd, &cd); + if (read_controller_data(fd, &cd)) + errx(EX_IOERR, "Identify request failed"); if (((cd.sanicap >> NVME_CTRLR_DATA_SANICAP_BES_SHIFT) & NVME_CTRLR_DATA_SANICAP_BES_MASK) == 0 && sanact == 2) - errx(1, "controller does not support Block Erase"); + errx(EX_UNAVAILABLE, "controller does not support Block Erase"); if (((cd.sanicap >> NVME_CTRLR_DATA_SANICAP_OWS_SHIFT) & NVME_CTRLR_DATA_SANICAP_OWS_MASK) == 0 && sanact == 3) - errx(1, "controller does not support Overwrite"); + errx(EX_UNAVAILABLE, "controller does not support Overwrite"); if (((cd.sanicap >> NVME_CTRLR_DATA_SANICAP_CES_SHIFT) & NVME_CTRLR_DATA_SANICAP_CES_MASK) == 0 && sanact == 4) - errx(1, "controller does not support Crypto Erase"); + errx(EX_UNAVAILABLE, "controller does not support Crypto Erase"); /* * If controller supports only one namespace, we may sanitize it. * If there can be more, make user explicit in his commands. */ if (nsid != 0 && cd.nn > 1) - errx(1, "can't sanitize one of namespaces, specify controller"); + errx(EX_UNAVAILABLE, "can't sanitize one of namespaces, specify controller"); memset(&pt, 0, sizeof(pt)); pt.cmd.opc = NVME_OPC_SANITIZE; pt.cmd.cdw10 = htole32((opt.ndas << 9) | (opt.oipbp << 8) | ((opt.owpass & 0xf) << 4) | (opt.ause << 3) | sanact); pt.cmd.cdw11 = htole32(opt.ovrpat); if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) - err(1, "sanitize request failed"); + err(EX_IOERR, "sanitize request failed"); if (nvme_completion_is_error(&pt.cpl)) - errx(1, "sanitize request returned error"); + errx(EX_IOERR, "sanitize request returned error"); wait: read_logpage(fd, NVME_LOG_SANITIZE_STATUS, NVME_GLOBAL_NAMESPACE_TAG, 0, 0, 0, &ss, sizeof(ss)); switch ((ss.sstat >> NVME_SS_PAGE_SSTAT_STATUS_SHIFT) & NVME_SS_PAGE_SSTAT_STATUS_MASK) { case NVME_SS_PAGE_SSTAT_STATUS_NEVER: printf("Never sanitized"); break; case NVME_SS_PAGE_SSTAT_STATUS_COMPLETED: printf("Sanitize completed"); break; case NVME_SS_PAGE_SSTAT_STATUS_INPROG: printf("Sanitize in progress: %u%% (%u/65535)\r", (ss.sprog * 100 + 32768) / 65536, ss.sprog); fflush(stdout); if (delay < 16) delay++; sleep(delay); goto wait; case NVME_SS_PAGE_SSTAT_STATUS_FAILED: printf("Sanitize failed"); break; case NVME_SS_PAGE_SSTAT_STATUS_COMPLETEDWD: printf("Sanitize completed with deallocation"); break; default: printf("Sanitize status unknown"); break; } if (delay > 1) printf(" "); printf("\n"); close(fd); exit(0); }