From 52dc35073a874ff5f08854b964d8caad7f9e5d66 Mon Sep 17 00:00:00 2001 From: Jeff Lien Date: Thu, 1 Oct 2020 14:15:28 -0500 Subject: [PATCH] [nvme-cli] Add support for enclosures to WDC plugin commands --- plugins/wdc/wdc-nvme.c | 432 ++++++++++++++++++++++++++++++++++++++++- plugins/wdc/wdc-nvme.h | 1 + 2 files changed, 427 insertions(+), 6 deletions(-) diff --git a/plugins/wdc/wdc-nvme.c b/plugins/wdc/wdc-nvme.c index 54a81076..0e912afa 100644 --- a/plugins/wdc/wdc-nvme.c +++ b/plugins/wdc/wdc-nvme.c @@ -38,6 +38,7 @@ #include "nvme-ioctl.h" #include "plugin.h" #include "json.h" +#include "nvme-status.h" #include "argconfig.h" #include "suffix.h" @@ -53,6 +54,11 @@ #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 @@ -353,6 +359,8 @@ /* 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 @@ -366,6 +374,21 @@ #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, @@ -601,6 +624,9 @@ static int wdc_vs_drive_info(int argc, char **argv, struct command *command, 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 { @@ -992,15 +1018,71 @@ free_id: 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; @@ -1015,14 +1097,44 @@ static bool wdc_check_device(int fd) 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: @@ -1081,8 +1193,8 @@ static __u64 wdc_get_drive_capabilities(int fd) { /* 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: @@ -1150,6 +1262,56 @@ static __u64 wdc_get_drive_capabilities(int fd) { 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; @@ -7055,3 +7217,261 @@ static int wdc_capabilities(int argc, char **argv, 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; +} diff --git a/plugins/wdc/wdc-nvme.h b/plugins/wdc/wdc-nvme.h index e8b4d753..740f484d 100644 --- a/plugins/wdc/wdc-nvme.h +++ b/plugins/wdc/wdc-nvme.h @@ -25,6 +25,7 @@ PLUGIN(NAME("wdc", "Western Digital vendor specific extensions"), ENTRY("drive-resize", "WDC Drive Resize", wdc_drive_resize) ENTRY("vs-fw-activate-history", "WDC Get FW Activate History", wdc_vs_fw_activate_history) ENTRY("clear-fw-activate-history", "WDC Clear FW Activate History", wdc_clear_fw_activate_history) + ENTRY("enc-get-log", "WDC Get Enclosure Log", wdc_enc_get_log) ENTRY("vs-telemetry-controller-option", "WDC Enable/Disable Controller Initiated Telemetry Log", wdc_vs_telemetry_controller_option) ENTRY("vs-error-reason-identifier", "WDC Telemetry Reason Identifier", wdc_reason_identifier) ENTRY("log-page-directory", "WDC Get Log Page Directory", wdc_log_page_directory) -- 2.50.1