From ffce99f502910435791b3ceb4700fd31589b99e4 Mon Sep 17 00:00:00 2001 From: Warner Losh Date: Sat, 4 Feb 2017 05:53:00 +0000 Subject: [PATCH] Implement 5 wdc-specific nvme control options for their HGST drives: wdc cap-diag Capture diagnostic data from drive wdc drive-log Capture drive history data from drive wdc get-crash-dump Retrieve firmware crash dump from drive --- sbin/nvmecontrol/Makefile | 2 +- sbin/nvmecontrol/nvmecontrol.8 | 18 ++ sbin/nvmecontrol/nvmecontrol.c | 1 + sbin/nvmecontrol/nvmecontrol.h | 4 + sbin/nvmecontrol/wdc.c | 341 +++++++++++++++++++++++++++++++++ 5 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 sbin/nvmecontrol/wdc.c diff --git a/sbin/nvmecontrol/Makefile b/sbin/nvmecontrol/Makefile index 42dd5916fc59..7d18a371063e 100644 --- a/sbin/nvmecontrol/Makefile +++ b/sbin/nvmecontrol/Makefile @@ -3,7 +3,7 @@ PACKAGE=runtime PROG= nvmecontrol SRCS= nvmecontrol.c devlist.c firmware.c identify.c logpage.c \ - perftest.c reset.c nvme_util.c power.c + perftest.c reset.c nvme_util.c power.c wdc.c MAN= nvmecontrol.8 .PATH: ${.CURDIR}/../../sys/dev/nvme diff --git a/sbin/nvmecontrol/nvmecontrol.8 b/sbin/nvmecontrol/nvmecontrol.8 index 73842212da31..099eb7472837 100644 --- a/sbin/nvmecontrol/nvmecontrol.8 +++ b/sbin/nvmecontrol/nvmecontrol.8 @@ -75,6 +75,24 @@ .Op Fl l .Op Fl p power_state .Op fl w workload_hint +.Nm +.Ic wdc cap-diag +.Op Fl o path_template +.Aq device id +.Nm +.Ic wdc drive-log +.Op Fl o path_template +.Aq device id +.Nm +.Ic wdc get-crash-dump +.Op Fl o path_template +.Aq device id +.\" .Nm +.\" .Ic wdc purge +.\" .Aq device id +.\" .Nm +.\" .Ic wdc purge-monitor +.\" .Aq device id .Sh DESCRIPTION NVM Express (NVMe) is a storage protocol standard, for SSDs and other high-speed storage devices over PCI Express. diff --git a/sbin/nvmecontrol/nvmecontrol.c b/sbin/nvmecontrol/nvmecontrol.c index 180a8a291a03..52dccb411bf9 100644 --- a/sbin/nvmecontrol/nvmecontrol.c +++ b/sbin/nvmecontrol/nvmecontrol.c @@ -54,6 +54,7 @@ static struct nvme_function funcs[] = { {"logpage", logpage, LOGPAGE_USAGE}, {"firmware", firmware, FIRMWARE_USAGE}, {"power", power, POWER_USAGE}, + {"wdc", wdc, WDC_USAGE}, {NULL, NULL, NULL}, }; diff --git a/sbin/nvmecontrol/nvmecontrol.h b/sbin/nvmecontrol/nvmecontrol.h index 745728718b36..3c03ed99160f 100644 --- a/sbin/nvmecontrol/nvmecontrol.h +++ b/sbin/nvmecontrol/nvmecontrol.h @@ -66,6 +66,9 @@ struct nvme_function { #define POWER_USAGE \ " nvmecontrol power [-l] [-p new-state [-w workload-hint]] \n" +#define WDC_USAGE \ +" nvmecontrol wdc (cap-diag|drive-log|get-crash-dump|purge|purge-montior)\n" + void devlist(int argc, char *argv[]); void identify(int argc, char *argv[]); void perftest(int argc, char *argv[]); @@ -73,6 +76,7 @@ void reset(int argc, char *argv[]); void logpage(int argc, char *argv[]); void firmware(int argc, char *argv[]); void power(int argc, char *argv[]); +void wdc(int argc, char *argv[]); int open_dev(const char *str, int *fd, int show_error, int exit_on_error); void parse_ns_str(const char *ns_str, char *ctrlr_str, int *nsid); diff --git a/sbin/nvmecontrol/wdc.c b/sbin/nvmecontrol/wdc.c new file mode 100644 index 000000000000..7a7b605f3763 --- /dev/null +++ b/sbin/nvmecontrol/wdc.c @@ -0,0 +1,341 @@ +/*- + * Copyright (c) 2017 Netflix, Inc + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nvmecontrol.h" + +#define WDC_NVME_TOC_SIZE 8 + +#define WDC_NVME_CAP_DIAG_OPCODE 0xe6 +#define WDC_NVME_CAP_DIAG_CMD 0x0000 + +#define WDC_NVME_DIAG_OPCODE 0xc6 +#define WDC_NVME_DRIVE_LOG_SIZE_CMD 0x0120 +#define WDC_NVME_DRIVE_LOG_CMD 0x0020 +#define WDC_NVME_CRASH_DUMP_SIZE_CMD 0x0320 +#define WDC_NVME_CRASH_DUMP_CMD 0x0420 +#define WDC_NVME_PFAIL_DUMP_SIZE_CMD 0x0520 +#define WDC_NVME_PFAIL_DUMP_CMD 0x0620 + +#define WDC_NVME_CLEAR_DUMP_OPCODE 0xff +#define WDC_NVME_CLEAR_CRASH_DUMP_CMD 0x0503 +#define WDC_NVME_CLEAR_PFAIL_DUMP_CMD 0x0603 + +static void wdc_cap_diag(int argc, char *argv[]); +static void wdc_drive_log(int argc, char *argv[]); +static void wdc_get_crash_dump(int argc, char *argv[]); +static void wdc_purge(int argc, char *argv[]); +static void wdc_purge_monitor(int argc, char *argv[]); + +#define WDC_CAP_DIAG_USAGE "\tnvmecontrol wdc cap-diag [-o path-template]\n" +#define WDC_DRIVE_LOG_USAGE "\tnvmecontrol wdc drive-log [-o path-template]\n" +#define WDC_GET_CRASH_DUMP_USAGE "\tnvmecontrol wdc get-crash-dump [-o path-template]\n" +#define WDC_PURGE_USAGE "\tnvmecontrol wdc purge [-o path-template]\n" +#define WDC_PURGE_MONITOR_USAGE "\tnvmecontrol wdc purge-montor\n" + +static struct nvme_function wdc_funcs[] = { + {"cap-diag", wdc_cap_diag, WDC_CAP_DIAG_USAGE}, + {"drive-log", wdc_drive_log, WDC_DRIVE_LOG_USAGE}, + {"get-crash-dump", wdc_get_crash_dump, WDC_GET_CRASH_DUMP_USAGE}, + {"purge", wdc_purge, WDC_PURGE_USAGE}, + {"purge_monitor", wdc_purge_monitor, WDC_PURGE_MONITOR_USAGE}, + {NULL, NULL, NULL}, +}; + +static void +wdc_append_serial_name(int fd, char *buf, size_t len, const char *suffix) +{ + struct nvme_controller_data cdata; + char sn[NVME_SERIAL_NUMBER_LENGTH + 1]; + char *walker; + + len -= strlen(buf); + buf += strlen(buf); + read_controller_data(fd, &cdata); + memcpy(sn, cdata.sn, NVME_SERIAL_NUMBER_LENGTH); + walker = sn + NVME_SERIAL_NUMBER_LENGTH - 1; + while (walker > sn && *walker == ' ') + walker--; + *walker = '\0'; + snprintf(buf, len, "%s%s.bin", sn, suffix); +} + +static void +wdc_get_data(int fd, uint32_t opcode, uint32_t len, uint32_t off, uint32_t cmd, + uint8_t *buffer, size_t buflen) +{ + struct nvme_pt_command pt; + + memset(&pt, 0, sizeof(pt)); + pt.cmd.opc = opcode; + pt.cmd.cdw10 = len / sizeof(uint32_t); /* - 1 like all the others ??? */ + pt.cmd.cdw11 = off / sizeof(uint32_t); + pt.cmd.cdw12 = cmd; + pt.buf = buffer; + pt.len = buflen; + pt.is_read = 1; +// printf("opcode %#x cdw10(len) %#x cdw11(offset?) %#x cdw12(cmd/sub) %#x buflen %zd\n", +// (int)opcode, (int)cdw10, (int)cdw11, (int)cdw12, buflen); + + if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) + err(1, "wdc_get_data request failed"); + if (nvme_completion_is_error(&pt.cpl)) + errx(1, "wdc_get_data request returned error"); +} + +static void +wdc_do_dump(int fd, char *tmpl, const char *suffix, uint32_t opcode, + uint32_t size_cmd, uint32_t cmd, int len_off) +{ + int fd2; + uint8_t *buf; + uint32_t len, resid, offset; + + wdc_append_serial_name(fd, tmpl, MAXPATHLEN, suffix); + + buf = aligned_alloc(PAGE_SIZE, WDC_NVME_TOC_SIZE); + if (buf == NULL) + errx(1, "Can't get buffer to get size"); + wdc_get_data(fd, opcode, WDC_NVME_TOC_SIZE, + 0, size_cmd, buf, WDC_NVME_TOC_SIZE); + len = be32dec(buf + len_off); + + if (len == 0) + errx(1, "No data for %s", suffix); + + printf("Dumping %d bytes to %s\n", len, tmpl); + /* XXX overwrite protection? */ + fd2 = open(tmpl, O_WRONLY | O_CREAT | O_TRUNC); + if (fd2 < 0) + err(1, "open %s", tmpl); + offset = 0; + buf = aligned_alloc(PAGE_SIZE, NVME_MAX_XFER_SIZE); + if (buf == NULL) + errx(1, "Can't get buffer to read dump"); + while (len > 0) { + resid = len > NVME_MAX_XFER_SIZE ? NVME_MAX_XFER_SIZE : len; + wdc_get_data(fd, opcode, resid, offset, cmd, buf, resid); + if (write(fd2, buf, resid) != resid) + err(1, "write"); + offset += resid; + len -= resid; + } + free(buf); + close(fd2); +} + +static void +wdc_do_clear_dump(int fd, uint32_t opcode, uint32_t cmd) +{ + struct nvme_pt_command pt; + + memset(&pt, 0, sizeof(pt)); + pt.cmd.opc = opcode; + pt.cmd.cdw12 = cmd; + if (ioctl(fd, NVME_PASSTHROUGH_CMD, &pt) < 0) + err(1, "wdc_do_clear_dump request failed"); + if (nvme_completion_is_error(&pt.cpl)) + errx(1, "wdc_do_clear_dump request returned error"); +} + +static void +wdc_cap_diag_usage() +{ + fprintf(stderr, "usage:\n"); + fprintf(stderr, WDC_CAP_DIAG_USAGE); + exit(1); +} + +static void +wdc_cap_diag(int argc, char *argv[]) +{ + char path_tmpl[MAXPATHLEN]; + int ch, fd; + + path_tmpl[0] = '\0'; + while ((ch = getopt(argc, argv, "o:")) != -1) { + switch ((char)ch) { + case 'o': + strlcpy(path_tmpl, optarg, MAXPATHLEN); + break; + default: + wdc_cap_diag_usage(); + } + } + /* Check that a controller was specified. */ + if (optind >= argc) + wdc_cap_diag_usage(); + open_dev(argv[optind], &fd, 1, 1); + + wdc_do_dump(fd, path_tmpl, "cap_diag", WDC_NVME_CAP_DIAG_OPCODE, + WDC_NVME_CAP_DIAG_CMD, WDC_NVME_CAP_DIAG_CMD, 4); + + close(fd); + + exit(1); +} + +static void +wdc_drive_log_usage() +{ + fprintf(stderr, "usage:\n"); + fprintf(stderr, WDC_DRIVE_LOG_USAGE); + exit(1); +} + +static void +wdc_drive_log(int argc, char *argv[]) +{ + char path_tmpl[MAXPATHLEN]; + int ch, fd; + + path_tmpl[0] = '\0'; + while ((ch = getopt(argc, argv, "o:")) != -1) { + switch ((char)ch) { + case 'o': + strlcpy(path_tmpl, optarg, MAXPATHLEN); + break; + default: + wdc_drive_log_usage(); + } + } + /* Check that a controller was specified. */ + if (optind >= argc) + wdc_drive_log_usage(); + open_dev(argv[optind], &fd, 1, 1); + + wdc_do_dump(fd, path_tmpl, "drive_log", WDC_NVME_DIAG_OPCODE, + WDC_NVME_DRIVE_LOG_SIZE_CMD, WDC_NVME_DRIVE_LOG_CMD, 0); + + close(fd); + + exit(1); +} + +static void +wdc_get_crash_dump_usage() +{ + fprintf(stderr, "usage:\n"); + fprintf(stderr, WDC_CAP_DIAG_USAGE); + exit(1); +} + +static void +wdc_get_crash_dump(int argc, char *argv[]) +{ + char path_tmpl[MAXPATHLEN]; + int ch, fd; + + while ((ch = getopt(argc, argv, "o:")) != -1) { + switch ((char)ch) { + case 'o': + strlcpy(path_tmpl, optarg, MAXPATHLEN); + break; + default: + wdc_get_crash_dump_usage(); + } + } + /* Check that a controller was specified. */ + if (optind >= argc) + wdc_get_crash_dump_usage(); + open_dev(argv[optind], &fd, 1, 1); + + wdc_do_dump(fd, path_tmpl, "crash_dump", WDC_NVME_DIAG_OPCODE, + WDC_NVME_CRASH_DUMP_SIZE_CMD, WDC_NVME_CRASH_DUMP_CMD, 0); + wdc_do_clear_dump(fd, WDC_NVME_CLEAR_DUMP_OPCODE, + WDC_NVME_CLEAR_CRASH_DUMP_CMD); +// wdc_led_beacon_disable(fd); + wdc_do_dump(fd, path_tmpl, "pfail_dump", WDC_NVME_DIAG_OPCODE, + WDC_NVME_PFAIL_DUMP_SIZE_CMD, WDC_NVME_PFAIL_DUMP_CMD, 0); + wdc_do_clear_dump(fd, WDC_NVME_CLEAR_DUMP_OPCODE, + WDC_NVME_CLEAR_PFAIL_DUMP_CMD); + + close(fd); + + exit(1); +} + +static void +wdc_purge(int argc, char *argv[]) +{ + char path_tmpl[MAXPATHLEN]; + int ch; + + while ((ch = getopt(argc, argv, "o:")) != -1) { + switch ((char)ch) { + case 'o': + strlcpy(path_tmpl, optarg, MAXPATHLEN); + break; + default: + wdc_cap_diag_usage(); + } + } + + printf("purge has not been implemented.\n"); + exit(1); +} + +static void +wdc_purge_monitor(int argc, char *argv[]) +{ + char path_tmpl[MAXPATHLEN]; + int ch; + + while ((ch = getopt(argc, argv, "o:")) != -1) { + switch ((char)ch) { + case 'o': + strlcpy(path_tmpl, optarg, MAXPATHLEN); + break; + default: + wdc_cap_diag_usage(); + } + } + + printf("purge has not been implemented.\n"); + exit(1); +} + +void +wdc(int argc, char *argv[]) +{ + + dispatch(argc, argv, wdc_funcs); +}