[PATCH 3/4] nvme-cli: Western Digital/HGST plug-in.

Chaitanya Kulkarni chaitanya.kulkarni at hgst.com
Tue Feb 28 18:14:09 PST 2017


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 <chaitanya.kulkarni at hgst.com>
---
 Makefile   |   2 +-
 wdc-nvme.c | 897 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 wdc-nvme.h |  23 ++
 3 files changed, 921 insertions(+), 1 deletion(-)
 create mode 100644 wdc-nvme.c
 create mode 100644 wdc-nvme.h

diff --git a/Makefile b/Makefile
index 2d25edc..414c039 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 0000000..8028f61
--- /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 <chaitanya.kulkarni at hgst.com>,
+ *           Dong Ho <dong.ho at hgst.com>
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#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 <sys/ioctl.h>
+#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 0000000..a3574c1
--- /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"
-- 
1.9.1




More information about the Linux-nvme mailing list