--- /dev/null
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <time.h>
+#include <locale.h>
+
+#include "linux/nvme_ioctl.h"
+#include "nvme.h"
+#include "nvme-print.h"
+#include "nvme-ioctl.h"
+#include "plugin.h"
+#include "argconfig.h"
+#include "suffix.h"
+
+#define CREATE_CMD
+#include "virtium-nvme.h"
+
+#define MIN2(a, b) ( ((a) < (b))? (a) : (b))
+
+#define HOUR_IN_SECONDS 3600
+
+#define MAX_HEADER_BUFF 16384
+#define MAX_LOG_BUFF 4096
+#define DEFAULT_TEST_NAME "Put the name of your test here"
+
+static char vt_default_log_file_name[256];
+
+struct vtview_log_header
+{
+ char path[256];
+ char test_name[256];
+ long int time_stamp;
+ struct nvme_id_ctrl raw_ctrl;
+ struct nvme_firmware_log_page raw_fw;
+};
+
+struct vtview_smart_log_entry
+{
+ char path[256];
+ long int time_stamp;
+ struct nvme_id_ns raw_ns;
+ struct nvme_id_ctrl raw_ctrl;
+ struct nvme_smart_log raw_smart;
+};
+
+struct vtview_save_log_settings
+{
+ double run_time_hrs;
+ double log_record_frequency_hrs;
+ const char* output_file;
+ const char* test_name;
+};
+
+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 void vt_initialize_header_buffer(struct vtview_log_header *pbuff)
+{
+ memset(pbuff->path, 0, sizeof(pbuff->path));
+ memset(pbuff->test_name, 0, sizeof(pbuff->test_name));
+}
+
+static void vt_convert_data_buffer_to_hex_string(const unsigned char *bufPtr, const unsigned int size, const bool isReverted, char *output)
+{
+ unsigned int i, pos;
+ const char hextable[16] = \
+ {
+ '0', '1', '2', '3',
+ '4', '5', '6', '7',
+ '8', '9', 'A', 'B',
+ 'C', 'D', 'E', 'F',
+ };
+
+ memset(output, 0, (size * 2) + 1);
+
+ for(i = 0; i < size; i++)
+ {
+ if(isReverted)
+ {
+ pos = size - 1 - i;
+ }
+ else
+ {
+ pos = i;
+ }
+ output[2 * i] = hextable[(bufPtr[pos] & 0xF0) >> 4];
+ output[2 * i + 1] = hextable[(bufPtr[pos] & 0x0F)];
+ }
+}
+
+// Generate log file name.
+// Log file name will be generated automatically if user leave log file option blank.
+// Log file name will be generated as vtView-Smart-log-date-time.txt
+static void vt_generate_vtview_log_file_name(char* fname)
+{
+ time_t current;
+ struct tm tstamp;
+ char temp[256];
+
+ time(¤t);
+
+ tstamp = *localtime(¤t);
+ snprintf(temp, sizeof(temp), "./vtView-Smart-log-");
+ strcat(fname, temp);
+ strftime(temp, sizeof(temp), "%Y-%m-%d", &tstamp);
+ strcat(fname, temp);
+ snprintf(temp, sizeof(temp), ".txt");
+ strcat(fname, temp);
+}
+
+static void vt_convert_smart_data_to_human_readable_format(struct vtview_smart_log_entry *smart, char *text)
+{
+ char tempbuff[1024] = "";
+ int i;
+ int temperature = ((smart->raw_smart.temperature[1] << 8) | smart->raw_smart.temperature[0]) - 273;
+
+ snprintf(tempbuff, sizeof(tempbuff), "log;%s;%lu;%s;%s;%-.*s;", smart->raw_ctrl.sn, smart->time_stamp, smart->path, \
+ smart->raw_ctrl.mn, (int)sizeof(smart->raw_ctrl.fr), smart->raw_ctrl.fr);
+ strcpy(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Capacity;%f;", (double)smart->raw_ns.nsze / 1000000000);
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Critical_Warning;%u;", smart->raw_smart.critical_warning);
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Temperature;%u;", temperature);
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Available_Spare;%u;", smart->raw_smart.avail_spare);
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Available_Spare_Threshold;%u;", smart->raw_smart.spare_thresh);
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Percentage_Used;%u;", smart->raw_smart.percent_used);
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Data_Units_Read;%0.Lf;", int128_to_double(smart->raw_smart.data_units_read));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Data_Units_Written;%0.Lf;", int128_to_double(smart->raw_smart.data_units_written));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Host_Read_Commands;%0.Lf;", int128_to_double(smart->raw_smart.host_reads));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Host_Write_Commands;%0.Lf;", int128_to_double(smart->raw_smart.host_writes));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Controller_Busy_Time;%0.Lf;", int128_to_double(smart->raw_smart.ctrl_busy_time));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Power_Cycles;%0.Lf;", int128_to_double(smart->raw_smart.power_cycles));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Power_On_Hours;%0.Lf;", int128_to_double(smart->raw_smart.power_on_hours));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Unsafe_Shutdowns;%0.Lf;", int128_to_double(smart->raw_smart.unsafe_shutdowns));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Media_Errors;%0.Lf;", int128_to_double(smart->raw_smart.media_errors));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Num_Err_Log_Entries;%0.Lf;", int128_to_double(smart->raw_smart.num_err_log_entries));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Warning_Temperature_Time;%u;", le32_to_cpu(smart->raw_smart.warning_temp_time));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Critical_Composite_Temperature_Time;%u;", le32_to_cpu(smart->raw_smart.critical_comp_time));
+ strcat(text, tempbuff);
+
+ for(i = 0; i < 8; i++)
+ {
+ __s32 temp = le16_to_cpu(smart->raw_smart.temp_sensor[i]);
+ if(0 == temp)
+ {
+ snprintf(tempbuff, sizeof(tempbuff), "Temperature_Sensor_%d;NC;", i);
+ strcat(text, tempbuff);
+ continue;
+ }
+ snprintf(tempbuff, sizeof(tempbuff), "Temperature_Sensor_%d;%d;", i, temp - 273);
+ strcat(text, tempbuff);
+ }
+
+ snprintf(tempbuff, sizeof(tempbuff), "Thermal_Management_T1_Trans_Count;%u;", le32_to_cpu(smart->raw_smart.thm_temp1_trans_count));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Thermal_Management_T2_Trans_Count;%u;", le32_to_cpu(smart->raw_smart.thm_temp2_trans_count));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Thermal_Management_T1_Total_Time;%u;", le32_to_cpu(smart->raw_smart.thm_temp1_total_time));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Thermal_Management_T2_Total_Time;%u;", le32_to_cpu(smart->raw_smart.thm_temp2_total_time));
+ strcat(text, tempbuff);
+
+ snprintf(tempbuff, sizeof(tempbuff), "Reversed_1;%d;\n", 0);
+ strcat(text, tempbuff);
+}
+
+static void vt_header_to_string(const struct vtview_log_header *header, char *text)
+{
+ char timebuff[50] = "";
+ char tempbuff[MAX_HEADER_BUFF] = "";
+ char identext[16384] = "";
+ char fwtext[2048] = "";
+
+ // fill session
+ strftime(timebuff, 50, "%Y-%m-%d %H:%M:%S", localtime(&(header->time_stamp)));
+ snprintf(tempbuff, MAX_HEADER_BUFF, "header;{\"session\":{\"testName\":\"%s\",\"dateTime\":\"%s\"},", \
+ header->test_name, timebuff);
+ strcpy(text, tempbuff);
+
+ // fill device
+ vt_convert_data_buffer_to_hex_string((unsigned char *)&(header->raw_ctrl), sizeof(header->raw_ctrl), false, identext);
+ vt_convert_data_buffer_to_hex_string((unsigned char *)&(header->raw_fw), sizeof(header->raw_fw), false, fwtext);
+ snprintf(tempbuff, MAX_HEADER_BUFF, \
+ "\"devices\":[{\"model\":\"%s\",\"port\":\"%s\",\"SN\":\"%s\",\"type\":\"NVMe\",\"identify\":\"%s\",\"firmwareSlot\":\"%s\"}]}\n", \
+ header->raw_ctrl.mn, header->path, header->raw_ctrl.sn, identext, fwtext);
+
+ strcat(text, tempbuff);
+}
+
+static int vt_append_text_file(const char *text, const char *filename)
+{
+ FILE *f;
+
+ f = fopen(filename, "a");
+ if(NULL == f)
+ {
+ printf("Cannot open %s\n", filename);
+ return -1;
+ }
+
+ fprintf(f, "%s", text);
+ fclose(f);
+
+ return 0;
+}
+
+static int vt_append_log(struct vtview_smart_log_entry *smart, const char *filename)
+{
+ char sm_log_text[MAX_LOG_BUFF] = "";
+
+ vt_convert_smart_data_to_human_readable_format(smart, sm_log_text);
+
+ return vt_append_text_file(sm_log_text, filename);
+}
+
+static int vt_append_header(const struct vtview_log_header *header, const char *filename)
+{
+ char header_text[MAX_HEADER_BUFF] = "";
+
+ vt_header_to_string(header, header_text);
+
+ return vt_append_text_file(header_text, filename);
+}
+
+static void vt_process_string(char *str, const size_t size)
+{
+ size_t i;
+ if(size == 0)
+ {
+ return;
+ }
+
+ i = size - 1;
+ while((0 != i) && (' ' == str[i]))
+ {
+ str[i] = 0;
+ i--;
+ }
+}
+
+static int vt_add_entry_to_log(const int fd, const char *path, const struct vtview_save_log_settings *cfg)
+{
+ struct vtview_smart_log_entry smart;
+ char filename[256] = "";
+ int ret = 0;
+ int nsid = 0;
+
+ memset(smart.path, 0, sizeof(smart.path));
+ strcpy(smart.path, path);
+ if(NULL == cfg->output_file)
+ {
+ strcpy(filename, vt_default_log_file_name);
+ }
+ else
+ {
+ strcpy(filename, cfg->output_file);
+ }
+
+ smart.time_stamp = time(0);
+ nsid = nvme_get_nsid(fd);
+
+ if(nsid <= 0)
+ {
+ printf("Cannot read namespace-id\n");
+ return -1;
+ }
+
+ ret = nvme_identify_ns(fd, nsid, 0, &smart.raw_ns);
+ if(ret)
+ {
+ printf("Cannot read namespace identify\n");
+ return -1;
+ }
+
+ ret = nvme_identify_ctrl(fd, &smart.raw_ctrl);
+ if(ret)
+ {
+ printf("Cannot read device identify controller\n");
+ return -1;
+ }
+
+ ret = nvme_smart_log(fd, NVME_NSID_ALL, &smart.raw_smart);
+ if(ret)
+ {
+ printf("Cannot read device SMART log\n");
+ return -1;
+ }
+
+ vt_process_string(smart.raw_ctrl.sn, sizeof(smart.raw_ctrl.sn));
+ vt_process_string(smart.raw_ctrl.mn, sizeof(smart.raw_ctrl.mn));
+
+ ret = vt_append_log(&smart, filename);
+
+ return (ret);
+}
+
+static int vt_update_vtview_log_header(const int fd, const char *path, const struct vtview_save_log_settings *cfg)
+{
+ struct vtview_log_header header;
+ char filename[256] = "";
+ int ret = 0;
+
+ vt_initialize_header_buffer(&header);
+ strcpy(header.path, path);
+
+ if(NULL == cfg->test_name)
+ {
+ strcpy(header.test_name, DEFAULT_TEST_NAME);
+ }
+ else
+ {
+ strcpy(header.test_name, cfg->test_name);
+ }
+
+ if(NULL == cfg->output_file)
+ {
+ strcpy(filename, vt_default_log_file_name);
+ }
+ else
+ {
+ strcpy(filename, cfg->output_file);
+ }
+
+ printf("Log file: %s\n", filename);
+ header.time_stamp = time(0);
+
+ ret = nvme_identify_ctrl(fd, &header.raw_ctrl);
+ if(ret)
+ {
+ printf("Cannot read identify device\n");
+ return -1;
+ }
+
+ ret = nvme_fw_log(fd, &header.raw_fw);
+ if(ret)
+ {
+ printf("Cannot read device firmware log\n");
+ return -1;
+ }
+
+ vt_process_string(header.raw_ctrl.sn, sizeof(header.raw_ctrl.sn));
+ vt_process_string(header.raw_ctrl.mn, sizeof(header.raw_ctrl.mn));
+
+ ret = vt_append_header(&header, filename);
+
+ return (ret);
+}
+
+void vt_build_identify_lv2(unsigned int data, unsigned int start, unsigned int count, const char **table, bool isEnd)
+{
+ unsigned int i, end, pos, sh = 1;
+ unsigned int temp;
+
+ end = start + count;
+
+ for(i = start; i < end; i++)
+ {
+ temp = ((data & (sh << i)) >> i);
+ pos = i * 2;
+ printf(" \"bit %u\":\"%ub %s\"\n", i, temp, table[pos]);
+ printf(" %s", table[pos + 1]);
+
+ if((end - 1) != i || !isEnd)
+ {
+ printf(",\n");
+ }
+ else
+ {
+ printf("\n");
+ }
+ }
+
+ if(isEnd)
+ {
+ printf(" },\n");
+ }
+}
+
+static void vt_parse_detail_identify(const struct nvme_id_ctrl *ctrl)
+{
+ unsigned char *buf;
+ unsigned int temp, pos;
+ char s[1024] = "";
+
+ const char *CMICtable[6] = {"0 = the NVM subsystem contains only a single NVM subsystem port", \
+ "1 = the NVM subsystem may contain more than one subsystem ports", \
+ "0 = the NVM subsystem contains only a single controller", \
+ "1 = the NVM subsystem may contain two or more controllers (see section 1.4.1)",\
+ "0 = the controller is associated with a PCI Function or a Fabrics connection", \
+ "1 = the controller is associated with an SR-IOV Virtual Function"};
+
+ const char *OAEStable[18] = {"Reversed", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "0 = does not support sending the Namespace Attribute Notices event nor the associated Changed Namespace List log page", \
+ "1 = supports sending the Namespace Attribute Notices & the associated Changed Namespace List log page", \
+ "0 = does not support sending sending Firmware Activation Notices event", \
+ "1 = supports sending Firmware Activation Notices"};
+
+ const char *CTRATTtable[4] = {"0 = does not support a 128-bit Host Identifier", \
+ "1 = supports a 128-bit Host Identifier", \
+ "0 = does not support Non-Operational Power State Permissive Mode", \
+ "1 = supports Non-Operational Power State Permissive Mode"};
+
+ const char *OACStable[18] = {"0 = does not support the Security Send and Security Receive commands", \
+ "1 = supports the Security Send and Security Receive commands", \
+ "0 = does not support the Format NVM command", \
+ "1 = supports the Format NVM command", \
+ "0 = does not support the Firmware Commit and Firmware Image Download commands", \
+ "1 = supports the Firmware Commit and Firmware Image Download commands",\
+ "0 = does not support the Namespace Management capability",\
+ "1 = supports the Namespace Management capability", \
+ "0 = does not support the Device Self-test command", \
+ "1 = supports the Device Self-test command", \
+ "0 = does not support Directives", \
+ "1 = supports Directive Send & Directive Receive commands", \
+ "0 = does not support the NVMe-MI Send and NVMe-MI Receive commands", \
+ "1 = supports the NVMe-MI Send and NVMe-MI Receive commands", \
+ "0 = does not support the Virtualization Management command", \
+ "1 = supports the Virtualization Management command", \
+ "0 = does not support the Doorbell Buffer Config command", \
+ "1 = supports the Doorbell Buffer Config command"};
+
+ const char *FRMWtable[10] = {"0 = the 1st firmware slot (slot 1) is read/write", \
+ "1 = the 1st firmware slot (slot 1) is read only", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "0 = requires a reset for firmware to be activated", \
+ "1 = supports firmware activation without a reset"};
+
+ const char *LPAtable[8] = {"0 = does not support the SMART / Health information log page on a per namespace basis", \
+ "1 = supports the SMART / Health information log page on a per namespace basis", \
+ "0 = does not support the Commands Supported & Effects log page", \
+ "1 = supports the Commands Supported Effects log page", \
+ "0 = does not support extended data for Get Log Page", \
+ "1 = supports extended data for Get Log Page (including extended Number of Dwords and Log Page Offset fields)", \
+ "0 = does not support the Telemetry Host-Initiated and Telemetry Controller-Initiated log pages and Telemetry Log Notices events", \
+ "1 = supports the Telemetry Host-Initiated and Telemetry Controller-Initiated log pages and sending Telemetry Log Notices"
+ };
+
+ const char *AVSCCtable[2] = {"0 = the format of all Admin Vendor Specific Commands are vendor specific", \
+ "1 = all Admin Vendor Specific Commands use the format defined in NVM Express specification"};
+
+ const char *APSTAtable[2] = {"0 = does not support autonomous power state transitions", \
+ "1 = supports autonomous power state transitions"};
+
+ const char *DSTOtable[2] = {"0 = the NVM subsystem supports one device self-test operation per controller at a time", \
+ "1 = the NVM subsystem supports only one device self-test operation in progress at a time"};
+
+ const char *HCTMAtable[2] = {"0 = does not support host controlled thermal management", \
+ "1 = supports host controlled thermal management. Supports Set Features & Get Features commands with the Feature Identifier field set to 10h"};
+
+ const char *SANICAPtable[6] = {"0 = does not support the Crypto Erase sanitize operation", \
+ "1 = supports the Crypto Erase sanitize operation", \
+ "0 = does not support the Block Erase sanitize operation", \
+ "1 = supports the Block Erase sanitize operation", \
+ "0 = does not support the Overwrite sanitize operation", \
+ "1 = supports the Overwrite sanitize operation"};
+
+ const char *ONCStable[14] = {"0 = does not support the Compare command", \
+ "1 = supports the Compare command", \
+ "0 = does not support the Write Uncorrectable command", \
+ "1 = supports the Write Uncorrectable command", \
+ "0 = does not support the Dataset Management command", \
+ "1 = supports the Dataset Management command", \
+ "0 = does not support the Write Zeroes command", \
+ "1 = supports the Write Zeroes command", \
+ "0 = does not support the Save field set to a non-zero value in the Set Features and the Get Features commands", \
+ "1 = supports the Save field set to a non-zero value in the Set Features and the Get Features commands", \
+ "0 = does not support reservations", \
+ "1 = supports reservations", \
+ "0 = does not support the Timestamp feature (refer to section 5.21.1.14)", \
+ "1 = supports the Timestamp feature"};
+
+ const char *FUSEStable[2] = {"0 = does not support the Compare and Write fused operation", \
+ "1 = supports the Compare and Write fused operation"};
+
+ const char *FNAtable[6] = {"0 = supports format on a per namespace basis", \
+ "1 = all namespaces shall be configured with the same attributes and a format (excluding secure erase) of any namespace results in a format of all namespaces in an NVM subsystem", \
+ "0 = any secure erase performed as part of a format results in a secure erase of a particular namespace specified", \
+ "1 = any secure erase performed as part of a format operation results in a secure erase of all namespaces in the NVM subsystem", \
+ "0 = cryptographic erase is not supported", \
+ "1 = cryptographic erase is supported as part of the secure erase functionality"};
+
+ const char *VWCtable[2] = {"0 = a volatile write cache is not present", \
+ "1 = a volatile write cache is present"};
+
+ const char *NVSCCtable[2] = {"0 = the format of all NVM Vendor Specific Commands are vendor specific", \
+ "1 = all NVM Vendor Specific Commands use the format defined in NVM Express specification"};
+
+ const char *SGLSSubtable[4] = {"00b = SGLs are not supported", \
+ "01b = SGLs are supported. There is no alignment nor granularity requirement for Data Blocks", \
+ "10b = SGLs are supported. There is a Dword alignment and granularity requirement for Data Blocks", \
+ "11b = Reserved"};
+
+ const char *SGLStable[42] = {"Used", \
+ "Used", \
+ "Used", \
+ "Used", \
+ "0 = does not support the Keyed SGL Data Block descriptor", \
+ "1 = supports the Keyed SGL Data Block descriptor", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "Reserved", \
+ "0 = the SGL Bit Bucket descriptor is not supported", \
+ "1 = the SGL Bit Bucket descriptor is supported", \
+ "0 = use of a byte aligned contiguous physical buffer of metadata is not supported", \
+ "1 = use of a byte aligned contiguous physical buffer of metadata is supported", \
+ "0 = the SGL length shall be equal to the amount of data to be transferred", \
+ "1 = supports commands that contain a data or metadata SGL of a length larger than the amount of data to be transferred", \
+ "0 = use of Metadata Pointer (MPTR) that contains an address of an SGL segment containing exactly one SGL Descriptor that is Qword aligned is not supported", \
+ "1 = use of Metadata Pointer (MPTR) that contains an address of an SGL segment containing exactly one SGL Descriptor that is Qword aligned is supported", \
+ "0 = the Address field specifying an offset is not supported", \
+ "1 = supports the Address field in SGL Data Block, SGL Segment, and SGL Last Segment descriptor types specifying an offset"};
+
+ buf = (unsigned char *)(ctrl);
+
+ printf("{\n");
+ vt_convert_data_buffer_to_hex_string(buf, 2, true, s);
+ printf(" \"PCI Vendor ID\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[2], 2, true, s);
+ printf(" \"PCI Subsystem Vendor ID\":\"%sh\",\n", s);
+ printf(" \"Serial Number\":\"%s\",\n", ctrl->sn);
+ printf(" \"Model Number\":\"%s\",\n", ctrl->mn);
+ printf(" \"Firmware Revision\":\"%-.*s\",\n", (int)sizeof(ctrl->fr), ctrl->fr);
+ vt_convert_data_buffer_to_hex_string(&buf[72], 1, true, s);
+ printf(" \"Recommended Arbitration Burst\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[73], 3, true, s);
+ printf(" \"IEEE OUI Identifier\":\"%sh\",\n", s);
+
+ temp = ctrl->cmic;
+ printf(" \"Controller Multi-Path I/O and Namespace Sharing Capabilities\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[76], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 2, CMICtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[77], 1, true, s);
+ printf(" \"Maximum Data Transfer Size\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[78], 2, true, s);
+ printf(" \"Controller ID\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[80], 4, true, s);
+ printf(" \"Version\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[84], 4, true, s);
+ printf(" \"RTD3 Resume Latency\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[88], 4, true, s);
+ printf(" \"RTD3 Entry Latency\":\"%sh\",\n", s);
+
+ temp = le32_to_cpu(ctrl->oaes);
+ printf(" \"Optional Asynchronous Events Supported\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[92], 4, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 8, 2, OAEStable, true);
+
+ temp = le32_to_cpu(ctrl->ctratt);
+ printf(" \"Controller Attributes\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[96], 4, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 2, CTRATTtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[122], 16, true, s);
+ printf(" \"FRU Globally Unique Identifier\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[240], 16, true, s);
+ printf(" \"NVMe Management Interface Specification\":\"%sh\",\n", s);
+
+ temp = le16_to_cpu(ctrl->oacs);
+ printf(" \"Optional Admin Command Support\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[256], 2, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 9, OACStable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[258], 1, true, s);
+ printf(" \"Abort Command Limit\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[259], 1, true, s);
+ printf(" \"Asynchronous Event Request Limit\":\"%sh\",\n", s);
+
+ temp = ctrl->frmw;
+ printf(" \"Firmware Updates\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[260], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, FRMWtable, false);
+ vt_convert_data_buffer_to_hex_string(&buf[260], 1, true, s);
+ printf(" \"Firmware Slot\":\"%uh\",\n", ((ctrl->frmw >> 1) & 0x07));
+ vt_build_identify_lv2(temp, 4, 1, FRMWtable, true);
+
+ temp = ctrl->lpa;
+ printf(" \"Log Page Attributes\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[261], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 4, LPAtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[262], 1, true, s);
+ printf(" \"Error Log Page Entries\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[263], 1, true, s);
+ printf(" \"Number of Power States Support\":\"%sh\",\n", s);
+
+ temp = ctrl->avscc;
+ printf(" \"Admin Vendor Specific Command Configuration\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[264], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, AVSCCtable, true);
+
+ temp = ctrl->apsta;
+ printf(" \"Autonomous Power State Transition Attributes\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[265], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, APSTAtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[266], 2, true, s);
+ printf(" \"Warning Composite Temperature Threshold\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[268], 2, true, s);
+ printf(" \"Critical Composite Temperature Threshold\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[270], 2, true, s);
+ printf(" \"Maximum Time for Firmware Activation\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[272], 4, true, s);
+ printf(" \"Host Memory Buffer Preferred Size\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[276], 4, true, s);
+ printf(" \"Host Memory Buffer Minimum Size\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[280], 16, true, s);
+ printf(" \"Total NVM Capacity\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[296], 16, true, s);
+ printf(" \"Unallocated NVM Capacity\":\"%sh\",\n", s);
+
+ temp = le32_to_cpu(ctrl->rpmbs);
+ printf(" \"Replay Protected Memory Block Support\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[312], 4, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ printf(" \"Number of RPMB Units\":\"%u\",\n", (temp & 0x00000003));
+ snprintf(s, sizeof(s), ((temp >> 3) & 0x00000007)? "Reserved" : "HMAC SHA-256");
+ printf(" \"Authentication Method\":\"%u: %s\",\n", ((temp >> 3) & 0x00000007), s);
+ printf(" \"Total Size\":\"%u\",\n", ((temp >> 16) & 0x000000FF));
+ printf(" \"Access Size\":\"%u\",\n", ((temp >> 24) & 0x000000FF));
+ printf(" },\n");
+
+ vt_convert_data_buffer_to_hex_string(&buf[316], 2, true, s);
+ printf(" \"Extended Device Self-test Time\":\"%sh\",\n", s);
+
+ temp = ctrl->dsto;
+ printf(" \"Device Self-test Options\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[318], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, DSTOtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[319], 1, true, s);
+ printf(" \"Firmware Update Granularity\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[320], 1, true, s);
+ printf(" \"Keep Alive Support\":\"%sh\",\n", s);
+
+ temp = le16_to_cpu(ctrl->hctma);
+ printf(" \"Host Controlled Thermal Management Attributes\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[322], 2, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, HCTMAtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[324], 2, true, s);
+ printf(" \"Minimum Thermal Management Temperature\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[326], 2, true, s);
+ printf(" \"Maximum Thermal Management Temperature\":\"%sh\",\n", s);
+
+ temp = le16_to_cpu(ctrl->sanicap);
+ printf(" \"Sanitize Capabilities\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[328], 2, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 3, SANICAPtable, true);
+
+ temp = ctrl->sqes;
+ printf(" \"Submission Queue Entry Size\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[512], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ printf(" \"Maximum Size\":\"%u\",\n", (temp & 0x0000000F));
+ printf(" \"Required Size\":\"%u\",\n", ((temp >> 4) & 0x0000000F));
+ printf(" }\n");
+
+ temp = ctrl->cqes;
+ printf(" \"Completion Queue Entry Size\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[513], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ printf(" \"Maximum Size\":\"%u\",\n", (temp & 0x0000000F));
+ printf(" \"Required Size\":\"%u\",\n", ((temp >> 4) & 0x0000000F));
+ printf(" }\n");
+
+ vt_convert_data_buffer_to_hex_string(&buf[514], 2, true, s);
+ printf(" \"Maximum Outstanding Commands\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[516], 4, true, s);
+ printf(" \"Number of Namespaces\":\"%sh\",\n", s);
+
+ temp = le16_to_cpu(ctrl->oncs);
+ printf(" \"Optional NVM Command Support\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[520], 2, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 7, ONCStable, true);
+
+ temp = le16_to_cpu(ctrl->fuses);
+ printf(" \"Optional NVM Command Support\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[522], 2, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, FUSEStable, true);
+
+ temp = ctrl->fna;
+ printf(" \"Format NVM Attributes\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[524], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 3, FNAtable, true);
+
+ temp = ctrl->vwc;
+ printf(" \"Volatile Write Cache\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[525], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, VWCtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[526], 2, true, s);
+ printf(" \"Atomic Write Unit Normal\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[528], 2, true, s);
+ printf(" \"Atomic Write Unit Power Fail\":\"%sh\",\n", s);
+
+ temp = ctrl->nvscc;
+ printf(" \"VNVM Vendor Specific Command Configuration\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[530], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, NVSCCtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[532], 2, true, s);
+ printf(" \"Atomic Compare 0 Write Unit\":\"%sh\",\n", s);
+
+ temp = le32_to_cpu(ctrl->sgls);
+ printf(" \"SGL Support\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[536], 4, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ pos = (temp & 0x00000003);
+ printf(" \"bit 1:0\":\"%s\",\n", SGLSSubtable[pos]);
+ vt_build_identify_lv2(temp, 2, 1, SGLStable, false);
+ vt_build_identify_lv2(temp, 16, 5, SGLStable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[768], 256, false, s);
+ printf(" \"NVM Subsystem NVMe Qualified Name\":\"%s\",\n", s);
+ printf("}\n");
+}
+
+static int vt_save_smart_to_vtview_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ int err = 0;
+ int fd, ret;
+ long int total_time = 0;
+ long int freq_time = 0;
+ long int cur_time = 0;
+ long int remain_time = 0;
+ long int start_time = 0;
+ long int end_time = 0;
+ char path[256] = "";
+ char *desc = "Save SMART data into log file with format that is easy to analyze (comma delimited). Maximum log file will be 4K.\n\n\
+Typical usages:\n\n\
+Temperature characterization: \n\
+\tvirtium save-smart-to-vtview-log /dev/yourDevice --run-time=100 --record-frequency=0.25 --test-name=burn-in-at-(-40)\n\n\
+Endurance testing : \n\
+\tvirtium save-smart-to-vtview-log /dev/yourDevice --run-time=100 --record-frequency=1 --test-name=Endurance-test-JEDEG-219-workload\n\n\
+Just logging :\n\
+\tvirtium save-smart-to-vtview-log /dev/yourDevice";
+
+ const char *run_time = "(optional) Number of hours to log data (default = 20 hours)";
+ const char *freq = "(optional) How often you want to log SMART data (0.25 = 15' , 0.5 = 30' , 1 = 1 hour, 2 = 2 hours, etc.). Default = 10 hours.";
+ const char *output_file = "(optional) Name of the log file (give it a name that easy for you to remember what the test is). You can leave it blank too, we will take care it for you.";
+ const char *test_name = "(optional) Name of the test you are doing. We use this as part of the name of the log file.";
+
+ struct vtview_save_log_settings cfg = \
+ {
+ .run_time_hrs = 20,
+ .log_record_frequency_hrs = 0.25,
+ .output_file = NULL,
+ .test_name = NULL,
+ };
+
+ const struct argconfig_commandline_options command_line_options[] = \
+ {
+ {"run-time", 'r', "NUM", CFG_DOUBLE, &cfg.run_time_hrs, required_argument, run_time},
+ {"freq", 'f', "NUM", CFG_DOUBLE, &cfg.log_record_frequency_hrs, required_argument, freq},
+ {"output-file", 'o', "FILE", CFG_STRING, &cfg.output_file, required_argument, output_file},
+ {"test-name", 'n', "NAME", CFG_STRING, &cfg.test_name, required_argument, test_name},
+ {NULL}
+ };
+
+ vt_generate_vtview_log_file_name(vt_default_log_file_name);
+
+ fd = parse_and_open(argc, argv, desc, command_line_options, &cfg, sizeof(cfg));
+ if (fd < 0)
+ {
+ printf("Error parse and open (fd = %d)\n", fd);
+
+ return (fd);
+ }
+
+ printf("argc: %d\n", argc);
+ strcpy(path, argv[1]);
+
+ printf("Running...\n");
+ printf("Collecting data for device %s\n", path);
+ printf("Running for %lf hour(s)\n", cfg.run_time_hrs);
+ printf("Logging SMART data for every %lf hour(s)\n", cfg.log_record_frequency_hrs);
+
+ ret = vt_update_vtview_log_header(fd, path, &cfg);
+ if(ret)
+ {
+ err = EINVAL;
+ close(fd);
+ return (err);
+ }
+
+ // start get log
+ total_time = cfg.run_time_hrs * (float)HOUR_IN_SECONDS;
+ freq_time = cfg.log_record_frequency_hrs * (float)HOUR_IN_SECONDS;
+
+ if(freq_time == 0)
+ {
+ freq_time = 1;
+ }
+
+ start_time = time(0);
+ end_time = start_time + total_time;
+
+ fflush(stdout);
+
+ while(1)
+ {
+ cur_time = time(0);
+ if(cur_time >= end_time)
+ {
+ break;
+ }
+
+ // update log
+ ret = vt_add_entry_to_log(fd, path, &cfg);
+ if(ret)
+ {
+ printf("Cannot update driver log\n");
+ break;
+ }
+
+ remain_time = end_time - cur_time;
+ freq_time = MIN2(freq_time, remain_time);
+ sleep(freq_time);
+ fflush(stdout);
+ }
+
+ close (fd);
+ return (err);
+}
+
+static int vt_show_identify(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ int err = 0;
+ int fd ,ret;
+ struct nvme_id_ctrl ctrl;
+ char *desc = "Parse identify data to json format\n\n\
+Typical usages:\n\n\
+virtium show-identify /dev/yourDevice\n";
+
+ const struct argconfig_commandline_options command_line_options[] = \
+ {
+ {NULL}
+ };
+
+ fd = parse_and_open(argc, argv, desc, command_line_options, NULL, 0);
+ if (fd < 0)
+ {
+ printf("Error parse and open (fd = %d)\n", fd);
+
+ return (fd);
+ }
+
+ ret = nvme_identify_ctrl(fd, &ctrl);
+ if(ret)
+ {
+ printf("Cannot read identify device\n");
+
+ close (fd);
+ return (-1);
+ }
+
+ vt_process_string(ctrl.sn, sizeof(ctrl.sn));
+ vt_process_string(ctrl.mn, sizeof(ctrl.mn));
+ // print out detail identify
+ vt_parse_detail_identify(&ctrl);
+
+ close(fd);
+ return (err);
+}