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 | |
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')
32 files changed, 15917 insertions, 0 deletions
diff --git a/plugins/dera/dera-nvme.c b/plugins/dera/dera-nvme.c new file mode 100644 index 0000000..dbb40cc --- /dev/null +++ b/plugins/dera/dera-nvme.c @@ -0,0 +1,211 @@ +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <dirent.h> +#include <ctype.h> +#include <sys/stat.h> +#include <sys/time.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 "dera-nvme.h" + +static int char4_to_int(__u8 *data) +{ + int i; + int result = 0; + + for (i = 0; i < 4; i++) { + result = result << 8; + result += data[3 - i]; + } + return result; +} + +struct nvme_dera_smart_info_log +{ + __u8 quick_rebuild_cnt0[4]; + __u8 quick_rebuild_cnt1[4]; + __u8 full_rebuild_cnt0[4]; + __u8 full_rebuild_cnt1[4]; + __u8 raw_rebuild_cnt0[4]; + __u8 raw_rebuild_cnt1[4]; + __u8 cap_aged; + __u8 cap_aged_ratio; + __u8 cap_status; + __u8 cap_voltage[4]; + __u8 cap_charge_ctrl_en; + __u8 cap_charge_ctrl_val[2]; + __u8 cap_charge_max_thr[2]; + __u8 cap_charge_min_thr[2]; + __u8 dev_status; + __u8 dev_status_up; + __u8 nand_erase_err_cnt[4]; + __u8 nand_program_err_cnt[4]; + __u8 ddra_1bit_err[2]; + __u8 ddra_2bit_err[2]; + __u8 ddrb_1bit_err[2]; + __u8 ddrb_2bit_err[2]; + __u8 ddr_err_bit; + __u8 pcie_corr_err[2]; + __u8 pcie_uncorr_err[2]; + __u8 pcie_fatal_err[2]; + __u8 pcie_err_bit; + __u8 power_level; + __u8 current_power[2]; + __u8 nand_init_fail[2]; + __u8 fw_loader_version[8]; + __u8 uefi_driver_version[8]; + __u8 gpio0_err[2]; + __u8 gpio5_err[2]; + __u8 gpio_err_bit[2]; + __u8 rebuild_percent; + __u8 pcie_volt_status; + __u8 current_pcie_volt[2]; + __u8 init_pcie_volt_thr[2]; + __u8 rt_pcie_volt_thr[2]; + __u8 init_pcie_volt_low[2]; + __u8 rt_pcie_volt_low[2]; + __u8 temp_sensor_abnormal[2]; + __u8 nand_read_retry_fail_cnt[4]; + __u8 fw_slot_version[8]; + __u8 rsved[395]; +}; + +enum dera_device_status +{ + DEVICE_STATUS_READY = 0x00, + DEVICE_STATUS_QUICK_REBUILDING = 0x01, + DEVICE_STATUS_FULL_REBUILDING = 0x02, + DEVICE_STATUS_RAW_REBUILDING = 0x03, + DEVICE_STATUS_CARD_READ_ONLY = 0x04, + DEVICE_STATUS_FATAL_ERROR = 0x05, + DEVICE_STATUS_BUSY = 0x06, + DEVICE_STAUTS_LOW_LEVEL_FORMAT = 0x07, + DEVICE_STAUTS_FW_COMMITING = 0x08, + DEVICE_STAUTS__OVER_TEMPRATURE = 0x09, +}; + +static int nvme_dera_get_device_status(int fd, enum dera_device_status *result) +{ + int err = 0; + + struct nvme_passthru_cmd cmd = { + .opcode = 0xc0, + .addr = (__u64)(uintptr_t)NULL, + .data_len = 0, + .cdw10 = 0, + .cdw12 = 0x104, + }; + + err = nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD, &cmd); + if (!err && result) { + *result = cmd.result; + } + + return err; +} + +static int get_status(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int fd, err; + struct nvme_dera_smart_info_log log; + enum dera_device_status state = DEVICE_STATUS_FATAL_ERROR; + char *desc = "Get the Dera device status"; + + OPT_ARGS(opts) = { + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + err = nvme_get_log(fd, 0xffffffff, 0xc0, false, sizeof(log), &log); + if (err) { + goto exit; + } + + const char* dev_status[] = { + "Normal", + "Quick Rebuilding", + "Full Rebuilding", + "Raw Rebuilding", + "Card Read Only", + "Fatal Error", + "Busy", + "Low Level Format", + "Firmware Committing", + "Over Temperature" }; + + const char *volt_status[] = { + "Normal", + "Initial Low", + "Runtime Low", + }; + + err = nvme_dera_get_device_status(fd, &state); + if (!err){ + if (state > 0 && state < 4){ + printf("device_status : %s %d%% completed\n", dev_status[state], log.rebuild_percent); + } + else{ + printf("device_status : %s\n", dev_status[state]); + } + } + else { + goto exit; + } + + printf("dev_status_up : %s\n", dev_status[log.dev_status_up]); + printf("cap_aged : %s\n", log.cap_aged == 1 ? "True" : "False"); + printf("cap_aged_ratio : %d%%\n", log.cap_aged_ratio < 100 ? log.cap_aged_ratio : 100); + printf("cap_status : %s\n", log.cap_status == 0 ? "Normal" : (log.cap_status == 1 ? "Warning" : "Critical")); + printf("cap_voltage : %d mV\n", char4_to_int(log.cap_voltage)); + printf("nand_erase_err_cnt : %d\n", char4_to_int(log.nand_erase_err_cnt)); + printf("nand_program_err_cnt : %d\n", char4_to_int(log.nand_program_err_cnt)); + printf("ddra_1bit_err : %d\n", log.ddra_1bit_err[1] << 8 | log.ddra_1bit_err[0]); + printf("ddra_2bit_err : %d\n", log.ddra_2bit_err[1] << 8 | log.ddra_2bit_err[0]); + printf("ddrb_1bit_err : %d\n", log.ddrb_1bit_err[1] << 8 | log.ddrb_1bit_err[0]); + printf("ddrb_2bit_err : %d\n", log.ddrb_2bit_err[1] << 8 | log.ddrb_2bit_err[0]); + printf("ddr_err_bit : %d\n", log.ddr_err_bit); + printf("pcie_corr_err : %d\n", log.pcie_corr_err[1] << 8 | log.pcie_corr_err[0]); + printf("pcie_uncorr_err : %d\n", log.pcie_uncorr_err[1] << 8 | log.pcie_uncorr_err[0]); + printf("pcie_fatal_err : %d\n", log.pcie_fatal_err[1] << 8 | log.pcie_fatal_err[0]); + printf("power_level : %d W\n", log.power_level); + printf("current_power : %d mW\n", log.current_power[1] << 8 | log.current_power[0]); + printf("nand_init_fail : %d\n", log.nand_init_fail[1] << 8 | log.nand_init_fail[0]); + printf("fw_loader_version : %.*s\n", 8, log.fw_loader_version); + printf("uefi_driver_version : %.*s\n", 8, log.uefi_driver_version); + + if (log.pcie_volt_status >= 0 && log.pcie_volt_status <= sizeof(volt_status) / sizeof(const char *)){ + printf("pcie_volt_status : %s\n", volt_status[log.pcie_volt_status]); + } + else{ + printf("pcie_volt_status : Unknown\n"); + } + + printf("current_pcie_volt : %d mV\n", log.current_pcie_volt[1] << 8 | log.current_pcie_volt[0]); + printf("init_pcie_volt_low_cnt : %d\n", log.init_pcie_volt_low[1] << 8 | log.init_pcie_volt_low[0]); + printf("rt_pcie_volt_low_cnt : %d\n", log.rt_pcie_volt_low[1] << 8 | log.rt_pcie_volt_low[0]); + printf("temp_sensor_abnormal_cnt : %d\n", log.temp_sensor_abnormal[1] << 8 | log.temp_sensor_abnormal[0]); + printf("nand_read_retry_fail_cnt : %d\n", char4_to_int(log.nand_read_retry_fail_cnt)); + printf("fw_slot_version : %.*s\n", 8, log.fw_slot_version); + +exit: + if (err > 0) + fprintf(stderr, "\nNVMe status:%s(0x%x)\n", nvme_status_to_string(err), err); + + return err; +} + diff --git a/plugins/dera/dera-nvme.h b/plugins/dera/dera-nvme.h new file mode 100644 index 0000000..dc54fab --- /dev/null +++ b/plugins/dera/dera-nvme.h @@ -0,0 +1,17 @@ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/dera/dera-nvme + +#if !defined(DERA_NVME) || defined(CMD_HEADER_MULTI_READ) +#define DERA_NVME + +#include "cmd.h" + +PLUGIN(NAME("dera", "Dera vendor specific extensions"), + COMMAND_LIST( + ENTRY("smart-log-add", "Retrieve Dera SMART Log, show it", get_status, "stat") + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/huawei/huawei-nvme.c b/plugins/huawei/huawei-nvme.c new file mode 100644 index 0000000..482ea0d --- /dev/null +++ b/plugins/huawei/huawei-nvme.c @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2017-2019 Huawei 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. + * + * Author: Zou Ming<zouming.zouming@huawei.com>, + * Yang Feng <philip.yang@huawei.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 <dirent.h> + +#include <sys/stat.h> + +#include "linux/nvme_ioctl.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 "huawei-nvme.h" + +#define HW_SSD_PCI_VENDOR_ID 0x19E5 +#define ARRAY_NAME_LEN 80 +#define NS_NAME_LEN 40 + +#define MIN_ARRAY_NAME_LEN 16 +#define MIN_NS_NAME_LEN 16 + +struct huawei_list_item { + char node[1024]; + struct nvme_id_ctrl ctrl; + int nsid; + struct nvme_id_ns ns; + unsigned block; + char ns_name[NS_NAME_LEN]; + char array_name[ARRAY_NAME_LEN]; + bool huawei_device; +}; + +struct huawei_list_element_len { + unsigned int node; + unsigned int ns_name; + unsigned int nguid; + unsigned int ns_id; + unsigned int usage; + unsigned int array_name; +}; + +static int huawei_get_nvme_info(int fd, struct huawei_list_item *item, const char *node) +{ + int err; + int len; + struct stat nvme_stat_info; + + memset(item, 0, sizeof(*item)); + + err = nvme_identify_ctrl(fd, &item->ctrl); + if (err) + return err; + + /*identify huawei device*/ + if (strstr(item->ctrl.mn, "Huawei") == NULL && + le16_to_cpu(item->ctrl.vid) != HW_SSD_PCI_VENDOR_ID) { + item->huawei_device = false; + return 0; + } + + item->huawei_device = true; + item->nsid = nvme_get_nsid(fd); + err = nvme_identify_ns(fd, item->nsid, 0, &item->ns); + if (err) + return err; + + err = fstat(fd, &nvme_stat_info); + if (err < 0) + return err; + + strcpy(item->node, node); + item->block = S_ISBLK(nvme_stat_info.st_mode); + + if (item->ns.vs[0] == 0) { + + len = snprintf(item->ns_name, NS_NAME_LEN, "%s", "----"); + if (len < 0) + return -EINVAL; + } + else { + memcpy(item->ns_name, item->ns.vs, NS_NAME_LEN); + item->ns_name[NS_NAME_LEN - 1] = '\0'; + } + + if (item->ctrl.vs[0] == 0) { + + len = snprintf(item->array_name, ARRAY_NAME_LEN, "%s", "----"); + if (len < 0) + return -EINVAL; + } + else { + memcpy(item->array_name, item->ctrl.vs, ARRAY_NAME_LEN); + item->array_name[ARRAY_NAME_LEN - 1] = '\0'; + } + return 0; +} + +static void format(char *formatter, size_t fmt_sz, char *tofmt, size_t tofmtsz) +{ + + fmt_sz = snprintf(formatter,fmt_sz, "%-*.*s", + (int)tofmtsz, (int)tofmtsz, tofmt); + /* trim() the obnoxious trailing white lines */ + while (fmt_sz) { + if (formatter[fmt_sz - 1] != ' ' && formatter[fmt_sz - 1] != '\0') { + formatter[fmt_sz] = '\0'; + break; + } + fmt_sz--; + } +} + +static void huawei_json_print_list_items(struct huawei_list_item *list_items, + unsigned len) +{ + struct json_object *root; + struct json_array *devices; + struct json_object *device_attrs; + char formatter[128] = { 0 }; + int index, i = 0; + + root = json_create_object(); + devices = json_create_array(); + for (i = 0; i < len; i++) { + device_attrs = json_create_object(); + + json_object_add_value_string(device_attrs, + "DevicePath", + list_items[i].node); + + if (sscanf(list_items[i].node, "/dev/nvme%d", &index) == 1) + json_object_add_value_int(device_attrs, + "Index", + index); + + format(formatter, sizeof(formatter), + list_items[i].ns_name, + sizeof(list_items[i].ns_name)); + + json_object_add_value_string(device_attrs, + "NS Name", + formatter); + + format(formatter, sizeof(formatter), + list_items[i].array_name, + sizeof(list_items[i].array_name)); + + json_object_add_value_string(device_attrs, + "Array Name", + formatter); + + json_array_add_value_object(devices, device_attrs); + } + json_object_add_value_array(root, "Devices", devices); + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static void huawei_print_list_head(struct huawei_list_element_len element_len) +{ + char dash[128]; + int i; + + for (i = 0; i < 128; i++) + dash[i] = '-'; + dash[127] = '\0'; + + printf("%-*.*s %-*.*s %-*.*s %-*.*s %-*.*s %-*.*s\n", + element_len.node, element_len.node, "Node", + element_len.ns_name, element_len.ns_name, "NS Name", + element_len.nguid, element_len.nguid, "Nguid", + element_len.ns_id, element_len.ns_id, "NS ID", + element_len.usage, element_len.usage, "Usage", + element_len.array_name, element_len.array_name, "Array Name"); + + printf("%-.*s %-.*s %-.*s %-.*s %-.*s %-.*s\n", + element_len.node, dash, element_len.ns_name, dash, + element_len.nguid, dash, element_len.ns_id, dash, + element_len.usage, dash, element_len.array_name, dash); +} + +static void huawei_print_list_item(struct huawei_list_item list_item, + struct huawei_list_element_len element_len) +{ + long long int lba = 1 << list_item.ns.lbaf[(list_item.ns.flbas & 0x0f)].ds; + double nsze = le64_to_cpu(list_item.ns.nsze) * lba; + double nuse = le64_to_cpu(list_item.ns.nuse) * lba; + + const char *s_suffix = suffix_si_get(&nsze); + const char *u_suffix = suffix_si_get(&nuse); + + char usage[128]; + char nguid_buf[2 * sizeof(list_item.ns.nguid) + 1]; + char *nguid = nguid_buf; + int i; + + sprintf(usage,"%6.2f %2sB / %6.2f %2sB", nuse, u_suffix, + nsze, s_suffix); + + memset(nguid, 0, sizeof(nguid_buf)); + for (i = 0; i < sizeof(list_item.ns.nguid); i++) + nguid += sprintf(nguid, "%02x", list_item.ns.nguid[i]); + + printf("%-*.*s %-*.*s %-*.*s %-*d %-*.*s %-*.*s\n", + element_len.node, element_len.node, list_item.node, + element_len.ns_name, element_len.ns_name, list_item.ns_name, + element_len.nguid, element_len.nguid, nguid_buf, + element_len.ns_id, list_item.nsid, + element_len.usage, element_len.usage, usage, + element_len.array_name, element_len.array_name, list_item.array_name); + +} + +static unsigned int choose_len(unsigned int old_len, unsigned int cur_len, unsigned int default_len) +{ + unsigned int temp_len; + + temp_len = (cur_len > default_len) ? cur_len : default_len; + if (temp_len > old_len) + { + return temp_len; + } + return old_len; +} + +static unsigned int huawei_get_ns_len(struct huawei_list_item *list_items, unsigned len, unsigned default_len) +{ + int i; + unsigned int min_len = default_len; + + for (i = 0 ; i < len ; i++) + min_len = choose_len(min_len , strlen(list_items->ns_name), default_len); + + return min_len; +} + +static int huawei_get_array_len(struct huawei_list_item *list_items, unsigned len, unsigned default_len) +{ + int i; + int min_len = default_len; + + for (i = 0 ; i < len ; i++) + min_len = choose_len(min_len , strlen(list_items->array_name), default_len); + + return min_len; +} + +static void huawei_print_list_items(struct huawei_list_item *list_items, unsigned len) +{ + unsigned i; + struct huawei_list_element_len element_len; + + element_len.node = 16; + element_len.nguid = 2 * sizeof(list_items->ns.nguid) + 1; + element_len.ns_id = 9; + element_len.usage = 26; + element_len.ns_name = huawei_get_ns_len(list_items, len, MIN_NS_NAME_LEN); + element_len.array_name = huawei_get_array_len(list_items, len, MIN_ARRAY_NAME_LEN); + + huawei_print_list_head(element_len); + + for (i = 0 ; i < len ; i++) + huawei_print_list_item(list_items[i], element_len); +} + +static int huawei_list(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char path[264]; + struct dirent **devices; + struct huawei_list_item *list_items; + unsigned int i, n, fd, ret; + unsigned int huawei_num = 0; + int fmt; + const char *desc = "Retrieve basic information for the given huawei device"; + 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() + }; + + argconfig_parse(argc, argv, desc, opts); + fmt = validate_output_format(cfg.output_format); + if (fmt != JSON && fmt != NORMAL) + return -EINVAL; + + n = scandir("/dev", &devices, scan_namespace_filter, alphasort); + if (n <= 0) + return n; + + list_items = calloc(n, sizeof(*list_items)); + if (!list_items) { + fprintf(stderr, "can not allocate controller list payload\n"); + return ENOMEM; + } + + for (i = 0; i < n; i++) { + snprintf(path, sizeof(path), "/dev/%s", devices[i]->d_name); + fd = open(path, O_RDONLY); + ret = huawei_get_nvme_info(fd, &list_items[huawei_num], path); + if (ret) + return ret; + if (list_items[huawei_num].huawei_device == true) { + huawei_num++; + } + } + + if (huawei_num > 0){ + if (fmt == JSON) + huawei_json_print_list_items(list_items, huawei_num); + else + huawei_print_list_items(list_items, huawei_num); + } + + for (i = 0; i < n; i++) + free(devices[i]); + free(devices); + free(list_items); + + return 0; +} + +static void huawei_do_id_ctrl(__u8 *vs, struct json_object *root) +{ + char array_name[ARRAY_NAME_LEN + 1] = {0}; + + memcpy(array_name, vs, ARRAY_NAME_LEN); + if (root) + json_object_add_value_string(root, "array name", strlen(array_name) > 1 ? array_name : "NULL"); + else + printf("array name : %s\n", strlen(array_name) > 1 ? array_name : "NULL"); +} + +static int huawei_id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return __id_ctrl(argc, argv, cmd, plugin, huawei_do_id_ctrl); +} diff --git a/plugins/huawei/huawei-nvme.h b/plugins/huawei/huawei-nvme.h new file mode 100644 index 0000000..7aac90c --- /dev/null +++ b/plugins/huawei/huawei-nvme.h @@ -0,0 +1,18 @@ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/huawei/huawei-nvme + +#if !defined(HUAWEI_NVME) || defined(CMD_HEADER_MULTI_READ) +#define HUAWEI_NVME + +#include "cmd.h" + +PLUGIN(NAME("huawei", "Huawei vendor specific extensions"), + COMMAND_LIST( + ENTRY("list", "List all Huawei NVMe devices and namespaces on machine", huawei_list) + ENTRY("id-ctrl", "Huawei identify controller", huawei_id_ctrl) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/intel/intel-nvme.c b/plugins/intel/intel-nvme.c new file mode 100644 index 0000000..8217c46 --- /dev/null +++ b/plugins/intel/intel-nvme.c @@ -0,0 +1,1316 @@ +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> + +#include "linux/nvme_ioctl.h" + +#include "common.h" +#include "nvme.h" +#include "nvme-print.h" +#include "nvme-ioctl.h" +#include "json.h" +#include "plugin.h" + +#include "argconfig.h" +#include "suffix.h" + +#define CREATE_CMD +#include "intel-nvme.h" + +struct __attribute__((packed)) nvme_additional_smart_log_item { + __u8 key; + __u8 _kp[2]; + __u8 norm; + __u8 _np; + union __attribute__((packed)) { + __u8 raw[6]; + struct __attribute__((packed)) wear_level { + __le16 min; + __le16 max; + __le16 avg; + } wear_level; + struct __attribute__((packed)) thermal_throttle { + __u8 pct; + __u32 count; + } thermal_throttle; + } ; + __u8 _rp; +} ; + +struct nvme_additional_smart_log { + struct nvme_additional_smart_log_item program_fail_cnt; + struct nvme_additional_smart_log_item erase_fail_cnt; + struct nvme_additional_smart_log_item wear_leveling_cnt; + struct nvme_additional_smart_log_item e2e_err_cnt; + struct nvme_additional_smart_log_item crc_err_cnt; + struct nvme_additional_smart_log_item timed_workload_media_wear; + struct nvme_additional_smart_log_item timed_workload_host_reads; + struct nvme_additional_smart_log_item timed_workload_timer; + struct nvme_additional_smart_log_item thermal_throttle_status; + struct nvme_additional_smart_log_item retry_buffer_overflow_cnt; + struct nvme_additional_smart_log_item pll_lock_loss_cnt; + struct nvme_additional_smart_log_item nand_bytes_written; + struct nvme_additional_smart_log_item host_bytes_written; +}; + +struct nvme_vu_id_ctrl_field { /* CDR MR5 */ + __u8 rsvd1[3]; + __u8 ss; + __u8 health[20]; + __u8 cls; + __u8 nlw; + __u8 scap; + __u8 sstat; + __u8 bl[8]; + __u8 rsvd2[38]; + __u8 ww[8]; /* little endian */ + __u8 mic_bl[4]; + __u8 mic_fw[4]; +}; + +static void json_intel_id_ctrl(struct nvme_vu_id_ctrl_field *id, + char *health, char *bl, char *ww, char *mic_bl, char *mic_fw, + struct json_object *root) +{ + json_object_add_value_int(root, "ss", id->ss); + json_object_add_value_string(root, "health", health ); + json_object_add_value_int(root, "cls", id->cls); + json_object_add_value_int(root, "nlw", id->nlw); + json_object_add_value_int(root, "scap", id->scap); + json_object_add_value_int(root, "sstat", id->sstat); + json_object_add_value_string(root, "bl", bl); + json_object_add_value_string(root, "ww", ww); + json_object_add_value_string(root, "mic_bl", mic_bl); + json_object_add_value_string(root, "mic_fw", mic_fw); +} + +static void intel_id_ctrl(__u8 *vs, struct json_object *root) +{ + struct nvme_vu_id_ctrl_field* id = (struct nvme_vu_id_ctrl_field *)vs; + + char health[21] = { 0 }; + char bl[9] = { 0 }; + char ww[19] = { 0 }; + char mic_bl[5] = { 0 }; + char mic_fw[5] = { 0 }; + + + if (id->health[0]==0) + { + snprintf(health, 21, "%s", "healthy"); + } + else + { + snprintf(health, 21, "%s", id->health); + } + + snprintf(bl, 9, "%s", id->bl); + snprintf(ww, 19, "%02X%02X%02X%02X%02X%02X%02X%02X", id->ww[7], + id->ww[6], id->ww[5], id->ww[4], id->ww[3], id->ww[2], + id->ww[1], id->ww[0]); + snprintf(mic_bl, 5, "%s", id->mic_bl); + snprintf(mic_fw, 5, "%s", id->mic_fw); + + if (root) { + json_intel_id_ctrl(id, health, bl, ww, mic_bl, mic_fw, root); + return; + } + + printf("ss : %d\n", id->ss); + printf("health : %s\n", health); + printf("cls : %d\n", id->cls); + printf("nlw : %d\n", id->nlw); + printf("scap : %d\n", id->scap); + printf("sstat : %d\n", id->sstat); + printf("bl : %s\n", bl); + printf("ww : %s\n", ww); + printf("mic_bl : %s\n", mic_bl); + printf("mic_fw : %s\n", mic_fw); +} + +static int id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return __id_ctrl(argc, argv, cmd, plugin, intel_id_ctrl); +} + +static void show_intel_smart_log_jsn(struct nvme_additional_smart_log *smart, + unsigned int nsid, const char *devname) +{ + struct json_object *root, *entry_stats, *dev_stats, *multi; + + root = json_create_object(); + json_object_add_value_string(root, "Intel Smart log", devname); + + dev_stats = json_create_object(); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->program_fail_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->program_fail_cnt.raw)); + json_object_add_value_object(dev_stats, "program_fail_count", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->erase_fail_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->erase_fail_cnt.raw)); + json_object_add_value_object(dev_stats, "erase_fail_count", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->wear_leveling_cnt.norm); + multi = json_create_object(); + json_object_add_value_int(multi, "min", le16_to_cpu(smart->wear_leveling_cnt.wear_level.min)); + json_object_add_value_int(multi, "max", le16_to_cpu(smart->wear_leveling_cnt.wear_level.max)); + json_object_add_value_int(multi, "avg", le16_to_cpu(smart->wear_leveling_cnt.wear_level.avg)); + json_object_add_value_object(entry_stats, "raw", multi); + json_object_add_value_object(dev_stats, "wear_leveling", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->e2e_err_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->e2e_err_cnt.raw)); + json_object_add_value_object(dev_stats, "end_to_end_error_detection_count", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->crc_err_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->crc_err_cnt.raw)); + json_object_add_value_object(dev_stats, "crc_error_count", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->timed_workload_media_wear.norm); + json_object_add_value_float(entry_stats, "raw", ((long double)int48_to_long(smart->timed_workload_media_wear.raw)) / 1024); + json_object_add_value_object(dev_stats, "timed_workload_media_wear", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->timed_workload_host_reads.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->timed_workload_host_reads.raw)); + json_object_add_value_object(dev_stats, "timed_workload_host_reads", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->timed_workload_timer.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->timed_workload_timer.raw)); + json_object_add_value_object(dev_stats, "timed_workload_timer", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->thermal_throttle_status.norm); + multi = json_create_object(); + json_object_add_value_int(multi, "pct", smart->thermal_throttle_status.thermal_throttle.pct); + json_object_add_value_int(multi, "cnt", smart->thermal_throttle_status.thermal_throttle.count); + json_object_add_value_object(entry_stats, "raw", multi); + json_object_add_value_object(dev_stats, "thermal_throttle_status", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->retry_buffer_overflow_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->retry_buffer_overflow_cnt.raw)); + json_object_add_value_object(dev_stats, "retry_buffer_overflow_count", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->pll_lock_loss_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->pll_lock_loss_cnt.raw)); + json_object_add_value_object(dev_stats, "pll_lock_loss_count", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->nand_bytes_written.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->nand_bytes_written.raw)); + json_object_add_value_object(dev_stats, "nand_bytes_written", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->host_bytes_written.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->host_bytes_written.raw)); + json_object_add_value_object(dev_stats, "host_bytes_written", entry_stats); + + json_object_add_value_object(root, "Device stats", dev_stats); + + json_print_object(root, NULL); + json_free_object(root); +} + +static void show_intel_smart_log(struct nvme_additional_smart_log *smart, + unsigned int nsid, const char *devname) +{ + printf("Additional Smart Log for NVME device:%s namespace-id:%x\n", + devname, nsid); + printf("key normalized raw\n"); + printf("program_fail_count : %3d%% %"PRIu64"\n", + smart->program_fail_cnt.norm, + int48_to_long(smart->program_fail_cnt.raw)); + printf("erase_fail_count : %3d%% %"PRIu64"\n", + smart->erase_fail_cnt.norm, + int48_to_long(smart->erase_fail_cnt.raw)); + printf("wear_leveling : %3d%% min: %u, max: %u, avg: %u\n", + smart->wear_leveling_cnt.norm, + le16_to_cpu(smart->wear_leveling_cnt.wear_level.min), + le16_to_cpu(smart->wear_leveling_cnt.wear_level.max), + le16_to_cpu(smart->wear_leveling_cnt.wear_level.avg)); + printf("end_to_end_error_detection_count: %3d%% %"PRIu64"\n", + smart->e2e_err_cnt.norm, + int48_to_long(smart->e2e_err_cnt.raw)); + printf("crc_error_count : %3d%% %"PRIu64"\n", + smart->crc_err_cnt.norm, + int48_to_long(smart->crc_err_cnt.raw)); + printf("timed_workload_media_wear : %3d%% %.3f%%\n", + smart->timed_workload_media_wear.norm, + ((float)int48_to_long(smart->timed_workload_media_wear.raw)) / 1024); + printf("timed_workload_host_reads : %3d%% %"PRIu64"%%\n", + smart->timed_workload_host_reads.norm, + int48_to_long(smart->timed_workload_host_reads.raw)); + printf("timed_workload_timer : %3d%% %"PRIu64" min\n", + smart->timed_workload_timer.norm, + int48_to_long(smart->timed_workload_timer.raw)); + printf("thermal_throttle_status : %3d%% %u%%, cnt: %u\n", + smart->thermal_throttle_status.norm, + smart->thermal_throttle_status.thermal_throttle.pct, + smart->thermal_throttle_status.thermal_throttle.count); + printf("retry_buffer_overflow_count : %3d%% %"PRIu64"\n", + smart->retry_buffer_overflow_cnt.norm, + int48_to_long(smart->retry_buffer_overflow_cnt.raw)); + printf("pll_lock_loss_count : %3d%% %"PRIu64"\n", + smart->pll_lock_loss_cnt.norm, + int48_to_long(smart->pll_lock_loss_cnt.raw)); + printf("nand_bytes_written : %3d%% sectors: %"PRIu64"\n", + smart->nand_bytes_written.norm, + int48_to_long(smart->nand_bytes_written.raw)); + printf("host_bytes_written : %3d%% sectors: %"PRIu64"\n", + smart->host_bytes_written.norm, + int48_to_long(smart->host_bytes_written.raw)); +} + +static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Get Intel vendor specific additional smart log (optionally, "\ + "for the specified namespace), and show it."; + const char *namespace = "(optional) desired namespace"; + const char *raw = "Dump output in binary format"; + const char *json= "Dump output in json format"; + + struct nvme_additional_smart_log smart_log; + int err, fd; + + struct config { + __u32 namespace_id; + int raw_binary; + int json; + }; + + struct config cfg = { + .namespace_id = NVME_NSID_ALL, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_FLAG("json", 'j', &cfg.json, json), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + err = nvme_get_log(fd, cfg.namespace_id, 0xca, false, + sizeof(smart_log), &smart_log); + if (!err) { + if (cfg.json) + show_intel_smart_log_jsn(&smart_log, cfg.namespace_id, devicename); + else if (!cfg.raw_binary) + show_intel_smart_log(&smart_log, cfg.namespace_id, devicename); + else + d_raw((unsigned char *)&smart_log, sizeof(smart_log)); + } + else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + return err; +} + +static int get_market_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Get Intel Marketing Name log and show it."; + const char *raw = "dump output in binary format"; + + char log[512]; + int err, fd; + + struct config { + int raw_binary; + }; + + struct config cfg = { + }; + + OPT_ARGS(opts) = { + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + err = nvme_get_log(fd, NVME_NSID_ALL, 0xdd, false, + sizeof(log), log); + if (!err) { + if (!cfg.raw_binary) + printf("Intel Marketing Name Log:\n%s\n", log); + else + d_raw((unsigned char *)&log, sizeof(log)); + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + return err; +} + +struct intel_temp_stats { + __le64 curr; + __le64 last_overtemp; + __le64 life_overtemp; + __le64 highest_temp; + __le64 lowest_temp; + __u8 rsvd[40]; + __le64 max_operating_temp; + __le64 min_operating_temp; + __le64 est_offset; +}; + +static void show_temp_stats(struct intel_temp_stats *stats) +{ + printf(" Intel Temperature Statistics\n"); + printf("--------------------------------\n"); + printf("Current temperature : %"PRIu64"\n", le64_to_cpu(stats->curr)); + printf("Last critical overtemp flag : %"PRIu64"\n", le64_to_cpu(stats->last_overtemp)); + printf("Life critical overtemp flag : %"PRIu64"\n", le64_to_cpu(stats->life_overtemp)); + printf("Highest temperature : %"PRIu64"\n", le64_to_cpu(stats->highest_temp)); + printf("Lowest temperature : %"PRIu64"\n", le64_to_cpu(stats->lowest_temp)); + printf("Max operating temperature : %"PRIu64"\n", le64_to_cpu(stats->max_operating_temp)); + printf("Min operating temperature : %"PRIu64"\n", le64_to_cpu(stats->min_operating_temp)); + printf("Estimated offset : %"PRIu64"\n", le64_to_cpu(stats->est_offset)); +} + +static int get_temp_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct intel_temp_stats stats; + int err, fd; + + const char *desc = "Get Intel Marketing Name log and show it."; + const char *raw = "dump output in binary format"; + struct config { + int raw_binary; + }; + + struct config cfg = { + }; + + OPT_ARGS(opts) = { + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + err = nvme_get_log(fd, NVME_NSID_ALL, 0xc5, false, + sizeof(stats), &stats); + if (!err) { + if (!cfg.raw_binary) + show_temp_stats(&stats); + else + d_raw((unsigned char *)&stats, sizeof(stats)); + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + return err; +} + +struct intel_lat_stats { + __u16 maj; + __u16 min; + __u32 data[1216]; +}; + +enum FormatUnit { + US, + MS, + S +}; + +/* + * COL_WIDTH controls width of columns in human-readable output. + * BUFSIZE is for local temp char[] + * US_IN_S and US_IN_MS are for unit conversions when printing. + */ +#define COL_WIDTH 12 +#define BUFSIZE 10 +#define US_IN_S 1000000 +#define US_IN_MS 1000 + +static const enum FormatUnit get_seconds_magnitude(__u32 microseconds) +{ + if (microseconds > US_IN_S) + return S; + else if (microseconds > US_IN_MS) + return MS; + else + return US; +} + +static const float convert_seconds(__u32 microseconds) +{ + float divisor = 1.0; + + if (microseconds > US_IN_S) + divisor = US_IN_S; + else if (microseconds > US_IN_MS) + divisor = US_IN_MS; + return microseconds / divisor; +} + +/* + * For control over whether a string will format to +/-INF or + * print out ####.##US normally. + */ +enum inf_bound_type { + NEGINF, + POSINF, + NOINF +}; + +/* + * Edge buckets may have range [#s, inf) or (-inf, #US] in some + * latency statistics formats. + * Passing in NEGINF to POSINF to bound_type overrides the string to + * either of "-INF" or "+INF", respectively. + */ +static void set_unit_string(char *buffer, __u32 microseconds, + enum FormatUnit unit, enum inf_bound_type bound_type) +{ + if (bound_type != NOINF) { + snprintf(buffer, 5, "%s", bound_type ? "+INF" : "-INF"); + return; + } + char *string; + + switch (unit) { + case US: + string = "us"; + break; + case MS: + string = "ms"; + break; + case S: + string = "s"; + break; + default: + string = "_s"; + break; + } + snprintf(buffer, 11, "%4.2f%s", + convert_seconds(microseconds), string); +} + +static void init_buffer(char *buffer, size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) + buffer[i] = i + '0'; +} + +static void show_lat_stats_bucket(struct intel_lat_stats *stats, + __u32 lower_us, enum inf_bound_type start_type, + __u32 upper_us, enum inf_bound_type end_type, int i) +{ + enum FormatUnit fu = S; + char buffer[BUFSIZE]; + + init_buffer(buffer, BUFSIZE); + printf("%-*d", COL_WIDTH, i); + + fu = get_seconds_magnitude(lower_us); + set_unit_string(buffer, lower_us, fu, start_type); + printf("%-*s", COL_WIDTH, buffer); + + fu = get_seconds_magnitude(upper_us); + set_unit_string(buffer, upper_us, fu, end_type); + printf("%-*s", COL_WIDTH, buffer); + + printf("%-*d\n", COL_WIDTH, stats->data[i]); +} + +static void show_lat_stats_linear(struct intel_lat_stats *stats, + __u32 start_offset, __u32 end_offset, __u32 bytes_per, + __u32 us_step, bool nonzero_print) +{ + for (int i = (start_offset / bytes_per) - 1; + i < end_offset / bytes_per; i++) { + if (nonzero_print && stats->data[i] == 0) + continue; + show_lat_stats_bucket(stats, us_step * i, NOINF, + us_step * (i + 1), NOINF, i); + } +} + +/* + * For 4.0-4.5 revision. + */ +static int lat_stats_log_scale(int i) +{ + static const int LATENCY_STATS_V4_BASE_BITS = 6; + static const int LATENCY_STATS_V4_BASE_VAL = ( + 1 << LATENCY_STATS_V4_BASE_BITS); + + // if (i < 128) + if (i < (LATENCY_STATS_V4_BASE_VAL << 1)) + return i; + + int error_bits = (i >> LATENCY_STATS_V4_BASE_BITS) - 1; + int base = 1 << (error_bits + LATENCY_STATS_V4_BASE_BITS); + int k = i % LATENCY_STATS_V4_BASE_VAL; + + return base + ((k + 0.5) * (1 << error_bits)); +} + +/* + * Creates a subroot in the following manner: + * { + * "latstats" : { + * "type" : "write" or "read", + * "values" : { + */ +static void lat_stats_make_json_root( + struct json_object *root, struct json_object *bucket_list, + int write) +{ + struct json_object *subroot = json_create_object(); + + json_object_add_value_object(root, "latstats", subroot); + json_object_add_value_string(subroot, "type", write ? "write" : "read"); + json_object_add_value_object(subroot, "values", bucket_list); +} + +/* + * Creates a bucket under the "values" json_object. Format is: + * "values" : { + * "bucket" : { + * "id" : #, + * "start" : string, + * "end" : string, + * "value" : 0, + * }, + */ +static void json_add_bucket(struct intel_lat_stats *stats, + struct json_object *bucket_list, __u32 id, + __u32 lower_us, enum inf_bound_type start_type, + __u32 upper_us, enum inf_bound_type end_type, __u32 val) +{ + char buffer[BUFSIZE]; + struct json_object *bucket = json_create_object(); + + init_buffer(buffer, BUFSIZE); + + json_object_add_value_object(bucket_list, + "bucket", bucket); + json_object_add_value_int(bucket, "id", id); + + set_unit_string(buffer, lower_us, + get_seconds_magnitude(lower_us), start_type); + json_object_add_value_string(bucket, "start", buffer); + + set_unit_string(buffer, upper_us, + get_seconds_magnitude(upper_us), end_type); + json_object_add_value_string(bucket, "end", buffer); + + json_object_add_value_int(bucket, "value", val); +} + +static void json_lat_stats_linear(struct intel_lat_stats *stats, + struct json_object *bucket_list, __u32 start_offset, + __u32 end_offset, __u32 bytes_per, + __u32 us_step, bool nonzero_print) +{ + for (int i = (start_offset / bytes_per) - 1; + i < end_offset / bytes_per; i++) { + if (nonzero_print && stats->data[i] == 0) + continue; + + json_add_bucket(stats, bucket_list, + i, us_step * i, NOINF, us_step * (i + 1), + NOINF, stats->data[i]); + } +} + +static void json_lat_stats_3_0(struct intel_lat_stats *stats, + int write) +{ + struct json_object *root = json_create_object(); + struct json_object *bucket_list = json_create_object(); + + lat_stats_make_json_root(root, bucket_list, write); + + json_lat_stats_linear(stats, bucket_list, 4, 131, 4, 32, false); + json_lat_stats_linear(stats, bucket_list, 132, 255, 4, 1024, false); + json_lat_stats_linear(stats, bucket_list, 256, 379, 4, 32768, false); + json_lat_stats_linear(stats, bucket_list, 380, 383, 4, 32, true); + json_lat_stats_linear(stats, bucket_list, 384, 387, 4, 32, true); + json_lat_stats_linear(stats, bucket_list, 388, 391, 4, 32, true); + + json_print_object(root, NULL); + json_free_object(root); +} + +static void json_lat_stats_4_0(struct intel_lat_stats *stats, + int write) +{ + struct json_object *root = json_create_object(); + struct json_object *bucket_list = json_create_object(); + + lat_stats_make_json_root(root, bucket_list, write); + + __u32 lower_us = 0; + __u32 upper_us = 1; + bool end = false; + int max = 1216; + + for (int i = 0; i < max; i++) { + lower_us = lat_stats_log_scale(i); + if (i >= max - 1) + end = true; + else + upper_us = lat_stats_log_scale(i + 1); + + json_add_bucket(stats, bucket_list, i, + lower_us, NOINF, upper_us, + end ? POSINF : NOINF, stats->data[i]); + } + json_print_object(root, NULL); + json_free_object(root); +} + +static void show_lat_stats_3_0(struct intel_lat_stats *stats) +{ + show_lat_stats_linear(stats, 4, 131, 4, 32, false); + show_lat_stats_linear(stats, 132, 255, 4, 1024, false); + show_lat_stats_linear(stats, 256, 379, 4, 32768, false); + show_lat_stats_linear(stats, 380, 383, 4, 32, true); + show_lat_stats_linear(stats, 384, 387, 4, 32, true); + show_lat_stats_linear(stats, 388, 391, 4, 32, true); +} + +static void show_lat_stats_4_0(struct intel_lat_stats *stats) +{ + int lower_us = 0; + int upper_us = 1; + bool end = false; + int max = 1216; + + for (int i = 0; i < max; i++) { + lower_us = lat_stats_log_scale(i); + if (i >= max - 1) + end = true; + else + upper_us = lat_stats_log_scale(i + 1); + + show_lat_stats_bucket(stats, lower_us, NOINF, + upper_us, end ? POSINF : NOINF, i); + } +} + +static void json_lat_stats(struct intel_lat_stats *stats, int write) +{ + switch (stats->maj) { + case 3: + json_lat_stats_3_0(stats, write); + break; + case 4: + switch (stats->min) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + json_lat_stats_4_0(stats, write); + break; + default: + printf(("Unsupported minor revision (%u.%u)\n" + "Defaulting to format for rev4.0"), + stats->maj, stats->min); + break; + } + break; + default: + printf("Unsupported revision (%u.%u)\n", + stats->maj, stats->min); + break; + } + printf("\n"); +} + +static void print_dash_separator(int count) +{ + for (int i = 0; i < count; i++) + putchar('-'); + putchar('\n'); +} + +static void show_lat_stats(struct intel_lat_stats *stats, int write) +{ + static const int separator_length = 50; + + printf("Intel IO %s Command Latency Statistics\n", + write ? "Write" : "Read"); + printf("Major Revision : %u\nMinor Revision : %u\n", + stats->maj, stats->min); + print_dash_separator(separator_length); + printf("%-12s%-12s%-12s%-20s\n", "Bucket", "Start", "End", "Value"); + print_dash_separator(separator_length); + + switch (stats->maj) { + case 3: + show_lat_stats_3_0(stats); + break; + case 4: + switch (stats->min) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + show_lat_stats_4_0(stats); + break; + default: + printf(("Unsupported minor revision (%u.%u)\n" + "Defaulting to format for rev4.0"), + stats->maj, stats->min); + break; + } + break; + default: + printf("Unsupported revision (%u.%u)\n", + stats->maj, stats->min); + break; + } +} + +static int get_lat_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct intel_lat_stats stats; + enum nvme_print_flags flags; + int err, fd; + + const char *desc = "Get Intel Latency Statistics log and show it."; + const char *raw = "dump output in binary format"; + const char *write = "Get write statistics (read default)"; + struct config { + char *output_format; + int raw_binary; + int write; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FLAG("write", 'w', &cfg.write, write), + OPT_FMT("output-format", 'o', &cfg.output_format, "Output format: normal|json|binary"), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + err = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + + if (cfg.raw_binary) + flags = BINARY; + + err = nvme_get_log(fd, NVME_NSID_ALL, cfg.write ? 0xc2 : 0xc1, + false, sizeof(stats), &stats); + if (!err) { + if (flags & JSON) + json_lat_stats(&stats, cfg.write); + else if (flags & BINARY) + d_raw((unsigned char *)&stats, sizeof(stats)); + else + show_lat_stats(&stats, cfg.write); + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + +close_fd: + close(fd); + return err; +} + +struct intel_assert_dump { + __u32 coreoffset; + __u32 assertsize; + __u8 assertdumptype; + __u8 assertvalid; + __u8 reserved[2]; +}; + +struct intel_event_dump { + __u32 numeventdumps; + __u32 coresize; + __u32 coreoffset; + __u32 eventidoffset[16]; + __u8 eventIdValidity[16]; +}; + +struct intel_vu_version { + __u16 major; + __u16 minor; +}; + +struct intel_event_header { + __u32 eventidsize; + struct intel_event_dump edumps[0]; +}; + +struct intel_vu_log { + struct intel_vu_version ver; + __u32 header; + __u32 size; + __u32 numcores; + __u8 reserved[4080]; +}; + +struct intel_vu_nlog { + struct intel_vu_version ver; + __u32 logselect; + __u32 totalnlogs; + __u32 nlognum; + __u32 nlogname; + __u32 nlogbytesize; + __u32 nlogprimarybuffsize; + __u32 tickspersecond; + __u32 corecount; + __u32 nlogpausestatus; + __u32 selectoffsetref; + __u32 selectnlogpause; + __u32 selectaddedoffset; + __u32 nlogbufnum; + __u32 nlogbufnummax; + __u32 coreselected; + __u32 reserved[3]; +}; + +struct intel_cd_log { + union { + struct { + __u32 selectLog : 3; + __u32 selectCore : 2; + __u32 selectNlog : 8; + __u8 selectOffsetRef : 1; + __u32 selectNlogPause : 2; + __u32 reserved2 : 16; + } fields; + __u32 entireDword; + } u; +}; + +static void print_intel_nlog(struct intel_vu_nlog *intel_nlog) +{ + printf("Version Major %u\n" + "Version Minor %u\n" + "Log_select %u\n" + "totalnlogs %u\n" + "nlognum %u\n" + "nlogname %u\n" + "nlogbytesze %u\n" + "nlogprimarybuffsize %u\n" + "tickspersecond %u\n" + "corecount %u\n" + "nlogpausestatus %u\n" + "selectoffsetref %u\n" + "selectnlogpause %u\n" + "selectaddedoffset %u\n" + "nlogbufnum %u\n" + "nlogbufnummax %u\n" + "coreselected %u\n", + intel_nlog->ver.major, intel_nlog->ver.minor, + intel_nlog->logselect, intel_nlog->totalnlogs, intel_nlog->nlognum, + intel_nlog->nlogname, intel_nlog->nlogbytesize, + intel_nlog->nlogprimarybuffsize, intel_nlog->tickspersecond, + intel_nlog->corecount, intel_nlog->nlogpausestatus, + intel_nlog->selectoffsetref, intel_nlog->selectnlogpause, + intel_nlog->selectaddedoffset, intel_nlog->nlogbufnum, + intel_nlog->nlogbufnummax, intel_nlog->coreselected); +} + +static int read_entire_cmd(struct nvme_passthru_cmd *cmd, int total_size, + const size_t max_tfer, int out_fd, int ioctl_fd, + __u8 *buf) +{ + int err = 0; + size_t dword_tfer = 0; + + dword_tfer = min(max_tfer, total_size); + while (total_size > 0) { + err = nvme_submit_admin_passthru(ioctl_fd, cmd); + if (err) { + fprintf(stderr, + "failed on cmd.data_len %u cmd.cdw13 %u cmd.cdw12 %x cmd.cdw10 %u err %x remaining size %d\n", + cmd->data_len, cmd->cdw13, cmd->cdw12, + cmd->cdw10, err, total_size); + goto out; + } + + if (out_fd > 0) { + err = write(out_fd, buf, cmd->data_len); + if (err < 0) { + perror("write failure"); + goto out; + } + err = 0; + } + total_size -= dword_tfer; + cmd->cdw13 += dword_tfer; + cmd->cdw10 = dword_tfer = min(max_tfer, total_size); + cmd->data_len = (min(max_tfer, total_size)) * 4; + } + + out: + return err; +} + +static int write_header(__u8 *buf, int fd, size_t amnt) +{ + if (write(fd, buf, amnt) < 0) + return 1; + return 0; +} + +static int read_header(struct nvme_passthru_cmd *cmd,__u8 *buf, int ioctl_fd, + __u32 dw12, int nsid) +{ + memset(cmd, 0, sizeof(*cmd)); + memset(buf, 0, 4096); + cmd->opcode = 0xd2; + cmd->nsid = nsid; + cmd->cdw10 = 0x400; + cmd->cdw12 = dw12; + cmd->data_len = 0x1000; + cmd->addr = (unsigned long)(void *)buf; + return read_entire_cmd(cmd, 0x400, 0x400, -1, ioctl_fd, buf); +} + +static int setup_file(char *f, char *file, int fd, int type) +{ + struct nvme_id_ctrl ctrl; + int err = 0, i = sizeof(ctrl.sn) - 1; + + err = nvme_identify_ctrl(fd, &ctrl); + if (err) + return err; + + /* Remove trailing spaces from the name */ + while (i && ctrl.sn[i] == ' ') { + ctrl.sn[i] = '\0'; + i--; + } + + sprintf(f, "%s_%-.*s.bin", type == 0 ? "Nlog" : + type == 1 ? "EventLog" : "AssertLog", + (int)sizeof(ctrl.sn), ctrl.sn); + return err; +} + +static int get_internal_log_old(__u8 *buf, int output, int fd, + struct nvme_passthru_cmd *cmd) +{ + struct intel_vu_log *intel; + int err = 0; + const int dwmax = 0x400; + const int dmamax = 0x1000; + + intel = (struct intel_vu_log *)buf; + + printf("Log major:%d minor:%d header:%d size:%d\n", + intel->ver.major, intel->ver.minor, intel->header, intel->size); + + err = write(output, buf, 0x1000); + if (err < 0) { + perror("write failure"); + goto out; + } + intel->size -= 0x400; + cmd->opcode = 0xd2; + cmd->cdw10 = min(dwmax, intel->size); + cmd->data_len = min(dmamax, intel->size); + err = read_entire_cmd(cmd, intel->size, dwmax, output, fd, buf); + if (err) + goto out; + + err = 0; + out: + return err; +} + +static int get_internal_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + __u8 buf[0x2000]; + char f[0x100]; + int err, fd, output, i, j, count = 0, core_num = 1; + struct nvme_passthru_cmd cmd; + struct intel_cd_log cdlog; + struct intel_vu_log *intel = malloc(sizeof(struct intel_vu_log)); + struct intel_vu_nlog *intel_nlog = (struct intel_vu_nlog *)buf; + struct intel_assert_dump *ad = (struct intel_assert_dump *) intel->reserved; + struct intel_event_header *ehdr = (struct intel_event_header *)intel->reserved; + + const char *desc = "Get Intel Firmware Log and save it."; + const char *log = "Log type: 0, 1, or 2 for nlog, event log, and assert log, respectively."; + const char *core = "Select which region log should come from. -1 for all"; + const char *nlognum = "Select which nlog to read. -1 for all nlogs"; + const char *file = "Output file; defaults to device name provided"; + const char *verbose = "To print out verbose nlog info"; + const char *namespace_id = "Namespace to get logs from"; + + struct config { + __u32 namespace_id; + __u32 log; + int core; + int lnum; + char *file; + bool verbose; + }; + + struct config cfg = { + .namespace_id = -1, + .file = NULL, + .lnum = -1, + .core = -1 + }; + + OPT_ARGS(opts) = { + OPT_UINT("log", 'l', &cfg.log, log), + OPT_INT("region", 'r', &cfg.core, core), + OPT_INT("nlognum", 'm', &cfg.lnum, nlognum), + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_FILE("output-file", 'o', &cfg.file, file), + OPT_FLAG("verbose-nlog", 'v', &cfg.verbose, verbose), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) { + free(intel); + return fd; + } + + if (cfg.log > 2 || cfg.core > 4 || cfg.lnum > 255) { + free(intel); + return EINVAL; + } + + if (!cfg.file) { + err = setup_file(f, cfg.file, fd, cfg.log); + if (err) + goto out; + cfg.file = f; + } + + cdlog.u.entireDword = 0; + + cdlog.u.fields.selectLog = cfg.log; + cdlog.u.fields.selectCore = cfg.core < 0 ? 0 : cfg.core; + cdlog.u.fields.selectNlog = cfg.lnum < 0 ? 0 : cfg.lnum; + + output = open(cfg.file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + + err = read_header(&cmd, buf, fd, cdlog.u.entireDword, cfg.namespace_id); + if (err) + goto out; + memcpy(intel, buf, sizeof(*intel)); + + /* for 1.1 Fultondales will use old nlog, but current assert/event */ + if ((intel->ver.major < 1 && intel->ver.minor < 1) || + (intel->ver.major <= 1 && intel->ver.minor <= 1 && cfg.log == 0)) { + cmd.addr = (unsigned long)(void *)buf; + err = get_internal_log_old(buf, output, fd, &cmd); + goto out; + } + + if (cfg.log == 2) { + if (cfg.verbose) + printf("Log major:%d minor:%d header:%d size:%d numcores:%d\n", + intel->ver.major, intel->ver.minor, + intel->header, intel->size, intel->numcores); + + err = write_header(buf, output, 0x1000); + if (err) { + perror("write failure"); + goto out; + } + + count = intel->numcores; + } else if (cfg.log == 0) { + if (cfg.lnum < 0) + count = intel_nlog->totalnlogs; + else + count = 1; + if (cfg.core < 0) + core_num = intel_nlog->corecount; + } else if (cfg.log == 1) { + core_num = intel->numcores; + count = 1; + err = write_header(buf, output, sizeof(*intel)); + if (err) + goto out; + } + + for (j = (cfg.core < 0 ? 0 : cfg.core); + j < (cfg.core < 0 ? core_num : cfg.core + 1); + j++) { + cdlog.u.fields.selectCore = j; + for (i = 0; i < count; i++) { + if (cfg.log == 2) { + if (!ad[i].assertvalid) + continue; + cmd.cdw13 = ad[i].coreoffset; + cmd.cdw10 = 0x400; + cmd.data_len = min(0x400, ad[i].assertsize) * 4; + err = read_entire_cmd(&cmd, ad[i].assertsize, + 0x400, output, fd, buf); + if (err) + goto out; + + } else if(cfg.log == 0) { + /* If the user selected to read the entire nlog */ + if (count > 1) + cdlog.u.fields.selectNlog = i; + + err = read_header(&cmd, buf, fd, cdlog.u.entireDword, + cfg.namespace_id); + if (err) + goto out; + err = write_header(buf, output, sizeof(*intel_nlog)); + if (err) + goto out; + if (cfg.verbose) + print_intel_nlog(intel_nlog); + cmd.cdw13 = 0x400; + cmd.cdw10 = 0x400; + cmd.data_len = min(0x1000, intel_nlog->nlogbytesize); + err = read_entire_cmd(&cmd, intel_nlog->nlogbytesize / 4, + 0x400, output, fd, buf); + if (err) + goto out; + } else if (cfg.log == 1) { + cmd.cdw13 = ehdr->edumps[j].coreoffset; + cmd.cdw10 = 0x400; + cmd.data_len = 0x400; + err = read_entire_cmd(&cmd, ehdr->edumps[j].coresize, + 0x400, output, fd, buf); + if (err) + goto out; + } + } + } + err = 0; + out: + if (err > 0) { + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + } else if (err < 0) { + perror("intel log"); + err = EIO; + } else + printf("Successfully wrote log to %s\n", cfg.file); + free(intel); + return err; +} + +static int enable_lat_stats_tracking(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + int err, fd; + const char *desc = ( + "Enable/Disable Intel Latency Statistics Tracking.\n" + "No argument prints current status."); + const char *enable_desc = "Enable LST"; + const char *disable_desc = "Disable LST"; + const __u32 nsid = 0; + const __u8 fid = 0xe2; + const __u8 sel = 0; + const __u32 cdw11 = 0x0; + const __u32 cdw12 = 0x0; + const __u32 data_len = 32; + const __u32 save = 0; + __u32 result; + void *buf = NULL; + + struct config { + bool enable, disable; + }; + + struct config cfg = { + .enable = false, + .disable = false, + }; + + const struct argconfig_commandline_options command_line_options[] = { + {"enable", 'e', "", CFG_NONE, &cfg.enable, no_argument, enable_desc}, + {"disable", 'd', "", CFG_NONE, &cfg.disable, no_argument, disable_desc}, + {NULL} + }; + + fd = parse_and_open(argc, argv, desc, command_line_options); + + enum Option { + None = -1, + True = 1, + False = 0, + }; + + enum Option option = None; + + if (cfg.enable && cfg.disable) + printf("Cannot enable and disable simultaneously."); + else if (cfg.enable || cfg.disable) + option = cfg.enable; + + if (fd < 0) + return fd; + switch (option) { + case None: + err = nvme_get_feature(fd, nsid, fid, sel, cdw11, data_len, buf, + &result); + if (!err) { + printf( + "Latency Statistics Tracking (FID 0x%X) is currently (%i).\n", + fid, result); + } else { + printf("Could not read feature id 0xE2.\n"); + return err; + } + break; + case True: + case False: + err = nvme_set_feature(fd, nsid, fid, option, cdw12, save, + data_len, buf, &result); + if (err > 0) { + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + } else if (err < 0) { + perror("Enable latency tracking"); + fprintf(stderr, "Command failed while parsing.\n"); + } else { + printf("Successfully set enable bit for FID (0x%X) to %i.\n", + fid, option); + } + break; + default: + printf("%d not supported.\n", option); + return EINVAL; + } + return fd; +} diff --git a/plugins/intel/intel-nvme.h b/plugins/intel/intel-nvme.h new file mode 100644 index 0000000..335e901 --- /dev/null +++ b/plugins/intel/intel-nvme.h @@ -0,0 +1,23 @@ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/intel/intel-nvme + +#if !defined(INTEL_NVME) || defined(CMD_HEADER_MULTI_READ) +#define INTEL_NVME + +#include "cmd.h" + +PLUGIN(NAME("intel", "Intel vendor specific extensions"), + COMMAND_LIST( + ENTRY("id-ctrl", "Send NVMe Identify Controller", id_ctrl) + ENTRY("internal-log", "Retrieve Intel internal firmware log, save it", get_internal_log) + ENTRY("lat-stats", "Retrieve Intel IO Latency Statistics log, show it", get_lat_stats_log) + ENTRY("lat-stats-tracking", "Enable and disable Latency Statistics logging.", enable_lat_stats_tracking) + ENTRY("market-name", "Retrieve Intel Marketing Name log, show it", get_market_log) + ENTRY("smart-log-add", "Retrieve Intel SMART Log, show it", get_additional_smart_log) + ENTRY("temp-stats", "Retrieve Intel Temperature Statistics log, show it", get_temp_stats_log) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/lnvm/lnvm-nvme.c b/plugins/lnvm/lnvm-nvme.c new file mode 100644 index 0000000..3678176 --- /dev/null +++ b/plugins/lnvm/lnvm-nvme.c @@ -0,0 +1,436 @@ +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> + +#include "nvme.h" +#include "nvme-print.h" +#include "nvme-ioctl.h" +#include "plugin.h" + +#include "nvme-lightnvm.h" + +#include "argconfig.h" +#include "suffix.h" + +#define CREATE_CMD +#include "lnvm-nvme.h" + +static int lnvm_init(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Initialize LightNVM device. A LightNVM/Open-Channel SSD"\ + " must have a media manager associated before it can"\ + " be exposed to the user. The default is to initialize" + " the general media manager on top of the device.\n\n" + "Example:" + " lnvm-init -d nvme0n1"; + const char *devname = "identifier of desired device. e.g. nvme0n1."; + const char *mmtype = "media manager to initialize on top of device. Default: gennvm."; + int ret; + + struct config { + char *devname; + char *mmtype; + }; + + struct config cfg = { + .devname = "", + .mmtype = "gennvm", + }; + + OPT_ARGS(opts) = { + OPT_STRING("device-name", 'd', "DEVICE", &cfg.devname, devname), + OPT_STRING("mediamgr-name", 'm', "MM", &cfg.mmtype, mmtype), + OPT_END() + }; + + ret = argconfig_parse(argc, argv, desc, opts); + if (ret < 0) + return ret; + + if (!strlen(cfg.devname)) { + fprintf(stderr, "device name missing\n"); + return -EINVAL; + } + + return lnvm_do_init(cfg.devname, cfg.mmtype); +} + +static int lnvm_list(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "List all devices registered with LightNVM."; + int ret; + + OPT_ARGS(opts) = { + OPT_END() + }; + + ret = argconfig_parse(argc, argv, desc, opts); + if (ret < 0) + return ret; + + return lnvm_do_list_devices(); +} + +static int lnvm_info(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Show general information and registered target types with LightNVM"; + int ret; + + OPT_ARGS(opts) = { + OPT_END() + }; + + ret = argconfig_parse(argc, argv, desc, opts); + if (ret < 0) + return ret; + + return lnvm_do_info(); +} + +static int lnvm_id_ns(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Send an Identify Geometry command to the "\ + "given LightNVM device, returns properties of the specified "\ + "namespace in either human-readable or binary format."; + const char *raw_binary = "show infos in binary format"; + const char *human_readable = "show infos in readable format"; + const char *namespace_id = "identifier of desired namespace. default: 1"; + unsigned int flags = 0; + int fd; + + struct config { + __u32 namespace_id; + int raw_binary; + int human_readable; + }; + + struct config cfg = { + .namespace_id = 1, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw_binary), + OPT_FLAG("human-readable", 'H', &cfg.human_readable, human_readable), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if (cfg.human_readable) + flags |= VERBOSE; + else if (cfg.raw_binary) + flags |= BINARY; + + return lnvm_do_id_ns(fd, cfg.namespace_id, flags); +} + +static int lnvm_chunk_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Retrieve the chunk information log for the "\ + "specified given LightNVM device, returns in either "\ + "human-readable or binary format.\n"\ + "This will request Geometry first to get the "\ + "num_grp,num_pu,num_chk first to figure out the total size "\ + "of the log pages."\ + ; + const char *output_format = "Output format: normal|binary"; + const char *human_readable = "Print normal in readable format"; + int err, fmt, fd; + struct nvme_nvm_id20 geo; + struct nvme_nvm_chunk_desc *chunk_log; + __u32 nsid; + __u32 data_len; + unsigned int flags = 0; + + struct config { + char *output_format; + int human_readable; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("human-readable",'H', &cfg.human_readable, human_readable), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + fmt = validate_output_format(cfg.output_format); + if (fmt < 0) { + err = fmt; + goto close; + } + + if (fmt == BINARY) + flags |= BINARY; + else if (cfg.human_readable) + flags |= VERBOSE; + + nsid = nvme_get_nsid(fd); + + /* + * It needs to figure out how many bytes will be requested by this + * subcommand by the (num_grp * num_pu * num_chk) from the Geometry. + */ + err = lnvm_get_identity(fd, nsid, (struct nvme_nvm_id *) &geo); + if (err) + goto close; + + data_len = (geo.num_grp * geo.num_pu * geo.num_chk) * + sizeof(struct nvme_nvm_chunk_desc); + chunk_log = malloc(data_len); + if (!chunk_log) { + fprintf(stderr, "cound not alloc for chunk log %dbytes\n", + data_len); + err = -ENOMEM; + goto close; + } + + err = lnvm_do_chunk_log(fd, nsid, data_len, chunk_log, flags); + if (err) + fprintf(stderr, "get log page for chunk information failed\n"); + + free(chunk_log); +close: + close(fd); + return err; +} + +static int lnvm_create_tgt(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Instantiate a target on top of a LightNVM enabled device."; + const char *devname = "identifier of desired device. e.g. nvme0n1."; + const char *tgtname = "target name of the device to initialize. e.g. target0."; + const char *tgttype = "identifier of target type. e.g. pblk."; + const char *lun_begin = "Define begin of luns to use for target."; + const char *lun_end = "Define set of luns to use for target."; + const char *over_prov = "Define over-provision percentage for target."; + const char *flag_factory = "Create target in factory mode"; + int flags; + int ret; + + struct config { + char *devname; + char *tgtname; + char *tgttype; + __u32 lun_begin; + __u32 lun_end; + __u32 over_prov; + + /* flags */ + __u32 factory; + }; + + struct config cfg = { + .devname = "", + .tgtname = "", + .tgttype = "", + .lun_begin = -1, + .lun_end = -1, + .over_prov = -1, + .factory = 0, + }; + + OPT_ARGS(opts) = { + OPT_STRING("device-name", 'd', "DEVICE", &cfg.devname, devname), + OPT_STRING("target-name", 'n', "TARGET", &cfg.tgtname, tgtname), + OPT_STRING("target-type", 't', "TARGETTYPE", &cfg.tgttype, tgttype), + OPT_UINT("lun-begin", 'b', &cfg.lun_begin, lun_begin), + OPT_UINT("lun-end", 'e', &cfg.lun_end, lun_end), + OPT_UINT("over-prov", 'o', &cfg.over_prov, over_prov), + OPT_FLAG("factory", 'f', &cfg.factory, flag_factory), + OPT_END() + }; + + ret = argconfig_parse(argc, argv, desc, opts); + if (ret < 0) + return ret; + + if (!strlen(cfg.devname)) { + fprintf(stderr, "device name missing\n"); + return -EINVAL; + } + if (!strlen(cfg.tgtname)) { + fprintf(stderr, "target name missing\n"); + return -EINVAL; + } + if (!strlen(cfg.tgttype)) { + fprintf(stderr, "target type missing\n"); + return -EINVAL; + } + + flags = 0; + if (cfg.factory) + flags |= NVM_TARGET_FACTORY; + + return lnvm_do_create_tgt(cfg.devname, cfg.tgtname, cfg.tgttype, cfg.lun_begin, cfg.lun_end, cfg.over_prov, flags); +} + +static int lnvm_remove_tgt(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Remove an initialized LightNVM target."; + const char *tgtname = "target name of the device to remove. e.g. target0."; + int ret; + + struct config { + char *tgtname; + }; + + struct config cfg = { + .tgtname = "", + }; + + OPT_ARGS(opts) = { + OPT_STRING("target-name", 'n', "TARGET", &cfg.tgtname, tgtname), + OPT_END() + }; + + ret = argconfig_parse(argc, argv, desc, opts); + if (ret < 0) + return ret; + + if (!strlen(cfg.tgtname)) { + fprintf(stderr, "target name missing\n"); + return -EINVAL; + } + + return lnvm_do_remove_tgt(cfg.tgtname); +} + +static int lnvm_factory_init(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Factory initialize a LightNVM enabled device."; + const char *devname = "identifier of desired device. e.g. nvme0n1."; + const char *erase_only_marked = "only erase marked blocks. default: all blocks."; + const char *host_marks = "remove host side blocks list. default: keep."; + const char *bb_marks = "remove grown bad blocks list. default: keep"; + int ret; + + struct config { + char *devname; + int erase_only_marked; + int clear_host_marks; + int clear_bb_marks; + }; + + struct config cfg = { + .devname = "", + }; + + OPT_ARGS(opts) = { + OPT_STRING("device-name", 'd', "DEVICE", &cfg.devname, devname), + OPT_FLAG("erase-only-marked", 'e', &cfg.erase_only_marked, erase_only_marked), + OPT_FLAG("clear-host-side-blks", 's', &cfg.clear_host_marks, host_marks), + OPT_FLAG("clear-bb-blks", 'b', &cfg.clear_bb_marks, bb_marks), + OPT_END() + }; + + ret = argconfig_parse(argc, argv, desc, opts); + if (ret < 0) + return ret; + + if (!strlen(cfg.devname)) { + fprintf(stderr, "device name missing\n"); + return -EINVAL; + } + + return lnvm_do_factory_init(cfg.devname, cfg.erase_only_marked, + cfg.clear_host_marks, cfg.clear_bb_marks); +} + +static int lnvm_get_bbtbl(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Receive bad block table from a LightNVM compatible"\ + " device."; + const char *namespace = "(optional) desired namespace"; + const char *ch = "channel identifier"; + const char *lun = "lun identifier (within a channel)"; + const char *raw_binary = "show infos in binary format"; + unsigned int fd, flags = 0; + + struct config { + __u32 namespace_id; + __u16 lunid; + __u16 chid; + int raw_binary; + }; + + struct config cfg = { + .namespace_id = 1, + .lunid = 0, + .chid = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace), + OPT_SHRT("channel-id", 'c', &cfg.chid, ch), + OPT_SHRT("lun-id", 'l', &cfg.lunid, lun), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw_binary), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + + if (cfg.raw_binary) + flags |= BINARY; + + return lnvm_do_get_bbtbl(fd, cfg.namespace_id, cfg.lunid, cfg.chid, flags); +} + +static int lnvm_set_bbtbl(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Update bad block table on a LightNVM compatible"\ + " device."; + const char *namespace = "(optional) desired namespace"; + const char *ch = "channel identifier"; + const char *lun = "lun identifier (within a channel)"; + const char *pln = "plane identifier (within a lun)"; + const char *blk = "block identifier (within a plane)"; + const char *value = "value to update the specific block to."; + int fd; + + struct config { + __u32 namespace_id; + __u16 lunid; + __u16 chid; + __u16 plnid; + __u16 blkid; + __u16 value; + }; + + struct config cfg = { + .namespace_id = 1, + .lunid = 0, + .chid = 0, + .plnid = 0, + .blkid = 0, + .value = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace), + OPT_SHRT("channel-id", 'c', &cfg.chid, ch), + OPT_SHRT("lun-id", 'l', &cfg.lunid, lun), + OPT_SHRT("plane-id", 'p', &cfg.plnid, pln), + OPT_SHRT("block-id", 'b', &cfg.blkid, blk), + OPT_SHRT("value", 'v', &cfg.value, value), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + + printf("Updating: Ch.: %u LUN: %u Plane: %u Block: %u -> %u\n", + cfg.chid, cfg.lunid, cfg.plnid, cfg.blkid, cfg.value); + return lnvm_do_set_bbtbl(fd, cfg.namespace_id, cfg.chid, cfg.lunid, + cfg.plnid, cfg.blkid, cfg.value); +} diff --git a/plugins/lnvm/lnvm-nvme.h b/plugins/lnvm/lnvm-nvme.h new file mode 100644 index 0000000..45b3cf0 --- /dev/null +++ b/plugins/lnvm/lnvm-nvme.h @@ -0,0 +1,27 @@ + +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/lnvm/lnvm-nvme + +#if !defined(LNVM_NVME) || defined(CMD_HEADER_MULTI_READ) +#define LNVM_NVME + +#include "cmd.h" + +PLUGIN(NAME("lnvm", "LightNVM specific extensions"), + COMMAND_LIST( + ENTRY("list", "List available LightNVM devices", lnvm_list) + ENTRY("info", "List general information and available target engines", lnvm_info) + ENTRY("id-ns", "List geometry for LightNVM device", lnvm_id_ns, "geometry") + ENTRY("chunk-log", "Chunk Information Log Page", lnvm_chunk_log) + ENTRY("init", "Initialize media manager on LightNVM device", lnvm_init) + ENTRY("create", "Create target on top of a LightNVM device", lnvm_create_tgt) + ENTRY("remove", "Remove target from device", lnvm_remove_tgt) + ENTRY("factory", "Reset device to factory state", lnvm_factory_init) + ENTRY("diag-bbtbl", "Diagnose bad block table", lnvm_get_bbtbl) + ENTRY("diag-set-bbtbl", "Update bad block table", lnvm_set_bbtbl) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/memblaze/memblaze-nvme.c b/plugins/memblaze/memblaze-nvme.c new file mode 100644 index 0000000..c75f49c --- /dev/null +++ b/plugins/memblaze/memblaze-nvme.c @@ -0,0 +1,759 @@ +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.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 "memblaze-nvme.h" +#include "memblaze-utils.h" + +enum { + MB_FEAT_POWER_MGMT = 0xc6, +}; + +/* + * Return -1 if @fw1 < @fw2 + * Return 0 if @fw1 == @fw2 + * Return 1 if @fw1 > @fw2 + */ +static int compare_fw_version(const char *fw1, const char *fw2) +{ + while (*fw1 != '\0') { + if (*fw2 == '\0' || *fw1 > *fw2) + return 1; + if (*fw1 < *fw2) + return -1; + fw1++; + fw2++; + } + + if (*fw2 != '\0') + return -1; + + return 0; +} + +/********************************************************** + * input: firmware version string + * output: + * 1: new intel format + * 0: old memblaze format + * *******************************************************/ +#define MEMBLAZE_FORMAT (0) +#define INTEL_FORMAT (1) + +// 2.83 = raisin +#define IS_RAISIN(str) (!strcmp(str, "2.83")) +// 2.13 = papaya +#define IS_PAPAYA(str) (!strcmp(str, "2.13")) +#define STR_VER_SIZE 5 + +int getlogpage_format_type(char *fw_ver) +{ + char fw_ver_local[STR_VER_SIZE]; + strncpy(fw_ver_local, fw_ver, STR_VER_SIZE); + *(fw_ver_local + STR_VER_SIZE - 1) = '\0'; + if ( IS_RAISIN(fw_ver_local) ) + { + return INTEL_FORMAT; + } + else + { + return MEMBLAZE_FORMAT; + } +} + +static __u32 item_id_2_u32(struct nvme_memblaze_smart_log_item *item) +{ + __le32 __id = 0; + memcpy(&__id, item->id, 3); + return le32_to_cpu(__id); +} + +static __u64 raw_2_u64(const __u8 *buf, size_t len) +{ + __le64 val = 0; + memcpy(&val, buf, len); + return le64_to_cpu(val); +} + +#define STRN2_01 "Additional Smart Log for NVME device" +#define STRN2_02 "namespace-id" +#define STRN1_01 "key" +#define STRN1_02 "normalized" +#define STRN1_03 "raw" +#define STR00_01 "program_fail_count" +#define STR01_01 "erase_fail_count" +#define STR02_01 "wear_leveling" +#define STR02_03 "min: " +#define STR02_04 ", max: " +#define STR02_05 ", avg: " +#define STR03_01 "end_to_end_error_detection_count" +#define STR04_01 "crc_error_count" +#define STR05_01 "timed_workload_media_wear" +#define STR06_01 "timed_workload_host_reads" +#define STR07_01 "timed_workload_timer" +#define STR07_02 " min" +#define STR08_01 "thermal_throttle_status" +#define STR08_02 ", cnt: " +#define STR09_01 "retry_buffer_overflow_count" +#define STR10_01 "pll_lock_loss_count" +#define STR11_01 "nand_bytes_written" +#define STR11_03 "sectors: " +#define STR12_01 "host_bytes_written" +#define STR12_03 "sectors: " +#define STR13_01 "system_area_life_left" +#define STR14_01 "total_read" +#define STR15_01 "tempt_since_born" +#define STR15_03 "max: " +#define STR15_04 ", min: " +#define STR15_05 ", curr: " +#define STR16_01 "power_consumption" +#define STR16_03 "max: " +#define STR16_04 ", min: " +#define STR16_05 ", curr: " +#define STR17_01 "tempt_since_bootup" +#define STR17_03 "max: " +#define STR17_04 ", min: " +#define STR17_05 ", curr: " +#define STR18_01 "power_loss_protection" +#define STR19_01 "read_fail" +#define STR20_01 "thermal_throttle_time" +#define STR21_01 "flash_media_error" + +static void get_memblaze_new_smart_info(struct nvme_p4_smart_log *smart, int index, u8 *nm_val, u8 *raw_val) +{ + memcpy(nm_val, smart->itemArr[index].nmVal, NM_SIZE); + memcpy(raw_val, smart->itemArr[index].rawVal, RAW_SIZE); +} + +static void show_memblaze_smart_log_new(struct nvme_memblaze_smart_log *s, + unsigned int nsid, const char *devname) +{ + struct nvme_p4_smart_log *smart = (struct nvme_p4_smart_log *)s; + u8 *nm = malloc(NM_SIZE * sizeof(u8)); + u8 *raw = malloc(RAW_SIZE * sizeof(u8)); + + /* Table Title */ + printf("%s:%s %s:%x\n", STRN2_01, devname, STRN2_02, nsid); + /* Clumn Name*/ + printf("%-34s%-11s%s\n", STRN1_01, STRN1_02, STRN1_03); + /* 00 RAISIN_SI_VD_PROGRAM_FAIL */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_PROGRAM_FAIL, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", STR00_01, *nm, int48_to_long(raw)); + /* 01 RAISIN_SI_VD_ERASE_FAIL */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_ERASE_FAIL, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", STR01_01, *nm, int48_to_long(raw)); + /* 02 RAISIN_SI_VD_WEARLEVELING_COUNT */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_WEARLEVELING_COUNT, nm, raw); + printf("%-31s : %3d%% %s%u%s%u%s%u\n", STR02_01, *nm, + STR02_03, *raw, STR02_04, *(raw+2), STR02_05, *(raw+4)); + /* 03 RAISIN_SI_VD_E2E_DECTECTION_COUNT */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_E2E_DECTECTION_COUNT, nm, raw); + printf("%-31s: %3d%% %"PRIu64"\n", STR03_01, *nm, int48_to_long(raw)); + /* 04 RAISIN_SI_VD_PCIE_CRC_ERR_COUNT */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_PCIE_CRC_ERR_COUNT, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", STR04_01, *nm, int48_to_long(raw)); + /* 05 RAISIN_SI_VD_TIMED_WORKLOAD_MEDIA_WEAR */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TIMED_WORKLOAD_MEDIA_WEAR, nm, raw); + printf("%-32s: %3d%% %.3f%%\n", STR05_01, *nm, ((float)int48_to_long(raw))/1000); + /* 06 RAISIN_SI_VD_TIMED_WORKLOAD_HOST_READ */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TIMED_WORKLOAD_HOST_READ, nm, raw); + printf("%-32s: %3d%% %"PRIu64"%%\n", STR06_01, *nm, int48_to_long(raw)); + /* 07 RAISIN_SI_VD_TIMED_WORKLOAD_TIMER */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TIMED_WORKLOAD_TIMER, nm, raw); + printf("%-32s: %3d%% %"PRIu64"%s\n", STR07_01, *nm, int48_to_long(raw), STR07_02); + /* 08 RAISIN_SI_VD_THERMAL_THROTTLE_STATUS */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_THERMAL_THROTTLE_STATUS, nm, raw); + printf("%-32s: %3d%% %"PRIu64"%%%s%"PRIu64"\n", STR08_01, *nm, + int48_to_long(raw), STR08_02, int48_to_long(raw+1)); + /* 09 RAISIN_SI_VD_RETRY_BUFF_OVERFLOW_COUNT */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_RETRY_BUFF_OVERFLOW_COUNT, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", STR09_01, *nm, int48_to_long(raw)); + /* 10 RAISIN_SI_VD_PLL_LOCK_LOSS_COUNT */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_PLL_LOCK_LOSS_COUNT, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", STR10_01, *nm, int48_to_long(raw)); + /* 11 RAISIN_SI_VD_TOTAL_WRITE */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TOTAL_WRITE, nm, raw); + printf("%-32s: %3d%% %s%"PRIu64"\n", STR11_01, *nm, STR11_03, int48_to_long(raw)); + /* 12 RAISIN_SI_VD_HOST_WRITE */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_HOST_WRITE, nm, raw); + printf("%-32s: %3d%% %s%"PRIu64"\n", STR12_01, *nm, STR12_03, int48_to_long(raw)); + /* 13 RAISIN_SI_VD_SYSTEM_AREA_LIFE_LEFT */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_SYSTEM_AREA_LIFE_LEFT, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", STR13_01, *nm, int48_to_long(raw)); + /* 14 RAISIN_SI_VD_TOTAL_READ */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TOTAL_READ, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", STR14_01, *nm, int48_to_long(raw)); + /* 15 RAISIN_SI_VD_TEMPT_SINCE_BORN */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TEMPT_SINCE_BORN, nm, raw); + printf("%-32s: %3d%% %s%u%s%u%s%u\n", STR15_01, *nm, + STR15_03, *raw, STR15_04, *(raw+2), STR15_05, *(raw+4)); + /* 16 RAISIN_SI_VD_POWER_CONSUMPTION */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_POWER_CONSUMPTION, nm, raw); + printf("%-32s: %3d%% %s%u%s%u%s%u\n", STR16_01, *nm, + STR16_03, *raw, STR16_04, *(raw+2), STR16_05, *(raw+4)); + /* 17 RAISIN_SI_VD_TEMPT_SINCE_BOOTUP */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TEMPT_SINCE_BOOTUP, nm, raw); + printf("%-32s: %3d%% %s%u%s%u%s%u\n", STR17_01, *nm, STR17_03, *raw, + STR17_04, *(raw+2), STR17_05, *(raw+4)); + /* 18 RAISIN_SI_VD_POWER_LOSS_PROTECTION */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_POWER_LOSS_PROTECTION, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", STR18_01, *nm, int48_to_long(raw)); + /* 19 RAISIN_SI_VD_READ_FAIL */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_READ_FAIL, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", STR19_01, *nm, int48_to_long(raw)); + /* 20 RAISIN_SI_VD_THERMAL_THROTTLE_TIME */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_THERMAL_THROTTLE_TIME, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", STR20_01, *nm, int48_to_long(raw)); + /* 21 RAISIN_SI_VD_FLASH_MEDIA_ERROR */ + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_FLASH_MEDIA_ERROR, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", STR21_01, *nm, int48_to_long(raw)); + + free(nm); + free(raw); +} + +static void show_memblaze_smart_log_old(struct nvme_memblaze_smart_log *smart, + unsigned int nsid, const char *devname, const char *fw_ver) +{ + char fw_ver_local[STR_VER_SIZE]; + struct nvme_memblaze_smart_log_item *item; + + strncpy(fw_ver_local, fw_ver, STR_VER_SIZE); + *(fw_ver_local + STR_VER_SIZE - 1) = '\0'; + + printf("Additional Smart Log for NVME device:%s namespace-id:%x\n", devname, nsid); + + printf("Total write in GB since last factory reset : %"PRIu64"\n", + int48_to_long(smart->items[TOTAL_WRITE].rawval)); + printf("Total read in GB since last factory reset : %"PRIu64"\n", + int48_to_long(smart->items[TOTAL_READ].rawval)); + + printf("Thermal throttling status[1:HTP in progress] : %u\n", + smart->items[THERMAL_THROTTLE].thermal_throttle.on); + printf("Total thermal throttling minutes since power on : %u\n", + smart->items[THERMAL_THROTTLE].thermal_throttle.count); + + printf("Maximum temperature in Kelvin since last factory reset : %u\n", + le16_to_cpu(smart->items[TEMPT_SINCE_RESET].temperature.max)); + printf("Minimum temperature in Kelvin since last factory reset : %u\n", + le16_to_cpu(smart->items[TEMPT_SINCE_RESET].temperature.min)); + if (compare_fw_version(fw_ver, "0.09.0300") != 0) { + printf("Maximum temperature in Kelvin since power on : %u\n", + le16_to_cpu(smart->items[TEMPT_SINCE_BOOTUP].temperature_p.max)); + printf("Minimum temperature in Kelvin since power on : %u\n", + le16_to_cpu(smart->items[TEMPT_SINCE_BOOTUP].temperature_p.min)); + } + printf("Current temperature in Kelvin : %u\n", + le16_to_cpu(smart->items[TEMPT_SINCE_RESET].temperature.curr)); + + printf("Maximum power in watt since power on : %u\n", + le16_to_cpu(smart->items[POWER_CONSUMPTION].power.max)); + printf("Minimum power in watt since power on : %u\n", + le16_to_cpu(smart->items[POWER_CONSUMPTION].power.min)); + printf("Current power in watt : %u\n", + le16_to_cpu(smart->items[POWER_CONSUMPTION].power.curr)); + + item = &smart->items[POWER_LOSS_PROTECTION]; + if (item_id_2_u32(item) == 0xEC) + printf("Power loss protection normalized value : %u\n", + item->power_loss_protection.curr); + + item = &smart->items[WEARLEVELING_COUNT]; + if (item_id_2_u32(item) == 0xAD) { + printf("Percentage of wearleveling count left : %u\n", + le16_to_cpu(item->nmval)); + printf("Wearleveling count min erase cycle : %u\n", + le16_to_cpu(item->wearleveling_count.min)); + printf("Wearleveling count max erase cycle : %u\n", + le16_to_cpu(item->wearleveling_count.max)); + printf("Wearleveling count avg erase cycle : %u\n", + le16_to_cpu(item->wearleveling_count.avg)); + } + + item = &smart->items[HOST_WRITE]; + if (item_id_2_u32(item) == 0xF5) + printf("Total host write in GiB since device born : %llu\n", + (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval))); + + item = &smart->items[THERMAL_THROTTLE_CNT]; + if (item_id_2_u32(item) == 0xEB) + printf("Thermal throttling count since device born : %u\n", + item->thermal_throttle_cnt.cnt); + + item = &smart->items[CORRECT_PCIE_PORT0]; + if (item_id_2_u32(item) == 0xED) + printf("PCIE Correctable Error Count of Port0 : %llu\n", + (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval))); + + item = &smart->items[CORRECT_PCIE_PORT1]; + if (item_id_2_u32(item) == 0xEE) + printf("PCIE Correctable Error Count of Port1 : %llu\n", + (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval))); + + item = &smart->items[REBUILD_FAIL]; + if (item_id_2_u32(item) == 0xEF) + printf("End-to-End Error Detection Count : %llu\n", + (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval))); + + item = &smart->items[ERASE_FAIL]; + if (item_id_2_u32(item) == 0xF0) + printf("Erase Fail Count : %llu\n", + (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval))); + + item = &smart->items[PROGRAM_FAIL]; + if (item_id_2_u32(item) == 0xF1) + printf("Program Fail Count : %llu\n", + (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval))); + + item = &smart->items[READ_FAIL]; + if (item_id_2_u32(item) == 0xF2) + printf("Read Fail Count : %llu\n", + (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval))); + + if ( IS_PAPAYA(fw_ver_local) ) { + struct nvme_p4_smart_log *s = (struct nvme_p4_smart_log *)smart; + u8 *nm = malloc(NM_SIZE * sizeof(u8)); + u8 *raw = malloc(RAW_SIZE * sizeof(u8)); + + /* 00 RAISIN_SI_VD_PROGRAM_FAIL */ + get_memblaze_new_smart_info(s, PROGRAM_FAIL, nm, raw); + printf("%-32s : %3d%% %"PRIu64"\n", + STR00_01, *nm, int48_to_long(raw)); + /* 01 RAISIN_SI_VD_ERASE_FAIL */ + get_memblaze_new_smart_info(s, ERASE_FAIL, nm, raw); + printf("%-32s : %3d%% %"PRIu64"\n", + STR01_01, *nm, int48_to_long(raw)); + /* 02 RAISIN_SI_VD_WEARLEVELING_COUNT */ + get_memblaze_new_smart_info(s, WEARLEVELING_COUNT, nm, raw); + printf("%-31s : %3d%% %s%u%s%u%s%u\n", + STR02_01, *nm, STR02_03, *raw, STR02_04, *(raw+2), STR02_05, *(raw+4)); + /* 11 RAISIN_SI_VD_TOTAL_WRITE */ + get_memblaze_new_smart_info(s, TOTAL_WRITE, nm, raw); + printf("%-32s : %3d%% %"PRIu64"\n", + STR11_01, *nm, 32*int48_to_long(raw)); + /* 12 RAISIN_SI_VD_HOST_WRITE */ + get_memblaze_new_smart_info(s, HOST_WRITE, nm, raw); + printf("%-32s : %3d%% %"PRIu64"\n", + STR12_01, *nm, 32*int48_to_long(raw)); + + free(nm); + free(raw); + } +} + +static int show_memblaze_smart_log(int fd, __u32 nsid, const char *devname, + struct nvme_memblaze_smart_log *smart) +{ + struct nvme_id_ctrl ctrl; + char fw_ver[10]; + int err = 0; + + err = nvme_identify_ctrl(fd, &ctrl); + if (err) + return err; + + snprintf(fw_ver, sizeof(fw_ver), "%c.%c%c.%c%c%c%c", + ctrl.fr[0], ctrl.fr[1], ctrl.fr[2], ctrl.fr[3], + ctrl.fr[4], ctrl.fr[5], ctrl.fr[6]); + + if (getlogpage_format_type(fw_ver)) // Intel Format & new format + { + show_memblaze_smart_log_new(smart, nsid, devname); + } + else // Memblaze Format & old format + { + show_memblaze_smart_log_old(smart, nsid, devname, fw_ver); + } + return err; +} + +static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct nvme_memblaze_smart_log smart_log; + int err, fd; + char *desc = "Get Memblaze vendor specific additional smart log (optionally, "\ + "for the specified namespace), and show it."; + const char *namespace = "(optional) desired namespace"; + const char *raw = "dump output in binary format"; + struct config { + __u32 namespace_id; + int raw_binary; + }; + + struct config cfg = { + .namespace_id = NVME_NSID_ALL, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + err = nvme_get_log(fd, cfg.namespace_id, 0xca, false, + sizeof(smart_log), &smart_log); + if (!err) { + if (!cfg.raw_binary) + err = show_memblaze_smart_log(fd, cfg.namespace_id, devicename, &smart_log); + else + d_raw((unsigned char *)&smart_log, sizeof(smart_log)); + } + if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(err), err); + + return err; +} + +static char *mb_feature_to_string(int feature) +{ + switch (feature) { + case MB_FEAT_POWER_MGMT: return "Memblaze power management"; + default: return "Unknown"; + } +} + +static int get_additional_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Read operating parameters of the "\ + "specified controller. Operating parameters are grouped "\ + "and identified by Feature Identifiers; each Feature "\ + "Identifier contains one or more attributes that may affect "\ + "behaviour of the feature. Each Feature has three possible "\ + "settings: default, saveable, and current. If a Feature is "\ + "saveable, it may be modified by set-feature. Default values "\ + "are vendor-specific and not changeable. Use set-feature to "\ + "change saveable Features.\n\n"\ + "Available additional feature id:\n"\ + "0xc6: Memblaze power management\n"\ + " (value 0 - 25w, 1 - 20w, 2 - 15w)"; + const char *raw = "show feature in binary format"; + const char *namespace_id = "identifier of desired namespace"; + const char *feature_id = "hexadecimal feature name"; + const char *sel = "[0-3]: curr./default/saved/supp."; + const char *data_len = "buffer len (if) data is returned"; + const char *cdw11 = "dword 11 for interrupt vector config"; + const char *human_readable = "show infos in readable format"; + int err, fd; + __u32 result; + void *buf = NULL; + + struct config { + __u32 namespace_id; + __u32 feature_id; + __u8 sel; + __u32 cdw11; + __u32 data_len; + int raw_binary; + int human_readable; + }; + + struct config cfg = { + .namespace_id = 1, + .feature_id = 0, + .sel = 0, + .cdw11 = 0, + .data_len = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("feature-id", 'f', &cfg.feature_id, feature_id), + OPT_BYTE("sel", 's', &cfg.sel, sel), + OPT_UINT("data-len", 'l', &cfg.data_len, data_len), + OPT_UINT("cdw11", 'c', &cfg.cdw11, cdw11), + OPT_FLAG("human-readable", 'H', &cfg.human_readable, human_readable), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if (cfg.sel > 7) { + fprintf(stderr, "invalid 'select' param:%d\n", cfg.sel); + return EINVAL; + } + if (!cfg.feature_id) { + fprintf(stderr, "feature-id required param\n"); + return EINVAL; + } + if (cfg.data_len) { + if (posix_memalign(&buf, getpagesize(), cfg.data_len)) + exit(ENOMEM); + memset(buf, 0, cfg.data_len); + } + + err = nvme_get_feature(fd, cfg.namespace_id, cfg.feature_id, cfg.sel, cfg.cdw11, + cfg.data_len, buf, &result); + if (!err) { + printf("get-feature:0x%02x (%s), %s value: %#08x\n", cfg.feature_id, + mb_feature_to_string(cfg.feature_id), + nvme_select_to_string(cfg.sel), result); + if (cfg.human_readable) + nvme_feature_show_fields(cfg.feature_id, result, buf); + else { + if (buf) { + if (!cfg.raw_binary) + d(buf, cfg.data_len, 16, 1); + else + d_raw(buf, cfg.data_len); + } + } + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + if (buf) + free(buf); + return err; +} + +static int set_additional_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Modify the saveable or changeable "\ + "current operating parameters of the controller. Operating "\ + "parameters are grouped and identified by Feature "\ + "Identifiers. Feature settings can be applied to the entire "\ + "controller and all associated namespaces, or to only a few "\ + "namespace(s) associated with the controller. Default values "\ + "for each Feature are vendor-specific and may not be modified."\ + "Use get-feature to determine which Features are supported by "\ + "the controller and are saveable/changeable.\n\n"\ + "Available additional feature id:\n"\ + "0xc6: Memblaze power management\n"\ + " (value 0 - 25w, 1 - 20w, 2 - 15w)"; + const char *namespace_id = "desired namespace"; + const char *feature_id = "hex feature name (required)"; + const char *data_len = "buffer length if data required"; + const char *data = "optional file for feature data (default stdin)"; + const char *value = "new value of feature (required)"; + const char *save = "specifies that the controller shall save the attribute"; + int err, fd; + __u32 result; + void *buf = NULL; + int ffd = STDIN_FILENO; + + struct config { + char *file; + __u32 namespace_id; + __u32 feature_id; + __u32 value; + __u32 data_len; + int save; + }; + + struct config cfg = { + .file = "", + .namespace_id = 0, + .feature_id = 0, + .value = 0, + .data_len = 0, + .save = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("feature-id", 'f', &cfg.feature_id, feature_id), + OPT_UINT("value", 'v', &cfg.value, value), + OPT_UINT("data-len", 'l', &cfg.data_len, data_len), + OPT_FILE("data", 'd', &cfg.file, data), + OPT_FLAG("save", 's', &cfg.save, save), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if (!cfg.feature_id) { + fprintf(stderr, "feature-id required param\n"); + return EINVAL; + } + + if (cfg.data_len) { + if (posix_memalign(&buf, getpagesize(), cfg.data_len)) + exit(ENOMEM); + memset(buf, 0, cfg.data_len); + } + + if (buf) { + if (strlen(cfg.file)) { + ffd = open(cfg.file, O_RDONLY); + if (ffd <= 0) { + fprintf(stderr, "no firmware file provided\n"); + err = EINVAL; + goto free; + } + } + if (read(ffd, (void *)buf, cfg.data_len) < 0) { + fprintf(stderr, "failed to read data buffer from input file\n"); + err = EINVAL; + goto free; + } + } + + err = nvme_set_feature(fd, cfg.namespace_id, cfg.feature_id, cfg.value, + 0, cfg.save, cfg.data_len, buf, &result); + if (err < 0) { + perror("set-feature"); + goto free; + } + if (!err) { + printf("set-feature:%02x (%s), value:%#08x\n", cfg.feature_id, + mb_feature_to_string(cfg.feature_id), cfg.value); + if (buf) + d(buf, cfg.data_len, 16, 1); + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + +free: + if (buf) + free(buf); + return err; +} + +static int memblaze_fw_commit(int fd, int select) +{ + struct nvme_admin_cmd cmd = { + .opcode = nvme_admin_activate_fw, + .cdw10 = 8, + .cdw12 = select, + }; + + return nvme_submit_admin_passthru(fd, &cmd); +} + +static int memblaze_selective_download(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = + "This performs a selective firmware download, which allows the user to " + "select which firmware binary to update for 9200 devices. This requires a power cycle once the " + "update completes. The options available are: \n\n" + "OOB - This updates the OOB and main firmware\n" + "EEP - This updates the eeprom and main firmware\n" + "ALL - This updates the eeprom, OOB, and main firmware"; + const char *fw = "firmware file (required)"; + const char *select = "FW Select (e.g., --select=OOB, EEP, ALL)"; + int xfer = 4096; + void *fw_buf; + int fd, selectNo,fw_fd,fw_size,err,offset = 0; + struct stat sb; + int i; + + struct config { + char *fw; + char *select; + }; + + struct config cfg = { + .fw = "", + .select = "\0", + }; + + OPT_ARGS(opts) = { + OPT_STRING("fw", 'f', "FILE", &cfg.fw, fw), + OPT_STRING("select", 's', "flag", &cfg.select, select), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if (strlen(cfg.select) != 3) { + fprintf(stderr, "Invalid select flag\n"); + err = EINVAL; + goto out; + } + + for (i = 0; i < 3; i++) { + cfg.select[i] = toupper(cfg.select[i]); + } + + if (strncmp(cfg.select,"OOB", 3) == 0) { + selectNo = 18; + } else if (strncmp(cfg.select,"EEP", 3) == 0) { + selectNo = 10; + } else if (strncmp(cfg.select,"ALL", 3) == 0) { + selectNo = 26; + } else { + fprintf(stderr, "Invalid select flag\n"); + err = EINVAL; + goto out; + } + + fw_fd = open(cfg.fw, O_RDONLY); + if (fw_fd < 0) { + fprintf(stderr, "no firmware file provided\n"); + err = EINVAL; + goto out; + } + + err = fstat(fw_fd, &sb); + if (err < 0) { + perror("fstat"); + err = errno; + } + + fw_size = sb.st_size; + if (fw_size & 0x3) { + fprintf(stderr, "Invalid size:%d for f/w image\n", fw_size); + err = EINVAL; + goto out; + } + + if (posix_memalign(&fw_buf, getpagesize(), fw_size)) { + fprintf(stderr, "No memory for f/w size:%d\n", fw_size); + err = ENOMEM; + goto out; + } + + if (read(fw_fd, fw_buf, fw_size) != ((ssize_t)(fw_size))) + return EIO; + + while (fw_size > 0) { + xfer = min(xfer, fw_size); + + err = nvme_fw_download(fd, offset, xfer, fw_buf); + if (err < 0) { + perror("fw-download"); + goto out; + } else if (err != 0) { + fprintf(stderr, "NVME Admin command error:%s(%x)\n", + nvme_status_to_string(err), err); + goto out; + } + fw_buf += xfer; + fw_size -= xfer; + offset += xfer; + } + + err = memblaze_fw_commit(fd,selectNo); + + if(err == 0x10B || err == 0x20B) { + err = 0; + fprintf(stderr, "Update successful! Please power cycle for changes to take effect\n"); + } + +out: + return err; +} + diff --git a/plugins/memblaze/memblaze-nvme.h b/plugins/memblaze/memblaze-nvme.h new file mode 100644 index 0000000..8f7024d --- /dev/null +++ b/plugins/memblaze/memblaze-nvme.h @@ -0,0 +1,27 @@ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/memblaze/memblaze-nvme + +#if !defined(MEMBLAZE_NVME) || defined(CMD_HEADER_MULTI_READ) +#define MEMBLAZE_NVME + +#include "cmd.h" +#include "common.h" + +#include <ctype.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +PLUGIN(NAME("memblaze", "Memblaze vendor specific extensions"), + COMMAND_LIST( + ENTRY("smart-log-add", "Retrieve Memblaze SMART Log, show it", get_additional_smart_log) + ENTRY("get-feature-add", "Get Memblaze feature and show the resulting value", get_additional_feature) + ENTRY("set-feature-add", "Set a Memblaze feature and show the resulting value", set_additional_feature) + ENTRY("select-download", "Selective Firmware Download", memblaze_selective_download) + ) +); + +#endif + +#include "define_cmd.h" + diff --git a/plugins/memblaze/memblaze-utils.h b/plugins/memblaze/memblaze-utils.h new file mode 100644 index 0000000..a67ee5e --- /dev/null +++ b/plugins/memblaze/memblaze-utils.h @@ -0,0 +1,164 @@ +#ifndef __MEMBLAZE_UTILS_H__ +#define __MEMBLAZE_UTILS_H__ + +#define SMART_INFO_OLD_SIZE 512 +#define SMART_INFO_NEW_SIZE 4096 + +#define ID_SIZE 3 +#define NM_SIZE 2 +#define RAW_SIZE 7 + +typedef unsigned char u8; + +// Intel Format & new format +/* Raisin Additional smart external ID */ +#define RAISIN_SI_VD_PROGRAM_FAIL_ID 0xAB +#define RAISIN_SI_VD_ERASE_FAIL_ID 0xAC +#define RAISIN_SI_VD_WEARLEVELING_COUNT_ID 0xAD +#define RAISIN_SI_VD_E2E_DECTECTION_COUNT_ID 0xB8 +#define RAISIN_SI_VD_PCIE_CRC_ERR_COUNT_ID 0xC7 +#define RAISIN_SI_VD_TIMED_WORKLOAD_MEDIA_WEAR_ID 0xE2 +#define RAISIN_SI_VD_TIMED_WORKLOAD_HOST_READ_ID 0xE3 +#define RAISIN_SI_VD_TIMED_WORKLOAD_TIMER_ID 0xE4 +#define RAISIN_SI_VD_THERMAL_THROTTLE_STATUS_ID 0xEA +#define RAISIN_SI_VD_RETRY_BUFF_OVERFLOW_COUNT_ID 0xF0 +#define RAISIN_SI_VD_PLL_LOCK_LOSS_COUNT_ID 0xF3 +#define RAISIN_SI_VD_TOTAL_WRITE_ID 0xF4 +#define RAISIN_SI_VD_HOST_WRITE_ID 0xF5 +#define RAISIN_SI_VD_SYSTEM_AREA_LIFE_LEFT_ID 0xF6 +#define RAISIN_SI_VD_TOTAL_READ_ID 0xFA +#define RAISIN_SI_VD_TEMPT_SINCE_BORN_ID 0xE7 +#define RAISIN_SI_VD_POWER_CONSUMPTION_ID 0xE8 +#define RAISIN_SI_VD_TEMPT_SINCE_BOOTUP_ID 0xAF +#define RAISIN_SI_VD_POWER_LOSS_PROTECTION_ID 0xEC +#define RAISIN_SI_VD_READ_FAIL_ID 0xF2 +#define RAISIN_SI_VD_THERMAL_THROTTLE_TIME_ID 0xEB +#define RAISIN_SI_VD_FLASH_MEDIA_ERROR_ID 0xED + +/* Raisin Addtional smart internal ID */ +typedef enum +{ + /* smart attr following intel */ + RAISIN_SI_VD_PROGRAM_FAIL = 0, /* 0xAB */ + RAISIN_SI_VD_ERASE_FAIL = 1, /* 0xAC */ + RAISIN_SI_VD_WEARLEVELING_COUNT = 2,/* 0xAD */ + RAISIN_SI_VD_E2E_DECTECTION_COUNT = 3, /* 0xB8 */ + RAISIN_SI_VD_PCIE_CRC_ERR_COUNT = 4, /* 0xC7, 2 port data in one attribute */ + RAISIN_SI_VD_TIMED_WORKLOAD_MEDIA_WEAR = 5, /* 0xE2 , unknown definition*/ + RAISIN_SI_VD_TIMED_WORKLOAD_HOST_READ = 6, /* 0xE3 , unknown definition */ + RAISIN_SI_VD_TIMED_WORKLOAD_TIMER = 7, /* 0xE4 , unknown definition */ + RAISIN_SI_VD_THERMAL_THROTTLE_STATUS = 8, /* 0xEA */ + RAISIN_SI_VD_RETRY_BUFF_OVERFLOW_COUNT = 9, /* 0xF0, unknown definition*/ + RAISIN_SI_VD_PLL_LOCK_LOSS_COUNT = 10, /* 0xF3, unknown definition*/ + RAISIN_SI_VD_TOTAL_WRITE = 11, /* 0xF4, unit is 32MiB */ + RAISIN_SI_VD_HOST_WRITE = 12, /* 0xF5, unit is 32MiB */ + RAISIN_SI_VD_SYSTEM_AREA_LIFE_LEFT = 13, /* 0xF6, unknown definition*/ + RAISIN_SI_VD_TOTAL_READ = 14, /* 0xFA, unit is 32MiB */ + + /* smart attr self defined */ + RAISIN_SI_VD_TEMPT_SINCE_BORN = 15, /* 0xE7 */ + RAISIN_SI_VD_POWER_CONSUMPTION = 16, /* 0xE8 */ + RAISIN_SI_VD_TEMPT_SINCE_BOOTUP = 17, /* 0xAF */ + RAISIN_SI_VD_POWER_LOSS_PROTECTION = 18, /* 0xEC */ + RAISIN_SI_VD_READ_FAIL = 19, /* 0xF2 */ + RAISIN_SI_VD_THERMAL_THROTTLE_TIME = 20, /* 0xEB */ + RAISIN_SI_VD_FLASH_MEDIA_ERROR = 21, /* 0xED */ + RAISIN_SI_VD_SMART_INFO_ITEMS_MAX, +} RAISIN_si_vendor_smart_item_e; + +// Memblaze Format & old format +enum { + /*0*/TOTAL_WRITE = 0, + /*1*/TOTAL_READ, + /*2*/THERMAL_THROTTLE, + /*3*/TEMPT_SINCE_RESET, + /*4*/POWER_CONSUMPTION, + /*5*/TEMPT_SINCE_BOOTUP, + /*6*/POWER_LOSS_PROTECTION, + /*7*/WEARLEVELING_COUNT, + /*8*/HOST_WRITE, + /*9*/THERMAL_THROTTLE_CNT, + /*10*/CORRECT_PCIE_PORT0, + /*11*/CORRECT_PCIE_PORT1, + /*12*/REBUILD_FAIL, + /*13*/ERASE_FAIL, + /*14*/PROGRAM_FAIL, + /*15*/READ_FAIL, + /*16*/NR_SMART_ITEMS = RAISIN_SI_VD_SMART_INFO_ITEMS_MAX, +}; + +// Memblaze Format & old format +#pragma pack(push, 1) +struct nvme_memblaze_smart_log_item { + __u8 id[3]; + union { + __u8 __nmval[2]; + __le16 nmval; + }; + union { + __u8 rawval[6]; + struct temperature { + __le16 max; + __le16 min; + __le16 curr; + } temperature; + struct power { + __le16 max; + __le16 min; + __le16 curr; + } power; + struct thermal_throttle_mb { + __u8 on; + __u32 count; + } thermal_throttle; + struct temperature_p { + __le16 max; + __le16 min; + } temperature_p; + struct power_loss_protection { + __u8 curr; + } power_loss_protection; + struct wearleveling_count { + __le16 min; + __le16 max; + __le16 avg; + } wearleveling_count; + struct thermal_throttle_cnt { + __u8 active; + __le32 cnt; + } thermal_throttle_cnt; + }; + __u8 resv; +}; +#pragma pack(pop) + +struct nvme_memblaze_smart_log { + struct nvme_memblaze_smart_log_item items[NR_SMART_ITEMS]; + u8 resv[SMART_INFO_OLD_SIZE - sizeof(struct nvme_memblaze_smart_log_item) * NR_SMART_ITEMS]; +}; + +// Intel Format & new format +struct nvme_p4_smart_log_item +{ + /* Item identifier */ + u8 id[ID_SIZE]; + /* Normalized value or percentage. In the range from 0 to 100. */ + u8 nmVal[NM_SIZE]; + /* raw value */ + u8 rawVal[RAW_SIZE]; +}; + +struct nvme_p4_smart_log +{ + struct nvme_p4_smart_log_item itemArr[NR_SMART_ITEMS]; + + /** + * change 512 to 4096. + * because micron's getlogpage request,the size of many commands have changed to 4k. + * request size > user malloc size,casuing parameters that are closed in momery are dirty. + */ + u8 resv[SMART_INFO_NEW_SIZE - sizeof(struct nvme_p4_smart_log_item) * NR_SMART_ITEMS]; +}; + +#endif // __MEMBLAZE_UTILS_H__ + diff --git a/plugins/micron/micron-nvme.c b/plugins/micron/micron-nvme.c new file mode 100644 index 0000000..165fcf0 --- /dev/null +++ b/plugins/micron/micron-nvme.c @@ -0,0 +1,1180 @@ +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <time.h> +#include <string.h> +#include <libgen.h> +#include <sys/stat.h> +#include "nvme.h" +#include "nvme-print.h" +#include "nvme-ioctl.h" +#include <sys/ioctl.h> +#include <limits.h> + +#define CREATE_CMD +#include "micron-nvme.h" +#define min(x, y) ((x) > (y) ? (y) : (x)) +#define SensorCount 2 +#define C5_log_size (((452 + 16 * 1024) / 4) * 4096) +#define D0_log_size 256 +#define MaxLogChunk 16 * 1024 +#define CommonChunkSize 16 * 4096 + +typedef struct _LogPageHeader_t { + unsigned char numDwordsInLogPageHeaderLo; + unsigned char logPageHeaderFormatVersion; + unsigned char logPageId; + unsigned char numDwordsInLogPageHeaderHi; + unsigned int numValidDwordsInPayload; + unsigned int numDwordsInEntireLogPage; +} LogPageHeader_t; + +/* + * Useful Helper functions + */ + +static int micron_fw_commit(int fd, int select) +{ + struct nvme_admin_cmd cmd = { + .opcode = nvme_admin_activate_fw, + .cdw10 = 8, + .cdw12 = select, + }; + return ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd); + +} + +static int ZipAndRemoveDir(char *strDirName, char *strFileName) +{ + int err = 0; + char strBuffer[PATH_MAX]; + int nRet; + + sprintf(strBuffer, "zip -r \"%s\" \"%s\" >temp.txt 2>&1", strFileName, + strDirName); + + nRet = system(strBuffer); + + if (nRet < 0) { + printf("Unable to create zip package!\n"); + err = EINVAL; + goto exit_status; + } + + sprintf(strBuffer, "rm -f -R \"%s\" >temp.txt 2>&1", strDirName); + nRet = system(strBuffer); + if (nRet < 0) { + printf("Unable to remove temporary files!\n"); + err = EINVAL; + goto exit_status; + } + + exit_status: + err = system("rm -f temp.txt"); + return err; +} + +static int SetupDebugDataDirectories(char *strSN, char *strFilePath, + char *strMainDirName, char *strOSDirName, + char *strCtrlDirName) +{ + int err = 0; + char strAppend[250]; + struct stat st; + char *fileLocation = NULL; + char *fileName; + int length = 0; + int nIndex = 0; + char *strTemp = NULL; + struct stat dirStat; + int j; + int k = 0; + int i = 0; + + if (strchr(strFilePath, '/') != NULL) { + fileName = strrchr(strFilePath, '\\'); + if (fileName == NULL) { + fileName = strrchr(strFilePath, '/'); + } + + if (fileName != NULL) { + if (!strcmp(fileName, "/")) { + goto exit_status; + } + + while (strFilePath[nIndex] != '\0') { + if ('\\' == strFilePath[nIndex] && '\\' == strFilePath[nIndex + 1]) { + goto exit_status; + } + nIndex++; + } + + length = (int)strlen(strFilePath) - (int)strlen(fileName); + + if (fileName == strFilePath) { + length = 1; + } + + fileLocation = (char *)malloc(length + 1); + strncpy(fileLocation, strFilePath, length); + fileLocation[length] = '\0'; + + while (fileLocation[k] != '\0') { + if (fileLocation[k] == '\\') { + fileLocation[k] = '/'; + } + } + + length = (int)strlen(fileLocation); + + if (':' == fileLocation[length - 1]) { + strTemp = (char *)malloc(length + 2); + strcpy(strTemp, fileLocation); + strcat(strTemp, "/"); + free(fileLocation); + + length = (int)strlen(strTemp); + fileLocation = (char *)malloc(length + 1); + memcpy(fileLocation, strTemp, length + 1); + free(strTemp); + } + + if (stat(fileLocation, &st) != 0) { + free(fileLocation); + goto exit_status; + } + free(fileLocation); + } else { + goto exit_status; + } + } + + nIndex = 0; + for (i = 0; i < (int)strlen(strSN); i++) { + if (strSN[i] != ' ' && strSN[i] != '\n' && strSN[i] != '\t' && strSN[i] != '\r') { + strMainDirName[nIndex++] = strSN[i]; + } + } + strMainDirName[nIndex] = '\0'; + + j = 1; + while (stat(strMainDirName, &dirStat) == 0) { + strMainDirName[nIndex] = '\0'; + sprintf(strAppend, "-%d", j); + strcat(strMainDirName, strAppend); + j++; + } + + mkdir(strMainDirName, 0777); + + if (strOSDirName != NULL) { + sprintf(strOSDirName, "%s/%s", strMainDirName, "OS"); + mkdir(strOSDirName, 0777); + + } + if (strCtrlDirName != NULL) { + sprintf(strCtrlDirName, "%s/%s", strMainDirName, "Controller"); + mkdir(strCtrlDirName, 0777); + + } + + exit_status: + return err; +} + +static int GetLogPageSize(int nFD, unsigned char ucLogID, int *nLogSize) +{ + int err = 0; + struct nvme_admin_cmd cmd; + unsigned int uiXferDwords = 0; + unsigned char pTmpBuf[CommonChunkSize] = { 0 }; + LogPageHeader_t *pLogHeader = NULL; + + if (ucLogID == 0xC1 || ucLogID == 0xC2 || ucLogID == 0xC4) { + cmd.opcode = 0x02; + cmd.cdw10 = ucLogID; + uiXferDwords = (unsigned int)(CommonChunkSize / 4); + cmd.nsid = 0xFFFFFFFF; + cmd.cdw10 |= ((uiXferDwords - 1) & 0x0000FFFF) << 16; + cmd.data_len = CommonChunkSize; + cmd.addr = (__u64) (uintptr_t) & pTmpBuf; + err = nvme_submit_passthru(nFD, NVME_IOCTL_ADMIN_CMD, &cmd); + if (err == 0) { + pLogHeader = (LogPageHeader_t *) pTmpBuf; + LogPageHeader_t *pLogHeader1 = (LogPageHeader_t *) pLogHeader; + *nLogSize = (int)(pLogHeader1->numDwordsInEntireLogPage) * 4; + } else { + printf ("Getting size of log page : 0x%X failed with %d\n", ucLogID, err); + *nLogSize = 0; + } + } + return err; +} + +static int NVMEGetLogPage(int nFD, unsigned char ucLogID, unsigned char *pBuffer, int nBuffSize) +{ + int err = 0; + struct nvme_admin_cmd cmd = { 0 }; + unsigned int uiNumDwords = (unsigned int)nBuffSize / sizeof(unsigned int); + unsigned int uiMaxChunk = uiNumDwords; + unsigned int uiNumChunks = 1; + unsigned int uiXferDwords = 0; + unsigned long long ullBytesRead = 0; + unsigned char *pTempPtr = pBuffer; + unsigned char ucOpCode = 0x02; + + if (ullBytesRead == 0 && (ucLogID == 0xE6 || ucLogID == 0xE7)) { + uiMaxChunk = 4096; + } else if (uiMaxChunk > 16 * 1024) { + uiMaxChunk = 16 * 1024; + } + + uiNumChunks = uiNumDwords / uiMaxChunk; + if (uiNumDwords % uiMaxChunk > 0) { + uiNumChunks += 1; + } + + for (unsigned int i = 0; i < uiNumChunks; i++) { + memset(&cmd, 0, sizeof(cmd)); + uiXferDwords = uiMaxChunk; + if (i == uiNumChunks - 1 && uiNumDwords % uiMaxChunk > 0) { + uiXferDwords = uiNumDwords % uiMaxChunk; + } + + cmd.opcode = ucOpCode; + cmd.cdw10 |= ucLogID; + cmd.cdw10 |= ((uiXferDwords - 1) & 0x0000FFFF) << 16; + + if (ucLogID == 0x7) { + cmd.cdw10 |= 0x80; + } + if (ullBytesRead == 0 && (ucLogID == 0xE6 || ucLogID == 0xE7)) { + cmd.cdw11 = 1; + } + if (ullBytesRead > 0 && !(ucLogID == 0xE6 || ucLogID == 0xE7)) { + unsigned long long ullOffset = ullBytesRead; + cmd.cdw12 = ullOffset & 0xFFFFFFFF; + cmd.cdw13 = (ullOffset >> 32) & 0xFFFFFFFF; + } + + cmd.addr = (__u64) (uintptr_t) pTempPtr; + cmd.nsid = 0xFFFFFFFF; + cmd.data_len = uiXferDwords * 4; + err = nvme_submit_passthru(nFD, NVME_IOCTL_ADMIN_CMD, &cmd); + ullBytesRead += uiXferDwords * 4; + pTempPtr = pBuffer + ullBytesRead; + } + + return err; +} + +static int NVMEResetLog(int nFD, unsigned char ucLogID, int nBufferSize, + long long llMaxSize) +{ + unsigned int *pBuffer = NULL; + int err = 0; + + if ((pBuffer = (unsigned int *)calloc(1, nBufferSize)) == NULL) + return err; + + while (err == 0 && llMaxSize > 0) { + err = NVMEGetLogPage(nFD, ucLogID, (unsigned char *)pBuffer, nBufferSize); + if (err) + return err; + + if (pBuffer[0] == 0xdeadbeef) + break; + + llMaxSize = llMaxSize - nBufferSize; + } + + free(pBuffer); + return err; +} + +static int GetCommonLogPage(int nFD, unsigned char ucLogID, unsigned char **pBuffer, int nBuffSize) +{ + struct nvme_admin_cmd cmd; + int err = 0; + unsigned char pTmpBuf[CommonChunkSize] = { 0 }; + unsigned int uiMaxChunk = 0; + unsigned int uiXferDwords = 0; + int nBytesRead = 0; + unsigned char *pTempPtr = NULL; + + uiMaxChunk = CommonChunkSize / 4; + pTempPtr = (unsigned char *)malloc(nBuffSize); + if (!pTempPtr) { + goto exit_status; + } + memset(pTempPtr, 0, nBuffSize); + + while (nBytesRead < nBuffSize) { + int nBytesRemaining = nBuffSize - nBytesRead; + + memset(pTmpBuf, 0, CommonChunkSize); + + uiXferDwords = uiMaxChunk; + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0x02; + cmd.cdw10 |= ucLogID; + cmd.cdw10 |= ((uiXferDwords - 1) & 0x0000FFFF) << 16; + + if (nBytesRead > 0) { + unsigned long long ullOffset = (unsigned long long)nBytesRead; + cmd.cdw12 = ullOffset & 0xFFFFFFFF; + cmd.cdw13 = (ullOffset >> 32) & 0xFFFFFFFF; + } + cmd.nsid = 0xFFFFFFFF; + cmd.data_len = uiXferDwords * 4; + cmd.addr = (__u64) (uintptr_t) pTmpBuf; + + err = nvme_submit_passthru(nFD, NVME_IOCTL_ADMIN_CMD, &cmd); + + if (nBytesRemaining >= (int)(uiMaxChunk * 4)) { + memcpy(&pTempPtr[nBytesRead], pTmpBuf, uiMaxChunk * 4); + } else { + memcpy(&pTempPtr[nBytesRead], pTmpBuf, nBytesRemaining); + } + + nBytesRead += (int)uiXferDwords *4; + } + *pBuffer = pTempPtr; + + exit_status: + return err; +} + +/* + * Plugin Commands + */ + +static int micron_selective_download(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = + "This performs a selective firmware download, which allows the user to " + "select which firmware binary to update for 9200 devices. This requires a power cycle once the " + "update completes. The options available are: \n\n" + "OOB - This updates the OOB and main firmware\n" + "EEP - This updates the eeprom and main firmware\n" + "ALL - This updates the eeprom, OOB, and main firmware"; + const char *fw = "firmware file (required)"; + const char *select = "FW Select (e.g., --select=ALL)"; + int xfer = 4096; + void *fw_buf; + int fd, selectNo, fw_fd, fw_size, err, offset = 0; + struct stat sb; + + struct config { + char *fw; + char *select; + }; + + struct config cfg = { + .fw = "", + .select = "\0", + }; + + OPT_ARGS(opts) = { + OPT_STRING("fw", 'f', "FILE", &cfg.fw, fw), + OPT_STRING("select", 's', "flag", &cfg.select, select), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + + if (fd < 0) + return fd; + + if (strlen(cfg.select) != 3) { + fprintf(stderr, "Invalid select flag\n"); + err = EINVAL; + goto out; + } + + for (int i = 0; i < 3; i++) { + cfg.select[i] = toupper(cfg.select[i]); + } + + if (strncmp(cfg.select, "OOB", 3) == 0) { + selectNo = 18; + } else if (strncmp(cfg.select, "EEP", 3) == 0) { + selectNo = 10; + } else if (strncmp(cfg.select, "ALL", 3) == 0) { + selectNo = 26; + } else { + fprintf(stderr, "Invalid select flag\n"); + err = EINVAL; + goto out; + } + + fw_fd = open(cfg.fw, O_RDONLY); + if (fw_fd < 0) { + fprintf(stderr, "no firmware file provided\n"); + err = EINVAL; + goto out; + } + + err = fstat(fw_fd, &sb); + if (err < 0) { + perror("fstat"); + err = errno; + } + + fw_size = sb.st_size; + if (fw_size & 0x3) { + fprintf(stderr, "Invalid size:%d for f/w image\n", fw_size); + err = EINVAL; + goto out; + } + + if (posix_memalign(&fw_buf, getpagesize(), fw_size)) { + fprintf(stderr, "No memory for f/w size:%d\n", fw_size); + err = ENOMEM; + goto out; + } + + if (read(fw_fd, fw_buf, fw_size) != ((ssize_t) (fw_size))) + return EIO; + + while (fw_size > 0) { + xfer = min(xfer, fw_size); + + err = nvme_fw_download(fd, offset, xfer, fw_buf); + if (err < 0) { + perror("fw-download"); + goto out; + } else if (err != 0) { + fprintf(stderr, "NVME Admin command error:%s(%x)\n", + nvme_status_to_string(err), err); + goto out; + } + fw_buf += xfer; + fw_size -= xfer; + offset += xfer; + } + + err = micron_fw_commit(fd, selectNo); + + if (err == 0x10B || err == 0x20B) { + err = 0; + fprintf(stderr, + "Update successful! Please power cycle for changes to take effect\n"); + } + + out: + return err; +} + +static int micron_temp_stats(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + + struct nvme_smart_log smart_log; + unsigned int temperature = 0, i = 0, fd = 0, err = 0; + unsigned int tempSensors[SensorCount] = { 0 }; + const char *desc = "Retrieve Micron temperature info for the given device "; + + OPT_ARGS(opts) = { + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) { + printf("\nDevice not found \n");; + return -1; + } + + err = nvme_smart_log(fd, 0xffffffff, &smart_log); + if (!err) { + printf("Micron temperature information:\n"); + temperature = ((smart_log.temperature[1] << 8) | smart_log.temperature[0]); + temperature = temperature ? temperature - 273 : 0; + for (i = 0; i < SensorCount; i++) { + tempSensors[i] = le16_to_cpu(smart_log.temp_sensor[i]); + tempSensors[i] = tempSensors[i] ? tempSensors[i] - 273 : 0; + } + printf("%-10s : %u C\n", "Current Composite Temperature", temperature); + for (i = 0; i < SensorCount; i++) { + printf("%-10s%d : %u C\n", "Temperature Sensor #", i + 1, tempSensors[i]); + } + } + return err; +} + +static int micron_pcie_stats(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int err = 0, bus = 0, domain = 0, device = 0, function = 0; + char strTempFile[1024], strTempFile2[1024], command[1024]; + char *businfo = NULL; + char *devicename = NULL; + char tdevice[NAME_MAX] = { 0 }; + ssize_t sLinkSize = 0; + FILE *fp; + char correctable[8] = { 0 }; + char uncorrectable[8] = { 0 }; + char *res; + + if (argc != 2) { + printf("vs-pcie-stats: Invalid argument\n"); + printf("Usage: nvme micron vs-pcie-stats <device>\n\n"); + goto out; + } + if (strstr(argv[optind], "/dev/nvme") && strstr(argv[optind], "n1")) { + devicename = strrchr(argv[optind], '/'); + } else if (strstr(argv[optind], "/dev/nvme")) { + devicename = strrchr(argv[optind], '/'); + sprintf(tdevice, "%s%s", devicename, "n1"); + devicename = tdevice; + } else { + printf("Invalid device specified!\n"); + goto out; + } + sprintf(strTempFile, "/sys/block/%s/device", devicename); + sLinkSize = readlink(strTempFile, strTempFile2, 1024); + if (sLinkSize < 0) { + printf("Unable to read device\n"); + goto out; + } + if (strstr(strTempFile2, "../../nvme")) { + sprintf(strTempFile, "/sys/block/%s/device/device", devicename); + sLinkSize = readlink(strTempFile, strTempFile2, 1024); + if (sLinkSize < 0) { + printf("Unable to read device\n"); + goto out; + } + } + businfo = strrchr(strTempFile2, '/'); + sscanf(businfo, "/%x:%x:%x.%x", &domain, &bus, &device, &function); + sprintf(command, "setpci -s %x:%x.%x ECAP_AER+10.L", bus, device, + function); + fp = popen(command, "r"); + if (fp == NULL) { + printf("Unable to retrieve error count\n"); + goto out; + } + res = fgets(correctable, sizeof(correctable), fp); + if (res == NULL) { + printf("Unable to retrieve error count\n"); + goto out; + } + pclose(fp); + + sprintf(command, "setpci -s %x:%x.%x ECAP_AER+0x4.L", bus, device, + function); + fp = popen(command, "r"); + if (fp == NULL) { + printf("Unable to retrieve error count\n"); + goto out; + } + res = fgets(uncorrectable, sizeof(uncorrectable), fp); + if (res == NULL) { + printf("Unable to retrieve error count\n"); + goto out; + } + pclose(fp); + printf("PCIE Stats:\n"); + printf("Device correctable errors detected: %s\n", correctable); + printf("Device uncorrectable errors detected: %s\n", uncorrectable); + + out: + return err; +} + +static int micron_clear_pcie_correctable_errors(int argc, char **argv, + struct command *cmd, + struct plugin *plugin) +{ + int err = 0, bus = 0, domain = 0, device = 0, function = 0; + char strTempFile[1024], strTempFile2[1024], command[1024]; + char *businfo = NULL; + char *devicename = NULL; + char tdevice[PATH_MAX] = { 0 }; + ssize_t sLinkSize = 0; + FILE *fp; + char correctable[8] = { 0 }; + char *res; + + if (argc != 2) { + printf("clear-pcie-correctable-errors: Invalid argument\n"); + printf ("Usage: nvme micron clear-pcie-correctable-errors <device>\n\n"); + goto out; + } + if (strstr(argv[optind], "/dev/nvme") && strstr(argv[optind], "n1")) { + devicename = strrchr(argv[optind], '/'); + } else if (strstr(argv[optind], "/dev/nvme")) { + devicename = strrchr(argv[optind], '/'); + sprintf(tdevice, "%s%s", devicename, "n1"); + devicename = tdevice; + } else { + printf("Invalid device specified!\n"); + goto out; + } + err = snprintf(strTempFile, sizeof(strTempFile), + "/sys/block/%s/device", devicename); + if (err < 0) + goto out; + sLinkSize = readlink(strTempFile, strTempFile2, 1024); + if (sLinkSize < 0) { + printf("Unable to read device\n"); + goto out; + } + if (strstr(strTempFile2, "../../nvme")) { + err = snprintf(strTempFile, sizeof(strTempFile), + "/sys/block/%s/device/device", devicename); + if (err < 0) + goto out; + sLinkSize = readlink(strTempFile, strTempFile2, 1024); + if (sLinkSize < 0) { + printf("Unable to read device\n"); + goto out; + } + } + businfo = strrchr(strTempFile2, '/'); + sscanf(businfo, "/%x:%x:%x.%x", &domain, &bus, &device, &function); + sprintf(command, "setpci -s %x:%x.%x ECAP_AER+0x10.L=0xffffffff", bus, + device, function); + + fp = popen(command, "r"); + if (fp == NULL) { + printf("Unable to clear error count\n"); + goto out; + } + pclose(fp); + + sprintf(command, "setpci -s %x:%x.%x ECAP_AER+0x10.L", bus, device, + function); + fp = popen(command, "r"); + if (fp == NULL) { + printf("Unable to retrieve error count\n"); + goto out; + } + res = fgets(correctable, sizeof(correctable), fp); + if (res == NULL) { + printf("Unable to retrieve error count\n"); + goto out; + } + pclose(fp); + printf("Device correctable errors cleared!\n"); + printf("Device correctable errors detected: %s\n", correctable); + + out: + return err; +} + +static int micron_nand_stats(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Retrieve Micron NAND stats for the given device "; + unsigned int extSmartLog[64] = { 0 }; + struct nvme_id_ctrl ctrl; + int fd, err; + + OPT_ARGS(opts) = { + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) { + printf("\nDevice not found \n");; + return -1; + } + + err = nvme_identify_ctrl(fd, &ctrl); + if (err) + goto out; + + err = NVMEGetLogPage(fd, 0xD0, (unsigned char *)extSmartLog, D0_log_size); + if (err) + goto out; + + unsigned long long count = ((unsigned long long)extSmartLog[45] << 32) | extSmartLog[44]; + printf("%-40s : 0x%llx\n", "NAND Writes (Bytes Written)", count); + printf("%-40s : ", "Program Failure Count"); + + unsigned long long count_hi = ((unsigned long long)extSmartLog[39] << 32) | extSmartLog[38]; + unsigned long long count_lo = ((unsigned long long)extSmartLog[37] << 32) | extSmartLog[36]; + if (count_hi != 0) + printf("0x%llx%016llx", count_hi, count_lo); + else + printf("0x%llx\n", count_lo); + + count = ((unsigned long long)extSmartLog[25] << 32) | extSmartLog[24]; + printf("%-40s : 0x%llx\n", "Erase Failures", count); + printf("%-40s : 0x%x\n", "Bad Block Count", extSmartLog[3]); + + count = (unsigned long long)extSmartLog[3] - (count_lo + count); + printf("%-40s : 0x%llx\n", "NAND XOR/RAID Recovery Trigger Events", count); + printf("%-40s : 0x%x\n", "NSZE Change Supported", (ctrl.oacs >> 3) & 0x1); + printf("%-40s : 0x%x\n", "Number of NSZE Modifications", extSmartLog[1]); + out: + close(fd); + return err; +} + +typedef enum { M5410 = 0, M51AX, M51BX, UNKNOWN_MODEL } eDriveModel; + +static char *fvendorid1 = "/sys/class/nvme/nvme%d/device/vendor"; +static char *fvendorid2 = "/sys/class/misc/nvme%d/device/vendor"; +static char *fdeviceid1 = "/sys/class/nvme/nvme%d/device/device"; +static char *fdeviceid2 = "/sys/class/misc/nvme%d/device/device"; +static unsigned short vendor_id; +static unsigned short device_id; + +static int ReadSysFile(const char *file, unsigned short *id) +{ + int ret = 0; + char idstr[32] = { '\0' }; + int fd = open(file, O_RDONLY); + + if (fd > 0) { + ret = read(fd, idstr, sizeof(idstr)); + close(fd); + } + + if (fd < 0 || ret < 0) + perror(file); + else + *id = strtol(idstr, NULL, 16); + + return ret; +} + +static eDriveModel GetDriveModel(int idx) +{ + eDriveModel eModel = UNKNOWN_MODEL; + char path[512]; + + sprintf(path, fvendorid1, idx); + if (ReadSysFile(path, &vendor_id) < 0) { + sprintf(path, fvendorid2, idx); + ReadSysFile(path, &vendor_id); + } + sprintf(path, fdeviceid1, idx); + if (ReadSysFile(path, &device_id) < 0) { + sprintf(path, fdeviceid2, idx); + ReadSysFile(path, &device_id); + } + + if (vendor_id == 0x1344) { + switch (device_id) { + case 0x5410: + eModel = M5410; + break; + case 0x51A0: + case 0x51A1: + case 0x51A2: + eModel = M51AX; + break; + case 0x51B0: + case 0x51B1: + case 0x51B2: + eModel = M51BX; + break; + default: + break; + } + } + return eModel; +} + +static void GetDriveInfo(const char *strOSDirName, int nFD, + struct nvme_id_ctrl *ctrlp) +{ + FILE *fpOutFile = NULL; + char tempFile[256] = { 0 }; + char strBuffer[1024] = { 0 }; + char model[41] = { 0 }; + char serial[21] = { 0 }; + char fwrev[9] = { 0 }; + char *strPDir = strdup(strOSDirName); + char *strDest = dirname(strPDir); + + sprintf(tempFile, "%s/%s", strDest, "drive-info.txt"); + fpOutFile = fopen(tempFile, "w+"); + if (!fpOutFile) { + printf("Unable to create %s\n", tempFile); + free(strPDir); + return; + } + + strncpy(model, ctrlp->mn, 40); + strncpy(serial, ctrlp->sn, 20); + strncpy(fwrev, ctrlp->fr, 8); + + sprintf(strBuffer, + "********************\nDrive Info\n********************\n"); + + fprintf(fpOutFile, "%s", strBuffer); + sprintf(strBuffer, + "%-20s : /dev/nvme%d\n%-20s : %s\n%-20s : %-20s\n%-20s : %-20s\n", + "Device Name", nFD, + "Model No", (char *)model, + "Serial No", (char *)serial, "FW-Rev", (char *)fwrev); + + fprintf(fpOutFile, "%s", strBuffer); + + sprintf(strBuffer, + "\n********************\nPCI Info\n********************\n"); + + fprintf(fpOutFile, "%s", strBuffer); + + sprintf(strBuffer, + "%-22s : %04X\n%-22s : %04X\n", + "VendorId", vendor_id, "DeviceId", device_id); + fprintf(fpOutFile, "%s", strBuffer); + fclose(fpOutFile); + free(strPDir); +} + +static void GetTimestampInfo(const char *strOSDirName) +{ + char outstr[200]; + time_t t; + struct tm *tmp; + FILE *fpOutFile = NULL; + size_t num; + char tempFolder[256] = { 0 }; + char *strPDir; + char *strDest; + + t = time(NULL); + tmp = localtime(&t); + if (tmp == NULL) + return; + + num = strftime(outstr, sizeof(outstr), "Timestamp (UTC): %a, %d %b %Y %T %z", tmp); + if (num) { + strPDir = strdup(strOSDirName); + strDest = dirname(strPDir); + sprintf(tempFolder, "%s/%s", strDest, "timestamp_info.txt"); + fpOutFile = fopen(tempFolder, "wb"); + if (fwrite(outstr, 1, num, fpOutFile) != num) + printf("Unable to write to %s file!", tempFolder); + if (fpOutFile) + fclose(fpOutFile); + free(strPDir); + } +} + +static void GetCtrlIDDInfo(const char *strCtrlDirName, struct nvme_id_ctrl *ctrlp) +{ + char tempFolder[PATH_MAX] = { 0 }; + FILE *fpOutFile; + sprintf(tempFolder, "%s/%s", strCtrlDirName, + "nvme_controller_identify_data.bin"); + fpOutFile = fopen(tempFolder, "wb"); + if (fwrite(ctrlp, 1, sizeof(*ctrlp), fpOutFile) != sizeof(*ctrlp)) + printf("Unable to write controller data to %s file!", tempFolder); + if (fpOutFile) + fclose(fpOutFile); +} + +static void GetSmartlogData(int fd, const char *strCtrlDirName) +{ + char tempFolder[PATH_MAX] = { 0 }; + FILE *fpOutFile = NULL; + struct nvme_smart_log smart_log; + if (nvme_smart_log(fd, -1, &smart_log) == 0) { + sprintf(tempFolder, "%s/%s", strCtrlDirName, "smart_data.bin"); + fpOutFile = fopen(tempFolder, "wb"); + if (fwrite(&smart_log, 1, sizeof(smart_log), fpOutFile) != sizeof(smart_log)) + printf("Unable to write smart log data to %s file!", tempFolder); + if (fpOutFile) + fclose(fpOutFile); + } +} + +static void GetErrorlogData(int fd, int entries, const char *strCtrlDirName) +{ + char tempFolder[PATH_MAX] = { 0 }; + FILE *fpOutFile = NULL; + int logSize = entries * sizeof(struct nvme_error_log_page); + struct nvme_error_log_page *error_log = (struct nvme_error_log_page *)calloc(1, logSize); + + if (error_log == NULL) + return; + + if (nvme_error_log(fd, entries, error_log) == 0) { + sprintf(tempFolder, "%s/%s", strCtrlDirName, + "error_information_log.bin"); + fpOutFile = fopen(tempFolder, "wb"); + if (fwrite(error_log, 1, logSize, fpOutFile) != logSize) + printf("Unable to write error log to %s file!", tempFolder); + if (fpOutFile) + fclose(fpOutFile); + } + free(error_log); +} + +static void GetNSIDDInfo(int fd, const char *strCtrlDirName, int nsid) +{ + char tempFolder[256] = { 0 }; + char strFileName[PATH_MAX] = { 0 }; + FILE *fpOutFile = NULL; + struct nvme_id_ns ns; + if (nvme_identify_ns(fd, nsid, 0, &ns) == 0) { + sprintf(tempFolder, "identify_namespace_%d_data.bin.bin", nsid); + sprintf(strFileName, "%s/%s", strCtrlDirName, tempFolder); + fpOutFile = fopen(strFileName, "wb"); + if (fwrite(&ns, 1, sizeof(ns), fpOutFile) != sizeof(ns)) + printf("Unable to write controller data to %s file!", tempFolder); + if (fpOutFile) + fclose(fpOutFile); + } +} + +static void GetOSConfig(const char *strOSDirName) +{ + FILE *fpOSConfig = NULL; + char strBuffer[1024], strTemp[1024]; + char strFileName[PATH_MAX]; + int i; + + struct { + char *strcmdHeader; + char *strCommand; + } cmdArray[] = { + { (char *)"SYSTEM INFORMATION", (char *)"uname -a >> %s" }, + { (char *)"LINUX KERNEL MODULE INFORMATION", (char *)"lsmod >> %s" }, + { (char *)"LINUX SYSTEM MEMORY INFORMATION", (char *)"cat /proc/meminfo >> %s" }, + { (char *)"SYSTEM INTERRUPT INFORMATION", (char *)"cat /proc/interrupts >> %s" }, + { (char *)"CPU INFORMATION", (char *)"cat /proc/cpuinfo >> %s" }, + { (char *)"IO MEMORY MAP INFORMATION", (char *)"cat /proc/iomem >> %s" }, + { (char *)"MAJOR NUMBER AND DEVICE GROUP", (char *)"cat /proc/devices >> %s" }, + { (char *)"KERNEL DMESG", (char *)"dmesg >> %s" }, + { (char *)"/VAR/LOG/MESSAGES", (char *)"cat /var/log/messages >> %s" } + }; + + sprintf(strFileName, "%s/%s", strOSDirName, "os_config.txt"); + + for (i = 0; i < 7; i++) { + fpOSConfig = fopen(strFileName, "a+"); + fprintf(fpOSConfig, + "\n\n\n\n%s\n-----------------------------------------------\n", + cmdArray[i].strcmdHeader); + if (NULL != fpOSConfig) { + fclose(fpOSConfig); + fpOSConfig = NULL; + } + strcpy(strTemp, cmdArray[i].strCommand); + sprintf(strBuffer, strTemp, strFileName); + if (system(strBuffer)) + fprintf(stderr, "Failed to send \"%s\"\n", strBuffer); + } +} + +static int micron_internal_logs(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + + int err = 0; + int fd; + int ctrlIdx; + FILE *fpOutFile = NULL; + char strOSDirName[1024]; + char strCtrlDirName[1024]; + char strMainDirName[256]; + char tempFolder[PATH_MAX] = { 0 }; + unsigned int *puiIDDBuf; + unsigned int uiMask; + struct nvme_id_ctrl ctrl; + char sn[20] = { 0 }; + + struct { + unsigned char ucLogPage; + const char *strFileName; + int nLogSize; + int nMaxSize; + } aVendorLogs[32] = { + { 0xC1, "nvmelog_C1.bin", 0, 0 }, + { 0xC2, "nvmelog_C2.bin", 0, 0 }, + { 0xC4, "nvmelog_C4.bin", 0, 0 }, + { 0xC5, "nvmelog_C5.bin", C5_log_size, 0 }, + { 0xD0, "nvmelog_D0.bin", D0_log_size, 0 }, + { 0xE6, "nvmelog_E6.bin", 0, 0 }, + { 0xE7, "nvmelog_E7.bin", 0, 0 } + }, + aM51XXLogs[] = { + { 0xFB, "nvmelog_FB.bin", 4096, 0 }, /* this should be collected first for M51AX */ + { 0xF7, "nvmelog_F7.bin", 4096, 512 * 1024 }, + { 0xF8, "nvmelog_F8.bin", 4096, 512 * 1024 }, + { 0xF9, "nvmelog_F9.bin", 4096, 200 * 1024 * 1024 }, + { 0xFC, "nvmelog_FC.bin", 4096, 200 * 1024 * 1024 }, + { 0xFD, "nvmelog_FD.bin", 4096, 80 * 1024 * 1024 } + }, + aM51AXLogs[] = { + { 0xD0, "nvmelog_D0.bin", 512, 0 }, + { 0xCA, "nvmelog_CA.bin", 512, 0 }, + { 0xFA, "nvmelog_FA.bin", 4096, 15232 }, + { 0xFE, "nvmelog_FE.bin", 4096, 512 * 1024 }, + { 0xFF, "nvmelog_FF.bin", 4096, 162 * 1024 }, + { 0x04, "changed_namespace_log.bin", 4096, 0 }, + { 0x05, "command_effects_log.bin", 4096, 0 }, + { 0x06, "drive_self_test.bin", 4096, 0 } + }, + aM51BXLogs[] = { + { 0xFA, "nvmelog_FA.bin", 4096, 16376 }, + { 0xFE, "nvmelog_FE.bin", 4096, 256 * 1024 }, + { 0xFF, "nvmelog_FF.bin", 4096, 64 * 1024 }, + { 0xCA, "nvmelog_CA.bin", 512, 1024 } + }; + + eDriveModel eModel; + + const char *desc = "This retrieves the micron debug log package"; + const char *package = "Log output package name (required)"; + unsigned char *dataBuffer = NULL; + int bSize = 0; + int maxSize = 0; + + struct config { + char *package; + }; + + struct config cfg = { + .package = "" + }; + + OPT_ARGS(opts) = { + OPT_STRING("package", 'p', "FILE", &cfg.package, package), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + + if (strlen(cfg.package) == 0) { + printf ("You must specify an output name for the log package. ie --p=logfiles.zip\n"); + goto out; + } + + if (fd < 0) + goto out; + + /* pull log details based on the model name */ + sscanf(argv[optind], "/dev/nvme%d", &ctrlIdx); + if ((eModel = GetDriveModel(ctrlIdx)) == UNKNOWN_MODEL) { + printf ("Unsupported drive model for vs-internal-log collection\n"); + close(fd); + goto out; + } + + printf("Preparing log package. This will take a few seconds...\n"); + err = nvme_identify_ctrl(fd, &ctrl); + if (err) + goto out; + + // trim spaces out of serial number string */ + int i, j = 0; + for (i = 0; i < sizeof(ctrl.sn); i++) { + if (isblank(ctrl.sn[i])) + continue; + sn[j++] = ctrl.sn[i]; + } + sn[j] = '\0'; + strcpy(ctrl.sn, sn); + + SetupDebugDataDirectories(ctrl.sn, cfg.package, strMainDirName, strOSDirName, strCtrlDirName); + + GetTimestampInfo(strOSDirName); + GetCtrlIDDInfo(strCtrlDirName, &ctrl); + GetOSConfig(strOSDirName); + GetDriveInfo(strOSDirName, ctrlIdx, &ctrl); + + for (int i = 1; i <= ctrl.nn; i++) + GetNSIDDInfo(fd, strCtrlDirName, i); + + GetSmartlogData(fd, strCtrlDirName); + GetErrorlogData(fd, ctrl.elpe, strCtrlDirName); + + if (eModel != M5410) { + memcpy(aVendorLogs, aM51XXLogs, sizeof(aM51XXLogs)); + if (eModel == M51AX) + memcpy((char *)aVendorLogs + sizeof(aM51XXLogs), aM51AXLogs, sizeof(aM51AXLogs)); + else + memcpy((char *)aVendorLogs + sizeof(aM51XXLogs), aM51BXLogs, sizeof(aM51BXLogs)); + } + + for (int i = 0; i < (int)(sizeof(aVendorLogs) / sizeof(aVendorLogs[0])) && aVendorLogs[i].ucLogPage != 0; i++) { + err = -1; + switch (aVendorLogs[i].ucLogPage) { + case 0xC1: + case 0xC2: + case 0xC4: + err = GetLogPageSize(fd, aVendorLogs[i].ucLogPage, &bSize); + if (err == 0 && bSize > 0) + err = GetCommonLogPage(fd, aVendorLogs[i].ucLogPage, &dataBuffer, bSize); + break; + + case 0xE6: + case 0xE7: + puiIDDBuf = (unsigned int *)&ctrl; + uiMask = puiIDDBuf[1015]; + if (uiMask == 0 || (aVendorLogs[i].ucLogPage == 0xE6 && uiMask == 2) || (aVendorLogs[i].ucLogPage == 0xE7 + && uiMask == 1)) { + bSize = 0; + } else { + bSize = (int)puiIDDBuf[1015]; + if (bSize % (16 * 1024)) { + bSize += (16 * 1024) - (bSize % (16 * 1024)); + } + } + if (bSize != 0) { + dataBuffer = (unsigned char *)malloc(bSize); + memset(dataBuffer, 0, bSize); + err = NVMEGetLogPage(fd, aVendorLogs[i].ucLogPage, dataBuffer, bSize); + } + break; + + case 0xF7: + case 0xF9: + case 0xFC: + case 0xFD: + if (eModel == M51BX) + (void)NVMEResetLog(fd, aVendorLogs[i].ucLogPage, aVendorLogs[i].nLogSize, aVendorLogs[i].nMaxSize); + default: + bSize = aVendorLogs[i].nLogSize; + dataBuffer = (unsigned char *)malloc(bSize); + memset(dataBuffer, 0, bSize); + err = NVMEGetLogPage(fd, aVendorLogs[i].ucLogPage, dataBuffer, bSize); + maxSize = aVendorLogs[i].nMaxSize - bSize; + while (err == 0 && maxSize > 0 && ((unsigned int *)dataBuffer)[0] != 0xdeadbeef) { + sprintf(tempFolder, "%s/%s", strCtrlDirName, + aVendorLogs[i].strFileName); + fpOutFile = fopen(tempFolder, "ab+"); + if (fwrite(dataBuffer, 1, bSize, fpOutFile) != bSize) { + printf ("Unable to write log to file %s\n!", aVendorLogs[i].strFileName); + } + if (fpOutFile) + fclose(fpOutFile); + err = NVMEGetLogPage(fd, aVendorLogs[i].ucLogPage, dataBuffer, bSize); + if (err || (((unsigned int *)dataBuffer)[0] == 0xdeadbeef)) + break; + maxSize -= bSize; + } + break; + } + + if (err == 0 && dataBuffer != NULL && ((unsigned int *)dataBuffer)[0] != 0xdeadbeef) { + sprintf(tempFolder, "%s/%s", strCtrlDirName, + aVendorLogs[i].strFileName); + fpOutFile = fopen(tempFolder, "ab+"); + if (fwrite(dataBuffer, 1, bSize, fpOutFile) != bSize) { + printf("Unable to write log to file %s\n!", aVendorLogs[i].strFileName); + } + if (fpOutFile) + fclose(fpOutFile); + } + + if (dataBuffer != NULL) { + free(dataBuffer); + dataBuffer = NULL; + } + } + + ZipAndRemoveDir(strMainDirName, cfg.package); + out: + return err; +} diff --git a/plugins/micron/micron-nvme.h b/plugins/micron/micron-nvme.h new file mode 100644 index 0000000..add36e4 --- /dev/null +++ b/plugins/micron/micron-nvme.h @@ -0,0 +1,21 @@ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/micron/micron-nvme + +#if !defined(MICRON_NVME) || defined(CMD_HEADER_MULTI_READ) +#define MICRON_NVME + +#include "cmd.h" + +PLUGIN(NAME("micron", "Micron vendor specific extensions"), + COMMAND_LIST(ENTRY("select-download", "Selective Firmware Download", micron_selective_download) + ENTRY("vs-temperature-stats", "Retrieve Micron temperature statistics ", micron_temp_stats) + ENTRY("vs-pcie-stats", "Retrieve Micron PCIe error stats", micron_pcie_stats) + ENTRY("clear-pcie-correctable-errors", "Clear correctable PCIe errors", micron_clear_pcie_correctable_errors) + ENTRY("vs-internal-log", "Retrieve Micron logs", micron_internal_logs) + ENTRY("vs-nand-stats", "Retrieve NAND Stats", micron_nand_stats) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/netapp/netapp-nvme.c b/plugins/netapp/netapp-nvme.c new file mode 100644 index 0000000..a942f57 --- /dev/null +++ b/plugins/netapp/netapp-nvme.c @@ -0,0 +1,639 @@ +/* +* Copyright (c) 2018 NetApp, Inc. +* +* 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. +* +*/ + +#include <stdio.h> +#include <dirent.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/ioctl.h> + +#include "nvme.h" +#include "nvme-ioctl.h" +#include "json.h" + +#include "suffix.h" + +#define CREATE_CMD +#include "netapp-nvme.h" + +#define ONTAP_C2_LOG_ID 0xC2 +#define ONTAP_C2_LOG_SIZE 4096 +#define ONTAP_LABEL_LEN 260 +#define ONTAP_NS_PATHLEN 525 + +enum { + NNORMAL, + NJSON, + NCOLUMN, +}; + +enum { + ONTAP_C2_LOG_SUPPORTED_LSP = 0x0, + ONTAP_C2_LOG_NSINFO_LSP = 0x1, +}; + +enum { + ONTAP_VSERVER_TLV = 0x11, + ONTAP_VOLUME_TLV = 0x12, + ONTAP_NS_TLV = 0x13, +}; + +static const char *dev_path = "/dev/"; + +struct smdevice_info { + int nsid; + struct nvme_id_ctrl ctrl; + struct nvme_id_ns ns; + char dev[265]; +}; + +struct ontapdevice_info { + int nsid; + struct nvme_id_ctrl ctrl; + struct nvme_id_ns ns; + char nsdesc[4096]; + unsigned char log_data[ONTAP_C2_LOG_SIZE]; + char dev[265]; +}; + +#define ARRAY_LABEL_LEN 60 +#define VOLUME_LABEL_LEN 60 + +/* + * Format of the string isn't tightly controlled yet. For now, squash UCS-2 into + * ASCII. dst buffer must be at least count + 1 bytes long + */ +static void netapp_convert_string(char *dst, char *src, unsigned int count) +{ + int i; + + if (!dst || !src || !count) + return; + + memset(dst, 0, count + 1); + for (i = 0; i < count; i++) + dst[i] = src[i * 2 + 1]; + /* the json routines won't accept empty strings */ + if (strlen(dst) == 0 && count) + dst[0] = ' '; +} + +static void netapp_nguid_to_str(char *str, __u8 *nguid) +{ + int i; + + memset(str, 0, 33); + for (i = 0; i < 16; i++) + str += sprintf(str, "%02x", nguid[i]); +} + +static void netapp_get_ns_size(char *size, long long *lba, + struct nvme_id_ns *ns) +{ + *lba = 1 << ns->lbaf[(ns->flbas & 0x0F)].ds; + double nsze = le64_to_cpu(ns->nsze) * (*lba); + const char *s_suffix = suffix_si_get(&nsze); + + sprintf(size, "%.2f%sB", nsze, s_suffix); +} + +static void netapp_uuid_to_str(char *str, void *data) +{ +#ifdef LIBUUID + uuid_t uuid; + struct nvme_ns_id_desc *desc = data; + + memcpy(uuid, data + sizeof(*desc), 16); + uuid_unparse_lower(uuid, str); +#endif +} + +static void ontap_labels_to_str(char *dst, char *src, int count) +{ + int i; + + memset(dst, 0, ONTAP_LABEL_LEN); + for (i = 0; i < count; i++) { + if (src[i] >= '!' && src[i] <= '~') + dst[i] = src[i]; + else + break; + } + dst[i] = '\0'; +} + +static void netapp_get_ontap_labels(char *vsname, char *nspath, + unsigned char *log_data) +{ + int lsp, tlv, label_len; + char *vserver_name, *volume_name, *namespace_name; + char vol_name[ONTAP_LABEL_LEN], ns_name[ONTAP_LABEL_LEN]; + const char *ontap_vol = "/vol/"; + int i, j; + + /* get the lsp */ + lsp = (*(__u8 *)&log_data[16]) & 0x0F; + if (lsp != ONTAP_C2_LOG_NSINFO_LSP) + /* lsp not related to nsinfo */ + return; + + /* get the vserver tlv and name */ + tlv = *(__u8 *)&log_data[32]; + if (tlv == ONTAP_VSERVER_TLV) { + label_len = (*(__u16 *)&log_data[34]) * 4; + vserver_name = (char *)&log_data[36]; + ontap_labels_to_str(vsname, vserver_name, label_len); + } else { + /* not the expected vserver tlv */ + fprintf(stderr, "Unable to fetch ONTAP vserver name\n"); + return; + } + + i = 36 + label_len; + j = i + 2; + /* get the volume tlv and name */ + tlv = *(__u8 *)&log_data[i]; + if (tlv == ONTAP_VOLUME_TLV) { + label_len = (*(__u16 *)&log_data[j]) * 4; + volume_name = (char *)&log_data[j + 2]; + ontap_labels_to_str(vol_name, volume_name, label_len); + } else { + /* not the expected volume tlv */ + fprintf(stderr, "Unable to fetch ONTAP volume name\n"); + return; + } + + i += 4 + label_len; + j += 4 + label_len; + /* get the namespace tlv and name */ + tlv = *(__u8 *)&log_data[i]; + if (tlv == ONTAP_NS_TLV) { + label_len = (*(__u16 *)&log_data[j]) * 4; + namespace_name = (char *)&log_data[j + 2]; + ontap_labels_to_str(ns_name, namespace_name, label_len); + } else { + /* not the expected namespace tlv */ + fprintf(stderr, "Unable to fetch ONTAP namespace name\n"); + return; + } + + snprintf(nspath, ONTAP_NS_PATHLEN, "%s%s%s%s", ontap_vol, + vol_name, "/", ns_name); +} + +static void netapp_smdevice_json(struct json_array *devices, char *devname, + char *arrayname, char *volname, int nsid, char *nguid, + char *ctrl, char *astate, char *size, long long lba, + long long nsze) +{ + struct json_object *device_attrs; + + device_attrs = json_create_object(); + json_object_add_value_string(device_attrs, "Device", devname); + json_object_add_value_string(device_attrs, "Array_Name", arrayname); + json_object_add_value_string(device_attrs, "Volume_Name", volname); + json_object_add_value_int(device_attrs, "NSID", nsid); + json_object_add_value_string(device_attrs, "Volume_ID", nguid); + json_object_add_value_string(device_attrs, "Controller", ctrl); + json_object_add_value_string(device_attrs, "Access_State", astate); + json_object_add_value_string(device_attrs, "Size", size); + json_object_add_value_int(device_attrs, "LBA_Data_Size", lba); + json_object_add_value_int(device_attrs, "Namespace_Size", nsze); + + json_array_add_value_object(devices, device_attrs); +} + +static void netapp_ontapdevice_json(struct json_array *devices, char *devname, + char *vsname, char *nspath, int nsid, char *uuid, + char *size, long long lba, long long nsze) +{ + struct json_object *device_attrs; + + device_attrs = json_create_object(); + json_object_add_value_string(device_attrs, "Device", devname); + json_object_add_value_string(device_attrs, "Vserver", vsname); + json_object_add_value_string(device_attrs, "Namespace_Path", nspath); + json_object_add_value_int(device_attrs, "NSID", nsid); + json_object_add_value_string(device_attrs, "UUID", uuid); + json_object_add_value_string(device_attrs, "Size", size); + json_object_add_value_int(device_attrs, "LBA_Data_Size", lba); + json_object_add_value_int(device_attrs, "Namespace_Size", nsze); + + json_array_add_value_object(devices, device_attrs); +} + +static void netapp_smdevices_print(struct smdevice_info *devices, int count, int format) +{ + struct json_object *root = NULL; + struct json_array *json_devices = NULL; + int i, slta; + char array_label[ARRAY_LABEL_LEN / 2 + 1]; + char volume_label[VOLUME_LABEL_LEN / 2 + 1]; + char nguid_str[33]; + char basestr[] = "%s, Array Name %s, Volume Name %s, NSID %d, " + "Volume ID %s, Controller %c, Access State %s, %s\n"; + char columnstr[] = "%-16s %-30s %-30s %4d %32s %c %-12s %9s\n"; + char *formatstr = basestr; /* default to "normal" output format */ + + if (format == NCOLUMN) { + /* for column output, change output string and print column headers */ + formatstr = columnstr; + printf("%-16s %-30s %-30s %-4s %-32s %-4s %-12s %-9s\n", + "Device", "Array Name", "Volume Name", "NSID", + "Volume ID", "Ctrl", "Access State", " Size"); + printf("%-16s %-30s %-30s %-4s %-32s %-4s %-12s %-9s\n", + "----------------", "------------------------------", + "------------------------------", "----", + "--------------------------------", "----", + "------------", "---------"); + } + else if (format == NJSON) { + /* prepare for json output */ + root = json_create_object(); + json_devices = json_create_array(); + } + + for (i = 0; i < count; i++) { + long long int lba = 1 << devices[i].ns.lbaf[(devices[i].ns.flbas & 0x0F)].ds; + double nsze = le64_to_cpu(devices[i].ns.nsze) * lba; + const char *s_suffix = suffix_si_get(&nsze); + char size[128]; + + sprintf(size, "%.2f%sB", nsze, s_suffix); + netapp_convert_string(array_label, (char *)&devices[i].ctrl.vs[20], + ARRAY_LABEL_LEN / 2); + slta = devices[i].ctrl.vs[0] & 0x1; + netapp_convert_string(volume_label, (char *)devices[i].ns.vs, + VOLUME_LABEL_LEN / 2); + netapp_nguid_to_str(nguid_str, devices[i].ns.nguid); + if (format == NJSON) + netapp_smdevice_json(json_devices, devices[i].dev, + array_label, volume_label, devices[i].nsid, + nguid_str, slta ? "A" : "B", "unknown", size, + lba, le64_to_cpu(devices[i].ns.nsze)); + else + printf(formatstr, devices[i].dev, array_label, + volume_label, devices[i].nsid, nguid_str, + slta ? 'A' : 'B', "unknown", size); + } + + if (format == NJSON) { + /* complete the json output */ + json_object_add_value_array(root, "SMdevices", json_devices); + json_print_object(root, NULL); + } +} + +static void netapp_ontapdevices_print(struct ontapdevice_info *devices, + int count, int format) +{ + struct json_object *root = NULL; + struct json_array *json_devices = NULL; + char vsname[ONTAP_LABEL_LEN] = " "; + char nspath[ONTAP_NS_PATHLEN] = " "; + long long lba; + char size[128]; + char uuid_str[37] = " "; + int i; + + char basestr[] = "%s, Vserver %s, Namespace Path %s, NSID %d, UUID %s, %s\n"; + char columnstr[] = "%-16s %-25s %-50s %-4d %-38s %-9s\n"; + + /* default to 'normal' output format */ + char *formatstr = basestr; + + if (format == NCOLUMN) { + /* change output string and print column headers */ + formatstr = columnstr; + printf("%-16s %-25s %-50s %-4s %-38s %-9s\n", + "Device", "Vserver", "Namespace Path", + "NSID", "UUID", "Size"); + printf("%-16s %-25s %-50s %-4s %-38s %-9s\n", + "----------------", "-------------------------", + "--------------------------------------------------", + "----", "--------------------------------------", + "---------"); + } else if (format == NJSON) { + /* prepare for json output */ + root = json_create_object(); + json_devices = json_create_array(); + } + + for (i = 0; i < count; i++) { + + netapp_get_ns_size(size, &lba, &devices[i].ns); + netapp_uuid_to_str(uuid_str, devices[i].nsdesc); + netapp_get_ontap_labels(vsname, nspath, devices[i].log_data); + + if (format == NJSON) { + netapp_ontapdevice_json(json_devices, devices[i].dev, + vsname, nspath, devices[i].nsid, + uuid_str, size, lba, + le64_to_cpu(devices[i].ns.nsze)); + } else + printf(formatstr, devices[i].dev, vsname, nspath, + devices[i].nsid, uuid_str, size); + } + + if (format == NJSON) { + /* complete the json output */ + json_object_add_value_array(root, "ONTAPdevices", json_devices); + json_print_object(root, NULL); + } +} + +static int nvme_get_ontap_c2_log(int fd, __u32 nsid, void *buf, __u32 buflen) +{ + struct nvme_admin_cmd get_log; + int err; + + memset(buf, 0, buflen); + memset(&get_log, 0, sizeof(struct nvme_admin_cmd)); + + get_log.opcode = nvme_admin_get_log_page; + get_log.nsid = nsid; + get_log.addr = (__u64)(uintptr_t)buf; + get_log.data_len = buflen; + + __u32 numd = (get_log.data_len >> 2) - 1; + __u32 numdu = numd >> 16; + __u32 numdl = numd & 0xFFFF; + + get_log.cdw10 = ONTAP_C2_LOG_ID | (numdl << 16); + get_log.cdw10 |= ONTAP_C2_LOG_NSINFO_LSP << 8; + get_log.cdw11 = numdu; + + err = nvme_submit_admin_passthru(fd, &get_log); + if (err) { + fprintf(stderr, "ioctl error %0x\n", err); + return 1; + } + + return 0; +} + +static int netapp_smdevices_get_info(int fd, struct smdevice_info *item, + const char *dev) +{ + int err; + + err = nvme_identify_ctrl(fd, &item->ctrl); + if (err) { + fprintf(stderr, "Identify Controller failed to %s (%s)\n", dev, + strerror(err)); + return 0; + } + + if (strncmp("NetApp E-Series", item->ctrl.mn, 15) != 0) + return 0; /* not the right model of controller */ + + item->nsid = nvme_get_nsid(fd); + err = nvme_identify_ns(fd, item->nsid, 0, &item->ns); + if (err) { + fprintf(stderr, "Unable to identify namespace for %s (%s)\n", + dev, strerror(err)); + return 0; + } + strncpy(item->dev, dev, sizeof(item->dev)); + + return 1; +} + +static int netapp_ontapdevices_get_info(int fd, struct ontapdevice_info *item, + const char *dev) +{ + int err; + + err = nvme_identify_ctrl(fd, &item->ctrl); + if (err) { + fprintf(stderr, "Identify Controller failed to %s (%s)\n", + dev, strerror(err)); + return 0; + } + + if (strncmp("NetApp ONTAP Controller", item->ctrl.mn, 23) != 0) + /* not the right controller model */ + return 0; + + item->nsid = nvme_get_nsid(fd); + + err = nvme_identify_ns(fd, item->nsid, 0, &item->ns); + if (err) { + fprintf(stderr, "Unable to identify namespace for %s (%s)\n", + dev, strerror(err)); + return 0; + } + + err = nvme_identify_ns_descs(fd, item->nsid, item->nsdesc); + if (err) { + fprintf(stderr, "Unable to identify namespace descriptor for %s (%s)\n", + dev, strerror(err)); + return 0; + } + + err = nvme_get_ontap_c2_log(fd, item->nsid, item->log_data, ONTAP_C2_LOG_SIZE); + if (err) { + fprintf(stderr, "Unable to get log page data for %s (%s)\n", + dev, strerror(err)); + return 0; + } + + strncpy(item->dev, dev, sizeof(item->dev)); + + return 1; +} + +static int netapp_nvme_filter(const struct dirent *d) +{ + char path[264]; + struct stat bd; + int ctrl, ns, partition; + + if (d->d_name[0] == '.') + return 0; + + if (strstr(d->d_name, "nvme")) { + snprintf(path, sizeof(path), "%s%s", dev_path, d->d_name); + if (stat(path, &bd)) + return 0; + if (sscanf(d->d_name, "nvme%dn%d", &ctrl, &ns) != 2) + return 0; + if (sscanf(d->d_name, "nvme%dn%dp%d", &ctrl, &ns, &partition) == 3) + return 0; + return 1; + } + return 0; +} + +static int netapp_output_format(char *format) +{ + if (!format) + return -EINVAL; + if (!strcmp(format, "normal")) + return NNORMAL; + if (!strcmp(format, "json")) + return NJSON; + if (!strcmp(format, "column")) + return NCOLUMN; + return -EINVAL; +} + +/* handler for 'nvme netapp smdevices' */ +static int netapp_smdevices(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Display information about E-Series volumes."; + + struct dirent **devices; + int num, i, fd, ret, fmt; + struct smdevice_info *smdevices; + char path[264]; + int num_smdevices = 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|column"), + OPT_END() + }; + + ret = argconfig_parse(argc, argv, desc, opts); + if (ret < 0) + return ret; + + fmt = netapp_output_format(cfg.output_format); + if (fmt != NNORMAL && fmt != NCOLUMN && fmt != NJSON) { + fprintf(stderr, "Unrecognized output format: %s\n", cfg.output_format); + return -EINVAL; + } + + num = scandir(dev_path, &devices, netapp_nvme_filter, alphasort); + if (num <= 0) { + fprintf(stderr, "No NVMe devices detected.\n"); + return num; + } + + smdevices = calloc(num, sizeof(*smdevices)); + if (!smdevices) { + fprintf(stderr, "Unable to allocate memory for devices.\n"); + return ENOMEM; + } + + for (i = 0; i < num; i++) { + snprintf(path, sizeof(path), "%s%s", dev_path, + devices[i]->d_name); + fd = open(path, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Unable to open %s: %s\n", path, + strerror(errno)); + continue; + } + + num_smdevices += netapp_smdevices_get_info(fd, + &smdevices[num_smdevices], path); + close(fd); + } + + if (num_smdevices) + netapp_smdevices_print(smdevices, num_smdevices, fmt); + + for (i = 0; i < num; i++) + free(devices[i]); + free(devices); + free(smdevices); + return 0; +} + +/* handler for 'nvme netapp ontapdevices' */ +static int netapp_ontapdevices(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Display information about ONTAP devices."; + struct dirent **devices; + int num, i, fd, ret, fmt; + struct ontapdevice_info *ontapdevices; + char path[264]; + int num_ontapdevices = 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|column"), + OPT_END() + }; + + ret = argconfig_parse(argc, argv, desc, opts); + if (ret < 0) + return ret; + + fmt = netapp_output_format(cfg.output_format); + if (fmt != NNORMAL && fmt != NCOLUMN && fmt != NJSON) { + fprintf(stderr, "Unrecognized output format: %s\n", cfg.output_format); + return -EINVAL; + } + + num = scandir(dev_path, &devices, netapp_nvme_filter, alphasort); + if (num <= 0) { + fprintf(stderr, "No NVMe devices detected.\n"); + return num; + } + + ontapdevices = calloc(num, sizeof(*ontapdevices)); + if (!ontapdevices) { + fprintf(stderr, "Unable to allocate memory for devices.\n"); + return -ENOMEM; + } + + for (i = 0; i < num; i++) { + snprintf(path, sizeof(path), "%s%s", dev_path, + devices[i]->d_name); + fd = open(path, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Unable to open %s: %s\n", path, + strerror(errno)); + continue; + } + + num_ontapdevices += netapp_ontapdevices_get_info(fd, + &ontapdevices[num_ontapdevices], path); + + close(fd); + } + + if (num_ontapdevices) + netapp_ontapdevices_print(ontapdevices, num_ontapdevices, fmt); + + for (i = 0; i < num; i++) + free(devices[i]); + free(devices); + free(ontapdevices); + return 0; +} diff --git a/plugins/netapp/netapp-nvme.h b/plugins/netapp/netapp-nvme.h new file mode 100644 index 0000000..d4eebd6 --- /dev/null +++ b/plugins/netapp/netapp-nvme.h @@ -0,0 +1,18 @@ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/netapp/netapp-nvme + +#if !defined(NETAPP_NVME) || defined(CMD_HEADER_MULTI_READ) +#define NETAPP_NVME + +#include "cmd.h" + +PLUGIN(NAME("netapp", "NetApp vendor specific extensions"), + COMMAND_LIST( + ENTRY("smdevices", "NetApp SMdevices", netapp_smdevices) + ENTRY("ontapdevices", "NetApp ONTAPdevices", netapp_ontapdevices) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/scaleflux/sfx-nvme.c b/plugins/scaleflux/sfx-nvme.c new file mode 100644 index 0000000..846ca77 --- /dev/null +++ b/plugins/scaleflux/sfx-nvme.c @@ -0,0 +1,873 @@ +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <linux/fs.h> +#include <inttypes.h> +#include <asm/byteorder.h> +#include <sys/ioctl.h> +#include <sys/sysinfo.h> + +#include "linux/nvme_ioctl.h" + +#include "nvme.h" +#include "nvme-print.h" +#include "nvme-ioctl.h" +#include "nvme-status.h" +#include "json.h" +#include "plugin.h" + +#include "argconfig.h" +#include "suffix.h" + +#define CREATE_CMD +#include "sfx-nvme.h" + +#define SFX_PAGE_SHIFT 12 +#define SECTOR_SHIFT 9 + +#define SFX_GET_FREESPACE _IOWR('N', 0x240, struct sfx_freespace_ctx) +#define IDEMA_CAP(exp_GB) (((__u64)exp_GB - 50ULL) * 1953504ULL + 97696368ULL) + + +enum { + SFX_LOG_LATENCY_READ_STATS = 0xc1, + SFX_LOG_SMART = 0xc2, + SFX_LOG_LATENCY_WRITE_STATS = 0xc3, + SFX_LOG_QUAL = 0xc4, + SFX_LOG_MISMATCHLBA = 0xc5, + SFX_LOG_MEDIA = 0xc6, + SFX_LOG_BBT = 0xc7, + SFX_LOG_IDENTIFY = 0xcc, + SFX_FEAT_ATOMIC = 0x01, +}; + +enum sfx_nvme_admin_opcode { + nvme_admin_query_cap_info = 0xd3, + nvme_admin_change_cap = 0xd4, + nvme_admin_sfx_set_features = 0xd5, + nvme_admin_sfx_get_features = 0xd6, +}; + +struct sfx_freespace_ctx +{ + __u64 free_space; + __u64 phy_cap; /* physical capacity, in unit of sector */ + __u64 phy_space; /* physical space considering OP, in unit of sector */ + __u64 user_space; /* user required space, in unit of sector*/ + __u64 hw_used; /* hw space used in 4K */ + __u64 app_written; /* app data written in 4K */ +}; + +struct nvme_capacity_info { + __u64 lba_sec_sz; + __u64 phy_sec_sz; + __u64 used_space; + __u64 free_space; +}; +struct __attribute__((packed)) nvme_additional_smart_log_item { + uint8_t key; + uint8_t _kp[2]; + uint8_t norm; + uint8_t _np; + union { + uint8_t raw[6]; + struct wear_level { + uint16_t min; + uint16_t max; + uint16_t avg; + } wear_level ; + struct thermal_throttle { + uint8_t pct; + uint32_t count; + } thermal_throttle; + }; + uint8_t _rp; +}; + +struct nvme_additional_smart_log { + struct nvme_additional_smart_log_item program_fail_cnt; + struct nvme_additional_smart_log_item erase_fail_cnt; + struct nvme_additional_smart_log_item wear_leveling_cnt; + struct nvme_additional_smart_log_item e2e_err_cnt; + struct nvme_additional_smart_log_item crc_err_cnt; + struct nvme_additional_smart_log_item timed_workload_media_wear; + struct nvme_additional_smart_log_item timed_workload_host_reads; + struct nvme_additional_smart_log_item timed_workload_timer; + struct nvme_additional_smart_log_item thermal_throttle_status; + struct nvme_additional_smart_log_item retry_buffer_overflow_cnt; + struct nvme_additional_smart_log_item pll_lock_loss_cnt; + struct nvme_additional_smart_log_item nand_bytes_written; + struct nvme_additional_smart_log_item host_bytes_written; + struct nvme_additional_smart_log_item raid_recover_cnt; // errors which can be recovered by RAID + struct nvme_additional_smart_log_item prog_timeout_cnt; + struct nvme_additional_smart_log_item erase_timeout_cnt; + struct nvme_additional_smart_log_item read_timeout_cnt; + struct nvme_additional_smart_log_item read_ecc_cnt;//retry cnt +}; + +int nvme_change_cap(int fd, __u32 nsid, __u64 capacity) +{ + struct nvme_admin_cmd cmd = { + .opcode = nvme_admin_change_cap, + .nsid = nsid, + .cdw10 = (capacity & 0xffffffff), + .cdw11 = (capacity >> 32), + }; + + + return nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD,&cmd); +} + +int nvme_sfx_set_features(int fd, __u32 nsid, __u32 fid, __u32 value) +{ + struct nvme_admin_cmd cmd = { + .opcode = nvme_admin_sfx_set_features, + .nsid = nsid, + .cdw10 = fid, + .cdw11 = value, + }; + + return nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD,&cmd); +} + +int nvme_sfx_get_features(int fd, __u32 nsid, __u32 fid, __u32 *result) +{ + int err = 0; + struct nvme_admin_cmd cmd = { + .opcode = nvme_admin_sfx_get_features, + .nsid = nsid, + .cdw10 = fid, + }; + + err = nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD,&cmd); + if (!err && result) { + *result = cmd.result; + } + + return err; +} + +static void show_sfx_smart_log_jsn(struct nvme_additional_smart_log *smart, + unsigned int nsid, const char *devname) +{ + struct json_object *root, *entry_stats, *dev_stats, *multi; + + root = json_create_object(); + json_object_add_value_string(root, "Intel Smart log", devname); + + dev_stats = json_create_object(); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->program_fail_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->program_fail_cnt.raw)); + json_object_add_value_object(dev_stats, "program_fail_count", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->erase_fail_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->erase_fail_cnt.raw)); + json_object_add_value_object(dev_stats, "erase_fail_count", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->wear_leveling_cnt.norm); + multi = json_create_object(); + json_object_add_value_int(multi, "min", le16_to_cpu(smart->wear_leveling_cnt.wear_level.min)); + json_object_add_value_int(multi, "max", le16_to_cpu(smart->wear_leveling_cnt.wear_level.max)); + json_object_add_value_int(multi, "avg", le16_to_cpu(smart->wear_leveling_cnt.wear_level.avg)); + json_object_add_value_object(entry_stats, "raw", multi); + json_object_add_value_object(dev_stats, "wear_leveling", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->e2e_err_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->e2e_err_cnt.raw)); + json_object_add_value_object(dev_stats, "end_to_end_error_detection_count", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->crc_err_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->crc_err_cnt.raw)); + json_object_add_value_object(dev_stats, "crc_error_count", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->timed_workload_media_wear.norm); + json_object_add_value_float(entry_stats, "raw", ((float)int48_to_long(smart->timed_workload_media_wear.raw)) / 1024); + json_object_add_value_object(dev_stats, "timed_workload_media_wear", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->timed_workload_host_reads.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->timed_workload_host_reads.raw)); + json_object_add_value_object(dev_stats, "timed_workload_host_reads", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->timed_workload_timer.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->timed_workload_timer.raw)); + json_object_add_value_object(dev_stats, "timed_workload_timer", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->thermal_throttle_status.norm); + multi = json_create_object(); + json_object_add_value_int(multi, "pct", smart->thermal_throttle_status.thermal_throttle.pct); + json_object_add_value_int(multi, "cnt", smart->thermal_throttle_status.thermal_throttle.count); + json_object_add_value_object(entry_stats, "raw", multi); + json_object_add_value_object(dev_stats, "thermal_throttle_status", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->retry_buffer_overflow_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->retry_buffer_overflow_cnt.raw)); + json_object_add_value_object(dev_stats, "retry_buffer_overflow_count", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->pll_lock_loss_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->pll_lock_loss_cnt.raw)); + json_object_add_value_object(dev_stats, "pll_lock_loss_count", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->nand_bytes_written.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->nand_bytes_written.raw)); + json_object_add_value_object(dev_stats, "nand_bytes_written", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->host_bytes_written.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->host_bytes_written.raw)); + json_object_add_value_object(dev_stats, "host_bytes_written", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->raid_recover_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->raid_recover_cnt.raw)); + json_object_add_value_object(dev_stats, "raid_recover_cnt", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->prog_timeout_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->prog_timeout_cnt.raw)); + json_object_add_value_object(dev_stats, "prog_timeout_cnt", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->erase_timeout_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->erase_timeout_cnt.raw)); + json_object_add_value_object(dev_stats, "erase_timeout_cnt", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->read_timeout_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->read_timeout_cnt.raw)); + json_object_add_value_object(dev_stats, "read_timeout_cnt", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->read_ecc_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->read_ecc_cnt.raw)); + json_object_add_value_object(dev_stats, "read_ecc_cnt", entry_stats); + + json_object_add_value_object(root, "Device stats", dev_stats); + + json_print_object(root, NULL); + printf("/n"); + json_free_object(root); +} + +static void show_sfx_smart_log(struct nvme_additional_smart_log *smart, + unsigned int nsid, const char *devname) +{ + printf("Additional Smart Log for ScaleFlux device:%s namespace-id:%x\n", + devname, nsid); + printf("key normalized raw\n"); + printf("program_fail_count : %3d%% %"PRIu64"\n", + smart->program_fail_cnt.norm, + int48_to_long(smart->program_fail_cnt.raw)); + printf("erase_fail_count : %3d%% %"PRIu64"\n", + smart->erase_fail_cnt.norm, + int48_to_long(smart->erase_fail_cnt.raw)); + printf("wear_leveling : %3d%% min: %u, max: %u, avg: %u\n", + smart->wear_leveling_cnt.norm, + le16_to_cpu(smart->wear_leveling_cnt.wear_level.min), + le16_to_cpu(smart->wear_leveling_cnt.wear_level.max), + le16_to_cpu(smart->wear_leveling_cnt.wear_level.avg)); + printf("end_to_end_error_detection_count: %3d%% %"PRIu64"\n", + smart->e2e_err_cnt.norm, + int48_to_long(smart->e2e_err_cnt.raw)); + printf("crc_error_count : %3d%% %"PRIu64"\n", + smart->crc_err_cnt.norm, + int48_to_long(smart->crc_err_cnt.raw)); + printf("timed_workload_media_wear : %3d%% %.3f%%\n", + smart->timed_workload_media_wear.norm, + ((float)int48_to_long(smart->timed_workload_media_wear.raw)) / 1024); + printf("timed_workload_host_reads : %3d%% %"PRIu64"%%\n", + smart->timed_workload_host_reads.norm, + int48_to_long(smart->timed_workload_host_reads.raw)); + printf("timed_workload_timer : %3d%% %"PRIu64" min\n", + smart->timed_workload_timer.norm, + int48_to_long(smart->timed_workload_timer.raw)); + printf("thermal_throttle_status : %3d%% %u%%, cnt: %u\n", + smart->thermal_throttle_status.norm, + smart->thermal_throttle_status.thermal_throttle.pct, + smart->thermal_throttle_status.thermal_throttle.count); + printf("retry_buffer_overflow_count : %3d%% %"PRIu64"\n", + smart->retry_buffer_overflow_cnt.norm, + int48_to_long(smart->retry_buffer_overflow_cnt.raw)); + printf("pll_lock_loss_count : %3d%% %"PRIu64"\n", + smart->pll_lock_loss_cnt.norm, + int48_to_long(smart->pll_lock_loss_cnt.raw)); + printf("nand_bytes_written : %3d%% sectors: %"PRIu64"\n", + smart->nand_bytes_written.norm, + int48_to_long(smart->nand_bytes_written.raw)); + printf("host_bytes_written : %3d%% sectors: %"PRIu64"\n", + smart->host_bytes_written.norm, + int48_to_long(smart->host_bytes_written.raw)); + printf("raid_recover_cnt : %3d%% %"PRIu64"\n", + smart->raid_recover_cnt.norm, + int48_to_long(smart->raid_recover_cnt.raw)); + printf("read_ecc_cnt : %3d%% %"PRIu64"\n", + smart->read_ecc_cnt.norm, + int48_to_long(smart->read_ecc_cnt.raw)); + printf("prog_timeout_cnt : %3d%% %"PRIu64"\n", + smart->prog_timeout_cnt.norm, + int48_to_long(smart->prog_timeout_cnt.raw)); + printf("erase_timeout_cnt : %3d%% %"PRIu64"\n", + smart->erase_timeout_cnt.norm, + int48_to_long(smart->erase_timeout_cnt.raw)); + printf("read_timeout_cnt : %3d%% %"PRIu64"\n", + smart->read_timeout_cnt.norm, + int48_to_long(smart->read_timeout_cnt.raw)); +} + +static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct nvme_additional_smart_log smart_log; + int err, fd; + char *desc = "Get ScaleFlux vendor specific additional smart log (optionally, "\ + "for the specified namespace), and show it."; + const char *namespace = "(optional) desired namespace"; + const char *raw = "dump output in binary format"; + const char *json= "Dump output in json format"; + struct config { + __u32 namespace_id; + int raw_binary; + int json; + }; + + struct config cfg = { + .namespace_id = 0xffffffff, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_FLAG("json", 'j', &cfg.json, json), + OPT_END() + }; + + + fd = parse_and_open(argc, argv, desc, opts); + + err = nvme_get_log(fd, cfg.namespace_id, 0xca, false, sizeof(smart_log), + (void *)&smart_log); + if (!err) { + if (cfg.json) + show_sfx_smart_log_jsn(&smart_log, cfg.namespace_id, devicename); + else if (!cfg.raw_binary) + show_sfx_smart_log(&smart_log, cfg.namespace_id, devicename); + else + d_raw((unsigned char *)&smart_log, sizeof(smart_log)); + } + else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + return err; +} + + +struct sfx_lat_stats { + __u16 maj; + __u16 min; + __u32 bucket_1[32]; /* 0~1ms, step 32us */ + __u32 bucket_2[31]; /* 1~32ms, step 1ms */ + __u32 bucket_3[31]; /* 32ms~1s, step 32ms */ + __u32 bucket_4[1]; /* 1s~2s, specifically 1024ms~2047ms */ + __u32 bucket_5[1]; /* 2s~4s, specifically 2048ms~4095ms */ + __u32 bucket_6[1]; /* 4s+, specifically 4096ms+ */ +}; + +static void show_lat_stats(struct sfx_lat_stats *stats, int write) +{ + int i; + + printf(" ScaleFlux IO %s Command Latency Statistics\n", write ? "Write" : "Read"); + printf("-------------------------------------\n"); + printf("Major Revision : %u\n", stats->maj); + printf("Minor Revision : %u\n", stats->min); + + printf("\nGroup 1: Range is 0-1ms, step is 32us\n"); + for (i = 0; i < 32; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_1[i]); + + printf("\nGroup 2: Range is 1-32ms, step is 1ms\n"); + for (i = 0; i < 31; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_2[i]); + + printf("\nGroup 3: Range is 32ms-1s, step is 32ms:\n"); + for (i = 0; i < 31; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_3[i]); + + printf("\nGroup 4: Range is 1s-2s:\n"); + printf("Bucket %2d: %u\n", 0, stats->bucket_4[0]); + + printf("\nGroup 5: Range is 2s-4s:\n"); + printf("Bucket %2d: %u\n", 0, stats->bucket_5[0]); + + printf("\nGroup 6: Range is 4s+:\n"); + printf("Bucket %2d: %u\n", 0, stats->bucket_6[0]); +} + +static int get_lat_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct sfx_lat_stats stats; + int err, fd; + + char *desc = "Get ScaleFlux Latency Statistics log and show it."; + const char *raw = "dump output in binary format"; + const char *write = "Get write statistics (read default)"; + struct config { + int raw_binary; + int write; + }; + + struct config cfg = { + }; + + OPT_ARGS(opts) = { + OPT_FLAG("write", 'w', &cfg.write, write), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + + err = nvme_get_log(fd, 0xffffffff, cfg.write ? 0xc3 : 0xc1, false, sizeof(stats), (void *)&stats); + if (!err) { + if (!cfg.raw_binary) + show_lat_stats(&stats, cfg.write); + else + d_raw((unsigned char *)&stats, sizeof(stats)); + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + return err; +} + +int sfx_nvme_get_log(int fd, __u32 nsid, __u8 log_id, __u32 data_len, void *data) +{ + struct nvme_admin_cmd cmd = { + .opcode = nvme_admin_get_log_page, + .nsid = nsid, + .addr = (__u64)(uintptr_t) data, + .data_len = data_len, + }; + __u32 numd = (data_len >> 2) - 1; + __u16 numdu = numd >> 16, numdl = numd & 0xffff; + + cmd.cdw10 = log_id | (numdl << 16); + cmd.cdw11 = numdu; + + return nvme_submit_admin_passthru(fd, &cmd); +} + +/** + * @brief get bb table through admin_passthru + * + * @param fd + * @param buf + * @param size + * + * @return -1 fail ; 0 success + */ +static int get_bb_table(int fd, __u32 nsid, unsigned char *buf, __u64 size) +{ + if (fd < 0 || !buf || size != 256*4096*sizeof(unsigned char)) { + fprintf(stderr, "Invalid Param \r\n"); + return EINVAL; + } + + return sfx_nvme_get_log(fd, nsid, SFX_LOG_BBT, size, (void *)buf); +} + +/** + * @brief display bb table + * + * @param bd_table buffer that contain bb table dumped from drvier + * @param table_size buffer size (BYTES), should at least has 8 bytes for mf_bb_count and grown_bb_count + */ +static void bd_table_show(unsigned char *bd_table, __u64 table_size) +{ + __u32 mf_bb_count = 0; + __u32 grown_bb_count = 0; + __u32 total_bb_count = 0; + __u32 remap_mfbb_count = 0; + __u32 remap_gbb_count = 0; + __u64 *bb_elem; + __u64 *elem_end = (__u64 *)(bd_table + table_size); + __u64 i; + + /*buf should at least have 8bytes for mf_bb_count & total_bb_count*/ + if (!bd_table || table_size < sizeof(__u64)) + return; + + mf_bb_count = *((__u32 *)bd_table); + grown_bb_count = *((__u32 *)(bd_table + sizeof(__u32))); + total_bb_count = *((__u32 *)(bd_table + 2 * sizeof(__u32))); + remap_mfbb_count = *((__u32 *)(bd_table + 3 * sizeof(__u32))); + remap_gbb_count = *((__u32 *)(bd_table + 4 * sizeof(__u32))); + bb_elem = (__u64 *)(bd_table + 5 * sizeof(__u32)); + + printf("Bad Block Table \n"); + printf("MF_BB_COUNT: %u\n", mf_bb_count); + printf("GROWN_BB_COUNT: %u\n", grown_bb_count); + printf("TOTAL_BB_COUNT: %u\n", total_bb_count); + printf("REMAP_MFBB_COUNT: %u\n", remap_mfbb_count); + printf("REMAP_GBB_COUNT: %u\n", remap_gbb_count); + + printf("REMAP_MFBB_TABLE ["); + i = 0; + while (bb_elem < elem_end && i < remap_mfbb_count) { + printf(" 0x%llx", *(bb_elem++)); + i++; + } + printf(" ]\n"); + + printf("REMAP_GBB_TABLE ["); + i = 0; + while (bb_elem < elem_end && i < remap_gbb_count) { + printf(" 0x%llx",*(bb_elem++)); + i++; + } + printf(" ]\n"); +} + +/** + * @brief "hooks of sfx get-bad-block" + * + * @param argc + * @param argv + * @param cmd + * @param plugin + * + * @return + */ +static int sfx_get_bad_block(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int fd; + unsigned char *data_buf; + const __u64 buf_size = 256*4096*sizeof(unsigned char); + int err = 0; + + char *desc = "Get bad block table of sfx block device."; + + OPT_ARGS(opts) = { + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + + if (fd < 0) { + return fd; + } + + data_buf = malloc(buf_size); + if (!data_buf) { + fprintf(stderr, "malloc fail, errno %d\r\n", errno); + return -1; + } + + err = get_bb_table(fd, 0xffffffff, data_buf, buf_size); + if (err < 0) { + perror("get-bad-block"); + } else if (err != 0) { + fprintf(stderr, "NVMe IO command error:%s(%x)\n", + nvme_status_to_string(err), err); + } else { + bd_table_show(data_buf, buf_size); + printf("ScaleFlux get bad block table: success\n"); + } + + free(data_buf); + return 0; +} + +static void show_cap_info(struct sfx_freespace_ctx *ctx) +{ + printf("user sectors: %#llx\n", ctx->user_space); + printf("totl physical sectors: %#llx\n", ctx->phy_space); + printf("free physical sectors: %#llx\n", ctx->free_space); + printf("used physical sectors: %#llx\n", ctx->phy_space - ctx->free_space); +} + +static int query_cap_info(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct sfx_freespace_ctx ctx = { 0 }; + int err = 0, fd; + char *desc = "query current capacity info of vanda"; + const char *raw = "dump output in binary format"; + const char *json= "Dump output in json format"; + struct config { + int raw_binary; + int json; + }; + struct config cfg; + + OPT_ARGS(opts) = { + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_FLAG("json", 'j', &cfg.json, json), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) { + return fd; + } + + if (ioctl(fd, SFX_GET_FREESPACE, &ctx)) { + fprintf(stderr, "vu ioctl fail, errno %d\r\n", errno); + return -1; + } + + show_cap_info(&ctx); + return err; +} + +static int change_cap_mem_check(int fd, __u64 trg_in_4k) +{ + struct sfx_freespace_ctx freespace_ctx = { 0 }; + struct sysinfo s_info; + __u64 mem_need = 0; + __u64 cur_in_4k = 0; + __u32 cnt_ms = 0; + + while (ioctl(fd, SFX_GET_FREESPACE, &freespace_ctx)) { + if (cnt_ms++ > 600) {//1min + fprintf(stderr, "vu ioctl fail, errno %d\r\n", errno); + return -1; + } + usleep(100000); + } + + cur_in_4k = freespace_ctx.user_space >> (SFX_PAGE_SHIFT - SECTOR_SHIFT); + if (cur_in_4k > trg_in_4k) { + return 0; + } + + if (sysinfo(&s_info) < 0) { + printf("change-cap query mem info fail\n"); + return -1; + } + + mem_need = (trg_in_4k - cur_in_4k) * 8; + if (s_info.freeram <= 10 || mem_need > s_info.freeram) { + fprintf(stderr, "WARNING: mem needed is %llu, free mem is %lu\n" + "Insufficient memory, please drop cache or add free memory and retry\n", + mem_need, s_info.freeram); + return -1; + } + return 0; +} + +static int change_cap(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int err = -1, fd; + char *desc = "query current capacity info of vanda"; + const char *raw = "dump output in binary format"; + const char *json= "Dump output in json format"; + const char *cap_gb = "cap size in GB"; + const char *cap_byte = "cap size in byte"; + const char *force = "The \"I know what I'm doing\" flag, skip confirmation before sending command"; + __u64 cap_in_4k = 0; + __u64 cap_in_sec = 0; + struct config { + __u64 cap_in_byte; + __u32 capacity_in_gb; + int raw_binary; + int json; + int force; + }; + + struct config cfg = { + .cap_in_byte = 0, + .capacity_in_gb = 0, + .force = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("cap", 'c', &cfg.capacity_in_gb, cap_gb), + OPT_UINT("cap-byte", 'z', &cfg.cap_in_byte, cap_byte), + OPT_FLAG("force", 'f', &cfg.force, force), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_FLAG("json", 'j', &cfg.json, json), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) { + return fd; + } + + if (!cfg.force) { + fprintf(stderr, "WARNING: Changing capacity may irrevocably delete user data.\n" + "You have 10 seconds to press Ctrl-C to cancel this operation.\n\n" + "Use the force [--force|-f] option to suppress this warning.\n"); + sleep(10); + fprintf(stderr, "Sending operation ... \n"); + } + + cap_in_sec = IDEMA_CAP(cfg.capacity_in_gb); + cap_in_4k = cap_in_sec >> 3; + if (cfg.cap_in_byte) + cap_in_4k = cfg.cap_in_byte >> 12; + printf("%dG %lluB %llu 4K\n", + cfg.capacity_in_gb, cfg.cap_in_byte, cap_in_4k); + if (change_cap_mem_check(fd, cap_in_4k)) + return err; + + err = nvme_change_cap(fd, 0xffffffff, cap_in_4k); + if (err < 0) + perror("sfx-change-cap"); + else if (err != 0) + fprintf(stderr, "NVMe IO command error:%s(%x)\n", + nvme_status_to_string(err), err); + else { + printf("ScaleFlux change-capacity: success\n"); + if(ioctl(fd, BLKRRPART) < 0) { + fprintf(stderr, "failed to re-read partition table\n"); + err = EFAULT; + } + } + return err; +} + +char *sfx_feature_to_string(int feature) +{ + switch (feature) { + case SFX_FEAT_ATOMIC: return "ATOMIC"; + + default: return "Unknown"; + } +} + +static int sfx_set_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int err = 0, fd; + char *desc = "ScaleFlux internal set features\n" + "feature id 1: ATOMIC"; + const char *value = "new value of feature (required)"; + const char *feature_id = "hex feature name (required)"; + const char *namespace_id = "desired namespace"; + struct nvme_id_ns ns; + + struct config { + __u32 namespace_id; + __u32 feature_id; + __u32 value; + }; + struct config cfg = { + .namespace_id = 1, + .feature_id = 0, + .value = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("feature-id", 'f', &cfg.feature_id, feature_id), + OPT_UINT("value", 'v', &cfg.value, value), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) { + return fd; + } + + if (!cfg.feature_id) { + fprintf(stderr, "feature-id required param\n"); + return EINVAL; + } + + if (cfg.feature_id == SFX_FEAT_ATOMIC) { + if (cfg.namespace_id != 0xffffffff) { + err = nvme_identify_ns(fd, cfg.namespace_id, 0, &ns); + if (err) { + if (err < 0) + perror("identify-namespace"); + else + fprintf(stderr, + "NVMe Admin command error:%s(%x)\n", + nvme_status_to_string(err), err); + return err; + } + /* + * atomic only support with sector-size = 4k now + */ + if ((ns.flbas & 0xf) != 1) { + printf("Please change-sector size to 4K, then retry\n"); + return EFAULT; + } + } + } + + err = nvme_sfx_set_features(fd, cfg.namespace_id, cfg.feature_id, cfg.value); + if (err < 0) { + perror("ScaleFlux-set-feature"); + return errno; + } else if (!err) { + printf("ScaleFlux set-feature:%02x (%s), value:%#08x\n", cfg.feature_id, + sfx_feature_to_string(cfg.feature_id), cfg.value); + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + + return err; +} + +static int sfx_get_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int err = 0, fd; + char *desc = "ScaleFlux internal set features\n" + "feature id 1: ATOMIC"; + const char *feature_id = "hex feature name (required)"; + const char *namespace_id = "desired namespace"; + __u32 result = 0; + + struct config { + __u32 namespace_id; + __u32 feature_id; + }; + struct config cfg = { + .namespace_id = 0, + .feature_id = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("feature-id", 'f', &cfg.feature_id, feature_id), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + + if (fd < 0) { + return fd; + } + + if (!cfg.feature_id) { + fprintf(stderr, "feature-id required param\n"); + return EINVAL; + } + + err = nvme_sfx_get_features(fd, cfg.namespace_id, cfg.feature_id, &result); + if (err < 0) { + perror("ScaleFlux-get-feature"); + return errno; + } else if (!err) { + printf("ScaleFlux get-feature:%02x (%s), value:%d\n", cfg.feature_id, + sfx_feature_to_string(cfg.feature_id), result); + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + + return err; + +} diff --git a/plugins/scaleflux/sfx-nvme.h b/plugins/scaleflux/sfx-nvme.h new file mode 100644 index 0000000..daf9c33 --- /dev/null +++ b/plugins/scaleflux/sfx-nvme.h @@ -0,0 +1,25 @@ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/scaleflux/sfx-nvme + +#if !defined(SFX_NVME) || defined(CMD_HEADER_MULTI_READ) +#define SFX_NVME + +#include "cmd.h" + +PLUGIN(NAME("sfx", "ScaleFlux vendor specific extensions"), + COMMAND_LIST( + ENTRY("smart-log-add", "Retrieve ScaleFlux SMART Log, show it", get_additional_smart_log) + ENTRY("lat-stats", "Retrieve ScaleFlux IO Latency Statistics log, show it", get_lat_stats_log) + ENTRY("get-bad-block", "Retrieve bad block table of block device, show it", sfx_get_bad_block) + ENTRY("query-cap", "Query current capacity info", query_cap_info) + ENTRY("change-cap", "Dynamic change capacity", change_cap) + ENTRY("set-feature", "Set a feature", sfx_set_feature) + ENTRY("get-feature", "get a feature", sfx_get_feature) + ) +); + +#endif + +#include "define_cmd.h" + + diff --git a/plugins/seagate/seagate-diag.h b/plugins/seagate/seagate-diag.h new file mode 100644 index 0000000..3dc2df7 --- /dev/null +++ b/plugins/seagate/seagate-diag.h @@ -0,0 +1,269 @@ +/* + * Do NOT modify or remove this copyright and license + * + * Copyright (c) 2017-2018 Seagate Technology LLC and/or its Affiliates, All Rights Reserved + * + * ****************************************************************************************** + * + * 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. + * + * \file seagate-diag.h + * \brief This file defines the functions and macros to make building a nvme-cli seagate plug-in. + */ + + +#ifndef SEAGATE_NVME_H +#define SEAGATE_NVME_H + +#define SEAGATE_PLUGIN_VERSION_MAJOR 1 +#define SEAGATE_PLUGIN_VERSION_MINOR 1 + +#define PERSIST_FILE_SIZE (2764800) +#define ONE_MB (1048576) /* (1024 * 1024) */ +#define PERSIST_CHUNK (65536) /* (1024 * 64) */ +#define FOUR_KB (4096) + + +/*************************** +*Supported Log-Pages from FW +***************************/ + +typedef struct log_page_map_entry { + __u32 LogPageID; + __u32 LogPageSignature; + __u32 LogPageVersion; +} log_page_map_entry; + +#define MAX_SUPPORTED_LOG_PAGE_ENTRIES ((4096 - sizeof(__u32)) / sizeof(log_page_map_entry)) + +typedef struct log_page_map { + __u32 NumLogPages; + log_page_map_entry LogPageEntry[ MAX_SUPPORTED_LOG_PAGE_ENTRIES ]; +} log_page_map; +/* EOF Supported Log-Pages from FW */ + + +/*************************** +* Extended-SMART Information +***************************/ +#pragma pack(1) +#define NUMBER_EXTENDED_SMART_ATTRIBUTES 42 + +typedef enum _EXTENDED_SMART_VERSION_ +{ + EXTENDED_SMART_VERSION_NONE, + EXTENDED_SMART_VERSION_GEN, + EXTENDED_SMART_VERSION_VENDOR1, +} EXTENDED_SMART_VERSION; + +typedef struct _SmartVendorSpecific +{ + __u8 AttributeNumber; + __u16 SmartStatus; + __u8 NominalValue; + __u8 LifetimeWorstValue; + __u32 Raw0_3; + __u8 RawHigh[3]; +} SmartVendorSpecific; + +typedef struct _EXTENDED_SMART_INFO_T +{ + __u16 Version; + SmartVendorSpecific vendorData[NUMBER_EXTENDED_SMART_ATTRIBUTES]; + __u8 vendor_specific_reserved[6]; +} EXTENDED_SMART_INFO_T; + +typedef struct vendor_smart_attribute_data +{ + __u8 AttributeNumber; /* 00 */ + __u8 Rsvd[3]; /* 01 -03 */ + __u32 LSDword; /* 04-07 */ + __u32 MSDword; /* 08 - 11 */ +} vendor_smart_attribute_data; + +struct nvme_temetry_log_hdr +{ + __u8 log_id; + __u8 rsvd1[4]; + __u8 ieee_id[3]; + __le16 tele_data_area1; + __le16 tele_data_area2; + __le16 tele_data_area3; + __u8 rsvd14[368]; + __u8 tele_data_aval; + __u8 tele_data_gen_num; + __u8 reason_identifier[128]; +}; + +typedef struct _U128 +{ + __u64 LS__u64; + __u64 MS__u64; +} U128; + +typedef struct _vendor_log_page_CF_Attr +{ + __u16 SuperCapCurrentTemperature; /* 00-01 */ + __u16 SuperCapMaximumTemperature; /* 02-03 */ + __u8 SuperCapStatus; /* 04 */ + __u8 Reserved5to7[3]; /* 05-07 */ + U128 DataUnitsReadToDramNamespace; /* 08-23 */ + U128 DataUnitsWrittenToDramNamespace; /* 24-39 */ + __u64 DramCorrectableErrorCount; /* 40-47 */ + __u64 DramUncorrectableErrorCount; /* 48-55 */ +}vendor_log_page_CF_Attr; + +typedef struct _vendor_log_page_CF +{ + vendor_log_page_CF_Attr AttrCF; + __u8 Vendor_Specific_Reserved[ 456 ]; /* 56-511 */ +}vendor_log_page_CF; + +#pragma pack() +/* EOF Extended-SMART Information*/ + + +/************************** +* PCIE ERROR INFORMATION +**************************/ +typedef struct pcie_error_log_page +{ + __u32 Version; + __u32 BadDllpErrCnt; + __u32 BadTlpErrCnt; + __u32 RcvrErrCnt; + __u32 ReplayTOErrCnt; + __u32 ReplayNumRolloverErrCnt; + __u32 FCProtocolErrCnt; + __u32 DllpProtocolErrCnt; + __u32 CmpltnTOErrCnt; + __u32 RcvrQOverflowErrCnt; + __u32 UnexpectedCplTlpErrCnt; + __u32 CplTlpURErrCnt; + __u32 CplTlpCAErrCnt; + __u32 ReqCAErrCnt; + __u32 ReqURErrCnt; + __u32 EcrcErrCnt; + __u32 MalformedTlpErrCnt; + __u32 CplTlpPoisonedErrCnt; + __u32 MemRdTlpPoisonedErrCnt; +} pcie_error_log_page; +/*EOF PCIE ERROR INFORMATION */ + +typedef enum +{ + VS_ATTR_SOFT_READ_ERROR_RATE, /* 0 OFFSET : 02 -13 bytes */ + VS_ATTR_REALLOCATED_SECTOR_COUNT, /* 1 OFFSET : 14 -25 bytes */ + VS_ATTR_POWER_ON_HOURS, /* 2 OFFSET : 26 -37 bytes */ + VS_ATTR_POWER_FAIL_EVENT_COUNT, /* 3 OFFSET : 38 -49 bytes */ + VS_ATTR_DEVICE_POWER_CYCLE_COUNT, /* 4 OFFSET : 50 -61 bytes */ + VS_ATTR_GB_ERASED, /* 5 OFFSET : 62 -73 bytes */ + VS_ATTR_LIFETIME_DEVSLEEP_EXIT_COUNT, /* 6 OFFSET : 74 -85 bytes */ + VS_ATTR_LIFETIME_ENTERING_PS4_COUNT, /* 7 OFFSET : 86 -97 bytes */ + VS_ATTR_LIFETIME_ENTERING_PS3_COUNT, /* 8 OFFSET : 98 -109 bytes */ + VS_ATTR_RETIRED_BLOCK_COUNT, /* 9 OFFSET : 110 -121 bytes */ + VS_ATTR_PROGRAM_FAILURE_COUNT, /* 10 OFFSET : 122 -133 bytes */ + VS_ATTR_ERASE_FAIL_COUNT, /* 11 OFFSET : 134 -145 bytes */ + VS_ATTR_AVG_ERASE_COUNT, /* 12 OFFSET : 146 -157 bytes */ + VS_ATTR_UNEXPECTED_POWER_LOSS_COUNT, /* 13 OFFSET : 158 -169 bytes */ + VS_ATTR_WEAR_RANGE_DELTA, /* 14 OFFSET : 170 -181 bytes */ + VS_ATTR_SATA_INTERFACE_DOWNSHIFT_COUNT, /* 15 OFFSET : 182 -193 bytes */ + VS_ATTR_END_TO_END_CRC_ERROR_COUNT, /* 16 OFFSET : 194 -205 bytes */ + VS_ATTR_MAX_LIFE_TEMPERATURE, /* 17 OFFSET : 206 -217 bytes */ + VS_ATTR_UNCORRECTABLE_RAISE_ERRORS, /* 18 OFFSET : 218 -229 bytes */ + VS_ATTR_DRIVE_LIFE_PROTECTION_STATUS, /* 19 OFFSET : 230 -241 bytes */ + VS_ATTR_REMAINING_SSD_LIFE, /* 20 OFFSET : 242 -253 bytes */ + VS_ATTR_LIFETIME_WRITES_TO_FLASH, /* 21 OFFSET : 254 -265 bytes */ + VS_ATTR_LIFETIME_WRITES_FROM_HOST, /* 22 OFFSET : 266 -277 bytes */ + VS_ATTR_LIFETIME_READS_TO_HOST, /* 23 OFFSET : 278 -289 bytes */ + VS_ATTR_FREE_SPACE, /* 24 OFFSET : 290 -301 bytes */ + VS_ATTR_TRIM_COUNT_LSB, /* 25 OFFSET : 302 -313 bytes */ + VS_ATTR_TRIM_COUNT_MSB, /* 26 OFFSET : 314 -325 bytes */ + VS_ATTR_OP_PERCENTAGE, /* 27 OFFSET : 326 -337 bytes */ + VS_ATTR_RAISE_ECC_CORRECTABLE_ERROR_COUNT, /* 28 OFFSET : 338 -349 bytes */ + VS_ATTR_UNCORRECTABLE_ECC_ERRORS , /* 29 OFFSET : 350 -361 bytes */ + VS_ATTR_LIFETIME_WRITES0_TO_FLASH, /* 30 362-372 */ + VS_ATTR_LIFETIME_WRITES1_TO_FLASH, /* 31 374-385 */ + VS_ATTR_LIFETIME_WRITES0_FROM_HOST, /* 32 386-397 */ + VS_ATTR_LIFETIME_WRITES1_FROM_HOST, /* 33 398-409 */ + VS_ATTR_LIFETIME_READ0_FROM_HOST, /* 34 410-421 */ + VS_ATTR_LIFETIME_READ1_FROM_HOST, /* 35 422-433 */ + VS_ATTR_PCIE_PHY_CRC_ERROR, /* 36 434-445 */ + VS_ATTR_BAD_BLOCK_COUNT_SYSTEM, /* 37 446-457 */ + VS_ATTR_BAD_BLOCK_COUNT_USER, /* 38 458-469 */ + VS_ATTR_THERMAL_THROTTLING_STATUS, /* 39 470-481 */ + VS_ATTR_POWER_CONSUMPTION, /* 40 482-493 */ + VS_ATTR_MAX_SOC_LIFE_TEMPERATURE, /* 41 494-505 */ + + VS_MAX_ATTR_NUMBER, + +} extended_smart_attributes; + +/*Smart attribute IDs */ + +typedef enum +{ + VS_ATTR_ID_SOFT_READ_ERROR_RATE = 1, + VS_ATTR_ID_REALLOCATED_SECTOR_COUNT = 5, + VS_ATTR_ID_POWER_ON_HOURS = 9, + VS_ATTR_ID_POWER_FAIL_EVENT_COUNT = 11, + VS_ATTR_ID_DEVICE_POWER_CYCLE_COUNT = 12, + VS_ATTR_ID_RAW_READ_ERROR_RATE = 13, + VS_ATTR_ID_GROWN_BAD_BLOCK_COUNT = 40, + VS_ATTR_ID_END_2_END_CORRECTION_COUNT = 41, + VS_ATTR_ID_MIN_MAX_WEAR_RANGE_COUNT = 42, + VS_ATTR_ID_REFRESH_COUNT = 43, + VS_ATTR_ID_BAD_BLOCK_COUNT_USER = 44, + VS_ATTR_ID_BAD_BLOCK_COUNT_SYSTEM = 45, + VS_ATTR_ID_THERMAL_THROTTLING_STATUS = 46, + VS_ATTR_ID_ALL_PCIE_CORRECTABLE_ERROR_COUNT = 47, + VS_ATTR_ID_ALL_PCIE_UNCORRECTABLE_ERROR_COUNT = 48, + VS_ATTR_ID_INCOMPLETE_SHUTDOWN_COUNT = 49, + VS_ATTR_ID_GB_ERASED_LSB = 100, + VS_ATTR_ID_GB_ERASED_MSB = 101, + VS_ATTR_ID_LIFETIME_ENTERING_PS4_COUNT = 102, + VS_ATTR_ID_LIFETIME_ENTERING_PS3_COUNT = 103, + VS_ATTR_ID_LIFETIME_DEVSLEEP_EXIT_COUNT = 104, + VS_ATTR_ID_RETIRED_BLOCK_COUNT = 170, + VS_ATTR_ID_PROGRAM_FAILURE_COUNT = 171, + VS_ATTR_ID_ERASE_FAIL_COUNT = 172, + VS_ATTR_ID_AVG_ERASE_COUNT = 173, + VS_ATTR_ID_UNEXPECTED_POWER_LOSS_COUNT = 174, + VS_ATTR_ID_WEAR_RANGE_DELTA = 177, + VS_ATTR_ID_SATA_INTERFACE_DOWNSHIFT_COUNT = 183, + VS_ATTR_ID_END_TO_END_CRC_ERROR_COUNT = 184, + VS_ATTR_ID_UNCORRECTABLE_READ_ERRORS = 188, + VS_ATTR_ID_MAX_LIFE_TEMPERATURE = 194, + VS_ATTR_ID_RAISE_ECC_CORRECTABLE_ERROR_COUNT = 195, + VS_ATTR_ID_UNCORRECTABLE_RAISE_ERRORS = 198, + VS_ATTR_ID_DRIVE_LIFE_PROTECTION_STATUS = 230, + VS_ATTR_ID_REMAINING_SSD_LIFE = 231, + VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB = 233, + VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB = 234, + VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB = 241, + VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB = 242, + VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB = 243, + VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB = 244, + VS_ATTR_ID_FREE_SPACE = 245, + VS_ATTR_ID_TRIM_COUNT_LSB = 250, + VS_ATTR_ID_TRIM_COUNT_MSB = 251, + VS_ATTR_ID_OP_PERCENTAGE = 252, + VS_ATTR_ID_MAX_SOC_LIFE_TEMPERATURE = 253, +} smart_attributes_ids; + +#define TELEMETRY_BLOCKS_TO_READ 8 + +void seaget_d_raw(unsigned char *buf, int len, int fd); + + +#define DP_CLASS_ID_FULL 0 + +#endif diff --git a/plugins/seagate/seagate-nvme.c b/plugins/seagate/seagate-nvme.c new file mode 100644 index 0000000..7ba14f8 --- /dev/null +++ b/plugins/seagate/seagate-nvme.c @@ -0,0 +1,1397 @@ +/* + * Do NOT modify or remove this copyright and license + * + * Copyright (c) 2017-2018 Seagate Technology LLC and/or its Affiliates, All Rights Reserved + * + * ****************************************************************************************** + * + * 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. + * + * \file seagate-nvme.c + * \brief This file defines the functions and macros to make building a nvme-cli seagate plug-in. + */ + + +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <ctype.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" +#include "json.h" + +#define CREATE_CMD + +#include "seagate-nvme.h" +#include "seagate-diag.h" + + +/*************************************** +*Command for "log-pages-supp" +***************************************/ +static char *log_pages_supp_print(__u32 pageID) +{ + switch(pageID) { + case 0x01: + return "ERROR_INFORMATION"; + break; + case 0x02: + return "SMART_INFORMATION"; + break; + case 0x03: + return "FW_SLOT_INFORMATION"; + break; + case 0x04: + return "CHANGED_NAMESPACE_LIST"; + break; + case 0x05: + return "COMMANDS_SUPPORTED_AND_EFFECTS"; + break; + case 0x06: + return "DEVICE_SELF_TEST"; + break; + case 0x07: + return "TELEMETRY_HOST_INITIATED"; + break; + case 0x08: + return "TELEMETRY_CONTROLLER_INITIATED"; + break; + case 0xC0: + return "VS_MEDIA_SMART_LOG"; + break; + case 0xC1: + return "VS_DEBUG_LOG1"; + break; + case 0xC2: + return "VS_SEC_ERROR_LOG_PAGE"; + break; + case 0xC3: + return "VS_LIFE_TIME_DRIVE_HISTORY"; + break; + case 0xC4: + return "VS_EXTENDED_SMART_INFO"; + break; + case 0xC5: + return "VS_LIST_SUPPORTED_LOG_PAGE"; + break; + case 0xC6: + return "VS_POWER_MONITOR_LOG_PAGE"; + break; + case 0xC7: + return "VS_CRITICAL_EVENT_LOG_PAGE"; + break; + case 0xC8: + return "VS_RECENT_DRIVE_HISTORY"; + break; + case 0xC9: + return "VS_SEC_ERROR_LOG_PAGE"; + break; + case 0xCA: + return "VS_LIFE_TIME_DRIVE_HISTORY"; + break; + case 0xCB: + return "VS_PCIE_ERROR_LOG_PAGE"; + break; + case 0xCF: + return "DRAM Supercap SMART Attributes"; + break; + case 0xD6: + return "VS_OEM2_WORK_LOAD"; + break; + case 0xD7: + return "VS_OEM2_FW_SECURITY"; + break; + case 0xD8: + return "VS_OEM2_REVISION"; + break; + default: + return "UNKNOWN"; + break; + } +} + + +static void json_log_pages_supp(log_page_map *logPageMap) +{ + struct json_object *root; + struct json_array *logPages; + __u32 i = 0; + + root = json_create_object(); + logPages = json_create_array(); + json_object_add_value_array(root, "supported_log_pages", logPages); + + for (i = 0; i < le32_to_cpu(logPageMap->NumLogPages); i++) { + struct json_object *lbaf = json_create_object(); + json_object_add_value_int(lbaf, "logpage_id", + le32_to_cpu(logPageMap->LogPageEntry[i].LogPageID)); + json_object_add_value_string(lbaf, "logpage_name", + log_pages_supp_print(le32_to_cpu(logPageMap->LogPageEntry[i].LogPageID))); + + json_array_add_value_object(logPages, lbaf); + } + json_print_object(root, NULL); + printf("\n"); +} + +static int log_pages_supp(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err = 0; + int fd = 0; + __u32 i = 0; + log_page_map logPageMap; + const char *desc = "Retrieve Seagate Supported Log-Page information for the given device "; + const char *output_format = "output in binary format"; + int fmt; + + struct config { + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + err = nvme_get_log(fd, 1, 0xc5, false, sizeof(logPageMap), &logPageMap); + if (!err) { + if (strcmp(cfg.output_format,"json")) { + printf ("Seagate Supported Log-pages count :%d\n", + le32_to_cpu(logPageMap.NumLogPages)); + printf ("%-15s %-30s\n", "LogPage-Id", "LogPage-Name"); + + for(fmt=0; fmt<45; fmt++) + printf ("-"); + printf("\n"); + } else + json_log_pages_supp(&logPageMap); + + for (i = 0; i<le32_to_cpu(logPageMap.NumLogPages); i++) { + if (strcmp(cfg.output_format,"json")) { + printf("0x%-15X", + le32_to_cpu(logPageMap.LogPageEntry[i].LogPageID)); + printf("%-30s\n", + log_pages_supp_print(le32_to_cpu(logPageMap.LogPageEntry[i].LogPageID))); + } + } + } + + if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + return err; +} + +/* EOF Command for "log-pages-supp" */ + + +/*************************************** +* Extended-SMART Information +***************************************/ +static char *print_ext_smart_id(__u8 attrId) +{ + switch(attrId) { + case VS_ATTR_ID_SOFT_READ_ERROR_RATE: + return "Soft ECC error count"; + break; + case VS_ATTR_ID_REALLOCATED_SECTOR_COUNT: + return "Bad NAND block count"; + break; + case VS_ATTR_ID_POWER_ON_HOURS: + return "Power On Hours"; + break; + case VS_ATTR_ID_POWER_FAIL_EVENT_COUNT: + return "Power Fail Event Count"; + break; + case VS_ATTR_ID_DEVICE_POWER_CYCLE_COUNT: + return "Device Power Cycle Count"; + break; + case VS_ATTR_ID_RAW_READ_ERROR_RATE: + return "Raw Read Error Count"; + break; + case VS_ATTR_ID_GROWN_BAD_BLOCK_COUNT: + return "Bad NAND block count"; + break; + case VS_ATTR_ID_END_2_END_CORRECTION_COUNT: + return "SSD End to end correction counts"; + break; + case VS_ATTR_ID_MIN_MAX_WEAR_RANGE_COUNT: + return "User data erase counts"; + break; + case VS_ATTR_ID_REFRESH_COUNT: + return "Refresh count"; + break; + case VS_ATTR_ID_BAD_BLOCK_COUNT_USER: + return "User data erase fail count"; + break; + case VS_ATTR_ID_BAD_BLOCK_COUNT_SYSTEM: + return "System area erase fail count"; + break; + case VS_ATTR_ID_THERMAL_THROTTLING_STATUS: + return "Thermal throttling status and count"; + break; + case VS_ATTR_ID_ALL_PCIE_CORRECTABLE_ERROR_COUNT: + return "PCIe Correctable Error count"; + break; + case VS_ATTR_ID_ALL_PCIE_UNCORRECTABLE_ERROR_COUNT: + return "PCIe Uncorrectable Error count"; + break; + case VS_ATTR_ID_INCOMPLETE_SHUTDOWN_COUNT: + return "Incomplete shutdowns"; + break; + case VS_ATTR_ID_GB_ERASED_LSB: + return "LSB of Flash GB erased"; + break; + case VS_ATTR_ID_GB_ERASED_MSB: + return "MSB of Flash GB erased"; + break; + case VS_ATTR_ID_LIFETIME_DEVSLEEP_EXIT_COUNT: + return "LIFETIME_DEV_SLEEP_EXIT_COUNT"; + break; + case VS_ATTR_ID_LIFETIME_ENTERING_PS4_COUNT: + return "LIFETIME_ENTERING_PS4_COUNT"; + break; + case VS_ATTR_ID_LIFETIME_ENTERING_PS3_COUNT: + return "LIFETIME_ENTERING_PS3_COUNT"; + break; + case VS_ATTR_ID_RETIRED_BLOCK_COUNT: + return "Retired block count"; /*VS_ATTR_ID_RETIRED_BLOCK_COUNT*/ + break; + case VS_ATTR_ID_PROGRAM_FAILURE_COUNT: + return "Program fail count"; + break; + case VS_ATTR_ID_ERASE_FAIL_COUNT: + return "Erase Fail Count"; + break; + case VS_ATTR_ID_AVG_ERASE_COUNT: + return "System data % used"; + break; + case VS_ATTR_ID_UNEXPECTED_POWER_LOSS_COUNT: + return "Unexpected power loss count"; + break; + case VS_ATTR_ID_WEAR_RANGE_DELTA: + return "Wear range delta"; + break; + case VS_ATTR_ID_SATA_INTERFACE_DOWNSHIFT_COUNT: + return "PCIE_INTF_DOWNSHIFT_COUNT"; + break; + case VS_ATTR_ID_END_TO_END_CRC_ERROR_COUNT: + return "E2E_CRC_ERROR_COUNT"; + break; + case VS_ATTR_ID_UNCORRECTABLE_READ_ERRORS: + return "Uncorrectable Read Error Count"; + break; + case VS_ATTR_ID_MAX_LIFE_TEMPERATURE: + return "Max lifetime temperature";/*VS_ATTR_ID_MAX_LIFE_TEMPERATURE for extended*/ + break; + case VS_ATTR_ID_RAISE_ECC_CORRECTABLE_ERROR_COUNT: + return "RAIS_ECC_CORRECT_ERR_COUNT"; + break; + case VS_ATTR_ID_UNCORRECTABLE_RAISE_ERRORS: + return "Uncorrectable read error count";/*VS_ATTR_ID_UNCORRECTABLE_RAISE_ERRORS*/ + break; + case VS_ATTR_ID_DRIVE_LIFE_PROTECTION_STATUS: + return "DRIVE_LIFE_PROTECTION_STATUS"; + break; + case VS_ATTR_ID_REMAINING_SSD_LIFE: + return "Remaining SSD life"; + break; + case VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB: + return "LSB of Physical (NAND) bytes written"; + break; + case VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB: + return "MSB of Physical (NAND) bytes written"; + break; + case VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB: + return "LSB of Physical (HOST) bytes written"; + break; + case VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB: + return "MSB of Physical (HOST) bytes written"; + break; + case VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB: + return "LSB of Physical (NAND) bytes read"; + break; + case VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB: + return "MSB of Physical (NAND) bytes read"; + break; + case VS_ATTR_ID_FREE_SPACE: + return "Free Space"; + break; + case VS_ATTR_ID_TRIM_COUNT_LSB: + return "LSB of Trim count"; + break; + case VS_ATTR_ID_TRIM_COUNT_MSB: + return "MSB of Trim count"; + break; + case VS_ATTR_ID_OP_PERCENTAGE: + return "OP percentage"; + break; + case VS_ATTR_ID_MAX_SOC_LIFE_TEMPERATURE: + return "Max lifetime SOC temperature"; + break; + default: + return "Un-Known"; + } +} + +static __u64 smart_attribute_vs(__u16 verNo, SmartVendorSpecific attr) +{ + __u64 val = 0; + vendor_smart_attribute_data *attrVendor; + + /** + * These are all Vendor A specific attributes. + */ + if (verNo >= EXTENDED_SMART_VERSION_VENDOR1) { + attrVendor = (vendor_smart_attribute_data *)&attr; + memcpy(&val, &(attrVendor->LSDword), sizeof(val)); + return val; + } else + return le32_to_cpu(attr.Raw0_3); +} + +static void print_smart_log(__u16 verNo, SmartVendorSpecific attr, int lastAttr) +{ + static __u64 lsbGbErased = 0, msbGbErased = 0, lsbLifWrtToFlash = 0, msbLifWrtToFlash = 0, + lsbLifWrtFrmHost = 0, msbLifWrtFrmHost = 0, lsbLifRdToHost = 0, msbLifRdToHost = 0, lsbTrimCnt = 0, msbTrimCnt = 0; + char buf[40] = {0}; + char strBuf[35] = {0}; + int hideAttr = 0; + + if (attr.AttributeNumber == VS_ATTR_ID_GB_ERASED_LSB) { + lsbGbErased = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_GB_ERASED_MSB) { + msbGbErased = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB) { + lsbLifWrtToFlash = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB) { + msbLifWrtToFlash = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB) { + lsbLifWrtFrmHost = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB) { + msbLifWrtFrmHost = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB) { + lsbLifRdToHost = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB) { + msbLifRdToHost = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_TRIM_COUNT_LSB) { + lsbTrimCnt = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_TRIM_COUNT_MSB) { + msbTrimCnt = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if ((attr.AttributeNumber != 0) && (hideAttr != 1)) { + printf("%-40s", print_ext_smart_id(attr.AttributeNumber)); + printf("%-15d", attr.AttributeNumber ); + printf(" 0x%016"PRIx64"", (uint64_t)smart_attribute_vs(verNo, attr)); + printf("\n"); + } + + if (lastAttr == 1) { + + sprintf(strBuf, "%s", (print_ext_smart_id(VS_ATTR_ID_GB_ERASED_LSB) + 7)); + printf("%-40s", strBuf); + + printf("%-15d", VS_ATTR_ID_GB_ERASED_MSB << 8 | VS_ATTR_ID_GB_ERASED_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbGbErased, (uint64_t)lsbGbErased); + printf(" %s", buf); + printf("\n"); + + sprintf(strBuf, "%s", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB) + 7)); + printf("%-40s", strBuf); + + printf("%-15d", VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB << 8 | VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifWrtToFlash, (uint64_t)lsbLifWrtToFlash); + printf(" %s", buf); + printf("\n"); + + sprintf(strBuf, "%s", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB) + 7)); + printf("%-40s", strBuf); + + printf("%-15d", VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB << 8 | VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifWrtFrmHost, (uint64_t)lsbLifWrtFrmHost); + printf(" %s", buf); + printf("\n"); + + sprintf(strBuf, "%s", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB) + 7)); + printf("%-40s", strBuf); + + printf("%-15d", VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB << 8 | VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifRdToHost, (uint64_t)lsbLifRdToHost); + printf(" %s", buf); + printf("\n"); + + sprintf(strBuf, "%s", (print_ext_smart_id(VS_ATTR_ID_TRIM_COUNT_LSB) + 7)); + printf("%-40s", strBuf); + + printf("%-15d", VS_ATTR_ID_TRIM_COUNT_MSB << 8 | VS_ATTR_ID_TRIM_COUNT_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbTrimCnt, (uint64_t)lsbTrimCnt); + printf(" %s", buf); + printf("\n"); + + } +} + +static void json_print_smart_log(struct json_object *root, + EXTENDED_SMART_INFO_T *ExtdSMARTInfo ) +{ + /*struct json_object *root; */ + struct json_array *lbafs; + int index = 0; + + static __u64 lsbGbErased = 0, msbGbErased = 0, lsbLifWrtToFlash = 0, msbLifWrtToFlash = 0, + lsbLifWrtFrmHost = 0, msbLifWrtFrmHost = 0, lsbLifRdToHost = 0, msbLifRdToHost = 0, lsbTrimCnt = 0, msbTrimCnt = 0; + char buf[40] = {0}; + + /*root = json_create_object();*/ + lbafs = json_create_array(); + json_object_add_value_array(root, "Extended-SMART-Attributes", lbafs); + + for (index =0; index < NUMBER_EXTENDED_SMART_ATTRIBUTES; index++) { + struct json_object *lbaf = json_create_object(); + if (ExtdSMARTInfo->vendorData[index].AttributeNumber != 0) { + json_object_add_value_string(lbaf, "attribute_name", print_ext_smart_id(ExtdSMARTInfo->vendorData[index].AttributeNumber)); + json_object_add_value_int(lbaf, "attribute_id",ExtdSMARTInfo->vendorData[index].AttributeNumber); + json_object_add_value_int(lbaf, "attribute_value", smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index])); + json_array_add_value_object(lbafs, lbaf); + + if(ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_GB_ERASED_LSB) + lsbGbErased = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if(ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_GB_ERASED_MSB) + msbGbErased = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if(ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB) + lsbLifWrtToFlash = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if(ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB) + msbLifWrtToFlash = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if(ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB) + lsbLifWrtFrmHost = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if(ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB) + msbLifWrtFrmHost = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if(ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB) + lsbLifRdToHost = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if(ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB) + msbLifRdToHost = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if(ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_TRIM_COUNT_LSB) + lsbTrimCnt = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if(ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_TRIM_COUNT_MSB) + msbTrimCnt = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + } + } + + struct json_object *lbaf = json_create_object(); + + json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_GB_ERASED_LSB) + 7)); + + json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_GB_ERASED_MSB << 8 | VS_ATTR_ID_GB_ERASED_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbGbErased, (uint64_t)lsbGbErased); + json_object_add_value_string(lbaf, "attribute_value", buf); + json_array_add_value_object(lbafs, lbaf); + + + lbaf = json_create_object(); + + json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB) + 7)); + + json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB << 8 | VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifWrtToFlash, (uint64_t)lsbLifWrtToFlash); + json_object_add_value_string(lbaf, "attribute_value", buf); + json_array_add_value_object(lbafs, lbaf); + + + lbaf = json_create_object(); + + json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB) + 7)); + + json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB << 8 | VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifWrtFrmHost, (uint64_t)lsbLifWrtFrmHost); + json_object_add_value_string(lbaf, "attribute_value", buf); + json_array_add_value_object(lbafs, lbaf); + + + lbaf = json_create_object(); + + json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB) + 7)); + + json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB << 8 | VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifRdToHost, (uint64_t)lsbLifRdToHost); + json_object_add_value_string(lbaf, "attribute_value", buf); + json_array_add_value_object(lbafs, lbaf); + + + lbaf = json_create_object(); + + json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_TRIM_COUNT_LSB) + 7)); + + json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_TRIM_COUNT_MSB << 8 | VS_ATTR_ID_TRIM_COUNT_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbTrimCnt, (uint64_t)lsbTrimCnt); + json_object_add_value_string(lbaf, "attribute_value", buf); + json_array_add_value_object(lbafs, lbaf); + + /* + json_print_object(root, NULL); + printf("\n"); + */ +} + +static void print_smart_log_CF(vendor_log_page_CF *pLogPageCF) +{ + __u64 currentTemp, maxTemp; + printf("\n\nSeagate DRAM Supercap SMART Attributes :\n"); + printf("%-39s %-19s \n", "Description", "Supercap Attributes"); + + printf("%-40s", "Super-cap current temperature"); + currentTemp = pLogPageCF->AttrCF.SuperCapCurrentTemperature; + /*currentTemp = currentTemp ? currentTemp - 273 : 0;*/ + printf(" 0x%016"PRIx64"", le64_to_cpu(currentTemp)); + printf("\n"); + + maxTemp = pLogPageCF->AttrCF.SuperCapMaximumTemperature; + /*maxTemp = maxTemp ? maxTemp - 273 : 0;*/ + printf("%-40s", "Super-cap maximum temperature"); + printf(" 0x%016"PRIx64"", le64_to_cpu(maxTemp)); + printf("\n"); + + printf("%-40s", "Super-cap status"); + printf(" 0x%016"PRIx64"", le64_to_cpu(pLogPageCF->AttrCF.SuperCapStatus)); + printf("\n"); + + printf("%-40s", "Data units read to DRAM namespace"); + printf(" 0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageCF->AttrCF.DataUnitsReadToDramNamespace.MS__u64), + le64_to_cpu(pLogPageCF->AttrCF.DataUnitsReadToDramNamespace.LS__u64)); + printf("\n"); + + printf("%-40s", "Data units written to DRAM namespace"); + printf(" 0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageCF->AttrCF.DataUnitsWrittenToDramNamespace.MS__u64), + le64_to_cpu(pLogPageCF->AttrCF.DataUnitsWrittenToDramNamespace.LS__u64)); + printf("\n"); + + printf("%-40s", "DRAM correctable error count"); + printf(" 0x%016"PRIx64"", le64_to_cpu(pLogPageCF->AttrCF.DramCorrectableErrorCount)); + printf("\n"); + + printf("%-40s", "DRAM uncorrectable error count"); + printf(" 0x%016"PRIx64"", le64_to_cpu(pLogPageCF->AttrCF.DramUncorrectableErrorCount)); + printf("\n"); + +} + +static void json_print_smart_log_CF(struct json_object *root, + vendor_log_page_CF *pLogPageCF) +{ + /*struct json_object *root;*/ + struct json_array *logPages; + unsigned int currentTemp, maxTemp; + char buf[40]; + + /*root = json_create_object(); */ + + logPages = json_create_array(); + json_object_add_value_array(root, "DRAM Supercap SMART Attributes", logPages); + struct json_object *lbaf = json_create_object(); + + currentTemp = pLogPageCF->AttrCF.SuperCapCurrentTemperature; + /*currentTemp = currentTemp ? currentTemp - 273 : 0;*/ + json_object_add_value_string(lbaf, "attribute_name", "Super-cap current temperature"); + json_object_add_value_int(lbaf, "attribute_value", currentTemp); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + maxTemp = pLogPageCF->AttrCF.SuperCapMaximumTemperature; + /*maxTemp = maxTemp ? maxTemp - 273 : 0;*/ + json_object_add_value_string(lbaf, "attribute_name", "Super-cap maximum temperature"); + json_object_add_value_int(lbaf, "attribute_value", maxTemp); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Super-cap status"); + json_object_add_value_int(lbaf, "attribute_value", pLogPageCF->AttrCF.SuperCapStatus); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Data units read to DRAM namespace"); + memset(buf, 0, sizeof(buf)); + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageCF->AttrCF.DataUnitsReadToDramNamespace.MS__u64), + le64_to_cpu(pLogPageCF->AttrCF.DataUnitsReadToDramNamespace.LS__u64)); + json_object_add_value_string(lbaf, "attribute_value", buf); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Data units written to DRAM namespace"); + memset(buf, 0, sizeof(buf)); + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageCF->AttrCF.DataUnitsWrittenToDramNamespace.MS__u64), + le64_to_cpu(pLogPageCF->AttrCF.DataUnitsWrittenToDramNamespace.LS__u64)); + json_object_add_value_string(lbaf, "attribute_value", buf); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "DRAM correctable error count"); + json_object_add_value_int(lbaf, "attribute_value", pLogPageCF->AttrCF.DramCorrectableErrorCount); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "DRAM uncorrectable error count"); + json_object_add_value_int(lbaf, "attribute_value", pLogPageCF->AttrCF.DramUncorrectableErrorCount); + json_array_add_value_object(logPages, lbaf); + + /* + json_print_object(root, NULL); + printf("\n"); + */ +} + +static int vs_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + EXTENDED_SMART_INFO_T ExtdSMARTInfo; + vendor_log_page_CF logPageCF; + int fd; + struct json_object *root; + struct json_array *lbafs; + struct json_object *lbafs_ExtSmart, *lbafs_DramSmart; + root = json_create_object(); + lbafs = json_create_array(); + + const char *desc = "Retrieve Seagate Extended SMART information for the given device "; + const char *output_format = "output in binary format"; + int err, index=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), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (strcmp(cfg.output_format,"json")) + printf("Seagate Extended SMART Information :\n"); + + err = nvme_get_log(fd, 1, 0xC4, false, sizeof(ExtdSMARTInfo), &ExtdSMARTInfo); + if (!err) { + if (strcmp(cfg.output_format,"json")) { + printf("%-39s %-15s %-19s \n", "Description", "Ext-Smart-Id", "Ext-Smart-Value"); + for(index=0; index<80; index++) + printf("-"); + printf("\n"); + for(index = 0; index < NUMBER_EXTENDED_SMART_ATTRIBUTES; index++) + print_smart_log(ExtdSMARTInfo.Version, ExtdSMARTInfo.vendorData[index], index == (NUMBER_EXTENDED_SMART_ATTRIBUTES - 1)); + + } else { + lbafs_ExtSmart = json_create_object(); + json_print_smart_log(lbafs_ExtSmart, &ExtdSMARTInfo); + + json_object_add_value_array(root, "SMART-Attributes", lbafs); + json_array_add_value_object(lbafs, lbafs_ExtSmart); + } + + /** + * Next get Log Page 0xCF + */ + + err = nvme_get_log(fd, 1, 0xCF, false, sizeof(logPageCF), &logPageCF); + if (!err) { + if(strcmp(cfg.output_format,"json")) { + /*printf("Seagate DRAM Supercap SMART Attributes :\n");*/ + + print_smart_log_CF(&logPageCF); + } else { + lbafs_DramSmart = json_create_object(); + json_print_smart_log_CF(lbafs_DramSmart, &logPageCF); + json_array_add_value_object(lbafs, lbafs_DramSmart); + json_print_object(root, NULL); + } + } else if (!strcmp(cfg.output_format, "json")) + json_print_object(root, NULL); + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + + return err; +} + +/*EOF Extended-SMART Information */ + +/*************************************** + * Temperature-Stats information + ***************************************/ +static void json_temp_stats(__u32 temperature, __u32 PcbTemp, __u32 SocTemp, __u32 maxTemperature, + __u32 MaxSocTemp, __u32 cf_err, __u32 scCurrentTemp, __u32 scMaxTem) +{ + struct json_object *root; + root = json_create_object(); + + json_object_add_value_int(root, "Current temperature", temperature); + json_object_add_value_int(root, "Current PCB temperature", PcbTemp); + json_object_add_value_int(root, "Current SOC temperature", SocTemp); + json_object_add_value_int(root, "Highest temperature", maxTemperature); + json_object_add_value_int(root, "Max SOC temperature", MaxSocTemp); + if(!cf_err) { + json_object_add_value_int(root, "SuperCap Current temperature", scCurrentTemp); + json_object_add_value_int(root, "SuperCap Max temperature", scMaxTem); + } + + json_print_object(root, NULL); + printf("\n"); + +} +static int temp_stats(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct nvme_smart_log smart_log; + EXTENDED_SMART_INFO_T ExtdSMARTInfo; + vendor_log_page_CF logPageCF; + + int fd; + int err, cf_err; + int index; + const char *desc = "Retrieve Seagate Temperature Stats information for the given device "; + const char *output_format = "output in binary format"; + unsigned int temperature = 0, PcbTemp = 0, SocTemp = 0, scCurrentTemp = 0, scMaxTemp = 0; + unsigned long long maxTemperature = 0, MaxSocTemp = 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), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) { + printf ("\nDevice not found \n");; + return -1; + } + + if(strcmp(cfg.output_format,"json")) + printf("Seagate Temperature Stats Information :\n"); + /*STEP-1 : Get Current Temperature from SMART */ + err = nvme_smart_log(fd, 0xffffffff, &smart_log); + if (!err) { + temperature = ((smart_log.temperature[1] << 8) | smart_log.temperature[0]); + temperature = temperature ? temperature - 273 : 0; + PcbTemp = le16_to_cpu(smart_log.temp_sensor[0]); + PcbTemp = PcbTemp ? PcbTemp - 273 : 0; + SocTemp = le16_to_cpu(smart_log.temp_sensor[1]); + SocTemp = SocTemp ? SocTemp - 273 : 0; + if (strcmp(cfg.output_format,"json")) { + printf("%-20s : %u C\n", "Current Temperature", temperature); + printf("%-20s : %u C\n", "Current PCB Temperature", PcbTemp); + printf("%-20s : %u C\n", "Current SOC Temperature", SocTemp); + } + } + + /* STEP-2 : Get Max temperature form Ext SMART-id 194 */ + err = nvme_get_log(fd, 1, 0xC4, false, sizeof(ExtdSMARTInfo), &ExtdSMARTInfo); + if (!err) { + for(index = 0; index < NUMBER_EXTENDED_SMART_ATTRIBUTES; index++) { + if (ExtdSMARTInfo.vendorData[index].AttributeNumber == VS_ATTR_ID_MAX_LIFE_TEMPERATURE) { + maxTemperature = smart_attribute_vs(ExtdSMARTInfo.Version, ExtdSMARTInfo.vendorData[index]); + maxTemperature = maxTemperature ? maxTemperature - 273 : 0; + if (strcmp(cfg.output_format,"json")) + printf("%-20s : %d C\n", "Highest Temperature", (unsigned int)maxTemperature); + } + + if (ExtdSMARTInfo.vendorData[index].AttributeNumber == VS_ATTR_ID_MAX_SOC_LIFE_TEMPERATURE) { + MaxSocTemp = smart_attribute_vs(ExtdSMARTInfo.Version, ExtdSMARTInfo.vendorData[index]); + MaxSocTemp = MaxSocTemp ? MaxSocTemp - 273 : 0; + if (strcmp(cfg.output_format,"json")) + printf("%-20s : %d C\n", "Max SOC Temperature", (unsigned int)MaxSocTemp); + } + } + } + else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + + cf_err = nvme_get_log(fd, 1, 0xCF, false, sizeof(ExtdSMARTInfo), &logPageCF); + + if(!cf_err) { + scCurrentTemp = logPageCF.AttrCF.SuperCapCurrentTemperature; + scCurrentTemp = scCurrentTemp ? scCurrentTemp - 273 : 0; + printf("%-20s : %d C\n", "Super-cap Current Temperature", scCurrentTemp); + + scMaxTemp = logPageCF.AttrCF.SuperCapMaximumTemperature; + scMaxTemp = scMaxTemp ? scMaxTemp - 273 : 0; + printf("%-20s : %d C\n", "Super-cap Max Temperature", scMaxTemp); + } + + if(!strcmp(cfg.output_format,"json")) + json_temp_stats(temperature, PcbTemp, SocTemp, maxTemperature, MaxSocTemp, cf_err, scCurrentTemp, scMaxTemp); + + return err; +} +/* EOF Temperature Stats information */ + +/*************************************** + * PCIe error-log information + ***************************************/ +static void print_vs_pcie_error_log(pcie_error_log_page pcieErrorLog) +{ + __u32 correctPcieEc = 0; + __u32 uncorrectPcieEc = 0; + correctPcieEc = pcieErrorLog.BadDllpErrCnt + pcieErrorLog.BadTlpErrCnt + + pcieErrorLog.RcvrErrCnt + pcieErrorLog.ReplayTOErrCnt + + pcieErrorLog.ReplayNumRolloverErrCnt; + + uncorrectPcieEc = pcieErrorLog.FCProtocolErrCnt + pcieErrorLog.DllpProtocolErrCnt + + pcieErrorLog.CmpltnTOErrCnt + pcieErrorLog.RcvrQOverflowErrCnt + + pcieErrorLog.UnexpectedCplTlpErrCnt + pcieErrorLog.CplTlpURErrCnt + + pcieErrorLog.CplTlpCAErrCnt + pcieErrorLog.ReqCAErrCnt + + pcieErrorLog.ReqURErrCnt + pcieErrorLog.EcrcErrCnt + + pcieErrorLog.MalformedTlpErrCnt + pcieErrorLog.CplTlpPoisonedErrCnt + + pcieErrorLog.MemRdTlpPoisonedErrCnt; + + printf("%-45s : %u\n", "PCIe Correctable Error Count", correctPcieEc); + printf("%-45s : %u\n", "PCIe Un-Correctable Error Count", uncorrectPcieEc); + printf("%-45s : %u\n", "Unsupported Request Error Status (URES)", pcieErrorLog.ReqURErrCnt); + printf("%-45s : %u\n", "ECRC Error Status (ECRCES)", pcieErrorLog.EcrcErrCnt); + printf("%-45s : %u\n", "Malformed TLP Status (MTS)", pcieErrorLog.MalformedTlpErrCnt); + printf("%-45s : %u\n", "Receiver Overflow Status (ROS)", pcieErrorLog.RcvrQOverflowErrCnt); + printf("%-45s : %u\n", "Unexpected Completion Status(UCS)", pcieErrorLog.UnexpectedCplTlpErrCnt); + printf("%-45s : %u\n", "Completion Timeout Status (CTS)", pcieErrorLog.CmpltnTOErrCnt); + printf("%-45s : %u\n", "Flow Control Protocol Error Status (FCPES)", pcieErrorLog.FCProtocolErrCnt); + printf("%-45s : %u\n", "Poisoned TLP Status (PTS)", pcieErrorLog.MemRdTlpPoisonedErrCnt); + printf("%-45s : %u\n", "Data Link Protocol Error Status(DLPES)", pcieErrorLog.DllpProtocolErrCnt); + printf("%-45s : %u\n", "Replay Timer Timeout Status(RTS)", pcieErrorLog.ReplayTOErrCnt); + printf("%-45s : %u\n", "Replay_NUM Rollover Status(RRS)", pcieErrorLog.ReplayNumRolloverErrCnt); + printf("%-45s : %u\n", "Bad DLLP Status (BDS)", pcieErrorLog.BadDllpErrCnt); + printf("%-45s : %u\n", "Bad TLP Status (BTS)", pcieErrorLog.BadTlpErrCnt); + printf("%-45s : %u\n", "Receiver Error Status (RES)", pcieErrorLog.RcvrErrCnt); + printf("%-45s : %u\n", "Cpl TLP Unsupported Request Error Count", pcieErrorLog.CplTlpURErrCnt); + printf("%-45s : %u\n", "Cpl TLP Completion Abort Error Count", pcieErrorLog.CplTlpCAErrCnt); + printf("%-45s : %u\n", "Cpl TLP Poisoned Error Count", pcieErrorLog.CplTlpPoisonedErrCnt); + printf("%-45s : %u\n", "Request Completion Abort Error Count", pcieErrorLog.ReqCAErrCnt); + printf("%-45s : %s\n", "Advisory Non-Fatal Error Status(ANFES)", "Not Supported"); + printf("%-45s : %s\n", "Completer Abort Status (CAS)", "Not Supported"); +} + +static void json_vs_pcie_error_log(pcie_error_log_page pcieErrorLog) +{ + struct json_object *root; + root = json_create_object(); + __u32 correctPcieEc = 0; + __u32 uncorrectPcieEc = 0; + correctPcieEc = pcieErrorLog.BadDllpErrCnt + pcieErrorLog.BadTlpErrCnt + + pcieErrorLog.RcvrErrCnt + pcieErrorLog.ReplayTOErrCnt + + pcieErrorLog.ReplayNumRolloverErrCnt; + + uncorrectPcieEc = pcieErrorLog.FCProtocolErrCnt + pcieErrorLog.DllpProtocolErrCnt + + pcieErrorLog.CmpltnTOErrCnt + pcieErrorLog.RcvrQOverflowErrCnt + + pcieErrorLog.UnexpectedCplTlpErrCnt + pcieErrorLog.CplTlpURErrCnt + + pcieErrorLog.CplTlpCAErrCnt + pcieErrorLog.ReqCAErrCnt + + pcieErrorLog.ReqURErrCnt + pcieErrorLog.EcrcErrCnt + + pcieErrorLog.MalformedTlpErrCnt + pcieErrorLog.CplTlpPoisonedErrCnt + + pcieErrorLog.MemRdTlpPoisonedErrCnt; + + json_object_add_value_int(root, "PCIe Correctable Error Count", correctPcieEc); + json_object_add_value_int(root, "PCIe Un-Correctable Error Count", uncorrectPcieEc); + json_object_add_value_int(root, "Unsupported Request Error Status (URES)", pcieErrorLog.ReqURErrCnt); + json_object_add_value_int(root, "ECRC Error Status (ECRCES)", pcieErrorLog.EcrcErrCnt); + json_object_add_value_int(root, "Malformed TLP Status (MTS)", pcieErrorLog.MalformedTlpErrCnt); + json_object_add_value_int(root, "Receiver Overflow Status (ROS)", pcieErrorLog.RcvrQOverflowErrCnt); + json_object_add_value_int(root, "Unexpected Completion Status(UCS)", pcieErrorLog.UnexpectedCplTlpErrCnt); + json_object_add_value_int(root, "Completion Timeout Status (CTS)", pcieErrorLog.CmpltnTOErrCnt); + json_object_add_value_int(root, "Flow Control Protocol Error Status (FCPES)", pcieErrorLog.FCProtocolErrCnt); + json_object_add_value_int(root, "Poisoned TLP Status (PTS)", pcieErrorLog.MemRdTlpPoisonedErrCnt); + json_object_add_value_int(root, "Data Link Protocol Error Status(DLPES)", pcieErrorLog.DllpProtocolErrCnt); + json_object_add_value_int(root, "Replay Timer Timeout Status(RTS)", pcieErrorLog.ReplayTOErrCnt); + json_object_add_value_int(root, "Replay_NUM Rollover Status(RRS)", pcieErrorLog.ReplayNumRolloverErrCnt); + json_object_add_value_int(root, "Bad DLLP Status (BDS)", pcieErrorLog.BadDllpErrCnt); + json_object_add_value_int(root, "Bad TLP Status (BTS)", pcieErrorLog.BadTlpErrCnt); + json_object_add_value_int(root, "Receiver Error Status (RES)", pcieErrorLog.RcvrErrCnt); + json_object_add_value_int(root, "Cpl TLP Unsupported Request Error Count", pcieErrorLog.CplTlpURErrCnt); + json_object_add_value_int(root, "Cpl TLP Completion Abort Error Count", pcieErrorLog.CplTlpCAErrCnt); + json_object_add_value_int(root, "Cpl TLP Poisoned Error Count", pcieErrorLog.CplTlpPoisonedErrCnt); + json_object_add_value_int(root, "Request Completion Abort Error Count", pcieErrorLog.ReqCAErrCnt); + json_print_object(root, NULL); + printf("\n"); +} + +static int vs_pcie_error_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + pcie_error_log_page pcieErrorLog; + int fd; + + const char *desc = "Retrieve Seagate PCIe error counters for the given device "; + const char *output_format = "output in binary format"; + int err; + struct config { + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if(strcmp(cfg.output_format,"json")) + printf("Seagate PCIe error counters Information :\n"); + + err = nvme_get_log(fd, 1, 0xCB, false, sizeof(pcieErrorLog), &pcieErrorLog); + if (!err) { + if(strcmp(cfg.output_format,"json")) { + print_vs_pcie_error_log(pcieErrorLog); + } else + json_vs_pcie_error_log(pcieErrorLog); + + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(err), err); + + return err; +} +/* EOF PCIE error-log information */ + +static int vs_clr_pcie_correctable_errs(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Clear Seagate PCIe Correctable counters for the given device "; + const char *save = "specifies that the controller shall save the attribute"; + int err, fd; + __u32 result; + void *buf = NULL; + + struct config { + int save; + }; + + struct config cfg = { + .save = 0, + }; + + OPT_ARGS(opts) = { + OPT_FLAG("save", 's', &cfg.save, save), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + + err = nvme_set_feature(fd, 0, 0xE1, 0xCB, 0, cfg.save, 0, buf, &result); + + if (err < 0) { + perror("set-feature"); + return errno; + } + + return err; + +} + +static int get_host_tele(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Capture the Telemetry Host-Initiated Data in either " \ + "hex-dump (default) or binary format"; + const char *namespace_id = "desired namespace"; + const char *log_specific = "1 - controller shall capture Data representing the internal " \ + "state of the controller at the time the command is processed. " \ + "0 - controller shall not update the Telemetry Host Initiated Data."; + const char *raw = "output in raw format"; + int err, fd, dump_fd; + struct nvme_temetry_log_hdr tele_log; + __le64 offset = 0; + int blkCnt, maxBlk = 0, blksToGet; + unsigned char *log; + + struct config { + __u32 namespace_id; + __u32 log_id; + int raw_binary; + }; + + struct config cfg = { + .namespace_id = 0xffffffff, + .log_id = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("log_specific", 'i', &cfg.log_id, log_specific), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + dump_fd = STDOUT_FILENO; + cfg.log_id = (cfg.log_id << 8) | 0x07; + err = nvme_get_log13(fd, cfg.namespace_id, cfg.log_id, + NVME_NO_LOG_LSP, offset, 0, false, + sizeof(tele_log), (void *)(&tele_log)); + if (!err) { + maxBlk = tele_log.tele_data_area3; + offset += 512; + + if (!cfg.raw_binary) { + printf("Device:%s log-id:%d namespace-id:%#x\n", + devicename, cfg.log_id, + cfg.namespace_id); + printf("Data Block 1 Last Block:%d Data Block 2 Last Block:%d Data Block 3 Last Block:%d\n", + tele_log.tele_data_area1, tele_log.tele_data_area2, tele_log.tele_data_area3); + + d((unsigned char *)(&tele_log), sizeof(tele_log), 16, 1); + } else + seaget_d_raw((unsigned char *)(&tele_log), sizeof(tele_log), dump_fd); + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + else + perror("log page"); + + blkCnt = 0; + + while(blkCnt < maxBlk) { + blksToGet = ((maxBlk - blkCnt) >= TELEMETRY_BLOCKS_TO_READ) ? TELEMETRY_BLOCKS_TO_READ : (maxBlk - blkCnt); + + if(blksToGet == 0) + return err; + + log = malloc(blksToGet * 512); + + if (!log) { + fprintf(stderr, "could not alloc buffer for log\n"); + return EINVAL; + } + + memset(log, 0, blksToGet * 512); + + err = nvme_get_log13(fd, cfg.namespace_id, cfg.log_id, + NVME_NO_LOG_LSP, offset, 0, false, + blksToGet * 512, (void *)log); + if (!err) { + offset += blksToGet * 512; + + if (!cfg.raw_binary) { + printf("\nBlock # :%d to %d\n", blkCnt + 1, blkCnt + blksToGet); + + d((unsigned char *)log, blksToGet * 512, 16, 1); + } else + seaget_d_raw((unsigned char *)log, blksToGet * 512, dump_fd); + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + else + perror("log page"); + + blkCnt += blksToGet; + + free(log); + } + + return err; +} + +static int get_ctrl_tele(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Capture the Telemetry Controller-Initiated Data in either " \ + "hex-dump (default) or binary format"; + const char *namespace_id = "desired namespace"; + const char *raw = "output in raw format"; + int err, fd, dump_fd; + struct nvme_temetry_log_hdr tele_log; + __le64 offset = 0; + __u16 log_id; + int blkCnt, maxBlk = 0, blksToGet; + unsigned char *log; + + struct config { + __u32 namespace_id; + int raw_binary; + }; + + struct config cfg = { + .namespace_id = 0xffffffff, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + dump_fd = STDOUT_FILENO; + + log_id = 0x08; + err = nvme_get_log13(fd, cfg.namespace_id, log_id, + NVME_NO_LOG_LSP, offset, 0, false, + sizeof(tele_log), (void *)(&tele_log)); + if (!err) { + maxBlk = tele_log.tele_data_area3; + offset += 512; + + if (!cfg.raw_binary) { + printf("Device:%s namespace-id:%#x\n", + devicename, cfg.namespace_id); + printf("Data Block 1 Last Block:%d Data Block 2 Last Block:%d Data Block 3 Last Block:%d\n", + tele_log.tele_data_area1, tele_log.tele_data_area2, tele_log.tele_data_area3); + + d((unsigned char *)(&tele_log), sizeof(tele_log), 16, 1); + } else + seaget_d_raw((unsigned char *)(&tele_log), sizeof(tele_log), dump_fd); + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + else + perror("log page"); + + blkCnt = 0; + + while(blkCnt < maxBlk) { + blksToGet = ((maxBlk - blkCnt) >= TELEMETRY_BLOCKS_TO_READ) ? TELEMETRY_BLOCKS_TO_READ : (maxBlk - blkCnt); + + if(blksToGet == 0) + return err; + + log = malloc(blksToGet * 512); + + if (!log) { + fprintf(stderr, "could not alloc buffer for log\n"); + return EINVAL; + } + + memset(log, 0, blksToGet * 512); + + err = nvme_get_log13(fd, cfg.namespace_id, log_id, + NVME_NO_LOG_LSP, offset, 0, false, + blksToGet * 512, (void *)log); + if (!err) { + offset += blksToGet * 512; + + if (!cfg.raw_binary) { + printf("\nBlock # :%d to %d\n", blkCnt + 1, blkCnt + blksToGet); + + d((unsigned char *)log, blksToGet * 512, 16, 1); + } else + seaget_d_raw((unsigned char *)log, blksToGet * 512, dump_fd); + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + else + perror("log page"); + + blkCnt += blksToGet; + + free(log); + } + return err; + +} + +void seaget_d_raw(unsigned char *buf, int len, int fd) +{ + /********************* + int i; + fflush(stdout); + for (i = 0; i < len; i++) + putchar(*(buf+i)); + *********************/ + + if (write(fd, (void *)buf, len) <= 0) + printf("%s: Write Failed\n",__FUNCTION__); +} + + +static int vs_internal_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Capture the Telemetry Controller-Initiated Data in " \ + "binary format"; + const char *namespace_id = "desired namespace"; + + const char *file = "dump file"; + int err, fd, dump_fd; + int flags = O_WRONLY | O_CREAT; + int mode = S_IRUSR | S_IWUSR |S_IRGRP | S_IWGRP| S_IROTH; + struct nvme_temetry_log_hdr tele_log; + __le64 offset = 0; + __u16 log_id; + int blkCnt, maxBlk = 0, blksToGet; + unsigned char *log; + + struct config { + __u32 namespace_id; + char *file; + }; + + struct config cfg = { + .namespace_id = 0xffffffff, + .file = "", + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_FILE("dump-file", 'f', &cfg.file, file), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + dump_fd = STDOUT_FILENO; + if(strlen(cfg.file)) { + dump_fd = open(cfg.file, flags, mode); + if (dump_fd < 0) { + perror(cfg.file); + return EINVAL; + } + } + + log_id = 0x08; + err = nvme_get_log13(fd, cfg.namespace_id, log_id, + NVME_NO_LOG_LSP, offset, 0, false, + sizeof(tele_log), (void *)(&tele_log)); + if (!err) { + maxBlk = tele_log.tele_data_area3; + offset += 512; + + /* + printf("Data Block 1 Last Block:%d Data Block 2 Last Block:%d Data Block 3 Last Block:%d\n", + tele_log.tele_data_area1, tele_log.tele_data_area2, tele_log.tele_data_area3); + */ + seaget_d_raw((unsigned char *)(&tele_log), sizeof(tele_log), dump_fd); + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + else + perror("log page"); + + blkCnt = 0; + + while(blkCnt < maxBlk) { + blksToGet = ((maxBlk - blkCnt) >= TELEMETRY_BLOCKS_TO_READ) ? TELEMETRY_BLOCKS_TO_READ : (maxBlk - blkCnt); + + if(blksToGet == 0) { + return err; + } + + log = malloc(blksToGet * 512); + + if (!log) { + fprintf(stderr, "could not alloc buffer for log\n"); + return EINVAL; + } + + memset(log, 0, blksToGet * 512); + + err = nvme_get_log13(fd, cfg.namespace_id, log_id, + NVME_NO_LOG_LSP, offset, 0, false, + blksToGet * 512, (void *)log); + if (!err) { + offset += blksToGet * 512; + + seaget_d_raw((unsigned char *)log, blksToGet * 512, dump_fd); + + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + else + perror("log page"); + + blkCnt += blksToGet; + + free(log); + } + + if(strlen(cfg.file)) + close(dump_fd); + + return err; +} + +/*SEAGATE-PLUGIN Version */ +static int seagate_plugin_version(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + printf("Seagate-Plugin version : %d.%d \n", + SEAGATE_PLUGIN_VERSION_MAJOR, + SEAGATE_PLUGIN_VERSION_MINOR); + return 0; +} +/*EOF SEAGATE-PLUGIN Version */ diff --git a/plugins/seagate/seagate-nvme.h b/plugins/seagate/seagate-nvme.h new file mode 100644 index 0000000..f570697 --- /dev/null +++ b/plugins/seagate/seagate-nvme.h @@ -0,0 +1,45 @@ +/* + * Do NOT modify or remove this copyright and license + * + * Copyright (c) 2017-2018 Seagate Technology LLC and/or its Affiliates, All Rights Reserved + * + * ****************************************************************************************** + * + * 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. + * + * \file seagate-nvme.h + * \brief This file defines the functions and macros to make building a nvme-cli seagate plug-in. + */ + +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/seagate/seagate-nvme + +#if !defined(SEAGATE_NVME) || defined(CMD_HEADER_MULTI_READ) +#define SEAGATE_NVME + +#include "cmd.h" + +PLUGIN(NAME("seagate", "Seagate vendor specific extensions"), + COMMAND_LIST( + ENTRY("vs-temperature-stats", "Retrieve Seagate temperature statistics ", temp_stats) + ENTRY("vs-log-page-sup", "Retrieve Seagate Supported Log-pages Information ", log_pages_supp) + ENTRY("vs-smart-add-log", "Retrieve Seagate extended-SMART Information ", vs_smart_log) + ENTRY("vs-pcie-stats", "Retrieve Seagate PCIe error statistics ", vs_pcie_error_log) + ENTRY("clear-pcie-correctable-errors", "Clear Seagate PCIe error statistics ", vs_clr_pcie_correctable_errs) + ENTRY("get-host-tele", "Retrieve Seagate Host-Initiated Telemetry ", get_host_tele) + ENTRY("get-ctrl-tele", "Retrieve Seagate Controller-Initiated Telemetry ", get_ctrl_tele) + ENTRY("vs-internal-log", "Retrieve Seagate Controller-Initiated Telemetry in binary format", vs_internal_log) + ENTRY("plugin-version", "Shows Seagate plugin's version information ", seagate_plugin_version) + ) +); + +#endif +#include "define_cmd.h" diff --git a/plugins/shannon/shannon-nvme.c b/plugins/shannon/shannon-nvme.c new file mode 100644 index 0000000..3aa6f8a --- /dev/null +++ b/plugins/shannon/shannon-nvme.c @@ -0,0 +1,374 @@ +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> + +#include "linux/nvme_ioctl.h" + +#include "common.h" +#include "nvme.h" +#include "nvme-print.h" +#include "nvme-ioctl.h" +#include "json.h" +#include "plugin.h" + +#include "argconfig.h" +#include "suffix.h" + +#define CREATE_CMD +#include "shannon-nvme.h" + +typedef enum { + PROGRAM_FAIL_CNT, + ERASE_FAIL_CNT, + WEARLEVELING_COUNT, + E2E_ERR_CNT, + CRC_ERR_CNT, + TIME_WORKLOAD_MEDIA_WEAR, + TIME_WORKLOAD_HOST_READS, + TIME_WORKLOAD_TIMER, + THERMAL_THROTTLE, + RETRY_BUFFER_OVERFLOW, + PLL_LOCK_LOSS, + NAND_WRITE, + HOST_WRITE, + SRAM_ERROR_CNT, + ADD_SMART_ITEMS, +}addtional_smart_items; + +#pragma pack(push,1) +struct nvme_shannon_smart_log_item { + __u8 rsv1[3]; + __u8 norm; + __u8 rsv2; + union { + __u8 item_val[6]; + struct wear_level { + __le16 min; + __le16 max; + __le16 avg; + } wear_level ; + struct thermal_throttle { + __u8 st; + __u32 count; + } thermal_throttle; + }; + __u8 _resv; +}; +#pragma pack(pop) + +struct nvme_shannon_smart_log { + struct nvme_shannon_smart_log_item items[ADD_SMART_ITEMS]; + __u8 vend_spec_resv; +}; + +static void show_shannon_smart_log(struct nvme_shannon_smart_log *smart, + unsigned int nsid, const char *devname) +{ + printf("Additional Smart Log for NVME device:%s namespace-id:%x\n", + devname, nsid); + printf("key normalized value\n"); + printf("program_fail_count : %3d%% %"PRIu64"\n", + smart->items[PROGRAM_FAIL_CNT].norm, + int48_to_long(smart->items[PROGRAM_FAIL_CNT].item_val)); + printf("erase_fail_count : %3d%% %"PRIu64"\n", + smart->items[ERASE_FAIL_CNT].norm, + int48_to_long(smart->items[ERASE_FAIL_CNT].item_val)); + printf("wear_leveling : %3d%% min: %u, max: %u, avg: %u\n", + smart->items[WEARLEVELING_COUNT].norm, + le16_to_cpu(smart->items[WEARLEVELING_COUNT].wear_level.min), + le16_to_cpu(smart->items[WEARLEVELING_COUNT].wear_level.max), + le16_to_cpu(smart->items[WEARLEVELING_COUNT].wear_level.avg)); + printf("end_to_end_error_detection_count: %3d%% %"PRIu64"\n", + smart->items[E2E_ERR_CNT].norm, + int48_to_long(smart->items[E2E_ERR_CNT].item_val)); + printf("crc_error_count : %3d%% %"PRIu64"\n", + smart->items[CRC_ERR_CNT].norm, + int48_to_long(smart->items[CRC_ERR_CNT].item_val)); + printf("timed_workload_media_wear : %3d%% %.3f%%\n", + smart->items[TIME_WORKLOAD_MEDIA_WEAR].norm, + ((float)int48_to_long(smart->items[TIME_WORKLOAD_MEDIA_WEAR].item_val)) / 1024); + printf("timed_workload_host_reads : %3d%% %"PRIu64"%%\n", + smart->items[TIME_WORKLOAD_HOST_READS].norm, + int48_to_long(smart->items[TIME_WORKLOAD_HOST_READS].item_val)); + printf("timed_workload_timer : %3d%% %"PRIu64" min\n", + smart->items[TIME_WORKLOAD_TIMER].norm, + int48_to_long(smart->items[TIME_WORKLOAD_TIMER].item_val)); + printf("thermal_throttle_status : %3d%% CurTTSta: %u%%, TTCnt: %u\n", + smart->items[THERMAL_THROTTLE].norm, + smart->items[THERMAL_THROTTLE].thermal_throttle.st, + smart->items[THERMAL_THROTTLE].thermal_throttle.count); + printf("retry_buffer_overflow_count : %3d%% %"PRIu64"\n", + smart->items[RETRY_BUFFER_OVERFLOW].norm, + int48_to_long(smart->items[RETRY_BUFFER_OVERFLOW].item_val)); + printf("pll_lock_loss_count : %3d%% %"PRIu64"\n", + smart->items[PLL_LOCK_LOSS].norm, + int48_to_long(smart->items[PLL_LOCK_LOSS].item_val)); + printf("nand_bytes_written : %3d%% sectors: %"PRIu64"\n", + smart->items[NAND_WRITE].norm, + int48_to_long(smart->items[NAND_WRITE].item_val)); + printf("host_bytes_written : %3d%% sectors: %"PRIu64"\n", + smart->items[HOST_WRITE].norm, + int48_to_long(smart->items[HOST_WRITE].item_val)); + printf("sram_error_count : %3d%% %"PRIu64"\n", + smart->items[RETRY_BUFFER_OVERFLOW].norm, + int48_to_long(smart->items[SRAM_ERROR_CNT].item_val)); +} + + +static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct nvme_shannon_smart_log smart_log; + int err, fd; + char *desc = "Get Shannon vendor specific additional smart log (optionally, "\ + "for the specified namespace), and show it."; + const char *namespace = "(optional) desired namespace"; + const char *raw = "dump output in binary format"; + struct config { + __u32 namespace_id; + int raw_binary; + }; + + struct config cfg = { + .namespace_id = NVME_NSID_ALL, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + err = nvme_get_log(fd, cfg.namespace_id, 0xca, false, + sizeof(smart_log), &smart_log); + if (!err) { + if (!cfg.raw_binary) + show_shannon_smart_log(&smart_log, cfg.namespace_id, devicename); + else + d_raw((unsigned char *)&smart_log, sizeof(smart_log)); + } + else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + return err; +} + +static int get_additional_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Read operating parameters of the "\ + "specified controller. Operating parameters are grouped "\ + "and identified by Feature Identifiers; each Feature "\ + "Identifier contains one or more attributes that may affect "\ + "behaviour of the feature. Each Feature has three possible "\ + "settings: default, saveable, and current. If a Feature is "\ + "saveable, it may be modified by set-feature. Default values "\ + "are vendor-specific and not changeable. Use set-feature to "\ + "change saveable Features.\n\n"\ + "Available additional feature id:\n"\ + "0x02: Shannon power management\n"; + const char *raw = "show infos in binary format"; + const char *namespace_id = "identifier of desired namespace"; + const char *feature_id = "hexadecimal feature name"; + const char *sel = "[0-3]: curr./default/saved/supp."; + const char *data_len = "buffer len (if) data is returned"; + const char *cdw11 = "dword 11 for interrupt vector config"; + const char *human_readable = "show infos in readable format"; + int err, fd; + __u32 result; + void *buf = NULL; + + struct config { + __u32 namespace_id; + __u32 feature_id; + __u8 sel; + __u32 cdw11; + __u32 data_len; + int raw_binary; + int human_readable; + }; + + struct config cfg = { + .namespace_id = 1, + .feature_id = 0, + .sel = 0, + .cdw11 = 0, + .data_len = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("feature-id", 'f', &cfg.feature_id, feature_id), + OPT_BYTE("sel", 's', &cfg.sel, sel), + OPT_UINT("data-len", 'l', &cfg.data_len, data_len), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_UINT("cdw11", 'c', &cfg.cdw11, cdw11), + OPT_FLAG("human-readable",'H', &cfg.human_readable, human_readable), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if (cfg.sel > 7) { + fprintf(stderr, "invalid 'select' param:%d\n", cfg.sel); + close(fd); + return EINVAL; + } + if (!cfg.feature_id) { + fprintf(stderr, "feature-id required param\n"); + close(fd); + return EINVAL; + } + if (cfg.data_len) { + if (posix_memalign(&buf, getpagesize(), cfg.data_len)) + { + close(fd); + exit(ENOMEM); + } + memset(buf, 0, cfg.data_len); + } + + err = nvme_get_feature(fd, cfg.namespace_id, cfg.feature_id, cfg.sel, cfg.cdw11, + cfg.data_len, buf, &result); + if (!err) { + printf("get-feature:0x%02x (%s), %s value: %#08x\n", cfg.feature_id, + nvme_feature_to_string(cfg.feature_id), + nvme_select_to_string(cfg.sel), result); + if (cfg.human_readable) + nvme_feature_show_fields(cfg.feature_id, result, buf); + else { + if (buf) { + if (!cfg.raw_binary) + d(buf, cfg.data_len, 16, 1); + else + d_raw(buf, cfg.data_len); + } + } + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + if (buf) + free(buf); + return err; +} + +static int set_additional_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Modify the saveable or changeable "\ + "current operating parameters of the controller. Operating "\ + "parameters are grouped and identified by Feature "\ + "Identifiers. Feature settings can be applied to the entire "\ + "controller and all associated namespaces, or to only a few "\ + "namespace(s) associated with the controller. Default values "\ + "for each Feature are vendor-specific and may not be modified."\ + "Use get-feature to determine which Features are supported by "\ + "the controller and are saveable/changeable.\n\n"\ + "Available additional feature id:\n"\ + "0x02: Shannon power management\n"; + const char *namespace_id = "desired namespace"; + const char *feature_id = "hex feature name (required)"; + const char *data_len = "buffer length if data required"; + const char *data = "optional file for feature data (default stdin)"; + const char *value = "new value of feature (required)"; + const char *save = "specifies that the controller shall save the attribute"; + int err, fd; + __u32 result; + void *buf = NULL; + int ffd = STDIN_FILENO; + + struct config { + char *file; + __u32 namespace_id; + __u32 feature_id; + __u32 value; + __u32 data_len; + int save; + }; + + struct config cfg = { + .file = "", + .namespace_id = 0, + .feature_id = 0, + .value = 0, + .data_len = 0, + .save = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("feature-id", 'f', &cfg.feature_id, feature_id), + OPT_UINT("value", 'v', &cfg.value, value), + OPT_UINT("data-len", 'l', &cfg.data_len, data_len), + OPT_FILE("data", 'd', &cfg.file, data), + OPT_FLAG("save", 's', &cfg.save, save), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) + return fd; + + if (!cfg.feature_id) { + fprintf(stderr, "feature-id required param\n"); + close(fd); + return EINVAL; + } + + if (cfg.data_len) { + if (posix_memalign(&buf, getpagesize(), cfg.data_len)){ + fprintf(stderr, "can not allocate feature payload\n"); + close(fd); + return ENOMEM; + } + memset(buf, 0, cfg.data_len); + } + + if (buf) { + if (strlen(cfg.file)) { + ffd = open(cfg.file, O_RDONLY); + if (ffd <= 0) { + fprintf(stderr, "no firmware file provided\n"); + err = EINVAL; + goto free; + } + } + err = read(ffd, (void *)buf, cfg.data_len); + if (err < 0) { + fprintf(stderr, "failed to read data buffer from input file\n"); + err = EINVAL; + goto free; + } + } + + err = nvme_set_feature(fd, cfg.namespace_id, cfg.feature_id, cfg.value, + 0, cfg.save, cfg.data_len, buf, &result); + if (err < 0) { + perror("set-feature"); + goto free; + } + if (!err) { + printf("set-feature:%02x (%s), value:%#08x\n", cfg.feature_id, + nvme_feature_to_string(cfg.feature_id), cfg.value); + if (buf) + d(buf, cfg.data_len, 16, 1); + } else if (err > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(err), err); + +free: + if (buf) + free(buf); + return err; +} + +static int shannon_id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return __id_ctrl(argc, argv, cmd, plugin, NULL); +} + + + diff --git a/plugins/shannon/shannon-nvme.h b/plugins/shannon/shannon-nvme.h new file mode 100644 index 0000000..d40732e --- /dev/null +++ b/plugins/shannon/shannon-nvme.h @@ -0,0 +1,20 @@ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/shannon/shannon-nvme + +#if !defined(SHANNON_NVME) || defined(CMD_HEADER_MULTI_READ) +#define SHANNON_NVME + +#include "cmd.h" + +PLUGIN(NAME("shannon", "Shannon vendor specific extensions"), + COMMAND_LIST( + ENTRY("smart-log-add", "Retrieve Shannon SMART Log, show it", get_additional_smart_log) + ENTRY("get-feature-add", "Get Shannon feature and show the resulting value", get_additional_feature) + ENTRY("set-feature-add", "Set a Shannon feature and show the resulting value", set_additional_feature) + ENTRY("id-ctrl", "Shannon NVMe Identify Controller", shannon_id_ctrl) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/toshiba/toshiba-nvme.c b/plugins/toshiba/toshiba-nvme.c new file mode 100644 index 0000000..c067ce8 --- /dev/null +++ b/plugins/toshiba/toshiba-nvme.c @@ -0,0 +1,563 @@ +#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 "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 "toshiba-nvme.h" + +static const __u32 OP_SCT_STATUS = 0xE0; +static const __u32 OP_SCT_COMMAND_TRANSFER = 0xE0; +static const __u32 OP_SCT_DATA_TRANSFER = 0xE1; + +static const __u32 DW10_SCT_STATUS_COMMAND = 0x0; +static const __u32 DW10_SCT_COMMAND_TRANSFER = 0x1; + +static const __u32 DW11_SCT_STATUS_COMMAND = 0x0; +static const __u32 DW11_SCT_COMMAND_TRANSFER = 0x0; + +static const __u16 INTERNAL_LOG_ACTION_CODE = 0xFFFB; +static const __u16 CURRENT_LOG_FUNCTION_CODE = 0x0001; +static const __u16 SAVED_LOG_FUNCTION_CODE = 0x0002; + +/* A bitmask field for supported devices */ +typedef enum { + MASK_0 = 1 << 0, + MASK_1 = 1 << 1, + /* + * Future devices can use the remaining 31 bits from this field + * and should use 1 << 2, 1 << 3, etc. + */ + MASK_IGNORE = 0 +} DeviceMask; + +/* Internal device codes */ +typedef enum { + CODE_0 = 0x0D, + CODE_1 = 0x10 +} DeviceCode; + + +static int nvme_sct_op(int fd, __u32 opcode, __u32 cdw10, __u32 cdw11, void* data, __u32 data_len ) +{ + void *metadata = NULL; + const __u32 cdw2 = 0; + const __u32 cdw3 = 0; + const __u32 cdw12 = 0; + const __u32 cdw13 = 0; + const __u32 cdw14 = 0; + const __u32 cdw15 = 0; + const __u32 timeout = 0; + const __u32 metadata_len = 0; + const __u32 namespace_id = 0x0; + const __u32 flags = 0; + const __u32 rsvd = 0; + int err = 0; + + __u32 result; + err = nvme_passthru(fd, NVME_IOCTL_ADMIN_CMD, opcode, flags, rsvd, + namespace_id, cdw2, cdw3, cdw10, + cdw11, cdw12, cdw13, cdw14, cdw15, + data_len, data, metadata_len, metadata, + timeout, &result); + return err; +} + +static int nvme_get_sct_status(int fd, __u32 device_mask) +{ + int err; + void* data = NULL; + size_t data_len = 512; + unsigned char *status; + + if (posix_memalign(&data, getpagesize(), data_len)) + return ENOMEM; + + memset(data, 0, data_len); + err = nvme_sct_op(fd, OP_SCT_STATUS, DW10_SCT_STATUS_COMMAND, DW11_SCT_STATUS_COMMAND, data, data_len); + if (err) { + fprintf(stderr, "%s: SCT status failed :%d\n", __func__, err); + goto end; + } + + status = data; + if (status[0] != 1U) { + /* Eek, wrong version in status header */ + fprintf(stderr, "%s: unexpected value in SCT status[0]:(%x)\n", __func__, status[0]); + err = -1; + errno = EINVAL; + goto end; + } + + /* Check if device is supported */ + if (device_mask != MASK_IGNORE) { + __u32 supported = 0; + switch (status[1]) { + case CODE_0: + supported = (device_mask & MASK_0); + break; + + case CODE_1: + supported = (device_mask & MASK_1); + break; + + default: + break; + }; + + if (0 == supported) { + fprintf(stderr, "%s: command unsupported on this device: (0x%x)\n",__func__, status[1]); + err = -1; + errno = EINVAL; + goto end; + } + } +end: + if (data) + free(data); + return err; +} + +static int nvme_sct_command_transfer_log(int fd, bool current) +{ + int err; + void *data = NULL; + size_t data_len = 512; + __u16 function_code, action_code = INTERNAL_LOG_ACTION_CODE; + + if (current) + function_code = CURRENT_LOG_FUNCTION_CODE; + else + function_code = SAVED_LOG_FUNCTION_CODE; + + if (posix_memalign(&data, getpagesize(), data_len)) + return ENOMEM; + + memset(data, 0, data_len); + memcpy(data, &action_code, sizeof(action_code)); + memcpy(data + 2, &function_code, sizeof(function_code)); + + err = nvme_sct_op(fd, OP_SCT_COMMAND_TRANSFER, DW10_SCT_COMMAND_TRANSFER, DW11_SCT_COMMAND_TRANSFER, data, data_len); + return err; +} + +static int nvme_sct_data_transfer(int fd, void* data, size_t data_len, size_t offset) +{ + __u32 dw10, dw11, lba_count = (data_len) / 512; + + if (lba_count) { + /* + * the count is a 0-based value, which seems to mean + * that it's actually last lba + */ + --lba_count; + } + + dw10 = (offset << 16) | lba_count; + dw11 = (offset >> 16); + return nvme_sct_op(fd, OP_SCT_DATA_TRANSFER, dw10, dw11, data, data_len); +} + +static int d_raw_to_fd(const unsigned char *buf, unsigned len, int fd) +{ + int written = 0; + int remaining = len; + + while (remaining) { + written = write(fd, buf, remaining); + if (written < 0) { + remaining = written; + break; + } else if (written <= remaining) { + remaining -= written; + } else { + /* Unexpected overwrite */ + break; + } + } + + /* return 0 on success or remaining/error */ + return remaining; +} + +/* Display progress (incoming 0->1.0) */ +static void progress_runner(float progress) +{ + const size_t barWidth = 70; + size_t i, pos; + + fprintf(stdout, "["); + pos = barWidth * progress; + for (i = 0; i < barWidth; ++i) { + if (i <= pos) + fprintf(stdout, "="); + else + fprintf(stdout, " "); + } + + fprintf(stdout, "] %d %%\r",(int)(progress * 100.0)); + fflush(stdout); +} + +static int nvme_get_internal_log(int fd, const char* const filename, bool current) +{ + int err; + int o_fd = -1; + void* page_data = NULL; + const size_t page_sector_len = 32; + const size_t page_data_len = page_sector_len * 512; /* 32 sectors per page */ + uint32_t* area1_last_page; + uint32_t* area2_last_page; + uint32_t* area3_last_page; + uint32_t log_sectors = 0; + size_t pages; + + /* + * By trial and error it seems that the largest transfer chunk size + * is 128 * 32 = 4k sectors = 2MB + */ + const __u32 max_pages = 128; + size_t i; + unsigned int j; + float progress = 0.0; + + err = nvme_sct_command_transfer_log(fd, current); + if (err) { + fprintf(stderr, "%s: SCT command transfer failed\n", __func__); + goto end; + } + + if (posix_memalign(&page_data, getpagesize(), max_pages * page_data_len)) { + err = ENOMEM; + goto end; + } + memset(page_data, 0, max_pages * page_data_len); + + /* Read the header to get the last log page - offsets 8->11, 12->15, 16->19 */ + err = nvme_sct_data_transfer(fd, page_data, page_data_len, 0); + if (err) { + fprintf(stderr, "%s: SCT data transfer failed, page 0\n",__func__); + goto end; + } + + area1_last_page = (uint32_t *) (page_data + 8); + area2_last_page = (uint32_t *) (page_data + 12); + area3_last_page = (uint32_t *) (page_data + 16); + + /* The number of total log sectors is the maximum + 1; */ + if (*area1_last_page > log_sectors) + log_sectors = *area1_last_page; + if (*area2_last_page > log_sectors) + log_sectors = *area2_last_page; + if (*area3_last_page > log_sectors) + log_sectors = *area3_last_page; + + ++log_sectors; + pages = log_sectors / page_sector_len; + if (filename == NULL) { + fprintf(stdout, "Page: %u of %zu\n", 0u, pages); + d(page_data, page_data_len, 16, 1); + } else { + progress_runner(progress); + o_fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (o_fd < 0) { + fprintf(stderr, "%s: couldn't output file %s\n", __func__, filename); + err = EINVAL; + goto end; + } + err = d_raw_to_fd(page_data, page_data_len, o_fd); + if (err) { + fprintf(stderr, "%s: couldn't write all data to output file\n", __func__); + goto end; + } + } + + /* Now read the rest */ + for (i = 1; i < pages;) { + __u32 pages_chunk = max_pages; + if (pages_chunk + i >= pages) + pages_chunk = pages - i; + + err = nvme_sct_data_transfer(fd, page_data, + pages_chunk * page_data_len, + i * page_sector_len); + if (err) { + fprintf(stderr, "%s: SCT data transfer command failed\n", __func__); + goto end; + } + + progress = (float) (i) / (float) (pages); + progress_runner(progress); + if (filename == NULL) { + for (j = 0; j < pages_chunk; ++j) { + fprintf(stdout, "Page: %zu of %zu\n", i + j, pages); + d(page_data + (j * page_data_len), page_data_len, 16, 1); + } + } else { + progress_runner(progress); + err = d_raw_to_fd(page_data, pages_chunk * page_data_len, o_fd); + if (err) { + fprintf(stderr, "%s: couldn't write all data to output file\n", + __func__); + goto end; + } + } + i += pages_chunk; + } + progress = 1.0f; + progress_runner(progress); + fprintf(stdout,"\n"); + err = nvme_get_sct_status(fd, MASK_IGNORE); + if (err) { + fprintf(stderr, "%s: bad SCT status\n", __func__); + goto end; + } +end: + if (o_fd >= 0) { + close(o_fd); + } + if (page_data) { + free(page_data); + } + return err; +} + +static int nvme_get_internal_log_file(int fd, const char* const filename, bool current) +{ + int err; + + /* Check device supported */ + err = nvme_get_sct_status(fd, MASK_0 | MASK_1); + if (!err) + err = nvme_get_internal_log(fd, filename, current); + return err; +} + +enum LOG_PAGE_C0 { + ERROR_LOG_C0 = 0, + SMART_HEALTH_LOG_C0, + FIRMWARE_SLOT_INFO_C0, + COMMAND_EFFECTS_C0, + DEVICE_SELF_TEST_C0, + LOG_PAGE_DIRECTORY_C0, + SMART_ATTRIBUTES_C0, + NR_SMART_ITEMS_C0, +}; + +struct nvme_xdn_smart_log_c0 { + __u8 items[NR_SMART_ITEMS_C0]; + __u8 resv[512 - NR_SMART_ITEMS_C0]; +}; + +static void default_show_vendor_log_c0(int fd, __u32 nsid, const char *devname, + struct nvme_xdn_smart_log_c0 *smart) +{ + printf("Vendor Log Page Directory 0xC0 for NVME device:%s namespace-id:%x\n", + devname, nsid); + printf("Error Log : %u \n", smart->items[ERROR_LOG_C0]); + printf("SMART Health Log : %u \n", smart->items[SMART_HEALTH_LOG_C0]); + printf("Firmware Slot Info : %u \n", smart->items[FIRMWARE_SLOT_INFO_C0]); + printf("Command Effects : %u \n", smart->items[COMMAND_EFFECTS_C0]); + printf("Device Self Test : %u \n", smart->items[DEVICE_SELF_TEST_C0]); + printf("Log Page Directory : %u \n", smart->items[LOG_PAGE_DIRECTORY_C0]); + printf("SMART Attributes : %u \n", smart->items[SMART_ATTRIBUTES_C0]); +} + +static int nvme_get_vendor_log(int fd, __u32 namespace_id, int log_page, + const char* const filename) +{ + int err; + void* log = NULL; + size_t log_len = 512; + + if (posix_memalign(&log, getpagesize(), log_len)) { + err = ENOMEM; + goto end; + } + + /* Check device supported */ + err = nvme_get_sct_status(fd, MASK_0 | MASK_1); + if (err) { + goto end; + } + err = nvme_get_log(fd, namespace_id, log_page, false, + log_len, log); + if (err) { + fprintf(stderr, "%s: couldn't get log 0x%x\n", __func__, + log_page); + goto end; + } + if (filename) { + int o_fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + + if (o_fd < 0) { + fprintf(stderr, "%s: couldn't output file %s\n", + __func__, filename); + err = EINVAL; + goto end; + } + err = d_raw_to_fd(log, log_len, o_fd); + if (err) { + fprintf(stderr, "%s: couldn't write all data to output file %s\n", + __func__, filename); + /* Attempt following close */ + } + if (close(o_fd)) { + err = errno; + goto end; + } + } else { + if (log_page == 0xc0) + default_show_vendor_log_c0(fd, namespace_id, devicename, + (struct nvme_xdn_smart_log_c0 *)log); + else + d(log, log_len,16,1); + } +end: + if (log) { + free(log); + } + return err; +} + +static int vendor_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int err, fd; + char *desc = "Get extended SMART information and show it."; + const char *namespace = "(optional) desired namespace"; + const char *output_file = "(optional) binary output filename"; + const char *log = "(optional) log ID (0xC0, or 0xCA), default 0xCA"; + + struct config { + __u32 namespace_id; + const char* output_file; + int log; + }; + + struct config cfg = { + .namespace_id = 0xffffffff, + .output_file = NULL, + .log = 0xca + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace), + OPT_FILE("output-file", 'o', &cfg.output_file, output_file), + OPT_UINT("log", 'l', &cfg.log, log), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) { + fprintf(stderr,"%s: failed to parse arguments\n", __func__); + return EINVAL; + } + + if ((cfg.log != 0xC0) && (cfg.log != 0xCA)) { + fprintf(stderr, "%s: invalid log page 0x%x - should be 0xC0 or 0xCA\n", __func__, cfg.log); + err = EINVAL; + goto end; + } + + err = nvme_get_vendor_log(fd, cfg.namespace_id, cfg.log, cfg.output_file); + if (err) + fprintf(stderr, "%s: couldn't get vendor log 0x%x\n", __func__, cfg.log); +end: + if (err > 0) + fprintf(stderr, "%s: NVMe Status:%s(%x)\n", __func__, nvme_status_to_string(err), err); + return err; +} + +static int internal_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int err, fd; + char *desc = "Get internal status log and show it."; + const char *output_file = "(optional) binary output filename"; + const char *prev_log = "(optional) use previous log. Otherwise uses current log."; + + struct config { + const char* output_file; + bool prev_log; + }; + + struct config cfg = { + .output_file = NULL, + .prev_log = false + }; + + OPT_ARGS(opts) = { + OPT_FILE("output-file", 'o', &cfg.output_file, output_file), + OPT_FLAG("prev-log", 'p', &cfg.prev_log, prev_log), + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) { + fprintf(stderr,"%s: failed to parse arguments\n", __func__); + return EINVAL; + } + + if (cfg.prev_log) + printf("Getting previous log\n"); + else + printf("Getting current log\n"); + + err = nvme_get_internal_log_file(fd, cfg.output_file, !cfg.prev_log); + if (err < 0) + fprintf(stderr, "%s: couldn't get fw log \n", __func__); + if (err > 0) + fprintf(stderr, "%s: NVMe Status:%s(%x)\n", __func__, + nvme_status_to_string(err), err); + return err; +} + +static int clear_correctable_errors(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err, fd; + char *desc = "Clear PCIe correctable error count."; + const __u32 namespace_id = 0xFFFFFFFF; + const __u32 feature_id = 0xCA; + const __u32 value = 1; /* Bit0 - reset clear PCIe correctable count */ + const __u32 cdw12 = 0; + const bool save = false; + __u32 result; + + OPT_ARGS(opts) = { + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) { + fprintf(stderr,"%s: failed to parse arguments\n", __func__); + return EINVAL; + } + + /* Check device supported */ + err = nvme_get_sct_status(fd, MASK_0 | MASK_1); + if (err) + goto end; + + err = nvme_set_feature(fd, namespace_id, feature_id, value, cdw12, save, + 0, NULL, &result); + if (err) + fprintf(stderr, "%s: couldn't clear PCIe correctable errors \n", + __func__); +end: + if (err > 0) + fprintf(stderr, "%s: NVMe Status:%s(%x)\n", __func__, + nvme_status_to_string(err), err); + return err; +} diff --git a/plugins/toshiba/toshiba-nvme.h b/plugins/toshiba/toshiba-nvme.h new file mode 100644 index 0000000..c405e78 --- /dev/null +++ b/plugins/toshiba/toshiba-nvme.h @@ -0,0 +1,20 @@ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/toshiba/toshiba-nvme + +#if !defined(TOSHIBA_NVME) || defined(CMD_HEADER_MULTI_READ) +#define TOSHIBA_NVME + +#include "cmd.h" +#include "plugin.h" + +PLUGIN(NAME("toshiba", "Toshiba NVME plugin"), + COMMAND_LIST( + ENTRY("vs-smart-add-log", "Extended SMART information", vendor_log) + ENTRY("vs-internal-log", "Get Internal Log", internal_log) + ENTRY("clear-pcie-correctable-errors", "Clear PCIe correctable error count", clear_correctable_errors) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/transcend/transcend-nvme.c b/plugins/transcend/transcend-nvme.c new file mode 100644 index 0000000..dbb56be --- /dev/null +++ b/plugins/transcend/transcend-nvme.c @@ -0,0 +1,92 @@ +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.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 "transcend-nvme.h" + +static const __u32 OP_BAD_BLOCK = 0xc2; +static const __u32 DW10_BAD_BLOCK = 0x400; +static const __u32 DW12_BAD_BLOCK = 0x5a; + +static int getHealthValue(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct nvme_smart_log smart_log; + char *desc = "Get nvme health percentage."; + int result=0, fd; + int percent_used = 0, healthvalue=0; + + OPT_ARGS(opts) = { + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + + if (fd < 0) { + printf("\nDevice not found \n");; + return -1; + } + result = nvme_smart_log(fd, 0xffffffff, &smart_log); + if (!result) { + printf("Transcend NVME heath value: "); + percent_used =smart_log.percent_used; + + if(percent_used>100 || percent_used<0) + { + printf("0%%\n"); + } + else + { + healthvalue = 100 - percent_used; + printf("%d%%\n",healthvalue); + } + + } + + return result; +} + + +static int getBadblock(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + + char *desc = "Get nvme bad block number."; + int result=0, fd; + + OPT_ARGS(opts) = { + + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) { + printf("\nDevice not found \n");; + return -1; + } + unsigned char data[1]={0}; + struct nvme_passthru_cmd nvmecmd; + memset(&nvmecmd,0,sizeof(nvmecmd)); + nvmecmd.opcode=OP_BAD_BLOCK; + nvmecmd.cdw10=DW10_BAD_BLOCK; + nvmecmd.cdw12=DW12_BAD_BLOCK; + nvmecmd.addr = (__u64)(uintptr_t)data; + nvmecmd.data_len = 0x1; + result = nvme_submit_admin_passthru(fd,&nvmecmd); + if(!result) { + int badblock = data[0]; + printf("Transcend NVME badblock count: %d\n",badblock); + } + + return result; +} diff --git a/plugins/transcend/transcend-nvme.h b/plugins/transcend/transcend-nvme.h new file mode 100644 index 0000000..14d62ec --- /dev/null +++ b/plugins/transcend/transcend-nvme.h @@ -0,0 +1,20 @@ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/transcend/transcend-nvme + +#if !defined(TRANSCEND_NVME) || defined(CMD_HEADER_MULTI_READ) +#define TRANSCEND_NVME + +#include "cmd.h" + + +PLUGIN(NAME("transcend", "Transcend vendor specific extensions"), + COMMAND_LIST( + ENTRY("healthvalue", "NVME health percentage", getHealthValue) + ENTRY("badblock", "Get NVME bad block number", getBadblock) + + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/virtium/virtium-nvme.c b/plugins/virtium/virtium-nvme.c new file mode 100644 index 0000000..47b0fdc --- /dev/null +++ b/plugins/virtium/virtium-nvme.c @@ -0,0 +1,1044 @@ +#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 (20 * 1024) +#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; + double capacity; + char *curlocale; + char *templocale; + + curlocale = setlocale(LC_ALL, NULL); + templocale = strdup(curlocale); + + if (NULL == templocale) + printf("Cannot malloc buffer\n"); + + setlocale(LC_ALL, "C"); + + long long int lba = 1 << smart->raw_ns.lbaf[(smart->raw_ns.flbas & 0x0f)].ds; + capacity = le64_to_cpu(smart->raw_ns.nsze) * lba; + + 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;%lf;", capacity / 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), "NandWrites;%d;\n", 0); + strcat(text, tempbuff); + + setlocale(LC_ALL, templocale); + free(templocale); +} + +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] = ""; + + 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); + + 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(NULL); + 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(NULL); + + 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); +} + +static 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_build_power_state_descriptor(const struct nvme_id_ctrl *ctrl) +{ + unsigned int i; + unsigned char *buf; + + printf("{\n"); + printf("\"Power State Descriptors\":{\n"); + printf(" \"NOPS\":\"Non-Operational State,\"\n"); + printf(" \"MPS\":\"Max Power Scale (0: in 0.01 Watts; 1: in 0.0001 Watts),\"\n"); + printf(" \"ENLAT\":\"Entry Latency in microseconds,\"\n"); + printf(" \"RWL\":\"Relative Write Latency,\"\n"); + printf(" \"RRL\":\"Relative Read Latency,\"\n"); + printf(" \"IPS\":\"Idle Power Scale (00b: Not reported; 01b: 0.0001 W; 10b: 0.01 W; 11b: Reserved),\"\n"); + printf(" \"APS\":\"Active Power Scale (00b: Not reported; 01b: 0.0001 W; 10b: 0.01 W; 11b: Reserved),\"\n"); + printf(" \"ACTP\":\"Active Power,\"\n"); + printf(" \"MP\":\"Maximum Power,\"\n"); + printf(" \"EXLAT\":\"Exit Latency in microsecond,\"\n"); + printf(" \"RWT\":\"Relative Write Throughput,\"\n"); + printf(" \"RRT\":\"Relative Read Throughput,\"\n"); + printf(" \"IDLP\":\"Idle Power,\"\n"); + printf(" \"APW\":\"Active Power Workload,\"\n"); + printf(" \"Ofs\":\"BYTE Offset,\"\n"); + + printf(" \"Power State Descriptors\":\"\n"); + + printf("%6s%10s%5s%4s%6s%10s%10s%10s%4s%4s%4s%4s%10s%4s%6s%10s%4s%5s%6s\n", "Entry", "0fs 00-03", "NOPS", "MPS", "MP", "ENLAT", "EXLAT", "0fs 12-15", + "RWL", "RWT", "RRL", "RRT", "0fs 16-19", "IPS", "IDLP", "0fs 20-23", "APS", "APW", "ACTP"); + + + printf("%6s%10s%5s%4s%6s%10s%10s%10s%4s%4s%4s%4s%10s%4s%6s%10s%4s%5s%6s\n", "=====", "=========", "====", "===", "=====", "=========", "=========", + "=========", "===", "===", "===", "===", "=========", "===", "=====", "=========", "===", "====", "====="); + + for (i = 0; i < 32; i++) { + char s[100]; + unsigned int temp; + + printf("%6d", i); + buf = (unsigned char*) (&ctrl->psd[i]); + vt_convert_data_buffer_to_hex_string(&buf[0], 4, true, s); + printf("%9sh", s); + + temp = ctrl->psd[i].flags; + printf("%4ub", ((unsigned char)temp & 0x02)); + printf("%3ub", ((unsigned char)temp & 0x01)); + vt_convert_data_buffer_to_hex_string(&buf[0], 2, true, s); + printf("%5sh", s); + + vt_convert_data_buffer_to_hex_string(&buf[4], 4, true, s); + printf("%9sh", s); + vt_convert_data_buffer_to_hex_string(&buf[8], 4, true, s); + printf("%9sh", s); + vt_convert_data_buffer_to_hex_string(&buf[12], 4, true, s); + printf("%9sh", s); + vt_convert_data_buffer_to_hex_string(&buf[15], 1, true, s); + printf("%3sh", s); + vt_convert_data_buffer_to_hex_string(&buf[14], 1, true, s); + printf("%3sh", s); + vt_convert_data_buffer_to_hex_string(&buf[13], 1, true, s); + printf("%3sh", s); + vt_convert_data_buffer_to_hex_string(&buf[12], 1, true, s); + printf("%3sh", s); + vt_convert_data_buffer_to_hex_string(&buf[16], 4, true, s); + printf("%9sh", s); + + temp = ctrl->psd[i].idle_scale; + snprintf(s, sizeof(s), "%u%u", (((unsigned char)temp >> 6) & 0x01), (((unsigned char)temp >> 7) & 0x01)); + printf("%3sb", s); + + vt_convert_data_buffer_to_hex_string(&buf[16], 2, true, s); + printf("%5sh", s); + vt_convert_data_buffer_to_hex_string(&buf[20], 4, true, s); + printf("%9sh", s); + + temp = ctrl->psd[i].active_work_scale; + snprintf(s, sizeof(s), "%u%u", (((unsigned char)temp >> 6) & 0x01), (((unsigned char)temp >> 7) & 0x01)); + printf("%3sb", s); + snprintf(s, sizeof(s), "%u%u%u", (((unsigned char)temp) & 0x01), (((unsigned char)temp >> 1) & 0x01), (((unsigned char)temp >> 2) & 0x01)); + printf("%4sb", s); + + vt_convert_data_buffer_to_hex_string(&buf[20], 2, true, s); + printf("%5sh", s); + printf("\n"); + } + + printf(" \"}\n}\n"); + +} + +static void vt_dump_hex_data(const unsigned char *pbuff, size_t pbuffsize) { + + char textbuf[33]; + unsigned long int i, j; + + textbuf[32] = '\0'; + printf("[%08X] ", 0); + for (i = 0; i < pbuffsize; i++) { + printf("%02X ", pbuff[i]); + + if (pbuff[i] >= ' ' && pbuff[i] <= '~') + textbuf[i % 32] = pbuff[i]; + else + textbuf[i % 32] = '.'; + + if ((((i + 1) % 8) == 0) || ((i + 1) == pbuffsize)) { + printf(" "); + if ((i + 1) % 32 == 0) { + printf(" %s\n", textbuf); + if((i + 1) != pbuffsize) + printf("[%08lX] ", (i + 1)); + } + else if (i + 1 == pbuffsize) { + textbuf[(i + 1) % 32] = '\0'; + if(((i + 1) % 8) == 0) + printf(" "); + + for (j = ((i + 1) % 32); j < 32; j++) { + printf(" "); + if(((j + 1) % 8) == 0) + printf(" "); + } + + printf("%s\n", textbuf); + } + } + } +} + +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[20] = {"Reserved", + "Reserved", + "Reserved", + "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 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, 3, 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(" \"Fused Operation 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(" \"NVM 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\n"); + + vt_build_power_state_descriptor(ctrl); + + + printf("\n{\n"); + printf("\"Vendor Specific\":\"\n"); + vt_dump_hex_data(&buf[3072], 1024); + 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 = 10, + .output_file = NULL, + .test_name = NULL, + }; + + OPT_ARGS(opts) = { + OPT_DOUBLE("run-time", 'r', &cfg.run_time_hrs, run_time), + OPT_DOUBLE("freq", 'f', &cfg.log_record_frequency_hrs, freq), + OPT_FILE("output-file", 'o', &cfg.output_file, output_file), + OPT_STRING("test-name", 'n', "NAME", &cfg.test_name, test_name), + OPT_END() + }; + + vt_generate_vtview_log_file_name(vt_default_log_file_name); + + if (argc >= 2) + strcpy(path, argv[1]); + + fd = parse_and_open(argc, argv, desc, opts); + if (fd < 0) { + printf("Error parse and open (fd = %d)\n", fd); + return (fd); + } + + 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); + } + + 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(NULL); + end_time = start_time + total_time; + + fflush(stdout); + + while (1) { + cur_time = time(NULL); + if(cur_time >= end_time) + break; + + 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"; + + OPT_ARGS(opts) = { + OPT_END() + }; + + fd = parse_and_open(argc, argv, desc, opts); + 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)); + vt_parse_detail_identify(&ctrl); + + close(fd); + return (err); +} diff --git a/plugins/virtium/virtium-nvme.h b/plugins/virtium/virtium-nvme.h new file mode 100644 index 0000000..b95c910 --- /dev/null +++ b/plugins/virtium/virtium-nvme.h @@ -0,0 +1,21 @@ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/virtium/virtium-nvme + +#if !defined(VIRTIUM_NVME) || defined(CMD_HEADER_MULTI_READ) +#define VIRTIUM_NVME + +#include "cmd.h" +#include "plugin.h" + +PLUGIN(NAME("virtium", "Virtium vendor specific extensions"), + COMMAND_LIST( + ENTRY("save-smart-to-vtview-log", "Periodically save smart attributes into a log file.\n\ + The data in this log file can be analyzed using excel or using Virtium’s vtView.\n\ + Visit vtView.virtium.com to see full potential uses of the data", vt_save_smart_to_vtview_log) + ENTRY("show-identify", "Shows detail features and current settings", vt_show_identify) + ) +); + +#endif + +#include "define_cmd.h" 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; +} + + diff --git a/plugins/wdc/wdc-nvme.h b/plugins/wdc/wdc-nvme.h new file mode 100644 index 0000000..c6b2d3f --- /dev/null +++ b/plugins/wdc/wdc-nvme.h @@ -0,0 +1,38 @@ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/wdc/wdc-nvme + +#if !defined(WDC_NVME) || defined(CMD_HEADER_MULTI_READ) +#define WDC_NVME + +#include "cmd.h" + +PLUGIN(NAME("wdc", "Western Digital vendor specific extensions"), + COMMAND_LIST( + ENTRY("cap-diag", "WDC Capture-Diagnostics", wdc_cap_diag) + ENTRY("drive-log", "WDC Drive Log", wdc_drive_log) + ENTRY("get-crash-dump", "WDC Crash Dump", wdc_get_crash_dump) + ENTRY("get-pfail-dump", "WDC Pfail Dump", wdc_get_pfail_dump) + ENTRY("id-ctrl", "WDC identify controller", wdc_id_ctrl) + ENTRY("purge", "WDC Purge", wdc_purge) + ENTRY("purge-monitor", "WDC Purge Monitor", wdc_purge_monitor) + ENTRY("vs-internal-log", "WDC Internal Firmware Log", wdc_vs_internal_fw_log) + ENTRY("vs-nand-stats", "WDC NAND Statistics", wdc_vs_nand_stats) + ENTRY("vs-smart-add-log", "WDC Additional Smart Log", wdc_vs_smart_add_log) + ENTRY("clear-pcie-correctable-errors", "WDC Clear PCIe Correctable Error Count", wdc_clear_pcie_correctable_errors) + ENTRY("drive-essentials", "WDC Drive Essentials", wdc_drive_essentials) + ENTRY("get-drive-status", "WDC Get Drive Status", wdc_drive_status) + ENTRY("clear-assert-dump", "WDC Clear Assert Dump", wdc_clear_assert_dump) + ENTRY("drive-resize", "WDC Drive Resize", wdc_drive_resize) + ENTRY("vs-fw-activate-history", "WDC Get FW Activate History", wdc_vs_fw_activate_history) + ENTRY("clear-fw-activate-history", "WDC Clear FW Activate History", wdc_clear_fw_activate_history) + ENTRY("vs-telemetry-controller-option", "WDC Enable/Disable Controller Initiated Telemetry Log", wdc_vs_telemetry_controller_option) + ENTRY("vs-error-reason-identifier", "WDC Telemetry Reason Identifier", wdc_reason_identifier) + ENTRY("log-page-directory", "WDC Get Log Page Directory", wdc_log_page_directory) + ENTRY("namespace-resize", "WDC NamespaceDrive Resize", wdc_namespace_resize) + ENTRY("vs-drive-info", "WDC Get Drive Info", wdc_vs_drive_info) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/wdc/wdc-utils.c b/plugins/wdc/wdc-utils.c new file mode 100644 index 0000000..80d9955 --- /dev/null +++ b/plugins/wdc/wdc-utils.c @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2017-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: Jeff Lien <jeff.lien@wdc.com>, + */ + +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <time.h> +#include "wdc-utils.h" + +int wdc_UtilsSnprintf(char *buffer, unsigned int sizeOfBuffer, const char *format, ...) +{ + int res = 0; + va_list vArgs; + + va_start(vArgs, format); + res = vsnprintf(buffer, sizeOfBuffer, format, vArgs); + va_end(vArgs); + + return res; +} + +void wdc_UtilsDeleteCharFromString(char* buffer, int buffSize, char charToRemove) +{ + int i = 0; + int count = 0; + + if (!buffer || !buffSize) + return; + + /* + * Traverse the given string. If current character is not charToRemove, + * then place it at index count++ + */ + for (i = 0; ((i < buffSize) && (buffer[i] != '\0')); i++) { + if (buffer[i] != charToRemove) + buffer[count++] = buffer[i]; + } + buffer[count] = '\0'; +} + +int wdc_UtilsGetTime(PUtilsTimeInfo timeInfo) +{ + time_t currTime; + struct tm currTimeInfo; + + if(!timeInfo) + return WDC_STATUS_INVALID_PARAMETER; + + tzset(); + time(&currTime); + localtime_r(&currTime, &currTimeInfo); + + timeInfo->year = currTimeInfo.tm_year + 1900; + timeInfo->month = currTimeInfo.tm_mon + 1; + timeInfo->dayOfWeek = currTimeInfo.tm_wday; + timeInfo->dayOfMonth = currTimeInfo.tm_mday; + timeInfo->hour = currTimeInfo.tm_hour; + timeInfo->minute = currTimeInfo.tm_min; + timeInfo->second = currTimeInfo.tm_sec; + timeInfo->msecs = 0; + timeInfo->isDST = currTimeInfo.tm_isdst; + timeInfo->zone = -currTimeInfo.tm_gmtoff / 60; + + return WDC_STATUS_SUCCESS; +} + +int wdc_UtilsCreateDir(char *path) +{ + int retStatus; + int status = WDC_STATUS_SUCCESS; + + if (!path ) + return WDC_STATUS_INVALID_PARAMETER; + + retStatus = mkdir(path, 0x999); + if (retStatus < 0) { + if (errno == EEXIST) + status = WDC_STATUS_DIR_ALREADY_EXISTS; + else if (errno == ENOENT) + status = WDC_STATUS_PATH_NOT_FOUND; + else + status = WDC_STATUS_CREATE_DIRECTORY_FAILED; + } + + return status; +} + +int wdc_WriteToFile(char *fileName, char *buffer, unsigned int bufferLen) +{ + int status = WDC_STATUS_SUCCESS; + FILE *file; + size_t bytesWritten = 0; + + file = fopen(fileName, "ab+"); + if (!file) { + status = WDC_STATUS_UNABLE_TO_OPEN_FILE; + goto end; + } + + bytesWritten = fwrite(buffer, 1, bufferLen, file); + if (bytesWritten != bufferLen) + status = WDC_STATUS_UNABLE_TO_WRITE_ALL_DATA; + +end: + if(file) + fclose(file); + return status; +} + +/** + * Compares the strings ignoring their cases. + * + * @param pcSrc Points to a null terminated string for comapring. + * @param pcDst Points to a null terminated string for comapring. + * + * @returns zero if the string matches or + * 1 if the pcSrc string is lexically higher than pcDst or + * -1 if the pcSrc string is lexically lower than pcDst. + */ +int wdc_UtilsStrCompare(char *pcSrc, char *pcDst) +{ + while ((toupper(*pcSrc) == toupper(*pcDst)) && (*pcSrc != '\0')) { + pcSrc++; + pcDst++; + } + return *pcSrc - *pcDst; +} + diff --git a/plugins/wdc/wdc-utils.h b/plugins/wdc/wdc-utils.h new file mode 100644 index 0000000..251339f --- /dev/null +++ b/plugins/wdc/wdc-utils.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2017-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: Jeff Lien <jeff.lien@wdc.com>, + */ + +#include <stdio.h> +#include <ctype.h> +#include <stdarg.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <assert.h> +#include <time.h> +#include <sys/time.h> +#include <sys/stat.h> + +#include <string.h> +#include <unistd.h> + +/* Create Dir Command Status */ +#define WDC_STATUS_SUCCESS 0 +#define WDC_STATUS_FAILURE -1 +#define WDC_STATUS_INSUFFICIENT_MEMORY -2 +#define WDC_STATUS_INVALID_PARAMETER -3 +#define WDC_STATUS_FILE_SIZE_ZERO -27 +#define WDC_STATUS_UNABLE_TO_WRITE_ALL_DATA -34 +#define WDC_STATUS_DIR_ALREADY_EXISTS -36 +#define WDC_STATUS_PATH_NOT_FOUND -37 +#define WDC_STATUS_CREATE_DIRECTORY_FAILED -38 +#define WDC_STATUS_DELETE_DIRECTORY_FAILED -39 +#define WDC_STATUS_UNABLE_TO_OPEN_FILE -40 +#define WDC_STATUS_UNABLE_TO_OPEN_ZIP_FILE -41 +#define WDC_STATUS_UNABLE_TO_ARCHIVE_EXCEEDED_FILES_LIMIT -256 +#define WDC_STATUS_NO_DATA_FILE_AVAILABLE_TO_ARCHIVE -271 + +#define WDC_NVME_FIRMWARE_REV_LEN 9 /* added 1 for end delimiter */ +#define WDC_SERIAL_NO_LEN 20 +#define SECONDS_IN_MIN 60 +#define MAX_PATH_LEN 256 + +typedef struct _UtilsTimeInfo +{ + unsigned int year; + unsigned int month; + unsigned int dayOfWeek; + unsigned int dayOfMonth; + unsigned int hour; + unsigned int minute; + unsigned int second; + unsigned int msecs; + unsigned char isDST; /*0 or 1 */ + int zone; /* Zone value like +530 or -300 */ +} UtilsTimeInfo, *PUtilsTimeInfo; + +int wdc_UtilsSnprintf(char *buffer, unsigned int sizeOfBuffer, const char *format, ...); +void wdc_UtilsDeleteCharFromString(char* buffer, int buffSize, char charToRemove); +int wdc_UtilsGetTime(PUtilsTimeInfo timeInfo); +int wdc_UtilsStrCompare(char *pcSrc, char *pcDst); +int wdc_UtilsCreateDir(char *path); +int wdc_WriteToFile(char *fileName, char *buffer, unsigned int bufferLen); + |