diff --git a/plugins/sandisk/sandisk-nvme.c b/plugins/sandisk/sandisk-nvme.c index 2f33dffed4..06290ddd7b 100644 --- a/plugins/sandisk/sandisk-nvme.c +++ b/plugins/sandisk/sandisk-nvme.c @@ -28,12 +28,404 @@ #include "sandisk-utils.h" #include "plugins/wdc/wdc-nvme-cmds.h" +static int sndk_do_cap_telemetry_log(struct nvme_dev *dev, const char *file, + __u32 bs, int type, int data_area) +{ + struct nvme_telemetry_log *log; + size_t full_size = 0; + int err = 0, output; + __u32 host_gen = 1; + int ctrl_init = 0; + __u8 *data_ptr = NULL; + int data_written = 0, data_remaining = 0; + struct nvme_id_ctrl ctrl; + __u64 capabilities = 0; + nvme_root_t r; + + memset(&ctrl, 0, sizeof(struct nvme_id_ctrl)); + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (err) { + fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed 0x%x\n", err); + return err; + } + + if (!(ctrl.lpa & 0x8)) { + fprintf(stderr, "Telemetry log pages not supported by device\n"); + return -EINVAL; + } + + r = nvme_scan(NULL); + capabilities = sndk_get_drive_capabilities(r, dev); + + if (type == SNDK_TELEMETRY_TYPE_HOST) { + host_gen = 1; + ctrl_init = 0; + } else if (type == SNDK_TELEMETRY_TYPE_CONTROLLER) { + if (capabilities & SNDK_DRIVE_CAP_INTERNAL_LOG) { + err = sndk_check_ctrl_telemetry_option_disabled(dev); + if (err) + return err; + } + host_gen = 0; + ctrl_init = 1; + } else { + fprintf(stderr, "%s: Invalid type parameter; type = %d\n", __func__, type); + return -EINVAL; + } + + if (!file) { + fprintf(stderr, "%s: Please provide an output file!\n", __func__); + return -EINVAL; + } + + output = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (output < 0) { + fprintf(stderr, "%s: Failed to open output file %s: %s!\n", + __func__, file, strerror(errno)); + return output; + } + + if (ctrl_init) + err = nvme_get_ctrl_telemetry(dev_fd(dev), true, &log, + data_area, &full_size); + else if (host_gen) + err = nvme_get_new_host_telemetry(dev_fd(dev), &log, + data_area, &full_size); + else + err = nvme_get_host_telemetry(dev_fd(dev), &log, data_area, + &full_size); + + if (err < 0) { + perror("get-telemetry-log"); + goto close_output; + } else if (err > 0) { + nvme_show_status(err); + fprintf(stderr, "%s: Failed to acquire telemetry header!\n", __func__); + goto close_output; + } + + /* + *Continuously pull data until the offset hits the end of the last + *block. + */ + data_written = 0; + data_remaining = full_size; + data_ptr = (__u8 *)log; + + while (data_remaining) { + data_written = write(output, data_ptr, data_remaining); + + if (data_written < 0) { + data_remaining = data_written; + break; + } else if (data_written <= data_remaining) { + data_remaining -= data_written; + data_ptr += data_written; + } else { + /* Unexpected overwrite */ + fprintf(stderr, "Failure: Unexpected telemetry log overwrite\n" \ + "- data_remaining = 0x%x, data_written = 0x%x\n", + data_remaining, data_written); + break; + } + } + + if (fsync(output) < 0) { + fprintf(stderr, "ERROR: %s: fsync: %s\n", __func__, strerror(errno)); + err = -1; + } + + free(log); +close_output: + close(output); + return err; +} + +static __u32 sndk_dump_udui_data(int fd, __u32 dataLen, __u32 offset, + __u8 *dump_data) +{ + int ret; + struct nvme_passthru_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd)); + admin_cmd.opcode = SNDK_NVME_CAP_UDUI_OPCODE; + admin_cmd.nsid = 0xFFFFFFFF; + admin_cmd.addr = (__u64)(uintptr_t)dump_data; + admin_cmd.data_len = dataLen; + admin_cmd.cdw10 = ((dataLen >> 2) - 1); + admin_cmd.cdw12 = offset; + ret = nvme_submit_admin_passthru(fd, &admin_cmd, NULL); + if (ret) { + fprintf(stderr, "ERROR: SNDK: reading DUI data failed\n"); + nvme_show_status(ret); + } + + return ret; +} + +static int sndk_do_cap_udui(int fd, char *file, __u32 xfer_size, int verbose, + __u64 file_size, __u64 offset) +{ + int ret = 0; + int output; + ssize_t written = 0; + struct nvme_telemetry_log *log; + __u32 udui_log_hdr_size = sizeof(struct nvme_telemetry_log); + __u32 chunk_size = xfer_size; + __u64 total_size; + + log = (struct nvme_telemetry_log *)malloc(udui_log_hdr_size); + if (!log) { + fprintf(stderr, + "%s: ERROR: log header malloc failed : status %s, size 0x%x\n", + __func__, strerror(errno), udui_log_hdr_size); + return -1; + } + memset(log, 0, udui_log_hdr_size); + + /* get the udui telemetry and log headers */ + ret = sndk_dump_udui_data(fd, udui_log_hdr_size, 0, (__u8 *)log); + if (ret) { + fprintf(stderr, "%s: ERROR: SNDK: Get UDUI header failed\n", __func__); + nvme_show_status(ret); + goto out; + } + + total_size = (le32_to_cpu(log->dalb4) + 1) * 512; + if (offset > total_size) { + fprintf(stderr, "%s: ERROR: SNDK: offset larger than log length = 0x%"PRIx64"\n", + __func__, (uint64_t)total_size); + goto out; + } + + if (file_size && (total_size - offset) > file_size) + total_size = offset + file_size; + + log = (struct nvme_telemetry_log *)realloc(log, chunk_size); + + output = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (output < 0) { + fprintf(stderr, "%s: Failed to open output file %s: %s!\n", __func__, file, + strerror(errno)); + goto out; + } + + while (offset < total_size) { + if (chunk_size > total_size - offset) + chunk_size = total_size - offset; + ret = sndk_dump_udui_data(fd, chunk_size, offset, + ((__u8 *)log)); + if (ret) { + fprintf(stderr, + "%s: ERROR: Get UDUI failed, offset = 0x%"PRIx64", size = %u\n", + __func__, (uint64_t)offset, chunk_size); + break; + } + + /* write the dump data into the file */ + written = write(output, (void *)log, chunk_size); + if (written != chunk_size) { + fprintf(stderr, + "%s: ERROR: SNDK: Failed to flush DUI data to file!\n" \ + "- written = %zd, offset = 0x%"PRIx64", chunk_size = %u\n", + __func__, written, (uint64_t)offset, chunk_size); + ret = errno; + break; + } + + offset += chunk_size; + } + + close(output); + nvme_show_status(ret); + if (verbose) + fprintf(stderr, + "INFO: SNDK: Capture Device Unit Info log length = 0x%"PRIx64"\n", + (uint64_t)total_size); + +out: + free(log); + return ret; +} static int sndk_vs_internal_fw_log(int argc, char **argv, struct command *command, struct plugin *plugin) { + const char *desc = "Internal Firmware Log."; + const char *file = "Output file pathname."; + const char *size = "Data retrieval transfer size."; + const char *data_area = + "Data area to retrieve up to. Currently only supported on the SN340, SN640, SN730, and SN840 devices."; + const char *file_size = "Output file size. Currently only supported on the SN340 device."; + const char *offset = + "Output file data offset. Currently only supported on the SN340 device."; + const char *type = + "Telemetry type - NONE, HOST, or CONTROLLER Currently only supported on the SN530, SN640, SN730, SN740, SN810, SN840 and ZN350 devices."; + const char *verbose = "Display more debug messages."; + char f[PATH_MAX] = {0}; + char fileSuffix[PATH_MAX] = {0}; + struct nvme_dev *dev; + nvme_root_t r; + __u32 xfer_size = 0; + int telemetry_type = 0, telemetry_data_area = 0; + struct SNDK_UtilsTimeInfo timeInfo; + __u8 timeStamp[SNDK_MAX_PATH_LEN]; + __u64 capabilities = 0; + __u32 device_id, read_vendor_id; + int ret = -1; + + struct config { + char *file; + __u32 xfer_size; + int data_area; + __u64 file_size; + __u64 offset; + char *type; + bool verbose; + }; + + struct config cfg = { + .file = NULL, + .xfer_size = 0x10000, + .data_area = 0, + .file_size = 0, + .offset = 0, + .type = NULL, + .verbose = false, + }; + + OPT_ARGS(opts) = { + OPT_FILE("output-file", 'o', &cfg.file, file), + OPT_UINT("transfer-size", 's', &cfg.xfer_size, size), + OPT_UINT("data-area", 'd', &cfg.data_area, data_area), + OPT_LONG("file-size", 'f', &cfg.file_size, file_size), + OPT_LONG("offset", 'e', &cfg.offset, offset), + OPT_FILE("type", 't', &cfg.type, type), + OPT_FLAG("verbose", 'v', &cfg.verbose, verbose), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + if (!sndk_check_device(r, dev)) + goto out; + + if (cfg.xfer_size) { + xfer_size = cfg.xfer_size; + } else { + fprintf(stderr, "ERROR: SNDK: Invalid length\n"); + goto out; + } + + ret = sndk_get_pci_ids(r, dev, &device_id, &read_vendor_id); + + if (cfg.file) { + int verify_file; + + /* verify file name and path is valid before getting dump data */ + verify_file = open(cfg.file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (verify_file < 0) { + fprintf(stderr, "ERROR: SNDK: open: %s\n", strerror(errno)); + goto out; + } + close(verify_file); + strncpy(f, cfg.file, PATH_MAX - 1); + } else { + sndk_UtilsGetTime(&timeInfo); + memset(timeStamp, 0, sizeof(timeStamp)); + sndk_UtilsSnprintf((char *)timeStamp, SNDK_MAX_PATH_LEN, + "%02u%02u%02u_%02u%02u%02u", timeInfo.year, + timeInfo.month, timeInfo.dayOfMonth, + timeInfo.hour, timeInfo.minute, + timeInfo.second); + snprintf(fileSuffix, PATH_MAX, "_internal_fw_log_%s", (char *)timeStamp); + + ret = sndk_get_serial_name(dev, f, PATH_MAX, fileSuffix); + if (ret) { + fprintf(stderr, "ERROR: SNDK: failed to generate file name\n"); + goto out; + } + } + + if (!cfg.file) { + if (strlen(f) > PATH_MAX - 5) { + fprintf(stderr, "ERROR: SNDK: file name overflow\n"); + ret = -1; + goto out; + } + strcat(f, ".bin"); + } + fprintf(stderr, "%s: filename = %s\n", __func__, f); + + if (cfg.data_area) { + if (cfg.data_area > 5 || cfg.data_area < 1) { + fprintf(stderr, "ERROR: SNDK: Data area must be 1-5\n"); + ret = -1; + goto out; + } + } + + if (!cfg.type || !strcmp(cfg.type, "NONE") || !strcmp(cfg.type, "none")) { + telemetry_type = SNDK_TELEMETRY_TYPE_NONE; + data_area = 0; + } else if (!strcmp(cfg.type, "HOST") || !strcmp(cfg.type, "host")) { + telemetry_type = SNDK_TELEMETRY_TYPE_HOST; + telemetry_data_area = cfg.data_area; + } else if (!strcmp(cfg.type, "CONTROLLER") || !strcmp(cfg.type, "controller")) { + telemetry_type = SNDK_TELEMETRY_TYPE_CONTROLLER; + telemetry_data_area = cfg.data_area; + } else { + fprintf(stderr, + "ERROR: SNDK: Invalid type - Must be NONE, HOST or CONTROLLER\n"); + ret = -1; + goto out; + } + + capabilities = sndk_get_drive_capabilities(r, dev); + + /* Supported through WDC plugin for non-telemetry */ + if ((capabilities & SNDK_DRIVE_CAP_INTERNAL_LOG) && + (telemetry_type != SNDK_TELEMETRY_TYPE_NONE)) { + /* Set the default DA to 3 if not specified */ + if (!telemetry_data_area) + telemetry_data_area = 3; + + ret = sndk_do_cap_telemetry_log(dev, f, xfer_size, + telemetry_type, telemetry_data_area); + goto out; + } + + if (capabilities & SNDK_DRIVE_CAP_UDUI) { + if ((telemetry_type == SNDK_TELEMETRY_TYPE_HOST) || + (telemetry_type == SNDK_TELEMETRY_TYPE_CONTROLLER)) { + /* Set the default DA to 3 if not specified */ + if (!telemetry_data_area) + telemetry_data_area = 3; + + ret = sndk_do_cap_telemetry_log(dev, f, xfer_size, + telemetry_type, telemetry_data_area); + goto out; + } else { + ret = sndk_do_cap_udui(dev_fd(dev), f, xfer_size, + cfg.verbose, cfg.file_size, + cfg.offset); + goto out; + } + } + + /* Fallback to WDC plugin if otherwise not supported */ + nvme_free_tree(r); + dev_close(dev); return run_wdc_vs_internal_fw_log(argc, argv, command, plugin); + +out: + nvme_free_tree(r); + dev_close(dev); + return ret; } static int sndk_vs_nand_stats(int argc, char **argv, diff --git a/plugins/sandisk/sandisk-nvme.h b/plugins/sandisk/sandisk-nvme.h index 037c895c4a..af22ff1366 100644 --- a/plugins/sandisk/sandisk-nvme.h +++ b/plugins/sandisk/sandisk-nvme.h @@ -5,7 +5,7 @@ #if !defined(SANDISK_NVME) || defined(CMD_HEADER_MULTI_READ) #define SANDISK_NVME -#define SANDISK_PLUGIN_VERSION "2.14.1" +#define SANDISK_PLUGIN_VERSION "3.0.0" #include "cmd.h" PLUGIN(NAME("sndk", "Sandisk vendor specific extensions", SANDISK_PLUGIN_VERSION), diff --git a/plugins/sandisk/sandisk-utils.c b/plugins/sandisk/sandisk-utils.c index a741264361..113c97f9b5 100644 --- a/plugins/sandisk/sandisk-utils.c +++ b/plugins/sandisk/sandisk-utils.c @@ -163,7 +163,7 @@ __u64 sndk_get_drive_capabilities(nvme_root_t r, struct nvme_dev *dev) SNDK_DRIVE_CAP_CA_LOG_PAGE | SNDK_DRIVE_CAP_OCP_C4_LOG_PAGE | SNDK_DRIVE_CAP_OCP_C5_LOG_PAGE | - SNDK_DRIVE_CAP_DUI | + SNDK_DRIVE_CAP_UDUI | SNDK_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2 | SNDK_DRIVE_CAP_VU_FID_CLEAR_PCIE | SNDK_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY | @@ -186,7 +186,7 @@ __u64 sndk_get_drive_capabilities(nvme_root_t r, struct nvme_dev *dev) case SNDK_NVME_SN7150_DEV_ID_3: case SNDK_NVME_SN7150_DEV_ID_4: case SNDK_NVME_SN7150_DEV_ID_5: - capabilities = SNDK_DRIVE_CAP_DUI; + capabilities = SNDK_DRIVE_CAP_UDUI; break; default: @@ -267,4 +267,101 @@ __u64 sndk_get_enc_drive_capabilities(nvme_root_t r, return capabilities; } +int sndk_get_serial_name(struct nvme_dev *dev, char *file, size_t len, + const char *suffix) +{ + int i; + int ret; + int res_len = 0; + char orig[PATH_MAX] = {0}; + struct nvme_id_ctrl ctrl; + int ctrl_sn_len = sizeof(ctrl.sn); + i = sizeof(ctrl.sn) - 1; + strncpy(orig, file, PATH_MAX - 1); + memset(file, 0, len); + memset(&ctrl, 0, sizeof(struct nvme_id_ctrl)); + ret = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (ret) { + fprintf(stderr, "ERROR: SNDK: 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--; + } + if (ctrl.sn[sizeof(ctrl.sn) - 1] == '\0') + ctrl_sn_len = strlen(ctrl.sn); + + res_len = snprintf(file, len, "%s%.*s%s", orig, ctrl_sn_len, ctrl.sn, suffix); + if (len <= res_len) { + fprintf(stderr, + "ERROR: SNDK: cannot format SN due to unexpected length\n"); + return -1; + } + + return 0; +} + +void sndk_UtilsGetTime(struct SNDK_UtilsTimeInfo *timeInfo) +{ + time_t currTime; + struct tm currTimeInfo; + + tzset(); + time(&currTime); + localtime_r(&currTime, &currTimeInfo); + + timeInfo->year = currTimeInfo.tm_year + 1900; + timeInfo->month = currTimeInfo.tm_mon + 1; + timeInfo->dayOfWeek = currTimeInfo.tm_wday; + timeInfo->dayOfMonth = currTimeInfo.tm_mday; + timeInfo->hour = currTimeInfo.tm_hour; + timeInfo->minute = currTimeInfo.tm_min; + timeInfo->second = currTimeInfo.tm_sec; + timeInfo->msecs = 0; + timeInfo->isDST = currTimeInfo.tm_isdst; +#ifdef HAVE_TM_GMTOFF + timeInfo->zone = -currTimeInfo.tm_gmtoff / 60; +#else /* HAVE_TM_GMTOFF */ + timeInfo->zone = -1 * (timezone / 60); +#endif /* HAVE_TM_GMTOFF */ +} + +int sndk_UtilsSnprintf(char *buffer, unsigned int sizeOfBuffer, const char *format, ...) +{ + int res = 0; + va_list vArgs; + + va_start(vArgs, format); + res = vsnprintf(buffer, sizeOfBuffer, format, vArgs); + va_end(vArgs); + + return res; +} + +/* Verify the Controller Initiated Option is enabled */ +int sndk_check_ctrl_telemetry_option_disabled(struct nvme_dev *dev) +{ + int err; + __u32 result; + + err = nvme_get_features_data(dev_fd(dev), + SNDK_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID, + 0, 4, NULL, &result); + if (!err) { + if (result) { + fprintf(stderr, + "%s: Controller-initiated option telemetry disabled\n", + __func__); + return -EINVAL; + } + } else { + fprintf(stderr, "ERROR: SNDK: Get telemetry option feature failed."); + nvme_show_status(err); + return -EPERM; + } + + return 0; +} diff --git a/plugins/sandisk/sandisk-utils.h b/plugins/sandisk/sandisk-utils.h index 3324c1e5bd..2f55815220 100644 --- a/plugins/sandisk/sandisk-utils.h +++ b/plugins/sandisk/sandisk-utils.h @@ -139,6 +139,7 @@ #define SNDK_DRIVE_CAP_OCP_C5_LOG_PAGE 0x0000008000000000 #define SNDK_DRIVE_CAP_DEVICE_WAF 0x0000010000000000 #define SNDK_DRIVE_CAP_SET_LATENCY_MONITOR 0x0000020000000000 +#define SNDK_DRIVE_CAP_UDUI 0x0000040000000000 /* Any new capability flags should be added to the WDC plugin */ #define SNDK_DRIVE_CAP_SMART_LOG_MASK (SNDK_DRIVE_CAP_C0_LOG_PAGE | \ @@ -150,6 +151,7 @@ SNDK_DRIVE_CAP_VU_FID_CLEAR_PCIE) #define SNDK_DRIVE_CAP_INTERNAL_LOG_MASK (SNDK_DRIVE_CAP_INTERNAL_LOG | \ SNDK_DRIVE_CAP_DUI | \ + SNDK_DRIVE_CAP_UDUI | \ SNDK_DRIVE_CAP_DUI_DATA | \ SNDK_DRIVE_CAP_VUC_LOG) #define SNDK_DRIVE_CAP_FW_ACTIVATE_HISTORY_MASK (SNDK_DRIVE_CAP_FW_ACTIVATE_HISTORY | \ @@ -170,6 +172,9 @@ #define SNDK_NVME_GET_FW_ACT_HISTORY_LOG_ID 0xCB #define SNDK_NVME_GET_VU_SMART_LOG_ID 0xD0 +/* Vendor defined Feature IDs */ +#define SNDK_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID 0xD2 + /* Customer ID's */ #define SNDK_CUSTOMER_ID_GN 0x0001 #define SNDK_CUSTOMER_ID_GD 0x0101 @@ -181,6 +186,30 @@ #define SNDK_CUSTOMER_ID_0x1304 0x1304 #define SNDK_INVALID_CUSTOMER_ID -1 +/* Capture Device Unit Info */ +#define SNDK_NVME_CAP_UDUI_OPCODE 0xFA + +/* Telemtery types for vs-internal-log command */ +#define SNDK_TELEMETRY_TYPE_NONE 0x0 +#define SNDK_TELEMETRY_TYPE_HOST 0x1 +#define SNDK_TELEMETRY_TYPE_CONTROLLER 0x2 + +/* Misc */ +#define SNDK_MAX_PATH_LEN 256 + +struct SNDK_UtilsTimeInfo { + unsigned int year; + unsigned int month; + unsigned int dayOfWeek; + unsigned int dayOfMonth; + unsigned int hour; + unsigned int minute; + unsigned int second; + unsigned int msecs; + unsigned char isDST; /*0 or 1 */ + int zone; /* Zone value like +530 or -300 */ +}; + int sndk_get_pci_ids(nvme_root_t r, struct nvme_dev *dev, uint32_t *device_id, @@ -198,3 +227,12 @@ __u64 sndk_get_drive_capabilities(nvme_root_t r, __u64 sndk_get_enc_drive_capabilities(nvme_root_t r, struct nvme_dev *dev); +int sndk_get_serial_name(struct nvme_dev *dev, char *file, size_t len, + const char *suffix); + +void sndk_UtilsGetTime(struct SNDK_UtilsTimeInfo *timeInfo); + +int sndk_UtilsSnprintf(char *buffer, unsigned int sizeOfBuffer, + const char *format, ...); + +int sndk_check_ctrl_telemetry_option_disabled(struct nvme_dev *dev); diff --git a/plugins/wdc/wdc-nvme.c b/plugins/wdc/wdc-nvme.c index 1dd77e9a9b..03564808fb 100644 --- a/plugins/wdc/wdc-nvme.c +++ b/plugins/wdc/wdc-nvme.c @@ -189,6 +189,7 @@ #define WDC_DRIVE_CAP_OCP_C5_LOG_PAGE 0x0000008000000000 #define WDC_DRIVE_CAP_DEVICE_WAF 0x0000010000000000 #define WDC_DRIVE_CAP_SET_LATENCY_MONITOR 0x0000020000000000 +#define WDC_DRIVE_CAP_RESERVED1 0x0000040000000000 /* Any new capability flags should be added to the SNDK plugin */ #define WDC_DRIVE_CAP_SMART_LOG_MASK (WDC_DRIVE_CAP_C0_LOG_PAGE | \