#include "nvme-ioctl.h"
#include "plugin.h"
#include "json.h"
+#include "nvme-status.h"
#include "argconfig.h"
#include "suffix.h"
#define WDC_NVME_LOG_SIZE_DATA_LEN 0x08
#define WDC_NVME_LOG_SIZE_HDR_LEN 0x08
+/* Enclosure */
+#define WDC_OPENFLEX_MI_DEVICE_MODEL "OpenFlex"
+#define WDC_RESULT_MORE_DATA 0x80000000
+#define WDC_RESULT_NOT_AVAILABLE 0x7FFFFFFF
+
/* Device Config */
#define WDC_NVME_VID 0x1c58
#define WDC_NVME_VID_2 0x1b96
/* VU Opcodes */
#define WDC_DE_VU_READ_SIZE_OPCODE 0xC0
#define WDC_DE_VU_READ_BUFFER_OPCODE 0xC2
+#define WDC_NVME_ADMIN_ENC_MGMT_SND 0xC9
+#define WDC_NVME_ADMIN_ENC_MGMT_RCV 0xCA
#define WDC_DE_FILE_HEADER_SIZE 4
#define WDC_DE_FILE_OFFSET_SIZE 2
#define WDC_DE_DESTN_SPI 1
#define WDC_DE_DUMPTRACE_DESTINATION 6
+#define NVME_ID_CTRL_MODEL_NUMBER_SIZE 40
+#define NVME_ID_CTRL_SERIAL_NUMBER_SIZE 20
+
+/* Enclosure log */
+#define WDC_NVME_ENC_LOG_SIZE_CHUNK 0x1000
+#define WDC_NVME_ENC_NIC_LOG_SIZE 0x400000
+
+/* Enclosure nic crash dump get-log id */
+#define WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_1 0xD1
+#define WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_2 0xD2
+#define WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_3 0xD3
+#define WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_4 0xD4
+#define WDC_ENC_CRASH_DUMP_ID 0xE4
+#define WDC_ENC_LOG_DUMP_ID 0xE2
+
typedef enum _NVME_FEATURES_SELECT
{
FS_CURRENT = 0,
struct plugin *plugin);
static int wdc_vs_temperature_stats(int argc, char **argv, struct command *command,
struct plugin *plugin);
+static __u64 wdc_get_enc_drive_capabilities(int fd);
+static int wdc_enc_get_nic_log(int fd, __u8 log_id, __u32 xfer_size, __u32 data_len, FILE *out);
+static int wdc_enc_submit_move_data(int fd, char *cmd, int len, int xfer_size, FILE *out, int data_id, int cdw14, int cdw15);
/* Drive log data size */
struct wdc_log_size {
return ret;
}
+static int wdc_get_vendor_id(int fd, uint32_t *vendor_id)
+{
+ 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;
+ }
+
+ *vendor_id = (uint32_t) ctrl.vid;
+
+ return ret;
+}
+
+static bool wdc_check_power_of_2(int num)
+{
+ int first_set = 1;
+
+ if (num == 0)
+ return false;
+
+ while ((first_set & num) == 0)
+ first_set <<= 1;
+
+ return (~first_set & num) ? false : true;
+}
+
+static int wdc_get_model_number(int fd, char *model)
+{
+ int ret,i;
+ 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;
+ }
+
+ memcpy(model,ctrl.mn,NVME_ID_CTRL_MODEL_NUMBER_SIZE);
+ /* get rid of the padded spaces */
+ i = NVME_ID_CTRL_MODEL_NUMBER_SIZE-1;
+ while (model[i] == ' ') i--;
+ model[i+1]=0;
+
+ return ret;
+}
+
static bool wdc_check_device(int fd)
{
int ret;
bool supported;
- uint32_t read_device_id, read_vendor_id;
+ uint32_t read_device_id = -1, read_vendor_id = -1;
ret = wdc_get_pci_ids(&read_device_id, &read_vendor_id);
- if (ret < 0)
- return false;
+ if (ret < 0) {
+ /* Use the identify nvme command to get vendor id due to NVMeOF device. */
+ if (wdc_get_vendor_id(fd, &read_vendor_id) < 0)
+ return false;
+ }
supported = false;
return supported;
}
+static bool wdc_enc_check_model(int fd)
+{
+ int ret;
+ bool supported;
+ char model[NVME_ID_CTRL_MODEL_NUMBER_SIZE+1];
+
+ ret = wdc_get_model_number(fd, model);
+ if (ret < 0)
+ return false;
+
+ supported = false;
+ model[NVME_ID_CTRL_MODEL_NUMBER_SIZE] = 0; /* forced termination */
+ if (strstr(model,WDC_OPENFLEX_MI_DEVICE_MODEL) != NULL)
+ supported = true;
+ else
+ fprintf(stderr, "ERROR : WDC: unsupported WDC enclosure, Model = %s\n",model);
+
+ return supported;
+}
+
static __u64 wdc_get_drive_capabilities(int fd) {
int ret;
- uint32_t read_device_id, read_vendor_id;
+ uint32_t read_device_id = -1, read_vendor_id = -1;
__u64 capabilities = 0;
ret = wdc_get_pci_ids(&read_device_id, &read_vendor_id);
if (ret < 0)
+ {
+ if (wdc_get_vendor_id(fd, &read_vendor_id) < 0)
+ return capabilities;
+ }
+
+ /* below check condition is added due in NVMeOF device we dont have device_id so we need to use only vendor_id*/
+ if (read_device_id == -1 && read_vendor_id != -1)
+ {
+ capabilities = wdc_get_enc_drive_capabilities(fd);
return capabilities;
+ }
switch (read_vendor_id) {
case WDC_NVME_VID:
/* FALLTHRU */
case WDC_NVME_ZN440_DEV_ID:
/* FALLTHRU */
- case WDC_NVME_SN440_DEV_ID:
- /* FALLTHRU */
+ case WDC_NVME_SN440_DEV_ID:
+ /* FALLTHRU */
case WDC_NVME_SN7GC_DEV_ID:
case WDC_NVME_SN7GC_DEV_ID_1:
case WDC_NVME_SN7GC_DEV_ID_2:
return capabilities;
}
+static __u64 wdc_get_enc_drive_capabilities(int fd) {
+ int ret;
+ uint32_t read_vendor_id;
+ __u64 capabilities = 0;
+
+ ret = wdc_get_vendor_id(fd, &read_vendor_id);
+ if (ret < 0)
+ return capabilities;
+
+ switch (read_vendor_id) {
+ case WDC_NVME_VID:
+ capabilities = (WDC_DRIVE_CAP_CAP_DIAG | WDC_DRIVE_CAP_INTERNAL_LOG | WDC_DRIVE_CAP_CLEAR_PCIE |
+ WDC_DRIVE_CAP_DRIVE_LOG | WDC_DRIVE_CAP_CRASH_DUMP | WDC_DRIVE_CAP_PFAIL_DUMP);
+
+ /* verify the 0xCA log page is supported */
+ if (wdc_nvme_check_supported_log_page(fd, WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE) == true)
+ capabilities |= WDC_DRIVE_CAP_CA_LOG_PAGE;
+
+ /* verify the 0xC1 log page is supported */
+ if (wdc_nvme_check_supported_log_page(fd, WDC_NVME_ADD_LOG_OPCODE) == true)
+ capabilities |= WDC_DRIVE_CAP_C1_LOG_PAGE;
+ break;
+ case WDC_NVME_VID_2:
+ capabilities = (WDC_DRIVE_CAP_CAP_DIAG | WDC_DRIVE_CAP_INTERNAL_LOG |
+ WDC_DRIVE_CAP_DRIVE_STATUS | WDC_DRIVE_CAP_CLEAR_ASSERT |
+ WDC_DRIVE_CAP_RESIZE | WDC_DRIVE_CAP_CLEAR_PCIE |
+ WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY);
+
+ /* verify the 0xCB log page is supported */
+ if (wdc_nvme_check_supported_log_page(fd, WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID) == true)
+ capabilities |= WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY;
+
+ /* verify the 0xCA log page is supported */
+ if (wdc_nvme_check_supported_log_page(fd, WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE) == true)
+ capabilities |= WDC_DRIVE_CAP_CA_LOG_PAGE;
+
+ /* verify the 0xD0 log page is supported */
+ if (wdc_nvme_check_supported_log_page(fd, WDC_NVME_GET_VU_SMART_LOG_OPCODE) == true)
+ capabilities |= WDC_DRIVE_CAP_D0_LOG_PAGE;
+ break;
+ case WDC_NVME_SNDK_VID:
+ capabilities = WDC_DRIVE_CAP_DRIVE_ESSENTIALS;
+ break;
+ default:
+ capabilities = 0;
+ }
+
+ return capabilities;
+}
+
static int wdc_get_serial_name(int fd, char *file, size_t len, const char *suffix)
{
int i;
printf("capabilities : Supported\n");
return 0;
}
+
+static int wdc_enc_get_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ char *desc = "Get Enclosure Log.";
+ char *file = "Output file pathname.";
+ char *size = "Data retrieval transfer size.";
+ char *log = "Enclosure Log Page ID.";
+ FILE *output_fd;
+ int xfer_size = 0;
+ int fd;
+ int len;
+ int err;
+
+ struct config {
+ char *file;
+ __u32 xfer_size;
+ __u32 log_id;
+ };
+
+ struct config cfg = {
+ .file = NULL,
+ .xfer_size = 0,
+ .log_id = 0xffffffff,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FILE("output-file", 'o', &cfg.file, file),
+ OPT_UINT("transfer-size", 's', &cfg.xfer_size, size),
+ OPT_UINT("log-id", 'l', &cfg.log_id, log),
+ OPT_END()
+ };
+
+ err = fd = parse_and_open(argc, argv, desc, opts);
+ if (fd < 0) {
+ goto ret;
+ }
+
+ if (!wdc_enc_check_model(fd)) {
+ err = -EINVAL;
+ goto closed_fd;
+ }
+
+ if (cfg.log_id > 0xff) {
+ fprintf(stderr, "Invalid log identifier: %d. Valid 0xd1, 0xd2, 0xd3, 0xd4, 0xe2, 0xe4\n", cfg.log_id);
+ goto closed_fd;
+ }
+
+ if (cfg.xfer_size != 0) {
+ xfer_size = cfg.xfer_size;
+ if (!wdc_check_power_of_2(cfg.xfer_size)) {
+ fprintf(stderr, "%s: ERROR : xfer-size (%d) must be a power of 2\n", __func__, cfg.xfer_size);
+ err = -EINVAL;
+ goto closed_fd;
+ }
+ }
+
+ // Log IDs are only for specific enclosures
+ if (cfg.log_id) {
+ xfer_size = (xfer_size) ? xfer_size : WDC_NVME_ENC_LOG_SIZE_CHUNK;
+ len = cfg.file==NULL?0:strlen(cfg.file);
+ if (len > 0) {
+ output_fd = fopen(cfg.file,"wb");
+ if (output_fd == 0) {
+ fprintf(stderr, "%s: ERROR : opening:%s : %s\n", __func__,cfg.file, strerror(errno));
+ err = -EINVAL;
+ goto closed_fd;
+ }
+ } else {
+ output_fd = stdout;
+ }
+ if (cfg.log_id == WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_1 || cfg.log_id == WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_2
+ || cfg.log_id == WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_3 || cfg.log_id == WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_4) {
+ fprintf(stderr, "args - sz:%x logid:%x of:%s\n",xfer_size,cfg.log_id,cfg.file);
+ err = wdc_enc_get_nic_log(fd, cfg.log_id, xfer_size, WDC_NVME_ENC_NIC_LOG_SIZE, output_fd);
+ } else {
+ fprintf(stderr, "args - sz:%x logid:%x of:%s\n",xfer_size,cfg.log_id,cfg.file);
+ err = wdc_enc_submit_move_data(fd, NULL, 0, xfer_size, output_fd, cfg.log_id, 0, 0);
+ }
+
+ if (err == WDC_RESULT_NOT_AVAILABLE) {
+ fprintf(stderr, "No Log/Crashdump available\n");
+ err = 0;
+ } else if (err) {
+ fprintf(stderr, "ERROR:0x%x Failed to collect log-id:%x \n",err, cfg.log_id);
+ }
+ }
+closed_fd:
+ close(fd);
+ret:
+ return nvme_status_to_errno(err, false);
+}
+
+static int wdc_enc_submit_move_data(int fd, char *cmd, int len, int xfer_size, FILE *out, int log_id, int cdw14, int cdw15)
+{
+ struct timespec time;
+ uint32_t response_size, more;
+ int err;
+ int handle;
+ uint32_t offset = 0;
+ char *buf;
+
+ buf = (char *)malloc(sizeof(__u8) * xfer_size);
+ if (buf == NULL) {
+ fprintf(stderr, "%s: ERROR : malloc : %s\n", __func__, strerror(errno));
+ return -1;
+ }
+ // send something no matter what
+ cmd = (len) ? cmd : buf;
+ len = (len) ? len : 0x20;
+
+ struct nvme_admin_cmd nvme_cmd = {
+ .opcode = WDC_NVME_ADMIN_ENC_MGMT_SND,
+ // ?
+ .nsid = 0,
+ .addr = (__u64)(uintptr_t) cmd,
+ .data_len = ((len + sizeof(uint32_t) - 1)/sizeof(uint32_t)) * sizeof(uint32_t),
+ .cdw10 = len,
+ .cdw12 = log_id,
+ .cdw13 = 0,
+ .cdw14 = cdw14,
+ .cdw15 = cdw15,
+ };
+
+ clock_gettime(CLOCK_REALTIME, &time);
+ srand(time.tv_nsec);
+ handle = random(); // Handle to associate send request with receive request
+ nvme_cmd.cdw11 = handle;
+
+#ifdef WDC_NVME_CLI_DEBUG
+ unsigned char *d = (unsigned char*) nvme_cmd.addr;
+ unsigned char *md = (unsigned char*) nvme_cmd.metadata;
+ printf("NVME_ADMIN_COMMAND:\n" \
+ "opcode: 0x%02x, flags: 0x%02x, rsvd: 0x%04x, nsid: 0x%08x, cdw2: 0x%08x, cdw3: 0x%08x, " \
+ "metadata_len: 0x%08x, data_len: 0x%08x, cdw10: 0x%08x, cdw11: 0x%08x, cdw12: 0x%08x, " \
+ "cdw13: 0x%08x, cdw14: 0x%08x, cdw15: 0x%08x, timeout_ms: 0x%08x, result: 0x%08x, " \
+ "metadata: %s, " \
+ "data: %s\n", \
+ nvme_cmd.opcode, nvme_cmd.flags, nvme_cmd.rsvd1, nvme_cmd.nsid, nvme_cmd.cdw2, nvme_cmd.cdw3, \
+ nvme_cmd.metadata_len, nvme_cmd.data_len, nvme_cmd.cdw10, nvme_cmd.cdw11, nvme_cmd.cdw12, \
+ nvme_cmd.cdw13, nvme_cmd.cdw14, nvme_cmd.cdw15, nvme_cmd.timeout_ms, nvme_cmd.result,
+ md, \
+ d);
+#endif
+ nvme_cmd.result = 0;
+ err = nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD, &nvme_cmd);
+ if (err == NVME_SC_INTERNAL) {
+ fprintf(stderr, "%s: WARNING : WDC : No log ID:x%x available\n",
+ __func__, log_id);
+ }
+ else if (err != 0) {
+ fprintf(stderr, "%s: ERROR : WDC : NVMe Snd Mgmt Status:%s(x%x)\n",
+ __func__, nvme_status_to_string(err), err );
+ } else {
+ if (nvme_cmd.result == WDC_RESULT_NOT_AVAILABLE)
+ {
+ free(buf);
+ return WDC_RESULT_NOT_AVAILABLE;
+ }
+
+ do {
+ /* Sent request, now go retrieve response */
+ nvme_cmd.flags = 0;
+ nvme_cmd.opcode = WDC_NVME_ADMIN_ENC_MGMT_RCV;
+ nvme_cmd.addr = (__u64)(uintptr_t) buf;
+ nvme_cmd.data_len = xfer_size;
+ nvme_cmd.cdw10 = xfer_size / sizeof(uint32_t);
+ nvme_cmd.cdw11 = handle;
+ nvme_cmd.cdw12 = log_id;
+ nvme_cmd.cdw13 = offset / sizeof(uint32_t);
+ nvme_cmd.cdw14 = cdw14;
+ nvme_cmd.cdw15 = cdw15;
+ nvme_cmd.result = 0; // returned result !=0 indicates more data available
+ err = nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD, &nvme_cmd);
+ if (err != 0) {
+ more = 0;
+ fprintf(stderr, "%s: ERROR : WDC : NVMe Rcv Mgmt Status:%s(x%x)\n",
+ __func__, nvme_status_to_string(err), err);
+ } else {
+ more = nvme_cmd.result & WDC_RESULT_MORE_DATA;
+ response_size = nvme_cmd.result & ~WDC_RESULT_MORE_DATA;
+ fwrite(buf, response_size, 1, out);
+ offset += response_size;
+ if (more && (response_size & (sizeof(uint32_t)-1))) {
+ fprintf(stderr, "%s: ERROR : WDC : NVMe Rcv Mgmt response size:x%x not LW aligned\n",
+ __func__, response_size);
+ }
+ }
+ } while (more);
+ free(buf);
+ }
+
+ return err;
+}
+
+static int wdc_enc_get_nic_log(int fd, __u8 log_id, __u32 xfer_size, __u32 data_len, FILE *out)
+{
+ __u8 *dump_data;
+ __u32 curr_data_offset, curr_data_len;
+ int i, ret;
+ struct nvme_admin_cmd admin_cmd;
+ __u32 dump_length = data_len;
+ __u32 numd;
+ __u16 numdu, numdl;
+
+ dump_data = (__u8 *) malloc(sizeof (__u8) * dump_length);
+ if (dump_data == NULL) {
+ fprintf(stderr, "%s: ERROR : malloc : %s\n",__func__, strerror(errno));
+ return -1;
+ }
+ memset(dump_data, 0, sizeof (__u8) * dump_length);
+ memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd));
+ curr_data_offset = 0;
+ curr_data_len = xfer_size;
+ i = 0;
+
+ numd = (curr_data_len >> 2) - 1;
+ numdu = numd >> 16;
+ numdl = numd & 0xffff;
+ admin_cmd.opcode = nvme_admin_get_log_page;
+ admin_cmd.nsid = curr_data_offset;
+ admin_cmd.addr = (__u64)(uintptr_t) dump_data;
+ admin_cmd.data_len = curr_data_len;
+ admin_cmd.cdw10 = log_id | (numdl << 16);
+ admin_cmd.cdw11 = numdu;
+
+ while (curr_data_offset < data_len) {
+#ifdef WDC_NVME_CLI_DEBUG
+ fprintf(stderr, "nsid 0x%08x addr 0x%08llx, data_len 0x%08x, cdw10 0x%08x, cdw11 0x%08x, cdw12 0x%08x, cdw13 0x%08x, cdw14 0x%08x \n", admin_cmd.nsid, admin_cmd.addr, admin_cmd.data_len, admin_cmd.cdw10, admin_cmd.cdw11, admin_cmd.cdw12, admin_cmd.cdw13, admin_cmd.cdw14);
+#endif
+ ret = nvme_submit_admin_passthru(fd, &admin_cmd);
+ if (ret !=0 ) {
+ fprintf(stderr, "%s: ERROR : WDC : NVMe Status:%s(%x)\n",__func__, nvme_status_to_string(ret), ret);
+ fprintf(stderr, "%s: ERROR : WDC : Get chunk %d, size = 0x%x, offset = 0x%x, addr = 0x%lx\n",
+ __func__, i, admin_cmd.data_len, curr_data_offset, (long unsigned int)admin_cmd.addr);
+ break;
+ }
+
+ if ((curr_data_offset + xfer_size) <= data_len)
+ curr_data_len = xfer_size;
+ else
+ curr_data_len = data_len - curr_data_offset; /* last transfer */
+
+ curr_data_offset += curr_data_len;
+ numd = (curr_data_len >> 2) - 1;
+ numdu = numd >> 16;
+ numdl = numd & 0xffff;
+ admin_cmd.addr = (__u64)(uintptr_t)dump_data + (__u64)curr_data_offset;
+ admin_cmd.nsid = curr_data_offset;
+ admin_cmd.data_len = curr_data_len;
+ admin_cmd.cdw10 = log_id | (numdl << 16);
+ admin_cmd.cdw11 = numdu;
+ i++;
+ }
+ fwrite(dump_data, data_len, 1, out);
+ free(dump_data);
+ return ret;
+}