From: Chaitanya Kulkarni Date: Wed, 1 Mar 2017 02:14:09 +0000 (-0800) Subject: nvme-cli: Western Digital/HGST plug-in. X-Git-Tag: v1.2~10 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=5902e8a0999464e57c3ba41a809898acbe3290ca;p=users%2Fsagi%2Fnvme-cli.git nvme-cli: Western Digital/HGST plug-in. This patch adds support for Vendor unique commands for Western Digital/HGST devices. Following commands are supported in current version of this extension:- 1.cap-diag WDC Capture-Diagnostics 2.drive-log WDC Drive Log 3.get-crash-dump WDC Crash Dump 4.id-ctrl WDC identify controller 5.purge WDC Purge 6.purge-monitor WDC Purge Monitor 7.smart-log-add WDC Additional Smart Log Signed-off-by: Chaitanya Kulkarni --- diff --git a/Makefile b/Makefile index 2d25edc4..414c0398 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ NVME_DPKG_VERSION=1~`lsb_release -sc` OBJS := argconfig.o suffix.o parser.o nvme-print.o nvme-ioctl.o \ nvme-lightnvm.o fabrics.o json.o plugin.o intel-nvme.o \ - lnvm-nvme.o memblaze-nvme.o nvme-models.o + lnvm-nvme.o memblaze-nvme.o wdc-nvme.o nvme-models.o nvme: nvme.c nvme.h $(OBJS) NVME-VERSION-FILE $(CC) $(CPPFLAGS) $(CFLAGS) nvme.c -o $(NVME) $(OBJS) $(LDFLAGS) diff --git a/wdc-nvme.c b/wdc-nvme.c new file mode 100644 index 00000000..8028f61f --- /dev/null +++ b/wdc-nvme.c @@ -0,0 +1,897 @@ +/* + * Copyright (c) 2015-2017 Western Digital Corporation or its affiliates. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * Author: Chaitanya Kulkarni , + * Dong Ho + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "linux/nvme_ioctl.h" + +#include "nvme.h" +#include "nvme-print.h" +#include "nvme-ioctl.h" +#include "plugin.h" +#include "json.h" + +#include "argconfig.h" +#include "suffix.h" +#include +#define CREATE_CMD +#include "wdc-nvme.h" + +#define WRITE_SIZE (sizeof(__u8) * 4096) + +#define WDC_NVME_SUBCMD_SHIFT 8 + +#define WDC_NVME_LOG_SIZE_DATA_LEN 0x08 +/* Device Config */ +#define WDC_NVME_GF_VID 0x1c58 +#define WDC_NVME_GF_CNTL_ID 0x0003 + +/* Capture Diagnostics */ +#define WDC_NVME_CAP_DIAG_HEADER_TOC_SIZE WDC_NVME_LOG_SIZE_DATA_LEN +#define WDC_NVME_CAP_DIAG_OPCODE 0xE6 +#define WDC_NVME_CAP_DIAG_CMD_OPCODE 0xC6 +#define WDC_NVME_CAP_DIAG_SUBCMD 0x00 +#define WDC_NVME_CAP_DIAG_CMD 0x00 + +/* Crash dump */ +#define WDC_NVME_CRASH_DUMP_SIZE_OPCODE WDC_NVME_CAP_DIAG_CMD_OPCODE +#define WDC_NVME_CRASH_DUMP_SIZE_DATA_LEN WDC_NVME_LOG_SIZE_DATA_LEN +#define WDC_NVME_CRASH_DUMP_SIZE_NDT 0x02 +#define WDC_NVME_CRASH_DUMP_SIZE_CMD 0x20 +#define WDC_NVME_CRASH_DUMP_SIZE_SUBCMD 0x03 + +#define WDC_NVME_CRASH_DUMP_OPCODE WDC_NVME_CAP_DIAG_CMD_OPCODE +#define WDC_NVME_CRASH_DUMP_CMD 0x20 +#define WDC_NVME_CRASH_DUMP_SUBCMD 0x04 + +/* Drive Log */ +#define WDC_NVME_DRIVE_LOG_SIZE_OPCODE WDC_NVME_CAP_DIAG_CMD_OPCODE +#define WDC_NVME_DRIVE_LOG_SIZE_DATA_LEN WDC_NVME_LOG_SIZE_DATA_LEN +#define WDC_NVME_DRIVE_LOG_SIZE_NDT 0x02 +#define WDC_NVME_DRIVE_LOG_SIZE_CMD 0x20 +#define WDC_NVME_DRIVE_LOG_SIZE_SUBCMD 0x01 + +#define WDC_NVME_DRIVE_LOG_OPCODE WDC_NVME_CAP_DIAG_CMD_OPCODE +#define WDC_NVME_DRIVE_LOG_CMD WDC_NVME_LOG_SIZE_DATA_LEN +#define WDC_NVME_DRIVE_LOG_SUBCMD 0x00 + +/* Purge and Purge Monitor */ +#define WDC_NVME_PURGE_CMD_OPCODE 0xDD +#define WDC_NVME_PURGE_MONITOR_OPCODE 0xDE +#define WDC_NVME_PURGE_MONITOR_DATA_LEN 0x2F +#define WDC_NVME_PURGE_MONITOR_CMD_CDW10 0x0000000C +#define WDC_NVME_PURGE_MONITOR_TIMEOUT 0x7530 +#define WDC_NVME_PURGE_CMD_SEQ_ERR 0x0C +#define WDC_NVME_PURGE_INT_DEV_ERR 0x06 + +#define WDC_NVME_PURGE_STATE_IDLE 0x00 +#define WDC_NVME_PURGE_STATE_DONE 0x01 +#define WDC_NVME_PURGE_STATE_BUSY 0x02 +#define WDC_NVME_PURGE_STATE_REQ_PWR_CYC 0x03 +#define WDC_NVME_PURGE_STATE_PWR_CYC_PURGE 0x04 + +/* Clear dumps */ +#define WDC_NVME_CLEAR_DUMP_OPCODE 0xFF +#define WDC_NVME_CLEAR_CRASH_DUMP_CMD 0x03 +#define WDC_NVME_CLEAR_CRASH_DUMP_SUBCMD 0x05 + +/* Additional Smart Log */ +#define WDC_ADD_LOG_BUF_LEN 0x4000 +#define WDC_NVME_ADD_LOG_OPCODE 0xC1 +#define WDC_GET_LOG_PAGE_SSD_PERFORMANCE 0x37 +#define WDC_NVME_GET_STAT_PERF_INTERVAL_LIFETIME 0x0F + +static int wdc_get_serial_name(int fd, char *file, size_t len, char *suffix); +static int wdc_create_log_file(char *file, __u8 *drive_log_data, + __u32 drive_log_length); +static int wdc_do_clear_dump(int fd, __u8 opcode, __u32 cdw12); +static int wdc_do_dump(int fd, __u32 opcode,__u32 data_len, __u32 cdw10, + __u32 cdw12, __u32 dump_length, char *file); +static int wdc_do_crash_dump(int fd, char *file); +static int wdc_crash_dump(int fd, char *file); +static int wdc_get_crash_dump(int argc, char **argv, struct command *command, + struct plugin *plugin); +static int wdc_do_drive_log(int fd, char *file); +static int wdc_drive_log(int argc, char **argv, struct command *command, + struct plugin *plugin); +static const char* wdc_purge_mon_status_to_string(__u32 status); +static int wdc_purge(int argc, char **argv, + struct command *command, struct plugin *plugin); +static int wdc_purge_monitor(int argc, char **argv, + struct command *command, struct plugin *plugin); + +/* Drive log data size */ +struct wdc_log_size { + __le32 log_size; +}; + +/* Purge monitor response */ +struct wdc_nvme_purge_monitor_data { + __le16 rsvd1; + __le16 rsvd2; + __le16 first_erase_failure_cnt; + __le16 second_erase_failure_cnt; + __le16 rsvd3; + __le16 programm_failure_cnt; + __le32 rsvd4; + __le32 rsvd5; + __le32 entire_progress_total; + __le32 entire_progress_current; + __u8 rsvd6[14]; +}; + +/* Additional Smart Log */ +struct wdc_log_page_header { + uint8_t num_subpages; + uint8_t reserved; + __le16 total_log_size; +}; + +struct wdc_log_page_subpage_header { + uint8_t spcode; + uint8_t pcset; + __le16 subpage_length; +}; + +struct wdc_ssd_perf_stats { + __le64 hr_cmds; /* Host Read Commands */ + __le64 hr_blks; /* Host Read Blocks */ + __le64 hr_ch_cmds; /* Host Read Cache Hit Commands */ + __le64 hr_ch_blks; /* Host Read Cache Hit Blocks */ + __le64 hr_st_cmds; /* Host Read Stalled Commands */ + __le64 hw_cmds; /* Host Write Commands */ + __le64 hw_blks; /* Host Write Blocks */ + __le64 hw_os_cmds; /* Host Write Odd Start Commands */ + __le64 hw_oe_cmds; /* Host Write Odd End Commands */ + __le64 hw_st_cmds; /* Host Write Commands Stalled */ + __le64 nr_cmds; /* NAND Read Commands */ + __le64 nr_blks; /* NAND Read Blocks */ + __le64 nw_cmds; /* NAND Write Commands */ + __le64 nw_blks; /* NAND Write Blocks */ + __le64 nrbw; /* NAND Read Before Write */ +}; + +static double safe_div_fp(double numerator, double denominator) +{ + return denominator ? numerator / denominator : 0; +} + +static double calc_percent(uint64_t numerator, uint64_t denominator) +{ + return denominator ? + (uint64_t)(((double)numerator / (double)denominator) * 100) : 0; +} + +static int wdc_check_device(int fd) +{ + int ret; + struct nvme_id_ctrl ctrl; + + memset(&ctrl, 0, sizeof (struct nvme_id_ctrl)); + ret = nvme_identify_ctrl(fd, &ctrl); + if (ret) { + fprintf(stderr, "ERROR : WDC : nvme_identify_ctrl() failed " + "0x%x\n", ret); + return -1; + } + ret = -1; + /* GF : ctrl->cntlid == PCI Device ID, use that with VID to identify GF Device */ + if ((le32_to_cpu(ctrl.cntlid) == WDC_NVME_GF_CNTL_ID) && + (le32_to_cpu(ctrl.vid) == WDC_NVME_GF_VID)) + ret = 0; + else + fprintf(stderr, "WARNING : WDC : Device not supported\n"); + + return ret; +} + +static int wdc_get_serial_name(int fd, char *file, size_t len, char *suffix) +{ + int i; + int ret; + char orig[PATH_MAX] = {0}; + struct nvme_id_ctrl ctrl; + + i = sizeof (ctrl.sn) - 1; + strncpy(orig, file, PATH_MAX); + memset(file, 0, len); + memset(&ctrl, 0, sizeof (struct nvme_id_ctrl)); + ret = nvme_identify_ctrl(fd, &ctrl); + if (ret) { + fprintf(stderr, "ERROR : WDC : nvme_identify_ctrl() failed " + "0x%x\n", ret); + return -1; + } + /* Remove trailing spaces from the name */ + while (i && ctrl.sn[i] == ' ') { + ctrl.sn[i] = '\0'; + i--; + } + snprintf(file, len, "%s%s%s.bin", orig, ctrl.sn, suffix); + return 0; +} + +static int wdc_create_log_file(char *file, __u8 *drive_log_data, + __u32 drive_log_length) +{ + int fd; + int ret; + + if (drive_log_length == 0) { + fprintf(stderr, "ERROR : WDC: invalid log file length\n"); + return -1; + } + + fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) { + fprintf(stderr, "ERROR : WDC: open : %s\n", strerror(errno)); + return -1; + } + + while (drive_log_length > WRITE_SIZE) { + ret = write(fd, drive_log_data, WRITE_SIZE); + if (ret < 0) { + fprintf (stderr, "ERROR : WDC: write : %s\n", strerror(errno)); + return -1; + } + drive_log_data += WRITE_SIZE; + drive_log_length -= WRITE_SIZE; + } + + ret = write(fd, drive_log_data, drive_log_length); + if (ret < 0) { + fprintf(stderr, "ERROR : WDC : write : %s\n", strerror(errno)); + return -1; + } + + if (fsync(fd) < 0) { + fprintf(stderr, "ERROR : WDC : fsync : %s\n", strerror(errno)); + return -1; + } + close(fd); + return 0; +} + +static int wdc_do_clear_dump(int fd, __u8 opcode, __u32 cdw12) +{ + int ret; + struct nvme_admin_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = opcode; + admin_cmd.cdw12 = cdw12; + ret = nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD, &admin_cmd); + if (ret != 0) { + fprintf(stdout, "ERROR : WDC : Crash dump erase failed\n"); + } + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + return ret; +} + +static __u32 wdc_dump_length(int fd, __u32 opcode, __u32 cdw10, __u32 cdw12, __u32 *dump_length) +{ + int ret; + __u8 buf[WDC_NVME_LOG_SIZE_DATA_LEN] = {0}; + struct wdc_log_size *l; + struct nvme_admin_cmd admin_cmd; + + l = (struct wdc_log_size *) buf; + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = opcode; + admin_cmd.addr = (__u64) buf; + admin_cmd.data_len = WDC_NVME_LOG_SIZE_DATA_LEN; + admin_cmd.cdw10 = cdw10; + admin_cmd.cdw12 = cdw12; + + ret = nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD, &admin_cmd); + if (ret != 0) { + l->log_size = 0; + ret = -1; + fprintf(stderr, "ERROR : WDC : reading dump length failed\n"); + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + return ret; + } + + if (opcode == WDC_NVME_CAP_DIAG_OPCODE) + *dump_length = buf[0x04] << 24 | buf[0x05] << 16 | buf[0x06] << 8 | buf[0x07]; + else + *dump_length = le32_to_cpu(l->log_size); + return ret; +} + +static int wdc_do_dump(int fd, __u32 opcode,__u32 data_len, __u32 cdw10, + __u32 cdw12, __u32 dump_length, char *file) +{ + int ret; + __u8 *dump_data; + struct nvme_admin_cmd admin_cmd; + + dump_data = (__u8 *) malloc(sizeof (__u8) * dump_length); + if (dump_data == NULL) { + fprintf(stderr, "ERROR : malloc : %s\n", strerror(errno)); + return -1; + } + memset(dump_data, 0, sizeof (__u8) * dump_length); + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = opcode; + admin_cmd.addr = (__u64) dump_data; + admin_cmd.data_len = data_len; + admin_cmd.cdw10 = cdw10; + admin_cmd.cdw12 = cdw12; + + ret = nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD, &admin_cmd); + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + if (ret == 0) { + ret = wdc_create_log_file(file, dump_data, dump_length); + } + free(dump_data); + return ret; +} + +static int wdc_do_cap_diag(int fd, char *file) +{ + int ret; + __u32 cap_diag_length; + + ret = wdc_dump_length(fd, WDC_NVME_CAP_DIAG_OPCODE, + WDC_NVME_CAP_DIAG_HEADER_TOC_SIZE, + 0x00, + &cap_diag_length); + if (ret == -1) { + return -1; + } + if (cap_diag_length == 0) { + fprintf(stderr, "INFO : WDC : Capture Dignostics log is empty\n"); + } else { + ret = wdc_do_dump(fd, WDC_NVME_CAP_DIAG_OPCODE, cap_diag_length, + cap_diag_length, + (WDC_NVME_CAP_DIAG_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | + WDC_NVME_CAP_DIAG_CMD, cap_diag_length, file); + + } + return ret; +} + +static int wdc_cap_diag(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char *desc = "Capture Diagnostics Log."; + char *file = "Output file pathname."; + char f[PATH_MAX] = {0}; + int fd; + + struct config { + char *file; + }; + + struct config cfg = { + .file = NULL + }; + + const struct argconfig_commandline_options command_line_options[] = { + {"output-file", 'o', "FILE", CFG_STRING, &cfg.file, required_argument, file}, + { NULL, '\0', NULL, CFG_NONE, NULL, no_argument, desc}, + {NULL} + }; + + fd = parse_and_open(argc, argv, desc, command_line_options, NULL, 0); + wdc_check_device(fd); + if (cfg.file != NULL) { + strncpy(f, cfg.file, PATH_MAX); + } + if (wdc_get_serial_name(fd, f, PATH_MAX, "cap_diag") == -1) { + fprintf(stderr, "ERROR : WDC: failed to generate file name\n"); + return -1; + } + return wdc_do_cap_diag(fd, f); +} + +static int wdc_do_crash_dump(int fd, char *file) +{ + int ret; + __u32 crash_dump_length; + __u8 opcode = WDC_NVME_CLEAR_DUMP_OPCODE; + __u32 cdw12 = ((WDC_NVME_CLEAR_CRASH_DUMP_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | + WDC_NVME_CLEAR_CRASH_DUMP_CMD); + + ret = wdc_dump_length(fd, WDC_NVME_CRASH_DUMP_SIZE_OPCODE, + WDC_NVME_CRASH_DUMP_SIZE_NDT, + ((WDC_NVME_CRASH_DUMP_SIZE_SUBCMD << + WDC_NVME_SUBCMD_SHIFT) | WDC_NVME_CRASH_DUMP_SIZE_CMD), + &crash_dump_length); + if (ret == -1) { + return -1; + } + if (crash_dump_length == 0) { + fprintf(stderr, "INFO : WDC: Crash dump is empty\n"); + } else { + ret = wdc_do_dump(fd, WDC_NVME_CRASH_DUMP_OPCODE, crash_dump_length, + crash_dump_length, + (WDC_NVME_CRASH_DUMP_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | + WDC_NVME_CRASH_DUMP_CMD, crash_dump_length, file); + if (ret == 0) + ret = wdc_do_clear_dump(fd, opcode, cdw12); + } + return ret; +} + +static int wdc_crash_dump(int fd, char *file) +{ + char f[PATH_MAX] = {0}; + + if (file != NULL) { + strncpy(f, file, PATH_MAX); + } + if (wdc_get_serial_name(fd, f, PATH_MAX, "crash_dump") == -1) { + fprintf(stderr, "ERROR : WDC : failed to generate file name\n"); + return -1; + } + return wdc_do_crash_dump(fd, f); +} + +static int wdc_do_drive_log(int fd, char *file) +{ + int ret; + __u8 *drive_log_data; + __u32 drive_log_length; + struct nvme_admin_cmd admin_cmd; + + ret = wdc_dump_length(fd, WDC_NVME_DRIVE_LOG_SIZE_OPCODE, + WDC_NVME_DRIVE_LOG_SIZE_NDT, + (WDC_NVME_DRIVE_LOG_SIZE_SUBCMD << + WDC_NVME_SUBCMD_SHIFT | WDC_NVME_DRIVE_LOG_SIZE_CMD), + &drive_log_length); + if (ret == -1) { + return -1; + } + + drive_log_data = (__u8 *) malloc(sizeof (__u8) * drive_log_length); + if (drive_log_data == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + + memset(drive_log_data, 0, sizeof (__u8) * drive_log_length); + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = WDC_NVME_DRIVE_LOG_OPCODE; + admin_cmd.addr = (__u64) drive_log_data; + admin_cmd.data_len = drive_log_length; + admin_cmd.cdw10 = drive_log_length; + admin_cmd.cdw12 = ((WDC_NVME_DRIVE_LOG_SUBCMD << + WDC_NVME_SUBCMD_SHIFT) | WDC_NVME_DRIVE_LOG_SIZE_CMD); + + ret = nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD, &admin_cmd); + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), + ret); + if (ret == 0) { + ret = wdc_create_log_file(file, drive_log_data, drive_log_length); + } + free(drive_log_data); + return ret; +} + +static int wdc_drive_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char *desc = "Capture Drive Log."; + char *file = "Output file pathname."; + char f[PATH_MAX] = {0}; + int fd; + struct config { + char *file; + }; + + struct config cfg = { + .file = NULL + }; + + const struct argconfig_commandline_options command_line_options[] = { + {"output-file", 'o', "FILE", CFG_STRING, &cfg.file, required_argument, file}, + { NULL, '\0', NULL, CFG_NONE, NULL, no_argument, desc}, + {NULL} + }; + + fd = parse_and_open(argc, argv, desc, command_line_options, NULL, 0); + wdc_check_device(fd); + if (cfg.file != NULL) { + strncpy(f, cfg.file, PATH_MAX); + } + if (wdc_get_serial_name(fd, f, PATH_MAX, "drive_log") == -1) { + fprintf(stderr, "ERROR : WDC : failed to generate file name\n"); + return -1; + } + return wdc_do_drive_log(fd, f); +} + +static int wdc_get_crash_dump(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char *desc = "Get Crash Dump."; + char *file = "Output file pathname."; + int fd; + int ret; + struct config { + char *file; + }; + + struct config cfg = { + .file = NULL, + }; + + const struct argconfig_commandline_options command_line_options[] = { + {"output-file", 'o', "FILE", CFG_STRING, &cfg.file, required_argument, file}, + { NULL, '\0', NULL, CFG_NONE, NULL, no_argument, desc}, + {NULL} + }; + + fd = parse_and_open(argc, argv, desc, command_line_options, NULL, 0); + wdc_check_device(fd); + ret = wdc_crash_dump(fd, cfg.file); + if (ret != 0) { + fprintf(stderr, "ERROR : WDC : failed to read crash dump\n"); + } + return ret; +} + +static void wdc_do_id_ctrl(__u8 *vs, struct json_object *root) +{ + char vsn[24] = {0}; + int base = 3072; + int vsn_start = 3081; + + memcpy(vsn, &vs[vsn_start - base], sizeof(vsn)); + if (root) + json_object_add_value_string(root, "wdc vsn", strlen(vsn) > 1 ? vsn : "NULL"); + else + printf("wdc vsn : %s\n", strlen(vsn) > 1 ? vsn : "NULL"); +} + +static int wdc_id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return __id_ctrl(argc, argv, cmd, plugin, wdc_do_id_ctrl); +} + +static const char* wdc_purge_mon_status_to_string(__u32 status) +{ + const char *str; + + switch (status) { + case WDC_NVME_PURGE_STATE_IDLE: + str = "Purge State Idle."; + break; + case WDC_NVME_PURGE_STATE_DONE: + str = "Purge State Done."; + break; + case WDC_NVME_PURGE_STATE_BUSY: + str = "Purge State Busy."; + break; + case WDC_NVME_PURGE_STATE_REQ_PWR_CYC: + str = "Purge Operation resulted in an error that requires " + "power cycle."; + break; + case WDC_NVME_PURGE_STATE_PWR_CYC_PURGE: + str = "The previous purge operation was interrupted by a power " + "cycle\nor reset interruption. Other commands may be " + "rejected until\nPurge Execute is issued and " + "completed."; + break; + default: + str = "Unknown."; + } + return str; +} + +static int wdc_purge(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + char *desc = "Send a Purge command."; + char *err_str; + int fd; + int ret; + struct nvme_passthru_cmd admin_cmd; + const struct argconfig_commandline_options command_line_options[] = { + { NULL, '\0', NULL, CFG_NONE, NULL, no_argument, desc }, + {NULL} + }; + + err_str = ""; + memset(&admin_cmd, 0, sizeof (admin_cmd)); + admin_cmd.opcode = WDC_NVME_PURGE_CMD_OPCODE; + + fd = parse_and_open(argc, argv, desc, command_line_options, NULL, 0); + wdc_check_device(fd); + ret = nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD, &admin_cmd); + if (ret > 0) { + switch (ret) { + case WDC_NVME_PURGE_CMD_SEQ_ERR: + err_str = "ERROR : WDC : Cannot execute purge, " + "Purge operation is in progress.\n"; + break; + case WDC_NVME_PURGE_INT_DEV_ERR: + err_str = "ERROR : WDC : Internal Device Error.\n"; + break; + default: + err_str = "ERROR : WDC\n"; + } + } + fprintf(stderr, "%s", err_str); + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + return ret; +} + +static int wdc_purge_monitor(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + char *desc = "Send a Purge Monitor command."; + int fd; + int ret; + __u8 output[WDC_NVME_PURGE_MONITOR_DATA_LEN]; + double progress_percent; + struct nvme_passthru_cmd admin_cmd; + struct wdc_nvme_purge_monitor_data *mon; + const struct argconfig_commandline_options command_line_options[] = { + { NULL, '\0', NULL, CFG_NONE, NULL, no_argument, desc }, + {NULL} + }; + + memset(output, 0, sizeof (output)); + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = WDC_NVME_PURGE_MONITOR_OPCODE; + admin_cmd.addr = (__u64) output; + admin_cmd.data_len = WDC_NVME_PURGE_MONITOR_DATA_LEN; + admin_cmd.cdw10 = WDC_NVME_PURGE_MONITOR_CMD_CDW10; + admin_cmd.timeout_ms = WDC_NVME_PURGE_MONITOR_TIMEOUT; + + fd = parse_and_open(argc, argv, desc, command_line_options, NULL, 0); + wdc_check_device(fd); + ret = nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD, &admin_cmd); + if (ret == 0) { + mon = (struct wdc_nvme_purge_monitor_data *) output; + printf("Purge state = 0x%0x\n", admin_cmd.result); + printf("%s\n", wdc_purge_mon_status_to_string(admin_cmd.result)); + if (admin_cmd.result == WDC_NVME_PURGE_STATE_BUSY) { + progress_percent = + ((double)le32_to_cpu(mon->entire_progress_current) * 100) / + le32_to_cpu(mon->entire_progress_total); + printf("Purge Progress = %f%%\n", progress_percent); + } + } + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + return ret; +} + +static void wdc_print_log_normal(struct wdc_ssd_perf_stats *perf) +{ + printf(" Performance Statistics :- \n"); + printf(" Host Read Commands %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->hr_cmds)); + printf(" Host Read Blocks %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->hr_blks)); + printf(" Average Read Size %20lf\n", + safe_div_fp((le64_to_cpu(perf->hr_blks)), (le64_to_cpu(perf->hr_cmds)))); + printf(" Host Read Cache Hit Commands %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->hr_ch_cmds)); + printf(" Host Read Cache Hit_Percentage %20"PRIu64"%%\n", + (uint64_t) calc_percent(le64_to_cpu(perf->hr_ch_cmds), le64_to_cpu(perf->hr_cmds))); + printf(" Host Read Cache Hit Blocks %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->hr_ch_blks)); + printf(" Average Read Cache Hit Size %20f\n", + safe_div_fp((le64_to_cpu(perf->hr_ch_blks)), (le64_to_cpu(perf->hr_ch_cmds)))); + printf(" Host Read Commands Stalled %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->hr_st_cmds)); + printf(" Host Read Commands Stalled Percentage %20"PRIu64"%%\n", + (uint64_t)calc_percent((le64_to_cpu(perf->hr_st_cmds)), le64_to_cpu(perf->hr_cmds))); + printf(" Host Write Commands %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->hw_cmds)); + printf(" Host Write Blocks %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->hw_blks)); + printf(" Average Write Size %20f\n", + safe_div_fp((le64_to_cpu(perf->hw_blks)), (le64_to_cpu(perf->hw_cmds)))); + printf(" Host Write Odd Start Commands %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->hw_os_cmds)); + printf(" Host Write Odd Start Commands Percentage %20"PRIu64"%%\n", + (uint64_t)calc_percent((le64_to_cpu(perf->hw_os_cmds)), (le64_to_cpu(perf->hw_cmds)))); + printf(" Host Write Odd End Commands %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->hw_oe_cmds)); + printf(" Host Write Odd End Commands Percentage %20"PRIu64"%%\n", + (uint64_t)calc_percent((le64_to_cpu(perf->hw_oe_cmds)), (le64_to_cpu((perf->hw_cmds))))); + printf(" Host Write Commands Stalled %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->hw_st_cmds)); + printf(" Host Write Commands Stalled Percentage %20"PRIu64"%%\n", + (uint64_t)calc_percent((le64_to_cpu(perf->hw_st_cmds)), (le64_to_cpu(perf->hw_cmds)))); + printf(" NAND Read Commands %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->nr_cmds)); + printf(" NAND Read Blocks Commands %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->nr_blks)); + printf(" Average NAND Read Size %20f\n", + safe_div_fp((le64_to_cpu(perf->nr_blks)), (le64_to_cpu((perf->nr_cmds))))); + printf(" Host Write Odd Start Commands %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->hw_os_cmds)); + printf(" Nand Write Commands %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->nw_cmds)); + printf(" NAND Write Blocks %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->nw_blks)); + printf(" Average NAND Write Size %20f\n", + safe_div_fp((le64_to_cpu(perf->nw_blks)), (le64_to_cpu(perf->nw_cmds)))); + printf(" NAND Read Before Write %20"PRIu64"\n", + (uint64_t)le64_to_cpu(perf->nrbw)); +} + +static void wdc_print_log_json(struct wdc_ssd_perf_stats *perf) +{ + struct json_object *root; + + root = json_create_object(); + json_object_add_value_int(root, "Host Read Commands", le64_to_cpu(perf->hr_cmds)); + json_object_add_value_int(root, "Host Read Blocks", le64_to_cpu(perf->hr_blks)); + json_object_add_value_int(root, "Average Read Size", + safe_div_fp((le64_to_cpu(perf->hr_blks)), (le64_to_cpu(perf->hr_cmds)))); + json_object_add_value_int(root, "Host Read Cache Hit Commands", + (uint64_t)le64_to_cpu(perf->hr_ch_cmds)); + json_object_add_value_int(root, "Host Read Cache Hit Percentage", + (uint64_t) calc_percent(le64_to_cpu(perf->hr_ch_cmds), le64_to_cpu(perf->hr_cmds))); + json_object_add_value_int(root, "Host Read Cache Hit Blocks", + (uint64_t)le64_to_cpu(perf->hr_ch_blks)); + json_object_add_value_int(root, "Average Read Cache Hit Size", + safe_div_fp((le64_to_cpu(perf->hr_ch_blks)), (le64_to_cpu(perf->hr_ch_cmds)))); + json_object_add_value_int(root, "Host Read Commands Stalled", + (uint64_t)le64_to_cpu(perf->hr_st_cmds)); + json_object_add_value_int(root, "Host Read Commands Stalled Percentage", + (uint64_t)calc_percent((le64_to_cpu(perf->hr_st_cmds)), le64_to_cpu(perf->hr_cmds))); + json_object_add_value_int(root, "Host Write Commands", + (uint64_t)le64_to_cpu(perf->hw_cmds)); + json_object_add_value_int(root, "Host Write Blocks", + (uint64_t)le64_to_cpu(perf->hw_blks)); + json_object_add_value_int(root, "Average Write Size", + safe_div_fp((le64_to_cpu(perf->hw_blks)), (le64_to_cpu(perf->hw_cmds)))); + json_object_add_value_int(root, "Host Write Odd Start Commands", + (uint64_t)le64_to_cpu(perf->hw_os_cmds)); + json_object_add_value_int(root, "Host Write Odd Start Commands Percentage", + (uint64_t)calc_percent((le64_to_cpu(perf->hw_os_cmds)), (le64_to_cpu(perf->hw_cmds)))); + json_object_add_value_int(root, "Host Write Odd End Commands", + (uint64_t)le64_to_cpu(perf->hw_oe_cmds)); + json_object_add_value_int(root, "Host Write Odd End Commands Percentage", + (uint64_t)calc_percent((le64_to_cpu(perf->hw_oe_cmds)), (le64_to_cpu((perf->hw_cmds))))); + json_object_add_value_int(root, "Host Write Commands Stalled", + (uint64_t)le64_to_cpu(perf->hw_st_cmds)); + json_object_add_value_int(root, "Host Write Commands Stalled Percentage", + (uint64_t)calc_percent((le64_to_cpu(perf->hw_st_cmds)), (le64_to_cpu(perf->hw_cmds)))); + json_object_add_value_int(root, "NAND Read Commands", + (uint64_t)le64_to_cpu(perf->nr_cmds)); + json_object_add_value_int(root, "NAND Read Blocks Commands", + (uint64_t)le64_to_cpu(perf->nr_blks)); + json_object_add_value_int(root, "Average NAND Read Size", + safe_div_fp((le64_to_cpu(perf->nr_blks)), (le64_to_cpu((perf->nr_cmds))))); + json_object_add_value_int(root, "Host Write Odd Start Commands", + (uint64_t)le64_to_cpu(perf->hw_os_cmds)); + json_object_add_value_int(root, "Nand Write Commands", + (uint64_t)le64_to_cpu(perf->nw_cmds)); + json_object_add_value_int(root, "NAND Write Blocks", + (uint64_t)le64_to_cpu(perf->nw_blks)); + json_object_add_value_int(root, "Average NAND Write Size", + safe_div_fp((le64_to_cpu(perf->nw_blks)), (le64_to_cpu(perf->nw_cmds)))); + json_object_add_value_int(root, "NAND Read Before Writen", + (uint64_t)le64_to_cpu(perf->nrbw)); + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static int wdc_print_log(struct wdc_ssd_perf_stats *perf, int fmt) +{ + if (!perf) { + fprintf(stderr, "ERROR : WDC : Invalid buffer to read perf stats\n"); + return -1; + } + switch (fmt) { + case NORMAL: + wdc_print_log_normal(perf); + break; + case JSON: + wdc_print_log_json(perf); + break; + } + return 0; +} + +static int wdc_smart_log_add(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char *desc = "Retrieve additional performance statistics."; + char *interval = "Interval to read the statistics from [1, 15]."; + __u8 *p; + __u8 *data; + int i; + int fd; + int ret; + int fmt = -1; + int skip_cnt = 4; + int total_subpages; + struct wdc_log_page_header *l; + struct wdc_log_page_subpage_header *sph; + struct wdc_ssd_perf_stats *perf; + + struct config { + uint8_t interval; + int vendor_specific; + char *output_format; + }; + + struct config cfg = { + .interval = 14, + .output_format = "normal", + }; + + const struct argconfig_commandline_options command_line_options[] = { + {"interval", 'i', "NUM", CFG_POSITIVE, &cfg.interval, required_argument, interval}, + {"output-format", 'o', "FMT", CFG_STRING, &cfg.output_format, required_argument, "Output Format: normal|json" }, + {NULL} + }; + + fd = parse_and_open(argc, argv, desc, command_line_options, NULL, 0); + wdc_check_device(fd); + fmt = validate_output_format(cfg.output_format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + if (cfg.interval < 1 || cfg.interval > 15) { + fprintf(stderr, "ERROR : WDC : interval out of range [1-15]\n"); + return -1; + } + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_ADD_LOG_BUF_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + memset(data, 0, sizeof (__u8) * WDC_ADD_LOG_BUF_LEN); + + ret = nvme_get_log(fd, 0x01, WDC_NVME_ADD_LOG_OPCODE, WDC_ADD_LOG_BUF_LEN, data); + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + if (ret == 0) { + l = (struct wdc_log_page_header*)data; + total_subpages = l->num_subpages + WDC_NVME_GET_STAT_PERF_INTERVAL_LIFETIME - 1; + for (i = 0, p = data + skip_cnt; i < total_subpages; i++, p += skip_cnt) { + sph = (struct wdc_log_page_subpage_header *) p; + if (sph->spcode == WDC_GET_LOG_PAGE_SSD_PERFORMANCE) { + if (sph->pcset == cfg.interval) { + perf = (struct wdc_ssd_perf_stats *) (p + 4); + ret = wdc_print_log(perf, fmt); + break; + } + } + skip_cnt = le32_to_cpu(sph->subpage_length) + 4; + } + if (ret) { + fprintf(stderr, "ERROR : WDC : Unable to read data from buffer\n"); + } + } + free(data); + return ret; +} diff --git a/wdc-nvme.h b/wdc-nvme.h new file mode 100644 index 00000000..a3574c1b --- /dev/null +++ b/wdc-nvme.h @@ -0,0 +1,23 @@ +#undef CMD_INC_FILE +#define CMD_INC_FILE wdc-nvme + +#if !defined(WDC_NVME) || defined(CMD_HEADER_MULTI_READ) +#define WDC_NVME + +#include "cmd.h" + +PLUGIN(NAME("wdc", "Western Digital vendor specific extensions"), + COMMAND_LIST( + ENTRY("cap-diag", "WDC Capture-Diagnostics", wdc_cap_diag) + ENTRY("drive-log", "WDC Drive Log", wdc_drive_log) + ENTRY("get-crash-dump", "WDC Crash Dump", wdc_get_crash_dump) + ENTRY("id-ctrl", "WDC identify controller", wdc_id_ctrl) + ENTRY("purge", "WDC Purge", wdc_purge) + ENTRY("purge-monitor", "WDC Purge Monitor", wdc_purge_monitor) + ENTRY("smart-log-add", "WDC Additional Smart Log", wdc_smart_log_add) + ) +); + +#endif + +#include "define_cmd.h"