diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-07-02 20:40:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-07-02 20:40:30 +0000 |
commit | dc597ce8df5ae6efd2728a2d7ba7d92486028f79 (patch) | |
tree | 55b9e9257eba4579667f9522368aa29f5be6754a /plugins/wdc/wdc-nvme.c | |
parent | Initial commit. (diff) | |
download | nvme-cli-dc597ce8df5ae6efd2728a2d7ba7d92486028f79.tar.xz nvme-cli-dc597ce8df5ae6efd2728a2d7ba7d92486028f79.zip |
Adding upstream version 1.12.upstream/1.12
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugins/wdc/wdc-nvme.c')
-rw-r--r-- | plugins/wdc/wdc-nvme.c | 5663 |
1 files changed, 5663 insertions, 0 deletions
diff --git a/plugins/wdc/wdc-nvme.c b/plugins/wdc/wdc-nvme.c new file mode 100644 index 0000000..0cebe3f --- /dev/null +++ b/plugins/wdc/wdc-nvme.c @@ -0,0 +1,5663 @@ +/* + * Copyright (c) 2015-2018 Western Digital Corporation or its affiliates. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com>, + * Dong Ho <dong.ho@hgst.com>, + * Jeff Lien <jeff.lien@wdc.com> + */ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <inttypes.h> +#include <errno.h> +#include <limits.h> +#include <fcntl.h> +#include <unistd.h> + +#include "linux/nvme_ioctl.h" + +#include "common.h" +#include "nvme.h" +#include "nvme-print.h" +#include "nvme-ioctl.h" +#include "plugin.h" +#include "json.h" + +#include "argconfig.h" +#include "suffix.h" +#include <sys/ioctl.h> +#define CREATE_CMD +#include "wdc-nvme.h" +#include "wdc-utils.h" + +#define WRITE_SIZE (sizeof(__u8) * 4096) + +#define WDC_NVME_SUBCMD_SHIFT 8 + +#define WDC_NVME_LOG_SIZE_DATA_LEN 0x08 +#define WDC_NVME_LOG_SIZE_HDR_LEN 0x08 + +/* Device Config */ +#define WDC_NVME_VID 0x1c58 +#define WDC_NVME_VID_2 0x1b96 +#define WDC_NVME_SNDK_VID 0x15b7 + +#define WDC_NVME_SN100_DEV_ID 0x0003 +#define WDC_NVME_SN200_DEV_ID 0x0023 +#define WDC_NVME_SN630_DEV_ID 0x2200 +#define WDC_NVME_SN630_DEV_ID_1 0x2201 +#define WDC_NVME_SN840_DEV_ID 0x2300 +#define WDC_NVME_SN840_DEV_ID_1 0x2500 +#define WDC_NVME_SN640_DEV_ID 0x2400 +#define WDC_NVME_SN640_DEV_ID_1 0x2401 +#define WDC_NVME_SN640_DEV_ID_2 0x2402 +#define WDC_NVME_SN640_DEV_ID_3 0x2404 +#define WDC_NVME_ZN440_DEV_ID 0x2600 +#define WDC_NVME_SN440_DEV_ID 0x2610 +#define WDC_NVME_SN7GC_DEV_ID 0x2700 +#define WDC_NVME_SN7GC_DEV_ID_1 0x2701 +#define WDC_NVME_SN7GC_DEV_ID_2 0x2702 +#define WDC_NVME_SXSLCL_DEV_ID 0x2001 +#define WDC_NVME_SN520_DEV_ID 0x5003 +#define WDC_NVME_SN520_DEV_ID_1 0x5004 +#define WDC_NVME_SN520_DEV_ID_2 0x5005 +#define WDC_NVME_SN720_DEV_ID 0x5002 +#define WDC_NVME_SN730A_DEV_ID 0x5006 +#define WDC_NVME_SN730B_DEV_ID 0x3714 +#define WDC_NVME_SN730B_DEV_ID_1 0x3734 +#define WDC_NVME_SN340_DEV_ID 0x500d + +#define WDC_DRIVE_CAP_CAP_DIAG 0x0000000000000001 +#define WDC_DRIVE_CAP_INTERNAL_LOG 0x0000000000000002 +#define WDC_DRIVE_CAP_C1_LOG_PAGE 0x0000000000000004 +#define WDC_DRIVE_CAP_CA_LOG_PAGE 0x0000000000000008 +#define WDC_DRIVE_CAP_D0_LOG_PAGE 0x0000000000000010 +#define WDC_DRIVE_CAP_DRIVE_STATUS 0x0000000000000020 +#define WDC_DRIVE_CAP_CLEAR_ASSERT 0x0000000000000040 +#define WDC_DRIVE_CAP_CLEAR_PCIE 0x0000000000000080 +#define WDC_DRIVE_CAP_RESIZE 0x0000000000000100 +#define WDC_DRIVE_CAP_NAND_STATS 0x0000000000000200 +#define WDC_DRIVE_CAP_DRIVE_LOG 0x0000000000000400 +#define WDC_DRIVE_CAP_CRASH_DUMP 0x0000000000000800 +#define WDC_DRIVE_CAP_PFAIL_DUMP 0x0000000000001000 +#define WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY 0x0000000000002000 +#define WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY 0x0000000000004000 +#define WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG 0x0000000000008000 +#define WDC_DRIVE_CAP_REASON_ID 0x0000000000010000 +#define WDC_DRIVE_CAP_LOG_PAGE_DIR 0x0000000000020000 +#define WDC_DRIVE_CAP_NS_RESIZE 0x0000000000040000 +#define WDC_DRIVE_CAP_INFO 0x0000000000080000 + +#define WDC_DRIVE_CAP_DRIVE_ESSENTIALS 0x0000000100000000 +#define WDC_DRIVE_CAP_DUI_DATA 0x0000000200000000 +#define WDC_SN730B_CAP_VUC_LOG 0x0000000400000000 +#define WDC_DRIVE_CAP_SN340_DUI 0x0000000800000000 +#define WDC_DRIVE_CAP_SMART_LOG_MASK (WDC_DRIVE_CAP_C1_LOG_PAGE | WDC_DRIVE_CAP_CA_LOG_PAGE | \ + WDC_DRIVE_CAP_D0_LOG_PAGE) + +/* SN730 Get Log Capabilities */ +#define SN730_NVME_GET_LOG_OPCODE 0xc2 +#define SN730_GET_FULL_LOG_LENGTH 0x00080009 +#define SN730_GET_KEY_LOG_LENGTH 0x00090009 +#define SN730_GET_COREDUMP_LOG_LENGTH 0x00120009 +#define SN730_GET_EXTENDED_LOG_LENGTH 0x00420009 + +#define SN730_GET_FULL_LOG_SUBOPCODE 0x00010009 +#define SN730_GET_KEY_LOG_SUBOPCODE 0x00020009 +#define SN730_GET_CORE_LOG_SUBOPCODE 0x00030009 +#define SN730_GET_EXTEND_LOG_SUBOPCODE 0x00040009 +#define SN730_LOG_CHUNK_SIZE 0x1000 + +/* Customer ID's */ +#define WDC_CUSTOMER_ID_GN 0x0001 +#define WDC_CUSTOMER_ID_GD 0x0101 +#define WDC_CUSTOMER_ID_0x1004 0x1004 +#define WDC_CUSTOMER_ID_0x1005 0x1005 + +/* Drive Resize */ +#define WDC_NVME_DRIVE_RESIZE_OPCODE 0xCC +#define WDC_NVME_DRIVE_RESIZE_CMD 0x03 +#define WDC_NVME_DRIVE_RESIZE_SUBCMD 0x01 + +/* Namespace Resize */ +#define WDC_NVME_NAMESPACE_RESIZE_OPCODE 0xFB + +/* Drive Info */ +#define WDC_NVME_DRIVE_INFO_OPCODE 0xC6 +#define WDC_NVME_DRIVE_INFO_CMD 0x22 +#define WDC_NVME_DRIVE_INFO_SUBCMD 0x06 + +/* Capture Diagnostics */ +#define WDC_NVME_CAP_DIAG_HEADER_TOC_SIZE WDC_NVME_LOG_SIZE_DATA_LEN +#define WDC_NVME_CAP_DIAG_OPCODE 0xE6 +#define WDC_NVME_CAP_DIAG_CMD_OPCODE 0xC6 +#define WDC_NVME_CAP_DIAG_SUBCMD 0x00 +#define WDC_NVME_CAP_DIAG_CMD 0x00 + +#define WDC_NVME_CRASH_DUMP_TYPE 1 +#define WDC_NVME_PFAIL_DUMP_TYPE 2 + +/* Capture Device Unit Info */ +#define WDC_NVME_CAP_DUI_HEADER_SIZE 0x400 +#define WDC_NVME_CAP_DUI_OPCODE 0xFA +#define WDC_NVME_CAP_DUI_DISABLE_IO 0x01 +#define WDC_NVME_DUI_MAX_SECTION 0x3A +#define WDC_NVME_DUI_MAX_SECTION_V2 0x26 +#define WDC_NVME_DUI_MAX_SECTION_V3 0x23 +#define WDC_NVME_DUI_MAX_DATA_AREA 0x05 + +/* Telemtery types for vs-internal-log command */ +#define WDC_TELEMETRY_TYPE_NONE 0x0 +#define WDC_TELEMETRY_TYPE_HOST 0x1 +#define WDC_TELEMETRY_TYPE_CONTROLLER 0x2 +#define WDC_TELEMETRY_HEADER_LENGTH 512 +#define WDC_TELEMETRY_BLOCK_SIZE 512 + +/* Crash dump */ +#define WDC_NVME_CRASH_DUMP_SIZE_DATA_LEN WDC_NVME_LOG_SIZE_DATA_LEN +#define WDC_NVME_CRASH_DUMP_SIZE_NDT 0x02 +#define WDC_NVME_CRASH_DUMP_SIZE_CMD 0x20 +#define WDC_NVME_CRASH_DUMP_SIZE_SUBCMD 0x03 + +#define WDC_NVME_CRASH_DUMP_OPCODE WDC_NVME_CAP_DIAG_CMD_OPCODE +#define WDC_NVME_CRASH_DUMP_CMD 0x20 +#define WDC_NVME_CRASH_DUMP_SUBCMD 0x04 + +/* PFail Crash dump */ +#define WDC_NVME_PF_CRASH_DUMP_SIZE_DATA_LEN WDC_NVME_LOG_SIZE_HDR_LEN +#define WDC_NVME_PF_CRASH_DUMP_SIZE_NDT 0x02 +#define WDC_NVME_PF_CRASH_DUMP_SIZE_CMD 0x20 +#define WDC_NVME_PF_CRASH_DUMP_SIZE_SUBCMD 0x05 + +#define WDC_NVME_PF_CRASH_DUMP_OPCODE WDC_NVME_CAP_DIAG_CMD_OPCODE +#define WDC_NVME_PF_CRASH_DUMP_CMD 0x20 +#define WDC_NVME_PF_CRASH_DUMP_SUBCMD 0x06 + +/* Drive Log */ +#define WDC_NVME_DRIVE_LOG_SIZE_OPCODE WDC_NVME_CAP_DIAG_CMD_OPCODE +#define WDC_NVME_DRIVE_LOG_SIZE_DATA_LEN WDC_NVME_LOG_SIZE_DATA_LEN +#define WDC_NVME_DRIVE_LOG_SIZE_NDT 0x02 +#define WDC_NVME_DRIVE_LOG_SIZE_CMD 0x20 +#define WDC_NVME_DRIVE_LOG_SIZE_SUBCMD 0x01 + +#define WDC_NVME_DRIVE_LOG_OPCODE WDC_NVME_CAP_DIAG_CMD_OPCODE +#define WDC_NVME_DRIVE_LOG_CMD 0x20 +#define WDC_NVME_DRIVE_LOG_SUBCMD 0x00 + +/* Purge and Purge Monitor */ +#define WDC_NVME_PURGE_CMD_OPCODE 0xDD +#define WDC_NVME_PURGE_MONITOR_OPCODE 0xDE +#define WDC_NVME_PURGE_MONITOR_DATA_LEN 0x2F +#define WDC_NVME_PURGE_MONITOR_CMD_CDW10 0x0000000C +#define WDC_NVME_PURGE_MONITOR_TIMEOUT 0x7530 +#define WDC_NVME_PURGE_CMD_SEQ_ERR 0x0C +#define WDC_NVME_PURGE_INT_DEV_ERR 0x06 + +#define WDC_NVME_PURGE_STATE_IDLE 0x00 +#define WDC_NVME_PURGE_STATE_DONE 0x01 +#define WDC_NVME_PURGE_STATE_BUSY 0x02 +#define WDC_NVME_PURGE_STATE_REQ_PWR_CYC 0x03 +#define WDC_NVME_PURGE_STATE_PWR_CYC_PURGE 0x04 + +/* Clear dumps */ +#define WDC_NVME_CLEAR_DUMP_OPCODE 0xFF +#define WDC_NVME_CLEAR_CRASH_DUMP_CMD 0x03 +#define WDC_NVME_CLEAR_CRASH_DUMP_SUBCMD 0x05 +#define WDC_NVME_CLEAR_PF_CRASH_DUMP_SUBCMD 0x06 + +/* Clear FW Activate History */ +#define WDC_NVME_CLEAR_FW_ACT_HIST_OPCODE 0xC6 +#define WDC_NVME_CLEAR_FW_ACT_HIST_CMD 0x23 +#define WDC_NVME_CLEAR_FW_ACT_HIST_SUBCMD 0x05 + +/* Additional Smart Log */ +#define WDC_ADD_LOG_BUF_LEN 0x4000 +#define WDC_NVME_ADD_LOG_OPCODE 0xC1 +#define WDC_GET_LOG_PAGE_SSD_PERFORMANCE 0x37 +#define WDC_NVME_GET_STAT_PERF_INTERVAL_LIFETIME 0x0F + +/* C2 Log Page */ +#define WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE 0xC2 +#define WDC_C2_LOG_BUF_LEN 0x1000 +#define WDC_C2_LOG_PAGES_SUPPORTED_ID 0x08 +#define WDC_C2_CUSTOMER_ID_ID 0x15 +#define WDC_C2_THERMAL_THROTTLE_STATUS_ID 0x18 +#define WDC_C2_ASSERT_DUMP_PRESENT_ID 0x19 +#define WDC_C2_USER_EOL_STATUS_ID 0x1A +#define WDC_C2_USER_EOL_STATE_ID 0x1C +#define WDC_C2_SYSTEM_EOL_STATE_ID 0x1D +#define WDC_C2_FORMAT_CORRUPT_REASON_ID 0x1E +#define WDC_EOL_STATUS_NORMAL cpu_to_le32(0x00000000) +#define WDC_EOL_STATUS_END_OF_LIFE cpu_to_le32(0x00000001) +#define WDC_EOL_STATUS_READ_ONLY cpu_to_le32(0x00000002) +#define WDC_ASSERT_DUMP_NOT_PRESENT cpu_to_le32(0x00000000) +#define WDC_ASSERT_DUMP_PRESENT cpu_to_le32(0x00000001) +#define WDC_THERMAL_THROTTLING_OFF cpu_to_le32(0x00000000) +#define WDC_THERMAL_THROTTLING_ON cpu_to_le32(0x00000001) +#define WDC_THERMAL_THROTTLING_UNAVAILABLE cpu_to_le32(0x00000002) +#define WDC_FORMAT_NOT_CORRUPT cpu_to_le32(0x00000000) +#define WDC_FORMAT_CORRUPT_FW_ASSERT cpu_to_le32(0x00000001) +#define WDC_FORMAT_CORRUPT_UNKNOWN cpu_to_le32(0x000000FF) + +/* CA Log Page */ +#define WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE 0xCA +#define WDC_FB_CA_LOG_BUF_LEN 0x80 +#define WDC_BD_CA_LOG_BUF_LEN 0x9C + +/* C0 EOL Status Log Page */ +#define WDC_NVME_GET_EOL_STATUS_LOG_OPCODE 0xC0 +#define WDC_NVME_EOL_STATUS_LOG_LEN 0x200 + +/* CB - FW Activate History Log Page */ +#define WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID 0xCB +#define WDC_FW_ACT_HISTORY_LOG_BUF_LEN 0x3d0 + +/* D0 Smart Log Page */ +#define WDC_NVME_GET_VU_SMART_LOG_OPCODE 0xD0 +#define WDC_NVME_VU_SMART_LOG_LEN 0x200 + +/* Log Page Directory defines */ +#define NVME_LOG_PERSISTENT_EVENT 0x0D +#define WDC_LOG_ID_C0 0xC0 +#define WDC_LOG_ID_C2 WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE +#define WDC_LOG_ID_C4 0xC4 +#define WDC_LOG_ID_C5 0xC5 +#define WDC_LOG_ID_C6 0xC6 +#define WDC_LOG_ID_CA WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE +#define WDC_LOG_ID_CB WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID +#define WDC_LOG_ID_D0 WDC_NVME_GET_VU_SMART_LOG_OPCODE +#define WDC_LOG_ID_D6 0xD6 +#define WDC_LOG_ID_D7 0xD7 +#define WDC_LOG_ID_D8 0xD8 +#define WDC_LOG_ID_DE 0xDE +#define WDC_LOG_ID_F0 0xF0 +#define WDC_LOG_ID_F1 0xF1 +#define WDC_LOG_ID_F2 0xF2 +#define WDC_LOG_ID_FA 0xFA + +/* Clear PCIe Correctable Errors */ +#define WDC_NVME_CLEAR_PCIE_CORR_OPCODE WDC_NVME_CAP_DIAG_CMD_OPCODE +#define WDC_NVME_CLEAR_PCIE_CORR_CMD 0x22 +#define WDC_NVME_CLEAR_PCIE_CORR_SUBCMD 0x04 + +/* Clear Assert Dump Status */ +#define WDC_NVME_CLEAR_ASSERT_DUMP_OPCODE 0xD8 +#define WDC_NVME_CLEAR_ASSERT_DUMP_CMD 0x03 +#define WDC_NVME_CLEAR_ASSERT_DUMP_SUBCMD 0x05 + +#define WDC_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID 0xD2 + +/* Drive Essentials */ +#define WDC_DE_DEFAULT_NUMBER_OF_ERROR_ENTRIES 64 +#define WDC_DE_GENERIC_BUFFER_SIZE 80 +#define WDC_DE_GLOBAL_NSID 0xFFFFFFFF +#define WDC_DE_DEFAULT_NAMESPACE_ID 0x01 +#define WDC_DE_PATH_SEPARATOR "/" +#define WDC_DE_TAR_FILES "*.bin" +#define WDC_DE_TAR_FILE_EXTN ".tar.gz" +#define WDC_DE_TAR_CMD "tar -czf" + +/* VS NAND Stats */ +#define WDC_NVME_NAND_STATS_LOG_ID 0xFB +#define WDC_NVME_NAND_STATS_SIZE 0x200 + +/* VU Opcodes */ +#define WDC_DE_VU_READ_SIZE_OPCODE 0xC0 +#define WDC_DE_VU_READ_BUFFER_OPCODE 0xC2 + +#define WDC_DE_FILE_HEADER_SIZE 4 +#define WDC_DE_FILE_OFFSET_SIZE 2 +#define WDC_DE_FILE_NAME_SIZE 32 +#define WDC_DE_VU_READ_BUFFER_STANDARD_OFFSET 0x8000 +#define WDC_DE_READ_MAX_TRANSFER_SIZE 0x8000 + +#define WDC_DE_MANUFACTURING_INFO_PAGE_FILE_NAME "manufacturing_info" /* Unique log entry page name. */ +#define WDC_DE_CORE_DUMP_FILE_NAME "core_dump" +#define WDC_DE_EVENT_LOG_FILE_NAME "event_log" +#define WDC_DE_DESTN_SPI 1 +#define WDC_DE_DUMPTRACE_DESTINATION 6 + +typedef enum _NVME_FEATURES_SELECT +{ + FS_CURRENT = 0, + FS_DEFAULT = 1, + FS_SAVED = 2, + FS_SUPPORTED_CAPBILITIES = 3 +} NVME_FEATURES_SELECT; + +typedef enum _NVME_FEATURE_IDENTIFIERS +{ + FID_ARBITRATION = 0x01, + FID_POWER_MANAGEMENT = 0x02, + FID_LBA_RANGE_TYPE = 0x03, + FID_TEMPERATURE_THRESHOLD = 0x04, + FID_ERROR_RECOVERY = 0x05, + FID_VOLATILE_WRITE_CACHE = 0x06, + FID_NUMBER_OF_QUEUES = 0x07, + FID_INTERRUPT_COALESCING = 0x08, + FID_INTERRUPT_VECTOR_CONFIGURATION = 0x09, + FID_WRITE_ATOMICITY = 0x0A, + FID_ASYNCHRONOUS_EVENT_CONFIGURATION = 0x0B, + FID_AUTONOMOUS_POWER_STATE_TRANSITION = 0x0C, +/*Below FID's are NVM Command Set Specific*/ + FID_SOFTWARE_PROGRESS_MARKER = 0x80, + FID_HOST_IDENTIFIER = 0x81, + FID_RESERVATION_NOTIFICATION_MASK = 0x82, + FID_RESERVATION_PERSISTENCE = 0x83 +} NVME_FEATURE_IDENTIFIERS; + +typedef enum +{ + WDC_DE_TYPE_IDENTIFY = 0x1, + WDC_DE_TYPE_SMARTATTRIBUTEDUMP = 0x2, + WDC_DE_TYPE_EVENTLOG = 0x4, + WDC_DE_TYPE_DUMPTRACE = 0x8, + WDC_DE_TYPE_DUMPSNAPSHOT = 0x10, + WDC_DE_TYPE_ATA_LOGS = 0x20, + WDC_DE_TYPE_SMART_LOGS = 0x40, + WDC_DE_TYPE_SCSI_LOGS = 0x80, + WDC_DE_TYPE_SCSI_MODE_PAGES = 0x100, + WDC_DE_TYPE_NVMe_FEATURES = 0x200, + WDC_DE_TYPE_DUMPSMARTERRORLOG3 = 0x400, + WDC_DE_TYPE_DUMPLOG3E = 0x800, + WDC_DE_TYPE_DUMPSCRAM = 0x1000, + WDC_DE_TYPE_PCU_LOG = 0x2000, + WDC_DE_TYPE_DUMP_ERROR_LOGS = 0x4000, + WDC_DE_TYPE_FW_SLOT_LOGS = 0x8000, + WDC_DE_TYPE_MEDIA_SETTINGS = 0x10000, + WDC_DE_TYPE_SMART_DATA = 0x20000, + WDC_DE_TYPE_NVME_SETTINGS = 0x40000, + WDC_DE_TYPE_NVME_ERROR_LOGS = 0x80000, + WDC_DE_TYPE_NVME_LOGS = 0x100000, + WDC_DE_TYPE_UART_LOGS = 0x200000, + WDC_DE_TYPE_DLOGS_SPI = 0x400000, + WDC_DE_TYPE_DLOGS_RAM = 0x800000, + WDC_DE_TYPE_NVME_MANF_INFO = 0x2000000, + WDC_DE_TYPE_NONE = 0x1000000, + WDC_DE_TYPE_ALL = 0xFFFFFFF, +} WDC_DRIVE_ESSENTIAL_TYPE; + +typedef struct __attribute__((__packed__)) _WDC_DE_VU_FILE_META_DATA +{ + __u8 fileName[WDC_DE_FILE_NAME_SIZE]; + __u16 fileID; + __u64 fileSize; +} WDC_DE_VU_FILE_META_DATA, *PWDC_DE_VU_FILE_META_DATA; + +typedef struct _WDC_DRIVE_ESSENTIALS +{ + WDC_DE_VU_FILE_META_DATA metaData; + WDC_DRIVE_ESSENTIAL_TYPE essentialType; +} WDC_DRIVE_ESSENTIALS; + +typedef struct _WDC_DE_VU_LOG_DIRECTORY +{ + WDC_DRIVE_ESSENTIALS *logEntry; /* Caller to allocate memory */ + __u32 maxNumLogEntries; /* Caller to input memory allocated */ + __u32 numOfValidLogEntries; /* API will output this value */ +} WDC_DE_VU_LOG_DIRECTORY,*PWDC_DE_VU_LOG_DIRECTORY; + +typedef struct _WDC_DE_CSA_FEATURE_ID_LIST +{ + NVME_FEATURE_IDENTIFIERS featureId; + __u8 featureName[WDC_DE_GENERIC_BUFFER_SIZE]; +} WDC_DE_CSA_FEATURE_ID_LIST; + +typedef struct tarfile_metadata { + char fileName[MAX_PATH_LEN]; + int8_t bufferFolderPath[MAX_PATH_LEN]; + char bufferFolderName[MAX_PATH_LEN]; + char tarFileName[MAX_PATH_LEN]; + char tarFiles[MAX_PATH_LEN]; + char tarCmd[MAX_PATH_LEN+MAX_PATH_LEN]; + char currDir[MAX_PATH_LEN]; + UtilsTimeInfo timeInfo; + uint8_t* timeString[MAX_PATH_LEN]; +} tarfile_metadata; + +static WDC_DE_CSA_FEATURE_ID_LIST deFeatureIdList[] = +{ + {0x00 , "Dummy Placeholder"}, + {FID_ARBITRATION , "Arbitration"}, + {FID_POWER_MANAGEMENT , "PowerMgmnt"}, + {FID_LBA_RANGE_TYPE , "LbaRangeType"}, + {FID_TEMPERATURE_THRESHOLD , "TempThreshold"}, + {FID_ERROR_RECOVERY , "ErrorRecovery"}, + {FID_VOLATILE_WRITE_CACHE , "VolatileWriteCache"}, + {FID_NUMBER_OF_QUEUES , "NumOfQueues"}, + {FID_INTERRUPT_COALESCING , "InterruptCoalesing"}, + {FID_INTERRUPT_VECTOR_CONFIGURATION , "InterruptVectorConfig"}, + {FID_WRITE_ATOMICITY , "WriteAtomicity"}, + {FID_ASYNCHRONOUS_EVENT_CONFIGURATION , "AsynEventConfig"}, + {FID_AUTONOMOUS_POWER_STATE_TRANSITION , "AutonomousPowerState"}, +}; + +typedef enum _NVME_VU_DE_LOGPAGE_NAMES +{ + NVME_DE_LOGPAGE_E3 = 0x01, + NVME_DE_LOGPAGE_C0 = 0x02 +} NVME_VU_DE_LOGPAGE_NAMES; +typedef struct _NVME_VU_DE_LOGPAGE_LIST +{ + NVME_VU_DE_LOGPAGE_NAMES logPageName; + __u32 logPageId; + __u32 logPageLen; + char logPageIdStr[5]; +} NVME_VU_DE_LOGPAGE_LIST, *PNVME_VU_DE_LOGPAGE_LIST; + +typedef struct _WDC_NVME_DE_VU_LOGPAGES +{ + NVME_VU_DE_LOGPAGE_NAMES vuLogPageReqd; + __u32 numOfVULogPages; +} WDC_NVME_DE_VU_LOGPAGES, *PWDC_NVME_DE_VU_LOGPAGES; + +static NVME_VU_DE_LOGPAGE_LIST deVULogPagesList[] = +{ + { NVME_DE_LOGPAGE_E3, 0xE3, 1072, "0xe3"}, + { NVME_DE_LOGPAGE_C0, 0xC0, 512, "0xc0"} +}; + +static int wdc_get_serial_name(int fd, 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(int fd, __u8 opcode, __u32 cdw12); +static int wdc_do_dump(int fd, __u32 opcode,__u32 data_len, + __u32 cdw12, char *file, __u32 xfer_size); +static int wdc_do_crash_dump(int fd, char *file, int type); +static int wdc_crash_dump(int fd, char *file, int type); +static int wdc_get_crash_dump(int argc, char **argv, struct command *command, + struct plugin *plugin); +static int wdc_do_drive_log(int fd, char *file); +static int wdc_drive_log(int argc, char **argv, struct command *command, + struct plugin *plugin); +static const char* wdc_purge_mon_status_to_string(__u32 status); +static int wdc_purge(int argc, char **argv, + struct command *command, struct plugin *plugin); +static int wdc_purge_monitor(int argc, char **argv, + struct command *command, struct plugin *plugin); +static bool wdc_nvme_check_supported_log_page(int fd, __u8 log_id); +static int wdc_clear_pcie_correctable_errors(int argc, char **argv, struct command *command, + struct plugin *plugin); +static int wdc_do_drive_essentials(int fd, char *dir, char *key); +static int wdc_drive_essentials(int argc, char **argv, struct command *command, + struct plugin *plugin); +static int wdc_drive_status(int argc, char **argv, struct command *command, + struct plugin *plugin); +static int wdc_clear_assert_dump(int argc, char **argv, struct command *command, + struct plugin *plugin); +static int wdc_drive_resize(int argc, char **argv, + struct command *command, struct plugin *plugin); +static int wdc_do_drive_resize(int fd, uint64_t new_size); +static int wdc_namespace_resize(int argc, char **argv, + struct command *command, struct plugin *plugin); +static int wdc_do_namespace_resize(int fd, __u32 nsid, __u32 op_option); +static int wdc_reason_identifier(int argc, char **argv, + struct command *command, struct plugin *plugin); +static int wdc_do_get_reason_id(int fd, char *file, int log_id); +static int wdc_save_reason_id(int fd, __u8 *rsn_ident, int size); +static int wdc_clear_reason_id(int fd); +static int wdc_dump_telemetry_hdr(int fd, int log_id, struct nvme_telemetry_log_page_hdr *log_hdr); +static int wdc_log_page_directory(int argc, char **argv, struct command *command, + struct plugin *plugin); +static int wdc_do_drive_info(int fd, __u32 *result); +static int wdc_vs_drive_info(int argc, char **argv, struct command *command, + struct plugin *plugin); + +/* Drive log data size */ +struct wdc_log_size { + __le32 log_size; +}; + +/* E6 log header */ +struct wdc_e6_log_hdr { + __le32 eye_catcher; + __u8 log_size[4]; +}; + +/* DUI log header */ +struct wdc_dui_log_section { + __le16 section_type; + __le16 data_area_id; + __le32 section_size; +}; + +/* DUI log header V2 */ +struct __attribute__((__packed__)) wdc_dui_log_section_v2 { + __le16 section_type; + __le16 data_area_id; + __le64 section_size; +}; + +struct wdc_dui_log_hdr { + __u8 telemetry_hdr[512]; + __le16 hdr_version; + __le16 section_count; + __le32 log_size; + struct wdc_dui_log_section log_section[WDC_NVME_DUI_MAX_SECTION]; + __u8 log_data[40]; +}; + +struct __attribute__((__packed__)) wdc_dui_log_hdr_v2 { + __u8 telemetry_hdr[512]; + __u8 hdr_version; + __u8 product_id; + __le16 section_count; + __le64 log_size; + struct wdc_dui_log_section_v2 log_section[WDC_NVME_DUI_MAX_SECTION_V2]; + __u8 log_data[40]; +}; + +struct __attribute__((__packed__)) wdc_dui_log_hdr_v3 { + __u8 telemetry_hdr[512]; + __u8 hdr_version; + __u8 product_id; + __le16 section_count; + __le64 log_size; + struct wdc_dui_log_section_v2 log_section[WDC_NVME_DUI_MAX_SECTION_V3]; + __u8 securityNonce[36]; + __u8 log_data[40]; +}; + +/* Purge monitor response */ +struct wdc_nvme_purge_monitor_data { + __le16 rsvd1; + __le16 rsvd2; + __le16 first_erase_failure_cnt; + __le16 second_erase_failure_cnt; + __le16 rsvd3; + __le16 programm_failure_cnt; + __le32 rsvd4; + __le32 rsvd5; + __le32 entire_progress_total; + __le32 entire_progress_current; + __u8 rsvd6[14]; +}; + +/* Additional Smart Log */ +struct wdc_log_page_header { + uint8_t num_subpages; + uint8_t reserved; + __le16 total_log_size; +}; + +struct wdc_log_page_subpage_header { + uint8_t spcode; + uint8_t pcset; + __le16 subpage_length; +}; + +struct wdc_ssd_perf_stats { + __le64 hr_cmds; /* Host Read Commands */ + __le64 hr_blks; /* Host Read Blocks */ + __le64 hr_ch_cmds; /* Host Read Cache Hit Commands */ + __le64 hr_ch_blks; /* Host Read Cache Hit Blocks */ + __le64 hr_st_cmds; /* Host Read Stalled Commands */ + __le64 hw_cmds; /* Host Write Commands */ + __le64 hw_blks; /* Host Write Blocks */ + __le64 hw_os_cmds; /* Host Write Odd Start Commands */ + __le64 hw_oe_cmds; /* Host Write Odd End Commands */ + __le64 hw_st_cmds; /* Host Write Commands Stalled */ + __le64 nr_cmds; /* NAND Read Commands */ + __le64 nr_blks; /* NAND Read Blocks */ + __le64 nw_cmds; /* NAND Write Commands */ + __le64 nw_blks; /* NAND Write Blocks */ + __le64 nrbw; /* NAND Read Before Write */ +}; + +/* Additional C2 Log Page */ +struct wdc_c2_log_page_header { + __le32 length; + __le32 version; +}; + +struct wdc_c2_log_subpage_header { + __le32 length; + __le32 entry_id; + __le32 data; +}; + +struct wdc_c2_cbs_data { + __le32 length; + __u8 data[]; +}; + +struct wdc_bd_ca_log_format { + __u8 field_id; + __u8 reserved1[2]; + __u8 normalized_value; + __u8 reserved2; + __u8 raw_value[7]; +}; + +struct __attribute__((__packed__)) wdc_ssd_ca_perf_stats { + __le64 nand_bytes_wr_lo; /* 0x00 - NAND Bytes Written lo */ + __le64 nand_bytes_wr_hi; /* 0x08 - NAND Bytes Written hi */ + __le64 nand_bytes_rd_lo; /* 0x10 - NAND Bytes Read lo */ + __le64 nand_bytes_rd_hi; /* 0x18 - NAND Bytes Read hi */ + __le64 nand_bad_block; /* 0x20 - NAND Bad Block Count */ + __le64 uncorr_read_count; /* 0x28 - Uncorrectable Read Count */ + __le64 ecc_error_count; /* 0x30 - Soft ECC Error Count */ + __le32 ssd_detect_count; /* 0x38 - SSD End to End Detection Count */ + __le32 ssd_correct_count; /* 0x3C - SSD End to End Correction Count */ + __u8 data_percent_used; /* 0x40 - System Data Percent Used */ + __le32 data_erase_max; /* 0x41 - User Data Erase Counts */ + __le32 data_erase_min; /* 0x45 - User Data Erase Counts */ + __le64 refresh_count; /* 0x49 - Refresh Count */ + __le64 program_fail; /* 0x51 - Program Fail Count */ + __le64 user_erase_fail; /* 0x59 - User Data Erase Fail Count */ + __le64 system_erase_fail; /* 0x61 - System Area Erase Fail Count */ + __u8 thermal_throttle_status; /* 0x69 - Thermal Throttling Status */ + __u8 thermal_throttle_count; /* 0x6A - Thermal Throttling Count */ + __le64 pcie_corr_error; /* 0x6B - pcie Correctable Error Count */ + __le32 incomplete_shutdown_count; /* 0x73 - Incomplete Shutdown Count */ + __u8 percent_free_blocks; /* 0x77 - Percent Free Blocks */ + __u8 rsvd[392]; /* 0x78 - Reserved bytes 120-511 */ +}; + +struct __attribute__((__packed__)) wdc_ssd_d0_smart_log { + __le32 smart_log_page_header; /* 0x00 - Smart Log Page Header */ + __le32 lifetime_realloc_erase_block_count; /* 0x04 - Lifetime reallocated erase block count */ + __le32 lifetime_power_on_hours; /* 0x08 - Lifetime power on hours */ + __le32 lifetime_uecc_count; /* 0x0C - Lifetime UECC count */ + __le32 lifetime_wrt_amp_factor; /* 0x10 - Lifetime write amplification factor */ + __le32 trailing_hr_wrt_amp_factor; /* 0x14 - Trailing hour write amplification factor */ + __le32 reserve_erase_block_count; /* 0x18 - Reserve erase block count */ + __le32 lifetime_program_fail_count; /* 0x1C - Lifetime program fail count */ + __le32 lifetime_block_erase_fail_count; /* 0x20 - Lifetime block erase fail count */ + __le32 lifetime_die_failure_count; /* 0x24 - Lifetime die failure count */ + __le32 lifetime_link_rate_downgrade_count; /* 0x28 - Lifetime link rate downgrade count */ + __le32 lifetime_clean_shutdown_count; /* 0x2C - Lifetime clean shutdown count on power loss */ + __le32 lifetime_unclean_shutdown_count; /* 0x30 - Lifetime unclean shutdowns on power loss */ + __le32 current_temp; /* 0x34 - Current temperature */ + __le32 max_recorded_temp; /* 0x38 - Max recorded temperature */ + __le32 lifetime_retired_block_count; /* 0x3C - Lifetime retired block count */ + __le32 lifetime_read_disturb_realloc_events; /* 0x40 - Lifetime read disturb reallocation events */ + __le64 lifetime_nand_writes; /* 0x44 - Lifetime NAND write Lpages */ + __le32 capacitor_health; /* 0x4C - Capacitor health */ + __le64 lifetime_user_writes; /* 0x50 - Lifetime user writes */ + __le64 lifetime_user_reads; /* 0x58 - Lifetime user reads */ + __le32 lifetime_thermal_throttle_act; /* 0x60 - Lifetime thermal throttle activations */ + __le32 percentage_pe_cycles_remaining; /* 0x64 - Percentage of P/E cycles remaining */ + __u8 rsvd[408]; /* 0x68 - 408 Reserved bytes */ +}; + +/* NAND Stats */ +struct __attribute__((__packed__)) wdc_nand_stats { + __u8 nand_write_tlc[16]; + __u8 nand_write_slc[16]; + __le32 nand_prog_failure; + __le32 nand_erase_failure; + __le32 bad_block_count; + __le64 nand_rec_trigger_event; + __le64 e2e_error_counter; + __le64 successful_ns_resize_event; + __u8 rsvd[444]; +}; + +struct wdc_fw_act_history_log_hdr { + __le32 eye_catcher; + __u8 version; + __u8 reserved1; + __u8 num_entries; + __u8 reserved2; + __le32 entry_size; + __le32 reserved3; +}; + +struct wdc_fw_act_history_log_entry { + __le32 entry_num; + __le32 power_cycle_count; + __le64 power_on_seconds; + __le64 previous_fw_version; + __le64 new_fw_version; + __u8 slot_number; + __u8 commit_action_type; + __le16 result; + __u8 reserved[12]; +}; + +#define WDC_REASON_INDEX_MAX 16 +#define WDC_REASON_ID_ENTRY_LEN 128 +#define WDC_REASON_ID_PATH_NAME "/usr/local/nvmecli" + + +static double safe_div_fp(double numerator, double denominator) +{ + return denominator ? numerator / denominator : 0; +} + +static double calc_percent(uint64_t numerator, uint64_t denominator) +{ + return denominator ? + (uint64_t)(((double)numerator / (double)denominator) * 100) : 0; +} + +static long double int128_to_double(__u8 *data) +{ + int i; + long double result = 0; + + for (i = 0; i < 16; i++) { + result *= 256; + result += data[15 - i]; + } + return result; +} + +static int wdc_get_pci_ids(uint32_t *device_id, uint32_t *vendor_id) +{ + int fd, ret = -1; + char *block, path[512], *id; + + id = calloc(1, 32); + if (!id) { + fprintf(stderr, "ERROR : WDC : %s : calloc failed\n", __func__); + return -1; + } + + block = nvme_char_from_block((char *)devicename); + + /* read the vendor ID from sys fs */ + sprintf(path, "/sys/class/nvme/%s/device/vendor", block); + + fd = open(path, O_RDONLY); + if (fd < 0) { + sprintf(path, "/sys/class/misc/%s/device/vendor", block); + fd = open(path, O_RDONLY); + } + if (fd < 0) { + fprintf(stderr, "ERROR : WDC : %s : Open vendor file failed\n", __func__); + ret = -1; + goto free_id; + } + + ret = read(fd, id, 32); + if (ret < 0) { + fprintf(stderr, "%s: Read of pci vendor id failed\n", __func__); + ret = -1; + goto close_fd; + } else { + if (id[strlen(id) - 1] == '\n') + id[strlen(id) - 1] = '\0'; + + /* convert the device id string to an int */ + *vendor_id = (int)strtol(&id[2], NULL, 16); + ret = 0; + } + + /* read the device ID from sys fs */ + sprintf(path, "/sys/class/nvme/%s/device/device", block); + + fd = open(path, O_RDONLY); + if (fd < 0) { + sprintf(path, "/sys/class/misc/%s/device/device", block); + fd = open(path, O_RDONLY); + } + if (fd < 0) { + fprintf(stderr, "ERROR : WDC : %s : Open device file failed\n", __func__); + ret = -1; + goto close_fd; + } + + ret = read(fd, id, 32); + if (ret < 0) { + fprintf(stderr, "%s: Read of pci device id failed\n", __func__); + ret = -1; + } else { + if (id[strlen(id) - 1] == '\n') + id[strlen(id) - 1] = '\0'; + + /* convert the device id string to an int */ + *device_id = strtol(&id[2], NULL, 16); + ret = 0; + } + +close_fd: + close(fd); +free_id: + free(block); + free(id); + return ret; +} + +static bool wdc_check_device(int fd) +{ + int ret; + bool supported; + uint32_t read_device_id, read_vendor_id; + + ret = wdc_get_pci_ids(&read_device_id, &read_vendor_id); + if (ret < 0) + return false; + + supported = false; + + if (read_vendor_id == WDC_NVME_VID || + read_vendor_id == WDC_NVME_VID_2 || + read_vendor_id == WDC_NVME_SNDK_VID) + supported = true; + else + fprintf(stderr, "ERROR : WDC: unsupported WDC device, Vendor ID = 0x%x, Device ID = 0x%x\n", + read_vendor_id, read_device_id); + + return supported; +} + +static __u64 wdc_get_drive_capabilities(int fd) { + int ret; + uint32_t read_device_id, read_vendor_id; + __u64 capabilities = 0; + + ret = wdc_get_pci_ids(&read_device_id, &read_vendor_id); + if (ret < 0) + return capabilities; + + switch (read_vendor_id) { + case WDC_NVME_VID: + switch (read_device_id) { + case WDC_NVME_SN100_DEV_ID: + capabilities = (WDC_DRIVE_CAP_CAP_DIAG | WDC_DRIVE_CAP_INTERNAL_LOG | WDC_DRIVE_CAP_C1_LOG_PAGE | + WDC_DRIVE_CAP_DRIVE_LOG | WDC_DRIVE_CAP_CRASH_DUMP | WDC_DRIVE_CAP_PFAIL_DUMP); + break; + case WDC_NVME_SN200_DEV_ID: + 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; + default: + capabilities = 0; + } + break; + case WDC_NVME_VID_2: + switch (read_device_id) { + case WDC_NVME_SN630_DEV_ID: + /* FALLTHRU */ + case WDC_NVME_SN630_DEV_ID_1: + 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); + /* 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_SN640_DEV_ID: + /* FALLTHRU */ + case WDC_NVME_SN640_DEV_ID_1: + /* FALLTHRU */ + case WDC_NVME_SN640_DEV_ID_2: + /* FALLTHRU */ + case WDC_NVME_SN640_DEV_ID_3: + /* FALLTHRU */ + case WDC_NVME_SN840_DEV_ID: + /* FALLTHRU */ + case WDC_NVME_SN840_DEV_ID_1: + /* FALLTHRU */ + case WDC_NVME_ZN440_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: + 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_FW_ACTIVATE_HISTORY | WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY | + WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG | WDC_DRIVE_CAP_REASON_ID | + WDC_DRIVE_CAP_LOG_PAGE_DIR | WDC_DRIVE_CAP_INFO); + + /* 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_SN730B_DEV_ID: + /* FALLTHRU */ + case WDC_NVME_SN730B_DEV_ID_1: + capabilities = WDC_SN730B_CAP_VUC_LOG; + break; + default: + capabilities = 0; + } + break; + case WDC_NVME_SNDK_VID: + switch (read_device_id) { + case WDC_NVME_SXSLCL_DEV_ID: + capabilities = WDC_DRIVE_CAP_DRIVE_ESSENTIALS; + break; + case WDC_NVME_SN520_DEV_ID: + /* FALLTHRU */ + case WDC_NVME_SN520_DEV_ID_1: + /* FALLTHRU */ + case WDC_NVME_SN520_DEV_ID_2: + capabilities = WDC_DRIVE_CAP_DUI_DATA; + break; + case WDC_NVME_SN720_DEV_ID: + capabilities = WDC_DRIVE_CAP_DUI_DATA | WDC_DRIVE_CAP_NAND_STATS | WDC_DRIVE_CAP_NS_RESIZE; + break; + case WDC_NVME_SN730A_DEV_ID: + capabilities = WDC_DRIVE_CAP_DUI_DATA | WDC_DRIVE_CAP_NAND_STATS; + break; + case WDC_NVME_SN340_DEV_ID: + capabilities = WDC_DRIVE_CAP_SN340_DUI; + break; + default: + capabilities = 0; + } + break; + default: + capabilities = 0; + } + + return capabilities; +} + +static int wdc_get_serial_name(int fd, 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(fd, &ctrl); + if (ret) { + fprintf(stderr, "ERROR : WDC : nvme_identify_ctrl() failed " + "0x%x\n", ret); + return -1; + } + /* Remove trailing spaces from the name */ + while (i && ctrl.sn[i] == ' ') { + ctrl.sn[i] = '\0'; + i--; + } + 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 : WDC : cannot format serial number due to data " + "of unexpected length\n"); + return -1; + } + + return 0; +} + +static int wdc_create_log_file(char *file, __u8 *drive_log_data, + __u32 drive_log_length) +{ + int fd; + int ret; + + if (drive_log_length == 0) { + fprintf(stderr, "ERROR : WDC: invalid log file length\n"); + return -1; + } + + fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) { + fprintf(stderr, "ERROR : WDC: open : %s\n", strerror(errno)); + return -1; + } + + while (drive_log_length > WRITE_SIZE) { + ret = write(fd, drive_log_data, WRITE_SIZE); + if (ret < 0) { + fprintf (stderr, "ERROR : WDC: write : %s\n", strerror(errno)); + return -1; + } + drive_log_data += WRITE_SIZE; + drive_log_length -= WRITE_SIZE; + } + + ret = write(fd, drive_log_data, drive_log_length); + if (ret < 0) { + fprintf(stderr, "ERROR : WDC : write : %s\n", strerror(errno)); + return -1; + } + + if (fsync(fd) < 0) { + fprintf(stderr, "ERROR : WDC : fsync : %s\n", strerror(errno)); + return -1; + } + close(fd); + return 0; +} + +static bool get_dev_mgment_cbs_data(int fd, __u8 log_id, void **cbs_data) +{ + int ret = -1; + __u8* data; + struct wdc_c2_log_page_header *hdr_ptr; + struct wdc_c2_log_subpage_header *sph; + __u32 length = 0; + bool found = false; + + *cbs_data = NULL; + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_C2_LOG_BUF_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return false; + } + memset(data, 0, sizeof (__u8) * WDC_C2_LOG_BUF_LEN); + + /* get the log page length */ + ret = nvme_get_log(fd, 0xFFFFFFFF, WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE, + false, WDC_C2_LOG_BUF_LEN, data); + if (ret) { + fprintf(stderr, "ERROR : WDC : Unable to get C2 Log Page length, ret = 0x%x\n", ret); + goto end; + } + + hdr_ptr = (struct wdc_c2_log_page_header *)data; + + if (le32_to_cpu(hdr_ptr->length) > WDC_C2_LOG_BUF_LEN) { + /* Log Page buffer too small, free and reallocate the necessary size */ + free(data); + data = calloc(le32_to_cpu(hdr_ptr->length), sizeof(__u8)); + if (data == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return false; + } + } + + ret = nvme_get_log(fd, 0xFFFFFFFF, WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE, + false, le32_to_cpu(hdr_ptr->length), data); + /* parse the data until the List of log page ID's is found */ + if (ret) { + fprintf(stderr, "ERROR : WDC : Unable to read C2 Log Page data, ret = 0x%x\n", ret); + goto end; + } + + length = sizeof(struct wdc_c2_log_page_header); + hdr_ptr = (struct wdc_c2_log_page_header *)data; + + while (length < le32_to_cpu(hdr_ptr->length)) { + sph = (struct wdc_c2_log_subpage_header *)(data + length); + + if (le32_to_cpu(sph->entry_id) == log_id) { + *cbs_data = (void *)&sph->data; + found = true; + break; + } + length += le32_to_cpu(sph->length); + } + +end: + free(data); + return found; +} + +static bool wdc_nvme_check_supported_log_page(int fd, __u8 log_id) +{ + int i; + bool found = false; + struct wdc_c2_cbs_data *cbs_data = NULL; + + if (get_dev_mgment_cbs_data(fd, WDC_C2_LOG_PAGES_SUPPORTED_ID, (void *)&cbs_data)) { + if (cbs_data != NULL) { + for (i = 0; i < le32_to_cpu(cbs_data->length); i++) { + if (log_id == cbs_data->data[i]) { + found = true; + break; + } + } + +#ifdef WDC_NVME_CLI_DEBUG + if (!found) { + fprintf(stderr, "ERROR : WDC : Log Page 0x%x not supported\n", log_id); + fprintf(stderr, "WDC : Supported Log Pages:\n"); + /* print the supported pages */ + d((__u8 *)cbs_data->data, le32_to_cpu(cbs_data->length), 16, 1); + } +#endif + } else + fprintf(stderr, "ERROR : WDC : cbs_data ptr = NULL\n"); + } else + fprintf(stderr, "ERROR : WDC : 0xC2 Log Page entry ID 0x%x not found\n", WDC_C2_LOG_PAGES_SUPPORTED_ID); + + return found; +} + +static bool wdc_nvme_get_dev_status_log_data(int fd, __le32 *ret_data, + __u8 log_id) +{ + __u32 *cbs_data = NULL; + + if (get_dev_mgment_cbs_data(fd, log_id, (void *)&cbs_data)) { + if (cbs_data != NULL) { + memcpy((void *)ret_data, (void *)cbs_data, 4); + return true; + } + } + + *ret_data = 0; + return false; +} + +static int wdc_do_clear_dump(int fd, __u8 opcode, __u32 cdw12) +{ + int ret; + struct nvme_admin_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = opcode; + admin_cmd.cdw12 = cdw12; + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + if (ret != 0) { + fprintf(stdout, "ERROR : WDC : Crash dump erase failed\n"); + } + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + return ret; +} + +static __u32 wdc_dump_length(int fd, __u32 opcode, __u32 cdw10, __u32 cdw12, __u32 *dump_length) +{ + int ret; + __u8 buf[WDC_NVME_LOG_SIZE_DATA_LEN] = {0}; + struct wdc_log_size *l; + struct nvme_admin_cmd admin_cmd; + + l = (struct wdc_log_size *) buf; + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = opcode; + admin_cmd.addr = (__u64)(uintptr_t)buf; + admin_cmd.data_len = WDC_NVME_LOG_SIZE_DATA_LEN; + admin_cmd.cdw10 = cdw10; + admin_cmd.cdw12 = cdw12; + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + if (ret != 0) { + l->log_size = 0; + ret = -1; + fprintf(stderr, "ERROR : WDC : reading dump length failed\n"); + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + return ret; + } + + if (opcode == WDC_NVME_CAP_DIAG_OPCODE) + *dump_length = buf[0x04] << 24 | buf[0x05] << 16 | buf[0x06] << 8 | buf[0x07]; + else + *dump_length = le32_to_cpu(l->log_size); + return ret; +} + +static __u32 wdc_dump_length_e6(int fd, __u32 opcode, __u32 cdw10, __u32 cdw12, struct wdc_e6_log_hdr *dump_hdr) +{ + int ret; + struct nvme_admin_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = opcode; + admin_cmd.addr = (__u64)(uintptr_t)dump_hdr; + admin_cmd.data_len = WDC_NVME_LOG_SIZE_HDR_LEN; + admin_cmd.cdw10 = cdw10; + admin_cmd.cdw12 = cdw12; + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + if (ret != 0) { + fprintf(stderr, "ERROR : WDC : reading dump length failed\n"); + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + } + + return ret; +} + +static __u32 wdc_dump_dui_data(int fd, __u32 dataLen, __u32 offset, __u8 *dump_data, bool last_xfer) +{ + int ret; + struct nvme_admin_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = WDC_NVME_CAP_DUI_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; + if (last_xfer) + admin_cmd.cdw14 = 0; + else + admin_cmd.cdw14 = WDC_NVME_CAP_DUI_DISABLE_IO; + + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + if (ret != 0) { + fprintf(stderr, "ERROR : WDC : reading DUI data failed\n"); + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + } + + return ret; +} + +static __u32 wdc_dump_dui_data_v2(int fd, __u32 dataLen, __u64 offset, __u8 *dump_data, bool last_xfer) +{ + int ret; + struct nvme_admin_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = WDC_NVME_CAP_DUI_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 = (__u32)(offset & 0x00000000FFFFFFFF); + admin_cmd.cdw13 = (__u32)(offset >> 32); + if (last_xfer) + admin_cmd.cdw14 = 0; + else + admin_cmd.cdw14 = WDC_NVME_CAP_DUI_DISABLE_IO; + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + if (ret != 0) { + fprintf(stderr, "ERROR : WDC : reading DUI data V2 failed\n"); + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + } + + return ret; +} + +static int wdc_do_dump(int fd, __u32 opcode,__u32 data_len, + __u32 cdw12, char *file, __u32 xfer_size) +{ + int ret = 0; + __u8 *dump_data; + __u32 curr_data_offset, curr_data_len; + int i; + struct nvme_admin_cmd admin_cmd; + __u32 dump_length = data_len; + + 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; + + admin_cmd.opcode = opcode; + admin_cmd.addr = (__u64)(uintptr_t)dump_data; + admin_cmd.data_len = curr_data_len; + admin_cmd.cdw10 = curr_data_len >> 2; + admin_cmd.cdw12 = cdw12; + admin_cmd.cdw13 = curr_data_offset; + + while (curr_data_offset < data_len) { + 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; + admin_cmd.addr = (__u64)(uintptr_t)dump_data + (__u64)curr_data_offset; + admin_cmd.data_len = curr_data_len; + admin_cmd.cdw10 = curr_data_len >> 2; + admin_cmd.cdw13 = curr_data_offset >> 2; + i++; + } + + if (ret == 0) { + fprintf(stderr, "%s: NVMe Status:%s(%x)\n", __func__, nvme_status_to_string(ret), ret); + ret = wdc_create_log_file(file, dump_data, dump_length); + } + free(dump_data); + return ret; +} + +static int wdc_do_dump_e6(int fd, __u32 opcode,__u32 data_len, + __u32 cdw12, char *file, __u32 xfer_size, __u8 *log_hdr) +{ + int ret = 0; + __u8 *dump_data; + __u32 curr_data_offset, log_size; + int i; + struct nvme_admin_cmd admin_cmd; + + dump_data = (__u8 *) malloc(sizeof (__u8) * data_len); + + if (dump_data == NULL) { + fprintf(stderr, "%s: ERROR : malloc : %s\n", __func__, strerror(errno)); + return -1; + } + memset(dump_data, 0, sizeof (__u8) * data_len); + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + curr_data_offset = WDC_NVME_LOG_SIZE_HDR_LEN; + i = 0; + + /* copy the 8 byte header into the dump_data buffer */ + memcpy(dump_data, log_hdr, WDC_NVME_LOG_SIZE_HDR_LEN); + + admin_cmd.opcode = opcode; + admin_cmd.cdw12 = cdw12; + + log_size = data_len; + while (log_size > 0) { + xfer_size = min(xfer_size, log_size); + + admin_cmd.addr = (__u64)(uintptr_t)dump_data + (__u64)curr_data_offset; + admin_cmd.data_len = xfer_size; + admin_cmd.cdw10 = xfer_size >> 2; + admin_cmd.cdw13 = curr_data_offset >> 2; + + 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; + } + + log_size -= xfer_size; + curr_data_offset += xfer_size; + i++; + } + + if (ret == 0) { + fprintf(stderr, "%s: NVMe Status:%s(%x)\n", __func__, nvme_status_to_string(ret), ret); + } else { + fprintf(stderr, "%s: FAILURE: NVMe Status:%s(%x)\n", __func__, nvme_status_to_string(ret), ret); + fprintf(stderr, "%s: Partial data may have been captured\n", __func__); + snprintf(file + strlen(file), PATH_MAX, "%s", "-PARTIAL"); + } + + ret = wdc_create_log_file(file, dump_data, data_len); + + free(dump_data); + return ret; +} + +static int wdc_do_cap_telemetry_log(int fd, char *file, __u32 bs, int type, int data_area) +{ + struct nvme_telemetry_log_page_hdr *hdr; + size_t full_size, offset = WDC_TELEMETRY_HEADER_LENGTH; + int err = 0, output; + void *page_log; + __u32 host_gen = 1; + int ctrl_init = 0; + __u32 result; + void *buf = NULL; + + + if (type == WDC_TELEMETRY_TYPE_HOST) { + host_gen = 1; + ctrl_init = 0; + } else if (type == WDC_TELEMETRY_TYPE_CONTROLLER) { + /* Verify the Controller Initiated Option is enabled */ + err = nvme_get_feature(fd, 0, WDC_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID, 0, 0, + 4, buf, &result); + if (err == 0) { + if (result == 0) { + /* enabled */ + host_gen = 0; + ctrl_init = 1; + } + else { + fprintf(stderr, "%s: Controller initiated option telemetry log page disabled\n", __func__); + err = -EINVAL; + goto close_fd; + } + } else { + fprintf(stderr, "ERROR : WDC: Get telemetry option feature failed. NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + err = -EPERM; + goto close_fd; + } + } else { + fprintf(stderr, "%s: Invalid type parameter; type = %d\n", __func__, type); + err = -EINVAL; + goto close_fd; + } + + if (!file) { + fprintf(stderr, "%s: Please provide an output file!\n", __func__); + err = -EINVAL; + goto close_fd; + } + + hdr = malloc(bs); + page_log = malloc(bs); + if (!hdr || !page_log) { + fprintf(stderr, "%s: Failed to allocate 0x%x bytes for log: %s\n", + __func__, bs, strerror(errno)); + err = -ENOMEM; + goto free_mem; + } + memset(hdr, 0, bs); + + 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)); + err = output; + goto free_mem; + } + + err = nvme_get_telemetry_log(fd, hdr, host_gen, ctrl_init, WDC_TELEMETRY_HEADER_LENGTH, 0); + if (err < 0) + perror("get-telemetry-log"); + else if (err > 0) { + nvme_show_status(err); + fprintf(stderr, "%s: Failed to acquire telemetry header!\n", __func__); + goto close_output; + } + + err = write(output, (void *) hdr, WDC_TELEMETRY_HEADER_LENGTH); + if (err != WDC_TELEMETRY_HEADER_LENGTH) { + fprintf(stderr, "%s: Failed to flush header data to file!, err = %d\n", __func__, err); + goto close_output; + } + + switch (data_area) { + case 1: + full_size = (le16_to_cpu(hdr->dalb1) * WDC_TELEMETRY_BLOCK_SIZE) + WDC_TELEMETRY_HEADER_LENGTH; + break; + case 2: + full_size = (le16_to_cpu(hdr->dalb2) * WDC_TELEMETRY_BLOCK_SIZE) + WDC_TELEMETRY_HEADER_LENGTH; + break; + case 3: + full_size = (le16_to_cpu(hdr->dalb3) * WDC_TELEMETRY_BLOCK_SIZE) + WDC_TELEMETRY_HEADER_LENGTH; + break; + default: + fprintf(stderr, "%s: Invalid data area requested, data area = %d\n", __func__, data_area); + err = -EINVAL; + goto close_output; + } + + /* + * Continuously pull data until the offset hits the end of the last + * block. + */ + while (offset < full_size) { + if ((full_size - offset) < bs) + bs = (full_size - offset); + + + err = nvme_get_telemetry_log(fd, page_log, 0, ctrl_init, bs, offset); + if (err < 0) { + perror("get-telemetry-log"); + break; + } else if (err > 0) { + nvme_show_status(err); + fprintf(stderr, "%s: Failed to acquire full telemetry log!\n", __func__); + nvme_show_status(err); + break; + } + + err = write(output, (void *) page_log, bs); + if (err != bs) { + fprintf(stderr, "%s: Failed to flush telemetry data to file!, err = %d\n", __func__, err); + break; + } + err = 0; + offset += bs; + } + +close_output: + close(output); +free_mem: + free(hdr); + free(page_log); +close_fd: + close(fd); + + return err; + +} + +static int wdc_do_cap_diag(int fd, char *file, __u32 xfer_size, int type, int data_area) +{ + int ret = -1; + __u32 e6_log_hdr_size = WDC_NVME_CAP_DIAG_HEADER_TOC_SIZE; + struct wdc_e6_log_hdr *log_hdr; + __u32 cap_diag_length; + + log_hdr = (struct wdc_e6_log_hdr *) malloc(e6_log_hdr_size); + if (log_hdr == NULL) { + fprintf(stderr, "%s: ERROR : malloc : %s\n", __func__, strerror(errno)); + ret = -1; + goto out; + } + memset(log_hdr, 0, e6_log_hdr_size); + + if (type == WDC_TELEMETRY_TYPE_NONE) { + ret = wdc_dump_length_e6(fd, WDC_NVME_CAP_DIAG_OPCODE, + WDC_NVME_CAP_DIAG_HEADER_TOC_SIZE>>2, + 0x00, + log_hdr); + if (ret == -1) { + ret = -1; + goto out; + } + + cap_diag_length = (log_hdr->log_size[0] << 24 | log_hdr->log_size[1] << 16 | + log_hdr->log_size[2] << 8 | log_hdr->log_size[3]); + + if (cap_diag_length == 0) { + fprintf(stderr, "INFO : WDC : Capture Diagnostics log is empty\n"); + } else { + ret = wdc_do_dump_e6(fd, WDC_NVME_CAP_DIAG_OPCODE, cap_diag_length, + (WDC_NVME_CAP_DIAG_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | WDC_NVME_CAP_DIAG_CMD, + file, xfer_size, (__u8 *)log_hdr); + + fprintf(stderr, "INFO : WDC : Capture Diagnostics log, length = 0x%x\n", cap_diag_length); + } + } else if ((type == WDC_TELEMETRY_TYPE_HOST) || + (type == WDC_TELEMETRY_TYPE_CONTROLLER)) { + /* Get the desired telemetry log page */ + ret = wdc_do_cap_telemetry_log(fd, file, xfer_size, type, data_area); + } else + fprintf(stderr, "%s: ERROR : Invalid type : %d\n", __func__, type); + +out: + free(log_hdr); + return ret; +} + +static int wdc_do_cap_dui(int fd, char *file, __u32 xfer_size, int data_area, int verbose, __u64 file_size, __u64 offset) +{ + int ret = 0; + __u32 dui_log_hdr_size = WDC_NVME_CAP_DUI_HEADER_SIZE; + struct wdc_dui_log_hdr *log_hdr; + struct wdc_dui_log_hdr_v3 *log_hdr_v3; + __u32 cap_dui_length; + __u64 cap_dui_length_v3; + __u8 *dump_data = NULL; + __u8 *buffer_addr; + __s64 total_size = 0; + int i; + int j; + bool last_xfer = false; + int err = 0, output = 0; + + log_hdr = (struct wdc_dui_log_hdr *) malloc(dui_log_hdr_size); + if (log_hdr == NULL) { + fprintf(stderr, "%s: ERROR : log header malloc failed : status %s, size 0x%x\n", + __func__, strerror(errno), dui_log_hdr_size); + return -1; + } + memset(log_hdr, 0, dui_log_hdr_size); + + /* get the dui telemetry and log headers */ + ret = wdc_dump_dui_data(fd, WDC_NVME_CAP_DUI_HEADER_SIZE, 0x00, (__u8 *)log_hdr, last_xfer); + if (ret != 0) { + fprintf(stderr, "%s: ERROR : WDC : Get DUI headers failed\n", __func__); + fprintf(stderr, "%s: ERROR : WDC : NVMe Status:%s(%x)\n", __func__, nvme_status_to_string(ret), ret); + goto out; + } + + /* Check the Log Header version */ + if (((log_hdr->hdr_version & 0xFF) == 0x02) || + ((log_hdr->hdr_version & 0xFF) == 0x03)) { /* Process Version 2 or 3 header */ + __s64 log_size = 0; + __u64 curr_data_offset = 0; + __u64 xfer_size_long = (__u64)xfer_size; + + log_hdr_v3 = (struct wdc_dui_log_hdr_v3 *)log_hdr; + + cap_dui_length_v3 = le64_to_cpu(log_hdr_v3->log_size); + + if (verbose) { + fprintf(stderr, "INFO : WDC : Capture V2 or V3 Device Unit Info log, data area = %d\n", data_area); + + fprintf(stderr, "INFO : WDC : DUI Header Version = 0x%x\n", log_hdr_v3->hdr_version); + if (log_hdr_v3->hdr_version >= 0x03) + fprintf(stderr, "INFO : WDC : DUI Product ID = %c\n", log_hdr_v3->product_id); + } + + if (cap_dui_length_v3 == 0) { + fprintf(stderr, "INFO : WDC : Capture V2 or V3 Device Unit Info log is empty\n"); + } else { + /* parse log header for all sections up to specified data area inclusively */ + if (data_area != WDC_NVME_DUI_MAX_DATA_AREA) { + for(j = 0; j < WDC_NVME_DUI_MAX_SECTION_V3; j++) { + if (log_hdr_v3->log_section[j].data_area_id <= data_area && + log_hdr_v3->log_section[j].data_area_id != 0) { + log_size += log_hdr_v3->log_section[j].section_size; + if (verbose) + fprintf(stderr, "%s: Data area ID %d : section size 0x%x, total size = 0x%lx\n", + __func__, log_hdr_v3->log_section[j].data_area_id, (unsigned int)log_hdr_v3->log_section[j].section_size, (long unsigned int)log_size); + } + else { + if (verbose) + fprintf(stderr, "%s: break, total size = 0x%lx\n", __func__, (long unsigned int)log_size); + break; + } + } + } else + log_size = cap_dui_length_v3; + + total_size = log_size; + + if (offset >= total_size) { + fprintf(stderr, "%s: INFO : WDC : Offset 0x%"PRIx64" exceeds total size 0x%"PRIx64", no data retrieved\n", + __func__, (uint64_t)offset, (uint64_t)total_size); + goto out; + } + + dump_data = (__u8 *) malloc(sizeof (__u8) * xfer_size_long); + if (dump_data == NULL) { + fprintf(stderr, "%s: ERROR : dump data v3 malloc failed : status %s, size = 0x%lx\n", + __func__, strerror(errno), (long unsigned int)xfer_size_long); + ret = -1; + goto out; + } + memset(dump_data, 0, sizeof (__u8) * xfer_size_long); + + 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)); + ret = output; + goto free_mem; + } + + curr_data_offset = 0; + + if (file_size != 0) { + /* Write the DUI data based on the passed in file size */ + if ((offset + file_size) > total_size) + log_size = min((total_size - offset), file_size); + else + log_size = min(total_size, file_size); + + if (verbose) + fprintf(stderr, "%s: INFO : WDC : Offset 0x%"PRIx64", file size 0x%"PRIx64", total size 0x%"PRIx64", log size 0x%"PRIx64"\n", + __func__, (uint64_t)offset, (uint64_t)file_size, (uint64_t)total_size, (uint64_t)log_size); + + curr_data_offset = offset; + + } + + i = 0; + buffer_addr = dump_data; + + for(; log_size > 0; log_size -= xfer_size_long) { + xfer_size_long = min(xfer_size_long, log_size); + + if (log_size <= xfer_size_long) + last_xfer = true; + + ret = wdc_dump_dui_data_v2(fd, (__u32)xfer_size_long, curr_data_offset, buffer_addr, last_xfer); + if (ret != 0) { + fprintf(stderr, "%s: ERROR : WDC : Get chunk %d, size = 0x%lx, offset = 0x%lx, addr = 0x%lx\n", + __func__, i, (long unsigned int)total_size, (long unsigned int)curr_data_offset, (long unsigned int)buffer_addr); + fprintf(stderr, "%s: ERROR : WDC : NVMe Status:%s(%x)\n", __func__, nvme_status_to_string(ret), ret); + break; + } + + /* write the dump data into the file */ + err = write(output, (void *)buffer_addr, xfer_size_long); + if (err != xfer_size_long) { + fprintf(stderr, "%s: ERROR : WDC : Failed to flush DUI data to file! chunk %d, err = 0x%x, xfer_size = 0x%lx\n", + __func__, i, err, (long unsigned int)xfer_size_long); + goto free_mem; + } + + curr_data_offset += xfer_size_long; + i++; + } + } + } else { + __s32 log_size = 0; + __u32 curr_data_offset = 0; + + cap_dui_length = le32_to_cpu(log_hdr->log_size); + + if (verbose) { + fprintf(stderr, "INFO : WDC : Capture V1 Device Unit Info log, data area = %d\n", data_area); + fprintf(stderr, "INFO : WDC : DUI Header Version = 0x%x\n", log_hdr->hdr_version); + } + + if (cap_dui_length == 0) { + fprintf(stderr, "INFO : WDC : Capture V1 Device Unit Info log is empty\n"); + } else { + /* parse log header for all sections up to specified data area inclusively */ + if (data_area != WDC_NVME_DUI_MAX_DATA_AREA) { + for(j = 0; j < WDC_NVME_DUI_MAX_SECTION; j++) { + if (log_hdr->log_section[j].data_area_id <= data_area && + log_hdr->log_section[j].data_area_id != 0) { + log_size += log_hdr->log_section[j].section_size; + if (verbose) + fprintf(stderr, "%s: Data area ID %d : section size 0x%x, total size = 0x%x\n", + __func__, log_hdr->log_section[j].data_area_id, (unsigned int)log_hdr->log_section[j].section_size, (unsigned int)log_size); + + } + else { + if (verbose) + fprintf(stderr, "%s: break, total size = 0x%x\n", __func__, (unsigned int)log_size); + break; + } + } + } else + log_size = cap_dui_length; + + total_size = log_size; + + dump_data = (__u8 *) malloc(sizeof (__u8) * xfer_size); + if (dump_data == NULL) { + fprintf(stderr, "%s: ERROR : dump data V1 malloc failed : status %s, size = 0x%x\n", + __func__, strerror(errno), (unsigned int)xfer_size); + ret = -1; + goto out; + } + memset(dump_data, 0, sizeof (__u8) * xfer_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)); + ret = output; + goto free_mem; + } + + /* write the telemetry and log headers into the dump_file */ + err = write(output, (void *)log_hdr, WDC_NVME_CAP_DUI_HEADER_SIZE); + if (err != WDC_NVME_CAP_DUI_HEADER_SIZE) { + fprintf(stderr, "%s: Failed to flush header data to file!\n", __func__); + goto free_mem; + } + + log_size -= WDC_NVME_CAP_DUI_HEADER_SIZE; + curr_data_offset = WDC_NVME_CAP_DUI_HEADER_SIZE; + i = 0; + buffer_addr = dump_data; + + for(; log_size > 0; log_size -= xfer_size) { + xfer_size = min(xfer_size, log_size); + + if (log_size <= xfer_size) + last_xfer = true; + + ret = wdc_dump_dui_data(fd, xfer_size, curr_data_offset, buffer_addr, last_xfer); + if (ret != 0) { + fprintf(stderr, "%s: ERROR : WDC : Get chunk %d, size = 0x%lx, offset = 0x%x, addr = %p\n", + __func__, i, (long unsigned int)log_size, curr_data_offset, buffer_addr); + fprintf(stderr, "%s: ERROR : WDC : NVMe Status:%s(%x)\n", __func__, nvme_status_to_string(ret), ret); + break; + } + + /* write the dump data into the file */ + err = write(output, (void *)buffer_addr, xfer_size); + if (err != xfer_size) { + fprintf(stderr, "%s: ERROR : WDC : Failed to flush DUI data to file! chunk %d, err = 0x%x, xfer_size = 0x%x\n", + __func__, i, err, xfer_size); + goto free_mem; + } + + curr_data_offset += xfer_size; + i++; + } + } + } + + fprintf(stderr, "%s: NVMe Status:%s(%x)\n", __func__, nvme_status_to_string(ret), ret); + if (verbose) + fprintf(stderr, "INFO : WDC : Capture Device Unit Info log, length = 0x%lx\n", (long unsigned int)total_size); + + free_mem: + close(output); + free(dump_data); + + out: + free(log_hdr); + return ret; +} + +static int wdc_cap_diag(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char *desc = "Capture Diagnostics Log."; + char *file = "Output file pathname."; + char *size = "Data retrieval transfer size."; + char f[PATH_MAX] = {0}; + __u32 xfer_size = 0; + int fd; + __u64 capabilities = 0; + + struct config { + char *file; + __u32 xfer_size; + }; + + struct config cfg = { + .file = NULL, + .xfer_size = 0x10000 + }; + + OPT_ARGS(opts) = { + OPT_FILE("output-file", 'o', &cfg.file, file), + OPT_UINT("transfer-size", 's', &cfg.xfer_size, size), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if (cfg.file != NULL) + strncpy(f, cfg.file, PATH_MAX - 1); + if (cfg.xfer_size != 0) + xfer_size = cfg.xfer_size; + if (wdc_get_serial_name(fd, f, PATH_MAX, "cap_diag") == -1) { + fprintf(stderr, "ERROR : WDC: failed to generate file name\n"); + return -1; + } + if (cfg.file == NULL) + snprintf(f + strlen(f), PATH_MAX, "%s", ".bin"); + + capabilities = wdc_get_drive_capabilities(fd); + if ((capabilities & WDC_DRIVE_CAP_CAP_DIAG) == WDC_DRIVE_CAP_CAP_DIAG) + return wdc_do_cap_diag(fd, f, xfer_size, 0, 0); + + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + return 0; +} + +static int wdc_do_get_sn730_log_len(int fd, uint32_t *len_buf, uint32_t subopcode) +{ + int ret; + uint32_t *output = NULL; + struct nvme_admin_cmd admin_cmd; + + if ((output = (uint32_t*)malloc(sizeof(uint32_t))) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + memset(output, 0, sizeof (uint32_t)); + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + + admin_cmd.data_len = 8; + admin_cmd.opcode = SN730_NVME_GET_LOG_OPCODE; + admin_cmd.addr = (uintptr_t)output; + admin_cmd.cdw12 = subopcode; + admin_cmd.cdw10 = SN730_LOG_CHUNK_SIZE / 4; + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + if (ret == 0) + *len_buf = *output; + free(output); + return ret; +} + +static int wdc_do_get_sn730_log(int fd, void * log_buf, uint32_t offset, uint32_t subopcode) +{ + int ret; + uint8_t *output = NULL; + struct nvme_admin_cmd admin_cmd; + + if ((output = (uint8_t*)calloc(SN730_LOG_CHUNK_SIZE, sizeof(uint8_t))) == NULL) { + fprintf(stderr, "ERROR : WDC : calloc : %s\n", strerror(errno)); + return -1; + } + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.data_len = SN730_LOG_CHUNK_SIZE; + admin_cmd.opcode = SN730_NVME_GET_LOG_OPCODE; + admin_cmd.addr = (uintptr_t)output; + admin_cmd.cdw12 = subopcode; + admin_cmd.cdw13 = offset; + admin_cmd.cdw10 = SN730_LOG_CHUNK_SIZE / 4; + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + if (!ret) + memcpy(log_buf, output, SN730_LOG_CHUNK_SIZE); + return ret; +} + +static int get_sn730_log_chunks(int fd, uint8_t* log_buf, uint32_t log_len, uint32_t subopcode) +{ + int ret = 0; + uint8_t* chunk_buf = NULL; + int remaining = log_len; + int curr_offset = 0; + + if ((chunk_buf = (uint8_t*) malloc(sizeof (uint8_t) * SN730_LOG_CHUNK_SIZE)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + ret = -1; + goto out; + } + + while (remaining > 0) { + memset(chunk_buf, 0, SN730_LOG_CHUNK_SIZE); + ret = wdc_do_get_sn730_log(fd, chunk_buf, curr_offset, subopcode); + if (!ret) { + if (remaining >= SN730_LOG_CHUNK_SIZE) { + memcpy(log_buf + (curr_offset * SN730_LOG_CHUNK_SIZE), + chunk_buf, SN730_LOG_CHUNK_SIZE); + } else { + memcpy(log_buf + (curr_offset * SN730_LOG_CHUNK_SIZE), + chunk_buf, remaining); + } + remaining -= SN730_LOG_CHUNK_SIZE; + curr_offset += 1; + } else + goto out; + } +out: + free(chunk_buf); + return ret; +} + +static int wdc_do_sn730_get_and_tar(int fd, char * outputName) +{ + int ret = 0; + void *retPtr; + uint8_t* full_log_buf = NULL; + uint8_t* key_log_buf = NULL; + uint8_t* core_dump_log_buf = NULL; + uint8_t* extended_log_buf = NULL; + uint32_t full_log_len = 0; + uint32_t key_log_len = 0; + uint32_t core_dump_log_len = 0; + uint32_t extended_log_len = 0; + tarfile_metadata* tarInfo = NULL; + + tarInfo = (struct tarfile_metadata*) malloc(sizeof(tarfile_metadata)); + if (tarInfo == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + ret = -1; + goto free_buf; + } + memset(tarInfo, 0, sizeof(tarfile_metadata)); + + /* Create Logs directory */ + wdc_UtilsGetTime(&tarInfo->timeInfo); + memset(tarInfo->timeString, 0, sizeof(tarInfo->timeString)); + wdc_UtilsSnprintf((char*)tarInfo->timeString, MAX_PATH_LEN, "%02u%02u%02u_%02u%02u%02u", + tarInfo->timeInfo.year, tarInfo->timeInfo.month, tarInfo->timeInfo.dayOfMonth, + tarInfo->timeInfo.hour, tarInfo->timeInfo.minute, tarInfo->timeInfo.second); + + wdc_UtilsSnprintf((char*)tarInfo->bufferFolderName, MAX_PATH_LEN, "%s", + (char*)outputName); + + retPtr = getcwd((char*)tarInfo->currDir, MAX_PATH_LEN); + if (retPtr != NULL) + wdc_UtilsSnprintf((char*)tarInfo->bufferFolderPath, MAX_PATH_LEN, "%s%s%s", + (char *)tarInfo->currDir, WDC_DE_PATH_SEPARATOR, (char *)tarInfo->bufferFolderName); + else { + fprintf(stderr, "ERROR : WDC : get current working directory failed\n"); + goto free_buf; + } + + ret = wdc_UtilsCreateDir((char*)tarInfo->bufferFolderPath); + if (ret) + { + fprintf(stderr, "ERROR : WDC : create directory failed, ret = %d, dir = %s\n", ret, tarInfo->bufferFolderPath); + goto free_buf; + } else { + fprintf(stderr, "Stored log files in directory: %s\n", tarInfo->bufferFolderPath); + } + + ret = wdc_do_get_sn730_log_len(fd, &full_log_len, SN730_GET_FULL_LOG_LENGTH); + if (ret) { + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + goto free_buf; + } + ret = wdc_do_get_sn730_log_len(fd, &key_log_len, SN730_GET_KEY_LOG_LENGTH); + if (ret) { + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + goto free_buf; + } + ret = wdc_do_get_sn730_log_len(fd, &core_dump_log_len, SN730_GET_COREDUMP_LOG_LENGTH); + if (ret) { + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + goto free_buf; + } + ret = wdc_do_get_sn730_log_len(fd, &extended_log_len, SN730_GET_EXTENDED_LOG_LENGTH); + if (ret) { + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + goto free_buf; + } + + full_log_buf = (uint8_t*) calloc(full_log_len, sizeof (uint8_t)); + key_log_buf = (uint8_t*) calloc(key_log_len, sizeof (uint8_t)); + core_dump_log_buf = (uint8_t*) calloc(core_dump_log_len, sizeof (uint8_t)); + extended_log_buf = (uint8_t*) calloc(extended_log_len, sizeof (uint8_t)); + + if (!full_log_buf || !key_log_buf || !core_dump_log_buf || !extended_log_buf) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + ret = -1; + goto free_buf; + } + + /* Get the full log */ + ret = get_sn730_log_chunks(fd, full_log_buf, full_log_len, SN730_GET_FULL_LOG_SUBOPCODE); + if (ret) { + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + goto free_buf; + } + + /* Get the key log */ + ret = get_sn730_log_chunks(fd, key_log_buf, key_log_len, SN730_GET_KEY_LOG_SUBOPCODE); + if (ret) { + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + goto free_buf; + } + + /* Get the core dump log */ + ret = get_sn730_log_chunks(fd, core_dump_log_buf, core_dump_log_len, SN730_GET_CORE_LOG_SUBOPCODE); + if (ret) { + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + goto free_buf; + } + + /* Get the extended log */ + ret = get_sn730_log_chunks(fd, extended_log_buf, extended_log_len, SN730_GET_EXTEND_LOG_SUBOPCODE); + if (ret) { + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + goto free_buf; + } + + /* Write log files */ + wdc_UtilsSnprintf(tarInfo->fileName, MAX_PATH_LEN, "%s%s%s_%s.bin", (char*)tarInfo->bufferFolderPath, WDC_DE_PATH_SEPARATOR, + "full_log", (char*)tarInfo->timeString); + wdc_WriteToFile(tarInfo->fileName, (char*)full_log_buf, full_log_len); + + wdc_UtilsSnprintf(tarInfo->fileName, MAX_PATH_LEN, "%s%s%s_%s.bin", (char*)tarInfo->bufferFolderPath, WDC_DE_PATH_SEPARATOR, + "key_log", (char*)tarInfo->timeString); + wdc_WriteToFile(tarInfo->fileName, (char*)key_log_buf, key_log_len); + + wdc_UtilsSnprintf(tarInfo->fileName, MAX_PATH_LEN, "%s%s%s_%s.bin", (char*)tarInfo->bufferFolderPath, WDC_DE_PATH_SEPARATOR, + "core_dump_log", (char*)tarInfo->timeString); + wdc_WriteToFile(tarInfo->fileName, (char*)core_dump_log_buf, core_dump_log_len); + + wdc_UtilsSnprintf(tarInfo->fileName, MAX_PATH_LEN, "%s%s%s_%s.bin", (char*)tarInfo->bufferFolderPath, WDC_DE_PATH_SEPARATOR, + "extended_log", (char*)tarInfo->timeString); + wdc_WriteToFile(tarInfo->fileName, (char*)extended_log_buf, extended_log_len); + + /* Tar the log directory */ + wdc_UtilsSnprintf(tarInfo->tarFileName, sizeof(tarInfo->tarFileName), "%s%s", (char*)tarInfo->bufferFolderPath, WDC_DE_TAR_FILE_EXTN); + wdc_UtilsSnprintf(tarInfo->tarFiles, sizeof(tarInfo->tarFiles), "%s%s%s", (char*)tarInfo->bufferFolderName, WDC_DE_PATH_SEPARATOR, WDC_DE_TAR_FILES); + wdc_UtilsSnprintf(tarInfo->tarCmd, sizeof(tarInfo->tarCmd), "%s %s %s", WDC_DE_TAR_CMD, (char*)tarInfo->tarFileName, (char*)tarInfo->tarFiles); + + ret = system(tarInfo->tarCmd); + + if (ret) + fprintf(stderr, "ERROR : WDC : Tar of log data failed, ret = %d\n", ret); + +free_buf: + free(tarInfo); + free(full_log_buf); + free(core_dump_log_buf); + free(key_log_buf); + free(extended_log_buf); + return ret; +} + +static int wdc_vs_internal_fw_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char *desc = "Internal Firmware Log."; + char *file = "Output file pathname."; + char *size = "Data retrieval transfer size."; + char *data_area = "Data area to retrieve up to. Currently only supported on the SN340, SN640, and SN840 devices."; + char *file_size = "Output file size. Currently only supported on the SN340 device."; + char *offset = "Output file data offset. Currently only supported on the SN340 device."; + char *type = "Telemetry type - NONE, HOST, or CONTROLLER. Currently only supported on the SN640 and SN840 devices."; + char *verbose = "Display more debug messages."; + char f[PATH_MAX] = {0}; + char fileSuffix[PATH_MAX] = {0}; + __u32 xfer_size = 0; + int fd; + int telemetry_type = 0, telemetry_data_area = 0; + UtilsTimeInfo timeInfo; + __u8 timeStamp[MAX_PATH_LEN]; + __u64 capabilities = 0; + + struct config { + char *file; + __u32 xfer_size; + int data_area; + __u64 file_size; + __u64 offset; + char *type; + int verbose; + }; + + struct config cfg = { + .file = NULL, + .xfer_size = 0x10000, + .data_area = 3, + .file_size = 0, + .offset = 0, + .type = NULL, + .verbose = 0, + }; + + 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() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if (!wdc_check_device(fd)) + return -1; + if (cfg.xfer_size != 0) + xfer_size = cfg.xfer_size; + else { + fprintf(stderr, "ERROR : WDC : Invalid length\n"); + return -1; + } + + if (cfg.file != NULL) { + int verify_file; + + /* 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)); + return -1; + } + 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); + + if (wdc_get_serial_name(fd, f, PATH_MAX, fileSuffix) == -1) { + fprintf(stderr, "ERROR : WDC: failed to generate file name\n"); + return -1; + } + } + + if (cfg.file == NULL) + snprintf(f + strlen(f), PATH_MAX, "%s", ".bin"); + fprintf(stderr, "%s: filename = %s\n", __func__, f); + + if (cfg.data_area > 5 || cfg.data_area == 0) { + fprintf(stderr, "ERROR : WDC: Data area must be 1-5\n"); + return -1; + } + + capabilities = wdc_get_drive_capabilities(fd); + if ((capabilities & WDC_DRIVE_CAP_INTERNAL_LOG) == WDC_DRIVE_CAP_INTERNAL_LOG) { + if ((cfg.type == NULL) || + (!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"); + return -1; + } + + return wdc_do_cap_diag(fd, f, xfer_size, telemetry_type, telemetry_data_area); + } + if ((capabilities & WDC_DRIVE_CAP_SN340_DUI) == WDC_DRIVE_CAP_SN340_DUI) { + /* FW requirement - xfer size must be 256k for data area 4 */ + if (cfg.data_area >= 4) + xfer_size = 0x40000; + return wdc_do_cap_dui(fd, f, xfer_size, cfg.data_area, cfg.verbose, cfg.file_size, cfg.offset); + } + if ((capabilities & WDC_DRIVE_CAP_DUI_DATA) == WDC_DRIVE_CAP_DUI_DATA) + return wdc_do_cap_dui(fd, f, xfer_size, WDC_NVME_DUI_MAX_DATA_AREA, cfg.verbose, 0, 0); + if ((capabilities & WDC_SN730B_CAP_VUC_LOG) == WDC_SN730B_CAP_VUC_LOG) + return wdc_do_sn730_get_and_tar(fd, f); + + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + return -1; +} + +static int wdc_do_crash_dump(int fd, char *file, int type) +{ + int ret; + __u32 crash_dump_length; + __u32 opcode; + __u32 cdw12; + __u32 cdw10_size; + __u32 cdw12_size; + __u32 cdw12_clear; + + if (type == WDC_NVME_PFAIL_DUMP_TYPE) { + /* set parms to get the PFAIL Crash Dump */ + opcode = WDC_NVME_PF_CRASH_DUMP_OPCODE; + cdw10_size = WDC_NVME_PF_CRASH_DUMP_SIZE_NDT; + cdw12_size = ((WDC_NVME_PF_CRASH_DUMP_SIZE_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | + WDC_NVME_PF_CRASH_DUMP_SIZE_CMD); + + cdw12 = (WDC_NVME_PF_CRASH_DUMP_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | + WDC_NVME_PF_CRASH_DUMP_CMD; + + cdw12_clear = ((WDC_NVME_CLEAR_PF_CRASH_DUMP_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | + WDC_NVME_CLEAR_CRASH_DUMP_CMD); + + } else { + /* set parms to get the Crash Dump */ + opcode = WDC_NVME_CRASH_DUMP_OPCODE; + cdw10_size = WDC_NVME_CRASH_DUMP_SIZE_NDT; + cdw12_size = ((WDC_NVME_CRASH_DUMP_SIZE_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | + WDC_NVME_CRASH_DUMP_SIZE_CMD); + + cdw12 = (WDC_NVME_CRASH_DUMP_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | + WDC_NVME_CRASH_DUMP_CMD; + + cdw12_clear = ((WDC_NVME_CLEAR_CRASH_DUMP_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | + WDC_NVME_CLEAR_CRASH_DUMP_CMD); + } + + ret = wdc_dump_length(fd, + opcode, + cdw10_size, + cdw12_size, + &crash_dump_length); + + if (ret == -1) { + if (type == WDC_NVME_PFAIL_DUMP_TYPE) + fprintf(stderr, "INFO : WDC: Pfail dump get size failed\n"); + else + fprintf(stderr, "INFO : WDC: Crash dump get size failed\n"); + + return -1; + } + + if (crash_dump_length == 0) { + if (type == WDC_NVME_PFAIL_DUMP_TYPE) + fprintf(stderr, "INFO : WDC: Pfail dump is empty\n"); + else + fprintf(stderr, "INFO : WDC: Crash dump is empty\n"); + } else { + ret = wdc_do_dump(fd, + opcode, + crash_dump_length, + cdw12, + file, + crash_dump_length); + + if (ret == 0) + ret = wdc_do_clear_dump(fd, WDC_NVME_CLEAR_DUMP_OPCODE, cdw12_clear); + } + return ret; +} + +static int wdc_crash_dump(int fd, char *file, int type) +{ + char f[PATH_MAX] = {0}; + const char *dump_type; + + if (file != NULL) { + strncpy(f, file, PATH_MAX - 1); + } + + if (type == WDC_NVME_PFAIL_DUMP_TYPE) + dump_type = "_pfail_dump"; + else + dump_type = "_crash_dump"; + + if (wdc_get_serial_name(fd, f, PATH_MAX, dump_type) == -1) { + fprintf(stderr, "ERROR : WDC : failed to generate file name\n"); + return -1; + } + return wdc_do_crash_dump(fd, f, type); +} + +static int wdc_do_drive_log(int fd, char *file) +{ + int ret; + __u8 *drive_log_data; + __u32 drive_log_length; + struct nvme_admin_cmd admin_cmd; + + ret = wdc_dump_length(fd, WDC_NVME_DRIVE_LOG_SIZE_OPCODE, + WDC_NVME_DRIVE_LOG_SIZE_NDT, + (WDC_NVME_DRIVE_LOG_SIZE_SUBCMD << + WDC_NVME_SUBCMD_SHIFT | WDC_NVME_DRIVE_LOG_SIZE_CMD), + &drive_log_length); + if (ret == -1) { + return -1; + } + + drive_log_data = (__u8 *) malloc(sizeof (__u8) * drive_log_length); + if (drive_log_data == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + + memset(drive_log_data, 0, sizeof (__u8) * drive_log_length); + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = WDC_NVME_DRIVE_LOG_OPCODE; + admin_cmd.addr = (__u64)(uintptr_t)drive_log_data; + admin_cmd.data_len = drive_log_length; + admin_cmd.cdw10 = drive_log_length; + admin_cmd.cdw12 = ((WDC_NVME_DRIVE_LOG_SUBCMD << + WDC_NVME_SUBCMD_SHIFT) | WDC_NVME_DRIVE_LOG_SIZE_CMD); + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), + ret); + if (ret == 0) { + ret = wdc_create_log_file(file, drive_log_data, drive_log_length); + } + free(drive_log_data); + return ret; +} + +static int wdc_drive_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Capture Drive Log."; + const char *file = "Output file pathname."; + char f[PATH_MAX] = {0}; + int fd; + int ret; + __u64 capabilities = 0; + struct config { + char *file; + }; + + struct config cfg = { + .file = NULL + }; + + OPT_ARGS(opts) = { + OPT_FILE("output-file", 'o', &cfg.file, file), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if (!wdc_check_device(fd)) + return -1; + capabilities = wdc_get_drive_capabilities(fd); + + if ((capabilities & WDC_DRIVE_CAP_DRIVE_LOG) == 0) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + } else { + if (cfg.file != NULL) { + strncpy(f, cfg.file, PATH_MAX - 1); + } + if (wdc_get_serial_name(fd, f, PATH_MAX, "drive_log") == -1) { + fprintf(stderr, "ERROR : WDC : failed to generate file name\n"); + return -1; + } + ret = wdc_do_drive_log(fd, f); + } + return ret; +} + +static int wdc_get_crash_dump(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Get Crash Dump."; + const char *file = "Output file pathname."; + int fd, ret; + __u64 capabilities = 0; + + struct config { + char *file; + }; + + struct config cfg = { + .file = NULL, + }; + + OPT_ARGS(opts) = { + OPT_FILE("output-file", 'o', &cfg.file, file), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if (!wdc_check_device(fd)) + return -1; + + capabilities = wdc_get_drive_capabilities(fd); + + if ((capabilities & WDC_DRIVE_CAP_CRASH_DUMP) == 0) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + } else { + ret = wdc_crash_dump(fd, cfg.file, WDC_NVME_CRASH_DUMP_TYPE); + if (ret != 0) { + fprintf(stderr, "ERROR : WDC : failed to read crash dump\n"); + } + } + return ret; +} + +static int wdc_get_pfail_dump(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char *desc = "Get Pfail Crash Dump."; + char *file = "Output file pathname."; + int fd; + int ret; + __u64 capabilities = 0; + struct config { + char *file; + }; + + struct config cfg = { + .file = NULL, + }; + + OPT_ARGS(opts) = { + OPT_FILE("output-file", 'o', &cfg.file, file), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if (!wdc_check_device(fd)) + return -1; + + capabilities = wdc_get_drive_capabilities(fd); + if ((capabilities & WDC_DRIVE_CAP_PFAIL_DUMP) == 0) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + } else { + ret = wdc_crash_dump(fd, cfg.file, WDC_NVME_PFAIL_DUMP_TYPE); + if (ret != 0) { + fprintf(stderr, "ERROR : WDC : failed to read pfail crash dump\n"); + } + } + + return ret; +} + +static void wdc_do_id_ctrl(__u8 *vs, struct json_object *root) +{ + char vsn[24] = {0}; + int base = 3072; + int vsn_start = 3081; + + memcpy(vsn, &vs[vsn_start - base], sizeof(vsn)); + if (root) + json_object_add_value_string(root, "wdc vsn", strlen(vsn) > 1 ? vsn : "NULL"); + else + printf("wdc vsn : %s\n", strlen(vsn) > 1 ? vsn : "NULL"); +} + +static int wdc_id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return __id_ctrl(argc, argv, cmd, plugin, wdc_do_id_ctrl); +} + +static const char* wdc_purge_mon_status_to_string(__u32 status) +{ + const char *str; + + switch (status) { + case WDC_NVME_PURGE_STATE_IDLE: + str = "Purge State Idle."; + break; + case WDC_NVME_PURGE_STATE_DONE: + str = "Purge State Done."; + break; + case WDC_NVME_PURGE_STATE_BUSY: + str = "Purge State Busy."; + break; + case WDC_NVME_PURGE_STATE_REQ_PWR_CYC: + str = "Purge Operation resulted in an error that requires " + "power cycle."; + break; + case WDC_NVME_PURGE_STATE_PWR_CYC_PURGE: + str = "The previous purge operation was interrupted by a power " + "cycle\nor reset interruption. Other commands may be " + "rejected until\nPurge Execute is issued and " + "completed."; + break; + default: + str = "Unknown."; + } + return str; +} + +static int wdc_purge(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + const char *desc = "Send a Purge command."; + char *err_str; + int fd, ret; + struct nvme_passthru_cmd admin_cmd; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err_str = ""; + memset(&admin_cmd, 0, sizeof (admin_cmd)); + admin_cmd.opcode = WDC_NVME_PURGE_CMD_OPCODE; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if (!wdc_check_device(fd)) + return -1; + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + if (ret > 0) { + switch (ret) { + case WDC_NVME_PURGE_CMD_SEQ_ERR: + err_str = "ERROR : WDC : Cannot execute purge, " + "Purge operation is in progress.\n"; + break; + case WDC_NVME_PURGE_INT_DEV_ERR: + err_str = "ERROR : WDC : Internal Device Error.\n"; + break; + default: + err_str = "ERROR : WDC\n"; + } + } + + fprintf(stderr, "%s", err_str); + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + return ret; +} + +static int wdc_purge_monitor(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + const char *desc = "Send a Purge Monitor command."; + int fd, ret; + __u8 output[WDC_NVME_PURGE_MONITOR_DATA_LEN]; + double progress_percent; + struct nvme_passthru_cmd admin_cmd; + struct wdc_nvme_purge_monitor_data *mon; + + OPT_ARGS(opts) = { + OPT_END() + }; + + memset(output, 0, sizeof (output)); + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = WDC_NVME_PURGE_MONITOR_OPCODE; + admin_cmd.addr = (__u64)(uintptr_t)output; + admin_cmd.data_len = WDC_NVME_PURGE_MONITOR_DATA_LEN; + admin_cmd.cdw10 = WDC_NVME_PURGE_MONITOR_CMD_CDW10; + admin_cmd.timeout_ms = WDC_NVME_PURGE_MONITOR_TIMEOUT; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if (!wdc_check_device(fd)) + return -1; + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + if (ret == 0) { + mon = (struct wdc_nvme_purge_monitor_data *) output; + printf("Purge state = 0x%0x\n", admin_cmd.result); + printf("%s\n", wdc_purge_mon_status_to_string(admin_cmd.result)); + if (admin_cmd.result == WDC_NVME_PURGE_STATE_BUSY) { + progress_percent = + ((double)le32_to_cpu(mon->entire_progress_current) * 100) / + le32_to_cpu(mon->entire_progress_total); + printf("Purge Progress = %f%%\n", progress_percent); + } + } + + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + return ret; +} + +static void wdc_print_log_normal(struct wdc_ssd_perf_stats *perf) +{ + printf(" C1 Log Page Performance Statistics :- \n"); + printf(" Host Read Commands %20"PRIu64"\n", + le64_to_cpu(perf->hr_cmds)); + printf(" Host Read Blocks %20"PRIu64"\n", + le64_to_cpu(perf->hr_blks)); + printf(" Average Read Size %20lf\n", + safe_div_fp((le64_to_cpu(perf->hr_blks)), (le64_to_cpu(perf->hr_cmds)))); + printf(" Host Read Cache Hit Commands %20"PRIu64"\n", + le64_to_cpu(perf->hr_ch_cmds)); + printf(" Host Read Cache Hit_Percentage %20"PRIu64"%%\n", + (uint64_t) calc_percent(le64_to_cpu(perf->hr_ch_cmds), le64_to_cpu(perf->hr_cmds))); + printf(" Host Read Cache Hit Blocks %20"PRIu64"\n", + le64_to_cpu(perf->hr_ch_blks)); + printf(" Average Read Cache Hit Size %20f\n", + safe_div_fp((le64_to_cpu(perf->hr_ch_blks)), (le64_to_cpu(perf->hr_ch_cmds)))); + printf(" Host Read Commands Stalled %20"PRIu64"\n", + le64_to_cpu(perf->hr_st_cmds)); + printf(" Host Read Commands Stalled Percentage %20"PRIu64"%%\n", + (uint64_t)calc_percent((le64_to_cpu(perf->hr_st_cmds)), le64_to_cpu(perf->hr_cmds))); + printf(" Host Write Commands %20"PRIu64"\n", + le64_to_cpu(perf->hw_cmds)); + printf(" Host Write Blocks %20"PRIu64"\n", + le64_to_cpu(perf->hw_blks)); + printf(" Average Write Size %20f\n", + safe_div_fp((le64_to_cpu(perf->hw_blks)), (le64_to_cpu(perf->hw_cmds)))); + printf(" Host Write Odd Start Commands %20"PRIu64"\n", + le64_to_cpu(perf->hw_os_cmds)); + printf(" Host Write Odd Start Commands Percentage %20"PRIu64"%%\n", + (uint64_t)calc_percent((le64_to_cpu(perf->hw_os_cmds)), (le64_to_cpu(perf->hw_cmds)))); + printf(" Host Write Odd End Commands %20"PRIu64"\n", + le64_to_cpu(perf->hw_oe_cmds)); + printf(" Host Write Odd End Commands Percentage %20"PRIu64"%%\n", + (uint64_t)calc_percent((le64_to_cpu(perf->hw_oe_cmds)), (le64_to_cpu((perf->hw_cmds))))); + printf(" Host Write Commands Stalled %20"PRIu64"\n", + le64_to_cpu(perf->hw_st_cmds)); + printf(" Host Write Commands Stalled Percentage %20"PRIu64"%%\n", + (uint64_t)calc_percent((le64_to_cpu(perf->hw_st_cmds)), (le64_to_cpu(perf->hw_cmds)))); + printf(" NAND Read Commands %20"PRIu64"\n", + le64_to_cpu(perf->nr_cmds)); + printf(" NAND Read Blocks Commands %20"PRIu64"\n", + le64_to_cpu(perf->nr_blks)); + printf(" Average NAND Read Size %20f\n", + safe_div_fp((le64_to_cpu(perf->nr_blks)), (le64_to_cpu((perf->nr_cmds))))); + printf(" Nand Write Commands %20"PRIu64"\n", + le64_to_cpu(perf->nw_cmds)); + printf(" NAND Write Blocks %20"PRIu64"\n", + le64_to_cpu(perf->nw_blks)); + printf(" Average NAND Write Size %20f\n", + safe_div_fp((le64_to_cpu(perf->nw_blks)), (le64_to_cpu(perf->nw_cmds)))); + printf(" NAND Read Before Write %20"PRIu64"\n", + le64_to_cpu(perf->nrbw)); +} + +static void wdc_print_log_json(struct wdc_ssd_perf_stats *perf) +{ + struct json_object *root; + + root = json_create_object(); + json_object_add_value_int(root, "Host Read Commands", le64_to_cpu(perf->hr_cmds)); + json_object_add_value_int(root, "Host Read Blocks", le64_to_cpu(perf->hr_blks)); + json_object_add_value_int(root, "Average Read Size", + safe_div_fp((le64_to_cpu(perf->hr_blks)), (le64_to_cpu(perf->hr_cmds)))); + json_object_add_value_int(root, "Host Read Cache Hit Commands", + le64_to_cpu(perf->hr_ch_cmds)); + json_object_add_value_int(root, "Host Read Cache Hit Percentage", + (uint64_t) calc_percent(le64_to_cpu(perf->hr_ch_cmds), le64_to_cpu(perf->hr_cmds))); + json_object_add_value_int(root, "Host Read Cache Hit Blocks", + le64_to_cpu(perf->hr_ch_blks)); + json_object_add_value_int(root, "Average Read Cache Hit Size", + safe_div_fp((le64_to_cpu(perf->hr_ch_blks)), (le64_to_cpu(perf->hr_ch_cmds)))); + json_object_add_value_int(root, "Host Read Commands Stalled", + le64_to_cpu(perf->hr_st_cmds)); + json_object_add_value_int(root, "Host Read Commands Stalled Percentage", + (uint64_t)calc_percent((le64_to_cpu(perf->hr_st_cmds)), le64_to_cpu(perf->hr_cmds))); + json_object_add_value_int(root, "Host Write Commands", + le64_to_cpu(perf->hw_cmds)); + json_object_add_value_int(root, "Host Write Blocks", + le64_to_cpu(perf->hw_blks)); + json_object_add_value_int(root, "Average Write Size", + safe_div_fp((le64_to_cpu(perf->hw_blks)), (le64_to_cpu(perf->hw_cmds)))); + json_object_add_value_int(root, "Host Write Odd Start Commands", + le64_to_cpu(perf->hw_os_cmds)); + json_object_add_value_int(root, "Host Write Odd Start Commands Percentage", + (uint64_t)calc_percent((le64_to_cpu(perf->hw_os_cmds)), (le64_to_cpu(perf->hw_cmds)))); + json_object_add_value_int(root, "Host Write Odd End Commands", + le64_to_cpu(perf->hw_oe_cmds)); + json_object_add_value_int(root, "Host Write Odd End Commands Percentage", + (uint64_t)calc_percent((le64_to_cpu(perf->hw_oe_cmds)), (le64_to_cpu((perf->hw_cmds))))); + json_object_add_value_int(root, "Host Write Commands Stalled", + le64_to_cpu(perf->hw_st_cmds)); + json_object_add_value_int(root, "Host Write Commands Stalled Percentage", + (uint64_t)calc_percent((le64_to_cpu(perf->hw_st_cmds)), (le64_to_cpu(perf->hw_cmds)))); + json_object_add_value_int(root, "NAND Read Commands", + le64_to_cpu(perf->nr_cmds)); + json_object_add_value_int(root, "NAND Read Blocks Commands", + le64_to_cpu(perf->nr_blks)); + json_object_add_value_int(root, "Average NAND Read Size", + safe_div_fp((le64_to_cpu(perf->nr_blks)), (le64_to_cpu((perf->nr_cmds))))); + json_object_add_value_int(root, "Nand Write Commands", + le64_to_cpu(perf->nw_cmds)); + json_object_add_value_int(root, "NAND Write Blocks", + le64_to_cpu(perf->nw_blks)); + json_object_add_value_int(root, "Average NAND Write Size", + safe_div_fp((le64_to_cpu(perf->nw_blks)), (le64_to_cpu(perf->nw_cmds)))); + json_object_add_value_int(root, "NAND Read Before Written", + le64_to_cpu(perf->nrbw)); + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static int wdc_print_log(struct wdc_ssd_perf_stats *perf, int fmt) +{ + if (!perf) { + fprintf(stderr, "ERROR : WDC : Invalid buffer to read perf stats\n"); + return -1; + } + switch (fmt) { + case NORMAL: + wdc_print_log_normal(perf); + break; + case JSON: + wdc_print_log_json(perf); + break; + } + return 0; +} + +static void wdc_print_fb_ca_log_normal(struct wdc_ssd_ca_perf_stats *perf) +{ + uint64_t converted = 0; + + printf(" CA Log Page Performance Statistics :- \n"); + printf(" NAND Bytes Written %20"PRIu64 "%20"PRIu64"\n", + le64_to_cpu(perf->nand_bytes_wr_hi), le64_to_cpu(perf->nand_bytes_wr_lo)); + printf(" NAND Bytes Read %20"PRIu64 "%20"PRIu64"\n", + le64_to_cpu(perf->nand_bytes_rd_hi), le64_to_cpu(perf->nand_bytes_rd_lo)); + + converted = le64_to_cpu(perf->nand_bad_block); + printf(" NAND Bad Block Count (Normalized) %20"PRIu64"\n", + converted & 0xFFFF); + printf(" NAND Bad Block Count (Raw) %20"PRIu64"\n", + converted >> 16); + + printf(" Uncorrectable Read Count %20"PRIu64"\n", + le64_to_cpu(perf->uncorr_read_count)); + printf(" Soft ECC Error Count %20"PRIu64"\n", + le64_to_cpu(perf->ecc_error_count)); + printf(" SSD End to End Detected Correction Count %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->ssd_detect_count)); + printf(" SSD End to End Corrected Correction Count %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->ssd_correct_count)); + printf(" System Data Percent Used %20"PRIu32"%%\n", + perf->data_percent_used); + printf(" User Data Erase Counts Max %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->data_erase_max)); + printf(" User Data Erase Counts Min %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->data_erase_min)); + printf(" Refresh Count %20"PRIu64"\n", + le64_to_cpu(perf->refresh_count)); + + converted = le64_to_cpu(perf->program_fail); + printf(" Program Fail Count (Normalized) %20"PRIu64"\n", + converted & 0xFFFF); + printf(" Program Fail Count (Raw) %20"PRIu64"\n", + converted >> 16); + + converted = le64_to_cpu(perf->user_erase_fail); + printf(" User Data Erase Fail Count (Normalized) %20"PRIu64"\n", + converted & 0xFFFF); + printf(" User Data Erase Fail Count (Raw) %20"PRIu64"\n", + converted >> 16); + + converted = le64_to_cpu(perf->system_erase_fail); + printf(" System Area Erase Fail Count (Normalized) %20"PRIu64"\n", + converted & 0xFFFF); + printf(" System Area Erase Fail Count (Raw) %20"PRIu64"\n", + converted >> 16); + + printf(" Thermal Throttling Status %20"PRIu8"\n", + perf->thermal_throttle_status); + printf(" Thermal Throttling Count %20"PRIu8"\n", + perf->thermal_throttle_count); + printf(" PCIe Correctable Error Count %20"PRIu64"\n", + le64_to_cpu(perf->pcie_corr_error)); + printf(" Incomplete Shutdown Count %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->incomplete_shutdown_count)); + printf(" Percent Free Blocks %20"PRIu32"%%\n", + perf->percent_free_blocks); +} + +static void wdc_print_fb_ca_log_json(struct wdc_ssd_ca_perf_stats *perf) +{ + struct json_object *root; + uint64_t converted = 0; + + root = json_create_object(); + json_object_add_value_int(root, "NAND Bytes Written Hi", le64_to_cpu(perf->nand_bytes_wr_hi)); + json_object_add_value_int(root, "NAND Bytes Written Lo", le64_to_cpu(perf->nand_bytes_wr_lo)); + json_object_add_value_int(root, "NAND Bytes Read Hi", le64_to_cpu(perf->nand_bytes_rd_hi)); + json_object_add_value_int(root, "NAND Bytes Read Lo", le64_to_cpu(perf->nand_bytes_rd_lo)); + + converted = le64_to_cpu(perf->nand_bad_block); + json_object_add_value_int(root, "NAND Bad Block Count (Normalized)", + converted & 0xFFFF); + json_object_add_value_int(root, "NAND Bad Block Count (Raw)", + converted >> 16); + + json_object_add_value_int(root, "Uncorrectable Read Count", le64_to_cpu(perf->uncorr_read_count)); + json_object_add_value_int(root, "Soft ECC Error Count", le64_to_cpu(perf->ecc_error_count)); + json_object_add_value_int(root, "SSD End to End Detected Correction Count", + le32_to_cpu(perf->ssd_detect_count)); + json_object_add_value_int(root, "SSD End to End Corrected Correction Count", + le32_to_cpu(perf->ssd_correct_count)); + json_object_add_value_int(root, "System Data Percent Used", + perf->data_percent_used); + json_object_add_value_int(root, "User Data Erase Counts Max", + le32_to_cpu(perf->data_erase_max)); + json_object_add_value_int(root, "User Data Erase Counts Min", + le32_to_cpu(perf->data_erase_min)); + json_object_add_value_int(root, "Refresh Count", le64_to_cpu(perf->refresh_count)); + + converted = le64_to_cpu(perf->program_fail); + json_object_add_value_int(root, "Program Fail Count (Normalized)", + converted & 0xFFFF); + json_object_add_value_int(root, "Program Fail Count (Raw)", + converted >> 16); + + converted = le64_to_cpu(perf->user_erase_fail); + json_object_add_value_int(root, "User Data Erase Fail Count (Normalized)", + converted & 0xFFFF); + json_object_add_value_int(root, "User Data Erase Fail Count (Raw)", + converted >> 16); + + converted = le64_to_cpu(perf->system_erase_fail); + json_object_add_value_int(root, "System Area Erase Fail Count (Normalized)", + converted & 0xFFFF); + json_object_add_value_int(root, "System Area Erase Fail Count (Raw)", + converted >> 16); + + json_object_add_value_int(root, "Thermal Throttling Status", + perf->thermal_throttle_status); + json_object_add_value_int(root, "Thermal Throttling Count", + perf->thermal_throttle_count); + json_object_add_value_int(root, "PCIe Correctable Error", le64_to_cpu(perf->pcie_corr_error)); + json_object_add_value_int(root, "Incomplete Shutdown Counte", le32_to_cpu(perf->incomplete_shutdown_count)); + json_object_add_value_int(root, "Percent Free Blocks", perf->percent_free_blocks); + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static void wdc_print_bd_ca_log_normal(void *data) +{ + struct wdc_bd_ca_log_format *bd_data = (struct wdc_bd_ca_log_format *)data; + __u64 *raw; + __u16 *word_raw; + __u32 *dword_raw; + __u8 *byte_raw; + + if (bd_data->field_id == 0x00) { + raw = (__u64*)bd_data->raw_value; + printf(" CA Log Page values :- \n"); + printf(" Program fail counts %20"PRIu64"\n", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + printf(" %% Remaining of allowable program fails %3"PRIu8"\n", + bd_data->normalized_value); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x01) { + raw = (__u64*)bd_data->raw_value; + printf(" Erase fail count %20"PRIu64"\n", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + printf(" %% Remaining of allowable erase fails %3"PRIu8"\n", + bd_data->normalized_value); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x02) { + word_raw = (__u16*)bd_data->raw_value; + printf(" Min erase cycles %10"PRIu16"\n", + le16_to_cpu(*word_raw)); + word_raw = (__u16*)&bd_data->raw_value[2]; + printf(" Max erase cycles %10"PRIu16"\n", + le16_to_cpu(*word_raw)); + word_raw = (__u16*)&bd_data->raw_value[4]; + printf(" Ave erase cycles %10"PRIu16"\n", + le16_to_cpu(*word_raw)); + printf(" Wear Leveling Normalized %3"PRIu8"\n", + bd_data->normalized_value); + + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x03) { + raw = (__u64*)bd_data->raw_value; + printf(" End to end error detection count %20"PRIu64"\n", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x04) { + raw = (__u64*)bd_data->raw_value; + printf(" Crc error count %20"PRIu64"\n", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x05) { + raw = (__u64*)bd_data->raw_value; + printf(" Timed workload media error %20.3f\n", + safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 1024.0)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x06) { + raw = (__u64*)bd_data->raw_value; + printf(" Timed workload host reads %% %3"PRIu64"\n", + le64_to_cpu(*raw & 0x00000000000000FF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x07) { + raw = (__u64*)bd_data->raw_value; + printf(" Timed workload timer %20"PRIu64"\n", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x08) { + byte_raw = (__u8*)bd_data->raw_value; + printf(" Throttle status %% %10"PRIu16"\n", + *byte_raw); + dword_raw = (__u32*)&bd_data->raw_value[1]; + printf(" Throttling event counter %10"PRIu16"\n", + le32_to_cpu(*dword_raw)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x09) { + raw = (__u64*)bd_data->raw_value; + printf(" Retry buffer overflow count %20"PRIu64"\n", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x0A) { + raw = (__u64*)bd_data->raw_value; + printf(" Pll lock loss count %20"PRIu64"\n", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x0B) { + raw = (__u64*)bd_data->raw_value; + printf(" Nand bytes written (32mb) %20.0f\n", + safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 0xFFFF)); + raw = (__u64*)bd_data->raw_value; + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x0C) { + raw = (__u64*)bd_data->raw_value; + printf(" Host bytes written (32mb) %20.0f\n", + safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 0xFFFF)); + raw = (__u64*)bd_data->raw_value; + } else { + goto invalid_id; + } + + goto done; + + invalid_id: + printf(" Invalid Field ID = %d\n", bd_data->field_id); + + done: + return; + +} + +static void wdc_print_bd_ca_log_json(void *data) +{ + struct wdc_bd_ca_log_format *bd_data = (struct wdc_bd_ca_log_format *)data; + __u64 *raw; + __u16 *word_raw; + __u32 *dword_raw; + __u8 *byte_raw; + struct json_object *root; + + root = json_create_object(); + if (bd_data->field_id == 0x00) { + raw = (__u64*)bd_data->raw_value; + json_object_add_value_int(root, "Program fail counts", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + json_object_add_value_int(root, "% Remaining of allowable program fails", + bd_data->normalized_value); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x01) { + raw = (__u64*)bd_data->raw_value; + json_object_add_value_int(root, "Erase fail count", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + json_object_add_value_int(root, "% Remaining of allowable erase fails", + bd_data->normalized_value); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x02) { + word_raw = (__u16*)bd_data->raw_value; + json_object_add_value_int(root, "Min erase cycles", le16_to_cpu(*word_raw)); + word_raw = (__u16*)&bd_data->raw_value[2]; + json_object_add_value_int(root, "Max erase cycles", le16_to_cpu(*word_raw)); + word_raw = (__u16*)&bd_data->raw_value[4]; + json_object_add_value_int(root, "Ave erase cycles", le16_to_cpu(*word_raw)); + json_object_add_value_int(root, "Wear Leveling Normalized", bd_data->normalized_value); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x03) { + raw = (__u64*)bd_data->raw_value; + json_object_add_value_int(root, "End to end error detection count", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x04) { + raw = (__u64*)bd_data->raw_value; + json_object_add_value_int(root, "Crc error count", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x05) { + raw = (__u64*)bd_data->raw_value; + json_object_add_value_float(root, "Timed workload media error", + safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 1024.0)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x06) { + raw = (__u64*)bd_data->raw_value; + json_object_add_value_int(root, "Timed workload host reads %", + le64_to_cpu(*raw & 0x00000000000000FF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x07) { + raw = (__u64*)bd_data->raw_value; + json_object_add_value_int(root, "Timed workload timer", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x08) { + byte_raw = (__u8*)bd_data->raw_value; + json_object_add_value_int(root, "Throttle status %", *byte_raw); + dword_raw = (__u32*)&bd_data->raw_value[1]; + json_object_add_value_int(root, "Throttling event counter", le32_to_cpu(*dword_raw)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x09) { + raw = (__u64*)bd_data->raw_value; + json_object_add_value_int(root, "Retry buffer overflow count", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x0A) { + raw = (__u64*)bd_data->raw_value; + json_object_add_value_int(root, "Pll lock loss count", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x0B) { + raw = (__u64*)bd_data->raw_value; + json_object_add_value_float(root, "Nand bytes written (32mb)", + safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 0xFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x0C) { + raw = (__u64*)bd_data->raw_value; + json_object_add_value_float(root, "Host bytes written (32mb)", + safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 0xFFFF)); + raw = (__u64*)bd_data->raw_value; + } else { + goto invalid_id; + } + + goto done; + + invalid_id: + printf(" Invalid Field ID = %d\n", bd_data->field_id); + + done: + return; + +} + +static void wdc_print_d0_log_normal(struct wdc_ssd_d0_smart_log *perf) +{ + printf(" D0 Smart Log Page Statistics :- \n"); + printf(" Lifetime Reallocated Erase Block Count %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->lifetime_realloc_erase_block_count)); + printf(" Lifetime Power on Hours %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->lifetime_power_on_hours)); + printf(" Lifetime UECC Count %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->lifetime_uecc_count)); + printf(" Lifetime Write Amplification Factor %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->lifetime_wrt_amp_factor)); + printf(" Trailing Hour Write Amplification Factor %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->trailing_hr_wrt_amp_factor)); + printf(" Reserve Erase Block Count %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->reserve_erase_block_count)); + printf(" Lifetime Program Fail Count %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->lifetime_program_fail_count)); + printf(" Lifetime Block Erase Fail Count %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->lifetime_block_erase_fail_count)); + printf(" Lifetime Die Failure Count %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->lifetime_die_failure_count)); + printf(" Lifetime Link Rate Downgrade Count %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->lifetime_link_rate_downgrade_count)); + printf(" Lifetime Clean Shutdown Count on Power Loss %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->lifetime_clean_shutdown_count)); + printf(" Lifetime Unclean Shutdowns on Power Loss %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->lifetime_unclean_shutdown_count)); + printf(" Current Temperature %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->current_temp)); + printf(" Max Recorded Temperature %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->max_recorded_temp)); + printf(" Lifetime Retired Block Count %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->lifetime_retired_block_count)); + printf(" Lifetime Read Disturb Reallocation Events %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->lifetime_read_disturb_realloc_events)); + printf(" Lifetime NAND Writes %20"PRIu64"\n", + le64_to_cpu(perf->lifetime_nand_writes)); + printf(" Capacitor Health %20"PRIu32"%%\n", + (uint32_t)le32_to_cpu(perf->capacitor_health)); + printf(" Lifetime User Writes %20"PRIu64"\n", + le64_to_cpu(perf->lifetime_user_writes)); + printf(" Lifetime User Reads %20"PRIu64"\n", + le64_to_cpu(perf->lifetime_user_reads)); + printf(" Lifetime Thermal Throttle Activations %20"PRIu32"\n", + (uint32_t)le32_to_cpu(perf->lifetime_thermal_throttle_act)); + printf(" Percentage of P/E Cycles Remaining %20"PRIu32"%%\n", + (uint32_t)le32_to_cpu(perf->percentage_pe_cycles_remaining)); +} + +static void wdc_print_d0_log_json(struct wdc_ssd_d0_smart_log *perf) +{ + struct json_object *root; + + root = json_create_object(); + json_object_add_value_int(root, "Lifetime Reallocated Erase Block Count", + le32_to_cpu(perf->lifetime_realloc_erase_block_count)); + json_object_add_value_int(root, "Lifetime Power on Hours", + le32_to_cpu(perf->lifetime_power_on_hours)); + json_object_add_value_int(root, "Lifetime UECC Count", + le32_to_cpu(perf->lifetime_uecc_count)); + json_object_add_value_int(root, "Lifetime Write Amplification Factor", + le32_to_cpu(perf->lifetime_wrt_amp_factor)); + json_object_add_value_int(root, "Trailing Hour Write Amplification Factor", + le32_to_cpu(perf->trailing_hr_wrt_amp_factor)); + json_object_add_value_int(root, "Reserve Erase Block Count", + le32_to_cpu(perf->reserve_erase_block_count)); + json_object_add_value_int(root, "Lifetime Program Fail Count", + le32_to_cpu(perf->lifetime_program_fail_count)); + json_object_add_value_int(root, "Lifetime Block Erase Fail Count", + le32_to_cpu(perf->lifetime_block_erase_fail_count)); + json_object_add_value_int(root, "Lifetime Die Failure Count", + le32_to_cpu(perf->lifetime_die_failure_count)); + json_object_add_value_int(root, "Lifetime Link Rate Downgrade Count", + le32_to_cpu(perf->lifetime_link_rate_downgrade_count)); + json_object_add_value_int(root, "Lifetime Clean Shutdown Count on Power Loss", + le32_to_cpu(perf->lifetime_clean_shutdown_count)); + json_object_add_value_int(root, "Lifetime Unclean Shutdowns on Power Loss", + le32_to_cpu(perf->lifetime_unclean_shutdown_count)); + json_object_add_value_int(root, "Current Temperature", + le32_to_cpu(perf->current_temp)); + json_object_add_value_int(root, "Max Recorded Temperature", + le32_to_cpu(perf->max_recorded_temp)); + json_object_add_value_int(root, "Lifetime Retired Block Count", + le32_to_cpu(perf->lifetime_retired_block_count)); + json_object_add_value_int(root, "Lifetime Read Disturb Reallocation Events", + le32_to_cpu(perf->lifetime_read_disturb_realloc_events)); + json_object_add_value_int(root, "Lifetime NAND Writes", + le64_to_cpu(perf->lifetime_nand_writes)); + json_object_add_value_int(root, "Capacitor Health", + le32_to_cpu(perf->capacitor_health)); + json_object_add_value_int(root, "Lifetime User Writes", + le64_to_cpu(perf->lifetime_user_writes)); + json_object_add_value_int(root, "Lifetime User Reads", + le64_to_cpu(perf->lifetime_user_reads)); + json_object_add_value_int(root, "Lifetime Thermal Throttle Activations", + le32_to_cpu(perf->lifetime_thermal_throttle_act)); + json_object_add_value_int(root, "Percentage of P/E Cycles Remaining", + le32_to_cpu(perf->percentage_pe_cycles_remaining)); + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static void wdc_get_commit_action_bin(__u8 commit_action_type, char *action_bin) +{ + + switch (commit_action_type) + { + case(0): + strcpy(action_bin, "000b"); + break; + case(1): + strcpy(action_bin, "001b"); + break; + case(2): + strcpy(action_bin, "010b"); + break; + case(3): + strcpy(action_bin, "011b"); + break; + case(4): + strcpy(action_bin, "100b"); + break; + case(5): + strcpy(action_bin, "101b"); + break; + case(6): + strcpy(action_bin, "110b"); + break; + case(7): + strcpy(action_bin, "111b"); + break; + default: + strcpy(action_bin, "INVALID"); + } + +} + +static void wdc_print_fw_act_history_log_normal(struct wdc_fw_act_history_log_entry *fw_act_history_entry, + int num_entries) +{ + int i; + char previous_fw[9]; + char new_fw[9]; + char commit_action_bin[8]; + memset((void *)previous_fw, 0, 9); + memset((void *)new_fw, 0, 9); + memset((void *)commit_action_bin, 0, 8); + char *null_fw = "--------"; + + + printf(" Firmware Activate History Log \n"); + printf(" Power on Hour Power Cycle Previous New \n"); + printf(" Entry hh:mm:ss Count Firmware Firmware Slot Action Result \n"); + printf(" ----- -------------- ------------ ---------- ---------- ----- ------ -------\n"); + + for (i = 0; i < num_entries; i++) { + memcpy(previous_fw, (char *)&(fw_act_history_entry->previous_fw_version), 8); + if (strlen((char *)&(fw_act_history_entry->new_fw_version)) > 1) + memcpy(new_fw, (char *)&(fw_act_history_entry->new_fw_version), 8); + else + memcpy(new_fw, null_fw, 8); + + printf("%5"PRIu32"", (uint32_t)le32_to_cpu(fw_act_history_entry->entry_num)); + printf(" "); + printf("%02d:%02d:%02d", (int)(le64_to_cpu(fw_act_history_entry->power_on_seconds)/3600), + (int)((le64_to_cpu(fw_act_history_entry->power_on_seconds)%3600)/60), + (int)(le64_to_cpu(fw_act_history_entry->power_on_seconds)%60)); + printf(" "); + printf("%8"PRIu32"", (uint32_t)le32_to_cpu(fw_act_history_entry->power_cycle_count)); + printf(" "); + printf("%s", (char *)previous_fw); + printf(" "); + printf("%s", (char *)new_fw); + printf(" "); + printf("%2"PRIu8"", (uint8_t)fw_act_history_entry->slot_number); + printf(" "); + wdc_get_commit_action_bin(fw_act_history_entry->commit_action_type,(char *)&commit_action_bin); + printf(" %s", (char *)commit_action_bin); + printf(" "); + if (le16_to_cpu(fw_act_history_entry->result) == 0) + printf("pass"); + else + printf("fail #%d", (uint16_t)le16_to_cpu(fw_act_history_entry->result)); + + printf("\n"); + + fw_act_history_entry++; + } +} + +static void wdc_print_fw_act_history_log_json(struct wdc_fw_act_history_log_entry *fw_act_history_entry, + int num_entries) +{ + struct json_object *root; + int i; + char previous_fw[9]; + char new_fw[9]; + char commit_action_bin[8]; + char fail_str[32]; + char time_str[9]; + memset((void *)previous_fw, 0, 9); + memset((void *)new_fw, 0, 9); + memset((void *)commit_action_bin, 0, 8); + memset((void *)time_str, 0, 9); + memset((void *)fail_str, 0, 11); + char *null_fw = "--------"; + + root = json_create_object(); + + for (i = 0; i < num_entries; i++) { + memcpy(previous_fw, (char *)&(fw_act_history_entry->previous_fw_version), 8); + if (strlen((char *)&(fw_act_history_entry->new_fw_version)) > 1) + memcpy(new_fw, (char *)&(fw_act_history_entry->new_fw_version), 8); + else + memcpy(new_fw, null_fw, 8); + + json_object_add_value_int(root, "Entry", + le32_to_cpu(fw_act_history_entry->entry_num)); + + sprintf((char *)time_str, "%02d:%02d:%02d", (int)(le64_to_cpu(fw_act_history_entry->power_on_seconds)/3600), + (int)((le64_to_cpu(fw_act_history_entry->power_on_seconds)%3600)/60), + (int)(le64_to_cpu(fw_act_history_entry->power_on_seconds)%60)); + json_object_add_value_string(root, "Power on Hour", time_str); + + json_object_add_value_int(root, "Power Cycle Count", + le32_to_cpu(fw_act_history_entry->power_cycle_count)); + json_object_add_value_string(root, "Previous Firmware", + previous_fw); + json_object_add_value_string(root, "New Firmware", + new_fw); + json_object_add_value_int(root, "Slot", + fw_act_history_entry->slot_number); + + wdc_get_commit_action_bin(fw_act_history_entry->commit_action_type,(char *)&commit_action_bin); + json_object_add_value_string(root, "Action", commit_action_bin); + + if (le16_to_cpu(fw_act_history_entry->result) == 0) + json_object_add_value_string(root, "Result", "pass"); + else { + sprintf((char *)fail_str, "fail #%d", (int)(le16_to_cpu(fw_act_history_entry->result))); + json_object_add_value_string(root, "Result", fail_str); + } + + fw_act_history_entry++; + } + + json_print_object(root, NULL); + printf("\n"); + + json_free_object(root); +} + +static int wdc_print_fb_ca_log(struct wdc_ssd_ca_perf_stats *perf, int fmt) +{ + if (!perf) { + fprintf(stderr, "ERROR : WDC : Invalid buffer to read perf stats\n"); + return -1; + } + switch (fmt) { + case NORMAL: + wdc_print_fb_ca_log_normal(perf); + break; + case JSON: + wdc_print_fb_ca_log_json(perf); + break; + } + return 0; +} + +static int wdc_print_bd_ca_log(void *bd_data, int fmt) +{ + if (!bd_data) { + fprintf(stderr, "ERROR : WDC : Invalid buffer to read data\n"); + return -1; + } + switch (fmt) { + case NORMAL: + wdc_print_bd_ca_log_normal(bd_data); + break; + case JSON: + wdc_print_bd_ca_log_json(bd_data); + break; + } + return 0; +} + +static int wdc_print_d0_log(struct wdc_ssd_d0_smart_log *perf, int fmt) +{ + if (!perf) { + fprintf(stderr, "ERROR : WDC : Invalid buffer to read perf stats\n"); + return -1; + } + switch (fmt) { + case NORMAL: + wdc_print_d0_log_normal(perf); + break; + case JSON: + wdc_print_d0_log_json(perf); + break; + } + return 0; +} + +static int wdc_print_fw_act_history_log(struct wdc_fw_act_history_log_entry *fw_act_history_entries, + int num_entries, + int fmt) +{ + if (!fw_act_history_entries) { + fprintf(stderr, "ERROR : WDC : Invalid buffer to read fw activate history entries\n"); + return -1; + } + + switch (fmt) { + case NORMAL: + wdc_print_fw_act_history_log_normal(fw_act_history_entries, num_entries); + break; + case JSON: + wdc_print_fw_act_history_log_json(fw_act_history_entries, num_entries); + break; + } + return 0; +} + +static int wdc_get_ca_log_page(int fd, char *format) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + __u32 *cust_id; + struct wdc_ssd_ca_perf_stats *perf; + uint32_t read_device_id, read_vendor_id; + + if (!wdc_check_device(fd)) + return -1; + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + /* verify the 0xCA log page is supported */ + if (wdc_nvme_check_supported_log_page(fd, WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE) == false) { + fprintf(stderr, "ERROR : WDC : 0xCA Log Page not supported\n"); + return -1; + } + + if (!get_dev_mgment_cbs_data(fd, WDC_C2_CUSTOMER_ID_ID, (void*)&data)) { + fprintf(stderr, "%s: ERROR : WDC : 0xC2 Log Page entry ID 0x%x not found\n", __func__, WDC_C2_CUSTOMER_ID_ID); + return -1; + } + + ret = wdc_get_pci_ids(&read_device_id, &read_vendor_id); + + cust_id = (__u32*)data; + + switch (read_device_id) { + + case WDC_NVME_SN200_DEV_ID: + + if (*cust_id == WDC_CUSTOMER_ID_0x1005) { + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_FB_CA_LOG_BUF_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + + memset(data, 0, sizeof (__u8) * WDC_FB_CA_LOG_BUF_LEN); + + ret = nvme_get_log(fd, 0xFFFFFFFF, WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE, + false, WDC_FB_CA_LOG_BUF_LEN, data); + if (strcmp(format, "json")) + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + + if (ret == 0) { + /* parse the data */ + perf = (struct wdc_ssd_ca_perf_stats *)(data); + ret = wdc_print_fb_ca_log(perf, fmt); + } else { + fprintf(stderr, "ERROR : WDC : Unable to read CA Log Page data\n"); + ret = -1; + } + } else { + + fprintf(stderr, "ERROR : WDC : Unsupported Customer id, id = %d\n", *cust_id); + return -1; + } + break; + + case WDC_NVME_SN640_DEV_ID: + case WDC_NVME_SN640_DEV_ID_1: + case WDC_NVME_SN640_DEV_ID_2: + case WDC_NVME_SN640_DEV_ID_3: + case WDC_NVME_SN840_DEV_ID: + case WDC_NVME_SN840_DEV_ID_1: + + if (*cust_id == WDC_CUSTOMER_ID_0x1005) { + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_FB_CA_LOG_BUF_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + + memset(data, 0, sizeof (__u8) * WDC_FB_CA_LOG_BUF_LEN); + + ret = nvme_get_log(fd, 0xFFFFFFFF, WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE, + false, WDC_FB_CA_LOG_BUF_LEN, data); + if (strcmp(format, "json")) + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + + if (ret == 0) { + /* parse the data */ + perf = (struct wdc_ssd_ca_perf_stats *)(data); + ret = wdc_print_fb_ca_log(perf, fmt); + } else { + fprintf(stderr, "ERROR : WDC : Unable to read CA Log Page data\n"); + ret = -1; + } + } else if ((*cust_id == WDC_CUSTOMER_ID_GN) || (*cust_id == WDC_CUSTOMER_ID_GD)) { + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_BD_CA_LOG_BUF_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + + memset(data, 0, sizeof (__u8) * WDC_BD_CA_LOG_BUF_LEN); + ret = nvme_get_log(fd, 0xFFFFFFFF, WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE, + false, WDC_BD_CA_LOG_BUF_LEN, data); + if (strcmp(format, "json")) + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + + if (ret == 0) { + /* parse the data */ + ret = wdc_print_bd_ca_log(data, fmt); + } else { + fprintf(stderr, "ERROR : WDC : Unable to read CA Log Page data\n"); + ret = -1; + } + + break; + } else { + + fprintf(stderr, "ERROR : WDC : Unsupported Customer id, id = %d\n", *cust_id); + return -1; + } + break; + + default: + + fprintf(stderr, "ERROR : WDC : Log page 0xCA not supported for this device\n"); + return -1; + break; + } + + free(data); + return ret; +} + +static int wdc_get_c1_log_page(int fd, char *format, uint8_t interval) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + __u8 *p; + int i; + int skip_cnt = 4; + int total_subpages; + struct wdc_log_page_header *l; + struct wdc_log_page_subpage_header *sph; + struct wdc_ssd_perf_stats *perf; + + if (!wdc_check_device(fd)) + return -1; + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + if (interval < 1 || interval > 15) { + fprintf(stderr, "ERROR : WDC : interval out of range [1-15]\n"); + return -1; + } + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_ADD_LOG_BUF_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + memset(data, 0, sizeof (__u8) * WDC_ADD_LOG_BUF_LEN); + + ret = nvme_get_log(fd, 0x01, WDC_NVME_ADD_LOG_OPCODE, false, + WDC_ADD_LOG_BUF_LEN, data); + if (strcmp(format, "json")) + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + if (ret == 0) { + l = (struct wdc_log_page_header*)data; + total_subpages = l->num_subpages + WDC_NVME_GET_STAT_PERF_INTERVAL_LIFETIME - 1; + for (i = 0, p = data + skip_cnt; i < total_subpages; i++, p += skip_cnt) { + sph = (struct wdc_log_page_subpage_header *) p; + if (sph->spcode == WDC_GET_LOG_PAGE_SSD_PERFORMANCE) { + if (sph->pcset == interval) { + perf = (struct wdc_ssd_perf_stats *) (p + 4); + ret = wdc_print_log(perf, fmt); + break; + } + } + skip_cnt = le16_to_cpu(sph->subpage_length) + 4; + } + if (ret) { + fprintf(stderr, "ERROR : WDC : Unable to read data from buffer\n"); + } + } + free(data); + return ret; +} + +static int wdc_get_d0_log_page(int fd, char *format) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + struct wdc_ssd_d0_smart_log *perf; + + if (!wdc_check_device(fd)) + return -1; + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + /* verify the 0xD0 log page is supported */ + if (wdc_nvme_check_supported_log_page(fd, WDC_NVME_GET_VU_SMART_LOG_OPCODE) == false) { + fprintf(stderr, "ERROR : WDC : 0xD0 Log Page not supported\n"); + return -1; + } + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_NVME_VU_SMART_LOG_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + memset(data, 0, sizeof (__u8) * WDC_NVME_VU_SMART_LOG_LEN); + + ret = nvme_get_log(fd, 0xFFFFFFFF, WDC_NVME_GET_VU_SMART_LOG_OPCODE, + false, WDC_NVME_VU_SMART_LOG_LEN, data); + if (strcmp(format, "json")) + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + + if (ret == 0) { + /* parse the data */ + perf = (struct wdc_ssd_d0_smart_log *)(data); + ret = wdc_print_d0_log(perf, fmt); + } else { + fprintf(stderr, "ERROR : WDC : Unable to read D0 Log Page data\n"); + ret = -1; + } + + free(data); + return ret; +} + +static int wdc_vs_smart_add_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve additional performance statistics."; + const char *interval = "Interval to read the statistics from [1, 15]."; + int fd; + int ret = 0; + __u64 capabilities = 0; + + struct config { + uint8_t interval; + int vendor_specific; + char *output_format; + }; + + struct config cfg = { + .interval = 14, + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_UINT("interval", 'i', &cfg.interval, interval), + OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + capabilities = wdc_get_drive_capabilities(fd); + + if ((capabilities & WDC_DRIVE_CAP_SMART_LOG_MASK) == 0) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + if ((capabilities & (WDC_DRIVE_CAP_CA_LOG_PAGE)) == (WDC_DRIVE_CAP_CA_LOG_PAGE)) { + /* Get the CA Log Page */ + ret = wdc_get_ca_log_page(fd, cfg.output_format); + if (ret) + fprintf(stderr, "ERROR : WDC : Failure reading the CA Log Page, ret = %d\n", ret); + } + if ((capabilities & WDC_DRIVE_CAP_C1_LOG_PAGE) == WDC_DRIVE_CAP_C1_LOG_PAGE) { + /* Get the C1 Log Page */ + ret = wdc_get_c1_log_page(fd, cfg.output_format, cfg.interval); + if (ret) + fprintf(stderr, "ERROR : WDC : Failure reading the C1 Log Page, ret = %d\n", ret); + } + if ((capabilities & WDC_DRIVE_CAP_D0_LOG_PAGE) == WDC_DRIVE_CAP_D0_LOG_PAGE) { + /* Get the D0 Log Page */ + ret = wdc_get_d0_log_page(fd, cfg.output_format); + if (ret) + fprintf(stderr, "ERROR : WDC : Failure reading the D0 Log Page, ret = %d\n", ret); + } +out: + return ret; +} + +static int wdc_clear_pcie_correctable_errors(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char *desc = "Clear PCIE Correctable Errors."; + int fd, ret; + __u64 capabilities = 0; + struct nvme_passthru_cmd admin_cmd; + + OPT_ARGS(opts) = { + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if (!wdc_check_device(fd)) { + ret = -1; + goto out; + } + + capabilities = wdc_get_drive_capabilities(fd); + if ((capabilities & WDC_DRIVE_CAP_CLEAR_PCIE) == 0) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + memset(&admin_cmd, 0, sizeof (admin_cmd)); + admin_cmd.opcode = WDC_NVME_CLEAR_PCIE_CORR_OPCODE; + admin_cmd.cdw12 = ((WDC_NVME_CLEAR_PCIE_CORR_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | + WDC_NVME_CLEAR_PCIE_CORR_CMD); + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); +out: + return ret; +} +static int wdc_drive_status(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char *desc = "Get Drive Status."; + int fd; + int ret = -1; + __le32 system_eol_state; + __le32 user_eol_state; + __le32 format_corrupt_reason = cpu_to_le32(0xFFFFFFFF); + __le32 eol_status; + __le32 assert_status = cpu_to_le32(0xFFFFFFFF); + __le32 thermal_status = cpu_to_le32(0xFFFFFFFF); + __u64 capabilities = 0; + + OPT_ARGS(opts) = { + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + capabilities = wdc_get_drive_capabilities(fd); + if ((capabilities & WDC_DRIVE_CAP_DRIVE_STATUS) != WDC_DRIVE_CAP_DRIVE_STATUS) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + /* verify the 0xC2 Device Manageability log page is supported */ + if (wdc_nvme_check_supported_log_page(fd, WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE) == false) { + fprintf(stderr, "ERROR : WDC : 0xC2 Log Page not supported\n"); + ret = -1; + goto out; + } + + /* Get the assert dump present status */ + if (!wdc_nvme_get_dev_status_log_data(fd, &assert_status, + WDC_C2_ASSERT_DUMP_PRESENT_ID)) + fprintf(stderr, "ERROR : WDC : Get Assert Status Failed\n"); + + /* Get the thermal throttling status */ + if (!wdc_nvme_get_dev_status_log_data(fd, &thermal_status, + WDC_C2_THERMAL_THROTTLE_STATUS_ID)) + fprintf(stderr, "ERROR : WDC : Get Thermal Throttling Status Failed\n"); + + /* Get EOL status */ + if (!wdc_nvme_get_dev_status_log_data(fd, &eol_status, + WDC_C2_USER_EOL_STATUS_ID)) { + fprintf(stderr, "ERROR : WDC : Get User EOL Status Failed\n"); + eol_status = cpu_to_le32(-1); + } + + /* Get Customer EOL state */ + if (!wdc_nvme_get_dev_status_log_data(fd, &user_eol_state, + WDC_C2_USER_EOL_STATE_ID)) + fprintf(stderr, "ERROR : WDC : Get User EOL State Failed\n"); + + /* Get System EOL state*/ + if (!wdc_nvme_get_dev_status_log_data(fd, &system_eol_state, + WDC_C2_SYSTEM_EOL_STATE_ID)) + fprintf(stderr, "ERROR : WDC : Get System EOL State Failed\n"); + + /* Get format corrupt reason*/ + if (!wdc_nvme_get_dev_status_log_data(fd, &format_corrupt_reason, + WDC_C2_FORMAT_CORRUPT_REASON_ID)) + fprintf(stderr, "ERROR : WDC : Get Format Corrupt Reason Failed\n"); + + printf(" Drive Status :- \n"); + if (le32_to_cpu(eol_status) >= 0) { + printf(" Percent Used: %"PRIu32"%%\n", + le32_to_cpu(eol_status)); + } + else + printf(" Percent Used: Unknown\n"); + if (system_eol_state == WDC_EOL_STATUS_NORMAL && user_eol_state == WDC_EOL_STATUS_NORMAL) + printf(" Drive Life Status: Normal\n"); + else if (system_eol_state == WDC_EOL_STATUS_END_OF_LIFE || user_eol_state == WDC_EOL_STATUS_END_OF_LIFE) + printf(" Drive Life Status: End Of Life\n"); + else if (system_eol_state == WDC_EOL_STATUS_READ_ONLY || user_eol_state == WDC_EOL_STATUS_READ_ONLY) + printf(" Drive Life Status: Read Only\n"); + else + printf(" Drive Life Status: Unknown : 0x%08x/0x%08x\n", + le32_to_cpu(user_eol_state), le32_to_cpu(system_eol_state)); + + if (assert_status == WDC_ASSERT_DUMP_PRESENT) + printf(" Assert Dump Status: Present\n"); + else if (assert_status == WDC_ASSERT_DUMP_NOT_PRESENT) + printf(" Assert Dump Status: Not Present\n"); + else + printf(" Assert Dump Status: Unknown : 0x%08x\n", le32_to_cpu(assert_status)); + + if (thermal_status == WDC_THERMAL_THROTTLING_OFF) + printf(" Thermal Throttling Status: Off\n"); + else if (thermal_status == WDC_THERMAL_THROTTLING_ON) + printf(" Thermal Throttling Status: On\n"); + else if (thermal_status == WDC_THERMAL_THROTTLING_UNAVAILABLE) + printf(" Thermal Throttling Status: Unavailable\n"); + else + printf(" Thermal Throttling Status: Unknown : 0x%08x\n", le32_to_cpu(thermal_status)); + + if (format_corrupt_reason == WDC_FORMAT_NOT_CORRUPT) + printf(" Format Corrupt Reason: Format Not Corrupted\n"); + else if (format_corrupt_reason == WDC_FORMAT_CORRUPT_FW_ASSERT) + printf(" Format Corrupt Reason: Format Corrupt due to FW Assert\n"); + else if (format_corrupt_reason == WDC_FORMAT_CORRUPT_UNKNOWN) + printf(" Format Corrupt Reason: Format Corrupt for Unknown Reason\n"); + else + printf(" Format Corrupt Reason: Unknown : 0x%08x\n", le32_to_cpu(format_corrupt_reason)); + +out: + return ret; +} + +static int wdc_clear_assert_dump(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char *desc = "Clear Assert Dump Present Status."; + int fd; + int ret = -1; + __le32 assert_status = cpu_to_le32(0xFFFFFFFF); + __u64 capabilities = 0; + struct nvme_passthru_cmd admin_cmd; + + OPT_ARGS(opts) = { + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + capabilities = wdc_get_drive_capabilities(fd); + if ((capabilities & WDC_DRIVE_CAP_CLEAR_ASSERT) != WDC_DRIVE_CAP_CLEAR_ASSERT) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + if (!wdc_nvme_get_dev_status_log_data(fd, &assert_status, + WDC_C2_ASSERT_DUMP_PRESENT_ID)) { + fprintf(stderr, "ERROR : WDC : Get Assert Status Failed\n"); + ret = -1; + goto out; + } + + /* Get the assert dump present status */ + if (assert_status == WDC_ASSERT_DUMP_PRESENT) { + memset(&admin_cmd, 0, sizeof (admin_cmd)); + admin_cmd.opcode = WDC_NVME_CLEAR_ASSERT_DUMP_OPCODE; + admin_cmd.cdw12 = ((WDC_NVME_CLEAR_ASSERT_DUMP_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | + WDC_NVME_CLEAR_ASSERT_DUMP_CMD); + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + } else + fprintf(stderr, "INFO : WDC : No Assert Dump Present\n"); + +out: + return ret; +} + +static int wdc_get_fw_act_history(int fd, char *format) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + struct wdc_fw_act_history_log_hdr *fw_act_history_hdr; + struct wdc_fw_act_history_log_entry *fw_act_history_entry; + + if (!wdc_check_device(fd)) + return -1; + + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + /* verify the FW Activate History log page is supported */ + if (wdc_nvme_check_supported_log_page(fd, WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID) == false) { + fprintf(stderr, "ERROR : WDC : %d Log Page not supported\n", WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID); + return -1; + } + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_FW_ACT_HISTORY_LOG_BUF_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + + memset(data, 0, sizeof (__u8) * WDC_FW_ACT_HISTORY_LOG_BUF_LEN); + + ret = nvme_get_log(fd, 0xFFFFFFFF, WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID, + false, WDC_FW_ACT_HISTORY_LOG_BUF_LEN, data); + + if (strcmp(format, "json")) + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + + if (ret == 0) { + /* parse the data */ + fw_act_history_hdr = (struct wdc_fw_act_history_log_hdr *)(data); + fw_act_history_entry = (struct wdc_fw_act_history_log_entry *)(data + sizeof(struct wdc_fw_act_history_log_hdr)); + + if (fw_act_history_hdr->num_entries > 0) + ret = wdc_print_fw_act_history_log(fw_act_history_entry, fw_act_history_hdr->num_entries, fmt); + else + fprintf(stderr, "INFO : WDC : No entries found in FW Activate History Log Page\n"); + } else { + fprintf(stderr, "ERROR : WDC : Unable to read FW Activate History Log Page data\n"); + ret = -1; + } + + free(data); + return ret; +} + +static int wdc_vs_fw_activate_history(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + int fd; + int ret = 0; + __u64 capabilities = 0; + const char *desc = "Retrieve FW activate history table."; + + + struct config { + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + + if (fd < 0) + return fd; + + capabilities = wdc_get_drive_capabilities(fd); + + if ((capabilities & WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY) == WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY) { + ret = wdc_get_fw_act_history(fd, cfg.output_format); + if (ret) + fprintf(stderr, "ERROR : WDC : Failure reading the FW Activate History, ret = %d\n", ret); + } else { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + } + + return ret; +} + +static int wdc_clear_fw_activate_history(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char *desc = "Clear FW activate history table."; + int fd; + int ret = -1; + __u64 capabilities = 0; + struct nvme_passthru_cmd admin_cmd; + + OPT_ARGS(opts) = { + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + capabilities = wdc_get_drive_capabilities(fd); + if ((capabilities & WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY) != WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + memset(&admin_cmd, 0, sizeof (admin_cmd)); + admin_cmd.opcode = WDC_NVME_CLEAR_FW_ACT_HIST_OPCODE; + admin_cmd.cdw12 = ((WDC_NVME_CLEAR_FW_ACT_HIST_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | + WDC_NVME_CLEAR_FW_ACT_HIST_CMD); + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + +out: + return ret; +} + +static int wdc_vs_telemetry_controller_option(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char *desc = "Disable/Enable Controller Option of the Telemetry Log Page."; + char *disable = "Disable controller option of the telemetry log page."; + char *enable = "Enable controller option of the telemetry log page."; + char *status = "Displays the current state of the controller initiated log page."; + int fd; + int ret = -1; + __u64 capabilities = 0; + __u32 result; + void *buf = NULL; + + + struct config { + int disable; + int enable; + int status; + }; + + struct config cfg = { + .disable = 0, + .enable = 0, + .status = 0, + }; + + OPT_ARGS(opts) = { + OPT_FLAG("disable", 'd', &cfg.disable, disable), + OPT_FLAG("enable", 'e', &cfg.enable, enable), + OPT_FLAG("status", 's', &cfg.status, status), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + capabilities = wdc_get_drive_capabilities(fd); + if ((capabilities & WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG) != WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + /* allow only one option at a time */ + if ((cfg.disable + cfg.enable + cfg.status) > 1) { + + fprintf(stderr, "ERROR : WDC : Invalid option\n"); + ret = -1; + goto out; + } + + if (cfg.disable) { + ret = nvme_set_feature(fd, 0, WDC_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID, 1, + 0, 0, 0, buf, &result); + + wdc_clear_reason_id(fd); + } + else { + if (cfg.enable) { + ret = nvme_set_feature(fd, 0, WDC_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID, 0, + 0, 0, 0, buf, &result); + } + else if (cfg.status) { + ret = nvme_get_feature(fd, 0, WDC_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID, 0, 0, + 4, buf, &result); + if (ret == 0) { + if (result) + fprintf(stderr, "Controller Option Telemetry Log Page State: Disabled\n"); + else + fprintf(stderr, "Controller Option Telemetry Log Page State: Enabled\n"); + } else { + fprintf(stderr, "ERROR : WDC: NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + } + } + else { + fprintf(stderr, "ERROR : WDC: unsupported option for this command\n"); + fprintf(stderr, "Please provide an option, -d, -e or -s\n"); + ret = -1; + goto out; + } + + } + +out: + return ret; +} + + +static int wdc_get_serial_and_fw_rev(int fd, char *sn, char *fw_rev) +{ + int i; + int ret; + struct nvme_id_ctrl ctrl; + + i = sizeof (ctrl.sn) - 1; + memset(sn, 0, WDC_SERIAL_NO_LEN); + memset(fw_rev, 0, WDC_NVME_FIRMWARE_REV_LEN); + memset(&ctrl, 0, sizeof (struct nvme_id_ctrl)); + ret = nvme_identify_ctrl(fd, &ctrl); + if (ret) { + fprintf(stderr, "ERROR : WDC : nvme_identify_ctrl() failed " + "0x%x\n", ret); + return -1; + } + /* Remove trailing spaces from the name */ + while (i && ctrl.sn[i] == ' ') { + ctrl.sn[i] = '\0'; + i--; + } + snprintf(sn, WDC_SERIAL_NO_LEN, "%s", ctrl.sn); + snprintf(fw_rev, WDC_NVME_FIRMWARE_REV_LEN, "%s", ctrl.fr); + + return 0; +} + +static int wdc_get_max_transfer_len(int fd, __u32 *maxTransferLen) +{ + int ret = 0; + struct nvme_id_ctrl ctrl; + + __u32 maxTransferLenDevice = 0; + + 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; + } + + maxTransferLenDevice = (1 << ctrl.mdts) * getpagesize(); + *maxTransferLen = maxTransferLenDevice; + + return ret; +} + +static int wdc_de_VU_read_size(int fd, __u32 fileId, __u16 spiDestn, __u32* logSize) +{ + int ret = WDC_STATUS_FAILURE; + struct nvme_admin_cmd cmd; + + if(!fd || !logSize ) + { + ret = WDC_STATUS_INVALID_PARAMETER; + goto end; + } + + memset(&cmd,0,sizeof(struct nvme_admin_cmd)); + cmd.opcode = WDC_DE_VU_READ_SIZE_OPCODE; + cmd.nsid = WDC_DE_DEFAULT_NAMESPACE_ID; + cmd.cdw13 = fileId<<16; + cmd.cdw14 = spiDestn; + + ret = nvme_submit_admin_passthru(fd, &cmd); + + if (!ret && logSize) + *logSize = cmd.result; + if( ret != WDC_STATUS_SUCCESS) + fprintf(stderr, "ERROR : WDC : VUReadSize() failed, status:%s(0x%x)\n", nvme_status_to_string(ret), ret); + + end: + return ret; +} + +static int wdc_de_VU_read_buffer(int fd, __u32 fileId, __u16 spiDestn, __u32 offsetInDwords, __u8* dataBuffer, __u32* bufferSize) +{ + int ret = WDC_STATUS_FAILURE; + struct nvme_admin_cmd cmd; + __u32 noOfDwordExpected = 0; + + if(!fd || !dataBuffer || !bufferSize) + { + ret = WDC_STATUS_INVALID_PARAMETER; + goto end; + } + + memset(&cmd,0,sizeof(struct nvme_admin_cmd)); + noOfDwordExpected = *bufferSize/sizeof(__u32); + cmd.opcode = WDC_DE_VU_READ_BUFFER_OPCODE; + cmd.nsid = WDC_DE_DEFAULT_NAMESPACE_ID; + cmd.cdw10 = noOfDwordExpected; + cmd.cdw13 = fileId<<16; + cmd.cdw14 = spiDestn; + cmd.cdw15 = offsetInDwords; + + cmd.addr = (__u64)(__u64)(uintptr_t)dataBuffer; + cmd.data_len = *bufferSize; + + ret = nvme_submit_admin_passthru(fd, &cmd); + + if( ret != WDC_STATUS_SUCCESS) + fprintf(stderr, "ERROR : WDC : VUReadBuffer() failed, status:%s(0x%x)\n", nvme_status_to_string(ret), ret); + + end: + return ret; +} + +static int wdc_get_log_dir_max_entries(int fd, __u32* maxNumOfEntries) +{ + int ret = WDC_STATUS_FAILURE; + __u32 headerPayloadSize = 0; + __u8* fileIdOffsetsBuffer = NULL; + __u32 fileIdOffsetsBufferSize = 0; + __u32 fileNum = 0; + __u16 fileOffset = 0; + + + if (!fd || !maxNumOfEntries) + { + ret = WDC_STATUS_INVALID_PARAMETER; + return ret; + } + /* 1.Get log directory first four bytes */ + if (WDC_STATUS_SUCCESS != (ret = wdc_de_VU_read_size(fd, 0, 5, (__u32*)&headerPayloadSize))) + { + fprintf(stderr, "ERROR : WDC : %s: Failed to get headerPayloadSize from file directory 0x%x\n", + __func__, ret); + goto end; + } + + fileIdOffsetsBufferSize = WDC_DE_FILE_HEADER_SIZE + (headerPayloadSize * WDC_DE_FILE_OFFSET_SIZE); + fileIdOffsetsBuffer = (__u8*)calloc(1, fileIdOffsetsBufferSize); + + /* 2.Read to get file offsets */ + if (WDC_STATUS_SUCCESS != (ret = wdc_de_VU_read_buffer(fd, 0, 5, 0, fileIdOffsetsBuffer, &fileIdOffsetsBufferSize))) + { + fprintf(stderr, "ERROR : WDC : %s: Failed to get fileIdOffsets from file directory 0x%x\n", + __func__, ret); + goto end; + } + /* 3.Determine valid entries */ + for (fileNum = 0; fileNum < (headerPayloadSize - WDC_DE_FILE_HEADER_SIZE) / WDC_DE_FILE_OFFSET_SIZE; fileNum++) + { + fileOffset = (fileIdOffsetsBuffer[WDC_DE_FILE_HEADER_SIZE + (fileNum * WDC_DE_FILE_OFFSET_SIZE)] << 8) + + fileIdOffsetsBuffer[WDC_DE_FILE_HEADER_SIZE + (fileNum * WDC_DE_FILE_OFFSET_SIZE) + 1]; + if (!fileOffset) + continue; + (*maxNumOfEntries)++; + } + end: + if (!fileIdOffsetsBuffer) + free(fileIdOffsetsBuffer); + return ret; +} + +static WDC_DRIVE_ESSENTIAL_TYPE wdc_get_essential_type(__u8 fileName[]) +{ + WDC_DRIVE_ESSENTIAL_TYPE essentialType = WDC_DE_TYPE_NONE; + + if (wdc_UtilsStrCompare((char*)fileName, WDC_DE_CORE_DUMP_FILE_NAME) == 0) + { + essentialType = WDC_DE_TYPE_DUMPSNAPSHOT; + } + else if (wdc_UtilsStrCompare((char*)fileName, WDC_DE_EVENT_LOG_FILE_NAME) == 0) + { + essentialType = WDC_DE_TYPE_EVENTLOG; + } + else if (wdc_UtilsStrCompare((char*)fileName, WDC_DE_MANUFACTURING_INFO_PAGE_FILE_NAME) == 0) + { + essentialType = WDC_DE_TYPE_NVME_MANF_INFO; + } + + return essentialType; +} + +static int wdc_fetch_log_directory(int fd, PWDC_DE_VU_LOG_DIRECTORY directory) +{ + int ret = WDC_STATUS_FAILURE; + __u8 *fileOffset = NULL; + __u8 *fileDirectory = NULL; + __u32 headerSize = 0; + __u32 fileNum = 0, startIdx = 0; + __u16 fileOffsetTemp = 0; + __u32 entryId = 0; + __u32 fileDirectorySize = 0; + + if (!fd || !directory) { + ret = WDC_STATUS_INVALID_PARAMETER; + goto end; + } + + ret = wdc_de_VU_read_size(fd, 0, 5, &fileDirectorySize); + if (WDC_STATUS_SUCCESS != ret) { + fprintf(stderr, + "ERROR : WDC : %s: Failed to get filesystem directory size, ret = %d\n", + __func__, ret); + goto end; + } + + fileDirectory = (__u8*)calloc(1, fileDirectorySize); + ret = wdc_de_VU_read_buffer(fd, 0, 5, 0, fileDirectory, &fileDirectorySize); + if (WDC_STATUS_SUCCESS != ret) { + fprintf(stderr, + "ERROR : WDC : %s: Failed to get filesystem directory, ret = %d\n", + __func__, ret); + goto end; + } + + /* First four bytes of header directory is headerSize */ + memcpy(&headerSize, fileDirectory, WDC_DE_FILE_HEADER_SIZE); + + /* minimum buffer for 1 entry is required */ + if (directory->maxNumLogEntries == 0) { + ret = WDC_STATUS_INVALID_PARAMETER; + goto end; + } + + for (fileNum = 0; + fileNum < (headerSize - WDC_DE_FILE_HEADER_SIZE) / WDC_DE_FILE_OFFSET_SIZE; + fileNum++) { + if (entryId >= directory->maxNumLogEntries) + break; + + startIdx = WDC_DE_FILE_HEADER_SIZE + (fileNum * WDC_DE_FILE_OFFSET_SIZE); + memcpy(&fileOffsetTemp, fileDirectory + startIdx, sizeof(fileOffsetTemp)); + fileOffset = fileDirectory + fileOffsetTemp; + + if (0 == fileOffsetTemp) + continue; + + memset(&directory->logEntry[entryId], 0, sizeof(WDC_DRIVE_ESSENTIALS)); + memcpy(&directory->logEntry[entryId].metaData, fileOffset, sizeof(WDC_DE_VU_FILE_META_DATA)); + directory->logEntry[entryId].metaData.fileName[WDC_DE_FILE_NAME_SIZE - 1] = '\0'; + wdc_UtilsDeleteCharFromString((char*)directory->logEntry[entryId].metaData.fileName, + WDC_DE_FILE_NAME_SIZE, ' '); + if (0 == directory->logEntry[entryId].metaData.fileID) + continue; + + directory->logEntry[entryId].essentialType = wdc_get_essential_type(directory->logEntry[entryId].metaData.fileName); + /*fprintf(stderr, "WDC : %s: NVMe VU Log Entry %d, fileName = %s, fileSize = 0x%lx, fileId = 0x%x\n", + __func__, entryId, directory->logEntry[entryId].metaData.fileName, + (long unsigned int)directory->logEntry[entryId].metaData.fileSize, directory->logEntry[entryId].metaData.fileID); + */ + entryId++; + } + + directory->numOfValidLogEntries = entryId; +end: + if (fileDirectory != NULL) + free(fileDirectory); + return ret; +} + +static int wdc_fetch_log_file_from_device(int fd, __u32 fileId, __u16 spiDestn, __u64 fileSize, __u8* dataBuffer) +{ + int ret = WDC_STATUS_FAILURE; + __u32 chunckSize = WDC_DE_VU_READ_BUFFER_STANDARD_OFFSET; + __u32 maximumTransferLength = 0; + __u32 buffSize = 0; + __u64 offsetIdx = 0; + + if (!fd || !dataBuffer || !fileSize) + { + ret = WDC_STATUS_INVALID_PARAMETER; + goto end; + } + + wdc_get_max_transfer_len(fd, &maximumTransferLength); + + /* Fetch Log File Data */ + if ((fileSize >= maximumTransferLength) || (fileSize > 0xFFFFFFFF)) + { + chunckSize = WDC_DE_VU_READ_BUFFER_STANDARD_OFFSET; + if (maximumTransferLength < WDC_DE_VU_READ_BUFFER_STANDARD_OFFSET) + chunckSize = maximumTransferLength; + + buffSize = chunckSize; + for (offsetIdx = 0; (offsetIdx * chunckSize) < fileSize; offsetIdx++) + { + if (((offsetIdx * chunckSize) + buffSize) > fileSize) + buffSize = (__u32)(fileSize - (offsetIdx * chunckSize)); + /* Limitation in VU read buffer - offsetIdx and bufferSize are not greater than u32 */ + ret = wdc_de_VU_read_buffer(fd, fileId, spiDestn, + (__u32)((offsetIdx * chunckSize) / sizeof(__u32)), dataBuffer + (offsetIdx * chunckSize), &buffSize); + if (ret != WDC_STATUS_SUCCESS) + { + fprintf(stderr, "ERROR : WDC : %s: wdc_de_VU_read_buffer failed with ret = %d, fileId = 0x%x, fileSize = 0x%lx\n", + __func__, ret, fileId, (long unsigned int)fileSize); + break; + } + } + } else { + buffSize = (__u32)fileSize; + ret = wdc_de_VU_read_buffer(fd, fileId, spiDestn, + (__u32)((offsetIdx * chunckSize) / sizeof(__u32)), dataBuffer, &buffSize); + if (ret != WDC_STATUS_SUCCESS) + { + fprintf(stderr, "ERROR : WDC : %s: wdc_de_VU_read_buffer failed with ret = %d, fileId = 0x%x, fileSize = 0x%lx\n", + __func__, ret, fileId, (long unsigned int)fileSize); + } + } + + end: + return ret; +} + +static int wdc_de_get_dump_trace(int fd, char * filePath, __u16 binFileNameLen, char *binFileName) +{ + int ret = WDC_STATUS_FAILURE; + __u8 *readBuffer = NULL; + __u32 readBufferLen = 0; + __u32 lastPktReadBufferLen = 0; + __u32 maxTransferLen = 0; + __u32 dumptraceSize = 0; + __u32 chunkSize = 0; + __u32 chunks = 0; + __u32 offset = 0; + __u8 loop = 0; + __u16 i = 0; + __u32 maximumTransferLength = 0; + + if (!fd || !binFileName || !filePath) + { + ret = WDC_STATUS_INVALID_PARAMETER; + return ret; + } + + wdc_get_max_transfer_len(fd, &maximumTransferLength); + + do + { + /* Get dumptrace size */ + ret = wdc_de_VU_read_size(fd, 0, WDC_DE_DUMPTRACE_DESTINATION, &dumptraceSize); + if (ret != WDC_STATUS_SUCCESS) + { + fprintf(stderr, "ERROR : WDC : %s: wdc_de_VU_read_size failed with ret = %d\n", + __func__, ret); + break; + } + + /* Make sure the size requested is greater than dword */ + if (dumptraceSize < 4) + { + ret = WDC_STATUS_FAILURE; + fprintf(stderr, "ERROR : WDC : %s: wdc_de_VU_read_size failed, read size is less than 4 bytes, dumptraceSize = 0x%x\n", + __func__, dumptraceSize); + break; + } + + /* Choose the least max transfer length */ + maxTransferLen = maximumTransferLength < WDC_DE_READ_MAX_TRANSFER_SIZE ? maximumTransferLength : WDC_DE_READ_MAX_TRANSFER_SIZE; + + /* Comment from FW Team: + * The max non - block transfer size is 0xFFFF (16 bits allowed as the block size).Use 0x8000 + * to keep it on a word - boundary. + * max_xfer = int(pow(2, id_data['MDTS'])) * 4096 # 4k page size as reported in pcie capabiltiies + */ + chunkSize = dumptraceSize < maxTransferLen ? dumptraceSize : maxTransferLen; + chunks = (dumptraceSize / maxTransferLen) + ((dumptraceSize % maxTransferLen) ? 1 : 0); + + readBuffer = (unsigned char *)calloc(dumptraceSize, sizeof(unsigned char)); + readBufferLen = chunkSize; + lastPktReadBufferLen = (dumptraceSize % maxTransferLen) ? (dumptraceSize % maxTransferLen) : chunkSize; + + if (readBuffer == NULL) + { + fprintf(stderr, "ERROR : WDC : %s: readBuffer calloc failed\n", __func__); + ret = WDC_STATUS_INSUFFICIENT_MEMORY; + break; + } + + for (i = 0; i < chunks; i++) + { + offset = ((i*chunkSize) / 4); + + /* Last loop call, Assign readBufferLen to read only left over bytes */ + if (i == (chunks - 1)) + { + readBufferLen = lastPktReadBufferLen; + } + + ret = wdc_de_VU_read_buffer(fd, 0, WDC_DE_DUMPTRACE_DESTINATION, 0, readBuffer + offset, &readBufferLen); + if (ret != WDC_STATUS_SUCCESS) + { + fprintf(stderr, "ERROR : WDC : %s: wdc_de_VU_read_buffer failed, ret = %d on offset 0x%x\n", + __func__, ret, offset); + break; + } + } + } while (loop); + + if (ret == WDC_STATUS_SUCCESS) + { + ret = wdc_WriteToFile(binFileName, (char*)readBuffer, dumptraceSize); + if (ret != WDC_STATUS_SUCCESS) + fprintf(stderr, "ERROR : WDC : %s: wdc_WriteToFile failed, ret = %d\n", __func__, ret); + } else { + fprintf(stderr, "ERROR : WDC : %s: Read Buffer Loop failed, ret = %d\n", __func__, ret); + } + + if (readBuffer) + { + free(readBuffer); + } + + return ret; +} + +static int wdc_do_drive_essentials(int fd, char *dir, char *key) +{ + int ret = 0; + void *retPtr; + char fileName[MAX_PATH_LEN]; + __s8 bufferFolderPath[MAX_PATH_LEN]; + char bufferFolderName[MAX_PATH_LEN]; + char tarFileName[MAX_PATH_LEN]; + char tarFiles[MAX_PATH_LEN]; + char tarCmd[MAX_PATH_LEN+MAX_PATH_LEN]; + UtilsTimeInfo timeInfo; + __u8 timeString[MAX_PATH_LEN]; + __u8 serialNo[WDC_SERIAL_NO_LEN]; + __u8 firmwareRevision[WDC_NVME_FIRMWARE_REV_LEN]; + __u8 idSerialNo[WDC_SERIAL_NO_LEN]; + __u8 idFwRev[WDC_NVME_FIRMWARE_REV_LEN]; + __u8 featureIdBuff[4]; + char currDir[MAX_PATH_LEN]; + char *dataBuffer = NULL; + __u32 elogNumEntries, elogBufferSize; + __u32 dataBufferSize; + __u32 listIdx = 0; + __u32 vuLogIdx = 0; + __u32 result; + __u32 maxNumOfVUFiles = 0; + struct nvme_id_ctrl ctrl; + struct nvme_id_ns ns; + struct nvme_error_log_page *elogBuffer; + struct nvme_smart_log smart_log; + struct nvme_firmware_log_page fw_log; + PWDC_NVME_DE_VU_LOGPAGES vuLogInput = NULL; + WDC_DE_VU_LOG_DIRECTORY deEssentialsList; + + memset(bufferFolderPath,0,sizeof(bufferFolderPath)); + memset(bufferFolderName,0,sizeof(bufferFolderName)); + memset(tarFileName,0,sizeof(tarFileName)); + memset(tarFiles,0,sizeof(tarFiles)); + memset(tarCmd,0,sizeof(tarCmd)); + memset(&timeInfo,0,sizeof(timeInfo)); + memset(&vuLogInput, 0, sizeof(vuLogInput)); + + if (wdc_get_serial_and_fw_rev(fd, (char *)idSerialNo, (char *)idFwRev)) + { + fprintf(stderr, "ERROR : WDC : get serial # and fw revision failed\n"); + return -1; + } else { + fprintf(stderr, "Get Drive Essentials Data for device serial #: %s and fw revision: %s\n", + idSerialNo, idFwRev); + } + + /* Create Drive Essentials directory */ + wdc_UtilsGetTime(&timeInfo); + memset(timeString, 0, sizeof(timeString)); + wdc_UtilsSnprintf((char*)timeString, MAX_PATH_LEN, "%02u%02u%02u_%02u%02u%02u", + timeInfo.year, timeInfo.month, timeInfo.dayOfMonth, + timeInfo.hour, timeInfo.minute, timeInfo.second); + + wdc_UtilsSnprintf((char*)serialNo,WDC_SERIAL_NO_LEN,(char*)idSerialNo); + /* Remove any space form serialNo */ + wdc_UtilsDeleteCharFromString((char*)serialNo, WDC_SERIAL_NO_LEN, ' '); + + memset(firmwareRevision, 0, sizeof(firmwareRevision)); + wdc_UtilsSnprintf((char*)firmwareRevision, WDC_NVME_FIRMWARE_REV_LEN, (char*)idFwRev); + /* Remove any space form FirmwareRevision */ + wdc_UtilsDeleteCharFromString((char*)firmwareRevision, WDC_NVME_FIRMWARE_REV_LEN, ' '); + + wdc_UtilsSnprintf((char*)bufferFolderName, MAX_PATH_LEN, "%s_%s_%s_%s", + "DRIVE_ESSENTIALS", (char*)serialNo, (char*)firmwareRevision, (char*)timeString); + + if (dir != NULL) { + wdc_UtilsSnprintf((char*)bufferFolderPath, MAX_PATH_LEN, "%s%s%s", + (char *)dir, WDC_DE_PATH_SEPARATOR, (char *)bufferFolderName); + } else { + retPtr = getcwd((char*)currDir, MAX_PATH_LEN); + if (retPtr != NULL) + wdc_UtilsSnprintf((char*)bufferFolderPath, MAX_PATH_LEN, "%s%s%s", + (char *)currDir, WDC_DE_PATH_SEPARATOR, (char *)bufferFolderName); + else { + fprintf(stderr, "ERROR : WDC : get current working directory failed\n"); + return -1; + } + } + + ret = wdc_UtilsCreateDir((char*)bufferFolderPath); + if (ret != 0) + { + fprintf(stderr, "ERROR : WDC : create directory failed, ret = %d, dir = %s\n", ret, bufferFolderPath); + return -1; + } else { + fprintf(stderr, "Store Drive Essentials bin files in directory: %s\n", bufferFolderPath); + } + + /* Get Identify Controller Data */ + memset(&ctrl, 0, sizeof (struct nvme_id_ctrl)); + ret = nvme_identify_ctrl(fd, &ctrl); + if (ret) { + fprintf(stderr, "ERROR : WDC : nvme_identify_ctrl() failed, ret = %d\n", ret); + return -1; + } else { + wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", (char*)bufferFolderPath, WDC_DE_PATH_SEPARATOR, + "IdentifyController", (char*)serialNo, (char*)timeString); + wdc_WriteToFile(fileName, (char*)&ctrl, sizeof (struct nvme_id_ctrl)); + } + + memset(&ns, 0, sizeof (struct nvme_id_ns)); + ret = nvme_identify_ns(fd, 1, 0, &ns); + if (ret) { + fprintf(stderr, "ERROR : WDC : nvme_identify_ns() failed, ret = %d\n", ret); + } else { + wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", (char*)bufferFolderPath, WDC_DE_PATH_SEPARATOR, + "IdentifyNamespace", (char*)serialNo, (char*)timeString); + wdc_WriteToFile(fileName, (char*)&ns, sizeof (struct nvme_id_ns)); + } + + /* Get Log Pages (0x01, 0x02, 0x03, 0xC0 and 0xE3) */ + elogNumEntries = WDC_DE_DEFAULT_NUMBER_OF_ERROR_ENTRIES; + elogBufferSize = elogNumEntries*sizeof(struct nvme_error_log_page); + dataBuffer = calloc(1, elogBufferSize); + elogBuffer = (struct nvme_error_log_page *)dataBuffer; + + ret = nvme_error_log(fd, elogNumEntries, elogBuffer); + if (ret) { + fprintf(stderr, "ERROR : WDC : nvme_error_log() failed, ret = %d\n", ret); + } else { + wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", (char*)bufferFolderPath, WDC_DE_PATH_SEPARATOR, + "ErrorLog", (char*)serialNo, (char*)timeString); + wdc_WriteToFile(fileName, (char*)elogBuffer, elogBufferSize); + } + + free(dataBuffer); + dataBuffer = NULL; + + /* Get Smart log page */ + memset(&smart_log, 0, sizeof (struct nvme_smart_log)); + ret = nvme_smart_log(fd, NVME_NSID_ALL, &smart_log); + if (ret) { + fprintf(stderr, "ERROR : WDC : nvme_smart_log() failed, ret = %d\n", ret); + } else { + wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", (char*)bufferFolderPath, WDC_DE_PATH_SEPARATOR, + "SmartLog", (char*)serialNo, (char*)timeString); + wdc_WriteToFile(fileName, (char*)&smart_log, sizeof(struct nvme_smart_log)); + } + + /* Get FW Slot log page */ + memset(&fw_log, 0, sizeof (struct nvme_firmware_log_page)); + ret = nvme_fw_log(fd, &fw_log); + if (ret) { + fprintf(stderr, "ERROR : WDC : nvme_fw_log() failed, ret = %d\n", ret); + } else { + wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", (char*)bufferFolderPath, WDC_DE_PATH_SEPARATOR, + "FwSLotLog", (char*)serialNo, (char*)timeString); + wdc_WriteToFile(fileName, (char*)&fw_log, sizeof(struct nvme_firmware_log_page)); + } + + /* Get VU log pages */ + /* define inputs for vendor unique log pages */ + vuLogInput = (PWDC_NVME_DE_VU_LOGPAGES)calloc(1, sizeof(WDC_NVME_DE_VU_LOGPAGES)); + vuLogInput->numOfVULogPages = sizeof(deVULogPagesList) / sizeof(deVULogPagesList[0]); + + for (vuLogIdx = 0; vuLogIdx < vuLogInput->numOfVULogPages; vuLogIdx++) + { + dataBufferSize = deVULogPagesList[vuLogIdx].logPageLen; + dataBuffer = calloc(1, dataBufferSize); + memset(dataBuffer, 0, dataBufferSize); + + ret = nvme_get_log(fd, WDC_DE_GLOBAL_NSID, deVULogPagesList[vuLogIdx].logPageId, + false, dataBufferSize, dataBuffer); + if (ret) { + fprintf(stderr, "ERROR : WDC : nvme_get_log() for log page 0x%x failed, ret = %d\n", + deVULogPagesList[vuLogIdx].logPageId, ret); + } else { + wdc_UtilsDeleteCharFromString((char*)deVULogPagesList[vuLogIdx].logPageIdStr, 4, ' '); + wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s_%s.bin", (char*)bufferFolderPath, WDC_DE_PATH_SEPARATOR, + "LogPage", (char*)&deVULogPagesList[vuLogIdx].logPageIdStr, (char*)serialNo, (char*)timeString); + wdc_WriteToFile(fileName, (char*)dataBuffer, dataBufferSize); + } + + free(dataBuffer); + dataBuffer = NULL; + } + + free(vuLogInput); + + /* Get NVMe Features (0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C) */ + for (listIdx = 1; listIdx < (sizeof(deFeatureIdList) / sizeof(deFeatureIdList[0])); listIdx++) + { + memset(featureIdBuff, 0, sizeof(featureIdBuff)); + /* skipping LbaRangeType as it is an optional nvme command and not supported */ + if (deFeatureIdList[listIdx].featureId == FID_LBA_RANGE_TYPE) + continue; + ret = nvme_get_feature(fd, WDC_DE_GLOBAL_NSID, deFeatureIdList[listIdx].featureId, FS_CURRENT, 0, + sizeof(featureIdBuff), &featureIdBuff, &result); + + if (ret) { + fprintf(stderr, "ERROR : WDC : nvme_get_feature id 0x%x failed, ret = %d\n", + deFeatureIdList[listIdx].featureId, ret); + } else { + wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s0x%x_%s_%s_%s.bin", (char*)bufferFolderPath, WDC_DE_PATH_SEPARATOR, + "FEATURE_ID_", deFeatureIdList[listIdx].featureId, + deFeatureIdList[listIdx].featureName, serialNo, timeString); + wdc_WriteToFile(fileName, (char*)featureIdBuff, sizeof(featureIdBuff)); + } + } + + /* Read Debug Directory */ + ret = wdc_get_log_dir_max_entries(fd, &maxNumOfVUFiles); + if (ret == WDC_STATUS_SUCCESS) + { + memset(&deEssentialsList, 0, sizeof(deEssentialsList)); + deEssentialsList.logEntry = (WDC_DRIVE_ESSENTIALS*)calloc(1, sizeof(WDC_DRIVE_ESSENTIALS)*maxNumOfVUFiles); + deEssentialsList.maxNumLogEntries = maxNumOfVUFiles; + + /* Fetch VU File Directory */ + ret = wdc_fetch_log_directory(fd, &deEssentialsList); + if (ret == WDC_STATUS_SUCCESS) + { + /* Get Debug Data Files */ + for (listIdx = 0; listIdx < deEssentialsList.numOfValidLogEntries; listIdx++) + { + if (0 == deEssentialsList.logEntry[listIdx].metaData.fileSize) + { + fprintf(stderr, "ERROR : WDC : File Size for %s is 0\n", + deEssentialsList.logEntry[listIdx].metaData.fileName); + ret = WDC_STATUS_FILE_SIZE_ZERO; + } else { + /* Fetch Log File Data */ + dataBuffer = (char *)calloc(1, (size_t)deEssentialsList.logEntry[listIdx].metaData.fileSize); + ret = wdc_fetch_log_file_from_device(fd, deEssentialsList.logEntry[listIdx].metaData.fileID, WDC_DE_DESTN_SPI, deEssentialsList.logEntry[listIdx].metaData.fileSize, + (__u8 *)dataBuffer); + + /* Write databuffer to file */ + if (ret == WDC_STATUS_SUCCESS) + { + memset(fileName, 0, sizeof(fileName)); + wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", bufferFolderPath, WDC_DE_PATH_SEPARATOR, + deEssentialsList.logEntry[listIdx].metaData.fileName, serialNo, timeString); + if (deEssentialsList.logEntry[listIdx].metaData.fileSize > 0xFFFFFFFF) + { + wdc_WriteToFile(fileName, dataBuffer, 0xFFFFFFFF); + wdc_WriteToFile(fileName, dataBuffer + 0xFFFFFFFF, (__u32)(deEssentialsList.logEntry[listIdx].metaData.fileSize - 0xFFFFFFFF)); + } else { + wdc_WriteToFile(fileName, dataBuffer, (__u32)deEssentialsList.logEntry[listIdx].metaData.fileSize); + } + } else { + fprintf(stderr, "ERROR : WDC : wdc_fetch_log_file_from_device: %s failed, ret = %d\n", + deEssentialsList.logEntry[listIdx].metaData.fileName, ret); + } + free(dataBuffer); + dataBuffer = NULL; + } + } + } else { + fprintf(stderr, "WDC : wdc_fetch_log_directory failed, ret = %d\n", ret); + } + + free(deEssentialsList.logEntry); + deEssentialsList.logEntry = NULL; + } else { + fprintf(stderr, "WDC : wdc_get_log_dir_max_entries failed, ret = %d\n", ret); + } + + /* Get Dump Trace Data */ + wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", (char*)bufferFolderPath, WDC_DE_PATH_SEPARATOR, "dumptrace", serialNo, timeString); + if (WDC_STATUS_SUCCESS != (ret = wdc_de_get_dump_trace(fd, (char*)bufferFolderPath, 0, fileName))) + { + fprintf(stderr, "ERROR : WDC : wdc_de_get_dump_trace failed, ret = %d\n", ret); + } + + /* Tar the Drive Essentials directory */ + wdc_UtilsSnprintf(tarFileName, sizeof(tarFileName), "%s%s", (char*)bufferFolderPath, WDC_DE_TAR_FILE_EXTN); + if (dir != NULL) { + wdc_UtilsSnprintf(tarFiles, sizeof(tarFiles), "%s%s%s%s%s", + (char*)dir, WDC_DE_PATH_SEPARATOR, (char*)bufferFolderName, WDC_DE_PATH_SEPARATOR, WDC_DE_TAR_FILES); + } else { + wdc_UtilsSnprintf(tarFiles, sizeof(tarFiles), "%s%s%s", (char*)bufferFolderName, WDC_DE_PATH_SEPARATOR, WDC_DE_TAR_FILES); + } + wdc_UtilsSnprintf(tarCmd, sizeof(tarCmd), "%s %s %s", WDC_DE_TAR_CMD, (char*)tarFileName, (char*)tarFiles); + + ret = system(tarCmd); + + if (ret) { + fprintf(stderr, "ERROR : WDC : Tar of Drive Essentials data failed, ret = %d\n", ret); + } + + fprintf(stderr, "Get of Drive Essentials data successful\n"); + return 0; +} + +static int wdc_drive_essentials(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char *desc = "Capture Drive Essentials."; + char *dirName = "Output directory pathname."; + char d[PATH_MAX] = {0}; + char k[PATH_MAX] = {0}; + char *d_ptr; + int fd; + __u64 capabilities = 0; + + struct config { + char *dirName; + }; + + struct config cfg = { + .dirName = NULL, + }; + + OPT_ARGS(opts) = { + OPT_STRING("dir-name", 'd', "DIRECTORY", &cfg.dirName, dirName), + OPT_END() + }; + + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + capabilities = wdc_get_drive_capabilities(fd); + if ((capabilities & WDC_DRIVE_CAP_DRIVE_ESSENTIALS) != WDC_DRIVE_CAP_DRIVE_ESSENTIALS) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + return -1; + } + + if (cfg.dirName != NULL) { + strncpy(d, cfg.dirName, PATH_MAX - 1); + d_ptr = d; + } else { + d_ptr = NULL; + } + + return wdc_do_drive_essentials(fd, d_ptr, k); +} + +static int wdc_do_drive_resize(int fd, uint64_t new_size) +{ + int ret; + struct nvme_admin_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = WDC_NVME_DRIVE_RESIZE_OPCODE; + admin_cmd.cdw12 = ((WDC_NVME_DRIVE_RESIZE_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | + WDC_NVME_DRIVE_RESIZE_CMD); + admin_cmd.cdw13 = new_size; + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + return ret; +} + +static int wdc_do_namespace_resize(int fd, __u32 nsid, __u32 op_option) +{ + int ret; + struct nvme_admin_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = WDC_NVME_NAMESPACE_RESIZE_OPCODE; + admin_cmd.nsid = nsid; + admin_cmd.cdw10 = op_option; + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + return ret; +} + +static int wdc_do_drive_info(int fd, __u32 *result) +{ + int ret; + struct nvme_admin_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof (struct nvme_admin_cmd)); + admin_cmd.opcode = WDC_NVME_DRIVE_INFO_OPCODE; + admin_cmd.cdw12 = ((WDC_NVME_DRIVE_INFO_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | + WDC_NVME_DRIVE_INFO_CMD); + + ret = nvme_submit_admin_passthru(fd, &admin_cmd); + + if (!ret && result) + *result = admin_cmd.result; + + return ret; +} + + +static int wdc_drive_resize(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + const char *desc = "Send a Resize command."; + const char *size = "The new size (in GB) to resize the drive to."; + uint64_t capabilities = 0; + int fd, ret; + + struct config { + uint64_t size; + }; + + struct config cfg = { + .size = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("size", 's', &cfg.size, size), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + wdc_check_device(fd); + capabilities = wdc_get_drive_capabilities(fd); + if ((capabilities & WDC_DRIVE_CAP_RESIZE) == WDC_DRIVE_CAP_RESIZE) { + ret = wdc_do_drive_resize(fd, cfg.size); + } else { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + } + + if (!ret) + printf("New size: %" PRIu64 " GB\n", cfg.size); + + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + return ret; +} + +static int wdc_namespace_resize(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + const char *desc = "Send a Namespace Resize command."; + const char *namespace_id = "The namespace id to resize."; + const char *op_option = "The over provisioning option to set for namespace."; + uint64_t capabilities = 0; + int fd, ret; + + struct config { + __u32 namespace_id; + __u32 op_option; + }; + + struct config cfg = { + .namespace_id = 0x1, + .op_option = 0xF, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("op-option", 'o', &cfg.op_option, op_option), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if ((cfg.op_option != 0x1) && + (cfg.op_option != 0x2) && + (cfg.op_option != 0x3) && + (cfg.op_option != 0xF)) + { + fprintf(stderr, "ERROR : WDC: unsupported OP option parameter\n"); + return -1; + } + + wdc_check_device(fd); + capabilities = wdc_get_drive_capabilities(fd); + if ((capabilities & WDC_DRIVE_CAP_NS_RESIZE) == WDC_DRIVE_CAP_NS_RESIZE) { + ret = wdc_do_namespace_resize(fd, cfg.namespace_id, cfg.op_option); + + if (ret != 0) + printf("ERROR : WDC: Namespace Resize of namespace id 0x%x, op option 0x%x failed\n", cfg.namespace_id, cfg.op_option); + } else { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + } + + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + return ret; +} + +static int wdc_reason_identifier(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + const char *desc = "Retrieve telemetry log reason identifier."; + const char *log_id = "Log ID to retrieve - host - 7 or controller - 8"; + const char *fname = "File name to save raw binary identifier"; + int fd; + int ret; + uint64_t capabilities = 0; + char f[PATH_MAX] = {0}; + char fileSuffix[PATH_MAX] = {0}; + UtilsTimeInfo timeInfo; + __u8 timeStamp[MAX_PATH_LEN]; + + + struct config { + int log_id; + char *file; + }; + struct config cfg = { + .log_id = 7, + .file = NULL, + }; + + OPT_ARGS(opts) = { + OPT_UINT("log-id", 'i', &cfg.log_id, log_id), + OPT_FILE("file", 'o', &cfg.file, fname), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + + if (fd < 0) + return fd; + + if (cfg.log_id != NVME_LOG_TELEMETRY_HOST && cfg.log_id != NVME_LOG_TELEMETRY_CTRL) { + fprintf(stderr, "ERROR : WDC: Invalid Log ID. It must be 7 (Host) or 8 (Controller)\n"); + ret = -1; + goto close_fd; + } + + if (cfg.file != NULL) { + int verify_file; + + /* 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)); + ret = -1; + goto close_fd; + } + 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); + if (cfg.log_id == NVME_LOG_TELEMETRY_CTRL) + snprintf(fileSuffix, PATH_MAX, "_error_reason_identifier_ctlr_%s", (char*)timeStamp); + else + snprintf(fileSuffix, PATH_MAX, "_error_reason_identifier_host_%s", (char*)timeStamp); + + if (wdc_get_serial_name(fd, f, PATH_MAX, fileSuffix) == -1) { + fprintf(stderr, "ERROR : WDC: failed to generate file name\n"); + ret = -1; + goto close_fd; + } + + snprintf(f + strlen(f), PATH_MAX, "%s", ".bin"); + } + + fprintf(stderr, "%s: filename = %s\n", __func__, f); + + capabilities = wdc_get_drive_capabilities(fd); + if ((capabilities & WDC_DRIVE_CAP_REASON_ID) == WDC_DRIVE_CAP_REASON_ID) { + ret = wdc_do_get_reason_id(fd, f, cfg.log_id); + } else { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + } + + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + + close_fd: + close(fd); + return ret; +} + +static const char *nvme_log_id_to_string(__u8 log_id) +{ + switch (log_id) { + case NVME_LOG_ERROR: return "Error Information Log ID"; + case NVME_LOG_SMART: return "Smart/Health Information Log ID"; + case NVME_LOG_FW_SLOT: return "Firmware Slot Information Log ID"; + case NVME_LOG_CHANGED_NS: return "Namespace Changed Log ID"; + case NVME_LOG_CMD_EFFECTS: return "Commamds Supported and Effects Log ID"; + case NVME_LOG_DEVICE_SELF_TEST: return "Device Self Test Log ID"; + case NVME_LOG_TELEMETRY_HOST: return "Telemetry Host Initiated Log ID"; + case NVME_LOG_TELEMETRY_CTRL: return "Telemetry Controller Generated Log ID"; + case NVME_LOG_ENDURANCE_GROUP: return "Endurance Group Log ID"; + case NVME_LOG_ANA: return "ANA Log ID"; + case NVME_LOG_PERSISTENT_EVENT: return "Persistent Event Log ID"; + case NVME_LOG_DISC: return "Discovery Log ID"; + case NVME_LOG_RESERVATION: return "Reservation Notification Log ID"; + case NVME_LOG_SANITIZE: return "Sanitize Status Log ID"; + + case WDC_LOG_ID_C0: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_C2: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_C4: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_C5: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_C6: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_CA: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_CB: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_D0: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_D6: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_D7: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_D8: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_DE: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_F0: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_F1: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_F2: return "WDC Vendor Unique Log ID"; + case WDC_LOG_ID_FA: return "WDC Vendor Unique Log ID"; + + default: return "Unknown Log ID"; + } +} + +static int wdc_log_page_directory(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve Log Page Directory."; + int fd; + int ret = 0; + __u64 capabilities = 0; + struct wdc_c2_cbs_data *cbs_data = NULL; + int i; + + struct config { + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json|binary"), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + ret = validate_output_format(cfg.output_format); + if (ret < 0) { + fprintf(stderr, "%s: ERROR : WDC : invalid output format\n", __func__); + return ret; + } + + capabilities = wdc_get_drive_capabilities(fd); + + if ((capabilities & WDC_DRIVE_CAP_LOG_PAGE_DIR) == 0) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + } else { + /* verify the 0xC2 Device Manageability log page is supported */ + if (wdc_nvme_check_supported_log_page(fd, WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE) == false) { + fprintf(stderr, "%s: ERROR : WDC : 0xC2 Log Page not supported\n", __func__); + ret = -1; + goto out; + } + + if (get_dev_mgment_cbs_data(fd, WDC_C2_LOG_PAGES_SUPPORTED_ID, (void *)&cbs_data)) { + if (cbs_data != NULL) { + 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; + 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]); + } + + 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); + } else + fprintf(stderr, "%s: ERROR : WDC : NULL_data ptr\n", __func__); + } else + fprintf(stderr, "%s: ERROR : WDC : 0xC2 Log Page entry ID 0x%x not found\n", __func__, WDC_C2_LOG_PAGES_SUPPORTED_ID); + + + } + + out: + return ret; +} + +static int wdc_get_drive_reason_id(int fd, char *drive_reason_id, size_t len) +{ + int i, j; + int ret; + int res_len = 0; + struct nvme_id_ctrl ctrl; + char *reason_id_str = "reason_id"; + + i = sizeof (ctrl.sn) - 1; + j = sizeof (ctrl.mn) - 1; + memset(drive_reason_id, 0, len); + memset(&ctrl, 0, sizeof (struct nvme_id_ctrl)); + ret = nvme_identify_ctrl(fd, &ctrl); + if (ret) { + fprintf(stderr, "ERROR : WDC : nvme_identify_ctrl() failed " + "0x%x\n", ret); + return -1; + } + /* Remove trailing spaces from the sn and mn */ + while (i && ctrl.sn[i] == ' ') { + ctrl.sn[i] = '\0'; + i--; + } + + while (j && ctrl.mn[j] == ' ') { + ctrl.mn[j] = '\0'; + j--; + } + + res_len = snprintf(drive_reason_id, len, "%s_%s_%s", ctrl.sn, ctrl.mn, reason_id_str); + if (len <= res_len) { + fprintf(stderr, "ERROR : WDC : cannot format serial number due to data " + "of unexpected length\n"); + return -1; + } + + return 0; +} + +static int wdc_save_reason_id(int fd, __u8 *rsn_ident, int size) +{ + int ret = 0; + char *reason_id_file; + char drive_reason_id[PATH_MAX] = {0}; + char reason_id_path[PATH_MAX] = WDC_REASON_ID_PATH_NAME; + struct stat st = {0}; + + if (wdc_get_drive_reason_id(fd, drive_reason_id, PATH_MAX) == -1) { + fprintf(stderr, "%s: ERROR : failed to get drive reason id\n", __func__); + return -1; + } + + /* make the nvmecli dir in /usr/local if it doesn't already exist */ + if (stat(reason_id_path, &st) == -1) { + mkdir(reason_id_path, 0700); + } + + if (asprintf(&reason_id_file, "%s/%s%s", reason_id_path, + drive_reason_id, ".bin") < 0) + return -ENOMEM; + + fprintf(stderr, "%s: reason id file = %s\n", __func__, reason_id_file); + + /* save off the error reason identifier to a file in /usr/local/nvmecli */ + ret = wdc_create_log_file(reason_id_file, rsn_ident, WDC_REASON_ID_ENTRY_LEN); + free(reason_id_file); + + return ret; +} + +static int wdc_clear_reason_id(int fd) +{ + int ret = -1; + int verify_file; + char *reason_id_file; + char drive_reason_id[PATH_MAX] = {0}; + + if (wdc_get_drive_reason_id(fd, drive_reason_id, PATH_MAX) == -1) { + fprintf(stderr, "%s: ERROR : failed to get drive reason id\n", __func__); + return -1; + } + + if (asprintf(&reason_id_file, "%s/%s%s", WDC_REASON_ID_PATH_NAME, + drive_reason_id, ".bin") < 0) + return -ENOMEM; + + /* verify the drive reason id file name and path is valid */ + verify_file = open(reason_id_file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (verify_file < 0) { + ret = -1; + goto free; + } + close(verify_file); + + /* remove the reason id file */ + ret = remove(reason_id_file); + + free: + free(reason_id_file); + + return ret; +} + +static int wdc_do_get_reason_id(int fd, char *file, int log_id) +{ + int ret; + struct nvme_telemetry_log_page_hdr *log_hdr; + __u32 log_hdr_size = sizeof(struct nvme_telemetry_log_page_hdr); + __u32 reason_id_size = 0; + + log_hdr = (struct nvme_telemetry_log_page_hdr *) malloc(log_hdr_size); + if (log_hdr == NULL) { + fprintf(stderr, "%s: ERROR : malloc failed, size : 0x%x, status : %s\n", __func__, log_hdr_size, strerror(errno)); + ret = -1; + goto out; + } + memset(log_hdr, 0, log_hdr_size); + + ret = wdc_dump_telemetry_hdr(fd, log_id, log_hdr); + if (ret != 0) { + fprintf(stderr, "%s: ERROR : get telemetry header failed, ret : %d\n", __func__, ret); + ret = -1; + goto out; + } + + reason_id_size = sizeof(log_hdr->rsnident); + + if (log_id == NVME_LOG_TELEMETRY_CTRL) + wdc_save_reason_id(fd, log_hdr->rsnident, reason_id_size); + + ret = wdc_create_log_file(file, (__u8 *)log_hdr->rsnident, reason_id_size); + +out: + free(log_hdr); + return ret; +} + +static int wdc_dump_telemetry_hdr(int fd, int log_id, struct nvme_telemetry_log_page_hdr *log_hdr) +{ + int ret = 0; + int host_gen = 0, ctrl_init = 0; + + if (log_id == NVME_LOG_TELEMETRY_HOST) + host_gen = 1; + else + ctrl_init = 1; + + ret = nvme_get_telemetry_log(fd, log_hdr, host_gen, ctrl_init, 512, 0); + if (ret < 0) + perror("get-telemetry-log"); + else if (ret > 0) { + nvme_show_status(ret); + fprintf(stderr, "%s: ERROR : Failed to acquire telemetry header, ret = %d!\n", __func__, ret); + } + + return ret; +} + +static void wdc_print_nand_stats_normal(struct wdc_nand_stats *data) +{ + printf(" NAND Statistics :- \n"); + printf(" NAND Writes TLC (Bytes) %.0Lf\n", + int128_to_double(data->nand_write_tlc)); + printf(" NAND Writes SLC (Bytes) %.0Lf\n", + int128_to_double(data->nand_write_slc)); + printf(" NAND Program Failures %"PRIu32"\n", + (uint32_t)le32_to_cpu(data->nand_prog_failure)); + printf(" NAND Erase Failures %"PRIu32"\n", + (uint32_t)le32_to_cpu(data->nand_erase_failure)); + printf(" Bad Block Count %"PRIu32"\n", + (uint32_t)le32_to_cpu(data->bad_block_count)); + printf(" NAND XOR/RAID Recovery Trigger Events %"PRIu64"\n", + le64_to_cpu(data->nand_rec_trigger_event)); + printf(" E2E Error Counter %"PRIu64"\n", + le64_to_cpu(data->e2e_error_counter)); + printf(" Number Successful NS Resizing Events %"PRIu64"\n", + le64_to_cpu(data->successful_ns_resize_event)); +} + +static void wdc_print_nand_stats_json(struct wdc_nand_stats *data) +{ + struct json_object *root; + + root = json_create_object(); + json_object_add_value_float(root, "NAND Writes TLC (Bytes)", + int128_to_double(data->nand_write_tlc)); + json_object_add_value_float(root, "NAND Writes SLC (Bytes)", + int128_to_double(data->nand_write_slc)); + json_object_add_value_uint(root, "NAND Program Failures", + le32_to_cpu(data->nand_prog_failure)); + json_object_add_value_uint(root, "NAND Erase Failures", + le32_to_cpu(data->nand_erase_failure)); + json_object_add_value_uint(root, "Bad Block Count", + le32_to_cpu(data->bad_block_count)); + json_object_add_value_uint(root, "NAND XOR/RAID Recovery Trigger Events", + le64_to_cpu(data->nand_rec_trigger_event)); + json_object_add_value_uint(root, "E2E Error Counter", + le64_to_cpu(data->e2e_error_counter)); + json_object_add_value_uint(root, "Number Successful NS Resizing Events", + le64_to_cpu(data->successful_ns_resize_event)); + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static int wdc_do_vs_nand_stats(int fd, char *format) +{ + int ret; + int fmt = -1; + uint8_t *output = NULL; + struct wdc_nand_stats *nand_stats; + + if ((output = (uint8_t*)calloc(WDC_NVME_NAND_STATS_SIZE, sizeof(uint8_t))) == NULL) { + fprintf(stderr, "ERROR : WDC : calloc : %s\n", strerror(errno)); + ret = -1; + goto out; + } + + ret = nvme_get_log(fd, 0xFFFFFFFF, WDC_NVME_NAND_STATS_LOG_ID, + false, WDC_NVME_NAND_STATS_SIZE, (void*)output); + if (ret) { + fprintf(stderr, "ERROR : WDC : %s : Failed to retreive NAND stats\n", __func__); + goto out; + } else { + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + ret = fmt; + goto out; + } + + /* parse the data */ + nand_stats = (struct wdc_nand_stats *)(output); + switch (fmt) { + case NORMAL: + wdc_print_nand_stats_normal(nand_stats); + break; + case JSON: + wdc_print_nand_stats_json(nand_stats); + break; + } + } + +out: + free(output); + return ret; +} + +static int wdc_vs_nand_stats(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve NAND statistics."; + int fd; + int ret = 0; + __u64 capabilities = 0; + + struct config { + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + capabilities = wdc_get_drive_capabilities(fd); + + if ((capabilities & WDC_DRIVE_CAP_NAND_STATS) == 0) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + } else { + ret = wdc_do_vs_nand_stats(fd, cfg.output_format); + if (ret) + fprintf(stderr, "ERROR : WDC : Failure reading NAND statistics, ret = %d\n", ret); + } + + return ret; +} + +static int wdc_vs_drive_info(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + const char *desc = "Send a vs-drive-info command."; + uint64_t capabilities = 0; + int fd, ret; + __le32 result; + __u16 size; + double rev; + + OPT_ARGS(opts) = { + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + wdc_check_device(fd); + capabilities = wdc_get_drive_capabilities(fd); + if ((capabilities & WDC_DRIVE_CAP_INFO) == WDC_DRIVE_CAP_INFO) { + ret = wdc_do_drive_info(fd, &result); + } else { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + } + + if (!ret) { + size = (__u16)((cpu_to_le32(result) & 0xffff0000) >> 16); + rev = (double)(cpu_to_le32(result) & 0x0000ffff); + + printf("Drive HW Revison: %4.1f\n", (.1 * rev)); + printf("FTL Unit Size: 0x%x KB\n", size); + } + + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret), ret); + return ret; +} + + |