From b5560dd1153d46c0204d3db3ffaa7c16ffe287a1 Mon Sep 17 00:00:00 2001 From: Brandon Paupore Date: Wed, 11 Oct 2023 17:20:28 -0500 Subject: [PATCH] plugins/wdc: enhanced SN861 device support This commit enables several commands to work with this device properly and adds a new plugin command set-latency-monitor-feature. Reviewed-by: Jeffrey Lien Signed-off-by: Brandon Paupore --- .../nvme-wdc-set-latency-monitor-feature.txt | 113 ++ plugins/wdc/wdc-nvme.c | 1207 +++++++++++++++-- plugins/wdc/wdc-nvme.h | 3 + 3 files changed, 1202 insertions(+), 121 deletions(-) create mode 100644 Documentation/nvme-wdc-set-latency-monitor-feature.txt diff --git a/Documentation/nvme-wdc-set-latency-monitor-feature.txt b/Documentation/nvme-wdc-set-latency-monitor-feature.txt new file mode 100644 index 00000000..1be7bf99 --- /dev/null +++ b/Documentation/nvme-wdc-set-latency-monitor-feature.txt @@ -0,0 +1,113 @@ +nvme-wdc-set-latency-monitor-feature(1) +======================================= + +NAME +---- +nvme-wdc-set-latency-monitor-feature - Set NVMe WDC latency monitor feature options + +SYNOPSIS +-------- +[verse] +'nvme wdc set-latency-monitor-feature' + [--active_bucket_timer_threshold= | -t ] + [--active_threshold_a= | -a ] + [--active_threshold_b= | -b ] + [--active_threshold_c= | -c ] + [--active_threshold_d= | -d ] + [--active_latency_config= | -f ] + [--active_latency_minimum_window= | -w ] + [--debug_log_trigger_enable= | -r ] + [--discard_debug_log= | -l ] + [--latency_monitor_feature_enable= | -e ] + +DESCRIPTION +----------- +For the NVMe device given, this command set the +latency monitor feature options (if supported by the device). + +The parameter is mandatory NVMe character device (ex: /dev/nvme0). + +Setting results can be checked with 'get-latency-monitor-log' command. + +On success it returns 0, error code otherwise. + +OPTIONS +------- +-t :: +--active_bucket_timer_threshold=:: + The value that loads the Active Bucket Timer Threshold; default value is 07E0h. + +-a :: +--active_threshold_a=:: + The value that loads into the Active Threshold A; default value is 05h. + +-b :: +--active_threshold_b=:: + The value that loads into the Active Threshold B; default value is 13h. + +-c :: +--active_threshold_c=:: + The value that loads into the Active Threshold C; default value is 1Eh. + +-d :: +--active_threshold_d=:: + The value that loads into the Active Threshold D; default value is 2Eh. + +-f :: +--active_latency_config=:: + The value that loads into the Active Latency Configuration. This configures how both the Active + Latency Stamp, and the Active Measured Latency Fields are updated on a per I/O command (Read, + Write, Deallocate) counter basis; default value is 0FFFh. + +-w :: +--active_latency_minimum_window=:: + The value that loads into the Active Latency Minimum Window; default value is 0Ah. + +-r :: +--debug_log_trigger_enable=:: + The value that loads into the Debug Log Trigger Enable; When set to 1b the first time the + bucket/counter combination is incremented a debug log is triggered. When cleared to 0b a + debug log will not be triggered when the bucket/counter combination is incremented. + +-l :: +--discard_debug_log=:: + Discard Debug Log. When cleared to 00h the debug log, if it exists, will not be cleared. + When set to 01h the debug log will be discarded so another log can be triggered. All the fields in + the Set Features Data structure are valid. When set to 02h the debug log will be discarded so + another log can be triggered. None of the other fields of the Set Features Data structure are valid. + +-e :: +--latency_monitor_feature_enable=:: + Latency Monitor Feature Enable; When set to 01h the Latency Monitor Feature is + enabled. When cleared to 00h the Latency Monitor Feature is disabled. + +EXAMPLES +-------- +* Set NVMe WDC latency monitor feature options enabled with default value values: ++ +------------ +# nvme wdc set-latency-monitor-feature /dev/nvme0 -e 1 +------------ +* Set NVMe WDC latency monitor feature options disabled with default value values: ++ +------------ +# nvme wdc set-latency-monitor-feature /dev/nvme0 -e 0 +------------ +* Set NVMe WDC latency monitor feature options enabled with specific values: ++ +------------ +# nvme wdc set-latency-monitor-feature /dev/nvme0 --active_bucket_timer_threshold=1 \ + --active_threshold_a=0x0 \ + --active_threshold_b=0x1 \ + --active_threshold_c=0x2 \ + --active_threshold_d=0x3 \ + --active_latency_config=0xfff \ + --active_latency_minimum_window=0 \ + --debug_log_trigger_enable=0 \ + --discard_debug_log=0 \ + --latency_monitor_feature_enable=0x1 +------------ + +NVME +---- +Part of the nvme-user suite. \ No newline at end of file diff --git a/plugins/wdc/wdc-nvme.c b/plugins/wdc/wdc-nvme.c index ae57e85f..b66c1c75 100644 --- a/plugins/wdc/wdc-nvme.c +++ b/plugins/wdc/wdc-nvme.c @@ -148,6 +148,8 @@ #define WDC_DRIVE_CAP_OCP_C4_LOG_PAGE 0x0000004000000000 #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_SMART_LOG_MASK (WDC_DRIVE_CAP_C0_LOG_PAGE | \ WDC_DRIVE_CAP_C1_LOG_PAGE | \ WDC_DRIVE_CAP_CA_LOG_PAGE | \ @@ -163,6 +165,7 @@ WDC_DRIVE_CAP_DUI | \ WDC_DRIVE_CAP_DUI_DATA | \ WDC_SN730B_CAP_VUC_LOG) + /* SN730 Get Log Capabilities */ #define SN730_NVME_GET_LOG_OPCODE 0xc2 #define SN730_GET_FULL_LOG_LENGTH 0x00080009 @@ -447,6 +450,12 @@ static __u8 wdc_lat_mon_guid[WDC_C3_GUID_LENGTH] = { #define WDC_ENC_CRASH_DUMP_ID 0xE4 #define WDC_ENC_LOG_DUMP_ID 0xE2 +/* OCP Log Page Directory Data Structure */ +#define BYTE_TO_BIT(byte) ((byte) * 8) + +/* Set latency monitor feature */ +#define NVME_FEAT_OCP_LATENCY_MONITOR 0xC5 + enum _NVME_FEATURES_SELECT { FS_CURRENT = 0, FS_DEFAULT = 1, @@ -657,6 +666,67 @@ enum { SCAO_LPG = 496, /* Log page GUID */ }; +struct ocp_bad_nand_block_count { + __u64 raw : 48; + __u16 normalized : 16; +}; + +struct ocp_e2e_correction_count { + __u32 detected; + __u32 corrected; +}; + +struct ocp_user_data_erase_count { + __u32 maximum; + __u32 minimum; +}; + +struct ocp_thermal_status { + __u8 num_events; + __u8 current_status; +}; + +struct __packed ocp_dssd_specific_ver { + __u8 errata_ver; + __u16 point_ver; + __u16 minor_ver; + __u8 major_ver; +}; + +struct ocp_cloud_smart_log { + __u8 physical_media_units_written[16]; + __u8 physical_media_units_read[16]; + struct ocp_bad_nand_block_count bad_user_nand_blocks; + struct ocp_bad_nand_block_count bad_system_nand_blocks; + __u64 xor_recovery_count; + __u64 uncorrectable_read_error_count; + __u64 soft_ecc_error_count; + struct ocp_e2e_correction_count e2e_correction_counts; + __u8 system_data_percent_used; + __u64 refresh_counts : 56; + struct ocp_user_data_erase_count user_data_erase_counts; + struct ocp_thermal_status thermal_status; + struct ocp_dssd_specific_ver dssd_specific_ver; + __u64 pcie_correctable_error_count; + __u32 incomplete_shutdowns; + __u8 rsvd116[4]; + __u8 percent_free_blocks; + __u8 rsvd121[7]; + __u16 capacitor_health; + __u8 nvme_errata_ver; + __u8 rsvd131[5]; + __u64 unaligned_io; + __u64 security_version_number; + __u64 total_nuse; + __u8 plp_start_count[16]; + __u8 endurance_estimate[16]; + __u64 pcie_link_retraining_cnt; + __u64 power_state_change_cnt; + __u8 rsvd208[286]; + __u16 log_page_version; + __u8 log_page_guid[16]; +}; + static __u8 scao_guid[WDC_C0_GUID_LENGTH] = { 0xC5, 0xAF, 0x10, 0x28, 0xEA, 0xBF, 0xF2, 0xA4, 0x9C, 0x4F, 0x6F, 0x7C, 0xC9, 0x14, 0xD5, 0xAF @@ -794,6 +864,46 @@ static struct NVME_VU_DE_LOGPAGE_LIST deVULogPagesList[] = { { NVME_DE_LOGPAGE_C0, 0xC0, 512, "0xc0"} }; +enum { + WDC_NVME_ADMIN_VUC_OPCODE_D2 = 0xD2, + WDC_VUC_SUBOPCODE_VS_DRIVE_INFO_D2 = 0x0000010A, + WDC_VUC_SUBOPCODE_LOG_PAGE_DIR_D2 = 0x00000105, +}; + +enum { + NVME_LOG_NS_BASE = 0x80, + NVME_LOG_VS_BASE = 0xC0, +}; + +/*drive_info struct*/ +struct ocp_drive_info { + __u32 hw_revision; + __u32 ftl_unit_size; +}; + +/*get log page directory struct*/ +struct log_page_directory { + __u64 supported_lid_bitmap; + __u64 rsvd; + __u64 supported_ns_lid_bitmap; + __u64 supported_vs_lid_bitmap; +}; + +/*set latency monitor feature */ +struct __packed feature_latency_monitor { + __u16 active_bucket_timer_threshold; + __u8 active_threshold_a; + __u8 active_threshold_b; + __u8 active_threshold_c; + __u8 active_threshold_d; + __u16 active_latency_config; + __u8 active_latency_minimum_window; + __u16 debug_log_trigger_enable; + __u8 discard_debug_log; + __u8 latency_monitor_feature_enable; + __u8 reserved[4083]; +}; + static int wdc_get_serial_name(struct nvme_dev *dev, char *file, size_t len, const char *suffix); static int wdc_create_log_file(char *file, __u8 *drive_log_data, __u32 drive_log_length); static int wdc_do_clear_dump(struct nvme_dev *dev, __u8 opcode, __u32 cdw12); @@ -1269,6 +1379,21 @@ static __u8 wdc_ocp_c5_guid[WDC_OCP_C5_GUID_LENGTH] = { 0x2F, 0x72, 0x9C, 0x0 #define WDC_REASON_ID_ENTRY_LEN 128 #define WDC_REASON_ID_PATH_NAME "/usr/local/nvmecli" +const char *log_page_name[256] = { + [NVME_LOG_LID_ERROR] = "Error Information", + [NVME_LOG_LID_SMART] = "SMART / Health Information", + [NVME_LOG_LID_FW_SLOT] = "Firmware Slot Information", + [NVME_LOG_LID_CHANGED_NS] = "Changed Namespace List", + [NVME_LOG_LID_CMD_EFFECTS] = "Command Supported and Effects", + [NVME_LOG_LID_TELEMETRY_HOST] = "Telemetry Host-Initiated", + [NVME_LOG_LID_TELEMETRY_CTRL] = "Telemetry Controller-Initiated", + [NVME_LOG_LID_SANITIZE] = "Sanitize Status", + [WDC_LOG_ID_C0] = "Extended SMART Information", + [WDC_LOG_ID_C2] = "Firmware Activation History", + [WDC_LOG_ID_C3] = "Latency Monitor", + [WDC_LOG_ID_C4] = "Device Capabilities", + [WDC_LOG_ID_C5] = "Unsupported Requirements", +}; static double safe_div_fp(double numerator, double denominator) { @@ -1668,16 +1793,20 @@ static __u64 wdc_get_drive_capabilities(nvme_root_t r, struct nvme_dev *dev) break; case WDC_NVME_SN861_DEV_ID: - capabilities |= (WDC_DRIVE_CAP_C0_LOG_PAGE | WDC_DRIVE_CAP_OCP_C1_LOG_PAGE | - WDC_DRIVE_CAP_C3_LOG_PAGE | WDC_DRIVE_CAP_OCP_C4_LOG_PAGE | - WDC_DRIVE_CAP_OCP_C5_LOG_PAGE); - - 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_FW_ACTIVATE_HISTORY | - WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG | - WDC_DRIVE_CAP_REASON_ID); - + capabilities |= (WDC_DRIVE_CAP_C0_LOG_PAGE | + WDC_DRIVE_CAP_C3_LOG_PAGE | + WDC_DRIVE_CAP_CA_LOG_PAGE | + WDC_DRIVE_CAP_OCP_C4_LOG_PAGE | + WDC_DRIVE_CAP_OCP_C5_LOG_PAGE | + WDC_DRIVE_CAP_INTERNAL_LOG | + WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2 | + WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE | + WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY | + WDC_DRIVE_CAP_INFO | + WDC_DRIVE_CAP_CLOUD_SSD_VERSION | + WDC_DRIVE_CAP_LOG_PAGE_DIR | + WDC_DRIVE_CAP_SET_LATENCY_MONITOR); + break; default: capabilities = 0; } @@ -3385,6 +3514,102 @@ free_buf: return ret; } +static int dump_internal_logs(struct nvme_dev *dev, char *dir_name, int verbose) +{ + char file_path[128]; + void *telemetry_log; + const size_t bs = 512; + struct nvme_telemetry_log *hdr; + size_t full_size, offset = bs; + int err, output; + + if (verbose) + printf("NVMe Telemetry log...\n"); + + hdr = malloc(bs); + telemetry_log = malloc(bs); + if (!hdr || !telemetry_log) { + fprintf(stderr, "Failed to allocate %zu bytes for log: %s\n", bs, strerror(errno)); + err = -ENOMEM; + goto free_mem; + } + memset(hdr, 0, bs); + + sprintf(file_path, "%s/telemetry.bin", dir_name); + output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (output < 0) { + fprintf(stderr, "Failed to open output file %s: %s!\n", file_path, strerror(errno)); + err = output; + goto free_mem; + } + + struct nvme_get_log_args args = { + .lpo = 0, + .result = NULL, + .log = hdr, + .args_size = sizeof(args), + .fd = dev_fd(dev), + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .lid = NVME_LOG_LID_TELEMETRY_HOST, + .len = bs, + .nsid = NVME_NSID_ALL, + .csi = NVME_CSI_NVM, + .lsi = NVME_LOG_LSI_NONE, + .lsp = NVME_LOG_TELEM_HOST_LSP_CREATE, + .uuidx = NVME_UUID_NONE, + .rae = true, + .ot = false, + }; + + err = nvme_get_log(&args); + if (err < 0) + perror("get-telemetry-log"); + else if (err > 0) { + nvme_show_status(err); + fprintf(stderr, "Failed to acquire telemetry header %d!\n", err); + goto close_output; + } + + err = write(output, (void *)hdr, bs); + if (err != bs) { + fprintf(stderr, "Failed to flush all data to file!\n"); + goto close_output; + } + + full_size = (le16_to_cpu(hdr->dalb3) * bs) + offset; + + while (offset != full_size) { + args.log = telemetry_log; + args.lpo = offset; + args.lsp = NVME_LOG_LSP_NONE; + err = nvme_get_log(&args); + if (err < 0) { + perror("get-telemetry-log"); + break; + } else if (err > 0) { + fprintf(stderr, "Failed to acquire full telemetry log!\n"); + nvme_show_status(err); + break; + } + + err = write(output, (void *)telemetry_log, bs); + if (err != bs) { + fprintf(stderr, "Failed to flush all data to file!\n"); + break; + } + err = 0; + offset += bs; + } + +close_output: + close(output); +free_mem: + free(hdr); + free(telemetry_log); + + return err; +} + static int wdc_vs_internal_fw_log(int argc, char **argv, struct command *command, struct plugin *plugin) { @@ -3397,6 +3622,7 @@ static int wdc_vs_internal_fw_log(int argc, char **argv, struct command *command char *type = "Telemetry type - NONE, HOST, or CONTROLLER. Currently only supported on the SN530, SN640, SN730, SN740, SN810, SN840 and ZN350 devices."; char *verbose = "Display more debug messages."; char f[PATH_MAX] = {0}; + char fb[PATH_MAX/2] = {0}; char fileSuffix[PATH_MAX] = {0}; struct nvme_dev *dev; nvme_root_t r; @@ -3405,6 +3631,9 @@ static int wdc_vs_internal_fw_log(int argc, char **argv, struct command *command UtilsTimeInfo timeInfo; __u8 timeStamp[MAX_PATH_LEN]; __u64 capabilities = 0; + __u32 device_id, read_vendor_id; + char file_path[PATH_MAX/2] = {0}; + char cmd_buf[PATH_MAX] = {0}; int ret = -1; struct config { @@ -3453,72 +3682,149 @@ static int wdc_vs_internal_fw_log(int argc, char **argv, struct command *command goto out; } - if (cfg.file) { - int verify_file; + ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id); - /* verify the passed in file name and path is valid before getting the dump data */ - verify_file = open(cfg.file, O_WRONLY | O_CREAT | O_TRUNC, 0666); - if (verify_file < 0) { - fprintf(stderr, "ERROR: WDC: open: %s\n", strerror(errno)); - goto out; + if (device_id != WDC_NVME_SN861_DEV_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: WDC: open: %s\n", strerror(errno)); + goto out; + } + close(verify_file); + strncpy(f, cfg.file, PATH_MAX - 1); + } else { + wdc_UtilsGetTime(&timeInfo); + memset(timeStamp, 0, sizeof(timeStamp)); + wdc_UtilsSnprintf((char *)timeStamp, 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 = wdc_get_serial_name(dev, f, PATH_MAX, fileSuffix); + if (ret) { + fprintf(stderr, "ERROR: WDC: failed to generate file name\n"); + goto out; + } } - close(verify_file); - strncpy(f, cfg.file, PATH_MAX - 1); - } else { - wdc_UtilsGetTime(&timeInfo); - memset(timeStamp, 0, sizeof(timeStamp)); - wdc_UtilsSnprintf((char *)timeStamp, 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 = wdc_get_serial_name(dev, f, PATH_MAX, fileSuffix); - if (ret) { - fprintf(stderr, "ERROR: WDC: failed to generate file name\n"); - goto out; + if (!cfg.file) { + if (strlen(f) > PATH_MAX - 5) { + fprintf(stderr, "ERROR: WDC: file name overflow\n"); + ret = -1; + goto out; + } + strcat(f, ".bin"); } - } + fprintf(stderr, "%s: filename = %s\n", __func__, f); - if (!cfg.file) { - if (strlen(f) > PATH_MAX - 5) { - fprintf(stderr, "ERROR: WDC: file name overflow\n"); + if (cfg.data_area) { + if (cfg.data_area > 5 || cfg.data_area < 1) { + fprintf(stderr, "ERROR: WDC: Data area must be 1-5\n"); + ret = -1; + goto out; + } + } + + if (!cfg.type || !strcmp(cfg.type, "NONE") || !strcmp(cfg.type, "none")) { + telemetry_type = WDC_TELEMETRY_TYPE_NONE; + data_area = 0; + } else if (!strcmp(cfg.type, "HOST") || !strcmp(cfg.type, "host")) { + telemetry_type = WDC_TELEMETRY_TYPE_HOST; + telemetry_data_area = cfg.data_area; + } else if (!strcmp(cfg.type, "CONTROLLER") || !strcmp(cfg.type, "controller")) { + telemetry_type = WDC_TELEMETRY_TYPE_CONTROLLER; + telemetry_data_area = cfg.data_area; + } else { + fprintf(stderr, + "ERROR: WDC: Invalid type - Must be NONE, HOST or CONTROLLER\n"); ret = -1; goto out; } - strcat(f, ".bin"); - } - fprintf(stderr, "%s: filename = %s\n", __func__, f); + } else { + if (cfg.file) { + strncpy(fb, cfg.file, PATH_MAX/2 - 8); + } else { + wdc_UtilsGetTime(&timeInfo); + memset(timeStamp, 0, sizeof(timeStamp)); + wdc_UtilsSnprintf((char *)timeStamp, 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 = wdc_get_serial_name(dev, fb, PATH_MAX/2 - 7, fileSuffix); + if (ret) { + fprintf(stderr, "ERROR: WDC: failed to generate file name\n"); + goto out; + } + + if (strlen(fb) > PATH_MAX/2 - 7) { + fprintf(stderr, "ERROR: WDC: file name overflow\n"); + ret = -1; + goto out; + } + } + fprintf(stderr, "%s: filename = %s.tar.gz\n", __func__, fb); + - if (cfg.data_area) { - if (cfg.data_area > 5 || cfg.data_area < 1) { - fprintf(stderr, "ERROR: WDC: Data area must be 1-5\n"); + memset(file_path, 0, sizeof(file_path)); + if (snprintf(file_path, PATH_MAX/2 - 8, "%s.tar.gz", fb) >= PATH_MAX/2 - 8) { + fprintf(stderr, "File path is too long!\n"); ret = -1; goto out; } - } - - if (!cfg.type || !strcmp(cfg.type, "NONE") || !strcmp(cfg.type, "none")) { - telemetry_type = WDC_TELEMETRY_TYPE_NONE; - data_area = 0; - } else if (!strcmp(cfg.type, "HOST") || !strcmp(cfg.type, "host")) { - telemetry_type = WDC_TELEMETRY_TYPE_HOST; - telemetry_data_area = cfg.data_area; - } else if (!strcmp(cfg.type, "CONTROLLER") || !strcmp(cfg.type, "controller")) { - telemetry_type = WDC_TELEMETRY_TYPE_CONTROLLER; - telemetry_data_area = cfg.data_area; - } else { - fprintf(stderr, "ERROR: WDC: Invalid type - Must be NONE, HOST or CONTROLLER\n"); - ret = -1; - goto out; + if (access(file_path, F_OK) != -1) { + fprintf(stderr, "Output file already exists!\n"); + ret = -EEXIST; + goto out; + } } capabilities = wdc_get_drive_capabilities(r, dev); if ((capabilities & WDC_DRIVE_CAP_INTERNAL_LOG) == WDC_DRIVE_CAP_INTERNAL_LOG) { - if (!telemetry_data_area) - telemetry_data_area = 3; /* Set the default DA to 3 if not specified */ + if (device_id != WDC_NVME_SN861_DEV_ID) { + /* Set the default DA to 3 if not specified */ + if (!telemetry_data_area) + telemetry_data_area = 3; + + ret = wdc_do_cap_diag(r, dev, f, xfer_size, + telemetry_type, telemetry_data_area); + } else { + if (cfg.verbose) + printf("Creating temp directory...\n"); + + ret = mkdir(fb, 0666); + if (ret) { + fprintf(stderr, "Failed to create directory!\n"); + goto out; + } + + ret = dump_internal_logs(dev, fb, cfg.verbose); + if (ret < 0) + perror("vs-internal-log"); + + if (cfg.verbose) + printf("Archiving...\n"); + + if (snprintf(cmd_buf, PATH_MAX, + "tar --remove-files -czf %s %s", + file_path, fb) >= PATH_MAX) { + fprintf(stderr, "Command buffer is too long!\n"); + ret = -1; + goto out; + } - ret = wdc_do_cap_diag(r, dev, f, xfer_size, - telemetry_type, telemetry_data_area); + ret = system(cmd_buf); + if (ret) + fprintf(stderr, "Failed to create an archive file!\n"); + } goto out; } if ((capabilities & WDC_DRIVE_CAP_DUI) == WDC_DRIVE_CAP_DUI) { @@ -5018,7 +5324,9 @@ static void wdc_get_commit_action_bin(__u8 commit_action_type, char *action_bin) } -static void wdc_print_fw_act_history_log_normal(__u8 *data, int num_entries, __u32 cust_id, __u32 vendor_id) +static void wdc_print_fw_act_history_log_normal(__u8 *data, int num_entries, + __u32 cust_id, __u32 vendor_id, + __u32 device_id) { int i, j; char previous_fw[9]; @@ -5032,7 +5340,9 @@ static void wdc_print_fw_act_history_log_normal(__u8 *data, int num_entries, __u if (data[0] == WDC_NVME_GET_FW_ACT_HISTORY_C2_LOG_ID) { printf(" Firmware Activate History Log\n"); - if (cust_id == WDC_CUSTOMER_ID_0x1005 || vendor_id == WDC_NVME_SNDK_VID) { + if (cust_id == WDC_CUSTOMER_ID_0x1005 || + vendor_id == WDC_NVME_SNDK_VID || + device_id == WDC_NVME_SN861_DEV_ID) { printf(" Power on Hour Power Cycle Previous New\n"); printf(" Entry hh:mm:ss Count Firmware Firmware Slot Action Result\n"); printf(" ----- ----------------- ----------------- --------- --------- ----- ------ -------\n"); @@ -5091,6 +5401,26 @@ static void wdc_print_fw_act_history_log_normal(__u8 *data, int num_entries, __u (int)((timestamp/1000)%60)); printf("%s", time_str); printf(" "); + } else if (device_id == WDC_NVME_SN861_DEV_ID) { + printf(" "); + char timestamp[20]; + __u64 hour; + __u8 min; + __u8 sec; + __u64 timestamp_sec; + + timestamp_sec = + le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp) + / 1000; + hour = timestamp_sec / 3600; + min = (timestamp_sec % 3600) / 60; + sec = timestamp_sec % 60; + + sprintf(timestamp, + "%"PRIu64":%02"PRIu8":%02"PRIu8, + (uint64_t)hour, min, sec); + printf("%-11s", timestamp); + printf(" "); } else { printf(" "); uint64_t timestamp = (0x0000FFFFFFFFFFFF & le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp)); @@ -5189,7 +5519,9 @@ static void wdc_print_fw_act_history_log_normal(__u8 *data, int num_entries, __u } } -static void wdc_print_fw_act_history_log_json(__u8 *data, int num_entries, __u32 cust_id, __u32 vendor_id) +static void wdc_print_fw_act_history_log_json(__u8 *data, int num_entries, + __u32 cust_id, __u32 vendor_id, + __u32 device_id) { struct json_object *root = json_create_object(); int i, j; @@ -5198,11 +5530,13 @@ static void wdc_print_fw_act_history_log_json(__u8 *data, int num_entries, __u32 char commit_action_bin[8]; char fail_str[32]; char time_str[11]; + char ext_time_str[20]; memset((void *)previous_fw, 0, 9); memset((void *)new_fw, 0, 9); memset((void *)commit_action_bin, 0, 8); memset((void *)time_str, 0, 11); + memset((void *)ext_time_str, 0, 20); memset((void *)fail_str, 0, 11); char *null_fw = "--------"; __u16 oldestEntryIdx = 0, entryIdx = 0; @@ -5254,6 +5588,17 @@ static void wdc_print_fw_act_history_log_json(__u8 *data, int num_entries, __u32 sprintf((char *)time_str, "%04d:%02d:%02d", (int)((timestamp/(3600*1000))%24), (int)((timestamp/(1000*60))%60), (int)((timestamp/1000)%60)); json_object_add_value_string(root, "Power on Hour", time_str); + } else if (device_id == WDC_NVME_SN861_DEV_ID) { + __u64 timestamp_sec = + le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp) + / 1000; + + sprintf((char *)ext_time_str, + "%"PRIu64":%02"PRIu8":%02"PRIu8, + (uint64_t)(__u64)(timestamp_sec/3600), + (__u8)((timestamp_sec%3600)/60), + (__u8)(timestamp_sec%60)); + json_object_add_value_string(root, "Power on Hour", ext_time_str); } else { uint64_t timestamp = (0x0000FFFFFFFFFFFF & le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp)); @@ -6526,8 +6871,6 @@ static int wdc_get_c0_log_page(nvme_root_t r, struct nvme_dev *dev, char *format case WDC_NVME_SN560_DEV_ID_3: fallthrough; case WDC_NVME_SN550_DEV_ID: - fallthrough; - case WDC_NVME_SN861_DEV_ID: ret = wdc_get_c0_log_page_sn(r, dev, uuid_index, format, namespace_id, fmt); break; case WDC_NVME_ZN350_DEV_ID: @@ -6711,7 +7054,9 @@ static int wdc_print_d0_log(struct wdc_ssd_d0_smart_log *perf, int fmt) return 0; } -static int wdc_print_fw_act_history_log(__u8 *data, int num_entries, int fmt, __u32 cust_id, __u32 vendor_id) +static int wdc_print_fw_act_history_log(__u8 *data, int num_entries, int fmt, + __u32 cust_id, __u32 vendor_id, + __u32 device_id) { if (!data) { fprintf(stderr, "ERROR: WDC: Invalid buffer to read fw activate history entries\n"); @@ -6720,10 +7065,12 @@ static int wdc_print_fw_act_history_log(__u8 *data, int num_entries, int fmt, __ switch (fmt) { case NORMAL: - wdc_print_fw_act_history_log_normal(data, num_entries, cust_id, vendor_id); + wdc_print_fw_act_history_log_normal(data, num_entries, cust_id, + vendor_id, device_id); break; case JSON: - wdc_print_fw_act_history_log_json(data, num_entries, cust_id, vendor_id); + wdc_print_fw_act_history_log_json(data, num_entries, cust_id, + vendor_id, device_id); break; } return 0; @@ -7249,6 +7596,235 @@ static int wdc_get_d0_log_page(nvme_root_t r, struct nvme_dev *dev, char *format return ret; } +static long double le_to_float(__u8 *data, int byte_len) +{ + long double result = 0; + int i; + + for (i = 0; i < byte_len; i++) { + result *= 256; + result += data[15 - i]; + } + + return result; +} + +static void stringify_log_page_guid(__u8 *guid, char *buf) +{ + char *ptr = buf; + int i; + + memset(buf, 0, sizeof(char) * 19); + + ptr += sprintf(ptr, "0x"); + for (i = 0; i < 16; i++) + ptr += sprintf(ptr, "%x", guid[15 - i]); +} + +static const char *const cloud_smart_log_thermal_status[] = { + [0x00] = "unthrottled", + [0x01] = "first_level", + [0x02] = "second_level", + [0x03] = "third_level", +}; + +static const char *stringify_cloud_smart_log_thermal_status(__u8 status) +{ + if (status < ARRAY_SIZE(cloud_smart_log_thermal_status) && + cloud_smart_log_thermal_status[status]) + return cloud_smart_log_thermal_status[status]; + return "unrecognized"; +} + +static void show_cloud_smart_log_json(struct ocp_cloud_smart_log *log) +{ + struct json_object *root; + struct json_object *bad_user_nand_blocks; + struct json_object *bad_system_nand_blocks; + struct json_object *e2e_correction_counts; + struct json_object *user_data_erase_counts; + struct json_object *thermal_status; + struct json_object *dssd_specific_ver; + char buf[2 * sizeof(log->log_page_guid) + 3]; + + bad_user_nand_blocks = json_create_object(); + json_object_add_value_uint(bad_user_nand_blocks, "normalized", + le16_to_cpu(log->bad_user_nand_blocks.normalized)); + json_object_add_value_uint(bad_user_nand_blocks, "raw", + le64_to_cpu(log->bad_user_nand_blocks.raw)); + + bad_system_nand_blocks = json_create_object(); + json_object_add_value_uint(bad_system_nand_blocks, "normalized", + le16_to_cpu(log->bad_system_nand_blocks.normalized)); + json_object_add_value_uint(bad_system_nand_blocks, "raw", + le64_to_cpu(log->bad_system_nand_blocks.raw)); + + e2e_correction_counts = json_create_object(); + json_object_add_value_uint(e2e_correction_counts, "corrected", + le32_to_cpu(log->e2e_correction_counts.corrected)); + json_object_add_value_uint(e2e_correction_counts, "detected", + le32_to_cpu(log->e2e_correction_counts.detected)); + + user_data_erase_counts = json_create_object(); + json_object_add_value_uint(user_data_erase_counts, "minimum", + le32_to_cpu(log->user_data_erase_counts.minimum)); + json_object_add_value_uint(user_data_erase_counts, "maximum", + le32_to_cpu(log->user_data_erase_counts.maximum)); + + thermal_status = json_create_object(); + json_object_add_value_string(thermal_status, "current_status", + stringify_cloud_smart_log_thermal_status(log->thermal_status.current_status)); + json_object_add_value_uint(thermal_status, "num_events", + log->thermal_status.num_events); + + dssd_specific_ver = json_create_object(); + json_object_add_value_uint(dssd_specific_ver, "major_ver", + log->dssd_specific_ver.major_ver); + json_object_add_value_uint(dssd_specific_ver, "minor_ver", + le16_to_cpu(log->dssd_specific_ver.minor_ver)); + json_object_add_value_uint(dssd_specific_ver, "point_ver", + le16_to_cpu(log->dssd_specific_ver.point_ver)); + json_object_add_value_uint(dssd_specific_ver, "errata_ver", + log->dssd_specific_ver.errata_ver); + + root = json_create_object(); + json_object_add_value_uint64(root, "physical_media_units_written", + le_to_float(log->physical_media_units_written, 16)); + json_object_add_value_uint64(root, "physical_media_units_read", + le_to_float(log->physical_media_units_read, 16)); + json_object_add_value_object(root, "bad_user_nand_blocks", + bad_user_nand_blocks); + json_object_add_value_object(root, "bad_system_nand_blocks", + bad_system_nand_blocks); + json_object_add_value_uint(root, "xor_recovery_count", + le64_to_cpu(log->xor_recovery_count)); + json_object_add_value_uint(root, "uncorrectable_read_error_count", + le64_to_cpu(log->uncorrectable_read_error_count)); + json_object_add_value_uint(root, "soft_ecc_error_count", + le64_to_cpu(log->soft_ecc_error_count)); + json_object_add_value_object(root, "e2e_correction_counts", + e2e_correction_counts); + json_object_add_value_uint(root, "system_data_percent_used", + log->system_data_percent_used); + json_object_add_value_uint(root, "refresh_counts", + le64_to_cpu(log->refresh_counts)); + json_object_add_value_object(root, "user_data_erase_counts", + user_data_erase_counts); + json_object_add_value_object(root, "thermal_status", thermal_status); + json_object_add_value_object(root, "dssd_specific_ver", + dssd_specific_ver); + json_object_add_value_uint(root, "pcie_correctable_error_count", + le64_to_cpu(log->pcie_correctable_error_count)); + json_object_add_value_uint(root, "incomplete_shutdowns", + le32_to_cpu(log->incomplete_shutdowns)); + json_object_add_value_uint(root, "percent_free_blocks", + log->percent_free_blocks); + json_object_add_value_uint(root, "capacitor_health", + le16_to_cpu(log->capacitor_health)); + sprintf(buf, "%c", log->nvme_errata_ver); + json_object_add_value_string(root, "nvme_errata_version", buf); + json_object_add_value_uint(root, "unaligned_io", + le64_to_cpu(log->unaligned_io)); + json_object_add_value_uint(root, "security_version_number", + le64_to_cpu(log->security_version_number)); + json_object_add_value_uint(root, "total_nuse", + le64_to_cpu(log->total_nuse)); + json_object_add_value_uint64(root, "plp_start_count", + le_to_float(log->plp_start_count, 16)); + json_object_add_value_uint64(root, "endurance_estimate", + le_to_float(log->endurance_estimate, 16)); + json_object_add_value_uint(root, "pcie_link_retraining_count", + le64_to_cpu(log->pcie_link_retraining_cnt)); + json_object_add_value_uint(root, "power_state_change_count", + le64_to_cpu(log->power_state_change_cnt)); + json_object_add_value_uint(root, "log_page_version", + le16_to_cpu(log->log_page_version)); + stringify_log_page_guid(log->log_page_guid, buf); + json_object_add_value_string(root, "log_page_guid", buf); + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static void show_cloud_smart_log_normal(struct ocp_cloud_smart_log *log, struct nvme_dev *dev) +{ + char buf[2 * sizeof(log->log_page_guid) + 3]; + + printf("Smart Extended Log for NVME device:%s\n", dev->name); + printf("Physical Media Units Written : %'.0Lf\n", + le_to_float(log->physical_media_units_written, 16)); + printf("Physical Media Units Read : %'.0Lf\n", + le_to_float(log->physical_media_units_read, 16)); + printf("Bad User NAND Blocks (Normalized) : %" PRIu16 "%%\n", + le16_to_cpu(log->bad_user_nand_blocks.normalized)); + printf("Bad User NAND Blocks (Raw) : %" PRIu64 "\n", + le64_to_cpu(log->bad_user_nand_blocks.raw)); + printf("Bad System NAND Blocks (Normalized) : %" PRIu16 "%%\n", + le16_to_cpu(log->bad_system_nand_blocks.normalized)); + printf("Bad System NAND Blocks (Raw) : %" PRIu64 "\n", + le64_to_cpu(log->bad_system_nand_blocks.raw)); + printf("XOR Recovery Count : %" PRIu64 "\n", + le64_to_cpu(log->xor_recovery_count)); + printf("Uncorrectable Read Error Count : %" PRIu64 "\n", + le64_to_cpu(log->uncorrectable_read_error_count)); + printf("Soft ECC Error Count : %" PRIu64 "\n", + le64_to_cpu(log->soft_ecc_error_count)); + printf("End to End Correction Counts (Corrected) : %" PRIu32 "\n", + le32_to_cpu(log->e2e_correction_counts.corrected)); + printf("End to End Correction Counts (Detected) : %" PRIu32 "\n", + le32_to_cpu(log->e2e_correction_counts.detected)); + printf("System Data %% Used : %" PRIu8 "%%\n", + log->system_data_percent_used); + printf("Refresh Counts : %" PRIu64 "\n", + le64_to_cpu(log->refresh_counts)); + printf("User Data Erase Counts (Minimum) : %" PRIu32 "\n", + le32_to_cpu(log->user_data_erase_counts.minimum)); + printf("User Data Erase Counts (Maximum) : %" PRIu32 "\n", + le32_to_cpu(log->user_data_erase_counts.maximum)); + printf("Thermal Throttling Status (Current Status) : %s\n", + stringify_cloud_smart_log_thermal_status(log->thermal_status.current_status)); + printf("Thermal Throttling Status (Number of Events) : %" PRIu8 "\n", + log->thermal_status.num_events); + printf("NVMe Major Version : %" PRIu8 "\n", + log->dssd_specific_ver.major_ver); + printf(" Minor Version : %" PRIu16 "\n", + le16_to_cpu(log->dssd_specific_ver.minor_ver)); + printf(" Point Version : %" PRIu16 "\n", + le16_to_cpu(log->dssd_specific_ver.point_ver)); + printf(" Errata Version : %" PRIu8 "\n", + log->dssd_specific_ver.errata_ver); + printf("PCIe Correctable Error Count : %" PRIu64 "\n", + le64_to_cpu(log->pcie_correctable_error_count)); + printf("Incomplete Shutdowns : %" PRIu32 "\n", + le32_to_cpu(log->incomplete_shutdowns)); + printf("%% Free Blocks : %" PRIu8 "%%\n", + log->percent_free_blocks); + printf("Capacitor Health : %" PRIu16 "%%\n", + le16_to_cpu(log->capacitor_health)); + printf("NVMe Errata Version : %c\n", + log->nvme_errata_ver); + printf("Unaligned IO : %" PRIu64 "\n", + le64_to_cpu(log->unaligned_io)); + printf("Security Version Number : %" PRIu64 "\n", + le64_to_cpu(log->security_version_number)); + printf("Total NUSE : %" PRIu64 "\n", + le64_to_cpu(log->total_nuse)); + printf("PLP Start Count : %'.0Lf\n", + le_to_float(log->plp_start_count, 16)); + printf("Endurance Estimate : %'.0Lf\n", + le_to_float(log->endurance_estimate, 16)); + printf("PCIe Link Retraining Count : %" PRIu64 "\n", + le64_to_cpu(log->pcie_link_retraining_cnt)); + printf("Power State Change Count : %" PRIu64 "\n", + le64_to_cpu(log->power_state_change_cnt)); + printf("Log Page Version : %" PRIu16 "\n", + le16_to_cpu(log->log_page_version)); + stringify_log_page_guid(log->log_page_guid, buf); + printf("Log Page GUID : %s\n", buf); + printf("\n\n"); +} + static int wdc_vs_smart_add_log(int argc, char **argv, struct command *command, struct plugin *plugin) { @@ -7262,8 +7838,10 @@ static int wdc_vs_smart_add_log(int argc, char **argv, struct command *command, int ret = 0; int uuid_index = 0; int page_mask = 0, num, i; + int fmt = -1; int log_page_list[16]; __u64 capabilities = 0; + __u32 device_id, read_vendor_id; struct config { uint8_t interval; @@ -7283,7 +7861,7 @@ static int wdc_vs_smart_add_log(int argc, char **argv, struct command *command, OPT_ARGS(opts) = { OPT_UINT("interval", 'i', &cfg.interval, interval), - OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), OPT_BYTE("log-page-version", 'l', &cfg.log_page_version, log_page_version), OPT_LIST("log-page-mask", 'p', &cfg.log_page_mask, log_page_mask), OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), @@ -7331,8 +7909,9 @@ static int wdc_vs_smart_add_log(int argc, char **argv, struct command *command, if (!page_mask) fprintf(stderr, "ERROR: WDC: Unknown log page mask - %s\n", cfg.log_page_mask); - capabilities = wdc_get_drive_capabilities(r, dev); + ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id); + capabilities = wdc_get_drive_capabilities(r, dev); if (!(capabilities & WDC_DRIVE_CAP_SMART_LOG_MASK)) { fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); ret = -1; @@ -7342,13 +7921,55 @@ static int wdc_vs_smart_add_log(int argc, char **argv, struct command *command, if (((capabilities & WDC_DRIVE_CAP_C0_LOG_PAGE) == WDC_DRIVE_CAP_C0_LOG_PAGE) && (page_mask & WDC_C0_PAGE_MASK)) { /* Get 0xC0 log page if possible. */ - ret = wdc_get_c0_log_page(r, dev, cfg.output_format, - uuid_index, cfg.namespace_id); - if (ret) - fprintf(stderr, "ERROR: WDC: Failure reading the C0 Log Page, ret = %d\n", ret); + if (device_id != WDC_NVME_SN861_DEV_ID) { + ret = wdc_get_c0_log_page(r, dev, cfg.output_format, + uuid_index, cfg.namespace_id); + if (ret) + fprintf(stderr, + "ERROR: WDC: Failure reading the C0 Log Page, ret = %d\n", + ret); + } else { + struct ocp_cloud_smart_log log; + char buf[2 * sizeof(log.log_page_guid) + 3]; + + fmt = validate_output_format(cfg.output_format); + if (fmt < 0) { + fprintf(stderr, "Invalid output format: %s\n", cfg.output_format); + goto out; + } + + ret = nvme_get_log_simple(dev_fd(dev), + WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID, + sizeof(log), &log); + if (!ret) { + char *ptr = buf; + int i; + __u8 *guid = log.log_page_guid; + + memset(buf, 0, sizeof(char) * 19); + + ptr += sprintf(ptr, "0x"); + for (i = 0; i < 16; i++) + ptr += sprintf(ptr, "%x", guid[15 - i]); + if (strcmp(buf, "0xafd514c97c6f4f9ca4f2bfea2810afc5")) + fprintf(stderr, "Invalid GUID: %s\n", buf); + else { + if (fmt & BINARY) + d_raw((unsigned char *)&log, sizeof(log)); + else if (fmt & JSON) + show_cloud_smart_log_json(&log); + else + show_cloud_smart_log_normal(&log, dev); + } + } else if (ret > 0) { + nvme_show_status(ret); + } else { + perror("vs-smart-add-log"); + } + } } if (((capabilities & (WDC_DRIVE_CAP_CA_LOG_PAGE)) == (WDC_DRIVE_CAP_CA_LOG_PAGE)) && - (page_mask & WDC_CA_PAGE_MASK)) { + (page_mask & WDC_CA_PAGE_MASK) && device_id != WDC_NVME_SN861_DEV_ID) { /* Get the CA Log Page */ ret = wdc_get_ca_log_page(r, dev, cfg.output_format); if (ret) @@ -8135,7 +8756,7 @@ static int wdc_get_fw_act_history(nvme_root_t r, struct nvme_dev *dev, if ((fw_act_history_hdr->num_entries > 0) && (fw_act_history_hdr->num_entries <= WDC_MAX_NUM_ACT_HIST_ENTRIES)) { ret = wdc_print_fw_act_history_log(data, fw_act_history_hdr->num_entries, - fmt, 0, 0); + fmt, 0, 0, 0); } else if (!fw_act_history_hdr->num_entries) { fprintf(stderr, "INFO: WDC: No FW Activate History entries found.\n"); ret = 0; @@ -8176,7 +8797,7 @@ static int wdc_get_fw_act_history_C2(nvme_root_t r, struct nvme_dev *dev, int ret = 0; int fmt = -1; __u8 *data; - __u32 cust_id; + __u32 cust_id = 0; struct wdc_fw_act_history_log_format_c2 *fw_act_history_log; __u32 tot_entries = 0, num_entries = 0; __u32 vendor_id = 0, device_id = 0; @@ -8214,15 +8835,20 @@ static int wdc_get_fw_act_history_C2(nvme_root_t r, struct nvme_dev *dev, if (tot_entries > 0) { /* get the FW customer id */ - cust_id = wdc_get_fw_cust_id(r, dev); - if (cust_id == WDC_INVALID_CUSTOMER_ID) { - fprintf(stderr, "%s: ERROR: WDC: invalid customer id\n", __func__); - ret = -1; - goto freeData; + if (device_id != WDC_NVME_SN861_DEV_ID) { + cust_id = wdc_get_fw_cust_id(r, dev); + if (cust_id == WDC_INVALID_CUSTOMER_ID) { + fprintf(stderr, + "%s: ERROR: WDC: invalid customer id\n", + __func__); + ret = -1; + goto freeData; + } } num_entries = (tot_entries < WDC_MAX_NUM_ACT_HIST_ENTRIES) ? tot_entries : WDC_MAX_NUM_ACT_HIST_ENTRIES; - ret = wdc_print_fw_act_history_log(data, num_entries, fmt, cust_id, vendor_id); + ret = wdc_print_fw_act_history_log(data, num_entries, + fmt, cust_id, vendor_id, device_id); } else { fprintf(stderr, "INFO: WDC: No FW Activate History entries found.\n"); ret = 0; @@ -9584,6 +10210,99 @@ static const char *nvme_log_id_to_string(__u8 log_id) } } +static void __json_log_page_directory(struct log_page_directory *directory) +{ + __u32 bitmap_idx; + __u8 log_id; + struct json_object *root; + struct json_object *entries; + + root = json_create_object(); + + entries = json_create_array(); + json_object_add_value_array(root, "Entries", entries); + + for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) { + log_id = bitmap_idx; + if (!log_page_name[log_id]) + continue; + if (directory->supported_lid_bitmap & (1ULL << bitmap_idx)) { + struct json_object *json_entry = json_create_object(); + + json_object_add_value_uint(json_entry, "Log ID", log_id); + json_object_add_value_string(json_entry, "Log Page Name", + log_page_name[log_id]); + + json_array_add_value_object(entries, json_entry); + } + } + + for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) { + log_id = NVME_LOG_NS_BASE + bitmap_idx; + if (!log_page_name[log_id]) + continue; + if (directory->supported_ns_lid_bitmap & (1ULL << bitmap_idx)) { + struct json_object *json_entry = json_create_object(); + + json_object_add_value_uint(json_entry, "Log ID", log_id); + json_object_add_value_string(json_entry, "Log Page Name", + log_page_name[log_id]); + + json_array_add_value_object(entries, json_entry); + } + } + + for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) { + log_id = NVME_LOG_VS_BASE + bitmap_idx; + if (!log_page_name[log_id]) + continue; + if (directory->supported_vs_lid_bitmap & (1ULL << bitmap_idx)) { + struct json_object *json_entry = json_create_object(); + + json_object_add_value_uint(json_entry, "Log ID", log_id); + json_object_add_value_string(json_entry, "Log Page Name", + log_page_name[log_id]); + + json_array_add_value_object(entries, json_entry); + } + } + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + + +static void __show_log_page_directory(struct log_page_directory *directory) +{ + __u32 bitmap_idx; + __u8 log_id; + + for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) { + if (directory->supported_lid_bitmap & (1ULL << bitmap_idx)) { + log_id = bitmap_idx; + if (log_page_name[log_id]) + printf("0x%02X: %s\n", log_id, log_page_name[log_id]); + } + } + + for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) { + if (directory->supported_ns_lid_bitmap & (1ULL << bitmap_idx)) { + log_id = NVME_LOG_NS_BASE + bitmap_idx; + if (log_page_name[log_id]) + printf("0x%02X: %s\n", log_id, log_page_name[log_id]); + } + } + + for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) { + if (directory->supported_vs_lid_bitmap & (1ULL << bitmap_idx)) { + log_id = NVME_LOG_VS_BASE + bitmap_idx; + if (log_page_name[log_id]) + printf("0x%02X: %s\n", log_id, log_page_name[log_id]); + } + } +} + static int wdc_log_page_directory(int argc, char **argv, struct command *command, struct plugin *plugin) { @@ -9630,53 +10349,94 @@ static int wdc_log_page_directory(int argc, char **argv, struct command *command ret = -1; } else { ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id); - log_id = (device_id == WDC_NVME_ZN350_DEV_ID || device_id == WDC_NVME_ZN350_DEV_ID_1) ? - WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE_C8 : WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE; - /* verify the 0xC2 Device Manageability log page is supported */ - if (wdc_nvme_check_supported_log_page(r, dev, log_id) == false) { - fprintf(stderr, "%s: ERROR: WDC: 0x%x Log Page not supported\n", __func__, log_id); - ret = -1; - goto out; - } - - if (get_dev_mgment_cbs_data(r, dev, WDC_C2_LOG_PAGES_SUPPORTED_ID, (void *)&cbs_data)) { - if (cbs_data) { - printf("Log Page Directory\n"); - /* print the supported pages */ - if (!strcmp(cfg.output_format, "normal")) { - for (i = 0; i < le32_to_cpu(cbs_data->length); i++) - printf("0x%x - %s\n", cbs_data->data[i], - nvme_log_id_to_string(cbs_data->data[i])); - } else if (!strcmp(cfg.output_format, "binary")) { - d((__u8 *)cbs_data->data, le32_to_cpu(cbs_data->length), 16, - 1); - } else if (!strcmp(cfg.output_format, "json")) { - struct json_object *root = json_create_object(); - - for (i = 0; i < le32_to_cpu(cbs_data->length); i++) - json_object_add_value_int(root, - nvme_log_id_to_string(cbs_data->data[i]), - cbs_data->data[i]); + log_id = (device_id == WDC_NVME_ZN350_DEV_ID || + device_id == WDC_NVME_ZN350_DEV_ID_1) ? + WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE_C8 : + WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE; + + if (device_id != WDC_NVME_SN861_DEV_ID) { + /* verify the 0xC2 Device Manageability log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, log_id) == false) { + fprintf(stderr, + "%s: ERROR: WDC: 0x%x Log Page not supported\n", + __func__, log_id); + ret = -1; + goto out; + } - json_print_object(root, NULL); - printf("\n"); - json_free_object(root); - } else { - fprintf(stderr, - "%s: ERROR: WDC: Invalid format, format = %s\n", - __func__, cfg.output_format); + if (!get_dev_mgment_cbs_data(r, dev, + WDC_C2_LOG_PAGES_SUPPORTED_ID, + (void *)&cbs_data)) { + fprintf(stderr, + "%s: ERROR: WDC: 0xC2 Log Page entry ID 0x%x not found\n", + __func__, WDC_C2_LOG_PAGES_SUPPORTED_ID); + ret = -1; + goto out; + } + if (!cbs_data) { + fprintf(stderr, "%s: ERROR: WDC: NULL_data ptr\n", __func__); + ret = -1; + goto out; + } + printf("Log Page Directory\n"); + /* print the supported pages */ + if (!strcmp(cfg.output_format, "normal")) { + for (i = 0; i < le32_to_cpu(cbs_data->length); i++) + printf("0x%x - %s\n", cbs_data->data[i], + nvme_log_id_to_string(cbs_data->data[i])); + } else if (!strcmp(cfg.output_format, "binary")) { + d((__u8 *)cbs_data->data, + le32_to_cpu(cbs_data->length), 16, 1); + } else if (!strcmp(cfg.output_format, "json")) { + struct json_object *root = json_create_object(); + + for (i = 0; i < le32_to_cpu(cbs_data->length); i++) { + json_object_add_value_int(root, + nvme_log_id_to_string(cbs_data->data[i]), + cbs_data->data[i]); } - free(cbs_data); + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); } else { - fprintf(stderr, "%s: ERROR: WDC: NULL_data ptr\n", __func__); + fprintf(stderr, + "%s: ERROR: WDC: Invalid format, format = %s\n", + __func__, cfg.output_format); } + + free(cbs_data); } else { - fprintf(stderr, "%s: ERROR: WDC: 0xC2 Log Page entry ID 0x%x not found\n", - __func__, WDC_C2_LOG_PAGES_SUPPORTED_ID); - } + struct log_page_directory *dir; + void *data = NULL; + __u32 result; + + if (posix_memalign(&data, getpagesize(), 512)) { + fprintf(stderr, + "can not allocate log page directory payload\n"); + ret = ENOMEM; + goto out; + } + dir = (struct log_page_directory *)data; + ret = nvme_admin_passthru(dev_fd(dev), WDC_NVME_ADMIN_VUC_OPCODE_D2, 0, 0, + 0, 0, 0, 8, + 0, WDC_VUC_SUBOPCODE_LOG_PAGE_DIR_D2, 0, 0, 0, + 32, data, 0, NULL, + 0, &result); + if (!ret) { + if (!strcmp(cfg.output_format, "binary")) + d_raw((unsigned char *)data, 32); + else if (!strcmp(cfg.output_format, "json")) + __json_log_page_directory(dir); + else + __show_log_page_directory(dir); + } else { + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(ret, false), ret); + } + } } out: @@ -10448,6 +11208,9 @@ static int wdc_vs_drive_info(int argc, char **argv, char rev_str[16] = { 0 }; uint32_t read_device_id = -1, read_vendor_id = -1; struct __packed wdc_nvme_ext_smart_log * ext_smart_log_ptr = NULL; + struct ocp_drive_info info; + __u32 data_len = 0; + unsigned int num_dwords = 0; struct config { char *output_format; @@ -10624,6 +11387,50 @@ static int wdc_vs_drive_info(int argc, char **argv, json_free_object(root); } + break; + case WDC_NVME_SN861_DEV_ID: + data_len = sizeof(info); + num_dwords = data_len / 4; + if (data_len % 4 != 0) + num_dwords += 1; + + ret = nvme_admin_passthru(dev_fd(dev), + WDC_NVME_ADMIN_VUC_OPCODE_D2, + 0, 0, 0, 0, 0, num_dwords, 0, + WDC_VUC_SUBOPCODE_VS_DRIVE_INFO_D2, + 0, 0, 0, data_len, &info, 0, + NULL, 0, NULL); + + if (!ret) { + __u16 hw_rev_major, hw_rev_minor; + + hw_rev_major = le32_to_cpu(info.hw_revision) / 10; + hw_rev_minor = le32_to_cpu(info.hw_revision) % 10; + if (fmt == NORMAL) { + printf("HW Revision : %" PRIu32 ".%" PRIu32 "\n", + hw_rev_major, hw_rev_minor); + printf("FTL Unit Size : %" PRIu32 "\n", + le32_to_cpu(info.ftl_unit_size)); + } else if (fmt == JSON) { + char buf[20]; + + root = json_create_object(); + + memset((void *)buf, 0, 20); + sprintf(buf, "%" PRIu32 ".%" PRIu32, + hw_rev_major, hw_rev_minor); + + json_object_add_value_string(root, + "hw_revision", buf); + json_object_add_value_uint(root, + "ftl_unit_size", + le32_to_cpu(info.ftl_unit_size)); + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); + } + } break; default: fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); @@ -10858,6 +11665,8 @@ static int wdc_capabilities(int argc, char **argv, struct command *command, stru capabilities & WDC_DRIVE_CAP_HW_REV_LOG_PAGE ? "Supported" : "Not Supported"); printf("vs-device_waf : %s\n", capabilities & WDC_DRIVE_CAP_DEVICE_WAF ? "Supported" : "Not Supported"); + printf("set-latency-monitor-feature : %s\n", + capabilities & WDC_DRIVE_CAP_SET_LATENCY_MONITOR ? "Supported" : "Not Supported"); printf("capabilities : Supported\n"); nvme_free_tree(r); dev_close(dev); @@ -11228,3 +12037,159 @@ static int wdc_enc_get_nic_log(struct nvme_dev *dev, __u8 log_id, __u32 xfer_siz free(dump_data); return ret; } + +//------------------------------------------------------------------------------------ +// Description: set latency monitor feature +// +int wdc_set_latency_monitor_feature(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "Set Latency Monitor feature."; + + uint64_t capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret; + __u32 result; + struct feature_latency_monitor buf = {0,}; + + const char *active_bucket_timer_threshold = + "This is the value that loads the Active Bucket Timer Threshold."; + const char *active_threshold_a = + "This is the value that loads into the Active Threshold A."; + const char *active_threshold_b = + "This is the value that loads into the Active Threshold B."; + const char *active_threshold_c = + "This is the value that loads into the Active Threshold C."; + const char *active_threshold_d = + "This is the value that loads into the Active Threshold D."; + const char *active_latency_config = + "This is the value that loads into the Active Latency Configuration."; + const char *active_latency_minimum_window = + "This is the value that loads into the Active Latency Minimum Window."; + const char *debug_log_trigger_enable = + "This is the value that loads into the Debug Log Trigger Enable."; + const char *discard_debug_log = "Discard Debug Log."; + const char *latency_monitor_feature_enable = "Latency Monitor Feature Enable."; + + struct config { + __u16 active_bucket_timer_threshold; + __u8 active_threshold_a; + __u8 active_threshold_b; + __u8 active_threshold_c; + __u8 active_threshold_d; + __u16 active_latency_config; + __u8 active_latency_minimum_window; + __u16 debug_log_trigger_enable; + __u8 discard_debug_log; + __u8 latency_monitor_feature_enable; + }; + + struct config cfg = { + .active_bucket_timer_threshold = 0x7E0, + .active_threshold_a = 0x5, + .active_threshold_b = 0x13, + .active_threshold_c = 0x1E, + .active_threshold_d = 0x2E, + .active_latency_config = 0xFFF, + .active_latency_minimum_window = 0xA, + .debug_log_trigger_enable = 0, + .discard_debug_log = 0, + .latency_monitor_feature_enable = 0x7, + }; + + OPT_ARGS(opts) = { + OPT_UINT("active_bucket_timer_threshold", 't', + &cfg.active_bucket_timer_threshold, + active_bucket_timer_threshold), + OPT_UINT("active_threshold_a", 'a', &cfg.active_threshold_a, + active_threshold_a), + OPT_UINT("active_threshold_b", 'b', &cfg.active_threshold_b, + active_threshold_b), + OPT_UINT("active_threshold_c", 'c', &cfg.active_threshold_c, + active_threshold_c), + OPT_UINT("active_threshold_d", 'd', &cfg.active_threshold_d, + active_threshold_d), + OPT_UINT("active_latency_config", 'f', + &cfg.active_latency_config, active_latency_config), + OPT_UINT("active_latency_minimum_window", 'w', + &cfg.active_latency_minimum_window, + active_latency_minimum_window), + OPT_UINT("debug_log_trigger_enable", 'r', + &cfg.debug_log_trigger_enable, debug_log_trigger_enable), + OPT_UINT("discard_debug_log", 'l', &cfg.discard_debug_log, + discard_debug_log), + OPT_UINT("latency_monitor_feature_enable", 'e', + &cfg.latency_monitor_feature_enable, + latency_monitor_feature_enable), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + + if (ret < 0) + return ret; + + /* get capabilities */ + r = nvme_scan(NULL); + wdc_check_device(r, dev); + capabilities = wdc_get_drive_capabilities(r, dev); + + if (!(capabilities & WDC_DRIVE_CAP_SET_LATENCY_MONITOR)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + return -1; + } + + memset(&buf, 0, sizeof(struct feature_latency_monitor)); + + buf.active_bucket_timer_threshold = cfg.active_bucket_timer_threshold; + buf.active_threshold_a = cfg.active_threshold_a; + buf.active_threshold_b = cfg.active_threshold_b; + buf.active_threshold_c = cfg.active_threshold_c; + buf.active_threshold_d = cfg.active_threshold_d; + buf.active_latency_config = cfg.active_latency_config; + buf.active_latency_minimum_window = cfg.active_latency_minimum_window; + buf.debug_log_trigger_enable = cfg.debug_log_trigger_enable; + buf.discard_debug_log = cfg.discard_debug_log; + buf.latency_monitor_feature_enable = cfg.latency_monitor_feature_enable; + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = NVME_FEAT_OCP_LATENCY_MONITOR, + .nsid = 0, + .cdw12 = 0, + .save = 1, + .data_len = sizeof(struct feature_latency_monitor), + .data = (void *)&buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + ret = nvme_set_features(&args); + + if (ret < 0) { + perror("set-feature"); + } else if (!ret) { + printf("NVME_FEAT_OCP_LATENCY_MONITOR: 0x%02x\n", + NVME_FEAT_OCP_LATENCY_MONITOR); + printf("active bucket timer threshold: 0x%x\n", + buf.active_bucket_timer_threshold); + printf("active threshold a: 0x%x\n", buf.active_threshold_a); + printf("active threshold b: 0x%x\n", buf.active_threshold_b); + printf("active threshold c: 0x%x\n", buf.active_threshold_c); + printf("active threshold d: 0x%x\n", buf.active_threshold_d); + printf("active latency config: 0x%x\n", buf.active_latency_config); + printf("active latency minimum window: 0x%x\n", + buf.active_latency_minimum_window); + printf("debug log trigger enable: 0x%x\n", + buf.debug_log_trigger_enable); + printf("discard debug log: 0x%x\n", buf.discard_debug_log); + printf("latency monitor feature enable: 0x%x\n", + buf.latency_monitor_feature_enable); + } else if (ret > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(ret, false), ret); + + return ret; +} diff --git a/plugins/wdc/wdc-nvme.h b/plugins/wdc/wdc-nvme.h index 2f39ae6b..c5d2b097 100644 --- a/plugins/wdc/wdc-nvme.h +++ b/plugins/wdc/wdc-nvme.h @@ -80,6 +80,9 @@ PLUGIN(NAME("wdc", "Western Digital vendor specific extensions", WDC_PLUGIN_VERS ENTRY("vs-device-waf", "WDC Calculate Device Write Amplication Factor", wdc_vs_device_waf) + ENTRY("set-latency-monitor-feature", + "WDC set Latency Monitor feature", + wdc_set_latency_monitor_feature) ) ); -- 2.50.1