diff options
Diffstat (limited to '')
108 files changed, 40156 insertions, 0 deletions
diff --git a/plugins/amzn/amzn-nvme.c b/plugins/amzn/amzn-nvme.c new file mode 100644 index 0000000..d359cc3 --- /dev/null +++ b/plugins/amzn/amzn-nvme.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" + +#define CREATE_CMD +#include "amzn-nvme.h" + +struct nvme_vu_id_ctrl_field { + __u8 bdev[32]; + __u8 reserved0[992]; +}; + +static void json_amzn_id_ctrl(struct nvme_vu_id_ctrl_field *id, + char *bdev, + struct json_object *root) +{ + json_object_add_value_string(root, "bdev", bdev); +} + +static void amzn_id_ctrl(__u8 *vs, struct json_object *root) +{ + struct nvme_vu_id_ctrl_field *id = (struct nvme_vu_id_ctrl_field *)vs; + + char bdev[32] = { 0 }; + + int len = 0; + + while (len < 31) { + if (id->bdev[++len] == ' ') + break; + } + snprintf(bdev, len+1, "%s", id->bdev); + + if (root) { + json_amzn_id_ctrl(id, bdev, root); + return; + } + + printf("bdev : %s\n", bdev); +} + +static int id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return __id_ctrl(argc, argv, cmd, plugin, amzn_id_ctrl); +} diff --git a/plugins/amzn/amzn-nvme.h b/plugins/amzn/amzn-nvme.h new file mode 100644 index 0000000..f6c4f8b --- /dev/null +++ b/plugins/amzn/amzn-nvme.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/amzn/amzn-nvme + +#if !defined(AMZN_NVME) || defined(CMD_HEADER_MULTI_READ) +#define AMZN_NVME + +#include "cmd.h" + +PLUGIN(NAME("amzn", "Amazon vendor specific extensions", NVME_VERSION), + COMMAND_LIST( + ENTRY("id-ctrl", "Send NVMe Identify Controller", id_ctrl) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/dell/dell-nvme.c b/plugins/dell/dell-nvme.c new file mode 100644 index 0000000..8ed10e7 --- /dev/null +++ b/plugins/dell/dell-nvme.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright © 2022 Dell Inc. or its subsidiaries. All Rights Reserved. +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" + +#define CREATE_CMD +#include "dell-nvme.h" + +#define ARRAY_NAME_LEN 80 + +struct nvme_vu_id_ctrl_field { + __u16 dell_mjr; + __u16 dell_mnr; + __u16 dell_ter; + __u8 reserved0[1018]; +}; + +static void dell_id_ctrl(__u8 *vs, struct json_object *root) +{ + struct nvme_vu_id_ctrl_field *id = (struct nvme_vu_id_ctrl_field *)vs; + char array_ver[16] = { 0 }; + char array_name[ARRAY_NAME_LEN + 1] = {0}; + + snprintf(array_ver, sizeof(array_ver), "0x%04x%04x%04x", + le16_to_cpu(id->dell_mjr), + le16_to_cpu(id->dell_mnr), + le16_to_cpu(id->dell_ter)); + + memcpy(array_name, vs + sizeof(array_ver), ARRAY_NAME_LEN); + + if (root) { + json_object_add_value_string(root, "array_name", strlen(array_name) > 1 ? array_name : "NULL"); + json_object_add_value_string(root, "array_ver", array_ver); + return; + } + + printf("array_name : %s\n", strlen(array_name) > 1 ? array_name : "NULL"); + printf("array_ver : %s\n", array_ver); +} + +static int id_ctrl(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + return __id_ctrl(argc, argv, cmd, plugin, dell_id_ctrl); +} diff --git a/plugins/dell/dell-nvme.h b/plugins/dell/dell-nvme.h new file mode 100644 index 0000000..aaf0de1 --- /dev/null +++ b/plugins/dell/dell-nvme.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/dell/dell-nvme + +#if !defined(DELL_NVME) || defined(CMD_HEADER_MULTI_READ) +#define DELL_NVME + +#include "cmd.h" + +PLUGIN(NAME("dell", "DELL vendor specific extensions", NVME_VERSION), + COMMAND_LIST( + ENTRY("id-ctrl", "Send NVMe Identify Controller", id_ctrl) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/dera/dera-nvme.c b/plugins/dera/dera-nvme.c new file mode 100644 index 0000000..ca4b53d --- /dev/null +++ b/plugins/dera/dera-nvme.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#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 "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-print.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_admin_passthru(fd, &cmd, NULL); + if (!err && result) + *result = cmd.result; + + return err; +} + +static int get_status(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct nvme_dera_smart_info_log log; + enum dera_device_status state = DEVICE_STATUS_FATAL_ERROR; + char *desc = "Get the Dera device status"; + struct nvme_dev *dev; + int err; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = nvme_get_log_simple(dev_fd(dev), 0xc0, sizeof(log), &log); + if (err) + goto exit; + + static const char *dev_status[] = { + "Normal", + "Quick Rebuilding", + "Full Rebuilding", + "Raw Rebuilding", + "Card Read Only", + "Fatal Error", + "Busy", + "Low Level Format", + "Firmware Committing", + "Over Temperature" }; + + static const char *volt_status[] = { + "Normal", + "Initial Low", + "Runtime Low", + }; + + err = nvme_dera_get_device_status(dev_fd(dev), &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 < 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) + nvme_show_status(err); + + dev_close(dev); + return err; +} + diff --git a/plugins/dera/dera-nvme.h b/plugins/dera/dera-nvme.h new file mode 100644 index 0000000..a5bb0ae --- /dev/null +++ b/plugins/dera/dera-nvme.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#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", NVME_VERSION), + COMMAND_LIST( + ENTRY("smart-log-add", "Retrieve Dera SMART Log, show it", get_status, "stat") + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/fdp/fdp.c b/plugins/fdp/fdp.c new file mode 100644 index 0000000..2a221f8 --- /dev/null +++ b/plugins/fdp/fdp.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> +#include <linux/fs.h> +#include <sys/stat.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "nvme-print.h" + +#define CREATE_CMD +#include "fdp.h" + +static int fdp_configs(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "Get Flexible Data Placement Configurations"; + const char *egid = "Endurance group identifier"; + const char *human_readable = "show log in readable format"; + const char *raw = "use binary output"; + + enum nvme_print_flags flags; + struct nvme_dev *dev; + struct nvme_fdp_config_log hdr; + void *log = NULL; + int err; + + struct config { + __u16 egid; + char *output_format; + bool human_readable; + bool raw_binary; + }; + + struct config cfg = { + .egid = 0, + .output_format = "normal", + .raw_binary = false, + }; + + OPT_ARGS(opts) = { + OPT_UINT("endgrp-id", 'e', &cfg.egid, egid), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_FLAG("human-readable", 'H', &cfg.human_readable, human_readable), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = validate_output_format(cfg.output_format, &flags); + if (flags < 0) + goto out; + + if (cfg.raw_binary) + flags = BINARY; + + if (cfg.human_readable) + flags |= VERBOSE; + + if (!cfg.egid) { + fprintf(stderr, "endurance group identifier required\n"); + err = -EINVAL; + goto out; + } + + err = nvme_get_log_fdp_configurations(dev->direct.fd, cfg.egid, 0, + sizeof(hdr), &hdr); + if (err) { + nvme_show_status(errno); + goto out; + } + + log = malloc(hdr.size); + if (!log) { + err = -ENOMEM; + goto out; + } + + err = nvme_get_log_fdp_configurations(dev->direct.fd, cfg.egid, 0, + hdr.size, log); + if (err) { + nvme_show_status(errno); + goto out; + } + + nvme_show_fdp_configs(log, hdr.size, flags); + +out: + dev_close(dev); + free(log); + + return err; +} + +static int fdp_usage(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Get Flexible Data Placement Reclaim Unit Handle Usage"; + const char *egid = "Endurance group identifier"; + const char *raw = "use binary output"; + + enum nvme_print_flags flags; + struct nvme_dev *dev; + struct nvme_fdp_ruhu_log hdr; + size_t len; + void *log = NULL; + int err; + + struct config { + __u16 egid; + char *output_format; + bool raw_binary; + }; + + struct config cfg = { + .egid = 0, + .output_format = "normal", + .raw_binary = false, + }; + + OPT_ARGS(opts) = { + OPT_UINT("endgrp-id", 'e', &cfg.egid, egid), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = validate_output_format(cfg.output_format, &flags); + if (flags < 0) + goto out; + + if (cfg.raw_binary) + flags = BINARY; + + err = nvme_get_log_reclaim_unit_handle_usage(dev->direct.fd, cfg.egid, + 0, sizeof(hdr), &hdr); + if (err) { + nvme_show_status(err); + goto out; + } + + len = sizeof(hdr) + le16_to_cpu(hdr.nruh) * sizeof(struct nvme_fdp_ruhu_desc); + log = malloc(len); + if (!log) { + err = -ENOMEM; + goto out; + } + + err = nvme_get_log_reclaim_unit_handle_usage(dev->direct.fd, cfg.egid, + 0, len, log); + if (err) { + nvme_show_status(err); + goto out; + } + + nvme_show_fdp_usage(log, len, flags); + +out: + dev_close(dev); + free(log); + + return err; +} + +static int fdp_stats(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Get Flexible Data Placement Statistics"; + const char *egid = "Endurance group identifier"; + const char *raw = "use binary output"; + + enum nvme_print_flags flags; + struct nvme_dev *dev; + struct nvme_fdp_stats_log stats; + int err; + + struct config { + __u16 egid; + char *output_format; + bool raw_binary; + }; + + struct config cfg = { + .egid = 0, + .output_format = "normal", + .raw_binary = false, + }; + + OPT_ARGS(opts) = { + OPT_UINT("endgrp-id", 'e', &cfg.egid, egid), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = validate_output_format(cfg.output_format, &flags); + if (flags < 0) + goto out; + + if (cfg.raw_binary) + flags = BINARY; + + memset(&stats, 0x0, sizeof(stats)); + + err = nvme_get_log_fdp_stats(dev->direct.fd, cfg.egid, 0, sizeof(stats), &stats); + if (err) { + nvme_show_status(err); + goto out; + } + + nvme_show_fdp_stats(&stats, flags); + +out: + dev_close(dev); + + return err; +} + +static int fdp_events(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Get Flexible Data Placement Events"; + const char *egid = "Endurance group identifier"; + const char *host_events = "Get host events"; + const char *raw = "use binary output"; + + enum nvme_print_flags flags; + struct nvme_dev *dev; + struct nvme_fdp_events_log events; + int err; + + struct config { + __u16 egid; + bool host_events; + char *output_format; + bool raw_binary; + }; + + struct config cfg = { + .egid = 0, + .host_events = false, + .output_format = "normal", + .raw_binary = false, + }; + + OPT_ARGS(opts) = { + OPT_UINT("endgrp-id", 'e', &cfg.egid, egid), + OPT_FLAG("host-events", 'E', &cfg.host_events, host_events), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = validate_output_format(cfg.output_format, &flags); + if (flags < 0) + goto out; + + if (cfg.raw_binary) + flags = BINARY; + + memset(&events, 0x0, sizeof(events)); + + err = nvme_get_log_fdp_events(dev->direct.fd, cfg.egid, + cfg.host_events, 0, sizeof(events), &events); + if (err) { + nvme_show_status(err); + goto out; + } + + nvme_show_fdp_events(&events, flags); + +out: + dev_close(dev); + + return err; +} + +static int fdp_status(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Reclaim Unit Handle Status"; + const char *namespace_id = "Namespace identifier"; + const char *raw = "use binary output"; + + enum nvme_print_flags flags; + struct nvme_dev *dev; + struct nvme_fdp_ruh_status hdr; + size_t len; + void *buf = NULL; + int err = -1; + + struct config { + __u32 namespace_id; + char *output_format; + bool raw_binary; + }; + + struct config cfg = { + .output_format = "normal", + .raw_binary = false, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = validate_output_format(cfg.output_format, &flags); + if (flags < 0) + goto out; + + if (cfg.raw_binary) + flags = BINARY; + + if (!cfg.namespace_id) { + err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); + if (err < 0) { + perror("get-namespace-id"); + goto out; + } + } + + err = nvme_fdp_reclaim_unit_handle_status(dev_fd(dev), + cfg.namespace_id, sizeof(hdr), &hdr); + if (err) { + nvme_show_status(err); + goto out; + } + + len = sizeof(struct nvme_fdp_ruh_status) + + le16_to_cpu(hdr.nruhsd) * sizeof(struct nvme_fdp_ruh_status_desc); + buf = malloc(len); + if (!buf) { + err = -ENOMEM; + goto out; + } + + err = nvme_fdp_reclaim_unit_handle_status(dev_fd(dev), + cfg.namespace_id, len, buf); + if (err) { + nvme_show_status(err); + goto out; + } + + nvme_show_fdp_ruh_status(buf, len, flags); + +out: + free(buf); + dev_close(dev); + + return err; +} + +static int fdp_update(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Reclaim Unit Handle Update"; + const char *namespace_id = "Namespace identifier"; + const char *_pids = "Comma-separated list of placement identifiers to update"; + + struct nvme_dev *dev; + unsigned short pids[256]; + __u16 buf[256]; + int npids; + int err = -1; + + struct config { + __u32 namespace_id; + char *pids; + }; + + struct config cfg = { + .pids = "", + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_LIST("pids", 'p', &cfg.pids, _pids), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + npids = argconfig_parse_comma_sep_array_short(cfg.pids, pids, ARRAY_SIZE(pids)); + if (npids < 0) { + perror("could not parse pids"); + err = -EINVAL; + goto out; + } else if (npids == 0) { + fprintf(stderr, "no placement identifiers set\n"); + err = -EINVAL; + goto out; + } + + if (!cfg.namespace_id) { + err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); + if (err < 0) { + perror("get-namespace-id"); + goto out; + } + } + + for (unsigned int i = 0; i < npids; i++) + buf[i] = cpu_to_le16(pids[i]); + + err = nvme_fdp_reclaim_unit_handle_update(dev_fd(dev), cfg.namespace_id, npids, buf); + if (err) { + nvme_show_status(err); + goto out; + } + + printf("update: Success\n"); + +out: + dev_close(dev); + + return err; +} + +static int fdp_set_events(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Enable or disable FDP events"; + const char *namespace_id = "Namespace identifier"; + const char *enable = "Enable/disable event"; + const char *event_types = "Comma-separated list of event types"; + const char *ph = "Placement Handle"; + const char *save = "specifies that the controller shall save the attribute"; + + struct nvme_dev *dev; + int err = -1; + unsigned short evts[255]; + int nev; + __u8 buf[255]; + + struct config { + __u32 namespace_id; + __u16 ph; + char *event_types; + bool enable; + bool save; + }; + + struct config cfg = { + .enable = false, + .save = false, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_SHRT("placement-handle", 'p', &cfg.ph, ph), + OPT_FLAG("enable", 'e', &cfg.enable, enable), + OPT_FLAG("save", 's', &cfg.save, save), + OPT_LIST("event-types", 't', &cfg.event_types, event_types), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + nev = argconfig_parse_comma_sep_array_short(cfg.event_types, evts, ARRAY_SIZE(evts)); + if (nev < 0) { + perror("could not parse event types"); + err = -EINVAL; + goto out; + } else if (nev == 0) { + fprintf(stderr, "no event types set\n"); + err = -EINVAL; + goto out; + } else if (nev > 255) { + fprintf(stderr, "too many event types (max 255)\n"); + err = -EINVAL; + goto out; + } + + if (!cfg.namespace_id) { + err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); + if (err < 0) { + if (errno != ENOTTY) { + fprintf(stderr, "get-namespace-id: %s\n", nvme_strerror(errno)); + goto out; + } + + cfg.namespace_id = NVME_NSID_ALL; + } + } + + for (unsigned int i = 0; i < nev; i++) + buf[i] = (__u8)evts[i]; + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = NVME_FEAT_FID_FDP_EVENTS, + .save = cfg.save, + .nsid = cfg.namespace_id, + .cdw11 = (nev << 16) | cfg.ph, + .cdw12 = cfg.enable ? 0x1 : 0x0, + .data_len = sizeof(buf), + .data = buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + + err = nvme_set_features(&args); + if (err) { + nvme_show_status(err); + goto out; + } + + printf("set-events: Success\n"); + +out: + dev_close(dev); + + return err; +} diff --git a/plugins/fdp/fdp.h b/plugins/fdp/fdp.h new file mode 100644 index 0000000..f162b32 --- /dev/null +++ b/plugins/fdp/fdp.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/fdp/fdp + +#if !defined(FDP_NVME) || defined(CMD_HEADER_MULTI_READ) +#define FDP_NVME + +#include "cmd.h" + +PLUGIN(NAME("fdp", "Manage Flexible Data Placement enabled devices", NVME_VERSION), + COMMAND_LIST( + ENTRY("configs", "List configurations", fdp_configs) + ENTRY("usage", "Show reclaim unit handle usage", fdp_usage) + ENTRY("stats", "Show statistics", fdp_stats) + ENTRY("events", "List events affecting reclaim units and media usage", fdp_events) + ENTRY("status", "Show reclaim unit handle status", fdp_status) + ENTRY("update", "Update a reclaim unit handle", fdp_update) + ENTRY("set-events", "Enabled or disable events", fdp_set_events) + ) +); + +#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..0272dea --- /dev/null +++ b/plugins/huawei/huawei-nvme.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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 "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" + +#include "util/suffix.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; + unsigned int nsid; + struct nvme_id_ns ns; + unsigned int 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; + err = nvme_get_nsid(fd, &item->nsid); + err = nvme_identify_ns(fd, item->nsid, &item->ns); + if (err) + return err; + + err = fstat(fd, &nvme_stat_info); + if (err < 0) + return err; + + strncpy(item->node, node, sizeof(item->node)); + item->node[sizeof(item->node) - 1] = '\0'; + 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 int len) +{ + struct json_object *root; + struct json_object *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) +{ + __u8 lba_index; + + nvme_id_ns_flbas_to_lbaf_inuse(list_item->ns.flbas, &lba_index); + unsigned long long lba = 1ULL << list_item->ns.lbaf[lba_index].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 int len, + unsigned int 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 int len, + unsigned int 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 int len) +{ + unsigned int 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, ret; + unsigned int huawei_num = 0; + enum nvme_print_flags 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() + }; + + ret = argconfig_parse(argc, argv, desc, opts); + if (ret) + return ret; + + ret = validate_output_format(cfg.output_format, &fmt); + if (ret < 0 || (fmt != JSON && fmt != NORMAL)) + return ret; + + n = scandir("/dev", &devices, nvme_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"); + ret = ENOMEM; + goto out_free_devices; + } + + for (i = 0; i < n; i++) { + int fd; + + snprintf(path, sizeof(path), "/dev/%s", devices[i]->d_name); + fd = open(path, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Cannot open device %s: %s\n", + path, strerror(errno)); + continue; + } + ret = huawei_get_nvme_info(fd, &list_items[huawei_num], path); + if (ret) { + close(fd); + goto out_free_list_items; + } + if (list_items[huawei_num].huawei_device == true) + huawei_num++; + close(fd); + } + + 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); + } +out_free_list_items: + free(list_items); +out_free_devices: + for (i = 0; i < n; i++) + free(devices[i]); + free(devices); + + return ret; +} + +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..f49e6fd --- /dev/null +++ b/plugins/huawei/huawei-nvme.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#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", NVME_VERSION), + 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/innogrit/innogrit-nvme.c b/plugins/innogrit/innogrit-nvme.c new file mode 100644 index 0000000..cd47efa --- /dev/null +++ b/plugins/innogrit/innogrit-nvme.c @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <sys/stat.h> +#include <time.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "nvme-print.h" +#include "typedef.h" + +#define CREATE_CMD +#include "innogrit-nvme.h" + +static int innogrit_smart_log_additional(int argc, char **argv, + struct command *command, + struct plugin *plugin) +{ + struct nvme_smart_log smart_log = { 0 }; + struct vsc_smart_log *pvsc_smart = (struct vsc_smart_log *)smart_log.rsvd232; + const char *desc = "Retrieve additional SMART log for the given device "; + const char *namespace = "(optional) desired namespace"; + struct nvme_dev *dev; + int err, i, iindex; + + struct config { + __u32 namespace_id; + }; + + struct config cfg = { + .namespace_id = NVME_NSID_ALL, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + nvme_get_log_smart(dev_fd(dev), cfg.namespace_id, false, &smart_log); + nvme_show_smart_log(&smart_log, cfg.namespace_id, dev->name, NORMAL); + + printf("DW0[0-1] Defect Cnt : %u\n", pvsc_smart->defect_cnt); + printf("DW0[2-3] Slc Spb Cnt : %u\n", pvsc_smart->slc_spb_cnt); + printf("DW1 Slc Total Ec Cnt : %u\n", pvsc_smart->slc_total_ec_cnt); + printf("DW2 Slc Max Ec Cnt : %u\n", pvsc_smart->slc_max_ec_cnt); + printf("DW3 Slc Min Ec Cnt : %u\n", pvsc_smart->slc_min_ec_cnt); + printf("DW4 Slc Avg Ec Cnt : %u\n", pvsc_smart->slc_avg_ec_cnt); + printf("DW5 Total Ec Cnt : %u\n", pvsc_smart->total_ec_cnt); + printf("DW6 Max Ec Cnt : %u\n", pvsc_smart->max_ec_cnt); + printf("DW7 Min Ec Cnt : %u\n", pvsc_smart->min_ec_cnt); + printf("DW8 Avg Ec Cnt : %u\n", pvsc_smart->avg_ec_cnt); + printf("DW9 Mrd Rr Good Cnt : %u\n", pvsc_smart->mrd_rr_good_cnt); + printf("DW10 Ard Rr Good Cnt : %u\n", pvsc_smart->ard_rr_good_cnt); + printf("DW11 Preset Cnt : %u\n", pvsc_smart->preset_cnt); + printf("DW12 Nvme Reset Cnt : %u\n", pvsc_smart->nvme_reset_cnt); + printf("DW13 Low Pwr Cnt : %u\n", pvsc_smart->low_pwr_cnt); + printf("DW14 Wa : %u\n", pvsc_smart->wa); + printf("DW15 Ps3 Entry Cnt : %u\n", pvsc_smart->ps3_entry_cnt); + printf("DW16[0] highest_temp[0] : %u\n", pvsc_smart->highest_temp[0]); + printf("DW16[1] highest_temp[1] : %u\n", pvsc_smart->highest_temp[1]); + printf("DW16[2] highest_temp[2] : %u\n", pvsc_smart->highest_temp[2]); + printf("DW16[3] highest_temp[3] : %u\n", pvsc_smart->highest_temp[3]); + printf("DW17 weight_ec : %u\n", pvsc_smart->weight_ec); + printf("DW18 slc_cap_mb : %u\n", pvsc_smart->slc_cap_mb); + printf("DW19-20 nand_page_write_cnt : %llu\n", pvsc_smart->nand_page_write_cnt); + printf("DW21 program_error_cnt : %u\n", pvsc_smart->program_error_cnt); + printf("DW22 erase_error_cnt : %u\n", pvsc_smart->erase_error_cnt); + printf("DW23[0] flash_type : %u\n", pvsc_smart->flash_type); + printf("DW24 hs_crc_err_cnt : %u\n", pvsc_smart->hs_crc_err_cnt); + printf("DW25 ddr_ecc_err_cnt : %u\n", pvsc_smart->ddr_ecc_err_cnt); + iindex = 26; + for (i = 0; i < (sizeof(pvsc_smart->reserved3)/4); i++) { + if (pvsc_smart->reserved3[i] != 0) + printf("DW%-37d : %u\n", iindex, pvsc_smart->reserved3[i]); + iindex++; + } + + return 0; +} + +static int sort_eventlog_fn(const void *a, const void *b) +{ + const struct eventlog_addindex *l = a; + const struct eventlog_addindex *r = b; + int rc; + + if (l->ms > r->ms) { + rc = 1; + } else if (l->ms < r->ms) { + rc = -1; + } else { + if (l->iindex < r->iindex) + rc = -1; + else + rc = 1; + } + + return rc; +} + +static void sort_eventlog(struct eventlog *data16ksrc, unsigned int icount) +{ + struct eventlog_addindex peventlogadd[512]; + unsigned int i; + + for (i = 0; i < icount; i++) { + memcpy(&peventlogadd[i], &data16ksrc[i], sizeof(struct eventlog)); + peventlogadd[i].iindex = i; + } + + qsort(peventlogadd, icount, sizeof(struct eventlog_addindex), sort_eventlog_fn); + + for (i = 0; i < icount; i++) + memcpy(&data16ksrc[i], &peventlogadd[i], sizeof(struct eventlog)); +} + +static unsigned char setfilecontent(char *filenamea, unsigned char *buffer, + unsigned int buffersize) +{ + FILE *fp = NULL; + int rc; + + if (buffersize == 0) + return true; + fp = fopen(filenamea, "a+"); + rc = fwrite(buffer, 1, buffersize, fp); + fclose(fp); + if (rc != buffersize) + return false; + return true; +} + +static int nvme_vucmd(int fd, unsigned char opcode, unsigned int cdw12, + unsigned int cdw13, unsigned int cdw14, + unsigned int cdw15, char *data, int data_len) +{ + struct nvme_passthru_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = opcode; + cmd.cdw2 = IGVSC_SIG; + cmd.cdw10 = data_len / 4; + cmd.cdw12 = cdw12; + cmd.cdw13 = cdw13; + cmd.cdw14 = cdw14; + cmd.cdw15 = cdw15; + cmd.nsid = 0xffffffff; + cmd.addr = (__u64)(__u64)(uintptr_t)data; + cmd.data_len = data_len; + return nvme_submit_admin_passthru(fd, &cmd, NULL); +} + +static int innogrit_vsc_geteventlog(int argc, char **argv, + struct command *command, + struct plugin *plugin) +{ + time_t timep; + struct tm *logtime; + int icount, ioffset16k, iblock, ivsctype; + char currentdir[128], filename[512]; + unsigned char data[4096], data16k[SIZE_16K], zerob[32]; + unsigned int *pcheckdata; + unsigned int isize, icheck_stopvalue, iend; + unsigned char bSortLog = false, bget_nextlog = true; + struct evlg_flush_hdr *pevlog = (struct evlg_flush_hdr *)data; + const char *desc = "Recrieve event log for the given device "; + const char *clean_opt = "(optional) 1 for clean event log"; + struct nvme_dev *dev; + int ret = -1; + + struct config { + __u32 clean_flg; + }; + + struct config cfg = { + .clean_flg = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("clean_flg", 'c', &cfg.clean_flg, clean_opt), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + + if (getcwd(currentdir, 128) == NULL) + return -1; + + ret = nvme_vucmd(dev_fd(dev), 0xFE, 0x82, 0x03, 0x00, 0x00, (char *)data, 4096); + if (ret == -1) + return ret; + + if (data[0] == 0x5A) + ivsctype = 1; + else + ivsctype = 0; + + time(&timep); + logtime = localtime(&timep); + sprintf(filename, "%s/eventlog_%02d%02d-%02d%02d%02d.elog", currentdir, logtime->tm_mon+1, + logtime->tm_mday, logtime->tm_hour, logtime->tm_min, logtime->tm_sec); + + iblock = 0; + ioffset16k = 0; + memset(data16k, 0, SIZE_16K); + memset(zerob, 0, 32); + + icount = 0; + while (bget_nextlog) { + if (icount % 100 == 0) { + printf("\rWait for Dump EventLog " XCLEAN_LINE); + fflush(stdout); + icount = 0; + } else if (icount % 5 == 0) { + printf("."); + fflush(stdout); + } + icount++; + + memset(data, 0, 4096); + if (ivsctype == 1) + ret = nvme_vucmd(dev_fd(dev), 0xFE, 0x60, 0x00, 0x00, 0x00, (char *)data, + 4096); + else + ret = nvme_vucmd(dev_fd(dev), NVME_VSC_GET_EVENT_LOG, 0, 0, + (SRB_SIGNATURE >> 32), + (SRB_SIGNATURE & 0xFFFFFFFF), + (char *)data, 4096); + if (ret == -1) + return ret; + + pcheckdata = (unsigned int *)&data[4096 - 32]; + icheck_stopvalue = pcheckdata[1]; + + if (icheck_stopvalue == 0xFFFFFFFF) { + isize = pcheckdata[0]; + if (isize == 0) { + /* Finish Log */ + bget_nextlog = false; + } else if (bSortLog) { + /* No Full 4K Package */ + for (iend = 0; iend < isize - 32; iend += sizeof(struct eventlog)) { + if (memcmp(&data[iend], zerob, sizeof(struct eventlog)) != 0) { + memcpy(&data16k[ioffset16k], &data[iend], sizeof(struct eventlog)); + ioffset16k += sizeof(struct eventlog); + } + } + } else { + setfilecontent(filename, data, isize); + } + } else { + /* Full 4K Package */ + if ((pevlog->signature == EVLOG_SIG) && (pevlog->log_type == 1)) + bSortLog = true; + + if (bSortLog) { + for (iend = 0; iend < SIZE_4K; iend += sizeof(struct eventlog)) { + if (memcmp(&data[iend], zerob, sizeof(struct eventlog)) != 0) { + memcpy(&data16k[ioffset16k], &data[iend], sizeof(struct eventlog)); + ioffset16k += sizeof(struct eventlog); + } + } + + iblock++; + if (iblock == 4) { + sort_eventlog((struct eventlog *)(data16k + sizeof(struct evlg_flush_hdr)), + (ioffset16k - sizeof(struct evlg_flush_hdr))/sizeof(struct eventlog)); + setfilecontent(filename, data16k, ioffset16k); + ioffset16k = 0; + iblock = 0; + memset(data16k, 0, SIZE_16K); + } + } else { + setfilecontent(filename, data, SIZE_4K); + } + + } + } + + if (bSortLog) { + if (ioffset16k > 0) { + sort_eventlog((struct eventlog *)(data16k + sizeof(struct evlg_flush_hdr)), + (ioffset16k - sizeof(struct evlg_flush_hdr))/sizeof(struct eventlog)); + setfilecontent(filename, data16k, ioffset16k); + } + } + + printf("\r" XCLEAN_LINE "Dump eventLog finish to %s\n", filename); + chmod(filename, 0666); + + if (cfg.clean_flg == 1) { + printf("Clean eventlog\n"); + nvme_vucmd(dev_fd(dev), NVME_VSC_CLEAN_EVENT_LOG, 0, 0, + (SRB_SIGNATURE >> 32), + (SRB_SIGNATURE & 0xFFFFFFFF), (char *)NULL, 0); + } + + dev_close(dev); + + return ret; +} + +static int innogrit_vsc_getcdump(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + time_t timep; + struct tm *logtime; + char currentdir[128], filename[512], fname[128]; + unsigned int itotal, icur, ivsctype; + unsigned char data[4096]; + struct cdumpinfo cdumpinfo; + unsigned char busevsc = false; + unsigned int ipackcount, ipackindex; + char fwvera[32]; + const char *desc = "Recrieve cdump data for the given device "; + struct nvme_dev *dev; + int ret = -1; + + OPT_ARGS(opts) = { + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + if (getcwd(currentdir, 128) == NULL) + return -1; + + time(&timep); + logtime = localtime(&timep); + + ivsctype = 0; + ipackindex = 0; + memset(data, 0, 4096); + + ret = nvme_vucmd(dev_fd(dev), 0xFE, 0x82, 0x03, 0x00, 0x00, (char *)data, 4096); + if (ret == -1) + return ret; + + if (data[0] == 0x5A) { + ivsctype = 1; + ret = nvme_vucmd(dev_fd(dev), 0xFE, 0x82, 0x08, 0x00, 0x00, (char *)data, 4096); + } else { + ivsctype = 0; + ret = nvme_vucmd(dev_fd(dev), NVME_VSC_GET, VSC_FN_GET_CDUMP, 0x00, + (SRB_SIGNATURE >> 32), (SRB_SIGNATURE & 0xFFFFFFFF), + (char *)data, 4096); + } + if (ret == -1) + return ret; + + memcpy(&cdumpinfo, &data[3072], sizeof(cdumpinfo)); + if (cdumpinfo.sig == 0x5a5b5c5d) { + busevsc = true; + ipackcount = cdumpinfo.ipackcount; + if (ipackcount == 0) { + itotal = 0; + } else { + itotal = cdumpinfo.cdumppack[ipackindex].ilenth; + memset(fwvera, 0, sizeof(fwvera)); + memcpy(fwvera, cdumpinfo.cdumppack[ipackindex].fwver, 8); + sprintf(fname, "cdump_%02d%02d-%02d%02d%02d_%d_%s.cdp", logtime->tm_mon+1, + logtime->tm_mday, logtime->tm_hour, logtime->tm_min, logtime->tm_sec, + ipackindex, fwvera); + sprintf(filename, "%s/%s", currentdir, fname); + } + } + + + if (busevsc == false) { + memset(data, 0, 4096); + ret = nvme_get_nsid_log(dev_fd(dev), true, 0x07, + NVME_NSID_ALL, + 4096, data); + if (ret != 0) + return ret; + + ipackcount = 1; + memcpy(&itotal, &data[4092], 4); + sprintf(fname, "cdump_%02d%02d-%02d%02d%02d.cdp", logtime->tm_mon+1, logtime->tm_mday, + logtime->tm_hour, logtime->tm_min, logtime->tm_sec); + sprintf(filename, "%s/%s", currentdir, fname); + } + + if (itotal == 0) { + printf("no cdump data\n"); + return 0; + } + + while (ipackindex < ipackcount) { + memset(data, 0, 4096); + strcpy((char *)data, "cdumpstart"); + setfilecontent(filename, data, strlen((char *)data)); + for (icur = 0; icur < itotal; icur += 4096) { + memset(data, 0, 4096); + if (busevsc) { + if (ivsctype == 1) + ret = nvme_vucmd(dev_fd(dev), 0xFE, 0x82, 0x08, 0x00, 0x00, + (char *)data, 4096); + else + ret = nvme_vucmd(dev_fd(dev), NVME_VSC_GET, VSC_FN_GET_CDUMP, 0x00, + (SRB_SIGNATURE >> 32), (SRB_SIGNATURE & 0xFFFFFFFF), + (char *)data, 4096); + } else { + ret = nvme_get_nsid_log(dev_fd(dev), true, + 0x07, + NVME_NSID_ALL, 4096, data); + } + if (ret != 0) + return ret; + + setfilecontent(filename, data, 4096); + + printf("\rWait for dump data %d%%" XCLEAN_LINE, ((icur+4096) * 100/itotal)); + } + memset(data, 0, 4096); + strcpy((char *)data, "cdumpend"); + setfilecontent(filename, data, strlen((char *)data)); + printf("\r%s\n", fname); + ipackindex++; + if (ipackindex != ipackcount) { + memset(data, 0, 4096); + if (busevsc) { + if (ivsctype == 1) + ret = nvme_vucmd(dev_fd(dev), 0xFE, 0x82, 0x08, 0x00, 0x00, + (char *)data, 4096); + else + ret = nvme_vucmd(dev_fd(dev), NVME_VSC_GET, VSC_FN_GET_CDUMP, 0x00, + (SRB_SIGNATURE >> 32), (SRB_SIGNATURE & 0xFFFFFFFF), + (char *)data, 4096); + } else { + ret = nvme_get_nsid_log(dev_fd(dev), true, + 0x07, + NVME_NSID_ALL, 4096, + data); + } + if (ret != 0) + return ret; + + itotal = cdumpinfo.cdumppack[ipackindex].ilenth; + memset(fwvera, 0, sizeof(fwvera)); + memcpy(fwvera, cdumpinfo.cdumppack[ipackindex].fwver, 8); + sprintf(fname, "cdump_%02d%02d-%02d%02d%02d_%d_%s.cdp", logtime->tm_mon+1, + logtime->tm_mday, logtime->tm_hour, logtime->tm_min, logtime->tm_sec, + ipackindex, fwvera); + sprintf(filename, "%s/%s", currentdir, fname); + } + + } + + printf("\n"); + dev_close(dev); + return ret; +} diff --git a/plugins/innogrit/innogrit-nvme.h b/plugins/innogrit/innogrit-nvme.h new file mode 100644 index 0000000..2de0502 --- /dev/null +++ b/plugins/innogrit/innogrit-nvme.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/innogrit/innogrit-nvme + +#if !defined(INNOGRIT_NVME) || defined(CMD_HEADER_MULTI_READ) +#define INNOGRIT_NVME + +#include "cmd.h" + +PLUGIN(NAME("innogrit", "innogrit vendor specific extensions", NVME_VERSION), + COMMAND_LIST( + ENTRY("smart-log-add", "Retrieve innogrit SMART Log, show it", innogrit_smart_log_additional) + ENTRY("get-eventlog", "get event log", innogrit_vsc_geteventlog) + ENTRY("get-cdump", "get cdump data", innogrit_vsc_getcdump) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/innogrit/typedef.h b/plugins/innogrit/typedef.h new file mode 100644 index 0000000..f2a59b4 --- /dev/null +++ b/plugins/innogrit/typedef.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#define SIZE_4K 4096 +#define SIZE_16K 16384 + +#define NVME_VSC_GET_EVENT_LOG 0xC2 +#define NVME_VSC_CLEAN_EVENT_LOG 0xD8 +#define NVME_VSC_GET 0xE6 +#define VSC_FN_GET_CDUMP 0x08 +#define EVLOG_SIG 0x65766C67 +#define IGVSC_SIG 0x69677673 +#define SRB_SIGNATURE 0x544952474F4E4E49ULL +#define XCLEAN_LINE "\033[K" + +struct evlg_flush_hdr { + unsigned int signature; + unsigned int fw_ver[2]; + unsigned int fw_type : 8; + unsigned int log_type : 8; + unsigned int project : 16; + unsigned int trace_cnt; + unsigned int sout_crc; + unsigned int reserved[2]; +}; + +struct eventlog { + unsigned int ms; + unsigned int param[7]; +}; + +struct eventlog_addindex { + unsigned int ms; + unsigned int param[7]; + unsigned int iindex; +}; + +#pragma pack(push) +#pragma pack(1) +struct vsc_smart_log { + unsigned short defect_cnt; + unsigned short slc_spb_cnt; + unsigned int slc_total_ec_cnt; + unsigned int slc_max_ec_cnt; + unsigned int slc_min_ec_cnt; + unsigned int slc_avg_ec_cnt; + unsigned int total_ec_cnt; + unsigned int max_ec_cnt; + unsigned int min_ec_cnt; + unsigned int avg_ec_cnt; + unsigned int mrd_rr_good_cnt; + unsigned int ard_rr_good_cnt; + unsigned int preset_cnt; + unsigned int nvme_reset_cnt; + unsigned int low_pwr_cnt; + unsigned int wa; + unsigned int ps3_entry_cnt; + u_char highest_temp[4]; + unsigned int weight_ec; + unsigned int slc_cap_mb; + unsigned long long nand_page_write_cnt; + unsigned int program_error_cnt; + unsigned int erase_error_cnt; + u_char flash_type; + u_char reserved2[3]; + unsigned int hs_crc_err_cnt; + unsigned int ddr_ecc_err_cnt; + unsigned int reserved3[44]; +}; +#pragma pack(pop) + +struct cdump_pack { + unsigned int ilenth; + char fwver[8]; +}; + +struct cdumpinfo { + unsigned int sig; + unsigned int ipackcount; + struct cdump_pack cdumppack[32]; +}; diff --git a/plugins/inspur/inspur-nvme.c b/plugins/inspur/inspur-nvme.c new file mode 100644 index 0000000..cda3507 --- /dev/null +++ b/plugins/inspur/inspur-nvme.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#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 "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "nvme-print.h" +#include "util/suffix.h" + +#define CREATE_CMD +#include "inspur-nvme.h" +#include "inspur-utils.h" + +void show_r1_vendor_log(r1_cli_vendor_log_t *vendorlog) +{ + int i = 0; + + if (vendorlog->device_state == 0) + printf("device_state : [healthy]\n"); + else + printf("device_state : [warning]\n"); + + printf("commit id : %s\n", vendorlog->commit_id); + printf("mcu data id(mcu) : 0x%x\n", le32_to_cpu(vendorlog->mcu_data_id)); + printf("power_info(mcu) : %u mW\n", le32_to_cpu(vendorlog->power_info)); + printf("voltage_info(mcu) : %u mV\n", le32_to_cpu(vendorlog->voltage_info)); + printf("current_info(mcu) : %u mA\n", le32_to_cpu(vendorlog->current_info)); + printf("history max_power(mcu) : %u mW\n", le32_to_cpu(vendorlog->max_power)); + printf("disk_max_temper(mcu) : %d C\n", le32_to_cpu(vendorlog->disk_max_temper) - 273); + printf("disk_overtemper_cout(mcu) : %u\n", le32_to_cpu(vendorlog->disk_overtemper_cout)); + printf("ctrl_max_temper(mcu) : %d C\n", le32_to_cpu(vendorlog->ctrl_max_temper) - 273); + printf("ctrl_overtemper_cout(mcu) : %u\n", le32_to_cpu(vendorlog->ctrl_overtemper_cout)); + printf("nand_max_temper(mcu) : %d C\n", le32_to_cpu(vendorlog->nand_max_temper) - 273); + printf("nand_overtemper_cout(mcu) : %u\n", le32_to_cpu(vendorlog->nand_overtemper_cout)); + + for (i = 0; i < 4; i++) + printf("temperature[%d](mcu) : %d C\n", i, le32_to_cpu(vendorlog->current_temp[i]) - 273); + + printf("CAP Time from 32v to 27v(mcu) : %u ms\n", le32_to_cpu(vendorlog->cap_transtime.cap_trans_time1)); + printf("CAP Time from 27v to 10v(mcu) : %u ms\n", le32_to_cpu(vendorlog->cap_transtime.cap_trans_time2)); + printf("cap_health_state(mcu) : %u\n", le32_to_cpu(vendorlog->cap_health_state)); + printf("warning bit(mcu) : 0x%x%08x\n", le32_to_cpu(vendorlog->detail_warning[1]), + le32_to_cpu(vendorlog->detail_warning[0])); + printf("-->high_format_fail : %x\n", vendorlog->detail_warning_bit.high_format_fail); + printf("-->low_format_fail : %x\n", vendorlog->detail_warning_bit.low_format_fail); + printf("-->current sensor : %x\n", vendorlog->detail_warning_bit.self_test_fail1); + printf("-->nand temp sensor : %x\n", vendorlog->detail_warning_bit.self_test_fail2); + printf("-->board temp sensor : %x\n", vendorlog->detail_warning_bit.self_test_fail3); + printf("-->cntl temp sensor : %x\n", vendorlog->detail_warning_bit.self_test_fail4); + printf("-->cap_timer_test_fail : %x\n", vendorlog->detail_warning_bit.capacitance_test_fail); + printf("-->readOnly_after_rebuild : %x\n", vendorlog->detail_warning_bit.readOnly_after_rebuild); + printf("-->firmware_loss : %x\n", vendorlog->detail_warning_bit.firmware_loss); + printf("-->cap_self_test : %x\n", vendorlog->detail_warning_bit.cap_unsupply); + printf("-->spare_space_warning : %x\n", vendorlog->detail_warning_bit.spare_space_warning); + printf("-->lifetime_warning : %x\n", vendorlog->detail_warning_bit.lifetime_warning); + printf("-->temp_high_warning : %x\n", vendorlog->detail_warning_bit.temp_high_warning); + printf("-->temp_low_warning : %x\n", vendorlog->detail_warning_bit.temp_low_warning); + printf("-->mcu_disable(mcu) : %x\n", vendorlog->detail_warning_bit.mcu_disable); + printf("warning history bit(mcu) : 0x%x%08x\n", le32_to_cpu(vendorlog->detail_warning_his[1]), + le32_to_cpu(vendorlog->detail_warning_his[0])); + printf("-->high_format_fail : %x\n", vendorlog->detail_warning_his_bit.high_format_fail); + printf("-->low_format_fail : %x\n", vendorlog->detail_warning_his_bit.low_format_fail); + printf("-->current sensor : %x\n", vendorlog->detail_warning_his_bit.self_test_fail1); + printf("-->nand temp sensor : %x\n", vendorlog->detail_warning_his_bit.self_test_fail2); + printf("-->board temp sensor : %x\n", vendorlog->detail_warning_his_bit.self_test_fail3); + printf("-->cntl temp sensor : %x\n", vendorlog->detail_warning_his_bit.self_test_fail4); + printf("-->cap_timer_test_fail : %x\n", vendorlog->detail_warning_his_bit.capacitance_test_fail); + printf("-->readOnly_after_rebuild : %x\n", vendorlog->detail_warning_his_bit.readOnly_after_rebuild); + printf("-->firmware_loss : %x\n", vendorlog->detail_warning_his_bit.firmware_loss); + printf("-->cap_self_test : %x\n", vendorlog->detail_warning_his_bit.cap_unsupply); + printf("-->spare_space_warning : %x\n", vendorlog->detail_warning_his_bit.spare_space_warning); + printf("-->lifetime_warning : %x\n", vendorlog->detail_warning_his_bit.lifetime_warning); + printf("-->temp_high_warning : %x\n", vendorlog->detail_warning_his_bit.temp_high_warning); + printf("-->temp_low_warning : %x\n", vendorlog->detail_warning_his_bit.temp_low_warning); + printf("-->mcu_disable(mcu) : %x\n", vendorlog->detail_warning_his_bit.mcu_disable); + + for (i = 0; i < 4; i++) + printf("[%d]nand_bytes_written : %" PRIu64 " GB\n", i, le64_to_cpu(vendorlog->nand_bytes_written[i])); + + for (i = 0; i < 4; i++) { + printf("[%d]io_apptag_err : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_apptag_err)); + printf("[%d]io_guard_err : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_guard_err)); + printf("[%d]io_reftag_err : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_reftag_err)); + printf("[%d]io_read_fail_cout : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_read_fail_cout)); + printf("[%d]io_write_fail_cout : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_write_fail_cout)); + printf("[%d]io_dma_disable_err : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_dma_disable_err)); + printf("[%d]io_dma_fatal_err : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_dma_fatal_err)); + printf("[%d]io_dma_linkdown_err : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_dma_linkdown_err)); + printf("[%d]io_dma_timeout_err : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_dma_timeout_err)); + printf("[%d]lba_err[0] : %u\n", i, le32_to_cpu(vendorlog->io_err[i].lba_err[0])); + printf("[%d]lba_err[1] : %u\n", i, le32_to_cpu(vendorlog->io_err[i].lba_err[1])); + printf("[%d]lba_err[2] : %u\n", i, le32_to_cpu(vendorlog->io_err[i].lba_err[2])); + printf("[%d]lba_err[3] : %u\n", i, le32_to_cpu(vendorlog->io_err[i].lba_err[3])); + printf("[%d]lba_err[4] : %u\n", i, le32_to_cpu(vendorlog->io_err[i].lba_err[4])); + printf("[%d]lba_err[5] : %u\n", i, le32_to_cpu(vendorlog->io_err[i].lba_err[5])); + } + + printf("temp_throttle_per : %u\n", le32_to_cpu(vendorlog->temp_throttle_per)); + printf("port0_flreset_cnt : %" PRIu64 "\n", le64_to_cpu(vendorlog->port0_fundamental_reset_cnt)); + printf("port0_hot_reset_cnt : %" PRIu64 "\n", le64_to_cpu(vendorlog->port0_hot_reset_cnt)); + printf("port0_func_reset_cnt : %" PRIu64 "\n", le64_to_cpu(vendorlog->port0_func_reset_cnt)); + printf("port0_linkdown_cnt : %" PRIu64 "\n", le64_to_cpu(vendorlog->port0_linkdown_cnt)); + printf("port0_ctrl_reset_cnt : %" PRIu64 "\n", le64_to_cpu(vendorlog->port0_ctrl_reset_cnt)); + printf("ces_RcvErr_cnt : %u\n", le32_to_cpu(vendorlog->ces_RcvErr_cnt)); + printf("ces_BadTlp_cnt : %u\n", le32_to_cpu(vendorlog->ces_BadTlp_cnt)); + printf("ces_BadDllp_cnt : %u\n", le32_to_cpu(vendorlog->ces_BadDllp_cnt)); + printf("ces_Rplyover_cnt : %u\n", le32_to_cpu(vendorlog->ces_Rplyover_cnt)); + printf("ces_RplyTo_cnt : %u\n", le32_to_cpu(vendorlog->ces_RplyTo_cnt)); + printf("ces_Hlo_cnt : %u\n", le32_to_cpu(vendorlog->ces_Hlo_cnt)); + printf("scan doorbell err cnt : %u\n", le32_to_cpu(vendorlog->scan_db_err_cnt)); + printf("doorbell interrupt err cnt : %u\n", le32_to_cpu(vendorlog->db_int_err_cnt)); + + printf("------------ncm-----------------------\n"); + for (i = 0; i < 4; i++) { + printf("------------part%d-----------------------\n", i); + printf("[%d]nand_rd_unc_count : %u\n", i, + le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_rd_unc_cnt)); + printf("[%d]nand_rd_srr_count : %u\n", i, + le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_rd_srr_cnt)); + printf("[%d]nand_rd_sdecode_count : %u\n", i, + le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_rd_soft_decode_cnt)); + printf("[%d]nand_rd_rb_fail_count : %u\n", i, + le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_rd_rebuild_fail_cnt)); + printf("[%d]nand_prg_fail_count : %u\n", i, + le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_prg_fail_cnt)); + printf("[%d]nand_eras_fail_count : %u\n", i, + le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_eras_fail_cnt)); + printf("[%d]nand_rd_count : %" PRIu64 "\n", i, + le64_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_rd_cnt)); + printf("[%d]nand_prg_count : %" PRIu64 "\n", i, + le64_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_prg_cnt)); + printf("[%d]nand_eras_count : %" PRIu64 "\n", i, + le64_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_eras_cnt)); + printf("[%d]BE_scan_unc_count : %u\n", i, + le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].BE_scan_unc_cnt)); + printf("[%d]rebuild_req_cnt : %u\n", i, + le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].rebuild_req_cnt)); + printf("[%d]retry_req_cnt : %u\n", i, + le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].retry_req_cnt)); + printf("[%d]retry_success_cnt : %u\n", i, + le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].retry_success_cnt)); + printf("[%d]prg_badblk_num : %u\n", i, + le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].prg_badblk_num)); + printf("[%d]eras_badblk_num : %u\n", i, + le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].eras_badblk_num)); + printf("[%d]read_badblk_num : %u\n", i, + le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].unc_badblk_num)); + } + + printf("[%d]temp_ctrl_limit_count : %u\n", i, le32_to_cpu(vendorlog->temp_ctrl_limit_cnt)); + printf("[%d]temp_ctrl_stop_count : %u\n", i, le32_to_cpu(vendorlog->temp_ctrl_stop_cnt)); + printf("------------wlm-----------------------\n"); + for (i = 0; i < 4; i++) { + printf("------------part%d-----------------------\n", i); + printf("[%d]fbb_count : %u\n", i, + le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].fbb_count)); + printf("[%d]ebb_count : %u\n", i, + le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].ebb_count)); + printf("[%d]lbb_count : %u\n", i, + le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].lbb_count)); + printf("[%d]gc_read_count : %u\n", i, + le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].gc_read_count)); + printf("[%d]gc_write_count : %u\n", i, + le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].gc_write_count)); + printf("[%d]gc_write_fail_count : %u\n", i, + le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].gc_write_fail_count)); + printf("[%d]force_gc_count : %u\n", i, + le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].force_gc_count)); + printf("[%d]avg_pe_count : %u\n", i, + le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].avg_pe_count)); + printf("[%d]max_pe_count : %u\n", i, + le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].max_pe_count)); + printf("[%d]free_blk_num1 : %u\n", i, + le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].free_blk_num1)); + printf("[%d]free_blk_num2 : %u\n", i, + le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].free_blk_num2)); + } + + printf("------------lkm-----------------------\n"); + printf("[%d]e2e_check_err_count1 : %u\n", i, le32_to_cpu(vendorlog->e2e_check_err_cnt1)); + printf("[%d]e2e_check_err_count2 : %u\n", i, le32_to_cpu(vendorlog->e2e_check_err_cnt2)); + printf("[%d]e2e_check_err_count3 : %u\n", i, le32_to_cpu(vendorlog->e2e_check_err_cnt3)); + printf("[%d]e2e_check_err_count4 : %u\n", i, le32_to_cpu(vendorlog->e2e_check_err_cnt4)); +} + +void show_r1_media_err_log(r1_cli_vendor_log_t *vendorlog) +{ + int i, j; + + for (i = 0; i < 4; i++) { + printf("DM%d read err lba:\n", i); + for (j = 0; j < 10; j++) + printf("[%d]lba : %" PRIu64 "\n", j, le64_to_cpu(vendorlog->media_err[i].lba_err[j])); + } +} + +static int nvme_get_vendor_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + __u8 local_mem[BYTE_OF_4K]; + char *desc = "Get the Inspur vendor log"; + struct nvme_dev *dev; + int err; + + OPT_ARGS(opts) = { OPT_END() }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + memset(local_mem, 0, BYTE_OF_4K); + err = nvme_get_log_simple(dev_fd(dev), + (enum nvme_cmd_get_log_lid)VENDOR_SMART_LOG_PAGE, + sizeof(r1_cli_vendor_log_t), local_mem); + if (!err) { + show_r1_vendor_log((r1_cli_vendor_log_t *)local_mem); + show_r1_media_err_log((r1_cli_vendor_log_t *)local_mem); + } else { + nvme_show_status(err); + } + + dev_close(dev); + return err; +} diff --git a/plugins/inspur/inspur-nvme.h b/plugins/inspur/inspur-nvme.h new file mode 100644 index 0000000..14a5e76 --- /dev/null +++ b/plugins/inspur/inspur-nvme.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/inspur/inspur-nvme + +#if !defined(INSPUR_NVME) || defined(CMD_HEADER_MULTI_READ) +#define INSPUR_NVME + +#include "cmd.h" + +PLUGIN(NAME("inspur", "Inspur vendor specific extensions", NVME_VERSION), + COMMAND_LIST( + ENTRY("nvme-vendor-log", "Retrieve Inspur Vendor Log, show it", nvme_get_vendor_log) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/inspur/inspur-utils.h b/plugins/inspur/inspur-utils.h new file mode 100644 index 0000000..d411bf0 --- /dev/null +++ b/plugins/inspur/inspur-utils.h @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __INSPUR_UTILS_H__ +#define __INSPUR_UTILS_H__ + +#define BYTE_OF_64K 65536UL +#define BYTE_OF_32K 32768UL +#define BYTE_OF_16K 16384UL +#define BYTE_OF_4K 4096UL +#define BYTE_OF_512 512UL +#define BYTE_OF_256 256UL +#define BYTE_OF_128 128UL + +/* Inspur specific LOG_PAGE_ID */ +typedef enum { + VENDOR_SMART_LOG_PAGE = 0xc0, +} vendor_sepc_log_page_id_e; + +#pragma pack(push, 1) +typedef struct r1_am_cap_transtime { + __u32 cap_trans_time1 : 16; + __u32 cap_trans_time2 : 16; +} r1_cap_transtime_t; + +typedef struct vendor_warning_bit { + __u32 high_format_fail : 1; + __u32 low_format_fail : 1; + __u32 rebuild_fail1 : 1; + __u32 rebuild_fail2 : 1; + __u32 rebuild_fail3 : 1; + __u32 rebuild_fail4 : 1; + __u32 rebuild_fail5 : 1; + __u32 rebuild_fail6 : 1; + __u32 self_test_fail1 : 1; + __u32 self_test_fail2 : 1; + __u32 self_test_fail3 : 1; + __u32 self_test_fail4 : 1; + __u32 internal_err1 : 1; + __u32 internal_err2 : 1; + __u32 internal_err3 : 1; + __u32 internal_err4 : 1; + __u32 internal_err5 : 1; + __u32 internal_err6 : 1; + __u32 internal_err7 : 1; + __u32 internal_err8 : 1; + __u32 internal_err9 : 1; + __u32 internal_err10 : 1; + __u32 internal_err11 : 1; + __u32 internal_err12 : 1; + __u32 internal_err13 : 1; + __u32 internal_err14 : 1; + __u32 internal_err15 : 1; + __u32 internal_err16 : 1; + __u32 capacitance_test_fail : 1; + __u32 IO_read_fail : 1; + __u32 IO_write_fail : 1; + __u32 readOnly_after_rebuild : 1; + __u32 firmware_loss : 1; + __u32 cap_unsupply : 1; + __u32 spare_space_warning : 1; + __u32 lifetime_warning : 1; + __u32 temp_high_warning : 1; + __u32 temp_low_warning : 1; + __u32 mcu_disable : 1; + __u32 rsv : 25; +} vendor_warning_str; + +typedef struct r1_vendor_log_ncm_cout { + __u32 nand_rd_unc_cnt; + __u32 nand_rd_srr_cnt; + __u32 nand_rd_soft_decode_cnt; + __u32 nand_rd_rebuild_fail_cnt; + __u32 nand_prg_fail_cnt; + __u32 nand_eras_fail_cnt; + __u64 nand_rd_cnt; + __u64 nand_prg_cnt; + __u64 nand_eras_cnt; + __u32 BE_scan_unc_cnt; + __u32 rebuild_req_cnt; + __u16 retry_req_cnt; + __u16 retry_success_cnt; + __u32 prg_badblk_num; + __u32 eras_badblk_num; + __u32 unc_badblk_num; +} r1_vendor_log_nandctl_count_t; + +typedef struct r1_wearlvl_vendor_log_count { + __u32 fbb_count; + __u32 ebb_count; + __u32 lbb_count; + __u32 gc_read_count; + __u32 gc_write_count; + __u32 gc_write_fail_count; + __u32 force_gc_count; + __u32 avg_pe_count; + __u32 max_pe_count; + __u32 free_blk_num1; + __u32 free_blk_num2; +} r1_wearlvl_vendor_log_count_t; + +typedef struct vendor_media_err { + __u64 lba_err[10]; +} vendor_media_err_t; + +typedef struct r1_vendor_log_io_err { + __u32 io_guard_err; + __u32 io_apptag_err; + __u32 io_reftag_err; + __u32 io_dma_linkdown_err; + __u32 io_dma_disable_err; + __u32 io_dma_timeout_err; + __u32 io_dma_fatal_err; + __u32 io_write_fail_cout; + __u32 io_read_fail_cout; + __u32 lba_err[6]; +} r1_vendor_log_io_err_t; + +typedef struct r1_vendor_log_s { + __u32 max_power; + __u32 disk_max_temper; + __u32 disk_overtemper_cout; + __u32 ctrl_max_temper; + __u32 ctrl_overtemper_cout; + r1_cap_transtime_t cap_transtime; + __u32 cap_health_state; + __u32 device_state; + r1_vendor_log_io_err_t io_err[4]; + union { + vendor_warning_str detail_warning_bit; + __u32 detail_warning[2]; + }; + union { + vendor_warning_str detail_warning_his_bit; + __u32 detail_warning_his[2]; + }; + __u32 ddr_bit_err_cout; + __u32 temp_throttle_per; + __u64 port0_fundamental_reset_cnt; + __u64 port0_hot_reset_cnt; + __u64 port0_func_reset_cnt; + __u64 port0_linkdown_cnt; + __u64 port0_ctrl_reset_cnt; + __u64 nand_bytes_written[4]; + __u32 power_info; + __u32 voltage_info; + __u32 current_info; + __u32 current_temp[4]; + __u32 nand_max_temper; + __u32 nand_overtemper_cout; + __u32 mcu_data_id; + __u8 commit_id[16]; + __u32 ces_RcvErr_cnt; + __u32 ces_BadTlp_cnt; + __u32 ces_BadDllp_cnt; + __u32 ces_Rplyover_cnt; + __u32 ces_RplyTo_cnt; + __u32 ces_Hlo_cnt; + __u32 scan_db_err_cnt; + __u32 db_int_err_cnt; + __u8 rsvFE[56]; + r1_vendor_log_nandctl_count_t vendor_log_nandctl_cnt[4]; + __u32 temp_ctrl_limit_cnt; + __u32 temp_ctrl_stop_cnt; + __u8 rsvncm[216]; + r1_wearlvl_vendor_log_count_t wearlvl_vendor_log_count[4]; + __u8 rsvwlm[512 - sizeof(r1_wearlvl_vendor_log_count_t) * 4 % 512]; + __u32 e2e_check_err_cnt1; + __u32 e2e_check_err_cnt2; + __u32 e2e_check_err_cnt3; + __u32 e2e_check_err_cnt4; + vendor_media_err_t media_err[4]; + __u8 rsvlkm[176]; +} r1_cli_vendor_log_t; +#pragma pack(pop) + +#endif // __INSPUR_UTILS_H__ diff --git a/plugins/intel/intel-nvme.c b/plugins/intel/intel-nvme.c new file mode 100644 index 0000000..378ecc0 --- /dev/null +++ b/plugins/intel/intel-nvme.c @@ -0,0 +1,1738 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-print.h" + +#define CREATE_CMD +#include "intel-nvme.h" + +struct __packed nvme_additional_smart_log_item { + __u8 key; + __u8 _kp[2]; + __u8 norm; + __u8 _np; + union __packed { + __u8 raw[6]; + struct __packed wear_level { + __le16 min; + __le16 max; + __le16 avg; + } wear_level; + struct __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_additional_smart_log_item host_ctx_wear_used; + struct nvme_additional_smart_log_item perf_stat_indicator; + struct nvme_additional_smart_log_item re_alloc_sectr_cnt; + struct nvme_additional_smart_log_item soft_ecc_err_rate; + struct nvme_additional_smart_log_item unexp_power_loss; + struct nvme_additional_smart_log_item media_bytes_read; + struct nvme_additional_smart_log_item avail_fw_downgrades; +}; + +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_double(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); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->host_ctx_wear_used.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->host_ctx_wear_used.raw)); + json_object_add_value_object(dev_stats, "host_ctx_wear_used", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->perf_stat_indicator.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->perf_stat_indicator.raw)); + json_object_add_value_object(dev_stats, "perf_stat_indicator", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->re_alloc_sectr_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->re_alloc_sectr_cnt.raw)); + json_object_add_value_object(dev_stats, "re_alloc_sectr_cnt", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->soft_ecc_err_rate.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->soft_ecc_err_rate.raw)); + json_object_add_value_object(dev_stats, "soft_ecc_err_rate", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->unexp_power_loss.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->unexp_power_loss.raw)); + json_object_add_value_object(dev_stats, "unexp_power_loss", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->media_bytes_read.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->media_bytes_read.raw)); + json_object_add_value_object(dev_stats, "media_bytes_read", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->avail_fw_downgrades.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->avail_fw_downgrades.raw)); + json_object_add_value_object(dev_stats, "avail_fw_downgrades", entry_stats); + + json_object_add_value_object(root, "Device stats", dev_stats); + + json_print_object(root, NULL); + json_free_object(root); +} + +static char *id_to_key(__u8 id) +{ + switch (id) { + case 0xAB: + return "program_fail_count"; + case 0xAC: + return "erase_fail_count"; + case 0xAD: + return "wear_leveling_count"; + case 0xB8: + return "e2e_error_detect_count"; + case 0xC7: + return "crc_error_count"; + case 0xE2: + return "media_wear_percentage"; + case 0xE3: + return "host_reads"; + case 0xE4: + return "timed_work_load"; + case 0xEA: + return "thermal_throttle_status"; + case 0xF0: + return "retry_buff_overflow_count"; + case 0xF3: + return "pll_lock_loss_counter"; + case 0xF4: + return "nand_bytes_written"; + case 0xF5: + return "host_bytes_written"; + case 0xF6: + return "host_context_wear_used"; + case 0xF7: + return "performance_status_indicator"; + case 0xF8: + return "media_bytes_read"; + case 0xF9: + return "available_fw_downgrades"; + case 0x05: + return "re-allocated_sector_count"; + case 0x0D: + return "soft_ecc_error_rate"; + case 0xAE: + return "unexpected_power_loss"; + default: + return "Invalid ID"; + } +} + +static void print_intel_smart_log_items(struct nvme_additional_smart_log_item *item) +{ + if (!item->key) + return; + + printf("%#x %-45s %3d %"PRIu64"\n", + item->key, id_to_key(item->key), + item->norm, int48_to_long(item->raw)); +} + +static void show_intel_smart_log(struct nvme_additional_smart_log *smart, + unsigned int nsid, const char *devname) +{ + struct nvme_additional_smart_log_item *iter = &smart->program_fail_cnt; + int num_items = sizeof(struct nvme_additional_smart_log) / + sizeof(struct nvme_additional_smart_log_item); + + printf("Additional Smart Log for NVME device:%s namespace-id:%x\n", + devname, nsid); + printf("ID KEY Normalized Raw\n"); + + for (int i = 0; i < num_items; i++, iter++) + print_intel_smart_log_items(iter); +} + +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; + struct nvme_dev *dev; + int err; + + struct config { + __u32 namespace_id; + bool raw_binary; + bool 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = nvme_get_log_simple(dev_fd(dev), 0xca, sizeof(smart_log), + &smart_log); + if (!err) { + if (cfg.json) + show_intel_smart_log_jsn(&smart_log, cfg.namespace_id, + dev->name); + else if (!cfg.raw_binary) + show_intel_smart_log(&smart_log, cfg.namespace_id, + dev->name); + else + d_raw((unsigned char *)&smart_log, sizeof(smart_log)); + } else if (err > 0) { + nvme_show_status(err); + } + dev_close(dev); + 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"; + struct nvme_dev *dev; + char log[512]; + int err; + + struct config { + bool raw_binary; + }; + + struct config cfg = { + }; + + OPT_ARGS(opts) = { + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = nvme_get_log_simple(dev_fd(dev), 0xdd, 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) + nvme_show_status(err); + dev_close(dev); + 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; + struct nvme_dev *dev; + int err; + + const char *desc = "Get Temperature Statistics log and show it."; + const char *raw = "dump output in binary format"; + struct config { + bool raw_binary; + }; + + struct config cfg = { + }; + + OPT_ARGS(opts) = { + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = nvme_get_log_simple(dev_fd(dev), 0xc5, 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) + nvme_show_status(err); + dev_close(dev); + return err; +} + +struct intel_lat_stats { + __u16 maj; + __u16 min; + __u32 data[1216]; +}; + +struct __packed optane_lat_stats { + __u16 maj; + __u16 min; + __u64 data[9]; +}; + +#define MEDIA_MAJOR_IDX 0 +#define MEDIA_MINOR_IDX 1 +#define MEDIA_MAX_LEN 2 +#define OPTANE_V1000_BUCKET_LEN 8 +#define OPTANE_V1000_BUCKET_LEN 8 +#define NAND_LAT_STATS_LEN 4868 + + +static struct intel_lat_stats stats; +static struct optane_lat_stats v1000_stats; + +struct v1000_thresholds { + __u32 read[OPTANE_V1000_BUCKET_LEN]; + __u32 write[OPTANE_V1000_BUCKET_LEN]; +}; + +static struct v1000_thresholds v1000_bucket; +static __u16 media_version[MEDIA_MAX_LEN] = {0}; + +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 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 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) +{ + char *string; + + if (bound_type != NOINF) { + snprintf(buffer, BUFSIZE, "%s", bound_type ? "+INF" : "-INF"); + return; + } + + switch (unit) { + case US: + string = "us"; + break; + case MS: + string = "ms"; + break; + case S: + string = "s"; + break; + default: + string = "_s"; + break; + } + + snprintf(buffer, BUFSIZE, "%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_optane_lat_stats_bucket(struct optane_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("%-*lu\n", COL_WIDTH, (unsigned long)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. + */ +#define LATENCY_STATS_V4_BASE_BITS 6 +#define LATENCY_STATS_V4_BASE_VAL (1 << LATENCY_STATS_V4_BASE_BITS) + +static int lat_stats_log_scale(int i) +{ + // 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_array_add(bucket_list, 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_add_bucket_optane(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_array_add(bucket_list, 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_uint(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_object_new_array(); + + 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_object_new_array(); + + 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_v1000_0(struct optane_lat_stats *stats, int write) +{ + int i; + struct json_object *root = json_create_object(); + struct json_object *bucket_list = json_object_new_array(); + + lat_stats_make_json_root(root, bucket_list, write); + + if (write) { + for (i = 0; i < OPTANE_V1000_BUCKET_LEN - 1; i++) + json_add_bucket_optane(bucket_list, i, + v1000_bucket.write[i], NOINF, + v1000_bucket.write[i + 1] - 1, + NOINF, + stats->data[i]); + + json_add_bucket_optane(bucket_list, + OPTANE_V1000_BUCKET_LEN - 1, + v1000_bucket.write[i], + NOINF, + v1000_bucket.write[i], + POSINF, stats->data[i]); + } else { + for (i = 0; i < OPTANE_V1000_BUCKET_LEN - 1; i++) + json_add_bucket_optane(bucket_list, i, + v1000_bucket.read[i], NOINF, + v1000_bucket.read[i + 1] - 1, + NOINF, + stats->data[i]); + + json_add_bucket_optane(bucket_list, + OPTANE_V1000_BUCKET_LEN - 1, + v1000_bucket.read[i], + NOINF, + v1000_bucket.read[i], + POSINF, stats->data[i]); + } + + struct json_object *subroot = json_create_object(); + + json_object_add_value_object(root, "Average latency since last reset", subroot); + + json_object_add_value_uint(subroot, "value in us", stats->data[8]); + + json_print_object(root, NULL); + json_free_object(root); + +} + +static void show_lat_stats_v1000_0(struct optane_lat_stats *stats, int write) +{ + int i; + + if (write) { + for (i = 0; i < OPTANE_V1000_BUCKET_LEN - 1; i++) + show_optane_lat_stats_bucket(stats, + v1000_bucket.write[i], + NOINF, + v1000_bucket.write[i + 1] - 1, + NOINF, i); + + show_optane_lat_stats_bucket(stats, v1000_bucket.write[i], + NOINF, v1000_bucket.write[i], + POSINF, i); + } else { + for (i = 0; i < OPTANE_V1000_BUCKET_LEN - 1; i++) + show_optane_lat_stats_bucket(stats, + v1000_bucket.read[i], + NOINF, + v1000_bucket.read[i + 1] - 1, + NOINF, i); + + show_optane_lat_stats_bucket(stats, v1000_bucket.read[i], + NOINF, v1000_bucket.read[i], + POSINF, i); + } + + printf("Average latency since last reset: %lu us\n", (unsigned long)stats->data[8]); + +} + +static void json_lat_stats(int write) +{ + switch (media_version[MEDIA_MAJOR_IDX]) { + case 3: + json_lat_stats_3_0(&stats, write); + break; + case 4: + switch (media_version[MEDIA_MINOR_IDX]) { + 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", + stats.maj, stats.min); + break; + } + break; + case 1000: + switch (media_version[MEDIA_MINOR_IDX]) { + case 0: + json_lat_stats_v1000_0(&v1000_stats, write); + break; + default: + printf("Unsupported minor revision (%u.%u)\n", + 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(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", + media_version[MEDIA_MAJOR_IDX], media_version[MEDIA_MINOR_IDX]); + print_dash_separator(separator_length); + printf("%-12s%-12s%-12s%-20s\n", "Bucket", "Start", "End", "Value"); + print_dash_separator(separator_length); + + switch (media_version[MEDIA_MAJOR_IDX]) { + case 3: + show_lat_stats_3_0(&stats); + break; + case 4: + switch (media_version[MEDIA_MINOR_IDX]) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + show_lat_stats_4_0(&stats); + break; + default: + printf("Unsupported minor revision (%u.%u)\n", + stats.maj, stats.min); + break; + } + break; + case 1000: + switch (media_version[MEDIA_MINOR_IDX]) { + case 0: + show_lat_stats_v1000_0(&v1000_stats, write); + break; + default: + printf("Unsupported minor revision (%u.%u)\n", + 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) +{ + __u8 data[NAND_LAT_STATS_LEN]; + struct nvme_dev *dev; + int err; + + const char *desc = "Get Intel Latency Statistics log and show it."; + const char *raw = "Dump output in binary format"; + const char *json = "Dump output in json format"; + const char *write = "Get write statistics (read default)"; + + struct config { + bool raw_binary; + bool json; + bool write; + }; + + struct config cfg = { + }; + + OPT_ARGS(opts) = { + OPT_FLAG("write", 'w', &cfg.write, write), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_FLAG("json", 'j', &cfg.json, json), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + /* For optate, latency stats are deleted every time their LID is pulled. + * Therefore, we query the longest lat_stats log page first. + */ + err = nvme_get_log_simple(dev_fd(dev), cfg.write ? 0xc2 : 0xc1, + sizeof(data), &data); + + media_version[0] = (data[1] << 8) | data[0]; + media_version[1] = (data[3] << 8) | data[2]; + + if (err) + goto close_dev; + + if (media_version[0] == 1000) { + __u32 thresholds[OPTANE_V1000_BUCKET_LEN] = {0}; + __u32 result; + + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = 0xf7, + .nsid = 0, + .sel = 0, + .cdw11 = cfg.write ? 0x1 : 0x0, + .uuidx = 0, + .data_len = sizeof(thresholds), + .data = thresholds, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_get_features(&args); + if (err) { + fprintf(stderr, "Querying thresholds failed. "); + nvme_show_status(err); + goto close_dev; + } + + /* Update bucket thresholds to be printed */ + if (cfg.write) { + for (int i = 0; i < OPTANE_V1000_BUCKET_LEN; i++) + v1000_bucket.write[i] = thresholds[i]; + } else { + for (int i = 0; i < OPTANE_V1000_BUCKET_LEN; i++) + v1000_bucket.read[i] = thresholds[i]; + + } + + /* Move counter values to optate stats struct which has a + * smaller size + */ + memcpy(&v1000_stats, (struct optane_lat_stats *)data, + sizeof(struct optane_lat_stats)); + } else { + memcpy(&stats, (struct intel_lat_stats *)data, + sizeof(struct intel_lat_stats)); + } + + if (cfg.json) + json_lat_stats(cfg.write); + else if (!cfg.raw_binary) + show_lat_stats(cfg.write); + else { + if (media_version[0] == 1000) + d_raw((unsigned char *)&v1000_stats, + sizeof(v1000_stats)); + else + d_raw((unsigned char *)&stats, + sizeof(stats)); + } + +close_dev: + dev_close(dev); + 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, NULL); + 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, 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; + struct nvme_dev *dev; + + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) { + free(intel); + return err; + } + + if (cfg.log > 2 || cfg.core > 4 || cfg.lnum > 255) { + err = -EINVAL; + goto out_free; + } + + if (!cfg.file) { + err = setup_file(f, cfg.file, dev_fd(dev), cfg.log); + if (err) + goto out_free; + 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); + if (output < 0) { + err = output; + goto out_free; + } + + err = read_header(&cmd, buf, dev_fd(dev), 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, dev_fd(dev), &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, + dev_fd(dev), + 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, dev_fd(dev), + 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, + dev_fd(dev), + 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, + dev_fd(dev), + buf); + if (err) + goto out; + } + } + } + err = 0; +out: + if (err > 0) { + nvme_show_status(err); + } else if (err < 0) { + perror("intel log"); + err = EIO; + } else + printf("Successfully wrote log to %s\n", cfg.file); + close(output); +out_free: + free(intel); + dev_close(dev); + return err; +} + +static int enable_lat_stats_tracking(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + 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; + struct nvme_dev *dev; + void *buf = NULL; + __u32 result; + int err; + + struct config { + bool enable, disable; + }; + + struct config cfg = { + .enable = false, + .disable = false, + }; + + struct argconfig_commandline_options command_line_options[] = { + {"enable", 'e', "", CFG_FLAG, &cfg.enable, no_argument, enable_desc}, + {"disable", 'd', "", CFG_FLAG, &cfg.disable, no_argument, disable_desc}, + {NULL} + }; + + err = parse_and_open(&dev, 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 (err) + return err; + + struct nvme_get_features_args args_get = { + .args_size = sizeof(args_get), + .fd = dev_fd(dev), + .fid = fid, + .nsid = nsid, + .sel = sel, + .cdw11 = cdw11, + .uuidx = 0, + .data_len = data_len, + .data = buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + struct nvme_set_features_args args_set = { + .args_size = sizeof(args_set), + .fd = dev_fd(dev), + .fid = fid, + .nsid = nsid, + .cdw11 = option, + .cdw12 = cdw12, + .save = save, + .uuidx = 0, + .cdw15 = 0, + .data_len = data_len, + .data = buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + switch (option) { + case None: + err = nvme_get_features(&args_get); + if (!err) { + printf( + "Latency Statistics Tracking (FID 0x%X) is currently (%i).\n", + fid, result); + } else { + printf("Could not read feature id 0xE2.\n"); + dev_close(dev); + return err; + } + break; + case True: + case False: + err = nvme_set_features(&args_set); + if (err > 0) { + nvme_show_status(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; + } + dev_close(dev); + return err; +} + +static int set_lat_stats_thresholds(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + const char *desc = "Write Intel Bucket Thresholds for Latency Statistics Tracking"; + const char *bucket_thresholds = "Bucket Threshold List, comma separated list: 0, 10, 20 ..."; + const char *write = "Set write bucket Thresholds for latency tracking (read default)"; + + const __u32 nsid = 0; + const __u8 fid = 0xf7; + const __u32 cdw12 = 0x0; + const __u32 save = 0; + struct nvme_dev *dev; + __u32 result; + int err, num; + + struct config { + bool write; + char *bucket_thresholds; + }; + + struct config cfg = { + .write = false, + .bucket_thresholds = "", + }; + + OPT_ARGS(opts) = { + OPT_FLAG("write", 'w', &cfg.write, write), + OPT_LIST("bucket-thresholds", 't', &cfg.bucket_thresholds, + bucket_thresholds), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + + if (err) + return err; + + /* Query maj and minor version first to figure out the amount of + * valid buckets a user is allowed to modify. Read or write doesn't + * matter + */ + err = nvme_get_log_simple(dev_fd(dev), 0xc2, + sizeof(media_version), media_version); + if (err) { + fprintf(stderr, "Querying media version failed. "); + nvme_show_status(err); + goto close_dev; + } + + if (media_version[0] == 1000) { + int thresholds[OPTANE_V1000_BUCKET_LEN] = {0}; + + num = argconfig_parse_comma_sep_array(cfg.bucket_thresholds, + thresholds, + sizeof(thresholds)); + if (num == -1) { + fprintf(stderr, "ERROR: Bucket list is malformed\n"); + goto close_dev; + + } + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = fid, + .nsid = nsid, + .cdw11 = cfg.write ? 0x1 : 0x0, + .cdw12 = cdw12, + .save = save, + .uuidx = 0, + .cdw15 = 0, + .data_len = sizeof(thresholds), + .data = thresholds, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_set_features(&args); + + if (err > 0) { + nvme_show_status(err); + } else if (err < 0) { + perror("Enable latency tracking"); + fprintf(stderr, "Command failed while parsing.\n"); + } + } else { + fprintf(stderr, "Unsupported command\n"); + } + +close_dev: + dev_close(dev); + return err; +} + diff --git a/plugins/intel/intel-nvme.h b/plugins/intel/intel-nvme.h new file mode 100644 index 0000000..165048a --- /dev/null +++ b/plugins/intel/intel-nvme.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#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", NVME_VERSION), + 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("set-bucket-thresholds", "Set Latency Stats Bucket Values, save it", set_lat_stats_thresholds) + 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/memblaze/memblaze-nvme.c b/plugins/memblaze/memblaze-nvme.c new file mode 100644 index 0000000..b215125 --- /dev/null +++ b/plugins/memblaze/memblaze-nvme.c @@ -0,0 +1,1842 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> +#include <time.h> + +#include "nvme.h" +#include "common.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-print.h" + +#define CREATE_CMD +#include "memblaze-nvme.h" +#include "memblaze-utils.h" + +enum { + /* feature id */ + MB_FEAT_POWER_MGMT = 0x02, + MB_FEAT_HIGH_LATENCY = 0xE1, + /* log id */ + GLP_ID_VU_GET_READ_LATENCY_HISTOGRAM = 0xC1, + GLP_ID_VU_GET_WRITE_LATENCY_HISTOGRAM = 0xC2, + GLP_ID_VU_GET_HIGH_LATENCY_LOG = 0xC3, + MB_FEAT_CLEAR_ERRORLOG = 0xF7, +}; + +#define LOG_PAGE_SIZE (0x1000) +#define DO_PRINT_FLAG (1) +#define NOT_PRINT_FLAG (0) +#define FID_C1_LOG_FILENAME "log_c1.csv" +#define FID_C2_LOG_FILENAME "log_c2.csv" +#define FID_C3_LOG_FILENAME "log_c3.csv" + +/* + * 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.13 = papaya */ +#define IS_PAPAYA(str) (!strcmp(str, "2.13")) +/* 2.83 = raisin */ +#define IS_RAISIN(str) (!strcmp(str, "2.83")) +/* 2.94 = kumquat */ +#define IS_KUMQUAT(str) (!strcmp(str, "2.94")) +/* 0.60 = loquat */ +#define IS_LOQUAT(str) (!strcmp(str, "0.60")) + +#define STR_VER_SIZE (5) + +int getlogpage_format_type(char *model_name) +{ + int logpage_format_type = INTEL_FORMAT; + const char *boundary_model_name1 = "P"; /* MEMBLAZE P7936DT0640M00 */ + const char *boundary_model_name2 = "P5920"; /* Use INTEL_FORMAT from Raisin P5920. */ + + if (!strncmp(model_name, boundary_model_name1, strlen(boundary_model_name1))) { + if (strncmp(model_name, boundary_model_name2, strlen(boundary_model_name2)) < 0) + logpage_format_type = MEMBLAZE_FORMAT; + } + return logpage_format_type; +} + +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); +} + +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)); + + if (!nm) { + if (raw) + free(raw); + return; + } + if (!raw) { + free(nm); + return; + } + + printf("%s:%s %s:%x\n", "Additional Smart Log for NVME device", devname, "namespace-id", nsid); + printf("%-34s%-11s%s\n", "key", "normalized", "raw"); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_PROGRAM_FAIL, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", "program_fail_count", *nm, int48_to_long(raw)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_ERASE_FAIL, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", "erase_fail_count", *nm, int48_to_long(raw)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_WEARLEVELING_COUNT, nm, raw); + printf("%-31s : %3d%% %s%u%s%u%s%u\n", "wear_leveling", *nm, + "min: ", *(__u16 *)raw, ", max: ", *(__u16 *)(raw+2), ", avg: ", *(__u16 *)(raw+4)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_E2E_DECTECTION_COUNT, nm, raw); + printf("%-31s: %3d%% %"PRIu64"\n", "end_to_end_error_detection_count", *nm, int48_to_long(raw)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_PCIE_CRC_ERR_COUNT, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", "crc_error_count", *nm, int48_to_long(raw)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TIMED_WORKLOAD_MEDIA_WEAR, nm, raw); + printf("%-32s: %3d%% %.3f%%\n", "timed_workload_media_wear", *nm, ((float)int48_to_long(raw))/1000); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TIMED_WORKLOAD_HOST_READ, nm, raw); + printf("%-32s: %3d%% %"PRIu64"%%\n", "timed_workload_host_reads", *nm, int48_to_long(raw)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TIMED_WORKLOAD_TIMER, nm, raw); + printf("%-32s: %3d%% %"PRIu64"%s\n", "timed_workload_timer", *nm, int48_to_long(raw), " min"); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_THERMAL_THROTTLE_STATUS, nm, raw); + printf("%-32s: %3d%% %u%%%s%"PRIu64"\n", "thermal_throttle_status", *nm, + *raw, ", cnt: ", int48_to_long(raw+1)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_RETRY_BUFF_OVERFLOW_COUNT, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", "retry_buffer_overflow_count", *nm, int48_to_long(raw)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_PLL_LOCK_LOSS_COUNT, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", "pll_lock_loss_count", *nm, int48_to_long(raw)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TOTAL_WRITE, nm, raw); + printf("%-32s: %3d%% %s%"PRIu64"\n", "nand_bytes_written", *nm, "sectors: ", int48_to_long(raw)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_HOST_WRITE, nm, raw); + printf("%-32s: %3d%% %s%"PRIu64"\n", "host_bytes_written", *nm, "sectors: ", int48_to_long(raw)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_SYSTEM_AREA_LIFE_LEFT, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", "system_area_life_left", *nm, int48_to_long(raw)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TOTAL_READ, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", "total_read", *nm, int48_to_long(raw)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TEMPT_SINCE_BORN, nm, raw); + printf("%-32s: %3d%% %s%u%s%u%s%u\n", "tempt_since_born", *nm, + "max: ", *(__u16 *)raw, ", min: ", *(__u16 *)(raw+2), ", curr: ", *(__u16 *)(raw+4)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_POWER_CONSUMPTION, nm, raw); + printf("%-32s: %3d%% %s%u%s%u%s%u\n", "power_consumption", *nm, + "max: ", *(__u16 *)raw, ", min: ", *(__u16 *)(raw+2), ", curr: ", *(__u16 *)(raw+4)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TEMPT_SINCE_BOOTUP, nm, raw); + printf("%-32s: %3d%% %s%u%s%u%s%u\n", "tempt_since_bootup", *nm, "max: ", *(__u16 *)raw, + ", min: ", *(__u16 *)(raw+2), ", curr: ", *(__u16 *)(raw+4)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_READ_FAIL, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", "read_fail_count", *nm, int48_to_long(raw)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_THERMAL_THROTTLE_TIME, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", "thermal_throttle_time", *nm, int48_to_long(raw)); + + get_memblaze_new_smart_info(smart, RAISIN_SI_VD_FLASH_MEDIA_ERROR, nm, raw); + printf("%-32s: %3d%% %"PRIu64"\n", "flash_media_error", *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 + 1]; + struct nvme_memblaze_smart_log_item *item; + + strncpy(fw_ver_local, fw_ver, STR_VER_SIZE); + *(fw_ver_local + STR_VER_SIZE) = '\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 kelvins since last factory reset : %u\n", + le16_to_cpu(smart->items[TEMPT_SINCE_RESET].temperature.max)); + printf("Minimum temperature in kelvins 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 kelvins since power on : %u\n", + le16_to_cpu(smart->items[TEMPT_SINCE_BOOTUP].temperature_p.max)); + printf("Minimum temperature in kelvins since power on : %u\n", + le16_to_cpu(smart->items[TEMPT_SINCE_BOOTUP].temperature_p.min)); + } + printf("Current temperature in kelvins : %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)); + + if (!nm) { + if (raw) + free(raw); + return; + } + if (!raw) { + free(nm); + return; + } + get_memblaze_new_smart_info(s, PROGRAM_FAIL, nm, raw); + printf("%-32s : %3d%% %"PRIu64"\n", + "program_fail_count", *nm, int48_to_long(raw)); + + get_memblaze_new_smart_info(s, ERASE_FAIL, nm, raw); + printf("%-32s : %3d%% %"PRIu64"\n", + "erase_fail_count", *nm, int48_to_long(raw)); + + get_memblaze_new_smart_info(s, WEARLEVELING_COUNT, nm, raw); + printf("%-31s : %3d%% %s%u%s%u%s%u\n", + "wear_leveling", *nm, "min: ", *(__u16 *)raw, ", max: ", *(__u16 *)(raw+2), ", avg: ", *(__u16 *)(raw+4)); + + get_memblaze_new_smart_info(s, TOTAL_WRITE, nm, raw); + printf("%-32s : %3d%% %"PRIu64"\n", + "nand_bytes_written", *nm, 32*int48_to_long(raw)); + + get_memblaze_new_smart_info(s, HOST_WRITE, nm, raw); + printf("%-32s : %3d%% %"PRIu64"\n", + "host_bytes_written", *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(ctrl.mn)) /* 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; +} + +int parse_params(char *str, int number, ...) +{ + va_list argp; + int *param; + char *c; + int value; + + va_start(argp, number); + + while (number > 0) { + c = strtok(str, ","); + if (!c) { + printf("No enough parameters. abort...\n"); + va_end(argp); + return 1; + } + + if (!isalnum((int)*c)) { + printf("%s is not a valid number\n", c); + va_end(argp); + return 1; + } + value = atoi(c); + param = va_arg(argp, int *); + *param = value; + + if (str) { + str = strchr(str, ','); + if (str) + str++; + } + number--; + } + va_end(argp); + + return 0; +} + +static int mb_get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct nvme_memblaze_smart_log smart_log; + 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 nvme_dev *dev; + struct config { + __u32 namespace_id; + bool raw_binary; + }; + int err; + + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = nvme_get_nsid_log(dev_fd(dev), false, 0xca, cfg.namespace_id, + sizeof(smart_log), &smart_log); + if (!err) { + if (!cfg.raw_binary) + err = show_memblaze_smart_log(dev_fd(dev), cfg.namespace_id, dev->name, + &smart_log); + else + d_raw((unsigned char *)&smart_log, sizeof(smart_log)); + } + if (err > 0) + nvme_show_status(err); + + dev_close(dev); + return err; +} + +static char *mb_feature_to_string(int feature) +{ + switch (feature) { + case MB_FEAT_POWER_MGMT: + return "Memblaze power management"; + case MB_FEAT_HIGH_LATENCY: + return "Memblaze high latency log"; + case MB_FEAT_CLEAR_ERRORLOG: + return "Memblaze clear error log"; + default: + return "Unknown"; + } +} + +static int mb_get_powermanager_status(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Get Memblaze power management ststus\n (value 0 - 25w, 1 - 20w, 2 - 15w)"; + __u32 result; + __u32 feature_id = MB_FEAT_POWER_MGMT; + struct nvme_dev *dev; + int err; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = feature_id, + .nsid = 0, + .sel = 0, + .cdw11 = 0, + .uuidx = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_get_features(&args); + if (err < 0) + perror("get-feature"); + if (!err) + printf("get-feature:0x%02x (%s), %s value: %#08x\n", feature_id, + mb_feature_to_string(feature_id), nvme_select_to_string(0), result); + else if (err > 0) + nvme_show_status(err); + dev_close(dev); + return err; +} + +static int mb_set_powermanager_status(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Set Memblaze power management status\n (value 0 - 25w, 1 - 20w, 2 - 15w)"; + const char *value = "new value of feature (required)"; + const char *save = "specifies that the controller shall save the attribute"; + struct nvme_dev *dev; + __u32 result; + int err; + + struct config { + __u32 feature_id; + __u32 value; + bool save; + }; + + struct config cfg = { + .feature_id = MB_FEAT_POWER_MGMT, + .value = 0, + .save = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("value", 'v', &cfg.value, value), + OPT_FLAG("save", 's', &cfg.save, save), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = cfg.feature_id, + .nsid = 0, + .cdw11 = cfg.value, + .cdw12 = 0, + .save = cfg.save, + .uuidx = 0, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_set_features(&args); + if (err < 0) + perror("set-feature"); + if (!err) + printf("set-feature:%02x (%s), value:%#08x\n", cfg.feature_id, + mb_feature_to_string(cfg.feature_id), cfg.value); + else if (err > 0) + nvme_show_status(err); + + dev_close(dev); + return err; +} + +#define P2MIN (1) +#define P2MAX (5000) +#define MB_FEAT_HIGH_LATENCY_VALUE_SHIFT (15) +static int mb_set_high_latency_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Set Memblaze high latency log\n" + " input parameter p1,p2\n" + " p1 value: 0 is disable, 1 is enable\n" + " p2 value: 1 .. 5000 ms"; + const char *param = "input parameters"; + int param1 = 0, param2 = 0; + struct nvme_dev *dev; + __u32 result; + int err; + + struct config { + __u32 feature_id; + char *param; + __u32 value; + }; + + struct config cfg = { + .feature_id = MB_FEAT_HIGH_LATENCY, + .param = "0,0", + .value = 0, + }; + + OPT_ARGS(opts) = { + OPT_LIST("param", 'p', &cfg.param, param), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (parse_params(cfg.param, 2, ¶m1, ¶m2)) { + printf("setfeature: invalid formats %s\n", cfg.param); + dev_close(dev); + return -EINVAL; + } + if ((param1 == 1) && (param2 < P2MIN || param2 > P2MAX)) { + printf("setfeature: invalid high io latency threshold %d\n", param2); + dev_close(dev); + return -EINVAL; + } + cfg.value = (param1 << MB_FEAT_HIGH_LATENCY_VALUE_SHIFT) | param2; + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = cfg.feature_id, + .nsid = 0, + .cdw11 = cfg.value, + .cdw12 = 0, + .save = false, + .uuidx = 0, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_set_features(&args); + if (err < 0) + perror("set-feature"); + if (!err) + printf("set-feature:0x%02X (%s), value:%#08x\n", cfg.feature_id, + mb_feature_to_string(cfg.feature_id), cfg.value); + else if (err > 0) + nvme_show_status(err); + + dev_close(dev); + return err; +} + +static int glp_high_latency_show_bar(FILE *fdi, int print) +{ + fPRINT_PARAM1("Memblaze High Latency Log\n"); + fPRINT_PARAM1("---------------------------------------------------------------------------------------------\n"); + fPRINT_PARAM1("Timestamp Type QID CID NSID StartLBA NumLBA Latency\n"); + fPRINT_PARAM1("---------------------------------------------------------------------------------------------\n"); + return 0; +} + +/* + * High latency log page definition + * Total 32 bytes + */ +struct log_page_high_latency { + __u8 port; + __u8 revision; + __u16 rsvd; + __u8 opcode; + __u8 sqe; + __u16 cid; + __u32 nsid; + __u32 latency; + __u64 sLBA; + __u16 numLBA; + __u16 timestampH; + __u32 timestampL; +}; /* total 32 bytes */ + +static int find_deadbeef(char *buf) +{ + if (((*(buf + 0) & 0xff) == 0xef) && ((*(buf + 1) & 0xff) == 0xbe) && + ((*(buf + 2) & 0xff) == 0xad) && ((*(buf + 3) & 0xff) == 0xde)) + return 1; + return 0; +} + +#define TIME_STR_SIZE (44) +static int glp_high_latency(FILE *fdi, char *buf, int buflen, int print) +{ + struct log_page_high_latency *logEntry; + char string[TIME_STR_SIZE]; + int i, entrySize; + __u64 timestamp; + time_t tt = 0; + struct tm *t = NULL; + int millisec = 0; + + if (find_deadbeef(buf)) + return 0; + + entrySize = sizeof(struct log_page_high_latency); + for (i = 0; i < buflen; i += entrySize) { + logEntry = (struct log_page_high_latency *)(buf + i); + + if (logEntry->latency == 0 && logEntry->revision == 0) + return 1; + + if (!logEntry->timestampH) { /* generate host time string */ + snprintf(string, sizeof(string), "%d", logEntry->timestampL); + } else { /* sort */ + timestamp = logEntry->timestampH; + timestamp = timestamp << 32; + timestamp += logEntry->timestampL; + tt = timestamp / 1000; + millisec = timestamp % 1000; + t = gmtime(&tt); + snprintf(string, sizeof(string), "%4d%02d%02d--%02d:%02d:%02d.%03d UTC", + 1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday, t->tm_hour, + t->tm_min, t->tm_sec, millisec); + } + + if (fdi) + fprintf(fdi, "%-32s %-7x %-6x %-6x %-8x %4x%08x %-8x %-d\n", + string, logEntry->opcode, logEntry->sqe, + logEntry->cid, logEntry->nsid, + (__u32)(logEntry->sLBA >> 32), + (__u32)logEntry->sLBA, logEntry->numLBA, + logEntry->latency); + if (print) + printf("%-32s %-7x %-6x %-6x %-8x %4x%08x %-8x %-d\n", + string, logEntry->opcode, logEntry->sqe, logEntry->cid, + logEntry->nsid, (__u32)(logEntry->sLBA >> 32), (__u32)logEntry->sLBA, + logEntry->numLBA, logEntry->latency); + } + return 1; +} + +static int mb_high_latency_log_print(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Get Memblaze high latency log"; + char buf[LOG_PAGE_SIZE]; + struct nvme_dev *dev; + FILE *fdi = NULL; + int err; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + fdi = fopen(FID_C3_LOG_FILENAME, "w+"); + + glp_high_latency_show_bar(fdi, DO_PRINT_FLAG); + err = nvme_get_log_simple(dev_fd(dev), GLP_ID_VU_GET_HIGH_LATENCY_LOG, + sizeof(buf), &buf); + + while (1) { + if (!glp_high_latency(fdi, buf, LOG_PAGE_SIZE, DO_PRINT_FLAG)) + break; + err = nvme_get_log_simple(dev_fd(dev), GLP_ID_VU_GET_HIGH_LATENCY_LOG, + sizeof(buf), &buf); + if (err) { + nvme_show_status(err); + break; + } + } + + if (fdi) + fclose(fdi); + dev_close(dev); + return err; +} + +static int memblaze_fw_commit(int fd, int select) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_fw_commit, + .cdw10 = 8, + .cdw12 = select, + }; + + return nvme_submit_admin_passthru(fd, &cmd, NULL); +} + +static int mb_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\n" + "select which firmware binary to update for 9200 devices. This requires a power cycle once the\n" + "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 selectNo, fw_fd, fw_size, err, offset = 0; + struct nvme_dev *dev; + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + 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)) { + selectNo = 18; + } else if (!strncmp(cfg.select, "EEP", 3)) { + selectNo = 10; + } else if (!strncmp(cfg.select, "ALL", 3)) { + 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; + goto out_close; + } + + 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_close; + } + + if (posix_memalign(&fw_buf, getpagesize(), fw_size)) { + fprintf(stderr, "No memory for f/w size:%d\n", fw_size); + err = ENOMEM; + goto out_close; + } + + if (read(fw_fd, fw_buf, fw_size) != ((ssize_t)(fw_size))) { + err = errno; + goto out_free; + } + + while (fw_size > 0) { + xfer = min(xfer, fw_size); + + struct nvme_fw_download_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .offset = offset, + .data_len = xfer, + .data = fw_buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + err = nvme_fw_download(&args); + if (err < 0) { + perror("fw-download"); + goto out_free; + } else if (err != 0) { + nvme_show_status(err); + goto out_free; + } + fw_buf += xfer; + fw_size -= xfer; + offset += xfer; + } + + err = memblaze_fw_commit(dev_fd(dev), selectNo); + + if (err == 0x10B || err == 0x20B) { + err = 0; + fprintf(stderr, "Update successful! Please power cycle for changes to take effect\n"); + } + +out_free: + free(fw_buf); +out_close: + close(fw_fd); +out: + dev_close(dev); + return err; +} + +static void ioLatencyHistogramOutput(FILE *fd, int index, int start, int end, char *unit0, + char *unit1, unsigned int *pHistogram, int print) +{ + int len; + char string[64], subString0[12], subString1[12]; + + snprintf(subString0, sizeof(subString0), "%d%s", start, unit0); + if (end != 0x7FFFFFFF) + snprintf(subString1, sizeof(subString1), "%d%s", end, unit1); + else + snprintf(subString1, sizeof(subString1), "%s", "+INF"); + len = snprintf(string, sizeof(string), "%-11d %-11s %-11s %-11u\n", + index, subString0, subString1, + pHistogram[index]); + fwrite(string, 1, len, fd); + if (print) + printf("%s", string); +} + +int io_latency_histogram(char *file, char *buf, int print, int logid) +{ + FILE *fdi = fopen(file, "w+"); + int i, index; + char unit[2][3]; + unsigned int *revision = (unsigned int *)buf; + + if (logid == GLP_ID_VU_GET_READ_LATENCY_HISTOGRAM) + fPRINT_PARAM1("Memblaze IO Read Command Latency Histogram\n"); + else if (logid == GLP_ID_VU_GET_WRITE_LATENCY_HISTOGRAM) + fPRINT_PARAM1("Memblaze IO Write Command Latency Histogram\n"); + fPRINT_PARAM2("Major Revision : %d\n", revision[1]); + fPRINT_PARAM2("Minor Revision : %d\n", revision[0]); + buf += 8; + + if (revision[1] == 1 && revision[0] == 0) { + fPRINT_PARAM1("--------------------------------------------------\n"); + fPRINT_PARAM1("Bucket Start End Value\n"); + fPRINT_PARAM1("--------------------------------------------------\n"); + index = 0; + strcpy(unit[0], "us"); + strcpy(unit[1], "us"); + for (i = 0; i < 32; i++, index++) { + if (i == 31) { + strcpy(unit[1], "ms"); + ioLatencyHistogramOutput(fdi, index, i * 32, 1, unit[0], unit[1], + (unsigned int *)buf, print); + } else { + ioLatencyHistogramOutput(fdi, index, i * 32, (i + 1) * 32, unit[0], + unit[1], (unsigned int *)buf, print); + } + } + + strcpy(unit[0], "ms"); + strcpy(unit[1], "ms"); + for (i = 1; i < 32; i++, index++) + ioLatencyHistogramOutput(fdi, index, i, i + 1, unit[0], unit[1], (unsigned int *)buf, print); + + for (i = 1; i < 32; i++, index++) { + if (i == 31) { + strcpy(unit[1], "s"); + ioLatencyHistogramOutput(fdi, index, i * 32, 1, unit[0], unit[1], + (unsigned int *)buf, print); + } else { + ioLatencyHistogramOutput(fdi, index, i * 32, (i + 1) * 32, unit[0], + unit[1], (unsigned int *)buf, print); + } + } + + strcpy(unit[0], "s"); + strcpy(unit[1], "s"); + for (i = 1; i < 4; i++, index++) + ioLatencyHistogramOutput(fdi, index, i, i + 1, unit[0], unit[1], (unsigned int *)buf, print); + + ioLatencyHistogramOutput(fdi, index, i, 0x7FFFFFFF, unit[0], unit[1], (unsigned int *)buf, print); + } else { + fPRINT_PARAM1("Unsupported io latency histogram revision\n"); + } + + if (fdi) + fclose(fdi); + return 1; +} + +static int mb_lat_stats_log_print(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + char stats[LOG_PAGE_SIZE]; + char f1[] = FID_C1_LOG_FILENAME; + char f2[] = FID_C2_LOG_FILENAME; + struct nvme_dev *dev; + int err; + + const char *desc = "Get Latency Statistics log and show it."; + const char *write = "Get write statistics (read default)"; + + struct config { + bool write; + }; + struct config cfg = { + .write = 0, + }; + + OPT_ARGS(opts) = { + OPT_FLAG("write", 'w', &cfg.write, write), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = nvme_get_log_simple(dev_fd(dev), cfg.write ? 0xc2 : 0xc1, + sizeof(stats), &stats); + if (!err) + io_latency_histogram(cfg.write ? f2 : f1, stats, DO_PRINT_FLAG, + cfg.write ? GLP_ID_VU_GET_WRITE_LATENCY_HISTOGRAM : + GLP_ID_VU_GET_READ_LATENCY_HISTOGRAM); + else + nvme_show_status(err); + + dev_close(dev); + return err; +} + +static int memblaze_clear_error_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + char *desc = "Clear Memblaze devices error log."; + struct nvme_dev *dev; + int err; + + __u32 result; + + struct config { + __u32 feature_id; + __u32 value; + int save; + }; + + struct config cfg = { + .feature_id = 0xf7, + .value = 0x534d0001, + .save = 0, + }; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = cfg.feature_id, + .nsid = 0, + .cdw11 = cfg.value, + .cdw12 = 0, + .save = cfg.save, + .uuidx = 0, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_set_features(&args); + if (err < 0) + perror("set-feature"); + if (!err) + printf("set-feature:%02x (%s), value:%#08x\n", cfg.feature_id, mb_feature_to_string(cfg.feature_id), cfg.value); + else if (err > 0) + nvme_show_status(err); + + dev_close(dev); + return err; +} + +static int mb_set_lat_stats(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + const char *desc = ( + "Enable/Disable 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; + struct nvme_dev *dev; + void *buf = NULL; + __u32 result; + int err; + + struct config { + bool enable, disable; + }; + + struct config cfg = { + .enable = false, + .disable = false, + }; + + struct argconfig_commandline_options command_line_options[] = { + {"enable", 'e', "", CFG_FLAG, &cfg.enable, no_argument, enable_desc}, + {"disable", 'd', "", CFG_FLAG, &cfg.disable, no_argument, disable_desc}, + {NULL} + }; + + err = parse_and_open(&dev, 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; + + struct nvme_get_features_args args_get = { + .args_size = sizeof(args_get), + .fd = dev_fd(dev), + .fid = fid, + .nsid = nsid, + .sel = sel, + .cdw11 = cdw11, + .uuidx = 0, + .data_len = data_len, + .data = buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + struct nvme_set_features_args args_set = { + .args_size = sizeof(args_set), + .fd = dev_fd(dev), + .fid = fid, + .nsid = nsid, + .cdw11 = option, + .cdw12 = cdw12, + .save = save, + .uuidx = 0, + .cdw15 = 0, + .data_len = data_len, + .data = buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + if (err) + return err; + switch (option) { + case None: + err = nvme_get_features(&args_get); + if (!err) { + printf( + "Latency Statistics Tracking (FID 0x%X) is currently (%i).\n", + fid, result); + } else { + printf("Could not read feature id 0xE2.\n"); + dev_close(dev); + return err; + } + break; + case True: + case False: + err = nvme_set_features(&args_set); + if (err > 0) { + nvme_show_status(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", + 0xe2, option); + } + break; + default: + printf("%d not supported.\n", option); + err = EINVAL; + } + dev_close(dev); + return err; +} + +// Global definitions + +static inline int K2C(int k) // KELVINS_2_CELSIUS +{ + return (k - 273); +}; + +// Global ID definitions + +enum { + // feature ids + FID_LATENCY_FEATURE = 0xd0, + + // log ids + LID_SMART_LOG_ADD = 0xca, + LID_LATENCY_STATISTICS = 0xd0, + LID_HIGH_LATENCY_LOG = 0xd1, + LID_PERFORMANCE_STATISTICS = 0xd2, +}; + +// smart-log-add + +struct smart_log_add_item { + uint32_t index; + char *attr; +}; + +struct __packed wear_level { + __le16 min; + __le16 max; + __le16 avg; +}; + +struct __packed smart_log_add_item_12 { + uint8_t id; + uint8_t rsvd[2]; + uint8_t norm; + uint8_t rsvd1; + union { + struct wear_level wear_level; // 0xad + struct temp_since_born { // 0xe7 + __le16 max; + __le16 min; + __le16 curr; + } temp_since_born; + struct power_consumption { // 0xe8 + __le16 max; + __le16 min; + __le16 curr; + } power_consumption; + struct temp_since_power_on { // 0xaf + __le16 max; + __le16 min; + __le16 curr; + } temp_since_power_on; + uint8_t raw[6]; + }; + uint8_t rsvd2; +}; + +struct __packed smart_log_add_item_10 { + uint8_t id; + uint8_t norm; + union { + struct wear_level wear_level; // 0xad + uint8_t raw[6]; + }; + uint8_t rsvd[2]; +}; + +struct smart_log_add { + union { + union { + struct smart_log_add_v0 { + struct smart_log_add_item_12 program_fail_count; + struct smart_log_add_item_12 erase_fail_count; + struct smart_log_add_item_12 wear_leveling_count; + struct smart_log_add_item_12 end_to_end_error_count; + struct smart_log_add_item_12 crc_error_count; + struct smart_log_add_item_12 timed_workload_media_wear; + struct smart_log_add_item_12 timed_workload_host_reads; + struct smart_log_add_item_12 timed_workload_timer; + struct smart_log_add_item_12 thermal_throttle_status; + struct smart_log_add_item_12 retry_buffer_overflow_counter; + struct smart_log_add_item_12 pll_lock_loss_count; + struct smart_log_add_item_12 nand_bytes_written; + struct smart_log_add_item_12 host_bytes_written; + struct smart_log_add_item_12 system_area_life_remaining; + struct smart_log_add_item_12 nand_bytes_read; + struct smart_log_add_item_12 temperature; + struct smart_log_add_item_12 power_consumption; + struct smart_log_add_item_12 power_on_temperature; + struct smart_log_add_item_12 power_loss_protection; + struct smart_log_add_item_12 read_fail_count; + struct smart_log_add_item_12 thermal_throttle_time; + struct smart_log_add_item_12 flash_error_media_count; + } v0; + + struct smart_log_add_item_12 v0_raw[22]; + }; + + union { + struct smart_log_add_v2 { + struct smart_log_add_item_12 program_fail_count; + struct smart_log_add_item_12 erase_fail_count; + struct smart_log_add_item_12 wear_leveling_count; + struct smart_log_add_item_12 end_to_end_error_count; + struct smart_log_add_item_12 crc_error_count; + struct smart_log_add_item_12 timed_workload_media_wear; + struct smart_log_add_item_12 timed_workload_host_reads; + struct smart_log_add_item_12 timed_workload_timer; + struct smart_log_add_item_12 thermal_throttle_status; + struct smart_log_add_item_12 lifetime_write_amplification; + struct smart_log_add_item_12 pll_lock_loss_count; + struct smart_log_add_item_12 nand_bytes_written; + struct smart_log_add_item_12 host_bytes_written; + struct smart_log_add_item_12 system_area_life_remaining; + struct smart_log_add_item_12 firmware_update_count; + struct smart_log_add_item_12 dram_cecc_count; + struct smart_log_add_item_12 dram_uecc_count; + struct smart_log_add_item_12 xor_pass_count; + struct smart_log_add_item_12 xor_fail_count; + struct smart_log_add_item_12 xor_invoked_count; + struct smart_log_add_item_12 inflight_read_io_cmd; + struct smart_log_add_item_12 flash_error_media_count; + struct smart_log_add_item_12 nand_bytes_read; + struct smart_log_add_item_12 temp_since_born; + struct smart_log_add_item_12 power_consumption; + struct smart_log_add_item_12 temp_since_bootup; + struct smart_log_add_item_12 thermal_throttle_time; + } v2; + + struct smart_log_add_item_12 v2_raw[27]; + }; + + union { + struct smart_log_add_v3 { + struct smart_log_add_item_10 program_fail_count; + struct smart_log_add_item_10 erase_fail_count; + struct smart_log_add_item_10 wear_leveling_count; + struct smart_log_add_item_10 ext_e2e_err_count; + struct smart_log_add_item_10 crc_err_count; + struct smart_log_add_item_10 nand_bytes_written; + struct smart_log_add_item_10 host_bytes_written; + struct smart_log_add_item_10 reallocated_sector_count; + struct smart_log_add_item_10 uncorrectable_sector_count; + struct smart_log_add_item_10 nand_uecc_detection; + struct smart_log_add_item_10 nand_xor_correction; + struct smart_log_add_item_10 gc_count; + struct smart_log_add_item_10 dram_uecc_detection_count; + struct smart_log_add_item_10 sram_uecc_detection_count; + struct smart_log_add_item_10 internal_raid_recovery_fail_count; + struct smart_log_add_item_10 inflight_cmds; + struct smart_log_add_item_10 internal_e2e_err_count; + struct smart_log_add_item_10 die_fail_count; + struct smart_log_add_item_10 wear_leveling_execution_count; + struct smart_log_add_item_10 read_disturb_count; + struct smart_log_add_item_10 data_retention_count; + struct smart_log_add_item_10 capacitor_health; + } v3; + + struct smart_log_add_item_10 v3_raw[24]; + }; + + uint8_t raw[512]; + }; +}; + +static void smart_log_add_v0_print(struct smart_log_add_item_12 *item, int item_count) +{ + static const struct smart_log_add_item items[0xff] = { + [0xab] = {0, "program_fail_count" }, + [0xac] = {1, "erase_fail_count" }, + [0xad] = {2, "wear_leveling_count" }, + [0xb8] = {3, "end_to_end_error_count" }, + [0xc7] = {4, "crc_error_count" }, + [0xe2] = {5, "timed_workload_media_wear" }, + [0xe3] = {6, "timed_workload_host_reads" }, + [0xe4] = {7, "timed_workload_timer" }, + [0xea] = {8, "thermal_throttle_status" }, + [0xf0] = {9, "retry_buffer_overflow_counter"}, + [0xf3] = {10, "pll_lock_loss_count" }, + [0xf4] = {11, "nand_bytes_written" }, + [0xf5] = {12, "host_bytes_written" }, + [0xf6] = {13, "system_area_life_remaining" }, + [0xfa] = {14, "nand_bytes_read" }, + [0xe7] = {15, "temperature" }, + [0xe8] = {16, "power_consumption" }, + [0xaf] = {17, "power_on_temperature" }, + [0xec] = {18, "power_loss_protection" }, + [0xf2] = {19, "read_fail_count" }, + [0xeb] = {20, "thermal_throttle_time" }, + [0xed] = {21, "flash_error_media_count" }, + }; + + for (int i = 0; i < item_count; i++, item++) { + if (item->id == 0) + continue; + + printf("%#-12" PRIx8 "%-36s%-12d", item->id, items[item->id].attr, item->norm); + switch (item->id) { + case 0xad: + printf("min: %d, max: %d, avg: %d\n", + le16_to_cpu(item->wear_level.min), + le16_to_cpu(item->wear_level.max), + le16_to_cpu(item->wear_level.avg)); + break; + case 0xe7: + printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n", + K2C(le16_to_cpu(item->temp_since_born.max)), + le16_to_cpu(item->temp_since_born.max), + K2C(le16_to_cpu(item->temp_since_born.min)), + le16_to_cpu(item->temp_since_born.min), + K2C(le16_to_cpu(item->temp_since_born.curr)), + le16_to_cpu(item->temp_since_born.curr)); + break; + case 0xe8: + printf("max: %d, min: %d, curr: %d\n", + le16_to_cpu(item->power_consumption.max), + le16_to_cpu(item->power_consumption.min), + le16_to_cpu(item->power_consumption.curr)); + break; + case 0xaf: + printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n", + K2C(le16_to_cpu(item->temp_since_power_on.max)), + le16_to_cpu(item->temp_since_power_on.max), + K2C(le16_to_cpu(item->temp_since_power_on.min)), + le16_to_cpu(item->temp_since_power_on.min), + K2C(le16_to_cpu(item->temp_since_power_on.curr)), + le16_to_cpu(item->temp_since_power_on.curr)); + break; + default: + printf("%" PRIu64 "\n", int48_to_long(item->raw)); + break; + } + } +} + +static void smart_log_add_v2_print(struct smart_log_add_item_12 *item, int item_count) +{ + static const struct smart_log_add_item items[0xff] = { + [0xab] = {0, "program_fail_count" }, + [0xac] = {1, "erase_fail_count" }, + [0xad] = {2, "wear_leveling_count" }, + [0xb8] = {3, "end_to_end_error_count" }, + [0xc7] = {4, "crc_error_count" }, + [0xe2] = {5, "timed_workload_media_wear" }, + [0xe3] = {6, "timed_workload_host_reads" }, + [0xe4] = {7, "timed_workload_timer" }, + [0xea] = {8, "thermal_throttle_status" }, + [0xf0] = {9, "lifetime_write_amplification"}, + [0xf3] = {10, "pll_lock_loss_count" }, + [0xf4] = {11, "nand_bytes_written" }, + [0xf5] = {12, "host_bytes_written" }, + [0xf6] = {13, "system_area_life_remaining" }, + [0xf9] = {14, "firmware_update_count" }, + [0xfa] = {15, "dram_cecc_count" }, + [0xfb] = {16, "dram_uecc_count" }, + [0xfc] = {17, "xor_pass_count" }, + [0xfd] = {18, "xor_fail_count" }, + [0xfe] = {19, "xor_invoked_count" }, + [0xe5] = {20, "inflight_read_io_cmd" }, + [0xe6] = {21, "flash_error_media_count" }, + [0xf8] = {22, "nand_bytes_read" }, + [0xe7] = {23, "temp_since_born" }, + [0xe8] = {24, "power_consumption" }, + [0xaf] = {25, "temp_since_bootup" }, + [0xeb] = {26, "thermal_throttle_time" }, + }; + + for (int i = 0; i < item_count; i++, item++) { + if (item->id == 0) + continue; + + printf("%#-12" PRIx8 "%-36s%-12d", item->id, items[item->id].attr, item->norm); + switch (item->id) { + case 0xad: + printf("min: %d, max: %d, avg: %d\n", + le16_to_cpu(item->wear_level.min), + le16_to_cpu(item->wear_level.max), + le16_to_cpu(item->wear_level.avg)); + break; + case 0xe7: + printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n", + K2C(le16_to_cpu(item->temp_since_born.max)), + le16_to_cpu(item->temp_since_born.max), + K2C(le16_to_cpu(item->temp_since_born.min)), + le16_to_cpu(item->temp_since_born.min), + K2C(le16_to_cpu(item->temp_since_born.curr)), + le16_to_cpu(item->temp_since_born.curr)); + break; + case 0xe8: + printf("max: %d, min: %d, curr: %d\n", + le16_to_cpu(item->power_consumption.max), + le16_to_cpu(item->power_consumption.min), + le16_to_cpu(item->power_consumption.curr)); + break; + case 0xaf: + printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n", + K2C(le16_to_cpu(item->temp_since_power_on.max)), + le16_to_cpu(item->temp_since_power_on.max), + K2C(le16_to_cpu(item->temp_since_power_on.min)), + le16_to_cpu(item->temp_since_power_on.min), + K2C(le16_to_cpu(item->temp_since_power_on.curr)), + le16_to_cpu(item->temp_since_power_on.curr)); + break; + default: + printf("%" PRIu64 "\n", int48_to_long(item->raw)); + break; + } + } +} + +static void smart_log_add_v3_print(struct smart_log_add_item_10 *item, int item_count) +{ + static const struct smart_log_add_item items[0xff] = { + [0xab] = {0, "program_fail_count" }, + [0xac] = {1, "erase_fail_count" }, + [0xad] = {2, "wear_leveling_count" }, + [0xb8] = {3, "ext_e2e_err_count" }, + [0xc7] = {4, "crc_err_count" }, + [0xf4] = {5, "nand_bytes_written" }, + [0xf5] = {6, "host_bytes_written" }, + [0xd0] = {7, "reallocated_sector_count" }, + [0xd1] = {8, "uncorrectable_sector_count" }, + [0xd2] = {9, "nand_uecc_detection" }, + [0xd3] = {10, "nand_xor_correction" }, + [0xd4] = {12, "gc_count" }, // 11 is reserved + [0xd5] = {13, "dram_uecc_detection_count" }, + [0xd6] = {14, "sram_uecc_detection_count" }, + [0xd7] = {15, "internal_raid_recovery_fail_count"}, + [0xd8] = {16, "inflight_cmds" }, + [0xd9] = {17, "internal_e2e_err_count" }, + [0xda] = {19, "die_fail_count" }, // 18 is reserved + [0xdb] = {20, "wear_leveling_execution_count" }, + [0xdc] = {21, "read_disturb_count" }, + [0xdd] = {22, "data_retention_count" }, + [0xde] = {23, "capacitor_health" }, + }; + + for (int i = 0; i < item_count; i++, item++) { + if (item->id == 0) + continue; + + printf("%#-12" PRIx8 "%-36s%-12d", item->id, items[item->id].attr, item->norm); + switch (item->id) { + case 0xad: + printf("min: %d, max: %d, avg: %d\n", + le16_to_cpu(item->wear_level.min), + le16_to_cpu(item->wear_level.max), + le16_to_cpu(item->wear_level.avg)); + break; + default: + printf("%" PRIu64 "\n", int48_to_long(item->raw)); + break; + } + } +} + +static void smart_log_add_print(struct smart_log_add *log, const char *devname) +{ + uint8_t version = log->raw[511]; + + printf("Version: %u\n", version); + printf("\n"); + printf("Additional Smart Log for NVMe device: %s\n", devname); + printf("\n"); + + printf("%-12s%-36s%-12s%s\n", "Id", "Key", "Normalized", "Raw"); + + switch (version) { + case 0: + return smart_log_add_v0_print(&log->v0_raw[0], + sizeof(struct smart_log_add_v0) / sizeof(struct smart_log_add_item_12)); + case 2: + return smart_log_add_v2_print(&log->v2_raw[0], + sizeof(struct smart_log_add_v2) / sizeof(struct smart_log_add_item_12)); + case 3: + return smart_log_add_v3_print(&log->v3_raw[0], + sizeof(struct smart_log_add_v3) / sizeof(struct smart_log_add_item_10)); + + case 1: + fprintf(stderr, "Version %d: N/A\n", version); + break; + default: + fprintf(stderr, "Version %d: Not supported yet\n", version); + break; + } +} + +static int mb_get_smart_log_add(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int err = 0; + + // Get the configuration + + struct config { + bool raw_binary; + }; + + struct config cfg = {0}; + + OPT_ARGS(opts) = { + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, "dump the whole log buffer in binary format"), + OPT_END()}; + + // Open device + + struct nvme_dev *dev = NULL; + + err = parse_and_open(&dev, argc, argv, cmd->help, opts); + if (err) + return err; + + // Get log + + struct smart_log_add log = {0}; + + err = nvme_get_log_simple(dev_fd(dev), LID_SMART_LOG_ADD, sizeof(struct smart_log_add), &log); + if (!err) { + if (!cfg.raw_binary) + smart_log_add_print(&log, dev->name); + else + d_raw((unsigned char *)&log, sizeof(struct smart_log_add)); + } else if (err > 0) { + nvme_show_status(err); + } else { + nvme_show_error("%s: %s", cmd->name, nvme_strerror(errno)); + } + + // Close device + + dev_close(dev); + return err; +} + +// performance-monitor + +struct latency_stats_bucket { + char *start_threshold; + char *end_threshold; +}; + +struct __packed latency_stats { + union { + struct latency_stats_v2_0 { + uint32_t minor_version; + uint32_t major_version; + uint32_t bucket_read_data[32]; + uint32_t rsvd[32]; + uint32_t bucket_write_data[32]; + uint32_t rsvd1[32]; + uint32_t bucket_trim_data[32]; + uint32_t rsvd2[32]; + uint8_t rsvd3[248]; + } v2_0; + uint8_t raw[1024]; + }; +}; + +struct __packed high_latency_log { + union { + struct high_latency_log_v1 { + uint32_t version; + struct high_latency_log_entry { + uint64_t timestamp; // ms + uint32_t latency; + uint32_t qid; + uint32_t opcode : 8; + uint32_t fuse : 2; + uint32_t psdt : 2; + uint32_t cid : 16; + uint32_t rsvd : 4; + uint32_t nsid; + uint64_t slba; + uint32_t nlb : 16; + uint32_t dtype : 8; + uint32_t pinfo : 4; + uint32_t fua : 1; + uint32_t lr : 1; + uint32_t rsvd1 : 2; + uint8_t rsvd2[28]; + } entries[1024]; + } v1; + uint8_t raw[4 + 1024 * 64]; + }; +}; + +struct __packed performance_stats { + union { + struct performance_stats_v1 { + uint8_t version; + uint8_t rsvd[3]; + struct performance_stats_timestamp { + uint8_t timestamp[6]; + struct performance_stats_entry { + uint16_t read_iops; // K IOPS + uint16_t read_bandwidth; // MiB + uint32_t read_latency; // us + uint32_t read_latency_max; // us + uint16_t write_iops; // K IOPS + uint16_t write_bandwidth; // MiB + uint32_t write_latency; // us + uint32_t write_latency_max; // us + } entries[3600]; + } timestamps[24]; + } v1; + uint8_t raw[4 + 24 * (6 + 3600 * 24)]; + }; +}; + +static int mb_set_latency_feature(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err = 0; + + // Get the configuration + + struct config { + uint32_t perf_monitor; + uint32_t cmd_mask; + uint32_t read_threshold; + uint32_t write_threshold; + uint32_t de_allocate_trim_threshold; + }; + + struct config cfg = {0}; + + OPT_ARGS(opts) = { + OPT_UINT("sel-perf-log", 's', &cfg.perf_monitor, + "Select features to turn on, default: Disable\n" + " bit 0: latency statistics\n" + " bit 1: high latency log\n" + " bit 2: Performance stat"), + OPT_UINT("set-commands-mask", 'm', &cfg.cmd_mask, + "Set Enable, default: Disable\n" + " bit 0: Read commands\n" + " bit 1: high Write commands\n" + " bit 2: De-allocate/TRIM (this bit is not worked for Performance stat.)"), + OPT_UINT("set-read-threshold", 'r', &cfg.read_threshold, + "set read high latency log threshold, it's a 0-based value and unit is 10ms"), + OPT_UINT("set-write-threshold", 'w', &cfg.write_threshold, + "set write high latency log threshold, it's a 0-based value and unit is 10ms"), + OPT_UINT("set-trim-threshold", 't', &cfg.de_allocate_trim_threshold, + "set trim high latency log threshold, it's a 0-based value and unit is 10ms"), + OPT_END()}; + + // Open device + + struct nvme_dev *dev = NULL; + + err = parse_and_open(&dev, argc, argv, cmd->help, opts); + if (err) + return err; + + + // Set feature + + uint32_t result = 0; + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = FID_LATENCY_FEATURE, + .nsid = 0, + .cdw11 = 0 | cfg.perf_monitor, + .cdw12 = 0 | cfg.cmd_mask, + .cdw13 = 0 | + (cfg.read_threshold & 0xff) | + ((cfg.write_threshold & 0xff) << 8) | + ((cfg.de_allocate_trim_threshold & 0xff) << 16), + .cdw15 = 0, + .save = 0, + .uuidx = 0, + .data = NULL, + .data_len = 0, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + err = nvme_set_features(&args); + if (!err) + printf("%s have done successfully. result = %#" PRIx32 ".\n", cmd->name, result); + else if (err > 0) + nvme_show_status(err); + else + nvme_show_error("%s: %s", cmd->name, nvme_strerror(errno)); + + // Close device + + dev_close(dev); + return err; +} + +static int mb_get_latency_feature(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err = 0; + + // Get the configuration + + OPT_ARGS(opts) = { + OPT_END()}; + + // Open device + + struct nvme_dev *dev = NULL; + + err = parse_and_open(&dev, argc, argv, cmd->help, opts); + if (err) + return err; + + // Get feature + + uint32_t result = 0; + + err = nvme_get_features_simple(dev_fd(dev), FID_LATENCY_FEATURE, 0, &result); + if (!err) { + printf("%s have done successfully. result = %#" PRIx32 ".\n", cmd->name, result); + + printf("latency statistics enable status = %d\n", (result & (0x01 << 0)) >> 0); + printf("high latency enable status = %d\n", (result & (0x01 << 1)) >> 1); + printf("performance stat enable status = %d\n", (result & (0x01 << 2)) >> 2); + + printf("Monitor Read command = %d\n", (result & (0x01 << 4)) >> 4); + printf("Monitor Write command = %d\n", (result & (0x01 << 5)) >> 5); + printf("Monitor Trim command = %d\n", (result & (0x01 << 6)) >> 6); + + printf("Threshold for Read = %dms\n", (((result & (0xff << 8)) >> 8) + 1) * 10); + printf("Threshold for Write = %dms\n", (((result & (0xff << 16)) >> 16) + 1) * 10); + printf("Threshold for Trim = %dms\n", (((result & (0xff << 24)) >> 24) + 1) * 10); + } else if (err > 0) { + nvme_show_status(err); + } else { + nvme_show_error("%s: %s", cmd->name, nvme_strerror(errno)); + } + + // Close device + + dev_close(dev); + return err; +} diff --git a/plugins/memblaze/memblaze-nvme.h b/plugins/memblaze/memblaze-nvme.h new file mode 100644 index 0000000..e25267b --- /dev/null +++ b/plugins/memblaze/memblaze-nvme.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#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" + +PLUGIN(NAME("memblaze", "Memblaze vendor specific extensions", NVME_VERSION), + COMMAND_LIST( + ENTRY("smart-log-add", "Retrieve Memblaze SMART Log, show it", mb_get_additional_smart_log) + ENTRY("get-pm-status", "Get Memblaze Power Manager Status", mb_get_powermanager_status) + ENTRY("set-pm-status", "Set Memblaze Power Manager Status", mb_set_powermanager_status) + ENTRY("select-download", "Selective Firmware Download", mb_selective_download) + ENTRY("lat-stats", "Enable and disable Latency Statistics logging", mb_set_lat_stats) + ENTRY("lat-stats-print", "Retrieve IO Latency Statistics log, show it", mb_lat_stats_log_print) + ENTRY("lat-log", "Set Memblaze High Latency Log", mb_set_high_latency_log) + ENTRY("lat-log-print", "Output Memblaze High Latency Log", mb_high_latency_log_print) + ENTRY("clear-error-log", "Clear error log", memblaze_clear_error_log) + ENTRY("smart-log-add-x", "Retrieve Memblaze SMART Log, show it", mb_get_smart_log_add) + ENTRY("lat-set-feature-x", "Set Enable/Disable for Latency Monitor feature", mb_set_latency_feature) + ENTRY("lat-get-feature-x", "Get Enabled/Disabled of Latency Monitor feature", mb_get_latency_feature) + ) +); + +#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..8914f95 --- /dev/null +++ b/plugins/memblaze/memblaze-utils.h @@ -0,0 +1,217 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#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 + +// 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 Additional 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 memery are dirty. + */ + __u8 resv[SMART_INFO_NEW_SIZE - sizeof(struct nvme_p4_smart_log_item) * NR_SMART_ITEMS]; +}; + +// base +#define DD do{ printf("=Memblaze= %s[%d]-%s():\n", __FILE__, __LINE__, __func__); }while(0) +#define DE(str) do{ printf("===ERROR!=== %s[%d]-%s():str=%s\n", __FILE__, __LINE__, __func__, \ + str); }while(0) +// integer +#define DI(i) do{ printf("=Memblaze= %s[%d]-%s():int=%d\n", __FILE__, __LINE__, __func__, \ + (int)i); } while(0) +#define DPI(prompt, i) do{ printf("=Memblaze= %s[%d]-%s():%s=%d\n", __FILE__, __LINE__, __func__, \ + prompt, i); }while(0) +#define DAI(prompt, i, arr, max) do{ printf("=Memblaze= %s[%d]-%s():%s", __FILE__, __LINE__, __func__, prompt); \ + for(i=0;i<max;i++) printf(" %d:%d", i, arr[i]); \ + printf("\n"); }while(0) +// char +#define DC(c) do{ printf("=Memblaze= %s[%d]-%s():char=%c\n", __FILE__, __LINE__, __func__, c); \ + }while(0) +#define DPC(prompt, c) do{ printf("=Memblaze= %s[%d]-%s():%s=%c\n", __FILE__, __LINE__, __func__, \ + prompt, c); }while(0) +// address +#define DA(add) do{ printf("=Memblaze= %s[%d]-%s():address=0x%08X\n", __FILE__, __LINE__, \ + __func__, add); }while(0) +#define DPA(prompt, add) do{ printf("=Memblaze= %s[%d]-%s():%s=0x%08X\n", __FILE__, __LINE__, __func__, \ + prompt, add); }while(0) +// string +#define DS(str) do{ printf("=Memblaze= %s[%d]-%s():str=%s\n", __FILE__, __LINE__, __func__, str); \ + }while(0) +#define DPS(prompt, str) do{ printf("=Memblaze= %s[%d]-%s():%s=%s\n", __FILE__, __LINE__, __func__, \ + prompt, str); }while(0) +#define DAS(prompt, i, arr, max) do{ printf("=Memblaze= %s[%d]-%s():%s", __FILE__, __LINE__, __func__, prompt); \ + for(i=0;i<max;i++) printf(" %d:%s", i, arr[i]); \ + printf("\n"); }while(0) +// array +#define DR(str, k) do{ int ip; for(ip=0;ip<k;ip++) if(NULL != argv[ip]) \ + printf("=Memblaze= %s[%d]-%s():%d=%s\n", \ + __FILE__, __LINE__, __func__, ip, str[ip]); }while(0) +#define DARG do{ DPI("argc", argc); \ + int ip; for(ip=0;ip<argc;ip++) if(NULL != argv[ip]) \ + printf("=Memblaze= %s[%d]-%s():%d=%s\n", \ + __FILE__, __LINE__, __func__, ip, argv[ip]); }while(0) + +#define fPRINT_PARAM1(format) \ + do { \ + if (fdi) \ + fprintf(fdi, format); \ + if (print) \ + printf(format); \ + } while (0) + +#define fPRINT_PARAM2(format, value) \ + do { \ + if (fdi) \ + fprintf(fdi, format, value); \ + if (print) \ + printf(format, value); \ + } while (0) + +#endif // __MEMBLAZE_UTILS_H__ diff --git a/plugins/meson.build b/plugins/meson.build new file mode 100644 index 0000000..bb4c9ad --- /dev/null +++ b/plugins/meson.build @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +if json_c_dep.found() + sources += [ + 'plugins/amzn/amzn-nvme.c', + 'plugins/dell/dell-nvme.c', + 'plugins/dera/dera-nvme.c', + 'plugins/fdp/fdp.c', + 'plugins/huawei/huawei-nvme.c', + 'plugins/innogrit/innogrit-nvme.c', + 'plugins/inspur/inspur-nvme.c', + 'plugins/intel/intel-nvme.c', + 'plugins/memblaze/memblaze-nvme.c', + 'plugins/micron/micron-nvme.c', + 'plugins/nbft/nbft-plugin.c', + 'plugins/netapp/netapp-nvme.c', + 'plugins/nvidia/nvidia-nvme.c', + 'plugins/scaleflux/sfx-nvme.c', + 'plugins/seagate/seagate-nvme.c', + 'plugins/shannon/shannon-nvme.c', + 'plugins/solidigm/solidigm-nvme.c', + 'plugins/toshiba/toshiba-nvme.c', + 'plugins/transcend/transcend-nvme.c', + 'plugins/virtium/virtium-nvme.c', + 'plugins/wdc/wdc-nvme.c', + 'plugins/wdc/wdc-utils.c', + 'plugins/ymtc/ymtc-nvme.c', + 'plugins/zns/zns.c', + ] + subdir('solidigm') + subdir('ocp') + if conf.has('HAVE_SED_OPAL') + subdir('sed') + endif +endif diff --git a/plugins/micron/micron-nvme.c b/plugins/micron/micron-nvme.c new file mode 100644 index 0000000..63a7a79 --- /dev/null +++ b/plugins/micron/micron-nvme.c @@ -0,0 +1,3390 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#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 "common.h" +#include "nvme.h" +#include "libnvme.h" +#include <limits.h> +#include "linux/types.h" +#include "nvme-print.h" +#include "util/cleanup.h" + +#define CREATE_CMD +#include "micron-nvme.h" + +/* Supported Vendor specific feature ids */ +#define MICRON_FEATURE_CLEAR_PCI_CORRECTABLE_ERRORS 0xC3 +#define MICRON_FEATURE_CLEAR_FW_ACTIVATION_HISTORY 0xC1 +#define MICRON_FEATURE_TELEMETRY_CONTROL_OPTION 0xCF +#define MICRON_FEATURE_SMBUS_OPTION 0xD5 + +/* Supported Vendor specific log page sizes */ +#define C5_log_size (((452 + 16 * 1024) / 4) * 4096) +#define C0_log_size 512 +#define C2_log_size 4096 +#define D0_log_size 512 +#define FB_log_size 512 +#define E1_log_size 256 +#define MaxLogChunk (16 * 1024) +#define CommonChunkSize (16 * 4096) + +#define min(x, y) ((x) > (y) ? (y) : (x)) +#define SensorCount 8 + +/* Plugin version major_number.minor_number.patch */ +static const char *__version_major = "1"; +static const char *__version_minor = "0"; +static const char *__version_patch = "14"; + +/* + * supported models of micron plugin; new models should be added at the end + * before UNKNOWN_MODEL. Make sure M5410 is first in the list ! + */ +enum eDriveModel { + M5410 = 0, + M51AX, + M51BX, + M51CX, + M5407, + M5411, + UNKNOWN_MODEL +}; + +#define MICRON_VENDOR_ID 0x1344 + +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; + +struct LogPageHeader_t { + unsigned char numDwordsInLogPageHeaderLo; + unsigned char logPageHeaderFormatVersion; + unsigned char logPageId; + unsigned char numDwordsInLogPageHeaderHi; + unsigned int numValidDwordsInPayload; + unsigned int numDwordsInEntireLogPage; +}; + +static void WriteData(__u8 *data, __u32 len, const char *dir, const char *file, const char *msg) +{ + char tempFolder[8192] = { 0 }; + FILE *fpOutFile = NULL; + + sprintf(tempFolder, "%s/%s", dir, file); + fpOutFile = fopen(tempFolder, "ab+"); + if (fpOutFile) { + if (fwrite(data, 1, len, fpOutFile) != len) + printf("Failed to write %s data to %s\n", msg, tempFolder); + fclose(fpOutFile); + } else { + printf("Failed to open %s file to write %s\n", tempFolder, msg); + } +} + +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) { + perror(file); + return fd; + } + + ret = read(fd, idstr, sizeof(idstr)); + close(fd); + if (ret < 0) + perror("read"); + else + *id = strtol(idstr, NULL, 16); + + return ret; +} + +static enum eDriveModel GetDriveModel(int idx) +{ + enum 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 == MICRON_VENDOR_ID) { + switch (device_id) { + case 0x5196: + fallthrough; + case 0x51A0: + fallthrough; + case 0x51A1: + fallthrough; + case 0x51A2: + eModel = M51AX; + break; + case 0x51B0: + fallthrough; + case 0x51B1: + fallthrough; + case 0x51B2: + eModel = M51BX; + break; + case 0x51C0: + fallthrough; + case 0x51C1: + fallthrough; + case 0x51C2: + fallthrough; + case 0x51C3: + eModel = M51CX; + break; + case 0x5405: + fallthrough; + case 0x5406: + fallthrough; + case 0x5407: + eModel = M5407; + break; + case 0x5410: + eModel = M5410; + break; + case 0x5411: + eModel = M5411; + break; + default: + break; + } + } + return eModel; +} + +static int ZipAndRemoveDir(char *strDirName, char *strFileName) +{ + int err = 0; + char strBuffer[PATH_MAX]; + int nRet; + bool is_tgz = false; + struct stat sb; + + if (strstr(strFileName, ".tar.gz") || strstr(strFileName, ".tgz")) { + sprintf(strBuffer, "tar -zcf \"%s\" \"%s\"", strFileName, strDirName); + is_tgz = true; + } else { + sprintf(strBuffer, "zip -r \"%s\" \"%s\" >temp.txt 2>&1", strFileName, + strDirName); + } + + err = EINVAL; + nRet = system(strBuffer); + + /* check if log file is created, if not print error message */ + if (nRet < 0 || (stat(strFileName, &sb) == -1)) { + if (is_tgz) + sprintf(strBuffer, "check if tar and gzip commands are installed"); + else + sprintf(strBuffer, "check if zip command is installed"); + + fprintf(stderr, "Failed to create log data package, %s!\n", strBuffer); + } + + sprintf(strBuffer, "rm -f -R \"%s\" >temp.txt 2>&1", strDirName); + nRet = system(strBuffer); + if (nRet < 0) + printf("Failed to remove temporary files!\n"); + + 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, '/')) { + fileName = strrchr(strFilePath, '\\'); + if (!fileName) + fileName = strrchr(strFilePath, '/'); + + if (fileName) { + 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); + if (!fileLocation) + goto exit_status; + strncpy(fileLocation, strFilePath, length); + fileLocation[length] = '\0'; + + while (fileLocation[k] != '\0') { + if (fileLocation[k] == '\\') + fileLocation[k] = '/'; + k++; + } + + length = (int)strlen(fileLocation); + + if (':' == fileLocation[length - 1]) { + strTemp = (char *)malloc(length + 2); + if (!strTemp) { + free(fileLocation); + goto exit_status; + } + strcpy(strTemp, fileLocation); + strcat(strTemp, "/"); + free(fileLocation); + + length = (int)strlen(strTemp); + fileLocation = (char *)malloc(length + 1); + if (!fileLocation) { + free(strTemp); + goto exit_status; + } + + memcpy(fileLocation, strTemp, length + 1); + free(strTemp); + } + + if (stat(fileLocation, &st)) { + 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)) { + strMainDirName[nIndex] = '\0'; + sprintf(strAppend, "-%d", j); + strcat(strMainDirName, strAppend); + j++; + } + + if (mkdir(strMainDirName, 0777) < 0) { + err = -1; + goto exit_status; + } + + if (strOSDirName) { + sprintf(strOSDirName, "%s/%s", strMainDirName, "OS"); + if (mkdir(strOSDirName, 0777) < 0) { + rmdir(strMainDirName); + err = -1; + goto exit_status; + } + } + if (strCtrlDirName) { + sprintf(strCtrlDirName, "%s/%s", strMainDirName, "Controller"); + if (mkdir(strCtrlDirName, 0777) < 0) { + if (strOSDirName) + rmdir(strOSDirName); + rmdir(strMainDirName); + err = -1; + } + } + +exit_status: + return err; +} + +static int GetLogPageSize(int nFD, unsigned char ucLogID, int *nLogSize) +{ + int err = 0; + unsigned char pTmpBuf[CommonChunkSize] = { 0 }; + struct LogPageHeader_t *pLogHeader = NULL; + + if (ucLogID == 0xC1 || ucLogID == 0xC2 || ucLogID == 0xC4) { + err = nvme_get_log_simple(nFD, ucLogID, CommonChunkSize, pTmpBuf); + if (!err) { + pLogHeader = (struct LogPageHeader_t *) pTmpBuf; + struct LogPageHeader_t *pLogHeader1 = (struct LogPageHeader_t *) pLogHeader; + *nLogSize = (int)(pLogHeader1->numDwordsInEntireLogPage) * 4; + if (!pLogHeader1->logPageHeaderFormatVersion) { + printf("Unsupported log page format version %d of log page : 0x%X\n", + ucLogID, err); + *nLogSize = 0; + err = -1; + } + } else { + printf("Getting size of log page : 0x%X failed with %d (ignored)!\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_passthru_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 && (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 && (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_admin_passthru(nFD, &cmd, NULL); + 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; + + pBuffer = (unsigned int *)calloc(1, nBufferSize); + if (!pBuffer) + return err; + + while (!err && llMaxSize > 0) { + err = NVMEGetLogPage(nFD, ucLogID, (unsigned char *)pBuffer, nBufferSize); + if (err) { + free(pBuffer); + 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) +{ + unsigned char *pTempPtr = NULL; + int err = 0; + + pTempPtr = (unsigned char *)malloc(nBuffSize); + if (!pTempPtr) + goto exit_status; + memset(pTempPtr, 0, nBuffSize); + err = nvme_get_log_simple(nFD, ucLogID, nBuffSize, pTempPtr); + *pBuffer = pTempPtr; + +exit_status: + return err; +} + +/* + * Plugin Commands + */ +static int micron_parse_options(struct nvme_dev **dev, int argc, char **argv, + const char *desc, + struct argconfig_commandline_options *opts, + enum eDriveModel *modelp) +{ + int idx; + int err = parse_and_open(dev, argc, argv, desc, opts); + + if (err) { + perror("open"); + return -1; + } + + if (modelp) { + if (sscanf(argv[optind], "/dev/nvme%d", &idx) != 1) + idx = 0; + *modelp = GetDriveModel(idx); + } + + return 0; +} + +static int micron_fw_commit(int fd, int select) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_fw_commit, + .cdw10 = 8, + .cdw12 = select, + }; + return ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd); +} + +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\n" + "select which firmware binary to update for 9200 devices. This requires\n" + "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 selectNo, fw_fd, fw_size, err, offset = 0; + struct nvme_dev *dev; + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (strlen(cfg.select) != 3) { + fprintf(stderr, "Invalid select flag\n"); + dev_close(dev); + return -EINVAL; + } + + for (int i = 0; i < 3; i++) + cfg.select[i] = toupper(cfg.select[i]); + + if (!strncmp(cfg.select, "OOB", 3)) { + selectNo = 18; + } else if (!strncmp(cfg.select, "EEP", 3)) { + selectNo = 10; + } else if (!strncmp(cfg.select, "ALL", 3)) { + selectNo = 26; + } else { + fprintf(stderr, "Invalid select flag\n"); + dev_close(dev); + return -EINVAL; + } + + fw_fd = open(cfg.fw, O_RDONLY); + if (fw_fd < 0) { + fprintf(stderr, "no firmware file provided\n"); + dev_close(dev); + return -EINVAL; + } + + err = fstat(fw_fd, &sb); + if (err < 0) { + perror("fstat"); + err = errno; + goto out; + } + + 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))) { + err = errno; + goto out_free; + } + + while (fw_size > 0) { + xfer = min(xfer, fw_size); + + struct nvme_fw_download_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .offset = offset, + .data_len = xfer, + .data = fw_buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + + err = nvme_fw_download(&args); + if (err < 0) { + perror("fw-download"); + goto out_free; + } else if (err) { + nvme_show_status(err); + goto out_free; + } + fw_buf += xfer; + fw_size -= xfer; + offset += xfer; + } + + err = micron_fw_commit(dev_fd(dev), selectNo); + + if (err == 0x10B || err == 0x20B) { + err = 0; + fprintf(stderr, + "Update successful! Power cycle for changes to take effect\n"); + } + +out_free: + free(fw_buf); +out: + close(fw_fd); + dev_close(dev); + return err; +} + +static int micron_smbus_option(int argc, char **argv, + struct command *cmd, struct plugin *plugin) +{ + __u32 result = 0; + __u32 cdw11 = 0; + const char *desc = "Enable/Disable/Get status of SMBUS option on controller"; + const char *option = "enable or disable or status"; + const char *value = + "1 - hottest component temperature, 0 - composite temperature (default) for enable option, 0 (current), 1 (default), 2 (saved) for status options"; + const char *save = "1 - persistent, 0 - non-persistent (default)"; + int fid = MICRON_FEATURE_SMBUS_OPTION; + enum eDriveModel model = UNKNOWN_MODEL; + struct nvme_dev *dev; + int err = 0; + + struct { + char *option; + int value; + int save; + int status; + } opt = { + .option = "disable", + .value = 0, + .save = 0, + .status = 0, + }; + + OPT_ARGS(opts) = { + OPT_STRING("option", 'o', "option", &opt.option, option), + OPT_UINT("value", 'v', &opt.value, value), + OPT_UINT("save", 's', &opt.save, save), + OPT_END() + }; + + err = micron_parse_options(&dev, argc, argv, desc, opts, &model); + if (err < 0) + return err; + + if (model != M5407 && model != M5411) { + printf("This option is not supported for specified drive\n"); + dev_close(dev); + return err; + } + + if (!strcmp(opt.option, "enable")) { + cdw11 = opt.value << 1 | 1; + err = nvme_set_features_simple(dev_fd(dev), fid, 1, cdw11, opt.save, + &result); + if (!err) + printf("successfully enabled SMBus on drive\n"); + else + printf("Failed to enabled SMBus on drive\n"); + } else if (!strcmp(opt.option, "status")) { + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = fid, + .nsid = 1, + .sel = opt.value, + .cdw11 = 0, + .uuidx = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + err = nvme_get_features(&args); + if (!err) + printf("SMBus status on the drive: %s (returns %s temperature)\n", + (result & 1) ? "enabled" : "disabled", + (result & 2) ? "hottest component" : "composite"); + else + printf("Failed to retrieve SMBus status on the drive\n"); + } else if (!strcmp(opt.option, "disable")) { + cdw11 = opt.value << 1 | 0; + err = nvme_set_features_simple(dev_fd(dev), fid, 1, cdw11, opt.save, + &result); + if (!err) + printf("Successfully disabled SMBus on drive\n"); + else + printf("Failed to disable SMBus on drive\n"); + } else { + printf("Invalid option %s, valid values are enable, disable or status\n", + opt.option); + dev_close(dev); + return -1; + } + + close(dev_fd(dev)); + 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, err = 0; + unsigned int tempSensors[SensorCount] = { 0 }; + const char *desc = "Retrieve Micron temperature info for the given device "; + const char *fmt = "output format normal|json"; + struct format { + char *fmt; + }; + struct format cfg = { + .fmt = "normal", + }; + bool is_json = false; + struct json_object *root; + struct json_object *logPages; + struct nvme_dev *dev; + + OPT_ARGS(opts) = { + OPT_FMT("format", 'f', &cfg.fmt, fmt), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) { + printf("\nDevice not found\n"); + return -1; + } + + if (!strcmp(cfg.fmt, "json")) + is_json = true; + + err = nvme_get_log_smart(dev_fd(dev), 0xffffffff, false, &smart_log); + if (!err) { + temperature = ((smart_log.temperature[1] << 8) | smart_log.temperature[0]); + temperature = temperature ? temperature - 273 : 0; + for (i = 0; i < SensorCount && tempSensors[i]; i++) { + tempSensors[i] = le16_to_cpu(smart_log.temp_sensor[i]); + tempSensors[i] = tempSensors[i] ? tempSensors[i] - 273 : 0; + } + if (is_json) { + struct json_object *stats = json_create_object(); + char tempstr[64] = { 0 }; + + root = json_create_object(); + logPages = json_create_array(); + json_object_add_value_array(root, "Micron temperature information", logPages); + sprintf(tempstr, "%u C", temperature); + json_object_add_value_string(stats, "Current Composite Temperature", tempstr); + for (i = 0; i < SensorCount && tempSensors[i]; i++) { + char sensor_str[256] = { 0 }; + char datastr[64] = { 0 }; + + sprintf(sensor_str, "Temperature Sensor #%d", (i + 1)); + sprintf(datastr, "%u C", tempSensors[i]); + json_object_add_value_string(stats, sensor_str, datastr); + } + json_array_add_value_object(logPages, stats); + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); + } else { + printf("Micron temperature information:\n"); + printf("%-10s : %u C\n", "Current Composite Temperature", temperature); + for (i = 0; i < SensorCount && tempSensors[i]; i++) + printf("%-10s%d : %u C\n", "Temperature Sensor #", i + 1, tempSensors[i]); + } + } + dev_close(dev); + return err; +} + +static int micron_pcie_stats(int argc, char **argv, + struct command *cmd, struct plugin *plugin) +{ + int i, err = 0, bus, domain, device, function, ctrlIdx; + char strTempFile[1024], strTempFile2[1024], command[1024]; + struct nvme_dev *dev; + 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 }; + struct nvme_passthru_cmd admin_cmd = { 0 }; + enum eDriveModel eModel = UNKNOWN_MODEL; + char *res; + bool is_json = true; + bool counters = false; + struct format { + char *fmt; + }; + const char *desc = "Retrieve PCIe event counters"; + const char *fmt = "output format json|normal"; + struct format cfg = { + .fmt = "json", + }; + struct pcie_error_counters { + __u16 receiver_error; + __u16 bad_tlp; + __u16 bad_dllp; + __u16 replay_num_rollover; + __u16 replay_timer_timeout; + __u16 advisory_non_fatal_error; + __u16 DLPES; + __u16 poisoned_tlp; + __u16 FCPC; + __u16 completion_timeout; + __u16 completion_abort; + __u16 unexpected_completion; + __u16 receiver_overflow; + __u16 malformed_tlp; + __u16 ecrc_error; + __u16 unsupported_request_error; + } pcie_error_counters = { 0 }; + + struct { + char *err; + int bit; + int val; + } pcie_correctable_errors[] = { + { "Unsupported Request Error Status (URES)", 20, + offsetof(struct pcie_error_counters, unsupported_request_error)}, + { "ECRC Error Status (ECRCES)", 19, + offsetof(struct pcie_error_counters, ecrc_error)}, + { "Malformed TLP Status (MTS)", 18, + offsetof(struct pcie_error_counters, malformed_tlp)}, + { "Receiver Overflow Status (ROS)", 17, + offsetof(struct pcie_error_counters, receiver_overflow)}, + { "Unexpected Completion Status (UCS)", 16, + offsetof(struct pcie_error_counters, unexpected_completion)}, + { "Completer Abort Status (CAS)", 15, + offsetof(struct pcie_error_counters, completion_abort)}, + { "Completion Timeout Status (CTS)", 14, + offsetof(struct pcie_error_counters, completion_timeout)}, + { "Flow Control Protocol Error Status (FCPES)", 13, + offsetof(struct pcie_error_counters, FCPC)}, + { "Poisoned TLP Status (PTS)", 12, + offsetof(struct pcie_error_counters, poisoned_tlp)}, + { "Data Link Protocol Error Status (DLPES)", 4, + offsetof(struct pcie_error_counters, DLPES)}, + }, + pcie_uncorrectable_errors[] = { + { "Advisory Non-Fatal Error Status (ANFES)", 13, + offsetof(struct pcie_error_counters, advisory_non_fatal_error)}, + { "Replay Timer Timeout Status (RTS)", 12, + offsetof(struct pcie_error_counters, replay_timer_timeout)}, + { "REPLAY_NUM Rollover Status (RRS)", 8, + offsetof(struct pcie_error_counters, replay_num_rollover)}, + { "Bad DLLP Status (BDS)", 7, + offsetof(struct pcie_error_counters, bad_dllp)}, + { "Bad TLP Status (BTS)", 6, + offsetof(struct pcie_error_counters, bad_tlp)}, + { "Receiver Error Status (RES)", 0, + offsetof(struct pcie_error_counters, receiver_error)}, + }; + + __u32 correctable_errors; + __u32 uncorrectable_errors; + + OPT_ARGS(opts) = { + OPT_FMT("format", 'f', &cfg.fmt, fmt), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) { + printf("\nDevice not found\n"); + return -1; + } + + /* pull log details based on the model name */ + if (sscanf(argv[optind], "/dev/nvme%d", &ctrlIdx) != 1) + ctrlIdx = 0; + eModel = GetDriveModel(ctrlIdx); + if (eModel == UNKNOWN_MODEL) { + printf("Unsupported drive model for vs-pcie-stats command\n"); + goto out; + } + + if (!strcmp(cfg.fmt, "normal")) + is_json = false; + + if (eModel == M5407) { + admin_cmd.opcode = 0xD6; + admin_cmd.addr = (__u64)(uintptr_t)&pcie_error_counters; + admin_cmd.data_len = sizeof(pcie_error_counters); + admin_cmd.cdw10 = 1; + err = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL); + if (!err) { + counters = true; + correctable_errors = 10; + uncorrectable_errors = 6; + goto print_stats; + } + } + + 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); + memset(strTempFile2, 0x0, 1024); + sLinkSize = readlink(strTempFile, strTempFile2, 1023); + if (sLinkSize < 0) { + err = -errno; + printf("Failed to read device\n"); + goto out; + } + if (strstr(strTempFile2, "../../nvme")) { + sprintf(strTempFile, "/sys/block/%s/device/device", devicename); + memset(strTempFile2, 0x0, 1024); + sLinkSize = readlink(strTempFile, strTempFile2, 1023); + if (sLinkSize < 0) { + err = -errno; + printf("Failed to read device\n"); + goto out; + } + } + businfo = strrchr(strTempFile2, '/'); + if (sscanf(businfo, "/%x:%x:%x.%x", &domain, &bus, &device, &function) != 4) + domain = bus = device = function = 0; + sprintf(command, "setpci -s %x:%x.%x ECAP_AER+10.L", bus, device, + function); + fp = popen(command, "r"); + if (!fp) { + printf("Failed to retrieve error count\n"); + goto out; + } + res = fgets(correctable, sizeof(correctable), fp); + if (!res) { + printf("Failed to retrieve error count\n"); + pclose(fp); + goto out; + } + pclose(fp); + + sprintf(command, "setpci -s %x:%x.%x ECAP_AER+0x4.L", bus, device, + function); + fp = popen(command, "r"); + if (!fp) { + printf("Failed to retrieve error count\n"); + goto out; + } + res = fgets(uncorrectable, sizeof(uncorrectable), fp); + if (!res) { + printf("Failed to retrieve error count\n"); + pclose(fp); + goto out; + } + pclose(fp); + + correctable_errors = (__u32)strtol(correctable, NULL, 16); + uncorrectable_errors = (__u32)strtol(uncorrectable, NULL, 16); + +print_stats: + if (is_json) { + struct json_object *root = json_create_object(); + struct json_object *pcieErrors = json_create_array(); + struct json_object *stats = json_create_object(); + __u8 *pcounter = (__u8 *)&pcie_error_counters; + + json_object_add_value_array(root, "PCIE Stats", pcieErrors); + for (i = 0; i < ARRAY_SIZE(pcie_correctable_errors); i++) { + __u16 val = counters ? *(__u16 *)(pcounter + pcie_correctable_errors[i].val) : + (correctable_errors >> pcie_correctable_errors[i].bit) & 1; + json_object_add_value_int(stats, pcie_correctable_errors[i].err, val); + } + for (i = 0; i < ARRAY_SIZE(pcie_uncorrectable_errors); i++) { + __u16 val = counters ? *(__u16 *)(pcounter + pcie_uncorrectable_errors[i].val) : + (uncorrectable_errors >> pcie_uncorrectable_errors[i].bit) & 1; + json_object_add_value_int(stats, pcie_uncorrectable_errors[i].err, val); + } + json_array_add_value_object(pcieErrors, stats); + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); + } else if (counters == true) { + __u8 *pcounter = (__u8 *)&pcie_error_counters; + + for (i = 0; i < ARRAY_SIZE(pcie_correctable_errors); i++) + printf("%-42s : %-1hu\n", pcie_correctable_errors[i].err, + *(__u16 *)(pcounter + pcie_correctable_errors[i].val)); + for (i = 0; i < ARRAY_SIZE(pcie_uncorrectable_errors); i++) + printf("%-42s : %-1hu\n", pcie_uncorrectable_errors[i].err, + *(__u16 *)(pcounter + pcie_uncorrectable_errors[i].val)); + } else if (eModel == M5407 || eModel == M5410) { + for (i = 0; i < ARRAY_SIZE(pcie_correctable_errors); i++) + printf("%-42s : %-1d\n", pcie_correctable_errors[i].err, + ((correctable_errors >> pcie_correctable_errors[i].bit) & 1)); + for (i = 0; i < ARRAY_SIZE(pcie_uncorrectable_errors); i++) + printf("%-42s : %-1d\n", pcie_uncorrectable_errors[i].err, + ((uncorrectable_errors >> pcie_uncorrectable_errors[i].bit) & 1)); + } else { + printf("PCIE Stats:\n"); + printf("Device correctable errors detected: %s\n", correctable); + printf("Device uncorrectable errors detected: %s\n", uncorrectable); + } + +out: + dev_close(dev); + return err; +} + +static int micron_clear_pcie_correctable_errors(int argc, char **argv, + struct command *cmd, + struct plugin *plugin) +{ + int err = -EINVAL, bus, domain, device, function; + char strTempFile[1024], strTempFile2[1024], command[1024]; + struct nvme_dev *dev; + char *businfo = NULL; + char *devicename = NULL; + char tdevice[PATH_MAX] = { 0 }; + ssize_t sLinkSize = 0; + enum eDriveModel model = UNKNOWN_MODEL; + struct nvme_passthru_cmd admin_cmd = { 0 }; + char correctable[8] = { 0 }; + FILE *fp; + char *res; + const char *desc = "Clear PCIe Device Correctable Errors"; + __u32 result = 0; + __u8 fid = MICRON_FEATURE_CLEAR_PCI_CORRECTABLE_ERRORS; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = micron_parse_options(&dev, argc, argv, desc, opts, &model); + if (err < 0) + return err; + + /* For M51CX models, PCIe errors are cleared using 0xC3 feature */ + if (model == M51CX) { + err = nvme_set_features_simple(dev_fd(dev), fid, 0, (1 << 31), false, + &result); + if (!err) + err = (int)result; + if (!err) { + printf("Device correctable errors are cleared!\n"); + goto out; + } + } else if (model == M5407) { + admin_cmd.opcode = 0xD6; + admin_cmd.addr = 0; + admin_cmd.cdw10 = 0; + err = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL); + if (!err) { + printf("Device correctable error counters are cleared!\n"); + goto out; + } else { + /* proceed to clear status bits using sysfs interface */ + } + } + + 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; + + memset(strTempFile2, 0x0, 1024); + sLinkSize = readlink(strTempFile, strTempFile2, 1023); + if (sLinkSize < 0) { + err = -errno; + printf("Failed 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; + memset(strTempFile2, 0x0, 1024); + sLinkSize = readlink(strTempFile, strTempFile2, 1023); + if (sLinkSize < 0) { + err = -errno; + printf("Failed to read device\n"); + goto out; + } + } + businfo = strrchr(strTempFile2, '/'); + if (sscanf(businfo, "/%x:%x:%x.%x", &domain, &bus, &device, &function) != 4) + domain = bus = device = function = 0; + sprintf(command, "setpci -s %x:%x.%x ECAP_AER+0x10.L=0xffffffff", bus, + device, function); + err = -1; + fp = popen(command, "r"); + if (!fp) { + printf("Failed 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) { + printf("Failed to retrieve error count\n"); + goto out; + } + res = fgets(correctable, sizeof(correctable), fp); + if (!res) { + printf("Failed to retrieve error count\n"); + pclose(fp); + goto out; + } + pclose(fp); + printf("Device correctable errors cleared!\n"); + printf("Device correctable errors detected: %s\n", correctable); + err = 0; +out: + dev_close(dev); + return err; +} + +static struct logpage { + const char *field; + char datastr[128]; +} d0_log_page[] = { + { "NAND Writes (Bytes Written)", { 0 }}, + { "Program Failure Count", { 0 }}, + { "Erase Failures", { 0 }}, + { "Bad Block Count", { 0 }}, + { "NAND XOR/RAID Recovery Trigger Events", { 0 }}, + { "NSZE Change Supported", { 0 }}, + { "Number of NSZE Modifications", { 0 }} +}; + +static void init_d0_log_page(__u8 *buf, __u8 nsze) +{ + unsigned int logD0[D0_log_size/sizeof(int)] = { 0 }; + __u64 count_lo, count_hi, count; + + memcpy(logD0, buf, sizeof(logD0)); + + + count = ((__u64)logD0[45] << 32) | logD0[44]; + sprintf(d0_log_page[0].datastr, "0x%"PRIx64, le64_to_cpu(count)); + + count_hi = ((__u64)logD0[39] << 32) | logD0[38]; + count_lo = ((__u64)logD0[37] << 32) | logD0[36]; + if (count_hi) + sprintf(d0_log_page[1].datastr, "0x%"PRIx64"%016"PRIx64, + le64_to_cpu(count_hi), le64_to_cpu(count_lo)); + else + sprintf(d0_log_page[1].datastr, "0x%"PRIx64, le64_to_cpu(count_lo)); + + count = ((__u64)logD0[25] << 32) | logD0[24]; + sprintf(d0_log_page[2].datastr, "0x%"PRIx64, le64_to_cpu(count)); + + sprintf(d0_log_page[3].datastr, "0x%x", logD0[3]); + + count_lo = ((__u64)logD0[37] << 32) | logD0[36]; + count = ((__u64)logD0[25] << 32) | logD0[24]; + count = (__u64)logD0[3] - (count_lo + count); + sprintf(d0_log_page[4].datastr, "0x%"PRIx64, le64_to_cpu(count)); + + sprintf(d0_log_page[5].datastr, "0x%x", nsze); + sprintf(d0_log_page[6].datastr, "0x%x", logD0[1]); +} + +/* OCP and Vendor specific log data format */ +struct micron_vs_logpage { + char *field; + int size; /* FB client spec version 1.0 sizes - M5410 models */ + int size2; /* FB client spec version 0.7 sizes - M5407 models */ +} +/* Smart Health Log information as per OCP spec M51CX models */ +ocp_c0_log_page[] = { + { "Physical Media Units Written", 16}, + { "Physical Media Units Read", 16 }, + { "Raw Bad User NAND Block Count", 6}, + { "Normalized Bad User NAND Block Count", 2}, + { "Raw Bad System NAND Block Count", 6}, + { "Normalized Bad System NAND Block Count", 2}, + { "XOR Recovery Count", 8}, + { "Uncorrectable Read Error Count", 8}, + { "Soft ECC Error Count", 8}, + { "SSD End to End Detected Counts", 4}, + { "SSD End to End Corrected Errors", 4}, + { "System data % life-used", 1}, + { "Refresh Count", 7}, + { "Maximum User Data Erase Count", 4}, + { "Minimum User Data Erase Count", 4}, + { "Thermal Throttling Count", 1}, + { "Thermal Throttling Status", 1}, + { "Reserved", 6}, + { "PCIe Correctable Error count", 8}, + { "Incomplete Shutdowns", 4}, + { "Reserved", 4}, + { "% Free Blocks", 1}, + { "Reserved", 7}, + { "Capacitor Health", 2}, + { "Reserved", 6}, + { "Unaligned I/O", 8}, + { "Security Version Number", 8}, + { "NUSE", 8}, + { "PLP Start Count", 16}, + { "Endurance Estimate", 16}, + { "Reserved", 302}, + { "Log Page Version", 2}, + { "Log Page GUID", 16}, +}, +/* Extended SMART log information */ +e1_log_page[] = { + { "Reserved", 12}, + { "Grown Bad Block Count", 4}, + { "Per Block Max Erase Count", 4}, + { "Power On Minutes", 4}, + { "Reserved", 24}, + { "Write Protect Reason", 4}, + { "Reserved", 12}, + { "Drive Capacity", 8}, + { "Reserved", 8}, + { "Total Erase Count", 8}, + { "Lifetime Use Rate", 8}, + { "Erase Fail Count", 8}, + { "Reserved", 8}, + { "Reported UC Errors", 8}, + { "Reserved", 24}, + { "Program Fail Count", 16}, + { "Total Bytes Read", 16}, + { "Total Bytes Written", 16}, + { "Reserved", 16}, + { "TU Size", 4}, + { "Total Block Stripe Count", 4}, + { "Free Block Stripe Count", 4}, + { "Block Stripe Size", 8}, + { "Reserved", 16}, + { "User Block Min Erase Count", 4}, + { "User Block Avg Erase Count", 4}, + { "User Block Max Erase Count", 4}, +}, +/* Vendor Specific Health Log information */ +fb_log_page[] = { + { "Physical Media Units Written - TLC", 16, 16 }, + { "Physical Media Units Written - SLC", 16, 16 }, + { "Normalized Bad User NAND Block Count", 2, 2}, + { "Raw Bad User NAND Block Count", 6, 6}, + { "XOR Recovery Count", 8, 8}, + { "Uncorrectable Read Error Count", 8, 8}, + { "SSD End to End Corrected Errors", 8, 8}, + { "SSD End to End Detected Counts", 4, 8}, + { "SSD End to End Uncorrected Counts", 4, 8}, + { "System data % life-used", 1, 1}, + { "Reserved", 0, 3}, + { "Minimum User Data Erase Count - TLC", 8, 8}, + { "Maximum User Data Erase Count - TLC", 8, 8}, + { "Average User Data Erase Count - TLC", 0, 8}, + { "Minimum User Data Erase Count - SLC", 8, 8}, + { "Maximum User Data Erase Count - SLC", 8, 8}, + { "Average User Data Erase Count - SLC", 0, 8}, + { "Normalized Program Fail Count", 2, 2}, + { "Raw Program Fail Count", 6, 6}, + { "Normalized Erase Fail Count", 2, 2}, + { "Raw Erase Fail Count", 6, 6}, + { "Pcie Correctable Error Count", 8, 8}, + { "% Free Blocks (User)", 1, 1}, + { "Reserved", 0, 3}, + { "Security Version Number", 8, 8}, + { "% Free Blocks (System)", 1, 1}, + { "Reserved", 0, 3}, + { "Dataset Management (Deallocate) Commands", 16, 16}, + { "Incomplete TRIM Data", 8, 8}, + { "% Age of Completed TRIM", 1, 2}, + { "Background Back-Pressure Gauge", 1, 1}, + { "Reserved", 0, 3}, + { "Soft ECC Error Count", 8, 8}, + { "Refresh Count", 8, 8}, + { "Normalized Bad System NAND Block Count", 2, 2}, + { "Raw Bad System NAND Block Count", 6, 6}, + { "Endurance Estimate", 16, 16}, + { "Thermal Throttling Status", 1, 1}, + { "Thermal Throttling Count", 1, 1}, + { "Unaligned I/O", 8, 8}, + { "Physical Media Units Read", 16, 16}, + { "Reserved", 279, 0}, + { "Log Page Version", 2, 0}, + { "READ CMDs exceeding threshold", 0, 4}, + { "WRITE CMDs exceeding threshold", 0, 4}, + { "TRIMs CMDs exceeding threshold", 0, 4}, + { "Reserved", 0, 4}, + { "Reserved", 0, 210}, + { "Log Page Version", 0, 2}, + { "Log Page GUID", 0, 16}, +}; + +/* + * Common function to print Micron VS log pages + * - buf: raw log data + * - log_page: format of the data + * - field_count: log field count + * - stats: json object to add fields + * - spec: ocp spec index + */ +static void print_micron_vs_logs(__u8 *buf, struct micron_vs_logpage *log_page, int field_count, + struct json_object *stats, __u8 spec) +{ + __u64 lval_lo, lval_hi; + __u32 ival; + __u16 sval; + __u8 cval, lval[8] = { 0 }; + int field; + int offset = 0; + + for (field = 0; field < field_count; field++) { + char datastr[1024] = { 0 }; + char *sfield = NULL; + int size = !spec ? log_page[field].size : log_page[field].size2; + + if (!size) + continue; + sfield = log_page[field].field; + if (size == 16) { + if (strstr(sfield, "GUID")) { + sprintf(datastr, "0x%"PRIx64"%"PRIx64"", + (uint64_t)le64_to_cpu(*(uint64_t *)(&buf[offset + 8])), + (uint64_t)le64_to_cpu(*(uint64_t *)(&buf[offset]))); + } else { + lval_lo = *((__u64 *)(&buf[offset])); + lval_hi = *((__u64 *)(&buf[offset + 8])); + if (lval_hi) + sprintf(datastr, "0x%"PRIx64"%016"PRIx64"", + le64_to_cpu(lval_hi), le64_to_cpu(lval_lo)); + else + sprintf(datastr, "0x%"PRIx64"", le64_to_cpu(lval_lo)); + } + } else if (size == 8) { + lval_lo = *((__u64 *)(&buf[offset])); + sprintf(datastr, "0x%"PRIx64"", le64_to_cpu(lval_lo)); + } else if (size == 7) { + /* 7 bytes will be in little-endian format, with last byte as MSB */ + memcpy(&lval[0], &buf[offset], 7); + memcpy((void *)&lval_lo, lval, 8); + sprintf(datastr, "0x%"PRIx64"", le64_to_cpu(lval_lo)); + } else if (size == 6) { + ival = *((__u32 *)(&buf[offset])); + sval = *((__u16 *)(&buf[offset + 4])); + lval_lo = (((__u64)sval << 32) | ival); + sprintf(datastr, "0x%"PRIx64"", le64_to_cpu(lval_lo)); + } else if (size == 4) { + ival = *((__u32 *)(&buf[offset])); + sprintf(datastr, "0x%x", le32_to_cpu(ival)); + } else if (size == 2) { + sval = *((__u16 *)(&buf[offset])); + sprintf(datastr, "0x%04x", le16_to_cpu(sval)); + } else if (size == 1) { + cval = buf[offset]; + sprintf(datastr, "0x%02x", cval); + } else { + sprintf(datastr, "0"); + } + offset += size; + /* do not print reserved values */ + if (strstr(sfield, "Reserved")) + continue; + if (stats) + json_object_add_value_string(stats, sfield, datastr); + else + printf("%-40s : %-4s\n", sfield, datastr); + } +} + +static void print_smart_cloud_health_log(__u8 *buf, bool is_json) +{ + struct json_object *root; + struct json_object *logPages; + struct json_object *stats = NULL; + int field_count = ARRAY_SIZE(ocp_c0_log_page); + + if (is_json) { + root = json_create_object(); + stats = json_create_object(); + logPages = json_create_array(); + json_object_add_value_array(root, "OCP SMART Cloud Health Log: 0xC0", + logPages); + } + + print_micron_vs_logs(buf, ocp_c0_log_page, field_count, stats, 0); + + if (is_json) { + json_array_add_value_object(logPages, stats); + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); + } +} + +static void print_nand_stats_fb(__u8 *buf, __u8 *buf2, __u8 nsze, bool is_json, __u8 spec) +{ + struct json_object *root; + struct json_object *logPages; + struct json_object *stats = NULL; + int field_count = ARRAY_SIZE(fb_log_page); + + if (is_json) { + root = json_create_object(); + stats = json_create_object(); + logPages = json_create_array(); + json_object_add_value_array(root, "Extended Smart Log Page : 0xFB", + logPages); + } + + print_micron_vs_logs(buf, fb_log_page, field_count, stats, spec); + + /* print last three entries from D0 log page */ + if (buf2) { + init_d0_log_page(buf2, nsze); + + if (is_json) { + for (int i = 0; i < 7; i++) + json_object_add_value_string(stats, + d0_log_page[i].field, + d0_log_page[i].datastr); + } else { + for (int i = 0; i < 7; i++) + printf("%-40s : %s\n", d0_log_page[i].field, d0_log_page[i].datastr); + } + } + + if (is_json) { + json_array_add_value_object(logPages, stats); + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); + } +} + +static void print_nand_stats_d0(__u8 *buf, __u8 oacs, bool is_json) +{ + init_d0_log_page(buf, oacs); + + if (is_json) { + struct json_object *root = json_create_object(); + struct json_object *stats = json_create_object(); + struct json_object *logPages = json_create_array(); + + json_object_add_value_array(root, + "Extended Smart Log Page : 0xD0", + logPages); + + for (int i = 0; i < 7; i++) + json_object_add_value_string(stats, + d0_log_page[i].field, + d0_log_page[i].datastr); + + json_array_add_value_object(logPages, stats); + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); + } else { + for (int i = 0; i < 7; i++) + printf("%-40s : %s\n", d0_log_page[i].field, d0_log_page[i].datastr); + } +} + +static bool nsze_from_oacs; /* read nsze for now from idd[4059] */ + +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[D0_log_size/sizeof(int)] = { 0 }; + unsigned int logFB[FB_log_size/sizeof(int)] = { 0 }; + enum eDriveModel eModel = UNKNOWN_MODEL; + struct nvme_id_ctrl ctrl; + struct nvme_dev *dev; + int err, ctrlIdx; + __u8 nsze; + bool has_d0_log = true; + bool has_fb_log = false; + bool is_json = true; + struct format { + char *fmt; + }; + const char *fmt = "output format json|normal"; + struct format cfg = { + .fmt = "json", + }; + + OPT_ARGS(opts) = { + OPT_FMT("format", 'f', &cfg.fmt, fmt), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) { + printf("\nDevice not found\n"); + return -1; + } + + if (!strcmp(cfg.fmt, "normal")) + is_json = false; + + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (err) { + printf("Error %d retrieving controller identification data\n", err); + goto out; + } + + /* pull log details based on the model name */ + if (sscanf(argv[optind], "/dev/nvme%d", &ctrlIdx) != 1) + ctrlIdx = 0; + eModel = GetDriveModel(ctrlIdx); + if ((eModel == UNKNOWN_MODEL) || (eModel == M51CX)) { + printf("Unsupported drive model for vs-nand-stats command\n"); + err = -1; + goto out; + } + + err = nvme_get_log_simple(dev_fd(dev), 0xD0, D0_log_size, extSmartLog); + has_d0_log = !err; + + /* should check for firmware version if this log is supported or not */ + if (eModel == M5407 || eModel == M5410) { + err = nvme_get_log_simple(dev_fd(dev), 0xFB, FB_log_size, logFB); + has_fb_log = !err; + } + + nsze = (ctrl.vs[987] == 0x12); + if (!nsze && nsze_from_oacs) + nsze = ((ctrl.oacs >> 3) & 0x1); + err = 0; + if (has_fb_log) { + __u8 spec = (eModel == M5410) ? 0 : 1; /* FB spec version */ + + print_nand_stats_fb((__u8 *)logFB, (__u8 *)extSmartLog, nsze, is_json, spec); + } else if (has_d0_log) { + print_nand_stats_d0((__u8 *)extSmartLog, nsze, is_json); + } else { + printf("Unable to retrieve extended smart log for the drive\n"); + err = -ENOTTY; + } +out: + dev_close(dev); + if (err > 0) + nvme_show_status(err); + + return err; +} + +static void print_ext_smart_logs_e1(__u8 *buf, bool is_json) +{ + struct json_object *root; + struct json_object *logPages; + struct json_object *stats = NULL; + int field_count = ARRAY_SIZE(e1_log_page); + + if (is_json) { + root = json_create_object(); + stats = json_create_object(); + logPages = json_create_array(); + json_object_add_value_array(root, "SMART Extended Log:0xE1", logPages); + } else { + printf("SMART Extended Log:0xE1\n"); + } + + print_micron_vs_logs(buf, e1_log_page, field_count, stats, 0); + + if (is_json) { + json_array_add_value_object(logPages, stats); + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); + } +} + +static int micron_smart_ext_log(int argc, char **argv, + struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Retrieve extended SMART logs for the given device "; + unsigned int extSmartLog[E1_log_size/sizeof(int)] = { 0 }; + enum eDriveModel eModel = UNKNOWN_MODEL; + int err = 0, ctrlIdx; + struct nvme_dev *dev; + bool is_json = true; + struct format { + char *fmt; + }; + const char *fmt = "output format json|normal"; + struct format cfg = { + .fmt = "json", + }; + OPT_ARGS(opts) = { + OPT_FMT("format", 'f', &cfg.fmt, fmt), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) { + printf("\nDevice not found\n"); + return -1; + } + if (!strcmp(cfg.fmt, "normal")) + is_json = false; + + if (sscanf(argv[optind], "/dev/nvme%d", &ctrlIdx) != 1) + ctrlIdx = 0; + eModel = GetDriveModel(ctrlIdx); + if (eModel != M51CX) { + printf("Unsupported drive model for vs-smart-ext-log command\n"); + err = -1; + goto out; + } + err = nvme_get_log_simple(dev_fd(dev), 0xE1, E1_log_size, extSmartLog); + if (!err) + print_ext_smart_logs_e1((__u8 *)extSmartLog, is_json); + +out: + dev_close(dev); + if (err > 0) + nvme_show_status(err); + return err; +} + +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("Failed 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) +{ + __u8 outstr[1024]; + time_t t; + struct tm *tmp; + size_t num; + char *strPDir; + char *strDest; + + t = time(NULL); + tmp = localtime(&t); + if (!tmp) + return; + + num = strftime((char *)outstr, sizeof(outstr), + "Timestamp (UTC): %a, %d %b %Y %T %z", tmp); + num += sprintf((char *)(outstr + num), "\nPackage Version: 1.4"); + if (num) { + strPDir = strdup(strOSDirName); + strDest = dirname(strPDir); + WriteData(outstr, num, strDest, "timestamp_info.txt", "timestamp"); + free(strPDir); + } +} + +static void GetCtrlIDDInfo(const char *dir, struct nvme_id_ctrl *ctrlp) +{ + WriteData((__u8 *)ctrlp, sizeof(*ctrlp), dir, + "nvme_controller_identify_data.bin", "id-ctrl"); +} + +static void GetSmartlogData(int fd, const char *dir) +{ + struct nvme_smart_log smart_log; + + if (!nvme_get_log_smart(fd, -1, false, &smart_log)) + WriteData((__u8 *)&smart_log, sizeof(smart_log), dir, + "smart_data.bin", "smart log"); +} + +static void GetErrorlogData(int fd, int entries, const char *dir) +{ + 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) + return; + + if (!nvme_get_log_error(fd, entries, false, error_log)) + WriteData((__u8 *)error_log, logSize, dir, + "error_information_log.bin", "error log"); + + free(error_log); +} + +static void GetGenericLogs(int fd, const char *dir) +{ + struct nvme_self_test_log self_test_log; + struct nvme_firmware_slot fw_log; + struct nvme_cmd_effects_log effects; + struct nvme_persistent_event_log pevent_log; + _cleanup_huge_ struct nvme_mem_huge mh = { 0, }; + void *pevent_log_info = NULL; + __u32 log_len = 0; + int err = 0; + + /* get self test log */ + if (!nvme_get_log_device_self_test(fd, &self_test_log)) + WriteData((__u8 *)&self_test_log, sizeof(self_test_log), dir, + "drive_self_test.bin", "self test log"); + + /* get fw slot info log */ + if (!nvme_get_log_fw_slot(fd, false, &fw_log)) + WriteData((__u8 *)&fw_log, sizeof(fw_log), dir, + "firmware_slot_info_log.bin", "firmware log"); + + /* get effects log */ + if (!nvme_get_log_cmd_effects(fd, NVME_CSI_NVM, &effects)) + WriteData((__u8 *)&effects, sizeof(effects), dir, + "command_effects_log.bin", "effects log"); + + /* get persistent event log */ + (void)nvme_get_log_persistent_event(fd, NVME_PEVENT_LOG_RELEASE_CTX, + sizeof(pevent_log), &pevent_log); + memset(&pevent_log, 0, sizeof(pevent_log)); + err = nvme_get_log_persistent_event(fd, NVME_PEVENT_LOG_EST_CTX_AND_READ, + sizeof(pevent_log), &pevent_log); + if (err) { + fprintf(stderr, "Setting persistent event log read ctx failed (ignored)!\n"); + return; + } + + log_len = le64_to_cpu(pevent_log.tll); + pevent_log_info = nvme_alloc_huge(log_len, &mh); + if (!pevent_log_info) { + perror("could not alloc buffer for persistent event log page (ignored)!\n"); + return; + } + + err = nvme_get_log_persistent_event(fd, NVME_PEVENT_LOG_READ, + log_len, pevent_log_info); + if (!err) + WriteData((__u8 *)pevent_log_info, log_len, dir, + "persistent_event_log.bin", "persistent event log"); +} + +static void GetNSIDDInfo(int fd, const char *dir, int nsid) +{ + char file[PATH_MAX] = { 0 }; + struct nvme_id_ns ns; + + if (!nvme_identify_ns(fd, nsid, &ns)) { + sprintf(file, "identify_namespace_%d_data.bin", nsid); + WriteData((__u8 *)&ns, sizeof(ns), dir, file, "id-ns"); + } +} + +static void GetOSConfig(const char *strOSDirName) +{ + FILE *fpOSConfig = NULL; + char strBuffer[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+"); + if (fpOSConfig) { + fprintf(fpOSConfig, + "\n\n\n\n%s\n-----------------------------------------------\n", + cmdArray[i].strcmdHeader); + fclose(fpOSConfig); + fpOSConfig = NULL; + } + snprintf(strBuffer, sizeof(strBuffer) - 1, + cmdArray[i].strCommand, strFileName); + if (system(strBuffer)) + fprintf(stderr, "Failed to send \"%s\"\n", strBuffer); + } +} + +static int micron_telemetry_log(int fd, __u8 type, __u8 **data, + int *logSize, int da) +{ + int err, bs = 512, offset = bs; + unsigned short data_area[4]; + unsigned char ctrl_init = (type == 0x8); + + __u8 *buffer = (unsigned char *)calloc(bs, 1); + + if (!buffer) + return -1; + if (ctrl_init) + err = nvme_get_log_telemetry_ctrl(fd, true, 0, bs, buffer); + else + err = nvme_get_log_telemetry_host(fd, 0, bs, buffer); + if (err) { + fprintf(stderr, "Failed to get telemetry log header for 0x%X\n", type); + if (buffer) + free(buffer); + return err; + } + + /* compute size of the log */ + data_area[1] = buffer[9] << 8 | buffer[8]; + data_area[2] = buffer[11] << 8 | buffer[10]; + data_area[3] = buffer[13] << 8 | buffer[12]; + data_area[0] = data_area[1] > data_area[2] ? data_area[1] : data_area[2]; + data_area[0] = data_area[3] > data_area[0] ? data_area[3] : data_area[0]; + + if (!data_area[da]) { + fprintf(stderr, "Requested telemetry data for 0x%X is empty\n", type); + if (buffer) { + free(buffer); + buffer = NULL; + } + return -1; + } + + *logSize = data_area[da] * bs; + offset = bs; + err = 0; + buffer = (unsigned char *)realloc(buffer, (size_t)(*logSize)); + if (buffer) { + while (!err && offset != *logSize) { + if (ctrl_init) + err = nvme_get_log_telemetry_ctrl(fd, true, 0, *logSize, buffer + offset); + else + err = nvme_get_log_telemetry_host(fd, 0, *logSize, buffer + offset); + offset += bs; + } + } + + if (!err && buffer) { + *data = buffer; + } else { + fprintf(stderr, "Failed to get telemetry data for 0x%x\n", type); + if (buffer) + free(buffer); + } + + return err; +} + +static int GetTelemetryData(int fd, const char *dir) +{ + unsigned char *buffer = NULL; + int i, err, logSize = 0; + char msg[256] = { 0 }; + struct { + __u8 log; + char *file; + } tmap[] = { + {0x07, "nvmetelemetrylog.bin"}, + {0x08, "nvmetelemetrylog.bin"}, + }; + + for (i = 0; i < (int)(ARRAY_SIZE(tmap)); i++) { + err = micron_telemetry_log(fd, tmap[i].log, &buffer, &logSize, 0); + if (!err && logSize > 0 && buffer) { + sprintf(msg, "telemetry log: 0x%X", tmap[i].log); + WriteData(buffer, logSize, dir, tmap[i].file, msg); + } + if (buffer) { + free(buffer); + buffer = NULL; + } + logSize = 0; + } + return err; +} + +static int GetFeatureSettings(int fd, const char *dir) +{ + unsigned char *bufp, buf[4096] = { 0 }; + int i, err, len, errcnt = 0; + __u32 attrVal = 0; + char msg[256] = { 0 }; + + struct features { + int id; + char *file; + } fmap[] = { + {0x01, "nvme_feature_setting_arbitration.bin"}, + {0x02, "nvme_feature_setting_pm.bin"}, + {0x03, "nvme_feature_setting_lba_range_namespace_1.bin"}, + {0x04, "nvme_feature_setting_temp_threshold.bin"}, + {0x05, "nvme_feature_setting_error_recovery.bin"}, + {0x06, "nvme_feature_setting_volatile_write_cache.bin"}, + {0x07, "nvme_feature_setting_num_queues.bin"}, + {0x08, "nvme_feature_setting_interrupt_coalescing.bin"}, + {0x09, "nvme_feature_setting_interrupt_vec_config.bin"}, + {0x0A, "nvme_feature_setting_write_atomicity.bin"}, + {0x0B, "nvme_feature_setting_async_event_config.bin"}, + {0x80, "nvme_feature_setting_sw_progress_marker.bin"}, + }; + + for (i = 0; i < (int)(ARRAY_SIZE(fmap)); i++) { + if (fmap[i].id == 0x03) { + len = 4096; + bufp = (unsigned char *)(&buf[0]); + } else { + len = 0; + bufp = NULL; + } + + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = fmap[i].id, + .nsid = 1, + .sel = 0, + .cdw11 = 0x0, + .uuidx = 0, + .data_len = len, + .data = bufp, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &attrVal, + }; + err = nvme_get_features(&args); + if (!err) { + sprintf(msg, "feature: 0x%X", fmap[i].id); + WriteData((__u8 *)&attrVal, sizeof(attrVal), dir, fmap[i].file, msg); + if (bufp) + WriteData(bufp, len, dir, fmap[i].file, msg); + } else { + fprintf(stderr, "Feature 0x%x data not retrieved, error %d (ignored)!\n", + fmap[i].id, err); + errcnt++; + } + } + return (int)(errcnt == ARRAY_SIZE(fmap)); +} + +static int micron_drive_info(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "Get drive HW information"; + struct nvme_id_ctrl ctrl = { 0 }; + struct nvme_passthru_cmd admin_cmd = { 0 }; + struct fb_drive_info { + unsigned char hw_ver_major; + unsigned char hw_ver_minor; + unsigned char ftl_unit_size; + unsigned char bs_ver_major; + unsigned char bs_ver_minor; + } dinfo = { 0 }; + enum eDriveModel model = UNKNOWN_MODEL; + bool is_json = false; + struct json_object *root, *driveInfo; + struct nvme_dev *dev; + struct format { + char *fmt; + }; + int err = 0; + + const char *fmt = "output format normal"; + struct format cfg = { + .fmt = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("format", 'f', &cfg.fmt, fmt), + OPT_END() + }; + + err = micron_parse_options(&dev, argc, argv, desc, opts, &model); + if (err < 0) + return err; + + if (model == UNKNOWN_MODEL) { + fprintf(stderr, "ERROR : Unsupported drive for vs-drive-info cmd"); + dev_close(dev); + return -1; + } + + if (!strcmp(cfg.fmt, "json")) + is_json = true; + + if (model == M5407) { + admin_cmd.opcode = 0xD4, + admin_cmd.addr = (__u64) (uintptr_t) &dinfo; + admin_cmd.data_len = (__u32)sizeof(dinfo); + admin_cmd.cdw12 = 3; + err = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL); + if (err) { + fprintf(stderr, "ERROR : drive-info opcode failed with 0x%x\n", err); + dev_close(dev); + return -1; + } + } else { + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (err) { + fprintf(stderr, "ERROR : identify_ctrl() failed with 0x%x\n", err); + dev_close(dev); + return -1; + } + dinfo.hw_ver_major = ctrl.vs[820]; + dinfo.hw_ver_minor = ctrl.vs[821]; + dinfo.ftl_unit_size = ctrl.vs[822]; + } + + if (is_json) { + struct json_object *pinfo = json_create_object(); + char tempstr[64] = { 0 }; + + root = json_create_object(); + driveInfo = json_create_array(); + json_object_add_value_array(root, "Micron Drive HW Information", driveInfo); + sprintf(tempstr, "%u.%u", dinfo.hw_ver_major, dinfo.hw_ver_minor); + json_object_add_value_string(pinfo, "Drive Hardware Version", tempstr); + + if (dinfo.ftl_unit_size) { + sprintf(tempstr, "%u KB", dinfo.ftl_unit_size); + json_object_add_value_string(pinfo, "FTL_unit_size", tempstr); + } + + if (dinfo.bs_ver_major || dinfo.bs_ver_minor) { + sprintf(tempstr, "%u.%u", dinfo.bs_ver_major, dinfo.bs_ver_minor); + json_object_add_value_string(pinfo, "Boot Spec.Version", tempstr); + } + + json_array_add_value_object(driveInfo, pinfo); + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); + } else { + printf("Drive Hardware Version: %u.%u\n", + dinfo.hw_ver_major, dinfo.hw_ver_minor); + + if (dinfo.ftl_unit_size) + printf("FTL_unit_size: %u KB\n", dinfo.ftl_unit_size); + + if (dinfo.bs_ver_major || dinfo.bs_ver_minor) + printf("Boot Spec.Version: %u.%u\n", + dinfo.bs_ver_major, dinfo.bs_ver_minor); + } + + dev_close(dev); + return 0; +} + +static int micron_cloud_ssd_plugin_version(int argc, char **argv, + struct command *cmd, struct plugin *plugin) +{ + printf("nvme-cli Micron cloud SSD plugin version: %s.%s\n", + __version_major, __version_minor); + return 0; +} + +static int micron_plugin_version(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + printf("nvme-cli Micron plugin version: %s.%s.%s\n", + __version_major, __version_minor, __version_patch); + return 0; +} + +/* Binary format of firmware activation history entry */ +struct __packed fw_activation_history_entry { + __u8 version; + __u8 length; + __u16 rsvd1; + __le16 valid; + __le64 power_on_hour; + __le64 rsvd2; + __le64 power_cycle_count; + __u8 previous_fw[8]; + __u8 activated_fw[8]; + __u8 slot; + __u8 commit_action_type; + __le16 result; + __u8 rsvd3[14]; +}; + +/* Binary format for firmware activation history table */ +struct __packed micron_fw_activation_history_table { + __u8 log_page; + __u8 rsvd1[3]; + __le32 num_entries; + struct fw_activation_history_entry entries[20]; + __u8 rsvd2[2790]; + __u16 version; + __u8 GUID[16]; +}; + +static int display_fw_activate_entry(int entry_count, struct fw_activation_history_entry *entry, + char *formatted_entry, struct json_object *stats) +{ + time_t timestamp, hours; + char buffer[32]; + __u8 minutes, seconds; + static const char * const ca[] = {"000b", "001b", "010b", "011b"}; + char *ptr = formatted_entry; + int index = 0, entry_size = 82; + + if ((entry->version != 1 && entry->version != 2) || entry->length != 64) + return -EINVAL; + + sprintf(ptr, "%d", entry_count); + ptr += 10; + + timestamp = (le64_to_cpu(entry->power_on_hour) & 0x0000FFFFFFFFFFFFUL) / 1000; + hours = timestamp / 3600; + minutes = (timestamp % 3600) / 60; + seconds = (timestamp % 3600) % 60; + sprintf(ptr, "|%"PRIu64":%u:%u", (uint64_t)hours, minutes, seconds); + ptr += 12; + + sprintf(ptr, "| %"PRIu64, le64_to_cpu(entry->power_cycle_count)); + ptr += 10; + + /* firmware details */ + memset(buffer, 0, sizeof(buffer)); + memcpy(buffer, entry->previous_fw, sizeof(entry->previous_fw)); + sprintf(ptr, "| %s", buffer); + ptr += 11; + + memset(buffer, 0, sizeof(buffer)); + memcpy(buffer, entry->activated_fw, sizeof(entry->activated_fw)); + sprintf(ptr, "| %s", buffer); + ptr += 12; + + /* firmware slot and commit action*/ + sprintf(ptr, "| %d", entry->slot); + ptr += 9; + + if (entry->commit_action_type <= 3) + sprintf(ptr, "| %s", ca[entry->commit_action_type]); + else + sprintf(ptr, "| xxxb"); + ptr += 9; + + /* result */ + if (entry->result) + sprintf(ptr, "| Fail #%d", entry->result); + else + sprintf(ptr, "| pass"); + + /* replace all null characters with spaces */ + ptr = formatted_entry; + while (index < entry_size) { + if (ptr[index] == '\0') + ptr[index] = ' '; + index++; + } + return 0; +} + +static void micron_fw_activation_history_header_print(void) +{ + /* header to be printed field widths = 10 | 12 | 10 | 11 | 12 | 9 | 9 | 9 */ + printf("__________________________________________________________________________________\n"); + printf(" | | | | | | |\n"); + printf("Firmware | Power On | Power | Previous | New FW | Slot | Commit | Result\n"); + printf("Activation| Hour | cycle | firmware | activated | number | Action |\n"); + printf("Counter | | count | | | | Type |\n"); + printf("__________|___________|_________|__________|___________|________|________|________\n"); +} + +static int micron_fw_activation_history(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "Retrieve Firmware Activation history of the given drive"; + char formatted_output[100]; + int count = 0; + unsigned int logC2[C2_log_size/sizeof(int)] = { 0 }; + enum eDriveModel eModel = UNKNOWN_MODEL; + struct nvme_dev *dev; + struct format { + char *fmt; + }; + int err; + + const char *fmt = "output format normal"; + struct format cfg = { + .fmt = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("format", 'f', &cfg.fmt, fmt), + OPT_END() + }; + + err = micron_parse_options(&dev, argc, argv, desc, opts, &eModel); + if (err < 0) + return -1; + + if (strcmp(cfg.fmt, "normal")) { + fprintf(stderr, "only normal format is supported currently\n"); + dev_close(dev); + return -1; + } + + /* check if product supports fw_history log */ + err = -EINVAL; + if (eModel != M51CX) { + fprintf(stderr, "Unsupported drive model for vs-fw-activate-history command\n"); + goto out; + } + + err = nvme_get_log_simple(dev_fd(dev), 0xC2, C2_log_size, logC2); + if (err) { + fprintf(stderr, "Failed to retrieve fw activation history log, error: %x\n", err); + goto out; + } + + /* check if we have at least one entry to print */ + struct micron_fw_activation_history_table *table = + (struct micron_fw_activation_history_table *)logC2; + + /* check version and log page */ + if (table->log_page != 0xC2 || (table->version != 2 && table->version != 1)) { + fprintf(stderr, "Unsupported fw activation history page: %x, version: %x\n", + table->log_page, table->version); + goto out; + } + + if (!table->num_entries) { + fprintf(stderr, "No entries were found in fw activation history log\n"); + goto out; + } + + micron_fw_activation_history_header_print(); + for (count = 0; count < table->num_entries; count++) { + memset(formatted_output, '\0', 100); + if (!display_fw_activate_entry(count, &table->entries[count], formatted_output, + NULL)) + printf("%s\n", formatted_output); + } +out: + dev_close(dev); + return err; +} + +#define MICRON_FID_LATENCY_MONITOR 0xD0 +#define MICRON_LOG_LATENCY_MONITOR 0xD1 + +static int micron_latency_stats_track(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err = 0; + __u32 result = 0; + const char *desc = "Enable, Disable or Get cmd latency monitoring stats"; + const char *option = "enable or disable or status, default is status"; + const char *command = + "commands to monitor for - all|read|write|trim, default is all i.e, enabled for all commands"; + const char *thrtime = + "The threshold value to use for latency monitoring in milliseconds, default is 800ms"; + + int fid = MICRON_FID_LATENCY_MONITOR; + enum eDriveModel model = UNKNOWN_MODEL; + uint32_t command_mask = 0x7; /* 1:read 2:write 4:trim 7:all */ + uint32_t timing_mask = 0x08080800; /* R[31-24]:W[23:16]:T[15:8]:0 */ + uint32_t enable = 2; + struct nvme_dev *dev; + struct { + char *option; + char *command; + uint32_t threshold; + } opt = { + .option = "status", + .command = "all", + .threshold = 0 + }; + + OPT_ARGS(opts) = { + OPT_STRING("option", 'o', "option", &opt.option, option), + OPT_STRING("command", 'c', "command", &opt.command, command), + OPT_UINT("threshold", 't', &opt.threshold, thrtime), + OPT_END() + }; + + + err = micron_parse_options(&dev, argc, argv, desc, opts, &model); + if (err < 0) + return -1; + + if (!strcmp(opt.option, "enable")) { + enable = 1; + } else if (!strcmp(opt.option, "disable")) { + enable = 0; + } else if (strcmp(opt.option, "status")) { + printf("Invalid control option %s specified\n", opt.option); + dev_close(dev); + return -1; + } + + struct nvme_get_features_args g_args = { + .args_size = sizeof(g_args), + .fd = dev_fd(dev), + .fid = fid, + .nsid = 0, + .sel = 0, + .cdw11 = 0, + .uuidx = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + err = nvme_get_features(&g_args); + if (err) { + printf("Failed to retrieve latency monitoring feature status\n"); + dev_close(dev); + return err; + } + + /* If it is to retrieve the status only */ + if (enable == 2) { + printf("Latency Tracking Statistics is currently %s", + (result & 0xFFFF0000) ? "enabled" : "disabled"); + if ((result & 7) == 7) { + printf(" for All commands\n"); + } else if ((result & 7) > 0) { + printf(" for"); + if (result & 1) + printf(" Read"); + if (result & 2) + printf(" Write"); + if (result & 4) + printf(" Trim"); + printf(" commands\n"); + } else if (!result) { + printf("\n"); + } + dev_close(dev); + return err; + } + + /* read and validate threshold values if enable option is specified */ + if (enable == 1) { + if (opt.threshold > 2550) { + printf("The maximum threshold value cannot be more than 2550 ms\n"); + dev_close(dev); + return -1; + } else if (opt.threshold % 10) { + /* timing mask is in terms of 10ms units, so min allowed is 10ms */ + printf("The threshold value should be multiple of 10 ms\n"); + dev_close(dev); + return -1; + } + opt.threshold /= 10; + } + + /* read-in command(s) to be monitored */ + if (!strcmp(opt.command, "read")) { + command_mask = 0x1; + timing_mask = (opt.threshold << 24); + } else if (!strcmp(opt.command, "write")) { + command_mask = 0x2; + timing_mask = (opt.threshold << 16); + } else if (!strcmp(opt.command, "trim")) { + command_mask = 0x4; + timing_mask = (opt.threshold << 8); + } else if (strcmp(opt.command, "all")) { + printf("Invalid command %s specified for option %s\n", + opt.command, opt.option); + dev_close(dev); + return -1; + } + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = MICRON_FID_LATENCY_MONITOR, + .nsid = 0, + .cdw11 = enable, + .cdw12 = command_mask, + .save = 1, + .uuidx = 0, + .cdw13 = timing_mask, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_set_features(&args); + if (!err) { + printf("Successfully %sd latency monitoring for %s commands with %dms threshold\n", + opt.option, opt.command, !opt.threshold ? 800 : opt.threshold * 10); + } else { + printf("Failed to %s latency monitoring for %s commands with %dms threshold\n", + opt.option, opt.command, !opt.threshold ? 800 : opt.threshold * 10); + } + + dev_close(dev); + return err; +} + + +static int micron_latency_stats_logs(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ +#define LATENCY_LOG_ENTRIES 16 + struct latency_log_entry { + uint64_t timestamp; + uint32_t latency; + uint32_t cmdtag; + union { + struct { + uint32_t opcode:8; + uint32_t fuse:2; + uint32_t rsvd1:4; + uint32_t psdt:2; + uint32_t cid:16; + }; + uint32_t dw0; + }; + uint32_t nsid; + uint32_t slba_low; + uint32_t slba_high; + union { + struct { + uint32_t nlb:16; + uint32_t rsvd2:9; + uint32_t deac:1; + uint32_t prinfo:4; + uint32_t fua:1; + uint32_t lr:1; + }; + uint32_t dw12; + }; + uint32_t dsm; + uint32_t rfu[6]; + } log[LATENCY_LOG_ENTRIES]; + enum eDriveModel model = UNKNOWN_MODEL; + struct nvme_dev *dev; + int err = -1; + const char *desc = "Display Latency tracking log information"; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = micron_parse_options(&dev, argc, argv, desc, opts, &model); + if (err) + return err; + memset(&log, 0, sizeof(log)); + err = nvme_get_log_simple(dev_fd(dev), 0xD1, sizeof(log), &log); + if (err) { + if (err < 0) + printf("Unable to retrieve latency stats log the drive\n"); + dev_close(dev); + return err; + } + /* print header and each log entry */ + printf("Timestamp, Latency, CmdTag, Opcode, Fuse, Psdt, Cid, Nsid, Slba_L, Slba_H, Nlb, "); + printf("DEAC, PRINFO, FUA, LR\n"); + for (int i = 0; i < LATENCY_LOG_ENTRIES; i++) + printf("%"PRIu64",%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u\n", + log[i].timestamp, log[i].latency, log[i].cmdtag, log[i].opcode, + log[i].fuse, log[i].psdt, log[i].cid, log[i].nsid, + log[i].slba_low, log[i].slba_high, log[i].nlb, + log[i].deac, log[i].prinfo, log[i].fua, log[i].lr); + printf("\n"); + dev_close(dev); + return err; +} + +static int micron_latency_stats_info(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "display command latency statistics"; + const char *command = "command to display stats - all|read|write|trimdefault is all"; + int err = 0; + struct nvme_dev *dev; + enum eDriveModel model = UNKNOWN_MODEL; + #define LATENCY_BUCKET_COUNT 32 + #define LATENCY_BUCKET_RSVD 32 + struct micron_latency_stats { + uint64_t version; /* major << 32 | minior */ + uint64_t all_cmds[LATENCY_BUCKET_COUNT + LATENCY_BUCKET_RSVD]; + uint64_t read_cmds[LATENCY_BUCKET_COUNT + LATENCY_BUCKET_RSVD]; + uint64_t write_cmds[LATENCY_BUCKET_COUNT + LATENCY_BUCKET_RSVD]; + uint64_t trim_cmds[LATENCY_BUCKET_COUNT + LATENCY_BUCKET_RSVD]; + uint32_t reserved[255]; /* round up to 4K */ + } log; + + struct latency_thresholds { + uint32_t start; + uint32_t end; + char *unit; + } thresholds[LATENCY_BUCKET_COUNT] = { + {0, 50, "us"}, {50, 100, "us"}, {100, 150, "us"}, {150, 200, "us"}, + {200, 300, "us"}, {300, 400, "us"}, {400, 500, "us"}, {500, 600, "us"}, + {600, 700, "us"}, {700, 800, "us"}, {800, 900, "us"}, {900, 1000, "us"}, + {1, 5, "ms"}, {5, 10, "ms"}, {10, 20, "ms"}, {20, 50, "ms"}, {50, 100, "ms"}, + {100, 200, "ms"}, {200, 300, "ms"}, {300, 400, "ms"}, {400, 500, "ms"}, + {500, 600, "ms"}, {600, 700, "ms"}, {700, 800, "ms"}, {800, 900, "ms"}, + {900, 1000, "ms"}, {1, 2, "s"}, {2, 3, "s"}, {3, 4, "s"}, {4, 5, "s"}, + {5, 8, "s"}, + {8, INT_MAX, "s"}, + }; + + struct { + char *command; + } opt = { + .command = "all" + }; + + uint64_t *cmd_stats = &log.all_cmds[0]; + char *cmd_str = "All"; + + OPT_ARGS(opts) = { + OPT_STRING("command", 'c', "command", &opt.command, command), + OPT_END() + }; + + err = micron_parse_options(&dev, argc, argv, desc, opts, &model); + if (err < 0) + return err; + if (!strcmp(opt.command, "read")) { + cmd_stats = &log.read_cmds[0]; + cmd_str = "Read"; + } else if (!strcmp(opt.command, "write")) { + cmd_stats = &log.write_cmds[0]; + cmd_str = "Write"; + } else if (!strcmp(opt.command, "trim")) { + cmd_stats = &log.trim_cmds[0]; + cmd_str = "Trim"; + } else if (strcmp(opt.command, "all")) { + printf("Invalid command option %s to display latency stats\n", opt.command); + dev_close(dev); + return -1; + } + + memset(&log, 0, sizeof(log)); + err = nvme_get_log_simple(dev_fd(dev), 0xD0, sizeof(log), &log); + if (err) { + if (err < 0) + printf("Unable to retrieve latency stats log the drive\n"); + dev_close(dev); + return err; + } + printf("Micron IO %s Command Latency Statistics\n" + "Major Revision : %d\nMinor Revision : %d\n", + cmd_str, (int)(log.version >> 32), (int)(log.version & 0xFFFFFFFF)); + printf("=============================================\n"); + printf("Bucket Start End Command Count\n"); + printf("=============================================\n"); + + for (int b = 0; b < LATENCY_BUCKET_COUNT; b++) { + int bucket = b + 1; + char start[32] = { 0 }; + char end[32] = { 0 }; + + sprintf(start, "%u%s", thresholds[b].start, thresholds[b].unit); + if (thresholds[b].end == INT_MAX) + sprintf(end, "INF"); + else + sprintf(end, "%u%s", thresholds[b].end, thresholds[b].unit); + printf("%2d %8s %8s %8"PRIu64"\n", bucket, start, end, cmd_stats[b]); + } + dev_close(dev); + return err; +} + +static int micron_ocp_smart_health_logs(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "Retrieve Smart or Extended Smart Health log for the given device "; + unsigned int logC0[C0_log_size/sizeof(int)] = { 0 }; + unsigned int logFB[FB_log_size/sizeof(int)] = { 0 }; + struct nvme_id_ctrl ctrl; + enum eDriveModel eModel = UNKNOWN_MODEL; + struct nvme_dev *dev; + bool is_json = true; + struct format { + char *fmt; + }; + const char *fmt = "output format normal|json"; + struct format cfg = { + .fmt = "json", + }; + int err = 0; + + OPT_ARGS(opts) = { + OPT_FMT("format", 'f', &cfg.fmt, fmt), + OPT_END() + }; + + err = micron_parse_options(&dev, argc, argv, desc, opts, &eModel); + if (err < 0) + return -1; + + if (!strcmp(cfg.fmt, "normal")) + is_json = false; + + /* For M5410 and M5407, this option prints 0xFB log page */ + if (eModel == M5410 || eModel == M5407) { + __u8 spec = (eModel == M5410) ? 0 : 1; + __u8 nsze; + + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (!err) + err = nvme_get_log_simple(dev_fd(dev), 0xFB, FB_log_size, logFB); + if (err) { + if (err < 0) + printf("Unable to retrieve smart log 0xFB for the drive\n"); + goto out; + } + + nsze = (ctrl.vs[987] == 0x12); + if (!nsze && nsze_from_oacs) + nsze = ((ctrl.oacs >> 3) & 0x1); + print_nand_stats_fb((__u8 *)logFB, NULL, nsze, is_json, spec); + goto out; + } + + /* check for models that support 0xC0 log */ + if (eModel != M51CX) { + printf("Unsupported drive model for vs-smart-add-log command\n"); + err = -1; + goto out; + } + + err = nvme_get_log_simple(dev_fd(dev), 0xC0, C0_log_size, logC0); + if (!err) + print_smart_cloud_health_log((__u8 *)logC0, is_json); + else if (err < 0) + printf("Unable to retrieve extended smart log 0xC0 for the drive\n"); +out: + dev_close(dev); + if (err > 0) + nvme_show_status(err); + return err; +} + +static int micron_clr_fw_activation_history(int argc, char **argv, + struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Clear FW activation history"; + __u32 result = 0; + __u8 fid = MICRON_FEATURE_CLEAR_FW_ACTIVATION_HISTORY; + enum eDriveModel model = UNKNOWN_MODEL; + struct nvme_dev *dev; + + OPT_ARGS(opts) = { + OPT_END() + }; + int err = 0; + + err = micron_parse_options(&dev, argc, argv, desc, opts, &model); + if (err < 0) + return err; + + if (model != M51CX) { + printf("This option is not supported for specified drive\n"); + dev_close(dev); + return err; + } + + err = nvme_set_features_simple(dev_fd(dev), fid, 1 << 31, 0, 0, &result); + if (!err) + err = (int)result; + else + printf("Failed to clear fw activation history, error = 0x%x\n", err); + + dev_close(dev); + return err; +} + +static int micron_telemetry_cntrl_option(int argc, char **argv, + struct command *cmd, struct plugin *plugin) +{ + int err = 0; + __u32 result = 0; + const char *desc = "Enable or Disable Controller telemetry log generation"; + const char *option = "enable or disable or status"; + const char *select = + "select/save values: enable/disable options1 - save (persistent), 0 - non-persistent and for status options: 0 - current, 1 - default, 2-saved"; + int fid = MICRON_FEATURE_TELEMETRY_CONTROL_OPTION; + enum eDriveModel model = UNKNOWN_MODEL; + struct nvme_id_ctrl ctrl = { 0 }; + struct nvme_dev *dev; + + struct { + char *option; + int select; + } opt = { + .option = "disable", + .select = 0, + }; + + OPT_ARGS(opts) = { + OPT_STRING("option", 'o', "option", &opt.option, option), + OPT_UINT("select", 's', &opt.select, select), + OPT_END() + }; + + err = micron_parse_options(&dev, argc, argv, desc, opts, &model); + if (err < 0) + return -1; + + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if ((ctrl.lpa & 0x8) != 0x8) { + printf("drive doesn't support host/controller generated telemetry logs\n"); + dev_close(dev); + return err; + } + + if (!strcmp(opt.option, "enable")) { + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = fid, + .nsid = 1, + .cdw11 = 1, + .cdw12 = 0, + .save = (opt.select & 0x1), + .uuidx = 0, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_set_features(&args); + if (!err) + printf("successfully set controller telemetry option\n"); + else + printf("Failed to set controller telemetry option\n"); + } else if (!strcmp(opt.option, "disable")) { + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = fid, + .nsid = 1, + .cdw11 = 0, + .cdw12 = 0, + .save = (opt.select & 0x1), + .uuidx = 0, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_set_features(&args); + if (!err) + printf("successfully disabled controller telemetry option\n"); + else + printf("Failed to disable controller telemetry option\n"); + } else if (!strcmp(opt.option, "status")) { + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = fid, + .nsid = 1, + .sel = opt.select & 0x3, + .cdw11 = 0, + .uuidx = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + err = nvme_get_features(&args); + if (!err) + printf("Controller telemetry option : %s\n", + (result) ? "enabled" : "disabled"); + else + printf("Failed to retrieve controller telemetry option\n"); + } else { + printf("invalid option %s, valid values are enable,disable or status\n", + opt.option); + dev_close(dev); + return -1; + } + + dev_close(dev); + return err; +} + +/* M51XX models log page header */ +struct micron_common_log_header { + uint8_t id; + uint8_t version; + uint16_t pn; + uint32_t log_size; + uint32_t max_size; + uint32_t write_pointer; + uint32_t next_pointer; + uint32_t overwritten_bytes; + uint8_t flags; + uint8_t reserved[7]; +}; + +/* helper function to retrieve logs with specific offset and max chunk size */ +int nvme_get_log_lpo(int fd, __u8 log_id, __u32 lpo, __u32 chunk, + __u32 data_len, void *data) +{ + __u32 offset = lpo, xfer_len = data_len; + void *ptr = data; + struct nvme_get_log_args args = { + .lpo = offset, + .result = NULL, + .log = ptr, + .args_size = sizeof(args), + .fd = fd, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .lid = log_id, + .len = xfer_len, + .nsid = NVME_NSID_ALL, + .csi = NVME_CSI_NVM, + .lsi = NVME_LOG_LSI_NONE, + .lsp = NVME_LOG_LSP_NONE, + .uuidx = NVME_UUID_NONE, + .rae = false, + .ot = false, + }; + int ret = 0; + + /* divide data into multiple chunks */ + do { + xfer_len = data_len - offset; + if (xfer_len > chunk) + xfer_len = chunk; + + args.lpo = offset; + args.log = ptr; + args.len = xfer_len; + ret = nvme_get_log(&args); + if (ret) + return ret; + offset += xfer_len; + ptr += xfer_len; + } while (offset < data_len); + return ret; +} + +/* retrieves logs with common log format */ +static int get_common_log(int fd, uint8_t id, uint8_t **buf, int *size) +{ + struct micron_common_log_header hdr = { 0 }; + int log_size = sizeof(hdr), first = 0, second = 0; + uint8_t *buffer = NULL; + int ret = -1; + int chunk = 0x4000; /* max chunk size to be used for these logs */ + + ret = nvme_get_log_simple(fd, id, sizeof(hdr), &hdr); + if (ret) { + fprintf(stderr, "pull hdr failed for %u with error: 0x%x\n", id, ret); + return ret; + } + + if (hdr.id != id || !hdr.log_size || !hdr.max_size || + hdr.write_pointer < sizeof(hdr)) { + fprintf(stderr, + "invalid log data for LOG: 0x%X, id: 0x%X, size: %u, max: %u, wp: %u, flags: %u, np: %u\n", + id, hdr.id, hdr.log_size, hdr.max_size, hdr.write_pointer, hdr.flags, + hdr.next_pointer); + return 1; + } + + /* + * we may have just 32-bytes for some models; write to wfile if log hasn't + * yet reached its max size + */ + if (hdr.log_size == sizeof(hdr)) { + buffer = (uint8_t *)malloc(sizeof(hdr)); + if (!buffer) { + fprintf(stderr, "malloc of %zu bytes failed for log: 0x%X\n", + sizeof(hdr), id); + return -ENOMEM; + } + memcpy(buffer, (uint8_t *)&hdr, sizeof(hdr)); + } else if (hdr.log_size < hdr.max_size) { + buffer = (uint8_t *)malloc(sizeof(hdr) + hdr.log_size); + if (!buffer) { + fprintf(stderr, "malloc of %zu bytes failed for log: 0x%X\n", + hdr.log_size + sizeof(hdr), id); + return -ENOMEM; + } + memcpy(buffer, &hdr, sizeof(hdr)); + ret = nvme_get_log_lpo(fd, id, sizeof(hdr), chunk, hdr.log_size, + buffer + sizeof(hdr)); + if (!ret) + log_size += hdr.log_size; + } else if (hdr.log_size >= hdr.max_size) { + /* + * reached maximum, to maintain, sequence we need to depend on write + * pointer to detect wrap-overs. FW doesn't yet implement the condition + * hdr.log_size > hdr.max_size; also ignore over-written log data; we + * also ignore collisions for now + */ + buffer = (uint8_t *)malloc(hdr.max_size + sizeof(hdr)); + if (!buffer) { + fprintf(stderr, "malloc of %zu bytes failed for log: 0x%X\n", + hdr.max_size + sizeof(hdr), id); + return -ENOMEM; + } + memcpy(buffer, &hdr, sizeof(hdr)); + + first = hdr.max_size - hdr.write_pointer; + second = hdr.write_pointer - sizeof(hdr); + + if (first) { + ret = nvme_get_log_lpo(fd, id, hdr.write_pointer, chunk, first, + buffer + sizeof(hdr)); + if (ret) { + free(buffer); + fprintf(stderr, "failed to get log: 0x%X\n", id); + return ret; + } + log_size += first; + } + if (second) { + ret = nvme_get_log_lpo(fd, id, sizeof(hdr), chunk, second, + buffer + sizeof(hdr) + first); + if (ret) { + fprintf(stderr, "failed to get log: 0x%X\n", id); + free(buffer); + return ret; + } + log_size += second; + } + } + *buf = buffer; + *size = log_size; + return ret; +} + +static int micron_internal_logs(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err = -EINVAL; + int ctrlIdx, telemetry_option = 0; + char strOSDirName[1024]; + char strCtrlDirName[1024]; + char strMainDirName[256]; + unsigned int *puiIDDBuf; + unsigned int uiMask; + struct nvme_id_ctrl ctrl; + char sn[20] = { 0 }; + char msg[256] = { 0 }; + int c_logs_index = 8; /* should be current size of aVendorLogs */ + struct nvme_dev *dev; + struct { + unsigned char ucLogPage; + const char *strFileName; + int nLogSize; + int nMaxSize; + } aVendorLogs[32] = { + { 0x03, "firmware_slot_info_log.bin", 512, 0 }, + { 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 */ + { 0xD0, "nvmelog_D0.bin", 512, 0 }, + { 0x03, "firmware_slot_info_log.bin", 512, 0}, + { 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[] = { + { 0xCA, "nvmelog_CA.bin", 512, 0 }, + { 0xFA, "nvmelog_FA.bin", 4096, 15232 }, + { 0xF6, "nvmelog_F6.bin", 4096, 512 * 1024 }, + { 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 } + }, + aM51CXLogs[] = { + { 0xE1, "nvmelog_E1.bin", 0, 0 }, + { 0xE2, "nvmelog_E2.bin", 0, 0 }, + { 0xE3, "nvmelog_E3.bin", 0, 0 }, + { 0xE4, "nvmelog_E4.bin", 0, 0 }, + { 0xE5, "nvmelog_E5.bin", 0, 0 }, + { 0xE8, "nvmelog_E8.bin", 0, 0 }, + { 0xE9, "nvmelog_E9.bin", 0, 0 }, + { 0xEA, "nvmelog_EA.bin", 0, 0 }, + }; + + enum eDriveModel eModel; + + const char *desc = "This retrieves the micron debug log package"; + const char *package = "Log output data file name (required)"; + const char *type = "telemetry log type - host or controller"; + const char *data_area = "telemetry log data area 1, 2 or 3"; + unsigned char *dataBuffer = NULL; + int bSize = 0; + int maxSize = 0; + + struct config { + char *type; + char *package; + int data_area; + int log; + }; + + struct config cfg = { + .type = "", + .package = "", + .data_area = -1, + .log = 0x07, + }; + + OPT_ARGS(opts) = { + OPT_STRING("type", 't', "log type", &cfg.type, type), + OPT_STRING("package", 'p', "FILE", &cfg.package, package), + OPT_UINT("data_area", 'd', &cfg.data_area, data_area), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + /* if telemetry type is specified, check for data area */ + if (strlen(cfg.type)) { + if (!strcmp(cfg.type, "controller")) { + cfg.log = 0x08; + } else if (strcmp(cfg.type, "host")) { + printf("telemetry type (host or controller) should be specified i.e. -t=host\n"); + goto out; + } + + if (cfg.data_area <= 0 || cfg.data_area > 3) { + printf("data area must be selected using -d option ie --d=1,2,3\n"); + goto out; + } + telemetry_option = 1; + } else if (cfg.data_area > 0) { + printf("data area option is valid only for telemetry option (i.e --type=host|controller)\n"); + goto out; + } + + if (!strlen(cfg.package)) { + if (telemetry_option) + printf("Log data file must be specified. ie -p=logfile.bin\n"); + else + printf("Log data file must be specified. ie -p=logfile.zip or -p=logfile.tgz|logfile.tar.gz\n"); + goto out; + } + + /* pull log details based on the model name */ + if (sscanf(argv[optind], "/dev/nvme%d", &ctrlIdx) != 1) + ctrlIdx = 0; + eModel = GetDriveModel(ctrlIdx); + if (eModel == UNKNOWN_MODEL) { + printf("Unsupported drive model for vs-internal-log collection\n"); + goto out; + } + + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (err) + goto out; + + err = -EINVAL; + if (telemetry_option) { + if ((ctrl.lpa & 0x8) != 0x8) { + printf("telemetry option is not supported for specified drive\n"); + goto out; + } + int logSize = 0; __u8 *buffer = NULL; const char *dir = "."; + + err = micron_telemetry_log(dev_fd(dev), cfg.log, &buffer, &logSize, + cfg.data_area); + if (!err && logSize > 0 && buffer) { + sprintf(msg, "telemetry log: 0x%X", cfg.log); + WriteData(buffer, logSize, dir, cfg.package, msg); + free(buffer); + } + goto out; + } + + printf("Preparing log package. This will take a few seconds...\n"); + + /* trim spaces out of serial number string */ + int i, j = 0; + + for (i = 0; i < sizeof(ctrl.sn); i++) { + if (isblank((int)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(dev_fd(dev), strCtrlDirName, i); + + GetSmartlogData(dev_fd(dev), strCtrlDirName); + GetErrorlogData(dev_fd(dev), ctrl.elpe, strCtrlDirName); + GetGenericLogs(dev_fd(dev), strCtrlDirName); + /* pull if telemetry log data is supported */ + if ((ctrl.lpa & 0x8) == 0x8) + GetTelemetryData(dev_fd(dev), strCtrlDirName); + + GetFeatureSettings(dev_fd(dev), strCtrlDirName); + + if (eModel != M5410 && eModel != M5407) { + memcpy(&aVendorLogs[c_logs_index], aM51XXLogs, sizeof(aM51XXLogs)); + c_logs_index += ARRAY_SIZE(aM51XXLogs); + if (eModel == M51AX) + memcpy((char *)&aVendorLogs[c_logs_index], aM51AXLogs, sizeof(aM51AXLogs)); + else if (eModel == M51BX) + memcpy((char *)&aVendorLogs[c_logs_index], aM51BXLogs, sizeof(aM51BXLogs)); + else if (eModel == M51CX) + memcpy((char *)&aVendorLogs[c_logs_index], aM51CXLogs, sizeof(aM51CXLogs)); + } + + for (int i = 0; i < (int)(ARRAY_SIZE(aVendorLogs)) && aVendorLogs[i].ucLogPage; i++) { + err = -1; + switch (aVendorLogs[i].ucLogPage) { + case 0xE1: + fallthrough; + case 0xE5: + fallthrough; + case 0xE9: + err = 1; + break; + case 0xE2: + fallthrough; + case 0xE3: + fallthrough; + case 0xE4: + fallthrough; + case 0xE8: + fallthrough; + case 0xEA: + err = get_common_log(dev_fd(dev), aVendorLogs[i].ucLogPage, + &dataBuffer, &bSize); + break; + case 0xC1: + fallthrough; + case 0xC2: + fallthrough; + case 0xC4: + err = GetLogPageSize(dev_fd(dev), aVendorLogs[i].ucLogPage, + &bSize); + if (!err && bSize > 0) + err = GetCommonLogPage(dev_fd(dev), aVendorLogs[i].ucLogPage, + &dataBuffer, bSize); + break; + case 0xE6: + fallthrough; + case 0xE7: + puiIDDBuf = (unsigned int *)&ctrl; + uiMask = puiIDDBuf[1015]; + if (!uiMask || (aVendorLogs[i].ucLogPage == 0xE6 && uiMask == 2) || + (aVendorLogs[i].ucLogPage == 0xE7 && uiMask == 1)) { + bSize = 0; + } else { + bSize = (int)puiIDDBuf[1023]; + if (bSize % (16 * 1024)) + bSize += (16 * 1024) - (bSize % (16 * 1024)); + } + dataBuffer = (unsigned char *)malloc(bSize); + if (bSize && dataBuffer) { + memset(dataBuffer, 0, bSize); + if (eModel == M5410 || eModel == M5407) + err = NVMEGetLogPage(dev_fd(dev), + aVendorLogs[i].ucLogPage, dataBuffer, + bSize); + else + err = nvme_get_log_simple(dev_fd(dev), + aVendorLogs[i].ucLogPage, + bSize, dataBuffer); + } + break; + case 0xF7: + fallthrough; + case 0xF9: + fallthrough; + case 0xFC: + fallthrough; + case 0xFD: + if (eModel == M51BX) + (void)NVMEResetLog(dev_fd(dev), aVendorLogs[i].ucLogPage, + aVendorLogs[i].nLogSize, aVendorLogs[i].nMaxSize); + fallthrough; + default: + bSize = aVendorLogs[i].nLogSize; + dataBuffer = (unsigned char *)malloc(bSize); + if (!dataBuffer) + break; + memset(dataBuffer, 0, bSize); + err = nvme_get_log_simple(dev_fd(dev), aVendorLogs[i].ucLogPage, + bSize, dataBuffer); + maxSize = aVendorLogs[i].nMaxSize - bSize; + while (!err && maxSize > 0 && ((unsigned int *)dataBuffer)[0] != 0xdeadbeef) { + sprintf(msg, "log 0x%x", aVendorLogs[i].ucLogPage); + WriteData(dataBuffer, bSize, strCtrlDirName, aVendorLogs[i].strFileName, msg); + err = nvme_get_log_simple(dev_fd(dev), + aVendorLogs[i].ucLogPage, + bSize, dataBuffer); + if (err || (((unsigned int *)dataBuffer)[0] == 0xdeadbeef)) + break; + maxSize -= bSize; + } + break; + } + + if (!err && dataBuffer && ((unsigned int *)dataBuffer)[0] != 0xdeadbeef) { + sprintf(msg, "log 0x%x", aVendorLogs[i].ucLogPage); + WriteData(dataBuffer, bSize, strCtrlDirName, aVendorLogs[i].strFileName, msg); + } + + if (dataBuffer) { + free(dataBuffer); + dataBuffer = NULL; + } + } + + err = ZipAndRemoveDir(strMainDirName, cfg.package); +out: + dev_close(dev); + return err; +} + +#define MIN_LOG_SIZE 512 +static int micron_logpage_dir(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err = -1; + const char *desc = "List the supported log pages"; + enum eDriveModel model = UNKNOWN_MODEL; + char logbuf[MIN_LOG_SIZE]; + struct nvme_dev *dev; + int i; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = micron_parse_options(&dev, argc, argv, desc, opts, &model); + if (err < 0) + return err; + + struct nvme_supported_logs { + uint8_t log_id; + uint8_t supported; + char *desc; + } log_list[] = { + {0x00, 0, "Support Log Pages"}, + {0x01, 0, "Error Information"}, + {0x02, 0, "SMART / Health Information"}, + {0x03, 0, "Firmware Slot Information"}, + {0x04, 0, "Changed Namespace List"}, + {0x05, 0, "Commands Supported and Effects"}, + {0x06, 0, "Device Self Test"}, + {0x07, 0, "Telemetry Host-Initiated"}, + {0x08, 0, "Telemetry Controller-Initiated"}, + {0x09, 0, "Endurance Group Information"}, + {0x0A, 0, "Predictable Latency Per NVM Set"}, + {0x0B, 0, "Predictable Latency Event Aggregate"}, + {0x0C, 0, "Asymmetric Namespace Access"}, + {0x0D, 0, "Persistent Event Log"}, + {0x0E, 0, "Predictable Latency Event Aggregate"}, + {0x0F, 0, "Endurance Group Event Aggregate"}, + {0x10, 0, "Media Unit Status"}, + {0x11, 0, "Supported Capacity Configuration List"}, + {0x12, 0, "Feature Identifiers Supported and Effects"}, + {0x13, 0, "NVMe-MI Commands Supported and Effects"}, + {0x14, 0, "Command and Feature lockdown"}, + {0x15, 0, "Boot Partition"}, + {0x16, 0, "Rotational Media Information"}, + {0x70, 0, "Discovery"}, + {0x80, 0, "Reservation Notification"}, + {0x81, 0, "Sanitize Status"}, + {0xC0, 0, "SMART Cloud Health Log"}, + {0xC2, 0, "Firmware Activation History"}, + {0xC3, 0, "Latency Monitor Log"}, + }; + + printf("Supported log page list\nLog ID : Description\n"); + for (i = 0; i < ARRAY_SIZE(log_list); i++) { + err = nvme_get_log_simple(dev_fd(dev), log_list[i].log_id, + MIN_LOG_SIZE, &logbuf[0]); + if (err) + continue; + printf("%02Xh : %s\n", log_list[i].log_id, log_list[i].desc); + } + + return err; +} diff --git a/plugins/micron/micron-nvme.h b/plugins/micron/micron-nvme.h new file mode 100644 index 0000000..4f7b892 --- /dev/null +++ b/plugins/micron/micron-nvme.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#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", NVME_VERSION), + 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-telemetry-controller-option", "Enable/Disable controller telemetry log generation", micron_telemetry_cntrl_option) + ENTRY("vs-nand-stats", "Retrieve NAND Stats", micron_nand_stats) + ENTRY("vs-smart-ext-log", "Retrieve extended SMART logs", micron_smart_ext_log) + ENTRY("vs-drive-info", "Retrieve Drive information", micron_drive_info) + ENTRY("plugin-version", "Display plugin version info", micron_plugin_version) + ENTRY("cloud-SSD-plugin-version", "Display plugin version info", micron_cloud_ssd_plugin_version) + ENTRY("log-page-directory", "Retrieve log page directory", micron_logpage_dir) + ENTRY("vs-fw-activate-history", "Display FW activation history", micron_fw_activation_history) + ENTRY("latency-tracking", "Latency monitoring feature control", micron_latency_stats_track) + ENTRY("latency-stats", "Latency information for tracked commands", micron_latency_stats_info) + ENTRY("latency-logs", "Latency log details tracked by drive", micron_latency_stats_logs) + ENTRY("vs-smart-add-log", "Retrieve extended SMART data", micron_ocp_smart_health_logs) + ENTRY("clear-fw-activate-history", "Clear FW activation history", micron_clr_fw_activation_history) + ENTRY("vs-smbus-option", "Enable/Disable SMBUS on the drive", micron_smbus_option) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/nbft/nbft-plugin.c b/plugins/nbft/nbft-plugin.c new file mode 100644 index 0000000..2193ffb --- /dev/null +++ b/plugins/nbft/nbft-plugin.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <errno.h> +#include <stdio.h> +#include <fnmatch.h> + +#include "nvme-print.h" +#include "nvme.h" +#include "nbft.h" +#include "libnvme.h" +#include "fabrics.h" + +#define CREATE_CMD +#include "nbft-plugin.h" + +static const char dash[100] = {[0 ... 98] = '-', [99] = '\0'}; + +#define PCI_SEGMENT(sbdf) ((sbdf & 0xffff0000) >> 16) +#define PCI_BUS(sbdf) ((sbdf & 0x0000ff00) >> 8) +#define PCI_DEV(sbdf) ((sbdf & 0x000000f8) >> 3) +#define PCI_FUNC(sbdf) ((sbdf & 0x00000007) >> 0) + +static const char *pci_sbdf_to_string(__u16 pci_sbdf) +{ + static char pcidev[13]; + + snprintf(pcidev, sizeof(pcidev), "%x:%x:%x.%x", + PCI_SEGMENT(pci_sbdf), + PCI_BUS(pci_sbdf), + PCI_DEV(pci_sbdf), + PCI_FUNC(pci_sbdf)); + return pcidev; +} + +static char *mac_addr_to_string(unsigned char mac_addr[6]) +{ + static char mac_string[18]; + + snprintf(mac_string, sizeof(mac_string), "%02x:%02x:%02x:%02x:%02x:%02x", + mac_addr[0], + mac_addr[1], + mac_addr[2], + mac_addr[3], + mac_addr[4], + mac_addr[5]); + return mac_string; +} + +static json_object *hfi_to_json(struct nbft_info_hfi *hfi) +{ + struct json_object *hfi_json; + + hfi_json = json_create_object(); + if (!hfi_json) + return NULL; + + if (json_object_add_value_int(hfi_json, "index", hfi->index) + || json_object_add_value_string(hfi_json, "transport", hfi->transport)) + goto fail; + + if (strcmp(hfi->transport, "tcp") == 0) { + if (json_object_add_value_string(hfi_json, "pcidev", + pci_sbdf_to_string(hfi->tcp_info.pci_sbdf)) + || json_object_add_value_string(hfi_json, "mac_addr", + mac_addr_to_string(hfi->tcp_info.mac_addr)) + || json_object_add_value_int(hfi_json, "vlan", + hfi->tcp_info.vlan) + || json_object_add_value_int(hfi_json, "ip_origin", + hfi->tcp_info.ip_origin) + || json_object_add_value_string(hfi_json, "ipaddr", + hfi->tcp_info.ipaddr) + || json_object_add_value_int(hfi_json, "subnet_mask_prefix", + hfi->tcp_info.subnet_mask_prefix) + || json_object_add_value_string(hfi_json, "gateway_ipaddr", + hfi->tcp_info.gateway_ipaddr) + || json_object_add_value_int(hfi_json, "route_metric", + hfi->tcp_info.route_metric) + || json_object_add_value_string(hfi_json, "primary_dns_ipaddr", + hfi->tcp_info.primary_dns_ipaddr) + || json_object_add_value_string(hfi_json, "secondary_dns_ipaddr", + hfi->tcp_info.secondary_dns_ipaddr) + || json_object_add_value_string(hfi_json, "dhcp_server_ipaddr", + hfi->tcp_info.dhcp_server_ipaddr) + || (hfi->tcp_info.host_name + && json_object_add_value_string(hfi_json, "host_name", + hfi->tcp_info.host_name)) + || json_object_add_value_int(hfi_json, "this_hfi_is_default_route", + hfi->tcp_info.this_hfi_is_default_route) + || json_object_add_value_int(hfi_json, "dhcp_override", + hfi->tcp_info.dhcp_override)) + goto fail; + else + return hfi_json; + } +fail: + json_free_object(hfi_json); + return NULL; +} + +static json_object *ssns_to_json(struct nbft_info_subsystem_ns *ss) +{ + struct json_object *ss_json; + struct json_object *hfi_array_json; + char json_str[40]; + char *json_str_p; + int i; + + ss_json = json_create_object(); + if (!ss_json) + return NULL; + + hfi_array_json = json_create_array(); + if (!hfi_array_json) + goto fail; + + for (i = 0; i < ss->num_hfis; i++) + if (json_array_add_value_object(hfi_array_json, + json_object_new_int(ss->hfis[i]->index))) + goto fail; + + if (json_object_add_value_int(ss_json, "index", ss->index) + || json_object_add_value_int(ss_json, "num_hfis", ss->num_hfis) + || json_object_object_add(ss_json, "hfis", hfi_array_json) + || json_object_add_value_string(ss_json, "transport", ss->transport) + || json_object_add_value_string(ss_json, "traddr", ss->traddr) + || json_object_add_value_string(ss_json, "trsvcid", ss->trsvcid) + || json_object_add_value_int(ss_json, "subsys_port_id", ss->subsys_port_id) + || json_object_add_value_int(ss_json, "nsid", ss->nsid)) + goto fail; + + memset(json_str, 0, sizeof(json_str)); + json_str_p = json_str; + + switch (ss->nid_type) { + case NBFT_INFO_NID_TYPE_EUI64: + if (json_object_add_value_string(ss_json, "nid_type", "eui64")) + goto fail; + for (i = 0; i < 8; i++) + json_str_p += sprintf(json_str_p, "%02x", ss->nid[i]); + break; + + case NBFT_INFO_NID_TYPE_NGUID: + if (json_object_add_value_string(ss_json, "nid_type", "nguid")) + goto fail; + for (i = 0; i < 16; i++) + json_str_p += sprintf(json_str_p, "%02x", ss->nid[i]); + break; + + case NBFT_INFO_NID_TYPE_NS_UUID: + if (json_object_add_value_string(ss_json, "nid_type", "uuid")) + goto fail; + nvme_uuid_to_string(ss->nid, json_str); + break; + + default: + break; + } + if (json_object_add_value_string(ss_json, "nid", json_str)) + goto fail; + + if ((ss->subsys_nqn + && json_object_add_value_string(ss_json, "subsys_nqn", ss->subsys_nqn)) + || json_object_add_value_int(ss_json, "controller_id", ss->controller_id) + || json_object_add_value_int(ss_json, "asqsz", ss->asqsz) + || (ss->dhcp_root_path_string + && json_object_add_value_string(ss_json, "dhcp_root_path_string", + ss->dhcp_root_path_string)) + || json_object_add_value_int(ss_json, "pdu_header_digest_required", + ss->pdu_header_digest_required) + || json_object_add_value_int(ss_json, "data_digest_required", + ss->data_digest_required)) + goto fail; + + return ss_json; +fail: + json_free_object(ss_json); + return NULL; +} + +static json_object *discovery_to_json(struct nbft_info_discovery *disc) +{ + struct json_object *disc_json; + + disc_json = json_create_object(); + if (!disc_json) + return NULL; + + if (json_object_add_value_int(disc_json, "index", disc->index) + || (disc->security + && json_object_add_value_int(disc_json, "security", disc->security->index)) + || (disc->hfi + && json_object_add_value_int(disc_json, "hfi", disc->hfi->index)) + || (disc->uri + && json_object_add_value_string(disc_json, "uri", disc->uri)) + || (disc->nqn + && json_object_add_value_string(disc_json, "nqn", disc->nqn))) { + json_free_object(disc_json); + return NULL; + } else + return disc_json; +} + +static const char *primary_admin_host_flag_to_str(unsigned int primary) +{ + static const char * const str[] = { + [NBFT_INFO_PRIMARY_ADMIN_HOST_FLAG_NOT_INDICATED] = "not indicated", + [NBFT_INFO_PRIMARY_ADMIN_HOST_FLAG_UNSELECTED] = "unselected", + [NBFT_INFO_PRIMARY_ADMIN_HOST_FLAG_SELECTED] = "selected", + [NBFT_INFO_PRIMARY_ADMIN_HOST_FLAG_RESERVED] = "reserved", + }; + + if (primary > NBFT_INFO_PRIMARY_ADMIN_HOST_FLAG_RESERVED) + return "INVALID"; + return str[primary]; +} + +static struct json_object *nbft_to_json(struct nbft_info *nbft, bool show_subsys, + bool show_hfi, bool show_discovery) +{ + struct json_object *nbft_json, *host_json; + + nbft_json = json_create_object(); + if (!nbft_json) + return NULL; + + if (json_object_add_value_string(nbft_json, "filename", nbft->filename)) + goto fail; + + host_json = json_create_object(); + if (!host_json) + goto fail; + if ((nbft->host.nqn + && json_object_add_value_string(host_json, "nqn", nbft->host.nqn)) + || (nbft->host.id + && json_object_add_value_string(host_json, "id", + util_uuid_to_string(nbft->host.id)))) + goto fail; + json_object_add_value_int(host_json, "host_id_configured", + nbft->host.host_id_configured); + json_object_add_value_int(host_json, "host_nqn_configured", + nbft->host.host_nqn_configured); + json_object_add_value_string(host_json, "primary_admin_host_flag", + primary_admin_host_flag_to_str(nbft->host.primary)); + if (json_object_object_add(nbft_json, "host", host_json)) { + json_free_object(host_json); + goto fail; + } + + if (show_subsys) { + struct json_object *subsys_array_json, *subsys_json; + struct nbft_info_subsystem_ns **ss; + + subsys_array_json = json_create_array(); + if (!subsys_array_json) + goto fail; + for (ss = nbft->subsystem_ns_list; ss && *ss; ss++) { + subsys_json = ssns_to_json(*ss); + if (!subsys_json) + goto fail; + if (json_object_array_add(subsys_array_json, subsys_json)) { + json_free_object(subsys_json); + goto fail; + } + } + if (json_object_object_add(nbft_json, "subsystem", subsys_array_json)) { + json_free_object(subsys_array_json); + goto fail; + } + } + if (show_hfi) { + struct json_object *hfi_array_json, *hfi_json; + struct nbft_info_hfi **hfi; + + hfi_array_json = json_create_array(); + if (!hfi_array_json) + goto fail; + for (hfi = nbft->hfi_list; hfi && *hfi; hfi++) { + hfi_json = hfi_to_json(*hfi); + if (!hfi_json) + goto fail; + if (json_object_array_add(hfi_array_json, hfi_json)) { + json_free_object(hfi_json); + goto fail; + } + } + if (json_object_object_add(nbft_json, "hfi", hfi_array_json)) { + json_free_object(hfi_array_json); + goto fail; + } + } + if (show_discovery) { + struct json_object *discovery_array_json, *discovery_json; + struct nbft_info_discovery **disc; + + discovery_array_json = json_create_array(); + if (!discovery_array_json) + goto fail; + for (disc = nbft->discovery_list; disc && *disc; disc++) { + discovery_json = discovery_to_json(*disc); + if (!discovery_json) + goto fail; + if (json_object_array_add(discovery_array_json, discovery_json)) { + json_free_object(discovery_json); + goto fail; + } + } + if (json_object_object_add(nbft_json, "discovery", discovery_array_json)) { + json_free_object(discovery_array_json); + goto fail; + } + } + return nbft_json; +fail: + json_free_object(nbft_json); + return NULL; +} + +static int json_show_nbfts(struct list_head *nbft_list, bool show_subsys, + bool show_hfi, bool show_discovery) +{ + struct json_object *nbft_json_array, *nbft_json; + struct nbft_file_entry *entry; + + nbft_json_array = json_create_array(); + if (!nbft_json_array) + return -ENOMEM; + + list_for_each(nbft_list, entry, node) { + nbft_json = nbft_to_json(entry->nbft, show_subsys, show_hfi, show_discovery); + if (!nbft_json) + goto fail; + if (json_object_array_add(nbft_json_array, nbft_json)) { + json_free_object(nbft_json); + goto fail; + } + } + + json_print_object(nbft_json_array, NULL); + printf("\n"); + json_free_object(nbft_json_array); + return 0; +fail: + json_free_object(nbft_json_array); + return -ENOMEM; +} + +static void print_nbft_hfi_info(struct nbft_info *nbft) +{ + struct nbft_info_hfi **hfi; + unsigned int ip_width = 8, gw_width = 8, dns_width = 8; + + hfi = nbft->hfi_list; + if (!hfi || !*hfi) + return; + + for (; *hfi; hfi++) { + unsigned int len; + + len = strlen((*hfi)->tcp_info.ipaddr); + if (len > ip_width) + ip_width = len; + len = strlen((*hfi)->tcp_info.gateway_ipaddr); + if (len > gw_width) + gw_width = len; + len = strlen((*hfi)->tcp_info.primary_dns_ipaddr); + if (len > dns_width) + dns_width = len; + } + + printf("\nNBFT HFIs:\n\n"); + printf("%-3.3s|%-4.4s|%-10.10s|%-17.17s|%-4.4s|%-*.*s|%-4.4s|%-*.*s|%-*.*s\n", + "Idx", "Trsp", "PCI Addr", "MAC Addr", "DHCP", + ip_width, ip_width, "IP Addr", "Mask", + gw_width, gw_width, "Gateway", dns_width, dns_width, "DNS"); + printf("%-.3s+%-.4s+%-.10s+%-.17s+%-.4s+%-.*s+%-.4s+%-.*s+%-.*s\n", + dash, dash, dash, dash, dash, ip_width, dash, dash, + gw_width, dash, dns_width, dash); + for (hfi = nbft->hfi_list; *hfi; hfi++) + printf("%-3d|%-4.4s|%-10.10s|%-17.17s|%-4.4s|%-*.*s|%-4d|%-*.*s|%-*.*s\n", + (*hfi)->index, + (*hfi)->transport, + pci_sbdf_to_string((*hfi)->tcp_info.pci_sbdf), + mac_addr_to_string((*hfi)->tcp_info.mac_addr), + (*hfi)->tcp_info.dhcp_override ? "yes" : "no", + ip_width, ip_width, (*hfi)->tcp_info.ipaddr, + (*hfi)->tcp_info.subnet_mask_prefix, + gw_width, gw_width, (*hfi)->tcp_info.gateway_ipaddr, + dns_width, dns_width, (*hfi)->tcp_info.primary_dns_ipaddr); +} + +static void print_nbft_discovery_info(struct nbft_info *nbft) +{ + struct nbft_info_discovery **disc; + unsigned int nqn_width = 20, uri_width = 12; + + disc = nbft->discovery_list; + if (!disc || !*disc) + return; + + for (; *disc; disc++) { + size_t len; + + len = strlen((*disc)->uri); + if (len > uri_width) + uri_width = len; + len = strlen((*disc)->nqn); + if (len > nqn_width) + nqn_width = len; + } + + printf("\nNBFT Discovery Controllers:\n\n"); + printf("%-3.3s|%-*.*s|%-*.*s\n", "Idx", uri_width, uri_width, "URI", + nqn_width, nqn_width, "NQN"); + printf("%-.3s+%-.*s+%-.*s\n", dash, uri_width, dash, nqn_width, dash); + for (disc = nbft->discovery_list; *disc; disc++) + printf("%-3d|%-*.*s|%-*.*s\n", (*disc)->index, + uri_width, uri_width, (*disc)->uri, + nqn_width, nqn_width, (*disc)->nqn); +} + +#define HFIS_LEN 20 +static size_t print_hfis(const struct nbft_info_subsystem_ns *ss, char buf[HFIS_LEN]) +{ + char hfi_buf[HFIS_LEN]; + size_t len, ofs; + int i; + + len = snprintf(hfi_buf, sizeof(hfi_buf), "%d", ss->hfis[0]->index); + for (i = 1; i < ss->num_hfis; i++) { + ofs = len; + len += snprintf(hfi_buf + ofs, sizeof(hfi_buf) - ofs, ",%d", + ss->hfis[i]->index); + /* + * If the list doesn't fit in HFIS_LEN characters, + * truncate and end with "..." + */ + if (len >= sizeof(hfi_buf)) { + while (ofs < sizeof(hfi_buf) - 1) + hfi_buf[ofs++] = '.'; + hfi_buf[ofs] = '\0'; + len = sizeof(hfi_buf) - 1; + break; + } + } + if (buf) + memcpy(buf, hfi_buf, len + 1); + return len; +} + + +static void print_nbft_subsys_info(struct nbft_info *nbft) +{ + struct nbft_info_subsystem_ns **ss; + unsigned int nqn_width = 20, adr_width = 8, hfi_width = 4; + + ss = nbft->subsystem_ns_list; + if (!ss || !*ss) + return; + for (; *ss; ss++) { + size_t len; + + len = strlen((*ss)->subsys_nqn); + if (len > nqn_width) + nqn_width = len; + len = strlen((*ss)->traddr); + if (len > adr_width) + adr_width = len; + len = print_hfis(*ss, NULL); + if (len > hfi_width) + hfi_width = len; + } + + printf("\nNBFT Subsystems:\n\n"); + printf("%-3.3s|%-*.*s|%-4.4s|%-*.*s|%-5.5s|%-*.*s\n", + "Idx", nqn_width, nqn_width, "NQN", + "Trsp", adr_width, adr_width, "Address", "SvcId", hfi_width, hfi_width, "HFIs"); + printf("%-.3s+%-.*s+%-.4s+%-.*s+%-.5s+%-.*s\n", + dash, nqn_width, dash, dash, adr_width, dash, dash, hfi_width, dash); + for (ss = nbft->subsystem_ns_list; *ss; ss++) { + char hfi_buf[HFIS_LEN]; + + print_hfis(*ss, hfi_buf); + printf("%-3d|%-*.*s|%-4.4s|%-*.*s|%-5.5s|%-*.*s\n", + (*ss)->index, nqn_width, nqn_width, (*ss)->subsys_nqn, + (*ss)->transport, adr_width, adr_width, (*ss)->traddr, + (*ss)->trsvcid, hfi_width, hfi_width, hfi_buf); + } +} + +static void normal_show_nbft(struct nbft_info *nbft, bool show_subsys, + bool show_hfi, bool show_discovery) +{ + printf("%s:\n", nbft->filename); + if ((!nbft->hfi_list || !*nbft->hfi_list) && + (!nbft->security_list || !*nbft->security_list) && + (!nbft->discovery_list || !*nbft->discovery_list) && + (!nbft->subsystem_ns_list || !*nbft->subsystem_ns_list)) + printf("(empty)\n"); + else { + if (show_subsys) + print_nbft_subsys_info(nbft); + if (show_hfi) + print_nbft_hfi_info(nbft); + if (show_discovery) + print_nbft_discovery_info(nbft); + } +} + +static void normal_show_nbfts(struct list_head *nbft_list, bool show_subsys, + bool show_hfi, bool show_discovery) +{ + bool not_first = false; + struct nbft_file_entry *entry; + + list_for_each(nbft_list, entry, node) { + if (not_first) + printf("\n"); + normal_show_nbft(entry->nbft, show_subsys, show_hfi, show_discovery); + not_first = true; + } +} + +int show_nbft(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Display contents of the ACPI NBFT files."; + struct list_head nbft_list; + char *format = "normal"; + char *nbft_path = NBFT_SYSFS_PATH; + enum nvme_print_flags flags; + int ret; + bool show_subsys = false, show_hfi = false, show_discovery = false; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &format, "Output format: normal|json"), + OPT_FLAG("subsystem", 's', &show_subsys, "show NBFT subsystems"), + OPT_FLAG("hfi", 'H', &show_hfi, "show NBFT HFIs"), + OPT_FLAG("discovery", 'd', &show_discovery, "show NBFT discovery controllers"), + OPT_STRING("nbft-path", 0, "STR", &nbft_path, "user-defined path for NBFT tables"), + OPT_END() + }; + + ret = argconfig_parse(argc, argv, desc, opts); + if (ret) + return ret; + + ret = validate_output_format(format, &flags); + if (ret < 0) + return ret; + + if (!(show_subsys || show_hfi || show_discovery)) + show_subsys = show_hfi = show_discovery = true; + + list_head_init(&nbft_list); + ret = read_nbft_files(&nbft_list, nbft_path); + if (!ret) { + if (flags == NORMAL) + normal_show_nbfts(&nbft_list, show_subsys, show_hfi, show_discovery); + else if (flags == JSON) + ret = json_show_nbfts(&nbft_list, show_subsys, show_hfi, show_discovery); + free_nbfts(&nbft_list); + } + return ret; +} diff --git a/plugins/nbft/nbft-plugin.h b/plugins/nbft/nbft-plugin.h new file mode 100644 index 0000000..018349d --- /dev/null +++ b/plugins/nbft/nbft-plugin.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/nbft/nbft-plugin + +#if !defined(NBFT) || defined(CMD_HEADER_MULTI_READ) +#define NBFT + +#include "cmd.h" + +PLUGIN(NAME("nbft", "ACPI NBFT table extensions", NVME_VERSION), + COMMAND_LIST( + ENTRY("show", "Show contents of ACPI NBFT tables", show_nbft) + ) +); + +#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..2ecdcc5 --- /dev/null +++ b/plugins/netapp/netapp-nvme.c @@ -0,0 +1,654 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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 "common.h" +#include "nvme.h" +#include "libnvme.h" + +#include "util/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 { + unsigned int nsid; + struct nvme_id_ctrl ctrl; + struct nvme_id_ns ns; + char dev[265]; +}; + +struct ontapdevice_info { + unsigned int nsid; + struct nvme_id_ctrl ctrl; + struct nvme_id_ns ns; + unsigned char uuid[NVME_UUID_LEN]; + 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, unsigned long long *lba, + struct nvme_id_ns *ns) +{ + __u8 lba_index; + + nvme_id_ns_flbas_to_lbaf_inuse(ns->flbas, &lba_index); + *lba = 1ULL << ns->lbaf[lba_index].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 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_object *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_object *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_object *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 */ + __u8 lba_index; + + 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++) { + nvme_id_ns_flbas_to_lbaf_inuse(devices[i].ns.flbas, &lba_index); + unsigned long long lba = 1ULL << devices[i].ns.lbaf[lba_index].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); + printf("\n"); + json_free_object(root); + } +} + +static void netapp_ontapdevices_print(struct ontapdevice_info *devices, + int count, int format) +{ + struct json_object *root = NULL; + struct json_object *json_devices = NULL; + char vsname[ONTAP_LABEL_LEN] = " "; + char nspath[ONTAP_NS_PATHLEN] = " "; + unsigned 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); + nvme_uuid_to_string(devices[i].uuid, uuid_str); + 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); + printf("\n"); + json_free_object(root); + } +} + +static int nvme_get_ontap_c2_log(int fd, __u32 nsid, void *buf, __u32 buflen) +{ + struct nvme_passthru_cmd get_log; + int err; + + memset(buf, 0, buflen); + memset(&get_log, 0, sizeof(struct nvme_passthru_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, NULL); + 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, + err < 0 ? strerror(-err) : + nvme_status_to_string(err, false)); + return 0; + } + + if (strncmp("NetApp E-Series", item->ctrl.mn, 15) != 0) + return 0; /* not the right model of controller */ + + err = nvme_get_nsid(fd, &item->nsid); + err = nvme_identify_ns(fd, item->nsid, &item->ns); + if (err) { + fprintf(stderr, + "Unable to identify namespace for %s (%s)\n", + dev, err < 0 ? strerror(-err) : + nvme_status_to_string(err, false)); + return 0; + } + strncpy(item->dev, dev, sizeof(item->dev) - 1); + + return 1; +} + +static int netapp_ontapdevices_get_info(int fd, struct ontapdevice_info *item, + const char *dev) +{ + int err; + void *nsdescs; + + err = nvme_identify_ctrl(fd, &item->ctrl); + if (err) { + fprintf(stderr, "Identify Controller failed to %s (%s)\n", + dev, err < 0 ? strerror(-err) : + nvme_status_to_string(err, false)); + return 0; + } + + if (strncmp("NetApp ONTAP Controller", item->ctrl.mn, 23) != 0) + /* not the right controller model */ + return 0; + + err = nvme_get_nsid(fd, &item->nsid); + + err = nvme_identify_ns(fd, item->nsid, &item->ns); + if (err) { + fprintf(stderr, "Unable to identify namespace for %s (%s)\n", + dev, err < 0 ? strerror(-err) : + nvme_status_to_string(err, false)); + return 0; + } + + if (posix_memalign(&nsdescs, getpagesize(), 0x1000)) { + fprintf(stderr, "Cannot allocate controller list payload\n"); + return 0; + } + + err = nvme_identify_ns_descs(fd, item->nsid, nsdescs); + if (err) { + fprintf(stderr, "Unable to identify namespace descriptor for %s (%s)\n", + dev, err < 0 ? strerror(-err) : + nvme_status_to_string(err, false)); + free(nsdescs); + return 0; + } + + memcpy(item->uuid, nsdescs + sizeof(struct nvme_ns_id_desc), sizeof(item->uuid)); + free(nsdescs); + + 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, err < 0 ? strerror(-err) : + nvme_status_to_string(err, false)); + return 0; + } + + strncpy(item->dev, dev, sizeof(item->dev) - 1); + + 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..73de4b4 --- /dev/null +++ b/plugins/netapp/netapp-nvme.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#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", NVME_VERSION), + COMMAND_LIST( + ENTRY("smdevices", "NetApp SMdevices", netapp_smdevices) + ENTRY("ontapdevices", "NetApp ONTAPdevices", netapp_ontapdevices) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/nvidia/nvidia-nvme.c b/plugins/nvidia/nvidia-nvme.c new file mode 100644 index 0000000..71e0bc3 --- /dev/null +++ b/plugins/nvidia/nvidia-nvme.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" + +#define CREATE_CMD +#include "nvidia-nvme.h" + +struct nvme_vu_id_ctrl_field { + __u16 json_rpc_2_0_mjr; + __u16 json_rpc_2_0_mnr; + __u16 json_rpc_2_0_ter; + __u8 reserved0[1018]; +}; + +static void json_nvidia_id_ctrl(struct nvme_vu_id_ctrl_field *id, + char *json_rpc_2_0_ver, struct json_object *root) +{ + json_object_add_value_string(root, "json_rpc_2_0_ver", + json_rpc_2_0_ver); +} + +static void nvidia_id_ctrl(__u8 *vs, struct json_object *root) +{ + struct nvme_vu_id_ctrl_field *id = (struct nvme_vu_id_ctrl_field *)vs; + char json_rpc_2_0_ver[16] = { 0 }; + + snprintf(json_rpc_2_0_ver, sizeof(json_rpc_2_0_ver), "0x%04x%04x%04x", + le16_to_cpu(id->json_rpc_2_0_mjr), + le16_to_cpu(id->json_rpc_2_0_mnr), + le16_to_cpu(id->json_rpc_2_0_ter)); + + if (root) { + json_nvidia_id_ctrl(id, json_rpc_2_0_ver, root); + return; + } + + printf("json_rpc_2_0_ver : %s\n", json_rpc_2_0_ver); +} + +static int id_ctrl(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + return __id_ctrl(argc, argv, cmd, plugin, nvidia_id_ctrl); +} diff --git a/plugins/nvidia/nvidia-nvme.h b/plugins/nvidia/nvidia-nvme.h new file mode 100644 index 0000000..3d870e1 --- /dev/null +++ b/plugins/nvidia/nvidia-nvme.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/nvidia/nvidia-nvme + +#if !defined(NVIDIA_NVME) || defined(CMD_HEADER_MULTI_READ) +#define NVIDIA_NVME + +#include "cmd.h" + +PLUGIN(NAME("nvidia", "NVIDIA vendor specific extensions", NVME_VERSION), + COMMAND_LIST( + ENTRY("id-ctrl", "Send NVMe Identify Controller", id_ctrl) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/ocp/meson.build b/plugins/ocp/meson.build new file mode 100644 index 0000000..64447ff --- /dev/null +++ b/plugins/ocp/meson.build @@ -0,0 +1,8 @@ +sources += [ + 'plugins/ocp/ocp-utils.c', + 'plugins/ocp/ocp-nvme.c', + 'plugins/ocp/ocp-clear-features.c', + 'plugins/ocp/ocp-smart-extended-log.c', + 'plugins/ocp/ocp-fw-activation-history.c', +] + diff --git a/plugins/ocp/ocp-clear-features.c b/plugins/ocp/ocp-clear-features.c new file mode 100644 index 0000000..0f49584 --- /dev/null +++ b/plugins/ocp/ocp-clear-features.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Authors: haro.panosyan@solidigm.com + * leonardo.da.cunha@solidigm.com + */ + +#include <unistd.h> +#include "ocp-utils.h" +#include "nvme-print.h" + +static const __u8 OCP_FID_CLEAR_FW_ACTIVATION_HISTORY = 0xC1; +static const __u8 OCP_FID_CLEAR_PCIE_CORRECTABLE_ERROR_COUNTERS = 0xC3; + +static int ocp_clear_feature(int argc, char **argv, const char *desc, const __u8 fid) +{ + __u32 result = 0; + __u32 clear = 1 << 31; + struct nvme_dev *dev; + int uuid_index = 0; + bool uuid = true; + int err; + + OPT_ARGS(opts) = { + OPT_FLAG("no-uuid", 'n', NULL, + "Skip UUID index search (UUID index not required for OCP 1.0)"), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (opts[0].seen) + uuid = false; + + if (uuid) { + /* OCP 2.0 requires UUID index support */ + err = ocp_get_uuid_index(dev, &uuid_index); + if (err || !uuid_index) { + fprintf(stderr, "ERROR: No OCP UUID index found\n"); + goto close_dev; + } + } + + struct nvme_set_features_args args = { + .result = &result, + .data = NULL, + .args_size = sizeof(args), + .fd = dev_fd(dev), + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .nsid = 0, + .cdw11 = clear, + .cdw12 = 0, + .cdw13 = 0, + .cdw15 = 0, + .data_len = 0, + .save = 0, + .uuidx = uuid_index, + .fid = fid, + }; + + err = nvme_set_features(&args); + + if (err == 0) + printf("Success : %s\n", desc); + else if (err > 0) + nvme_show_status(err); + else + printf("Fail : %s\n", desc); +close_dev: + /* Redundant close() to make static code analysis happy */ + close(dev->direct.fd); + dev_close(dev); + return err; +} + +int ocp_clear_fw_update_history(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "OCP Clear Firmware Update History"; + + return ocp_clear_feature(argc, argv, desc, OCP_FID_CLEAR_FW_ACTIVATION_HISTORY); +} + +int ocp_clear_pcie_correctable_errors(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "OCP Clear PCIe Correctable Error Counters"; + + return ocp_clear_feature(argc, argv, desc, + OCP_FID_CLEAR_PCIE_CORRECTABLE_ERROR_COUNTERS); +} diff --git a/plugins/ocp/ocp-clear-features.h b/plugins/ocp/ocp-clear-features.h new file mode 100644 index 0000000..99766dd --- /dev/null +++ b/plugins/ocp/ocp-clear-features.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2022 Solidigm. + * + * Authors: haro.panosyan@solidigm.com + * leonardo.da.cunha@solidigm.com + */ + +int ocp_clear_fw_update_history(int argc, char **argv, struct command *cmd, struct plugin *plugin); + +int ocp_clear_pcie_correctable_errors(int argc, char **argv, struct command *cmd, + struct plugin *plugin); diff --git a/plugins/ocp/ocp-fw-activation-history.c b/plugins/ocp/ocp-fw-activation-history.c new file mode 100644 index 0000000..ad96c6b --- /dev/null +++ b/plugins/ocp/ocp-fw-activation-history.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: karl.dedow@solidigm.com + */ + +#include "ocp-fw-activation-history.h" + +#include <errno.h> +#include <stdio.h> + +#include "common.h" +#include "nvme-print.h" + +#include "ocp-utils.h" + +static const unsigned char ocp_fw_activation_history_guid[16] = { + 0x6D, 0x79, 0x9a, 0x76, + 0xb4, 0xda, 0xf6, 0xa3, + 0xe2, 0x4d, 0xb2, 0x8a, + 0xac, 0xf3, 0x1c, 0xd1 +}; + +struct __packed fw_activation_history_entry { + __u8 ver_num; + __u8 entry_length; + __u16 reserved1; + __u16 activation_count; + __u64 timestamp; + __u64 reserved2; + __u64 power_cycle_count; + char previous_fw[8]; + char new_fw[8]; + __u8 slot_number; + __u8 commit_action; + __u16 result; + __u8 reserved3[14]; +}; + +struct __packed fw_activation_history { + __u8 log_id; + __u8 reserved1[3]; + __u32 valid_entries; + struct fw_activation_history_entry entries[20]; + __u8 reserved2[2790]; + __u16 log_page_version; + __u64 log_page_guid[2]; +}; + +static void ocp_fw_activation_history_normal(const struct fw_activation_history *fw_history) +{ + printf("Firmware History Log:\n"); + + printf(" %-26s%d\n", "log identifier:", fw_history->log_id); + printf(" %-26s%d\n", "valid entries:", le32_to_cpu(fw_history->valid_entries)); + + printf(" entries:\n"); + + for (int index = 0; index < fw_history->valid_entries; index++) { + const struct fw_activation_history_entry *entry = &fw_history->entries[index]; + + printf(" entry[%d]:\n", le32_to_cpu(index)); + printf(" %-22s%d\n", "version number:", entry->ver_num); + printf(" %-22s%d\n", "entry length:", entry->entry_length); + printf(" %-22s%d\n", "activation count:", + le16_to_cpu(entry->activation_count)); + printf(" %-22s%"PRIu64"\n", "timestamp:", + le64_to_cpu(entry->timestamp)); + printf(" %-22s%"PRIu64"\n", "power cycle count:", + le64_to_cpu(entry->power_cycle_count)); + printf(" %-22s%.*s\n", "previous firmware:", (int)sizeof(entry->previous_fw), + entry->previous_fw); + printf(" %-22s%.*s\n", "new firmware:", (int)sizeof(entry->new_fw), + entry->new_fw); + printf(" %-22s%d\n", "slot number:", entry->slot_number); + printf(" %-22s%d\n", "commit action type:", entry->commit_action); + printf(" %-22s%d\n", "result:", le16_to_cpu(entry->result)); + } + + printf(" %-26s%d\n", "log page version:", + le16_to_cpu(fw_history->log_page_version)); + + printf(" %-26s0x%"PRIx64"%"PRIx64"\n", "log page guid:", + le64_to_cpu(fw_history->log_page_guid[1]), + le64_to_cpu(fw_history->log_page_guid[0])); + + printf("\n"); +} + +static void ocp_fw_activation_history_json(const struct fw_activation_history *fw_history) +{ + struct json_object *root = json_create_object(); + + json_object_add_value_uint(root, "log identifier", fw_history->log_id); + json_object_add_value_uint(root, "valid entries", le32_to_cpu(fw_history->valid_entries)); + + struct json_object *entries = json_create_array(); + + for (int index = 0; index < fw_history->valid_entries; index++) { + const struct fw_activation_history_entry *entry = &fw_history->entries[index]; + struct json_object *entry_obj = json_create_object(); + + json_object_add_value_uint(entry_obj, "version number", entry->ver_num); + json_object_add_value_uint(entry_obj, "entry length", entry->entry_length); + json_object_add_value_uint(entry_obj, "activation count", + le16_to_cpu(entry->activation_count)); + json_object_add_value_uint64(entry_obj, "timestamp", + le64_to_cpu(entry->timestamp)); + json_object_add_value_uint(entry_obj, "power cycle count", + le64_to_cpu(entry->power_cycle_count)); + + struct json_object *fw = json_object_new_string_len(entry->previous_fw, + sizeof(entry->previous_fw)); + + json_object_add_value_object(entry_obj, "previous firmware", fw); + + fw = json_object_new_string_len(entry->new_fw, sizeof(entry->new_fw)); + + json_object_add_value_object(entry_obj, "new firmware", fw); + json_object_add_value_uint(entry_obj, "slot number", entry->slot_number); + json_object_add_value_uint(entry_obj, "commit action type", entry->commit_action); + json_object_add_value_uint(entry_obj, "result", le16_to_cpu(entry->result)); + + json_array_add_value_object(entries, entry_obj); + } + + json_object_add_value_array(root, "entries", entries); + + json_object_add_value_uint(root, "log page version", + le16_to_cpu(fw_history->log_page_version)); + + char guid[2 * sizeof(fw_history->log_page_guid) + 3] = { 0 }; + + sprintf(guid, "0x%"PRIx64"%"PRIx64"", + le64_to_cpu(fw_history->log_page_guid[1]), + le64_to_cpu(fw_history->log_page_guid[0])); + json_object_add_value_string(root, "log page guid", guid); + + json_print_object(root, NULL); + json_free_object(root); + + printf("\n"); +} + +int ocp_fw_activation_history_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const __u8 log_id = 0xC2; + const char *description = "Retrieves the OCP firmware activation history log."; + + char *format = "normal"; + + OPT_ARGS(options) = { + OPT_FMT("output-format", 'o', &format, "output format : normal | json"), + OPT_END() + }; + + struct nvme_dev *dev = NULL; + int err = parse_and_open(&dev, argc, argv, description, options); + + if (err) + return err; + + int uuid_index = 0; + + /* + * Best effort attempt at uuid. Otherwise, assume no index (i.e. 0) + * Log GUID check will ensure correctness of returned data + */ + ocp_get_uuid_index(dev, &uuid_index); + + struct fw_activation_history fw_history = { 0 }; + + struct nvme_get_log_args args = { + .lpo = 0, + .result = NULL, + .log = &fw_history, + .args_size = sizeof(args), + .fd = dev_fd(dev), + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .lid = log_id, + .len = sizeof(fw_history), + .nsid = NVME_NSID_ALL, + .csi = NVME_CSI_NVM, + .lsi = NVME_LOG_LSI_NONE, + .lsp = 0, + .uuidx = uuid_index, + .rae = false, + .ot = false, + }; + + err = nvme_get_log(&args); + + if (err) + nvme_show_status(err); + + dev_close(dev); + + int guid_cmp_res = memcmp(fw_history.log_page_guid, ocp_fw_activation_history_guid, + sizeof(ocp_fw_activation_history_guid)); + + if (!err && guid_cmp_res) { + fprintf(stderr, + "Error: Unexpected data. Log page guid does not match with expected.\n"); + err = -EINVAL; + } + + if (!err) { + enum nvme_print_flags print_flag; + + err = validate_output_format(format, &print_flag); + if (err < 0) { + fprintf(stderr, "Error: Invalid output format.\n"); + return err; + } + + if (print_flag == JSON) + ocp_fw_activation_history_json(&fw_history); + else if (print_flag == NORMAL) + ocp_fw_activation_history_normal(&fw_history); + } + + return err; +} diff --git a/plugins/ocp/ocp-fw-activation-history.h b/plugins/ocp/ocp-fw-activation-history.h new file mode 100644 index 0000000..a7f9058 --- /dev/null +++ b/plugins/ocp/ocp-fw-activation-history.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2022 Solidigm. + * + * Authors: karl.dedow@solidigm.com + */ + +#ifndef OCP_FIRMWARE_ACTIVATION_HISTORY_H +#define OCP_FIRMWARE_ACTIVATION_HISTORY_H + +struct command; +struct plugin; + +int ocp_fw_activation_history_log(int argc, char **argv, + struct command *cmd, struct plugin *plugin); + +#endif diff --git a/plugins/ocp/ocp-nvme.c b/plugins/ocp/ocp-nvme.c new file mode 100644 index 0000000..53ae0f4 --- /dev/null +++ b/plugins/ocp/ocp-nvme.c @@ -0,0 +1,2971 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Meta Platforms, Inc. + * + * Authors: Arthur Shau <arthurshau@meta.com>, + * Wei Zhang <wzhang@meta.com>, + * Venkat Ramesh <venkatraghavan@meta.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 <time.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "util/types.h" +#include "nvme-print.h" + +#include "ocp-smart-extended-log.h" +#include "ocp-clear-features.h" +#include "ocp-fw-activation-history.h" + +#define CREATE_CMD +#include "ocp-nvme.h" +#include "ocp-utils.h" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/// Latency Monitor Log + +#define C3_LATENCY_MON_LOG_BUF_LEN 0x200 +#define C3_LATENCY_MON_OPCODE 0xC3 +#define C3_LATENCY_MON_VERSION 0x0001 +#define C3_GUID_LENGTH 16 +#define NVME_FEAT_OCP_LATENCY_MONITOR 0xC5 + +#define C3_ACTIVE_BUCKET_TIMER_INCREMENT 5 +#define C3_ACTIVE_THRESHOLD_INCREMENT 5 +#define C3_MINIMUM_WINDOW_INCREMENT 100 +#define C3_BUCKET_NUM 4 + +static __u8 lat_mon_guid[C3_GUID_LENGTH] = { + 0x92, 0x7a, 0xc0, 0x8c, + 0xd0, 0x84, 0x6c, 0x9c, + 0x70, 0x43, 0xe6, 0xd4, + 0x58, 0x5e, 0xd4, 0x85 +}; + +#define READ 3 +#define WRITE 2 +#define TRIM 1 +#define RESERVED 0 + +struct __packed ssd_latency_monitor_log { + __u8 feature_status; /* 0x00 */ + __u8 rsvd1; /* 0x01 */ + __le16 active_bucket_timer; /* 0x02 */ + __le16 active_bucket_timer_threshold; /* 0x04 */ + __u8 active_threshold_a; /* 0x06 */ + __u8 active_threshold_b; /* 0x07 */ + __u8 active_threshold_c; /* 0x08 */ + __u8 active_threshold_d; /* 0x09 */ + __le16 active_latency_config; /* 0x0A */ + __u8 active_latency_min_window; /* 0x0C */ + __u8 rsvd2[0x13]; /* 0x0D */ + + __le32 active_bucket_counter[4][4]; /* 0x20 - 0x5F */ + __le64 active_latency_timestamp[4][3]; /* 0x60 - 0xBF */ + __le16 active_measured_latency[4][3]; /* 0xC0 - 0xD7 */ + __le16 active_latency_stamp_units; /* 0xD8 */ + __u8 rsvd3[0x16]; /* 0xDA */ + + __le32 static_bucket_counter[4][4]; /* 0x0F0 - 0x12F */ + __le64 static_latency_timestamp[4][3]; /* 0x130 - 0x18F */ + __le16 static_measured_latency[4][3]; /* 0x190 - 0x1A7 */ + __le16 static_latency_stamp_units; /* 0x1A8 */ + __u8 rsvd4[0x16]; /* 0x1AA */ + + __le16 debug_log_trigger_enable; /* 0x1C0 */ + __le16 debug_log_measured_latency; /* 0x1C2 */ + __le64 debug_log_latency_stamp; /* 0x1C4 */ + __le16 debug_log_ptr; /* 0x1CC */ + __le16 debug_log_counter_trigger; /* 0x1CE */ + __u8 debug_log_stamp_units; /* 0x1D0 */ + __u8 rsvd5[0x1D]; /* 0x1D1 */ + + __le16 log_page_version; /* 0x1EE */ + __u8 log_page_guid[0x10]; /* 0x1F0 */ +}; + +struct __packed feature_latency_monitor { + __u16 active_bucket_timer_threshold; + __u8 active_threshold_a; + __u8 active_threshold_b; + __u8 active_threshold_c; + __u8 active_threshold_d; + __u16 active_latency_config; + __u8 active_latency_minimum_window; + __u16 debug_log_trigger_enable; + __u8 discard_debug_log; + __u8 latency_monitor_feature_enable; + __u8 reserved[4083]; +}; + +static int ocp_print_C3_log_normal(struct nvme_dev *dev, + struct ssd_latency_monitor_log *log_data) +{ + char ts_buf[128]; + int i, j; + + printf("-Latency Monitor/C3 Log Page Data-\n"); + printf(" Controller : %s\n", dev->name); + printf(" Feature Status 0x%x\n", + log_data->feature_status); + printf(" Active Bucket Timer %d min\n", + C3_ACTIVE_BUCKET_TIMER_INCREMENT * + le16_to_cpu(log_data->active_bucket_timer)); + printf(" Active Bucket Timer Threshold %d min\n", + C3_ACTIVE_BUCKET_TIMER_INCREMENT * + le16_to_cpu(log_data->active_bucket_timer_threshold)); + printf(" Active Threshold A %d ms\n", + C3_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_a+1)); + printf(" Active Threshold B %d ms\n", + C3_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_b+1)); + printf(" Active Threshold C %d ms\n", + C3_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_c+1)); + printf(" Active Threshold D %d ms\n", + C3_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_d+1)); + printf(" Active Latency Configuration 0x%x \n", + le16_to_cpu(log_data->active_latency_config)); + printf(" Active Latency Minimum Window %d ms\n", + C3_MINIMUM_WINDOW_INCREMENT * + le16_to_cpu(log_data->active_latency_min_window)); + printf(" Active Latency Stamp Units %d\n", + le16_to_cpu(log_data->active_latency_stamp_units)); + printf(" Static Latency Stamp Units %d\n", + le16_to_cpu(log_data->static_latency_stamp_units)); + printf(" Debug Log Trigger Enable %d\n", + le16_to_cpu(log_data->debug_log_trigger_enable)); + printf(" Debug Log Measured Latency %d\n", + le16_to_cpu(log_data->debug_log_measured_latency)); + if (le64_to_cpu(log_data->debug_log_latency_stamp) == -1) { + printf(" Debug Log Latency Time Stamp N/A\n"); + } else { + convert_ts(le64_to_cpu(log_data->debug_log_latency_stamp), ts_buf); + printf(" Debug Log Latency Time Stamp %s\n", ts_buf); + } + printf(" Debug Log Pointer %d\n", + le16_to_cpu(log_data->debug_log_ptr)); + printf(" Debug Counter Trigger Source %d\n", + le16_to_cpu(log_data->debug_log_counter_trigger)); + printf(" Debug Log Stamp Units %d\n", + le16_to_cpu(log_data->debug_log_stamp_units)); + printf(" Log Page Version %d\n", + le16_to_cpu(log_data->log_page_version)); + + char guid[(C3_GUID_LENGTH * 2) + 1]; + char *ptr = &guid[0]; + + for (i = C3_GUID_LENGTH - 1; i >= 0; i--) + ptr += sprintf(ptr, "%02X", log_data->log_page_guid[i]); + + printf(" Log Page GUID %s\n", guid); + printf("\n"); + + printf(" Read Write Deallocate/Trim\n"); + for (i = 0; i < C3_BUCKET_NUM; i++) { + printf(" Active Bucket Counter: Bucket %d %27d %27d %27d\n", + i, + le32_to_cpu(log_data->active_bucket_counter[i][READ]), + le32_to_cpu(log_data->active_bucket_counter[i][WRITE]), + le32_to_cpu(log_data->active_bucket_counter[i][TRIM])); + } + + for (i = 0; i < C3_BUCKET_NUM; i++) { + printf(" Active Latency Time Stamp: Bucket %d ", i); + for (j = 2; j >= 0; j--) { + if (le64_to_cpu(log_data->active_latency_timestamp[3-i][j]) == -1) { + printf(" N/A "); + } else { + convert_ts(le64_to_cpu(log_data->active_latency_timestamp[3-i][j]), ts_buf); + printf("%s ", ts_buf); + } + } + printf("\n"); + } + + for (i = 0; i < C3_BUCKET_NUM; i++) { + printf(" Active Measured Latency: Bucket %d %27d ms %27d ms %27d ms\n", + i, + le16_to_cpu(log_data->active_measured_latency[3-i][READ-1]), + le16_to_cpu(log_data->active_measured_latency[3-i][WRITE-1]), + le16_to_cpu(log_data->active_measured_latency[3-i][TRIM-1])); + } + + printf("\n"); + for (i = 0; i < C3_BUCKET_NUM; i++) { + printf(" Static Bucket Counter: Bucket %d %27d %27d %27d\n", + i, + le32_to_cpu(log_data->static_bucket_counter[i][READ]), + le32_to_cpu(log_data->static_bucket_counter[i][WRITE]), + le32_to_cpu(log_data->static_bucket_counter[i][TRIM])); + } + + for (i = 0; i < C3_BUCKET_NUM; i++) { + printf(" Static Latency Time Stamp: Bucket %d ", i); + for (j = 2; j >= 0; j--) { + if (le64_to_cpu(log_data->static_latency_timestamp[3-i][j]) == -1) { + printf(" N/A "); + } else { + convert_ts(le64_to_cpu(log_data->static_latency_timestamp[3-i][j]), ts_buf); + printf("%s ", ts_buf); + } + } + printf("\n"); + } + + for (i = 0; i < C3_BUCKET_NUM; i++) { + printf(" Static Measured Latency: Bucket %d %27d ms %27d ms %27d ms\n", + i, + le16_to_cpu(log_data->static_measured_latency[3-i][READ-1]), + le16_to_cpu(log_data->static_measured_latency[3-i][WRITE-1]), + le16_to_cpu(log_data->static_measured_latency[3-i][TRIM-1])); + } + + return 0; +} + +static void ocp_print_C3_log_json(struct ssd_latency_monitor_log *log_data) +{ + struct json_object *root; + char ts_buf[128]; + char buf[128]; + int i, j; + char *operation[3] = {"Trim", "Write", "Read"}; + + root = json_create_object(); + + json_object_add_value_uint(root, "Feature Status", + log_data->feature_status); + json_object_add_value_uint(root, "Active Bucket Timer", + C3_ACTIVE_BUCKET_TIMER_INCREMENT * + le16_to_cpu(log_data->active_bucket_timer)); + json_object_add_value_uint(root, "Active Bucket Timer Threshold", + C3_ACTIVE_BUCKET_TIMER_INCREMENT * + le16_to_cpu(log_data->active_bucket_timer_threshold)); + json_object_add_value_uint(root, "Active Threshold A", + C3_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_a + 1)); + json_object_add_value_uint(root, "Active Threshold B", + C3_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_b + 1)); + json_object_add_value_uint(root, "Active Threshold C", + C3_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_c + 1)); + json_object_add_value_uint(root, "Active Threshold D", + C3_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_d + 1)); + json_object_add_value_uint(root, "Active Latency Configuration", + le16_to_cpu(log_data->active_latency_config)); + json_object_add_value_uint(root, "Active Latency Minimum Window", + C3_MINIMUM_WINDOW_INCREMENT * + le16_to_cpu(log_data->active_latency_min_window)); + + for (i = 0; i < C3_BUCKET_NUM; i++) { + struct json_object *bucket; + + bucket = json_create_object(); + sprintf(buf, "Active Bucket Counter: Bucket %d", i); + for (j = 2; j >= 0; j--) { + json_object_add_value_uint(bucket, operation[j], + le32_to_cpu(log_data->active_bucket_counter[i][j+1])); + } + json_object_add_value_object(root, buf, bucket); + } + + for (i = 0; i < C3_BUCKET_NUM; i++) { + struct json_object *bucket; + + bucket = json_create_object(); + sprintf(buf, "Active Latency Time Stamp: Bucket %d", i); + for (j = 2; j >= 0; j--) { + if (le64_to_cpu(log_data->active_latency_timestamp[3-i][j]) == -1) { + json_object_add_value_string(bucket, operation[j], "NA"); + } else { + convert_ts(le64_to_cpu(log_data->active_latency_timestamp[3-i][j]), ts_buf); + json_object_add_value_string(bucket, operation[j], ts_buf); + } + } + json_object_add_value_object(root, buf, bucket); + } + + for (i = 0; i < C3_BUCKET_NUM; i++) { + struct json_object *bucket; + + bucket = json_create_object(); + sprintf(buf, "Active Measured Latency: Bucket %d", i); + for (j = 2; j >= 0; j--) { + json_object_add_value_uint(bucket, operation[j], + le16_to_cpu(log_data->active_measured_latency[3-i][j])); + } + json_object_add_value_object(root, buf, bucket); + } + + json_object_add_value_uint(root, "Active Latency Stamp Units", + le16_to_cpu(log_data->active_latency_stamp_units)); + + for (i = 0; i < C3_BUCKET_NUM; i++) { + struct json_object *bucket; + + bucket = json_create_object(); + sprintf(buf, "Static Bucket Counter: Bucket %d", i); + for (j = 2; j >= 0; j--) { + json_object_add_value_uint(bucket, operation[j], + le32_to_cpu(log_data->static_bucket_counter[i][j+1])); + } + json_object_add_value_object(root, buf, bucket); + } + + for (i = 0; i < C3_BUCKET_NUM; i++) { + struct json_object *bucket; + + bucket = json_create_object(); + sprintf(buf, "Static Latency Time Stamp: Bucket %d", i); + for (j = 2; j >= 0; j--) { + if (le64_to_cpu(log_data->static_latency_timestamp[3-i][j]) == -1) { + json_object_add_value_string(bucket, operation[j], "NA"); + } else { + convert_ts(le64_to_cpu(log_data->static_latency_timestamp[3-i][j]), ts_buf); + json_object_add_value_string(bucket, operation[j], ts_buf); + } + } + json_object_add_value_object(root, buf, bucket); + } + + for (i = 0; i < C3_BUCKET_NUM; i++) { + struct json_object *bucket; + + bucket = json_create_object(); + sprintf(buf, "Static Measured Latency: Bucket %d", i); + for (j = 2; j >= 0; j--) { + json_object_add_value_uint(bucket, operation[j], + le16_to_cpu(log_data->static_measured_latency[3-i][j])); + } + json_object_add_value_object(root, buf, bucket); + } + + json_object_add_value_uint(root, "Static Latency Stamp Units", + le16_to_cpu(log_data->static_latency_stamp_units)); + json_object_add_value_uint(root, "Debug Log Trigger Enable", + le16_to_cpu(log_data->debug_log_trigger_enable)); + json_object_add_value_uint(root, "Debug Log Measured Latency", + le16_to_cpu(log_data->debug_log_measured_latency)); + if (le64_to_cpu(log_data->debug_log_latency_stamp) == -1) { + json_object_add_value_string(root, "Debug Log Latency Time Stamp", "NA"); + } else { + convert_ts(le64_to_cpu(log_data->debug_log_latency_stamp), ts_buf); + json_object_add_value_string(root, "Debug Log Latency Time Stamp", ts_buf); + } + json_object_add_value_uint(root, "Debug Log Pointer", + le16_to_cpu(log_data->debug_log_ptr)); + json_object_add_value_uint(root, "Debug Counter Trigger Source", + le16_to_cpu(log_data->debug_log_counter_trigger)); + json_object_add_value_uint(root, "Debug Log Stamp Units", + le16_to_cpu(log_data->debug_log_stamp_units)); + json_object_add_value_uint(root, "Log Page Version", + le16_to_cpu(log_data->log_page_version)); + + char guid[(C3_GUID_LENGTH * 2) + 1]; + char *ptr = &guid[0]; + + for (i = C3_GUID_LENGTH - 1; i >= 0; i--) + ptr += sprintf(ptr, "%02X", log_data->log_page_guid[i]); + + json_object_add_value_string(root, "Log Page GUID", guid); + + json_print_object(root, NULL); + printf("\n"); + + json_free_object(root); +} + +static int get_c3_log_page(struct nvme_dev *dev, char *format) +{ + struct ssd_latency_monitor_log *log_data; + enum nvme_print_flags fmt; + int ret; + __u8 *data; + int i; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR : OCP : invalid output format\n"); + return ret; + } + + data = malloc(sizeof(__u8) * C3_LATENCY_MON_LOG_BUF_LEN); + if (!data) { + fprintf(stderr, "ERROR : OCP : malloc : %s\n", strerror(errno)); + return -1; + } + memset(data, 0, sizeof(__u8) * C3_LATENCY_MON_LOG_BUF_LEN); + + ret = nvme_get_log_simple(dev_fd(dev), C3_LATENCY_MON_OPCODE, + C3_LATENCY_MON_LOG_BUF_LEN, data); + + if (strcmp(format, "json")) + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret, false), ret); + + if (!ret) { + log_data = (struct ssd_latency_monitor_log *)data; + + /* check log page version */ + if (log_data->log_page_version != C3_LATENCY_MON_VERSION) { + fprintf(stderr, + "ERROR : OCP : invalid latency monitor version\n"); + ret = -1; + goto out; + } + + /* + * check log page guid + * Verify GUID matches + */ + for (i = 0; i < 16; i++) { + if (lat_mon_guid[i] != log_data->log_page_guid[i]) { + int j; + + fprintf(stderr, "ERROR : OCP : Unknown GUID in C3 Log Page data\n"); + fprintf(stderr, "ERROR : OCP : Expected GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", lat_mon_guid[j]); + + fprintf(stderr, "\nERROR : OCP : Actual GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", log_data->log_page_guid[j]); + fprintf(stderr, "\n"); + + ret = -1; + goto out; + } + } + + switch (fmt) { + case NORMAL: + ocp_print_C3_log_normal(dev, log_data); + break; + case JSON: + ocp_print_C3_log_json(log_data); + break; + default: + fprintf(stderr, "unhandled output format\n"); + + } + } else { + fprintf(stderr, + "ERROR : OCP : Unable to read C3 data from buffer\n"); + } + +out: + free(data); + return ret; +} + +static int ocp_latency_monitor_log(int argc, char **argv, + struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve latency monitor log data."; + struct nvme_dev *dev; + int ret = 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + ret = get_c3_log_page(dev, cfg.output_format); + if (ret) + fprintf(stderr, + "ERROR : OCP : Failure reading the C3 Log Page, ret = %d\n", + ret); + + dev_close(dev); + return ret; +} + +int ocp_set_latency_monitor_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int err = -1; + struct nvme_dev *dev; + __u32 result; + struct feature_latency_monitor buf = {0,}; + __u32 nsid = NVME_NSID_ALL; + struct stat nvme_stat; + struct nvme_id_ctrl ctrl; + + const char *desc = "Set Latency Monitor feature."; + const char *active_bucket_timer_threshold = "This is the value that loads the Active Bucket Timer Threshold."; + const char *active_threshold_a = "This is the value that loads into the Active Threshold A."; + const char *active_threshold_b = "This is the value that loads into the Active Threshold B."; + const char *active_threshold_c = "This is the value that loads into the Active Threshold C."; + const char *active_threshold_d = "This is the value that loads into the Active Threshold D."; + const char *active_latency_config = "This is the value that loads into the Active Latency Configuration."; + const char *active_latency_minimum_window = "This is the value that loads into the Active Latency Minimum Window."; + const char *debug_log_trigger_enable = "This is the value that loads into the Debug Log Trigger Enable."; + const char *discard_debug_log = "Discard Debug Log."; + const char *latency_monitor_feature_enable = "Latency Monitor Feature Enable."; + + struct config { + __u16 active_bucket_timer_threshold; + __u8 active_threshold_a; + __u8 active_threshold_b; + __u8 active_threshold_c; + __u8 active_threshold_d; + __u16 active_latency_config; + __u8 active_latency_minimum_window; + __u16 debug_log_trigger_enable; + __u8 discard_debug_log; + __u8 latency_monitor_feature_enable; + }; + + struct config cfg = { + .active_bucket_timer_threshold = 0x7E0, + .active_threshold_a = 0x5, + .active_threshold_b = 0x13, + .active_threshold_c = 0x1E, + .active_threshold_d = 0x2E, + .active_latency_config = 0xFFF, + .active_latency_minimum_window = 0xA, + .debug_log_trigger_enable = 0, + .discard_debug_log = 0, + .latency_monitor_feature_enable = 0x7, + }; + + OPT_ARGS(opts) = { + OPT_UINT("active_bucket_timer_threshold", 't', &cfg.active_bucket_timer_threshold, active_bucket_timer_threshold), + OPT_UINT("active_threshold_a", 'a', &cfg.active_threshold_a, active_threshold_a), + OPT_UINT("active_threshold_b", 'b', &cfg.active_threshold_b, active_threshold_b), + OPT_UINT("active_threshold_c", 'c', &cfg.active_threshold_c, active_threshold_c), + OPT_UINT("active_threshold_d", 'd', &cfg.active_threshold_d, active_threshold_d), + OPT_UINT("active_latency_config", 'f', &cfg.active_latency_config, active_latency_config), + OPT_UINT("active_latency_minimum_window", 'w', &cfg.active_latency_minimum_window, active_latency_minimum_window), + OPT_UINT("debug_log_trigger_enable", 'r', &cfg.debug_log_trigger_enable, debug_log_trigger_enable), + OPT_UINT("discard_debug_log", 'l', &cfg.discard_debug_log, discard_debug_log), + OPT_UINT("latency_monitor_feature_enable", 'e', &cfg.latency_monitor_feature_enable, latency_monitor_feature_enable), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = fstat(dev_fd(dev), &nvme_stat); + if (err < 0) + return err; + + if (S_ISBLK(nvme_stat.st_mode)) { + err = nvme_get_nsid(dev_fd(dev), &nsid); + if (err < 0) { + perror("invalid-namespace-id"); + return err; + } + } + + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (err) + return err; + + memset(&buf, 0, sizeof(struct feature_latency_monitor)); + + buf.active_bucket_timer_threshold = cfg.active_bucket_timer_threshold; + buf.active_threshold_a = cfg.active_threshold_a; + buf.active_threshold_b = cfg.active_threshold_b; + buf.active_threshold_c = cfg.active_threshold_c; + buf.active_threshold_d = cfg.active_threshold_d; + buf.active_latency_config = cfg.active_latency_config; + buf.active_latency_minimum_window = cfg.active_latency_minimum_window; + buf.debug_log_trigger_enable = cfg.debug_log_trigger_enable; + buf.discard_debug_log = cfg.discard_debug_log; + buf.latency_monitor_feature_enable = cfg.latency_monitor_feature_enable; + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = NVME_FEAT_OCP_LATENCY_MONITOR, + .nsid = 0, + .cdw12 = 0, + .save = 1, + .data_len = sizeof(struct feature_latency_monitor), + .data = (void *)&buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + err = nvme_set_features(&args); + if (err < 0) { + perror("set-feature"); + } else if (!err) { + printf("NVME_FEAT_OCP_LATENCY_MONITOR: 0x%02x\n", NVME_FEAT_OCP_LATENCY_MONITOR); + printf("active bucket timer threshold: 0x%x\n", buf.active_bucket_timer_threshold); + printf("active threshold a: 0x%x\n", buf.active_threshold_a); + printf("active threshold b: 0x%x\n", buf.active_threshold_b); + printf("active threshold c: 0x%x\n", buf.active_threshold_c); + printf("active threshold d: 0x%x\n", buf.active_threshold_d); + printf("active latency config: 0x%x\n", buf.active_latency_config); + printf("active latency minimum window: 0x%x\n", buf.active_latency_minimum_window); + printf("debug log trigger enable: 0x%x\n", buf.debug_log_trigger_enable); + printf("discard debug log: 0x%x\n", buf.discard_debug_log); + printf("latency monitor feature enable: 0x%x\n", buf.latency_monitor_feature_enable); + } else if (err > 0) { + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(err, false), err); + } + + return err; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/// EOL/PLP Failure Mode + +static const char *eol_plp_failure_mode_to_string(__u8 mode) +{ + switch (mode) { + case 1: + return "Read only mode (ROM)"; + case 2: + return "Write through mode (WTM)"; + case 3: + return "Normal mode"; + default: + break; + } + + return "Reserved"; +} + +static int eol_plp_failure_mode_get(struct nvme_dev *dev, const __u32 nsid, + const __u8 fid, __u8 sel) +{ + __u32 result; + int err; + + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = fid, + .nsid = nsid, + .sel = sel, + .cdw11 = 0, + .uuidx = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + err = nvme_get_features(&args); + if (!err) { + nvme_show_result("End of Life Behavior (feature: %#0*x): %#0*x (%s: %s)", + fid ? 4 : 2, fid, result ? 10 : 8, result, + nvme_select_to_string(sel), + eol_plp_failure_mode_to_string(result)); + if (sel == NVME_GET_FEATURES_SEL_SUPPORTED) + nvme_show_select_result(fid, result); + } else { + nvme_show_error("Could not get feature: %#0*x.", fid ? 4 : 2, fid); + } + + return err; +} + +static int eol_plp_failure_mode_set(struct nvme_dev *dev, const __u32 nsid, + const __u8 fid, __u8 mode, bool save, + bool uuid) +{ + __u32 result; + int err; + int uuid_index = 0; + + if (uuid) { + /* OCP 2.0 requires UUID index support */ + err = ocp_get_uuid_index(dev, &uuid_index); + if (err || !uuid_index) { + nvme_show_error("ERROR: No OCP UUID index found"); + return err; + } + } + + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = fid, + .nsid = nsid, + .cdw11 = mode << 30, + .cdw12 = 0, + .save = save, + .uuidx = uuid_index, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + err = nvme_set_features(&args); + if (err > 0) { + nvme_show_status(err); + } else if (err < 0) { + nvme_show_perror("Define EOL/PLP failure mode"); + fprintf(stderr, "Command failed while parsing.\n"); + } else { + nvme_show_result("Successfully set mode (feature: %#0*x): %#0*x (%s: %s).", + fid ? 4 : 2, fid, mode ? 10 : 8, mode, + save ? "Save" : "Not save", + eol_plp_failure_mode_to_string(mode)); + } + + return err; +} + +static int eol_plp_failure_mode(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "Define EOL or PLP circuitry failure mode.\n" + "No argument prints current mode."; + const char *mode = "[0-3]: default/rom/wtm/normal"; + const char *save = "Specifies that the controller shall save the attribute"; + const char *sel = "[0-3,8]: current/default/saved/supported/changed"; + const __u32 nsid = 0; + const __u8 fid = 0xc2; + struct nvme_dev *dev; + int err; + + struct config { + __u8 mode; + bool save; + __u8 sel; + }; + + struct config cfg = { + .mode = 0, + .save = false, + .sel = 0, + }; + + OPT_ARGS(opts) = { + OPT_BYTE("mode", 'm', &cfg.mode, mode), + OPT_FLAG("save", 's', &cfg.save, save), + OPT_BYTE("sel", 'S', &cfg.sel, sel), + OPT_FLAG("no-uuid", 'n', NULL, + "Skip UUID index search (UUID index not required for OCP 1.0)"), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (argconfig_parse_seen(opts, "mode")) + err = eol_plp_failure_mode_set(dev, nsid, fid, cfg.mode, + cfg.save, + !argconfig_parse_seen(opts, "no-uuid")); + else + err = eol_plp_failure_mode_get(dev, nsid, fid, cfg.sel); + + dev_close(dev); + + return err; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/// Telemetry Log + +#define TELEMETRY_HEADER_SIZE 512 +#define TELEMETRY_BYTE_PER_BLOCK 512 +#define TELEMETRY_TRANSFER_SIZE 1024 +#define FILE_NAME_SIZE 2048 + +enum TELEMETRY_TYPE { + TELEMETRY_TYPE_NONE = 0, + TELEMETRY_TYPE_HOST = 7, + TELEMETRY_TYPE_CONTROLLER = 8, + TELEMETRY_TYPE_HOST_0 = 9, + TELEMETRY_TYPE_HOST_1 = 10, +}; + +struct telemetry_initiated_log { + __u8 LogIdentifier; + __u8 Reserved1[4]; + __u8 IEEE[3]; + __le16 DataArea1LastBlock; + __le16 DataArea2LastBlock; + __le16 DataArea3LastBlock; + __u8 Reserved2[368]; + __u8 DataAvailable; + __u8 DataGenerationNumber; + __u8 ReasonIdentifier[128]; +}; + +struct telemetry_data_area_1 { + __le16 major_version; + __le16 minor_version; + __u8 reserved1[4]; + __le64 timestamp; + __u8 log_page_guid[16]; + __u8 no_of_tps_supp; + __u8 tps; + __u8 reserved2[6]; + __le16 sls; + __u8 reserved3[8]; + __le16 fw_revision; + __u8 reserved4[32]; + __le16 da1_stat_start; + __le16 da1_stat_size; + __le16 da2_stat_start; + __le16 da2_stat_size; + __u8 reserved5[32]; + __u8 event_fifo_da[16]; + __le64 event_fifo_start[16]; + __le64 event_fifo_size[16]; + __u8 reserved6[80]; + __u8 smart_health_info[512]; + __u8 smart_health_info_extended[512]; +}; +static void get_serial_number(struct nvme_id_ctrl *ctrl, char *sn) +{ + int i; + /* Remove trailing spaces from the name */ + for (i = 0; i < sizeof(ctrl->sn); i++) { + if (ctrl->sn[i] == ' ') + break; + sn[i] = ctrl->sn[i]; + } +} + +static int get_telemetry_header(struct nvme_dev *dev, __u32 ns, __u8 tele_type, + __u32 data_len, void *data, __u8 nLSP, __u8 nRAE) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_get_log_page, + .nsid = ns, + .addr = (__u64)(uintptr_t) data, + .data_len = data_len, + }; + + __u32 numd = (data_len >> 2) - 1; + __u16 numdu = numd >> 16; + __u16 numdl = numd & 0xffff; + + cmd.cdw10 = tele_type | (nLSP & 0x0F) << 8 | (nRAE & 0x01) << 15 | (numdl & 0xFFFF) << 16; + cmd.cdw11 = numdu; + cmd.cdw12 = 0; + cmd.cdw13 = 0; + cmd.cdw14 = 0; + + return nvme_submit_admin_passthru(dev_fd(dev), &cmd, NULL); +} + +static void print_telemetry_header(struct telemetry_initiated_log *logheader, + int tele_type) +{ + if (logheader) { + unsigned int i = 0, j = 0; + + if (tele_type == TELEMETRY_TYPE_HOST) + printf("============ Telemetry Host Header ============\n"); + else + printf("========= Telemetry Controller Header =========\n"); + + printf("Log Identifier : 0x%02X\n", logheader->LogIdentifier); + printf("IEEE : 0x%02X%02X%02X\n", + logheader->IEEE[0], logheader->IEEE[1], logheader->IEEE[2]); + printf("Data Area 1 Last Block : 0x%04X\n", + le16_to_cpu(logheader->DataArea1LastBlock)); + printf("Data Area 2 Last Block : 0x%04X\n", + le16_to_cpu(logheader->DataArea2LastBlock)); + printf("Data Area 3 Last Block : 0x%04X\n", + le16_to_cpu(logheader->DataArea3LastBlock)); + printf("Data Available : 0x%02X\n", logheader->DataAvailable); + printf("Data Generation Number : 0x%02X\n", logheader->DataGenerationNumber); + printf("Reason Identifier :\n"); + + for (i = 0; i < 8; i++) { + for (j = 0; j < 16; j++) + printf("%02X ", logheader->ReasonIdentifier[127 - ((i * 16) + j)]); + printf("\n"); + } + printf("===============================================\n\n"); + } +} +static int get_telemetry_data(struct nvme_dev *dev, __u32 ns, __u8 tele_type, + __u32 data_len, void *data, __u8 nLSP, __u8 nRAE, + __u64 offset) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_get_log_page, + .nsid = ns, + .addr = (__u64)(uintptr_t) data, + .data_len = data_len, + }; + __u32 numd = (data_len >> 2) - 1; + __u16 numdu = numd >> 16; + __u16 numdl = numd & 0xffff; + cmd.cdw10 = tele_type | (nLSP & 0x0F) << 8 | (nRAE & 0x01) << 15 | (numdl & 0xFFFF) << 16; + cmd.cdw11 = numdu; + cmd.cdw12 = offset; + cmd.cdw13 = 0; + cmd.cdw14 = 0; + return nvme_submit_admin_passthru(dev_fd(dev), &cmd, NULL); +} +static void print_telemetry_data_area_1(struct telemetry_data_area_1 *da1, + int tele_type) +{ + if (da1) { + unsigned int i = 0; + if (tele_type == TELEMETRY_TYPE_HOST) + printf("============ Telemetry Host Data area 1 ============\n"); + else + printf("========= Telemetry Controller Data area 1 =========\n"); + printf("Major Version : 0x%x\n", le16_to_cpu(da1->major_version)); + printf("Minor Version : 0x%x\n", le16_to_cpu(da1->minor_version)); + for (i = 0; i < 4; i++) + printf("reserved1 : 0x%x\n", da1->reserved1[i]); + printf("Timestamp : %"PRIu64"\n", le64_to_cpu(da1->timestamp)); + for (i = 15; i >= 0; i--) + printf("%x", da1->log_page_guid[i]); + printf("Number Telemetry Profiles Supported : 0x%x\n", da1->no_of_tps_supp); + printf("Telemetry Profile Selected (TPS) : 0x%x\n", da1->tps); + for (i = 0; i < 6; i++) + printf("reserved2 : 0x%x\n", da1->reserved2[i]); + printf("Telemetry String Log Size (SLS) : 0x%x\n", le16_to_cpu(da1->sls)); + for (i = 0; i < 8; i++) + printf("reserved3 : 0x%x\n", da1->reserved3[i]); + printf("Firmware Revision : 0x%x\n", le16_to_cpu(da1->fw_revision)); + for (i = 0; i < 32; i++) + printf("reserved4 : 0x%x\n", da1->reserved4[i]); + printf("Data Area 1 Statistic Start : 0x%x\n", le16_to_cpu(da1->da1_stat_start)); + printf("Data Area 1 Statistic Size : 0x%x\n", le16_to_cpu(da1->da1_stat_size)); + printf("Data Area 2 Statistic Start : 0x%x\n", le16_to_cpu(da1->da2_stat_start)); + printf("Data Area 2 Statistic Size : 0x%x\n", le16_to_cpu(da1->da2_stat_size)); + for (i = 0; i < 32; i++) + printf("reserved5 : 0x%x\n", da1->reserved5[i]); + for (i = 0; i < 17; i++){ + printf("Event FIFO %d Data Area : 0x%x\n", i, da1->event_fifo_da[i]); + printf("Event FIFO %d Start : %"PRIu64"\n", i, le64_to_cpu(da1->event_fifo_start[i])); + printf("Event FIFO %d Size : %"PRIu64"\n", i, le64_to_cpu(da1->event_fifo_size[i])); + } + for (i = 0; i < 80; i++) + printf("reserved6 : 0x%x\n", da1->reserved6[i]); + for (i = 0; i < 512; i++){ + printf("SMART / Health Information : 0x%x\n", da1->smart_health_info[i]); + printf("SMART / Health Information Extended : 0x%x\n", da1->smart_health_info_extended[i]); + } + printf("===============================================\n\n"); + } +} +static void print_telemetry_da1_stat(__u8 *da1_stat, int tele_type, __u16 buf_size) +{ + if (da1_stat) { + unsigned int i = 0; + if (tele_type == TELEMETRY_TYPE_HOST) + printf("============ Telemetry Host Data area 1 Statistics ============\n"); + else + printf("========= Telemetry Controller Data area 1 Statistics =========\n"); + while((i + 8) < buf_size) { + printf("Statistics Identifier : 0x%x\n", (da1_stat[i] | da1_stat[i+1] << 8)); + printf("Statistics info : 0x%x\n", da1_stat[i+2]); + printf("NS info : 0x%x\n", da1_stat[i+3]); + printf("Statistic Data Size : 0x%x\n", (da1_stat[i+4] | da1_stat[i+5] << 8)); + printf("Reserved : 0x%x\n", (da1_stat[i+6] | da1_stat[i+7] << 8)); + i = 8 + ((da1_stat[i+4] | da1_stat[i+5] << 8) * 4); + } + printf("===============================================\n\n"); + } +} +static void print_telemetry_da1_fifo(__u8 *da1_fifo, int tele_type, __u16 buf_size) +{ + if (da1_fifo) { + unsigned int i = 0; + if (tele_type == TELEMETRY_TYPE_HOST) + printf("============ Telemetry Host Data area 1 FIFO ============\n"); + else + printf("========= Telemetry Controller Data area 1 FIFO =========\n"); + while((i + 4) < buf_size) { + printf("Debug Event Class Type : 0x%x\n", da1_fifo[i]); + printf("Event ID : 0x%x\n", (da1_fifo[i+1] | da1_fifo[i+2] << 8)); + printf("Event Data Size : 0x%x\n", da1_fifo[3]); + i = 4 + ((da1_fifo[3]) * 4); + } + printf("===============================================\n\n"); + } +} +static void print_telemetry_da2_stat(__u8 *da1_stat, int tele_type, __u16 buf_size) +{ + if (da1_stat) { + unsigned int i = 0; + if (tele_type == TELEMETRY_TYPE_HOST) + printf("============ Telemetry Host Data area 1 Statistics ============\n"); + else + printf("========= Telemetry Controller Data area 1 Statistics =========\n"); + while((i + 8) < buf_size) { + printf("Statistics Identifier : 0x%x\n", (da1_stat[i] | da1_stat[i+1] << 8)); + printf("Statistics info : 0x%x\n", da1_stat[i+2]); + printf("NS info : 0x%x\n", da1_stat[i+3]); + printf("Statistic Data Size : 0x%x\n", (da1_stat[i+4] | da1_stat[i+5] << 8)); + printf("Reserved : 0x%x\n", (da1_stat[i+6] | da1_stat[i+7] << 8)); + i = 8 + ((da1_stat[i+4] | da1_stat[i+5] << 8) * 4); + } + printf("===============================================\n\n"); + } +} +static void print_telemetry_da2_fifo(__u8 *da1_fifo, int tele_type, __u16 buf_size) +{ + if (da1_fifo) { + unsigned int i = 0; + if (tele_type == TELEMETRY_TYPE_HOST) + printf("============ Telemetry Host Data area 1 Statistics ============\n"); + else + printf("========= Telemetry Controller Data area 1 Statistics =========\n"); + while((i + 4) < buf_size) { + printf("Debug Event Class Type : 0x%x\n", da1_fifo[i]); + printf("Event ID : 0x%x\n", (da1_fifo[i+1] | da1_fifo[i+2] << 8)); + printf("Event Data Size : 0x%x\n", da1_fifo[3]); + i = 4 + ((da1_fifo[3]) * 4); + } + printf("===============================================\n\n"); + } +} + +static int extract_dump_get_log(struct nvme_dev *dev, char *featurename, char *filename, char *sn, + int dumpsize, int transfersize, __u32 nsid, __u8 log_id, + __u8 lsp, __u64 offset, bool rae) +{ + int i = 0, err = 0; + + char *data = calloc(transfersize, sizeof(char)); + char filepath[FILE_NAME_SIZE] = {0,}; + int output = 0; + int total_loop_cnt = dumpsize / transfersize; + int last_xfer_size = dumpsize % transfersize; + + if (last_xfer_size) + total_loop_cnt++; + else + last_xfer_size = transfersize; + + if (filename == 0) + snprintf(filepath, FILE_NAME_SIZE, "%s_%s.bin", featurename, sn); + else + snprintf(filepath, FILE_NAME_SIZE, "%s%s_%s.bin", filename, featurename, sn); + + for (i = 0; i < total_loop_cnt; i++) { + memset(data, 0, transfersize); + + struct nvme_get_log_args args = { + .lpo = offset, + .result = NULL, + .log = (void *)data, + .args_size = sizeof(args), + .fd = dev_fd(dev), + .lid = log_id, + .len = transfersize, + .nsid = nsid, + .lsp = lsp, + .uuidx = 0, + .rae = rae, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .csi = NVME_CSI_NVM, + .ot = false, + }; + + err = nvme_get_log(&args); + if (err) { + if (i > 0) + goto close_output; + else + goto end; + } + + if (i != total_loop_cnt - 1) { + if (!i) { + output = open(filepath, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (output < 0) { + err = -13; + goto end; + } + } + if (write(output, data, transfersize) < 0) { + err = -10; + goto close_output; + } + } else { + if (write(output, data, last_xfer_size) < 0) { + err = -10; + goto close_output; + } + } + offset += transfersize; + printf("%d%%\r", (i + 1) * 100 / total_loop_cnt); + } + printf("100%%\nThe log file was saved at \"%s\"\n", filepath); + +close_output: + close(output); + +end: + free(data); + return err; +} + +static int get_telemetry_dump(struct nvme_dev *dev, char *filename, char *sn, + enum TELEMETRY_TYPE tele_type, int data_area, bool header_print) +{ + __u32 err = 0, nsid = 0; + __u8 lsp = 0, rae = 0; + unsigned int i = 0; + char data[TELEMETRY_TRANSFER_SIZE] = { 0 }; + char data1[1536] = { 0 }; + char *featurename = 0; + struct telemetry_initiated_log *logheader = (struct telemetry_initiated_log *)data; + struct telemetry_data_area_1 *da1 = (struct telemetry_data_area_1 *)data1; + __u64 offset = 0, size = 0; + char dumpname[FILE_NAME_SIZE] = { 0 }; + + if (tele_type == TELEMETRY_TYPE_HOST_0) { + featurename = "Host(0)"; + lsp = 0; + rae = 0; + tele_type = TELEMETRY_TYPE_HOST; + } else if (tele_type == TELEMETRY_TYPE_HOST_1) { + featurename = "Host(1)"; + lsp = 1; + rae = 0; + tele_type = TELEMETRY_TYPE_HOST; + } else { + featurename = "Controller"; + lsp = 0; + rae = 1; + } + + err = get_telemetry_header(dev, nsid, tele_type, TELEMETRY_HEADER_SIZE, + (void *)data, lsp, rae); + if (err) + return err; + + if (header_print) + print_telemetry_header(logheader, tele_type); + err = get_telemetry_data(dev, nsid, tele_type, 1536, + (void *)data1, lsp, rae, 512); + if (err) + return err; + print_telemetry_data_area_1(da1, tele_type); + char *da1_stat = calloc((da1->da1_stat_size * 4), sizeof(char)); + err = get_telemetry_data(dev, nsid, tele_type, (da1->da1_stat_size) * 4, + (void *)da1_stat, lsp, rae, (da1->da1_stat_start) * 4); + if (err) + return err; + print_telemetry_da1_stat((void *)da1_stat, tele_type, (da1->da1_stat_size) * 4); + for (i = 0; i < 17 ; i++){ + if (da1->event_fifo_da[i] == 1){ + char *da1_fifo = calloc((da1->event_fifo_size[i]) * 4, sizeof(char)); + err = get_telemetry_data(dev, nsid, tele_type, (da1->event_fifo_size[i]) * 4, + (void *)da1_stat, lsp, rae, (da1->event_fifo_start[i]) * 4); + if (err) + return err; + print_telemetry_da1_fifo((void *)da1_fifo, tele_type, (da1->event_fifo_size[i]) * 4); + } + } + char *da2_stat = calloc((da1->da2_stat_size * 4), sizeof(char)); + err = get_telemetry_data(dev, nsid, tele_type, (da1->da2_stat_size) * 4, + (void *)da2_stat, lsp, rae, (da1->da2_stat_start) * 4); + if (err) + return err; + print_telemetry_da2_stat((void *)da2_stat, tele_type, (da1->da2_stat_size) * 4); + for (i = 0; i < 17 ; i++){ + if (da1->event_fifo_da[i] == 2){ + char *da1_fifo = calloc((da1->event_fifo_size[i]) * 4, sizeof(char)); + err = get_telemetry_data(dev, nsid, tele_type, (da1->event_fifo_size[i]) * 4, + (void *)da1_stat, lsp, rae, (da1->event_fifo_start[i]) * 4); + if (err) + return err; + print_telemetry_da2_fifo((void *)da1_fifo, tele_type, (da1->event_fifo_size[i]) * 4); + } + } + + switch (data_area) { + case 1: + offset = TELEMETRY_HEADER_SIZE; + size = le16_to_cpu(logheader->DataArea1LastBlock); + break; + case 2: + offset = TELEMETRY_HEADER_SIZE + + (le16_to_cpu(logheader->DataArea1LastBlock) * TELEMETRY_BYTE_PER_BLOCK); + size = le16_to_cpu(logheader->DataArea2LastBlock) + - le16_to_cpu(logheader->DataArea1LastBlock); + break; + case 3: + offset = TELEMETRY_HEADER_SIZE + + (le16_to_cpu(logheader->DataArea2LastBlock) * TELEMETRY_BYTE_PER_BLOCK); + size = le16_to_cpu(logheader->DataArea3LastBlock) + - le16_to_cpu(logheader->DataArea2LastBlock); + break; + default: + break; + } + + if (!size) { + printf("Telemetry %s Area %d is empty.\n", featurename, data_area); + return err; + } + + snprintf(dumpname, FILE_NAME_SIZE, + "Telemetry_%s_Area_%d", featurename, data_area); + err = extract_dump_get_log(dev, dumpname, filename, sn, size * TELEMETRY_BYTE_PER_BLOCK, + TELEMETRY_TRANSFER_SIZE, nsid, tele_type, + 0, offset, rae); + + return err; +} + +static int ocp_telemetry_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + struct nvme_dev *dev; + int err = 0; + const char *desc = "Retrieve and save telemetry log."; + const char *type = "Telemetry Type; 'host[Create bit]' or 'controller'"; + const char *area = "Telemetry Data Area; 1 or 3"; + const char *file = "Output file name with path;\n" + "e.g. '-o ./path/name'\n'-o ./path1/path2/';\n" + "If requested path does not exist, the directory will be newly created."; + + __u32 nsid = NVME_NSID_ALL; + struct stat nvme_stat; + char sn[21] = {0,}; + struct nvme_id_ctrl ctrl; + bool is_support_telemetry_controller; + + int tele_type = 0; + int tele_area = 0; + + struct config { + char *type; + int area; + char *file; + }; + + struct config cfg = { + .type = NULL, + .area = 0, + .file = NULL, + }; + + OPT_ARGS(opts) = { + OPT_STR("telemetry_type", 't', &cfg.type, type), + OPT_INT("telemetry_data_area", 'a', &cfg.area, area), + OPT_FILE("output-file", 'o', &cfg.file, file), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = fstat(dev_fd(dev), &nvme_stat); + if (err < 0) + return err; + + if (S_ISBLK(nvme_stat.st_mode)) { + err = nvme_get_nsid(dev_fd(dev), &nsid); + if (err < 0) + return err; + } + + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (err) + return err; + + get_serial_number(&ctrl, sn); + + is_support_telemetry_controller = ((ctrl.lpa & 0x8) >> 3); + + if (!cfg.type && !cfg.area) { + tele_type = TELEMETRY_TYPE_NONE; + tele_area = 0; + } else if (cfg.type && cfg.area) { + if (!strcmp(cfg.type, "host0")) + tele_type = TELEMETRY_TYPE_HOST_0; + else if (!strcmp(cfg.type, "host1")) + tele_type = TELEMETRY_TYPE_HOST_1; + else if (!strcmp(cfg.type, "controller")) + tele_type = TELEMETRY_TYPE_CONTROLLER; + + tele_area = cfg.area; + + if ((tele_area != 1 && tele_area != 3) || + (tele_type == TELEMETRY_TYPE_CONTROLLER && tele_area != 3)) { + printf("\nUnsupported parameters entered.\n"); + printf("Possible combinations; {'host0',1}, {'host0',3}, {'host1',1}, {'host1',3}, {'controller',3}\n"); + return err; + } + } else { + printf("\nShould provide these all; 'telemetry_type' and 'telemetry_data_area'\n"); + return err; + } + + if (tele_type == TELEMETRY_TYPE_NONE) { + printf("\n-------------------------------------------------------------\n"); + /* Host 0 (lsp == 0) must be executed before Host 1 (lsp == 1). */ + printf("\nExtracting Telemetry Host 0 Dump (Data Area 1)...\n"); + + err = get_telemetry_dump(dev, cfg.file, sn, + TELEMETRY_TYPE_HOST_0, 1, true); + if (err) + fprintf(stderr, "NVMe Status: %s(%x)\n", nvme_status_to_string(err, false), err); + + printf("\n-------------------------------------------------------------\n"); + + printf("\nExtracting Telemetry Host 0 Dump (Data Area 3)...\n"); + + err = get_telemetry_dump(dev, cfg.file, sn, + TELEMETRY_TYPE_HOST_0, 3, false); + if (err) + fprintf(stderr, "NVMe Status: %s(%x)\n", nvme_status_to_string(err, false), err); + + printf("\n-------------------------------------------------------------\n"); + + printf("\nExtracting Telemetry Host 1 Dump (Data Area 1)...\n"); + + err = get_telemetry_dump(dev, cfg.file, sn, + TELEMETRY_TYPE_HOST_1, 1, true); + if (err) + fprintf(stderr, "NVMe Status: %s(%x)\n", nvme_status_to_string(err, false), err); + + printf("\n-------------------------------------------------------------\n"); + + printf("\nExtracting Telemetry Host 1 Dump (Data Area 3)...\n"); + + err = get_telemetry_dump(dev, cfg.file, sn, + TELEMETRY_TYPE_HOST_1, 3, false); + if (err) + fprintf(stderr, "NVMe Status: %s(%x)\n", nvme_status_to_string(err, false), err); + + printf("\n-------------------------------------------------------------\n"); + + printf("\nExtracting Telemetry Controller Dump (Data Area 3)...\n"); + + if (is_support_telemetry_controller == true) { + err = get_telemetry_dump(dev, cfg.file, sn, + TELEMETRY_TYPE_CONTROLLER, 3, true); + if (err) + fprintf(stderr, "NVMe Status: %s(%x)\n", nvme_status_to_string(err, false), err); + } + + printf("\n-------------------------------------------------------------\n"); + } else if (tele_type == TELEMETRY_TYPE_CONTROLLER) { + printf("Extracting Telemetry Controller Dump (Data Area %d)...\n", tele_area); + + if (is_support_telemetry_controller == true) { + err = get_telemetry_dump(dev, cfg.file, sn, tele_type, tele_area, true); + if (err) + fprintf(stderr, "NVMe Status: %s(%x)\n", nvme_status_to_string(err, false), err); + } + } else { + printf("Extracting Telemetry Host(%d) Dump (Data Area %d)...\n", + (tele_type == TELEMETRY_TYPE_HOST_0) ? 0 : 1, tele_area); + + err = get_telemetry_dump(dev, cfg.file, sn, tele_type, tele_area, true); + if (err) + fprintf(stderr, "NVMe Status: %s(%x)\n", nvme_status_to_string(err, false), err); + } + + printf("telemetry-log done.\n"); + +return err; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/// Unsupported Requirement Log Page (LID : C5h) + +/* C5 Unsupported Requirement Log Page */ +#define C5_GUID_LENGTH 16 +#define C5_UNSUPPORTED_REQS_LEN 4096 +#define C5_UNSUPPORTED_REQS_OPCODE 0xC5 +#define C5_UNSUPPORTED_REQS_LOG_VERSION 0x1 +#define C5_NUM_UNSUPPORTED_REQ_ENTRIES 253 + +static __u8 unsupported_req_guid[C5_GUID_LENGTH] = { + 0x2F, 0x72, 0x9C, 0x0E, + 0x99, 0x23, 0x2C, 0xBB, + 0x63, 0x48, 0x32, 0xD0, + 0xB7, 0x98, 0xBB, 0xC7 +}; + +/* + * struct unsupported_requirement_log - unsupported requirement list + * @unsupported_count: Number of Unsupported Requirement IDs + * @rsvd1: Reserved + * @unsupported_req_list: Unsupported Requirements lists upto 253. + * @rsvd2: Reserved + * @log_page_version: indicates the version of the mapping this log page uses. + * Shall be set to 0001h + * @log_page_guid: Shall be set to C7BB98B7D0324863BB2C23990E9C722Fh. + */ +struct __packed unsupported_requirement_log { + __le16 unsupported_count; + __u8 rsvd1[14]; + __u8 unsupported_req_list[C5_NUM_UNSUPPORTED_REQ_ENTRIES][16]; + __u8 rsvd2[14]; + __le16 log_page_version; + __u8 log_page_guid[C5_GUID_LENGTH]; +}; + +/* Function declaration for unsupported requirement log page (LID:C5h) */ +static int ocp_unsupported_requirements_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin); + +static int ocp_print_C5_log_normal(struct nvme_dev *dev, + struct unsupported_requirement_log *log_data) +{ + int j; + + printf("Unsupported Requirement-C5 Log Page Data-\n"); + + printf(" Number Unsupported Req IDs : 0x%x\n", le16_to_cpu(log_data->unsupported_count)); + + for (j = 0; j < le16_to_cpu(log_data->unsupported_count); j++) + printf(" Unsupported Requirement List %d : %s\n", j, log_data->unsupported_req_list[j]); + + printf(" Log Page Version : 0x%x\n", le16_to_cpu(log_data->log_page_version)); + printf(" Log page GUID : 0x"); + for (j = C5_GUID_LENGTH - 1; j >= 0; j--) + printf("%x", log_data->log_page_guid[j]); + printf("\n"); + + return 0; +} + +static void ocp_print_C5_log_json(struct unsupported_requirement_log *log_data) +{ + int j; + struct json_object *root; + char unsup_req_list_str[40]; + char guid_buf[C5_GUID_LENGTH]; + char *guid = guid_buf; + + root = json_create_object(); + + json_object_add_value_int(root, "Number Unsupported Req IDs", le16_to_cpu(log_data->unsupported_count)); + + memset((void *)unsup_req_list_str, 0, 40); + for (j = 0; j < le16_to_cpu(log_data->unsupported_count); j++) { + sprintf((char *)unsup_req_list_str, "Unsupported Requirement List %d", j); + json_object_add_value_string(root, unsup_req_list_str, (char *)log_data->unsupported_req_list[j]); + } + + json_object_add_value_int(root, "Log Page Version", le16_to_cpu(log_data->log_page_version)); + + memset((void *)guid, 0, C5_GUID_LENGTH); + for (j = C5_GUID_LENGTH - 1; j >= 0; j--) + guid += sprintf(guid, "%02x", log_data->log_page_guid[j]); + json_object_add_value_string(root, "Log page GUID", guid_buf); + + json_print_object(root, NULL); + printf("\n"); + + json_free_object(root); +} + +static void ocp_print_c5_log_binary(struct unsupported_requirement_log *log_data) +{ + return d_raw((unsigned char *)log_data, sizeof(*log_data)); +} + +static int get_c5_log_page(struct nvme_dev *dev, char *format) +{ + enum nvme_print_flags fmt; + int ret; + __u8 *data; + int i; + struct unsupported_requirement_log *log_data; + int j; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR : OCP : invalid output format\n"); + return ret; + } + + data = (__u8 *)malloc(sizeof(__u8) * C5_UNSUPPORTED_REQS_LEN); + if (!data) { + fprintf(stderr, "ERROR : OCP : malloc : %s\n", strerror(errno)); + return -1; + } + memset(data, 0, sizeof(__u8) * C5_UNSUPPORTED_REQS_LEN); + + ret = nvme_get_log_simple(dev_fd(dev), C5_UNSUPPORTED_REQS_OPCODE, + C5_UNSUPPORTED_REQS_LEN, data); + if (!ret) { + log_data = (struct unsupported_requirement_log *)data; + + /* check log page version */ + if (log_data->log_page_version != C5_UNSUPPORTED_REQS_LOG_VERSION) { + fprintf(stderr, "ERROR : OCP : invalid unsupported requirement version\n"); + ret = -1; + goto out; + } + + /* + * check log page guid + * Verify GUID matches + */ + for (i = 0; i < 16; i++) { + if (unsupported_req_guid[i] != log_data->log_page_guid[i]) { + fprintf(stderr, "ERROR : OCP : Unknown GUID in C5 Log Page data\n"); + fprintf(stderr, "ERROR : OCP : Expected GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", unsupported_req_guid[j]); + fprintf(stderr, "\nERROR : OCP : Actual GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", log_data->log_page_guid[j]); + fprintf(stderr, "\n"); + + ret = -1; + goto out; + } + } + + switch (fmt) { + case NORMAL: + ocp_print_C5_log_normal(dev, log_data); + break; + case JSON: + ocp_print_C5_log_json(log_data); + break; + case BINARY: + ocp_print_c5_log_binary(log_data); + break; + default: + break; + } + } else { + fprintf(stderr, "ERROR : OCP : Unable to read C3 data from buffer\n"); + } + +out: + free(data); + return ret; +} + + +static int ocp_unsupported_requirements_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "Retrieve unsupported requirements log data."; + struct nvme_dev *dev; + int ret = 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + ret = get_c5_log_page(dev, cfg.output_format); + if (ret) + fprintf(stderr, "ERROR : OCP : Failure reading the C5 Log Page, ret = %d\n", ret); + + dev_close(dev); + return ret; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/// Error Recovery Log Page(0xC1) + +#define C1_ERROR_RECOVERY_LOG_BUF_LEN 0x200 +#define C1_ERROR_RECOVERY_OPCODE 0xC1 +#define C1_ERROR_RECOVERY_VERSION 0x0002 +#define C1_GUID_LENGTH 16 +static __u8 error_recovery_guid[C1_GUID_LENGTH] = { + 0x44, 0xd9, 0x31, 0x21, + 0xfe, 0x30, 0x34, 0xae, + 0xab, 0x4d, 0xfd, 0x3d, + 0xba, 0x83, 0x19, 0x5a +}; + +/** + * struct ocp_error_recovery_log_page - Error Recovery Log Page + * @panic_reset_wait_time: Panic Reset Wait Time + * @panic_reset_action: Panic Reset Action + * @device_recover_action_1: Device Recovery Action 1 + * @panic_id: Panic ID + * @device_capabilities: Device Capabilities + * @vendor_specific_recovery_opcode: Vendor Specific Recovery Opcode + * @reserved: Reserved + * @vendor_specific_command_cdw12: Vendor Specific Command CDW12 + * @vendor_specific_command_cdw13: Vendor Specific Command CDW13 + * @vendor_specific_command_timeout: Vendor Specific Command Timeout + * @device_recover_action_2: Device Recovery Action 2 + * @device_recover_action_2_timeout: Device Recovery Action 2 Timeout + * @reserved2: Reserved + * @log_page_version: Log Page Version + * @log_page_guid: Log Page GUID + */ +struct __packed ocp_error_recovery_log_page { + __le16 panic_reset_wait_time; /* 2 bytes - 0x00 - 0x01 */ + __u8 panic_reset_action; /* 1 byte - 0x02 */ + __u8 device_recover_action_1; /* 1 byte - 0x03 */ + __le64 panic_id; /* 8 bytes - 0x04 - 0x0B */ + __le32 device_capabilities; /* 4 bytes - 0x0C - 0x0F */ + __u8 vendor_specific_recovery_opcode; /* 1 byte - 0x10 */ + __u8 reserved[0x3]; /* 3 bytes - 0x11 - 0x13 */ + __le32 vendor_specific_command_cdw12; /* 4 bytes - 0x14 - 0x17 */ + __le32 vendor_specific_command_cdw13; /* 4 bytes - 0x18 - 0x1B */ + __u8 vendor_specific_command_timeout; /* 1 byte - 0x1C */ + __u8 device_recover_action_2; /* 1 byte - 0x1D */ + __u8 device_recover_action_2_timeout; /* 1 byte - 0x1E */ + __u8 reserved2[0x1cf]; /* 463 bytes - 0x1F - 0x1ED */ + __le16 log_page_version; /* 2 bytes - 0x1EE - 0x1EF */ + __u8 log_page_guid[0x10]; /* 16 bytes - 0x1F0 - 0x1FF */ +}; + +static void ocp_print_c1_log_normal(struct ocp_error_recovery_log_page *log_data); +static void ocp_print_c1_log_json(struct ocp_error_recovery_log_page *log_data); +static void ocp_print_c1_log_binary(struct ocp_error_recovery_log_page *log_data); +static int get_c1_log_page(struct nvme_dev *dev, char *format); +static int ocp_error_recovery_log(int argc, char **argv, struct command *cmd, struct plugin *plugin); + +static void ocp_print_c1_log_normal(struct ocp_error_recovery_log_page *log_data) +{ + int i; + + printf(" Error Recovery/C1 Log Page Data\n"); + printf(" Panic Reset Wait Time : 0x%x\n", le16_to_cpu(log_data->panic_reset_wait_time)); + printf(" Panic Reset Action : 0x%x\n", log_data->panic_reset_action); + printf(" Device Recovery Action 1 : 0x%x\n", log_data->device_recover_action_1); + printf(" Panic ID : 0x%x\n", le32_to_cpu(log_data->panic_id)); + printf(" Device Capabilities : 0x%x\n", le32_to_cpu(log_data->device_capabilities)); + printf(" Vendor Specific Recovery Opcode : 0x%x\n", log_data->vendor_specific_recovery_opcode); + printf(" Vendor Specific Command CDW12 : 0x%x\n", le32_to_cpu(log_data->vendor_specific_command_cdw12)); + printf(" Vendor Specific Command CDW13 : 0x%x\n", le32_to_cpu(log_data->vendor_specific_command_cdw13)); + printf(" Vendor Specific Command Timeout : 0x%x\n", log_data->vendor_specific_command_timeout); + printf(" Device Recovery Action 2 : 0x%x\n", log_data->device_recover_action_2); + printf(" Device Recovery Action 2 Timeout : 0x%x\n", log_data->device_recover_action_2_timeout); + printf(" Log Page Version : 0x%x\n", le16_to_cpu(log_data->log_page_version)); + printf(" Log page GUID : 0x"); + for (i = C1_GUID_LENGTH - 1; i >= 0; i--) + printf("%x", log_data->log_page_guid[i]); + printf("\n"); +} + +static void ocp_print_c1_log_json(struct ocp_error_recovery_log_page *log_data) +{ + struct json_object *root; + + root = json_create_object(); + char guid[64]; + + json_object_add_value_int(root, "Panic Reset Wait Time", le16_to_cpu(log_data->panic_reset_wait_time)); + json_object_add_value_int(root, "Panic Reset Action", log_data->panic_reset_action); + json_object_add_value_int(root, "Device Recovery Action 1", log_data->device_recover_action_1); + json_object_add_value_int(root, "Panic ID", le32_to_cpu(log_data->panic_id)); + json_object_add_value_int(root, "Device Capabilities", le32_to_cpu(log_data->device_capabilities)); + json_object_add_value_int(root, "Vendor Specific Recovery Opcode", log_data->vendor_specific_recovery_opcode); + json_object_add_value_int(root, "Vendor Specific Command CDW12", le32_to_cpu(log_data->vendor_specific_command_cdw12)); + json_object_add_value_int(root, "Vendor Specific Command CDW13", le32_to_cpu(log_data->vendor_specific_command_cdw13)); + json_object_add_value_int(root, "Vendor Specific Command Timeout", log_data->vendor_specific_command_timeout); + json_object_add_value_int(root, "Device Recovery Action 2", log_data->device_recover_action_2); + json_object_add_value_int(root, "Device Recovery Action 2 Timeout", log_data->device_recover_action_2_timeout); + json_object_add_value_int(root, "Log Page Version", le16_to_cpu(log_data->log_page_version)); + + memset((void *)guid, 0, 64); + sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"", (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[8]), + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[0])); + json_object_add_value_string(root, "Log page GUID", guid); + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static void ocp_print_c1_log_binary(struct ocp_error_recovery_log_page *log_data) +{ + return d_raw((unsigned char *)log_data, sizeof(*log_data)); +} + +static int get_c1_log_page(struct nvme_dev *dev, char *format) +{ + struct ocp_error_recovery_log_page *log_data; + enum nvme_print_flags fmt; + int ret; + __u8 *data; + int i, j; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR : OCP : invalid output format\n"); + return ret; + } + + data = (__u8 *)malloc(sizeof(__u8) * C1_ERROR_RECOVERY_LOG_BUF_LEN); + if (!data) { + fprintf(stderr, "ERROR : OCP : malloc : %s\n", strerror(errno)); + return -1; + } + memset(data, 0, sizeof(__u8) * C1_ERROR_RECOVERY_LOG_BUF_LEN); + + ret = nvme_get_log_simple(dev_fd(dev), C1_ERROR_RECOVERY_OPCODE, C1_ERROR_RECOVERY_LOG_BUF_LEN, data); + + if (!ret) { + log_data = (struct ocp_error_recovery_log_page *)data; + + /* check log page version */ + if (log_data->log_page_version != C1_ERROR_RECOVERY_VERSION) { + fprintf(stderr, "ERROR : OCP : invalid error recovery log page version\n"); + ret = -1; + goto out; + } + + /* + * check log page guid + * Verify GUID matches + */ + for (i = 0; i < 16; i++) { + if (error_recovery_guid[i] != log_data->log_page_guid[i]) { + fprintf(stderr, "ERROR : OCP : Unknown GUID in C1 Log Page data\n"); + fprintf(stderr, "ERROR : OCP : Expected GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", error_recovery_guid[j]); + fprintf(stderr, "\nERROR : OCP : Actual GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", log_data->log_page_guid[j]); + fprintf(stderr, "\n"); + + ret = -1; + goto out; + } + } + + switch (fmt) { + case NORMAL: + ocp_print_c1_log_normal(log_data); + break; + case JSON: + ocp_print_c1_log_json(log_data); + break; + case BINARY: + ocp_print_c1_log_binary(log_data); + break; + default: + break; + } + } else { + fprintf(stderr, "ERROR : OCP : Unable to read C1 data from buffer\n"); + } + +out: + free(data); + return ret; +} + +static int ocp_error_recovery_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Retrieve C1h Error Recovery Log data."; + struct nvme_dev *dev; + int ret = 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|binary"), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + ret = get_c1_log_page(dev, cfg.output_format); + if (ret) + fprintf(stderr, "ERROR : OCP : Failure reading the C1h Log Page, ret = %d\n", ret); + dev_close(dev); + return ret; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/// Device Capabilities (Log Identifier C4h) Requirements + +#define C4_DEV_CAP_REQ_LEN 0x1000 +#define C4_DEV_CAP_REQ_OPCODE 0xC4 +#define C4_DEV_CAP_REQ_VERSION 0x0001 +#define C4_GUID_LENGTH 16 +static __u8 dev_cap_req_guid[C4_GUID_LENGTH] = { + 0x97, 0x42, 0x05, 0x0d, + 0xd1, 0xe1, 0xc9, 0x98, + 0x5d, 0x49, 0x58, 0x4b, + 0x91, 0x3c, 0x05, 0xb7 +}; + +/** + * struct ocp_device_capabilities_log_page - Device Capability Log page + * @pcie_exp_port: PCI Express Ports + * @oob_management_support: OOB Management Support + * @wz_cmd_support: Write Zeroes Command Support + * @sanitize_cmd_support: Sanitize Command Support + * @dsm_cmd_support: Dataset Management Command Support + * @wu_cmd_support: Write Uncorrectable Command Support + * @fused_operation_support: Fused Operation Support + * @min_valid_dssd_pwr_state: Minimum Valid DSSD Power State + * @dssd_pwr_state_desc: DSSD Power State Descriptors + * @vendor_specific_command_timeout: Vendor Specific Command Timeout + * @reserved: Reserved + * @log_page_version: Log Page Version + * @log_page_guid: Log Page GUID + */ +struct __packed ocp_device_capabilities_log_page { + __le16 pcie_exp_port; + __le16 oob_management_support; + __le16 wz_cmd_support; + __le16 sanitize_cmd_support; + __le16 dsm_cmd_support; + __le16 wu_cmd_support; + __le16 fused_operation_support; + __le16 min_valid_dssd_pwr_state; + __u8 dssd_pwr_state_desc[128]; + __u8 reserved[3934]; + __le16 log_page_version; + __u8 log_page_guid[16]; +}; + +static void ocp_print_c4_log_normal(struct ocp_device_capabilities_log_page *log_data); +static void ocp_print_c4_log_json(struct ocp_device_capabilities_log_page *log_data); +static void ocp_print_c4_log_binary(struct ocp_device_capabilities_log_page *log_data); +static int get_c4_log_page(struct nvme_dev *dev, char *format); +static int ocp_device_capabilities_log(int argc, char **argv, struct command *cmd, struct plugin *plugin); + +static void ocp_print_c4_log_normal(struct ocp_device_capabilities_log_page *log_data) +{ + int i; + + printf(" Device Capability/C4 Log Page Data\n"); + printf(" PCI Express Ports : 0x%x\n", le16_to_cpu(log_data->pcie_exp_port)); + printf(" OOB Management Support : 0x%x\n", le16_to_cpu(log_data->oob_management_support)); + printf(" Write Zeroes Command Support : 0x%x\n", le16_to_cpu(log_data->wz_cmd_support)); + printf(" Sanitize Command Support : 0x%x\n", le16_to_cpu(log_data->sanitize_cmd_support)); + printf(" Dataset Management Command Support : 0x%x\n", le16_to_cpu(log_data->dsm_cmd_support)); + printf(" Write Uncorrectable Command Support : 0x%x\n", le16_to_cpu(log_data->wu_cmd_support)); + printf(" Fused Operation Support : 0x%x\n", le16_to_cpu(log_data->fused_operation_support)); + printf(" Minimum Valid DSSD Power State : 0x%x\n", le16_to_cpu(log_data->min_valid_dssd_pwr_state)); + printf(" DSSD Power State Descriptors : 0x"); + for (i = 0; i <= 127; i++) + printf("%x", log_data->dssd_pwr_state_desc[i]); + printf("\n"); + printf(" Log Page Version : 0x%x\n", le16_to_cpu(log_data->log_page_version)); + printf(" Log page GUID : 0x"); + for (i = C4_GUID_LENGTH - 1; i >= 0; i--) + printf("%x", log_data->log_page_guid[i]); + printf("\n"); +} + +static void ocp_print_c4_log_json(struct ocp_device_capabilities_log_page *log_data) +{ + struct json_object *root = json_create_object(); + char guid[64]; + int i; + + json_object_add_value_int(root, "PCI Express Ports", le16_to_cpu(log_data->pcie_exp_port)); + json_object_add_value_int(root, "OOB Management Support", le16_to_cpu(log_data->oob_management_support)); + json_object_add_value_int(root, "Write Zeroes Command Support", le16_to_cpu(log_data->wz_cmd_support)); + json_object_add_value_int(root, "Sanitize Command Support", le16_to_cpu(log_data->sanitize_cmd_support)); + json_object_add_value_int(root, "Dataset Management Command Support", le16_to_cpu(log_data->dsm_cmd_support)); + json_object_add_value_int(root, "Write Uncorrectable Command Support", le16_to_cpu(log_data->wu_cmd_support)); + json_object_add_value_int(root, "Fused Operation Support", le16_to_cpu(log_data->fused_operation_support)); + json_object_add_value_int(root, "Minimum Valid DSSD Power State", le16_to_cpu(log_data->min_valid_dssd_pwr_state)); + for (i = 0; i <= 127; i++) + json_object_add_value_int(root, "DSSD Power State Descriptors", log_data->dssd_pwr_state_desc[i]); + json_object_add_value_int(root, "Log Page Version", le16_to_cpu(log_data->log_page_version)); + + memset((void *)guid, 0, 64); + sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"", (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[8]), + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[0])); + json_object_add_value_string(root, "Log page GUID", guid); + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static void ocp_print_c4_log_binary(struct ocp_device_capabilities_log_page *log_data) +{ + return d_raw((unsigned char *)log_data, sizeof(*log_data)); +} + +static int get_c4_log_page(struct nvme_dev *dev, char *format) +{ + struct ocp_device_capabilities_log_page *log_data; + enum nvme_print_flags fmt; + int ret; + __u8 *data; + int i, j; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR : OCP : invalid output format\n"); + return ret; + } + + data = (__u8 *)malloc(sizeof(__u8) * C4_DEV_CAP_REQ_LEN); + if (!data) { + fprintf(stderr, "ERROR : OCP : malloc : %s\n", strerror(errno)); + return -1; + } + memset(data, 0, sizeof(__u8) * C4_DEV_CAP_REQ_LEN); + + ret = nvme_get_log_simple(dev_fd(dev), C4_DEV_CAP_REQ_OPCODE, C4_DEV_CAP_REQ_LEN, data); + + if (!ret) { + log_data = (struct ocp_device_capabilities_log_page *)data; + + /* check log page version */ + if (log_data->log_page_version != C4_DEV_CAP_REQ_VERSION) { + fprintf(stderr, "ERROR : OCP : invalid device capabilities log page version\n"); + ret = -1; + goto out; + } + + /* + * check log page guid + * Verify GUID matches + */ + for (i = 0; i < 16; i++) { + if (dev_cap_req_guid[i] != log_data->log_page_guid[i]) { + fprintf(stderr, "ERROR : OCP : Unknown GUID in C4 Log Page data\n"); + fprintf(stderr, "ERROR : OCP : Expected GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", dev_cap_req_guid[j]); + fprintf(stderr, "\nERROR : OCP : Actual GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", log_data->log_page_guid[j]); + fprintf(stderr, "\n"); + + ret = -1; + goto out; + } + } + + switch (fmt) { + case NORMAL: + ocp_print_c4_log_normal(log_data); + break; + case JSON: + ocp_print_c4_log_json(log_data); + break; + case BINARY: + ocp_print_c4_log_binary(log_data); + break; + default: + break; + } + } else { + fprintf(stderr, "ERROR : OCP : Unable to read C4 data from buffer\n"); + } + +out: + free(data); + return ret; +} + +static int ocp_device_capabilities_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Retrieve C4h Device Capabilities Log data."; + struct nvme_dev *dev; + int ret = 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|binary"), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + ret = get_c4_log_page(dev, cfg.output_format); + if (ret) + fprintf(stderr, "ERROR : OCP : Failure reading the C4h Log Page, ret = %d\n", ret); + dev_close(dev); + return ret; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/// DSSD Power State (Feature Identifier C7h) Set Feature + +static int set_dssd_power_state(struct nvme_dev *dev, const __u32 nsid, + const __u8 fid, __u8 power_state, bool save, + bool uuid) +{ + __u32 result; + int err; + int uuid_index = 0; + + if (uuid) { + /* OCP 2.0 requires UUID index support */ + err = ocp_get_uuid_index(dev, &uuid_index); + if (err || !uuid_index) { + nvme_show_error("ERROR: No OCP UUID index found"); + return err; + } + } + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = fid, + .nsid = nsid, + .cdw11 = power_state, + .cdw12 = 0, + .save = save, + .uuidx = uuid_index, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + err = nvme_set_features(&args); + if (err > 0) { + nvme_show_status(err); + } else if (err < 0) { + nvme_show_perror("Define DSSD Power State"); + fprintf(stderr, "Command failed while parsing.\n"); + } else { + printf("Successfully set DSSD Power State (feature: 0xC7) to below values\n"); + printf("DSSD Power State: 0x%x\n", power_state); + printf("Save bit Value: 0x%x\n", save); + } + + return err; +} + +static int set_dssd_power_state_feature(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "Define DSSD Power State (Feature Identifier C7h) Set Feature."; + const char *power_state = "DSSD Power State to set in watts"; + const char *save = "Specifies that the controller shall save the attribute"; + const __u32 nsid = 0; + const __u8 fid = 0xC7; + struct nvme_dev *dev; + int err; + + struct config { + __u8 power_state; + bool save; + }; + + struct config cfg = { + .power_state = 0, + .save = false, + }; + + OPT_ARGS(opts) = { + OPT_BYTE("power-state", 'p', &cfg.power_state, power_state), + OPT_FLAG("save", 's', &cfg.save, save), + OPT_FLAG("no-uuid", 'n', NULL, + "Skip UUID index search (UUID index not required for OCP 1.0)"), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (argconfig_parse_seen(opts, "power state")) + err = set_dssd_power_state(dev, nsid, fid, cfg.power_state, + cfg.save, + !argconfig_parse_seen(opts, "no-uuid")); + + dev_close(dev); + + return err; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/// plp_health_check_interval + +static int set_plp_health_check_interval(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + + const char *desc = "Define Issue Set Feature command (FID : 0xC6) PLP Health Check Interval"; + const char *plp_health_interval = "[31:16]:PLP Health Check Interval"; + const char *save = "Specifies that the controller shall save the attribute"; + const __u32 nsid = 0; + const __u8 fid = 0xc6; + struct nvme_dev *dev; + int err; + __u32 result; + int uuid_index = 0; + + struct config { + __le16 plp_health_interval; + bool save; + }; + + struct config cfg = { + .plp_health_interval = 0, + .save = false, + }; + + OPT_ARGS(opts) = { + OPT_BYTE("plp_health_interval", 'p', &cfg.plp_health_interval, plp_health_interval), + OPT_FLAG("save", 's', &cfg.save, save), + OPT_FLAG("no-uuid", 'n', NULL, + "Skip UUID index search (UUID index not required for OCP 1.0)"), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + + if (!argconfig_parse_seen(opts, "no-uuid")) { + /* OCP 2.0 requires UUID index support */ + err = ocp_get_uuid_index(dev, &uuid_index); + if (err || !uuid_index) { + printf("ERROR: No OCP UUID index found"); + return err; + } + } + + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = fid, + .nsid = nsid, + .cdw11 = cfg.plp_health_interval << 16, + .cdw12 = 0, + .save = cfg.save, + .uuidx = uuid_index, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + err = nvme_set_features(&args); + if (err > 0) { + nvme_show_status(err); + } else if (err < 0) { + nvme_show_perror("Define PLP Health Check Interval"); + fprintf(stderr, "Command failed while parsing.\n"); + } else { + printf("Successfully set the PLP Health Check Interval"); + printf("PLP Health Check Interval: 0x%x\n", cfg.plp_health_interval); + printf("Save bit Value: 0x%x\n", cfg.save); + } + return err; +} + +static int get_plp_health_check_interval(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + + const char *desc = "Define Issue Get Feature command (FID : 0xC6) PLP Health Check Interval"; + const char *sel = "[0-3,8]: current/default/saved/supported/changed"; + const __u32 nsid = 0; + const __u8 fid = 0xc6; + struct nvme_dev *dev; + __u32 result; + int err; + + struct config { + __u8 sel; + }; + + struct config cfg = { + .sel = 0, + }; + + OPT_ARGS(opts) = { + OPT_BYTE("sel", 'S', &cfg.sel, sel), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = fid, + .nsid = nsid, + .sel = cfg.sel, + .cdw11 = 0, + .uuidx = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + err = nvme_get_features(&args); + if (!err) { + printf("get-feature:0xC6 %s value: %#08x\n", nvme_select_to_string(cfg.sel), result); + + if (cfg.sel == NVME_GET_FEATURES_SEL_SUPPORTED) + nvme_show_select_result(fid, result); + } else { + nvme_show_error("Could not get feature: 0xC6"); + } + + return err; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/// Telemetry String Log Format Log Page (LID : C9h) + +/* C9 Telemetry String Log Format Log Page */ +#define C9_GUID_LENGTH 16 +#define C9_TELEMETRY_STRING_LOG_ENABLE_OPCODE 0xC9 +#define C9_TELEMETRY_STR_LOG_LEN 432 +#define C9_TELEMETRY_STR_LOG_SIST_OFST 431 + +/** + * struct telemetry_str_log_format - Telemetry String Log Format + * @log_page_version: indicates the version of the mapping this log page uses + * Shall be set to 01h. + * @reserved1: Reserved. + * @log_page_guid: Shall be set to B13A83691A8F408B9EA495940057AA44h. + * @sls: Shall be set to the number of DWORDS in the String Log. + * @reserved2: reserved. + * @sits: shall be set to the number of DWORDS in the Statistics + * Identifier String Table + * @ests: Shall be set to the number of DWORDS from byte 0 of this + * log page to the start of the Event String Table + * @estsz: shall be set to the number of DWORDS in the Event String Table + * @vu_eve_sts: Shall be set to the number of DWORDS from byte 0 of this + * log page to the start of the VU Event String Table + * @vu_eve_st_sz: shall be set to the number of DWORDS in the VU Event String Table + * @ascts: the number of DWORDS from byte 0 of this log page until the ASCII Table Starts. + * @asctsz: the number of DWORDS in the ASCII Table + * @fifo1: FIFO 0 ASCII String + * @fifo2: FIFO 1 ASCII String + * @fifo3: FIFO 2 ASCII String + * @fifo4: FIFO 3 ASCII String + * @fif05: FIFO 4 ASCII String + * @fifo6: FIFO 5 ASCII String + * @fifo7: FIFO 6 ASCII String + * @fifo8: FIFO 7 ASCII String + * @fifo9: FIFO 8 ASCII String + * @fifo10: FIFO 9 ASCII String + * @fif011: FIFO 10 ASCII String + * @fif012: FIFO 11 ASCII String + * @fifo13: FIFO 12 ASCII String + * @fif014: FIFO 13 ASCII String + * @fif015: FIFO 14 ASCII String + * @fif016: FIFO 15 ASCII String + * @reserved3: reserved + */ +struct __attribute__((__packed__)) telemetry_str_log_format { + __u8 log_page_version; + __u8 reserved1[15]; + __u8 log_page_guid[C9_GUID_LENGTH]; + __le64 sls; + __u8 reserved2[24]; + __le64 sits; + __le64 sitsz; + __le64 ests; + __le64 estsz; + __le64 vu_eve_sts; + __le64 vu_eve_st_sz; + __le64 ascts; + __le64 asctsz; + __u8 fifo1[16]; + __u8 fifo2[16]; + __u8 fifo3[16]; + __u8 fifo4[16]; + __u8 fifo5[16]; + __u8 fifo6[16]; + __u8 fifo7[16]; + __u8 fifo8[16]; + __u8 fifo9[16]; + __u8 fifo10[16]; + __u8 fifo11[16]; + __u8 fifo12[16]; + __u8 fifo13[16]; + __u8 fifo14[16]; + __u8 fifo15[16]; + __u8 fifo16[16]; + __u8 reserved3[48]; +}; + +/* + * struct statistics_id_str_table_entry - Statistics Identifier String Table Entry + * @vs_si: Shall be set the Vendor Unique Statistic Identifier number. + * @reserved1: Reserved + * @ascii_id_len: Shall be set the number of ASCII Characters that are valid. + * @ascii_id_ofst: Shall be set to the offset from DWORD 0/Byte 0 of the Start + * of the ASCII Table to the first character of the string for + * this Statistic Identifier string.. + * @reserved2 reserved + */ +struct __attribute__((__packed__)) statistics_id_str_table_entry { + __le16 vs_si; + __u8 reserved1; + __u8 ascii_id_len; + __le64 ascii_id_ofst; + __le32 reserved2; +}; + +/* + * struct event_id_str_table_entry - Event Identifier String Table Entry + * @deb_eve_class: Shall be set the Debug Class. + * @ei: Shall be set to the Event Identifier + * @ascii_id_len: Shall be set the number of ASCII Characters that are valid. + * @ascii_id_ofst: This is the offset from DWORD 0/ Byte 0 of the start of the + * ASCII table to the ASCII data for this identifier + * @reserved2 reserved + */ +struct __attribute__((__packed__)) event_id_str_table_entry { + __u8 deb_eve_class; + __le16 ei; + __u8 ascii_id_len; + __le64 ascii_id_ofst; + __le32 reserved2; +}; + +/* + * struct vu_event_id_str_table_entry - VU Event Identifier String Table Entry + * @deb_eve_class: Shall be set the Debug Class. + * @vu_ei: Shall be set to the VU Event Identifier + * @ascii_id_len: Shall be set the number of ASCII Characters that are valid. + * @ascii_id_ofst: This is the offset from DWORD 0/ Byte 0 of the start of the + * ASCII table to the ASCII data for this identifier + * @reserved reserved + */ +struct __attribute__((__packed__)) vu_event_id_str_table_entry { + __u8 deb_eve_class; + __le16 vu_ei; + __u8 ascii_id_len; + __le64 ascii_id_ofst; + __le32 reserved; +}; + +/* Function declaration for Telemetry String Log Format (LID:C9h) */ +static int ocp_telemetry_str_log_format(int argc, char **argv, struct command *cmd, + struct plugin *plugin); + + +static int ocp_print_C9_log_normal(struct telemetry_str_log_format *log_data,__u8 *log_data_buf) +{ + //calculating the index value for array + __le64 stat_id_index = (log_data->sitsz * 4) / 16; + __le64 eve_id_index = (log_data->estsz * 4) / 16; + __le64 vu_eve_index = (log_data->vu_eve_st_sz * 4) / 16; + __le64 ascii_table_index = (log_data->asctsz * 4); + //Calculating the offset for dynamic fields. + __le64 stat_id_str_table_ofst = C9_TELEMETRY_STR_LOG_SIST_OFST + (log_data->sitsz * 4); + __le64 event_str_table_ofst = stat_id_str_table_ofst + (log_data->estsz * 4); + __le64 vu_event_str_table_ofst = event_str_table_ofst + (log_data->vu_eve_st_sz * 4); + __le64 ascii_table_ofst = vu_event_str_table_ofst + (log_data->asctsz * 4); + struct statistics_id_str_table_entry stat_id_str_table_arr[stat_id_index]; + struct event_id_str_table_entry event_id_str_table_arr[eve_id_index]; + struct vu_event_id_str_table_entry vu_event_id_str_table_arr[vu_eve_index]; + __u8 ascii_table_info_arr[ascii_table_index]; + int j; + + printf(" Log Page Version : 0x%x\n", log_data->log_page_version); + + printf(" Reserved : "); + for (j = 0; j < 15; j++) + printf("%d", log_data->reserved1[j]); + printf("\n"); + + printf(" Log page GUID : 0x"); + for (j = C9_GUID_LENGTH - 1; j >= 0; j--) + printf("%x", log_data->log_page_guid[j]); + printf("\n"); + + printf(" Telemetry String Log Size : 0x%lx\n", le64_to_cpu(log_data->sls)); + + printf(" Reserved : "); + for (j = 0; j < 24; j++) + printf("%d", log_data->reserved2[j]); + printf("\n"); + + printf(" Statistics Identifier String Table Start : 0x%lx\n", le64_to_cpu(log_data->sits)); + printf(" Statistics Identifier String Table Size : 0x%lx\n", le64_to_cpu(log_data->sitsz)); + printf(" Event String Table Start : 0x%lx\n", le64_to_cpu(log_data->ests)); + printf(" Event String Table Size : 0x%lx\n", le64_to_cpu(log_data->estsz)); + printf(" VU Event String Table Start : 0x%lx\n", le64_to_cpu(log_data->vu_eve_sts)); + printf(" VU Event String Table Size : 0x%lx\n", le64_to_cpu(log_data->vu_eve_st_sz)); + printf(" ASCII Table Start : 0x%lx\n", le64_to_cpu(log_data->ascts)); + printf(" ASCII Table Size : 0x%lx\n", le64_to_cpu(log_data->asctsz)); + + printf(" FIFO 1 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf(" %d %d %c \n", j, log_data->fifo1[j], log_data->fifo1[j]); + } + + printf(" FIFO 2 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf(" %d %d %c \n", j, log_data->fifo2[j], log_data->fifo2[j]); + } + + printf(" FIFO 3 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf(" %d %d %c \n", j, log_data->fifo3[j], log_data->fifo3[j]); + } + + printf(" FIFO 4 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + + printf(" %d %d %c \n", j, log_data->fifo4[j], log_data->fifo4[j]); + } + + printf(" FIFO 5 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf(" %d %d %c \n", j, log_data->fifo5[j], log_data->fifo5[j]); + } + + printf(" FIFO 6 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf(" %d %d %c \n", j, log_data->fifo6[j], log_data->fifo6[j]); + } + + printf(" FIFO 7 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf(" %d %d %c \n", j, log_data->fifo7[j], log_data->fifo7[j]); + } + + printf(" FIFO 8 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf("index value ascii_val"); + printf(" %d %d %c \n", j, log_data->fifo8[j], log_data->fifo8[j]); + } + + printf(" FIFO 9 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf(" %d %d %c \n", j, log_data->fifo9[j], log_data->fifo9[j]); + } + + printf(" FIFO 10 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf(" %d %d %c \n", j, log_data->fifo10[j], log_data->fifo10[j]); + } + + printf(" FIFO 11 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf(" %d %d %c \n", j, log_data->fifo11[j], log_data->fifo11[j]); + } + + printf(" FIFO 12 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf(" %d %d %c \n", j, log_data->fifo12[j], log_data->fifo12[j]); + } + + printf(" FIFO 13 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf(" %d %d %c \n", j, log_data->fifo13[j], log_data->fifo13[j]); + } + + printf(" FIFO 14 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf(" %d %d %c \n", j, log_data->fifo14[j], log_data->fifo14[j]); + } + + printf(" FIFO 15 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf(" %d %d %c \n", j, log_data->fifo15[j], log_data->fifo16[j]); + } + + printf(" FIFO 16 ASCII String\n"); + printf(" index value ascii_val\n"); + for (j = 0; j < 16; j++){ + printf(" %d %d %c \n", j, log_data->fifo16[j], log_data->fifo16[j]); + } + + printf(" Reserved : "); + for (j = 0; j < 48; j++) + printf("%d", log_data->reserved3[j]); + printf("\n"); + + memcpy(stat_id_str_table_arr, (__u8*)log_data_buf + stat_id_str_table_ofst, (log_data->sitsz * 4)); + memcpy(event_id_str_table_arr, (__u8*)log_data_buf + event_str_table_ofst, (log_data->estsz * 4)); + memcpy(vu_event_id_str_table_arr, (__u8*)log_data_buf + vu_event_str_table_ofst, (log_data->vu_eve_st_sz * 4)); + memcpy(ascii_table_info_arr, (__u8*)log_data_buf + ascii_table_ofst, (log_data->asctsz * 4)); + + printf(" Statistics Identifier String Table\n"); + for (j = 0; j < stat_id_index; j++){ + printf(" Vendor Specific Statistic Identifier : 0x%x\n",le16_to_cpu(stat_id_str_table_arr[j].vs_si)); + printf(" Reserved : 0x%d",stat_id_str_table_arr[j].reserved1); + printf(" ASCII ID Length : 0x%x\n",stat_id_str_table_arr[j].ascii_id_len); + printf(" ASCII ID offset : 0x%lx\n",le64_to_cpu(stat_id_str_table_arr[j].ascii_id_ofst)); + printf(" Reserved : 0x%d\n",stat_id_str_table_arr[j].reserved2); + } + + printf(" Event Identifier String Table Entry\n"); + for (j = 0; j < eve_id_index; j++){ + printf(" Debug Event Class : 0x%x\n",event_id_str_table_arr[j].deb_eve_class); + printf(" Event Identifier : 0x%x\n",le16_to_cpu(event_id_str_table_arr[j].ei)); + printf(" ASCII ID Length : 0x%x\n",event_id_str_table_arr[j].ascii_id_len); + printf(" ASCII ID offset : 0x%lx\n",le64_to_cpu(event_id_str_table_arr[j].ascii_id_ofst)); + printf(" Reserved : 0x%d\n",event_id_str_table_arr[j].reserved2); + + } + + printf(" VU Event Identifier String Table Entry\n"); + for (j = 0; j < vu_eve_index; j++){ + printf(" Debug Event Class : 0x%x\n",vu_event_id_str_table_arr[j].deb_eve_class); + printf(" VU Event Identifier : 0x%x\n",le16_to_cpu(vu_event_id_str_table_arr[j].vu_ei)); + printf(" ASCII ID Length : 0x%x\n",vu_event_id_str_table_arr[j].ascii_id_len); + printf(" ASCII ID offset : 0x%lx\n",le64_to_cpu(vu_event_id_str_table_arr[j].ascii_id_ofst)); + printf(" Reserved : 0x%d\n",vu_event_id_str_table_arr[j].reserved); + + } + + printf(" ASCII Table\n"); + printf(" Byte Data_Byte ASCII_Character\n"); + for (j = 0; j < ascii_table_index; j++){ + printf(" %lld 0x%x %c \n",ascii_table_ofst+j,ascii_table_info_arr[j],ascii_table_info_arr[j]); + } + return 0; +} + +static int ocp_print_C9_log_json(struct telemetry_str_log_format *log_data,__u8 *log_data_buf) +{ + struct json_object *root = json_create_object(); + struct json_object *stat_table = json_create_object(); + struct json_object *eve_table = json_create_object(); + struct json_object *vu_eve_table = json_create_object(); + struct json_object *entry = json_create_object(); + char res_arr[48]; + char *res = res_arr; + char guid_buf[C9_GUID_LENGTH]; + char *guid = guid_buf; + char fifo_arr[16]; + char *fifo = fifo_arr; + //calculating the index value for array + __le64 stat_id_index = (log_data->sitsz * 4) / 16; + __le64 eve_id_index = (log_data->estsz * 4) / 16; + __le64 vu_eve_index = (log_data->vu_eve_st_sz * 4) / 16; + __le64 ascii_table_index = (log_data->asctsz * 4); + //Calculating the offset for dynamic fields. + __le64 stat_id_str_table_ofst = C9_TELEMETRY_STR_LOG_SIST_OFST + (log_data->sitsz * 4); + __le64 event_str_table_ofst = stat_id_str_table_ofst + (log_data->estsz * 4); + __le64 vu_event_str_table_ofst = event_str_table_ofst + (log_data->vu_eve_st_sz * 4); + __le64 ascii_table_ofst = vu_event_str_table_ofst + (log_data->asctsz * 4); + struct statistics_id_str_table_entry stat_id_str_table_arr[stat_id_index]; + struct event_id_str_table_entry event_id_str_table_arr[eve_id_index]; + struct vu_event_id_str_table_entry vu_event_id_str_table_arr[vu_eve_index]; + __u8 ascii_table_info_arr[ascii_table_index]; + char ascii_buf[ascii_table_index]; + char *ascii = ascii_buf; + int j; + + json_object_add_value_int(root, "Log Page Version", le16_to_cpu(log_data->log_page_version)); + + memset((__u8 *)res, 0, 15); + for (j = 0; j < 15; j++) + res += sprintf(res, "%d", log_data->reserved1[j]); + json_object_add_value_string(root, "Reserved", res_arr); + + memset((void *)guid, 0, C9_GUID_LENGTH); + for (j = C9_GUID_LENGTH - 1; j >= 0; j--) + guid += sprintf(guid, "%02x", log_data->log_page_guid[j]); + json_object_add_value_string(root, "Log page GUID", guid_buf); + + json_object_add_value_int(root, "Telemetry String Log Size", le64_to_cpu(log_data->sls)); + + memset((__u8 *)res, 0, 24); + for (j = 0; j < 24; j++) + res += sprintf(res, "%d", log_data->reserved2[j]); + json_object_add_value_string(root, "Reserved", res_arr); + + json_object_add_value_int(root, "Statistics Identifier String Table Start", le64_to_cpu(log_data->sits)); + json_object_add_value_int(root, "Event String Table Start", le64_to_cpu(log_data->ests)); + json_object_add_value_int(root, "Event String Table Size", le64_to_cpu(log_data->estsz)); + json_object_add_value_int(root, "VU Event String Table Start", le64_to_cpu(log_data->vu_eve_sts)); + json_object_add_value_int(root, "VU Event String Table Size", le64_to_cpu(log_data->vu_eve_st_sz)); + json_object_add_value_int(root, "ASCII Table Start", le64_to_cpu(log_data->ascts)); + json_object_add_value_int(root, "ASCII Table Size", le64_to_cpu(log_data->asctsz)); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo1[j]); + json_object_add_value_string(root, "FIFO 1 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo2[j]); + json_object_add_value_string(root, "FIFO 2 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo3[j]); + json_object_add_value_string(root, "FIFO 3 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo4[j]); + json_object_add_value_string(root, "FIFO 4 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo5[j]); + json_object_add_value_string(root, "FIFO 5 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo6[j]); + json_object_add_value_string(root, "FIFO 6 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo7[j]); + json_object_add_value_string(root, "FIFO 7 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo8[j]); + json_object_add_value_string(root, "FIFO 8 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo9[j]); + json_object_add_value_string(root, "FIFO 9 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo10[j]); + json_object_add_value_string(root, "FIFO 10 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo11[j]); + json_object_add_value_string(root, "FIFO 11 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo12[j]); + json_object_add_value_string(root, "FIFO 12 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo13[j]); + json_object_add_value_string(root, "FIFO 13 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo14[j]); + json_object_add_value_string(root, "FIFO 14 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo15[j]); + json_object_add_value_string(root, "FIFO 15 ASCII String", fifo_arr); + + memset((void *)fifo, 0, 16); + for (j = 0; j < 16; j++) + fifo += sprintf(fifo, "%c", log_data->fifo16[j]); + json_object_add_value_string(root, "FIFO 16 ASCII String", fifo_arr); + + memset((__u8 *)res, 0, 48); + for (j = 0; j < 48; j++) + res += sprintf(res, "%d", log_data->reserved3[j]); + json_object_add_value_string(root, "Reserved", res_arr); + + memcpy(stat_id_str_table_arr, (__u8*)log_data_buf + stat_id_str_table_ofst, (log_data->sitsz * 4)); + memcpy(event_id_str_table_arr, (__u8*)log_data_buf + event_str_table_ofst, (log_data->estsz * 4)); + memcpy(vu_event_id_str_table_arr, (__u8*)log_data_buf + vu_event_str_table_ofst, (log_data->vu_eve_st_sz * 4)); + memcpy(ascii_table_info_arr, (__u8*)log_data_buf + ascii_table_ofst, (log_data->asctsz * 4)); + + for (j = 0; j < stat_id_index; j++){ + json_object_add_value_int(entry, "Vendor Specific Statistic Identifier", le16_to_cpu(stat_id_str_table_arr[j].vs_si)); + json_object_add_value_int(entry, "Reserved", le64_to_cpu(stat_id_str_table_arr[j].reserved1)); + json_object_add_value_int(entry, "ASCII ID Length", le64_to_cpu(stat_id_str_table_arr[j].ascii_id_len)); + json_object_add_value_int(entry, "ASCII ID offset", le64_to_cpu(stat_id_str_table_arr[j].ascii_id_ofst)); + json_object_add_value_int(entry, "Reserved", le64_to_cpu(stat_id_str_table_arr[j].reserved2)); + json_array_add_value_object(stat_table, entry); + } + json_object_add_value_array(root, "Statistics Identifier String Table", stat_table); + + for (j = 0; j < eve_id_index; j++){ + json_object_add_value_int(entry, "Debug Event Class", le16_to_cpu(event_id_str_table_arr[j].deb_eve_class)); + json_object_add_value_int(entry, "Event Identifier", le16_to_cpu(event_id_str_table_arr[j].ei)); + json_object_add_value_int(entry, "ASCII ID Length", le64_to_cpu(event_id_str_table_arr[j].ascii_id_len)); + json_object_add_value_int(entry, "ASCII ID offset", le64_to_cpu(event_id_str_table_arr[j].ascii_id_ofst)); + json_object_add_value_int(entry, "Reserved", le64_to_cpu(event_id_str_table_arr[j].reserved2)); + json_array_add_value_object(eve_table, entry); + } + json_object_add_value_array(root, "Event Identifier String Table Entry", eve_table); + + for (j = 0; j < vu_eve_index; j++){ + json_object_add_value_int(entry, "Debug Event Class", le16_to_cpu(vu_event_id_str_table_arr[j].deb_eve_class)); + json_object_add_value_int(entry, "VU Event Identifier", le16_to_cpu(vu_event_id_str_table_arr[j].vu_ei)); + json_object_add_value_int(entry, "ASCII ID Length", le64_to_cpu(vu_event_id_str_table_arr[j].ascii_id_len)); + json_object_add_value_int(entry, "ASCII ID offset", le64_to_cpu(vu_event_id_str_table_arr[j].ascii_id_ofst)); + json_object_add_value_int(entry, "Reserved", le64_to_cpu(vu_event_id_str_table_arr[j].reserved)); + json_array_add_value_object(vu_eve_table, entry); + } + json_object_add_value_array(root, "VU Event Identifier String Table Entry", vu_eve_table); + + memset((void *)ascii, 0, ascii_table_index); + for (j = 0; j < ascii_table_index; j++) + ascii += sprintf(ascii, "%c", ascii_table_info_arr[j]); + json_object_add_value_string(root, "ASCII Table", ascii_buf); + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); + json_free_object(stat_table); + json_free_object(eve_table); + json_free_object(vu_eve_table); + + return 0; +} + +static void ocp_print_c9_log_binary(__u8 *log_data_buf,int total_log_page_size) +{ + return d_raw((unsigned char *)log_data_buf, total_log_page_size); +} + +static int get_c9_log_page(struct nvme_dev *dev, char *format) +{ + int ret = 0; + __u8 *header_data; + struct telemetry_str_log_format *log_data; + enum nvme_print_flags fmt; + __u8 *full_log_buf_data = NULL; + __le64 stat_id_str_table_ofst = 0; + __le64 event_str_table_ofst = 0; + __le64 vu_event_str_table_ofst = 0; + __le64 ascii_table_ofst = 0; + __le64 total_log_page_sz = 0; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR : OCP : invalid output format\n"); + return ret; + } + + header_data = (__u8 *)malloc(sizeof(__u8) * C9_TELEMETRY_STR_LOG_LEN); + if (!header_data) { + fprintf(stderr, "ERROR : OCP : malloc : %s\n", strerror(errno)); + return -1; + } + memset(header_data, 0, sizeof(__u8) * C9_TELEMETRY_STR_LOG_LEN); + + ret = nvme_get_log_simple(dev_fd(dev), C9_TELEMETRY_STRING_LOG_ENABLE_OPCODE, + C9_TELEMETRY_STR_LOG_LEN, header_data); + + if (!ret) { + log_data = (struct telemetry_str_log_format *)header_data; + printf("Statistics Identifier String Table Size = %lld\n",log_data->sitsz); + printf("Event String Table Size = %lld\n",log_data->estsz); + printf("VU Event String Table Size = %lld\n",log_data->vu_eve_st_sz); + printf("ASCII Table Size = %lld\n",log_data->asctsz); + + //Calculating the offset for dynamic fields. + stat_id_str_table_ofst = C9_TELEMETRY_STR_LOG_SIST_OFST + (log_data->sitsz * 4); + event_str_table_ofst = stat_id_str_table_ofst + (log_data->estsz * 4); + vu_event_str_table_ofst = event_str_table_ofst + (log_data->vu_eve_st_sz * 4); + ascii_table_ofst = vu_event_str_table_ofst + (log_data->asctsz * 4); + total_log_page_sz = stat_id_str_table_ofst + event_str_table_ofst + vu_event_str_table_ofst + ascii_table_ofst; + + printf("stat_id_str_table_ofst = %lld\n",stat_id_str_table_ofst); + printf("event_str_table_ofst = %lld\n",event_str_table_ofst); + printf("vu_event_str_table_ofst = %lld\n",vu_event_str_table_ofst); + printf("ascii_table_ofst = %lld\n",ascii_table_ofst); + printf("total_log_page_sz = %lld\n",total_log_page_sz); + + full_log_buf_data = (__u8 *)malloc(sizeof(__u8) * total_log_page_sz); + if (!full_log_buf_data) { + fprintf(stderr, "ERROR : OCP : malloc : %s\n", strerror(errno)); + return -1; + } + memset(full_log_buf_data, 0, sizeof(__u8) * total_log_page_sz); + + ret = nvme_get_log_simple(dev_fd(dev), C9_TELEMETRY_STRING_LOG_ENABLE_OPCODE, + total_log_page_sz, full_log_buf_data); + + if (!ret) { + switch (fmt) { + case NORMAL: + ocp_print_C9_log_normal(log_data,full_log_buf_data); + break; + case JSON: + ocp_print_C9_log_json(log_data,full_log_buf_data); + break; + case BINARY: + ocp_print_c9_log_binary(full_log_buf_data,total_log_page_sz); + break; + default: + fprintf(stderr, "unhandled output format\n"); + break; + } + } else{ + fprintf(stderr, "ERROR : OCP : Unable to read C9 data from buffer\n"); + } + } else { + fprintf(stderr, "ERROR : OCP : Unable to read C9 data from buffer\n"); + } + + free(header_data); + free(full_log_buf_data); + + return ret; +} + +static int ocp_telemetry_str_log_format(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + struct nvme_dev *dev; + int ret = 0; + const char *desc = "Retrieve telemetry string log format"; + + 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + ret = get_c9_log_page(dev, cfg.output_format); + if (ret) + fprintf(stderr, "ERROR : OCP : Failure reading the C9 Log Page, ret = %d\n", ret); + + dev_close(dev); + + return ret; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/// Misc + +static int clear_fw_update_history(int argc, char **argv, + struct command *cmd, struct plugin *plugin) +{ + return ocp_clear_fw_update_history(argc, argv, cmd, plugin); +} + +static int smart_add_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + return ocp_smart_add_log(argc, argv, cmd, plugin); +} + +static int clear_pcie_correctable_error_counters(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + return ocp_clear_pcie_correctable_errors(argc, argv, cmd, plugin); +} + +static int fw_activation_history_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + return ocp_fw_activation_history_log(argc, argv, cmd, plugin); +} diff --git a/plugins/ocp/ocp-nvme.h b/plugins/ocp/ocp-nvme.h new file mode 100644 index 0000000..95539b0 --- /dev/null +++ b/plugins/ocp/ocp-nvme.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2022 Meta Platforms, Inc. + * + * Authors: Arthur Shau <arthurshau@fb.com>, + * Wei Zhang <wzhang@fb.com>, + * Venkat Ramesh <venkatraghavan@fb.com> + */ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/ocp/ocp-nvme + +#if !defined(OCP_NVME) || defined(CMD_HEADER_MULTI_READ) +#define OCP_NVME + +#include "cmd.h" + +PLUGIN(NAME("ocp", "OCP cloud SSD extensions", NVME_VERSION), + COMMAND_LIST( + ENTRY("smart-add-log", "Retrieve extended SMART Information", smart_add_log) + ENTRY("latency-monitor-log", "Get Latency Monitor Log Page", ocp_latency_monitor_log) + ENTRY("set-latency-monitor-feature", "Set Latency Monitor feature", ocp_set_latency_monitor_feature) + ENTRY("internal-log", "Retrieve and save internal device telemetry log", ocp_telemetry_log) + ENTRY("clear-fw-activate-history", "Clear firmware update history log", clear_fw_update_history) + ENTRY("eol-plp-failure-mode", "Define EOL or PLP circuitry failure mode.", eol_plp_failure_mode) + ENTRY("clear-pcie-correctable-errors", "Clear PCIe correctable error counters", clear_pcie_correctable_error_counters) + ENTRY("fw-activate-history", "Get firmware activation history log", fw_activation_history_log) + ENTRY("unsupported-reqs-log", "Get Unsupported Requirements Log Page", ocp_unsupported_requirements_log) + ENTRY("error-recovery-log", "Retrieve Error Recovery Log Page", ocp_error_recovery_log) + ENTRY("device-capability-log", "Get Device capabilities Requirements Log Page", ocp_device_capabilities_log) + ENTRY("set-dssd-power-state-feature", "Get Device capabilities Requirements Log Page", set_dssd_power_state_feature) + ENTRY("set-plp-health-check-interval", "Set PLP Health Check Interval", set_plp_health_check_interval) + ENTRY("get-plp-health-check-interval", "Get PLP Health Check Interval", get_plp_health_check_interval) + ENTRY("telemetry-string-log", "Retrieve Telemetry string Log Page", ocp_telemetry_str_log_format) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/ocp/ocp-smart-extended-log.c b/plugins/ocp/ocp-smart-extended-log.c new file mode 100644 index 0000000..0d8ba81 --- /dev/null +++ b/plugins/ocp/ocp-smart-extended-log.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (c) 2022 Meta Platforms, Inc. + * + * Authors: Arthur Shau <arthurshau@fb.com>, + * Wei Zhang <wzhang@fb.com>, + * Venkat Ramesh <venkatraghavan@fb.com> + */ + +#include "ocp-smart-extended-log.h" + +#include <errno.h> +#include <stdio.h> + +#include "common.h" +#include "nvme-print.h" + +/* C0 SCAO Log Page */ +#define C0_SMART_CLOUD_ATTR_LEN 0x200 +#define C0_SMART_CLOUD_ATTR_OPCODE 0xC0 +#define C0_GUID_LENGTH 16 + +static __u8 scao_guid[C0_GUID_LENGTH] = { + 0xC5, 0xAF, 0x10, 0x28, + 0xEA, 0xBF, 0xF2, 0xA4, + 0x9C, 0x4F, 0x6F, 0x7C, + 0xC9, 0x14, 0xD5, 0xAF +}; + +enum { + SCAO_PMUW = 0, /* Physical media units written */ + SCAO_PMUR = 16, /* Physical media units read */ + SCAO_BUNBR = 32, /* Bad user nand blocks raw */ + SCAO_BUNBN = 38, /* Bad user nand blocks normalized */ + SCAO_BSNBR = 40, /* Bad system nand blocks raw */ + SCAO_BSNBN = 46, /* Bad system nand blocks normalized */ + SCAO_XRC = 48, /* XOR recovery count */ + SCAO_UREC = 56, /* Uncorrectable read error count */ + SCAO_SEEC = 64, /* Soft ecc error count */ + SCAO_EEDC = 72, /* End to end detected errors */ + SCAO_EECE = 76, /* End to end corrected errors */ + SCAO_SDPU = 80, /* System data percent used */ + SCAO_RFSC = 81, /* Refresh counts */ + SCAO_MXUDEC = 88, /* Max User data erase counts */ + SCAO_MNUDEC = 92, /* Min User data erase counts */ + SCAO_NTTE = 96, /* Number of Thermal throttling events */ + SCAO_CTS = 97, /* Current throttling status */ + SCAO_EVF = 98, /* Errata Version Field */ + SCAO_PVF = 99, /* Point Version Field */ + SCAO_MIVF = 101, /* Minor Version Field */ + SCAO_MAVF = 103, /* Major Version Field */ + SCAO_PCEC = 104, /* PCIe correctable error count */ + SCAO_ICS = 112, /* Incomplete shutdowns */ + SCAO_PFB = 120, /* Percent free blocks */ + SCAO_CPH = 128, /* Capacitor health */ + SCAO_NEV = 130, /* NVMe Errata Version */ + SCAO_UIO = 136, /* Unaligned I/O */ + SCAO_SVN = 144, /* Security Version Number */ + SCAO_NUSE = 152, /* NUSE - Namespace utilization */ + SCAO_PSC = 160, /* PLP start count */ + SCAO_EEST = 176, /* Endurance estimate */ + SCAO_PLRC = 192, /* PCIe Link Retraining Count */ + SCAO_PSCC = 200, /* Power State Change Count */ + SCAO_LPV = 494, /* Log page version */ + SCAO_LPG = 496, /* Log page GUID */ +}; + +static void ocp_print_C0_log_normal(void *data) +{ + uint16_t smart_log_ver = 0; + __u8 *log_data = data; + + printf("SMART Cloud Attributes :-\n"); + + printf(" Physical media units written - %"PRIu64" %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUW + 8] & 0xFFFFFFFFFFFFFFFF), + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUW] & 0xFFFFFFFFFFFFFFFF)); + printf(" Physical media units read - %"PRIu64" %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUR + 8] & 0xFFFFFFFFFFFFFFFF), + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUR] & 0xFFFFFFFFFFFFFFFF)); + printf(" Bad user nand blocks - Raw %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BUNBR] & 0x0000FFFFFFFFFFFF)); + printf(" Bad user nand blocks - Normalized %d\n", + (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BUNBN])); + printf(" Bad system nand blocks - Raw %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BSNBR] & 0x0000FFFFFFFFFFFF)); + printf(" Bad system nand blocks - Normalized %d\n", + (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BSNBN])); + printf(" XOR recovery count %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_XRC])); + printf(" Uncorrectable read error count %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UREC])); + printf(" Soft ecc error count %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SEEC])); + printf(" End to end detected errors %"PRIu32"\n", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EEDC])); + printf(" End to end corrected errors %"PRIu32"\n", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EECE])); + printf(" System data percent used %d\n", + (__u8)log_data[SCAO_SDPU]); + printf(" Refresh counts %"PRIu64"\n", + (uint64_t)(le64_to_cpu(*(uint64_t *)&log_data[SCAO_RFSC]) & 0x00FFFFFFFFFFFFFF)); + printf(" Max User data erase counts %"PRIu32"\n", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MXUDEC])); + printf(" Min User data erase counts %"PRIu32"\n", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MNUDEC])); + printf(" Number of Thermal throttling events %d\n", + (__u8)log_data[SCAO_NTTE]); + printf(" Current throttling status 0x%x\n", + (__u8)log_data[SCAO_CTS]); + printf(" PCIe correctable error count %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PCEC])); + printf(" Incomplete shutdowns %"PRIu32"\n", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_ICS])); + printf(" Percent free blocks %d\n", + (__u8)log_data[SCAO_PFB]); + printf(" Capacitor health %"PRIu16"\n", + (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_CPH])); + printf(" Unaligned I/O %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UIO])); + printf(" Security Version Number %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SVN])); + printf(" NUSE - Namespace utilization %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_NUSE])); + printf(" PLP start count %s\n", + uint128_t_to_string(le128_to_cpu(&log_data[SCAO_PSC]))); + printf(" Endurance estimate %s\n", + uint128_t_to_string(le128_to_cpu(&log_data[SCAO_EEST]))); + smart_log_ver = (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_LPV]); + printf(" Log page version %"PRIu16"\n", smart_log_ver); + printf(" Log page GUID 0x"); + printf("%"PRIx64"%"PRIx64"\n", (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG + 8]), + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG])); + if (smart_log_ver > 2) { + printf(" Errata Version Field %d\n", + (__u8)log_data[SCAO_EVF]); + printf(" Point Version Field %"PRIu16"\n", + le16_to_cpu(*(uint16_t *)&log_data[SCAO_PVF])); + printf(" Minor Version Field %"PRIu16"\n", + le16_to_cpu(*(uint16_t *)&log_data[SCAO_MIVF])); + printf(" Major Version Field %d\n", + (__u8)log_data[SCAO_MAVF]); + printf(" NVMe Errata Version %d\n", + (__u8)log_data[SCAO_NEV]); + printf(" PCIe Link Retraining Count %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PLRC])); + printf(" Power State Change Count %"PRIu64"\n", + le64_to_cpu(*(uint64_t *)&log_data[SCAO_PSCC])); + } + printf("\n"); +} + +static void ocp_print_C0_log_json(void *data) +{ + struct json_object *root; + struct json_object *pmuw; + struct json_object *pmur; + uint16_t smart_log_ver = 0; + __u8 *log_data = data; + char guid[40]; + + root = json_create_object(); + pmuw = json_create_object(); + pmur = json_create_object(); + + json_object_add_value_uint64(pmuw, "hi", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUW + 8] & 0xFFFFFFFFFFFFFFFF)); + json_object_add_value_uint64(pmuw, "lo", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUW] & 0xFFFFFFFFFFFFFFFF)); + json_object_add_value_object(root, "Physical media units written", pmuw); + json_object_add_value_uint64(pmur, "hi", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUR + 8] & 0xFFFFFFFFFFFFFFFF)); + json_object_add_value_uint64(pmur, "lo", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUR] & 0xFFFFFFFFFFFFFFFF)); + json_object_add_value_object(root, "Physical media units read", pmur); + json_object_add_value_uint64(root, "Bad user nand blocks - Raw", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BUNBR] & 0x0000FFFFFFFFFFFF)); + json_object_add_value_uint(root, "Bad user nand blocks - Normalized", + (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BUNBN])); + json_object_add_value_uint64(root, "Bad system nand blocks - Raw", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BSNBR] & 0x0000FFFFFFFFFFFF)); + json_object_add_value_uint(root, "Bad system nand blocks - Normalized", + (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BSNBN])); + json_object_add_value_uint64(root, "XOR recovery count", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_XRC])); + json_object_add_value_uint64(root, "Uncorrectable read error count", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UREC])); + json_object_add_value_uint64(root, "Soft ecc error count", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SEEC])); + json_object_add_value_uint(root, "End to end detected errors", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EEDC])); + json_object_add_value_uint(root, "End to end corrected errors", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EECE])); + json_object_add_value_uint(root, "System data percent used", + (__u8)log_data[SCAO_SDPU]); + json_object_add_value_uint64(root, "Refresh counts", + (uint64_t)(le64_to_cpu(*(uint64_t *)&log_data[SCAO_RFSC]) & 0x00FFFFFFFFFFFFFF)); + json_object_add_value_uint(root, "Max User data erase counts", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MXUDEC])); + json_object_add_value_uint(root, "Min User data erase counts", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MNUDEC])); + json_object_add_value_uint(root, "Number of Thermal throttling events", + (__u8)log_data[SCAO_NTTE]); + json_object_add_value_uint(root, "Current throttling status", + (__u8)log_data[SCAO_CTS]); + json_object_add_value_uint64(root, "PCIe correctable error count", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PCEC])); + json_object_add_value_uint(root, "Incomplete shutdowns", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_ICS])); + json_object_add_value_uint(root, "Percent free blocks", + (__u8)log_data[SCAO_PFB]); + json_object_add_value_uint(root, "Capacitor health", + (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_CPH])); + json_object_add_value_uint64(root, "Unaligned I/O", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UIO])); + json_object_add_value_uint64(root, "Security Version Number", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SVN])); + json_object_add_value_uint64(root, "NUSE - Namespace utilization", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_NUSE])); + json_object_add_value_uint128(root, "PLP start count", + le128_to_cpu(&log_data[SCAO_PSC])); + json_object_add_value_uint128(root, "Endurance estimate", + le128_to_cpu(&log_data[SCAO_EEST])); + smart_log_ver = (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_LPV]); + + json_object_add_value_uint(root, "Log page version", smart_log_ver); + + memset((void *)guid, 0, 40); + sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"", (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG + 8]), + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG])); + json_object_add_value_string(root, "Log page GUID", guid); + + if (smart_log_ver > 2) { + json_object_add_value_uint(root, "Errata Version Field", + (__u8)log_data[SCAO_EVF]); + json_object_add_value_uint(root, "Point Version Field", + le16_to_cpu(*(uint16_t *)&log_data[SCAO_PVF])); + json_object_add_value_uint(root, "Minor Version Field", + le16_to_cpu(*(uint16_t *)&log_data[SCAO_MIVF])); + json_object_add_value_uint(root, "Major Version Field", + (__u8)log_data[SCAO_MAVF]); + json_object_add_value_uint(root, "NVMe Errata Version", + (__u8)log_data[SCAO_NEV]); + json_object_add_value_uint(root, "PCIe Link Retraining Count", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PLRC])); + json_object_add_value_uint(root, "Power State Change Count", + le64_to_cpu(*(uint64_t *)&log_data[SCAO_PSCC])); + } + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static int get_c0_log_page(int fd, char *format) +{ + enum nvme_print_flags fmt; + __u8 *data; + int i; + int ret; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR : OCP : invalid output format\n"); + return ret; + } + + data = malloc(sizeof(__u8) * C0_SMART_CLOUD_ATTR_LEN); + if (!data) { + fprintf(stderr, "ERROR : OCP : malloc : %s\n", strerror(errno)); + return -1; + } + memset(data, 0, sizeof(__u8) * C0_SMART_CLOUD_ATTR_LEN); + + ret = nvme_get_log_simple(fd, C0_SMART_CLOUD_ATTR_OPCODE, + C0_SMART_CLOUD_ATTR_LEN, data); + + if (strcmp(format, "json")) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(ret, false), ret); + + if (ret == 0) { + /* check log page guid */ + /* Verify GUID matches */ + for (i = 0; i < 16; i++) { + if (scao_guid[i] != data[SCAO_LPG + i]) { + int j; + + fprintf(stderr, "ERROR : OCP : Unknown GUID in C0 Log Page data\n"); + fprintf(stderr, "ERROR : OCP : Expected GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", scao_guid[j]); + + fprintf(stderr, "\nERROR : OCP : Actual GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", data[SCAO_LPG + j]); + fprintf(stderr, "\n"); + + ret = -1; + goto out; + } + } + + /* print the data */ + switch (fmt) { + case NORMAL: + ocp_print_C0_log_normal(data); + break; + case JSON: + ocp_print_C0_log_json(data); + break; + default: + break; + } + } else { + fprintf(stderr, "ERROR : OCP : Unable to read C0 data from buffer\n"); + } + +out: + free(data); + return ret; +} + +int ocp_smart_add_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "Retrieve the extended SMART health data."; + struct nvme_dev *dev; + int ret = 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + ret = get_c0_log_page(dev_fd(dev), cfg.output_format); + if (ret) + fprintf(stderr, "ERROR : OCP : Failure reading the C0 Log Page, ret = %d\n", + ret); + dev_close(dev); + return ret; +} diff --git a/plugins/ocp/ocp-smart-extended-log.h b/plugins/ocp/ocp-smart-extended-log.h new file mode 100644 index 0000000..42c1f98 --- /dev/null +++ b/plugins/ocp/ocp-smart-extended-log.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2022 Meta Platforms, Inc. + * + * Authors: Arthur Shau <arthurshau@fb.com>, + * Wei Zhang <wzhang@fb.com>, + * Venkat Ramesh <venkatraghavan@fb.com> + */ + +#ifndef OCP_SMART_EXTENDED_LOG_H +#define OCP_SMART_EXTENDED_LOG_H + +struct command; +struct plugin; + +int ocp_smart_add_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin); + +#endif diff --git a/plugins/ocp/ocp-utils.c b/plugins/ocp/ocp-utils.c new file mode 100644 index 0000000..1257b30 --- /dev/null +++ b/plugins/ocp/ocp-utils.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include <unistd.h> +#include "ocp-utils.h" +#include "nvme-print.h" + +const unsigned char ocp_uuid[NVME_UUID_LEN] = { + 0xc1, 0x94, 0xd5, 0x5b, 0xe0, 0x94, 0x47, 0x94, 0xa2, 0x1d, + 0x29, 0x99, 0x8f, 0x56, 0xbe, 0x6f }; + +int ocp_get_uuid_index(struct nvme_dev *dev, int *index) +{ + struct nvme_id_uuid_list uuid_list; + int err = nvme_identify_uuid(dev_fd(dev), &uuid_list); + + *index = 0; + if (err) + return err; + + for (int i = 0; i < NVME_ID_UUID_LIST_MAX; i++) { + if (memcmp(ocp_uuid, &uuid_list.entry[i].uuid, NVME_UUID_LEN) == 0) { + *index = i + 1; + break; + } + } + return err; +} diff --git a/plugins/ocp/ocp-utils.h b/plugins/ocp/ocp-utils.h new file mode 100644 index 0000000..d02bea9 --- /dev/null +++ b/plugins/ocp/ocp-utils.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include "nvme.h" + +/** + * ocp_get_uuid_index() - Get OCP UUID index + * @dev: nvme device + * @index: integer pointer to here to save the index + * @result: The command completion result from CQE dword0 + * + * Return: Zero if nvme device has UUID list log page, or result of get uuid list otherwise. + */ +int ocp_get_uuid_index(struct nvme_dev *dev, int *index); diff --git a/plugins/scaleflux/sfx-nvme.c b/plugins/scaleflux/sfx-nvme.c new file mode 100644 index 0000000..f752d5d --- /dev/null +++ b/plugins/scaleflux/sfx-nvme.c @@ -0,0 +1,1687 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <linux/fs.h> +#include <inttypes.h> +#include <asm/byteorder.h> +#include <sys/sysinfo.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <dirent.h> +#include <time.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-wrap.h" +#include "nvme-print.h" +#include "util/cleanup.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 NVME_IOCTL_CLR_CARD _IO('N', 0x47) + +#define IDEMA_CAP(exp_GB) (((__u64)exp_GB - 50ULL) * 1953504ULL + 97696368ULL) +#define IDEMA_CAP2GB(exp_sector) (((__u64)exp_sector - 97696368ULL) / 1953504ULL + 50ULL) + +#define VANDA_MAJOR_IDX 0 +#define VANDA_MINOR_IDX 0 + +#define MYRTLE_MAJOR_IDX 4 +#define MYRTLE_MINOR_IDX 1 + +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, + SFX_FEAT_UP_P_CAP = 0xac, + SFX_FEAT_CLR_CARD = 0xdc, +}; + +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 */ + __u64 out_of_space; + __u64 map_unit; + __u64 max_user_space; + __u64 extendible_user_cap_lba_count; + __u64 friendly_change_cap_support; +}; + +struct nvme_capacity_info { + __u64 lba_sec_sz; + __u64 phy_sec_sz; + __u64 used_space; + __u64 free_space; +}; + +struct __packed nvme_additional_smart_log_item { + __u8 key; + __u8 _kp[2]; + __u8 norm; + __u8 _np; + union __packed { + __u8 raw[6]; + struct __packed wear_level { + __le16 min; + __le16 max; + __le16 avg; + } wear_level; + struct __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_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 */ + struct nvme_additional_smart_log_item non_media_crc_err_cnt; + struct nvme_additional_smart_log_item compression_path_err_cnt; + struct nvme_additional_smart_log_item out_of_space_flag; + struct nvme_additional_smart_log_item physical_usage_ratio; + struct nvme_additional_smart_log_item grown_bb; /* grown bad block */ +}; + +int nvme_query_cap(int fd, __u32 nsid, __u32 data_len, void *data) +{ + int rc = 0; + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_query_cap_info, + .nsid = nsid, + .addr = (__u64)(uintptr_t) data, + .data_len = data_len, + }; + + rc = ioctl(fd, SFX_GET_FREESPACE, data); + return rc ? nvme_submit_admin_passthru(fd, &cmd, NULL) : 0; +} + +int nvme_change_cap(int fd, __u32 nsid, __u64 capacity) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_change_cap, + .nsid = nsid, + .cdw10 = (capacity & 0xffffffff), + .cdw11 = (capacity >> 32), + }; + + return nvme_submit_admin_passthru(fd, &cmd, NULL); +} + +int nvme_sfx_set_features(int fd, __u32 nsid, __u32 fid, __u32 value) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_sfx_set_features, + .nsid = nsid, + .cdw10 = fid, + .cdw11 = value, + }; + + return nvme_submit_admin_passthru(fd, &cmd, NULL); +} + +int nvme_sfx_get_features(int fd, __u32 nsid, __u32 fid, __u32 *result) +{ + int err = 0; + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_sfx_get_features, + .nsid = nsid, + .cdw10 = fid, + }; + + err = nvme_submit_admin_passthru(fd, &cmd, NULL); + 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); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->non_media_crc_err_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->non_media_crc_err_cnt.raw)); + json_object_add_value_object(dev_stats, "non_media_crc_err_cnt", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->compression_path_err_cnt.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->compression_path_err_cnt.raw)); + json_object_add_value_object(dev_stats, "compression_path_err_cnt", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->out_of_space_flag.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->out_of_space_flag.raw)); + json_object_add_value_object(dev_stats, "out_of_space_flag", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->physical_usage_ratio.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->physical_usage_ratio.raw)); + json_object_add_value_object(dev_stats, "physical_usage_ratio", entry_stats); + + entry_stats = json_create_object(); + json_object_add_value_int(entry_stats, "normalized", smart->grown_bb.norm); + json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->grown_bb.raw)); + json_object_add_value_object(dev_stats, "grown_bb", 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)); + printf("non_media_crc_err_cnt : %3d%% %" PRIu64 "\n", + smart->non_media_crc_err_cnt.norm, + int48_to_long(smart->non_media_crc_err_cnt.raw)); + printf("compression_path_err_cnt : %3d%% %" PRIu64 "\n", + smart->compression_path_err_cnt.norm, + int48_to_long(smart->compression_path_err_cnt.raw)); + printf("out_of_space_flag : %3d%% %" PRIu64 "\n", + smart->out_of_space_flag.norm, + int48_to_long(smart->out_of_space_flag.raw)); + printf("phy_capacity_used_ratio : %3d%% %" PRIu64 "\n", + smart->physical_usage_ratio.norm, + int48_to_long(smart->physical_usage_ratio.raw)); + printf("grown_bb_count : %3d%% %" PRIu64 "\n", + smart->grown_bb.norm, int48_to_long(smart->grown_bb.raw)); + + +} + +static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct nvme_additional_smart_log smart_log; + 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 nvme_dev *dev; + struct config { + __u32 namespace_id; + bool raw_binary; + bool json; + }; + int err; + + 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() + }; + + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = nvme_get_nsid_log(dev_fd(dev), false, 0xca, cfg.namespace_id, + sizeof(smart_log), (void *)&smart_log); + if (!err) { + if (cfg.json) + show_sfx_smart_log_jsn(&smart_log, cfg.namespace_id, + dev->name); + else if (!cfg.raw_binary) + show_sfx_smart_log(&smart_log, cfg.namespace_id, + dev->name); + else + d_raw((unsigned char *)&smart_log, sizeof(smart_log)); + } else if (err > 0) { + nvme_show_status(err); + } + dev_close(dev); + return err; +} + +struct __packed sfx_lat_stats_vanda { + __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+ */ +}; + +struct __packed sfx_lat_stats_myrtle { + __u16 maj; + __u16 min; + __u32 bucket_1[64]; /* 0us~63us, step 1us */ + __u32 bucket_2[64]; /* 63us~127us, step 1us */ + __u32 bucket_3[64]; /* 127us~255us, step 2us */ + __u32 bucket_4[64]; /* 255us~510us, step 4us */ + __u32 bucket_5[64]; /* 510us~1.02ms step 8us */ + __u32 bucket_6[64]; /* 1.02ms~2.04ms step 16us */ + __u32 bucket_7[64]; /* 2.04ms~4.08ms step 32us */ + __u32 bucket_8[64]; /* 4.08ms~8.16ms step 64us */ + __u32 bucket_9[64]; /* 8.16ms~16.32ms step 128us */ + __u32 bucket_10[64]; /* 16.32ms~32.64ms step 256us */ + __u32 bucket_11[64]; /* 32.64ms~65.28ms step 512us */ + __u32 bucket_12[64]; /* 65.28ms~130.56ms step 1.024ms */ + __u32 bucket_13[64]; /* 130.56ms~261.12ms step 2.048ms */ + __u32 bucket_14[64]; /* 261.12ms~522.24ms step 4.096ms */ + __u32 bucket_15[64]; /* 522.24ms~1.04s step 8.192ms */ + __u32 bucket_16[64]; /* 1.04s~2.09s step 16.384ms */ + __u32 bucket_17[64]; /* 2.09s~4.18s step 32.768ms */ + __u32 bucket_18[64]; /* 4.18s~8.36s step 65.536ms */ + __u32 bucket_19[64]; /* 8.36s~ step 131.072ms */ + __u64 average; /* average latency statistics */ +}; + + +struct __packed sfx_lat_status_ver { + __u16 maj; + __u16 min; +}; + +struct sfx_lat_stats { + union { + struct sfx_lat_status_ver ver; + struct sfx_lat_stats_vanda vanda; + struct sfx_lat_stats_myrtle myrtle; + }; +}; + +static void show_lat_stats_vanda(struct sfx_lat_stats_vanda *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 void show_lat_stats_myrtle(struct sfx_lat_stats_myrtle *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 0us~63us, step 1us\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_1[i]); + + printf("\nGroup 2: Range is 63us~127us, step 1us\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_2[i]); + + printf("\nGroup 3: Range is 127us~255us, step 2us\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_3[i]); + + printf("\nGroup 4: Range is 255us~510us, step 4us\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_4[i]); + + printf("\nGroup 5: Range is 510us~1.02ms step\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_5[i]); + + printf("\nGroup 6: Range is 1.02ms~2.04ms step 16us\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_6[i]); + + printf("\nGroup 7: Range is 2.04ms~4.08ms step 32us\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_7[i]); + + printf("\nGroup 8: Range is 4.08ms~8.16ms step 64us\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_8[i]); + + printf("\nGroup 9: Range is 8.16ms~16.32ms step 128us\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_9[i]); + + printf("\nGroup 10: Range is 16.32ms~32.64ms step 256us\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_10[i]); + + printf("\nGroup 11: Range is 32.64ms~65.28ms step 512us\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_11[i]); + + printf("\nGroup 12: Range is 65.28ms~130.56ms step 1.024ms\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_12[i]); + + printf("\nGroup 13: Range is 130.56ms~261.12ms step 2.048ms\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_13[i]); + + printf("\nGroup 14: Range is 261.12ms~522.24ms step 4.096ms\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_14[i]); + + printf("\nGroup 15: Range is 522.24ms~1.04s step 8.192ms\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_15[i]); + + printf("\nGroup 16: Range is 1.04s~2.09s step 16.384ms\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_16[i]); + + printf("\nGroup 17: Range is 2.09s~4.18s step 32.768ms\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_17[i]); + + printf("\nGroup 18: Range is 4.18s~8.36s step 65.536ms\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_18[i]); + + printf("\nGroup 19: Range is 8.36s~ step 131.072ms\n"); + for (i = 0; i < 64; i++) + printf("Bucket %2d: %u\n", i, stats->bucket_19[i]); + + printf("\nAverage latency statistics %" PRIu64 "\n", + (uint64_t)stats->average); +} + + +static int get_lat_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct sfx_lat_stats stats; + 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 nvme_dev *dev; + struct config { + bool raw_binary; + bool write; + }; + int err; + + struct config cfg = { + }; + + OPT_ARGS(opts) = { + OPT_FLAG("write", 'w', &cfg.write, write), + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = nvme_get_log_simple(dev_fd(dev), cfg.write ? 0xc3 : 0xc1, + sizeof(stats), (void *)&stats); + if (!err) { + if ((stats.ver.maj == VANDA_MAJOR_IDX) && (stats.ver.min == VANDA_MINOR_IDX)) { + if (!cfg.raw_binary) + show_lat_stats_vanda(&stats.vanda, cfg.write); + else + d_raw((unsigned char *)&stats.vanda, sizeof(struct sfx_lat_stats_vanda)); + } else if ((stats.ver.maj == MYRTLE_MAJOR_IDX) && (stats.ver.min == MYRTLE_MINOR_IDX)) { + if (!cfg.raw_binary) + show_lat_stats_myrtle(&stats.myrtle, cfg.write); + else + d_raw((unsigned char *)&stats.myrtle, sizeof(struct sfx_lat_stats_myrtle)); + } else { + printf("ScaleFlux IO %s Command Latency Statistics Invalid Version Maj %d Min %d\n", + cfg.write ? "Write" : "Read", stats.ver.maj, stats.ver.min); + } + } else if (err > 0) { + nvme_show_status(err); + } + dev_close(dev); + return err; +} + +int sfx_nvme_get_log(int fd, __u32 nsid, __u8 log_id, __u32 data_len, void *data) +{ + struct nvme_passthru_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, NULL); +} + +/** + * @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%"PRIx64"", (uint64_t)*(bb_elem++)); + i++; + } + printf(" ]\n"); + + printf("REMAP_GBB_TABLE ["); + i = 0; + while (bb_elem < elem_end && i < remap_gbb_count) { + printf(" 0x%"PRIx64"", (uint64_t)*(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) +{ + const __u64 buf_size = 256*4096*sizeof(unsigned char); + unsigned char *data_buf; + struct nvme_dev *dev; + int err = 0; + + char *desc = "Get bad block table of sfx block device."; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + data_buf = malloc(buf_size); + if (!data_buf) { + fprintf(stderr, "malloc fail, errno %d\r\n", errno); + dev_close(dev); + return -1; + } + + err = get_bb_table(dev_fd(dev), 0xffffffff, data_buf, buf_size); + if (err < 0) { + perror("get-bad-block"); + } else if (err) { + nvme_show_status(err); + } else { + bd_table_show(data_buf, buf_size); + printf("ScaleFlux get bad block table: success\n"); + } + + free(data_buf); + dev_close(dev); + return 0; +} + +static void show_cap_info(struct sfx_freespace_ctx *ctx) +{ + + printf("logic capacity:%5lluGB(0x%"PRIx64")\n", + IDEMA_CAP2GB(ctx->user_space), (uint64_t)ctx->user_space); + printf("provisioned capacity:%5lluGB(0x%"PRIx64")\n", + IDEMA_CAP2GB(ctx->phy_space), (uint64_t)ctx->phy_space); + printf("free provisioned capacity:%5lluGB(0x%"PRIx64")\n", + IDEMA_CAP2GB(ctx->free_space), (uint64_t)ctx->free_space); + printf("used provisioned capacity:%5lluGB(0x%"PRIx64")\n", + IDEMA_CAP2GB(ctx->phy_space) - IDEMA_CAP2GB(ctx->free_space), + (uint64_t)(ctx->phy_space - ctx->free_space)); + printf("map_unit :0x%"PRIx64"K\n", (uint64_t)(ctx->map_unit * 4)); +} + +static int query_cap_info(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct sfx_freespace_ctx ctx = { 0 }; + char *desc = "query current capacity info"; + const char *raw = "dump output in binary format"; + struct nvme_dev *dev; + struct config { + bool raw_binary; + }; + struct config cfg; + int err = 0; + + OPT_ARGS(opts) = { + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (nvme_query_cap(dev_fd(dev), 0xffffffff, sizeof(ctx), &ctx)) { + perror("sfx-query-cap"); + err = -1; + } + + if (!err) { + if (!cfg.raw_binary) + show_cap_info(&ctx); + else + d_raw((unsigned char *)&ctx, sizeof(ctx)); + } + dev_close(dev); + return err; +} + +static int change_sanity_check(int fd, __u64 trg_in_4k, int *shrink) +{ + struct sfx_freespace_ctx freespace_ctx = { 0 }; + struct sysinfo s_info; + __u64 mem_need = 0; + __u64 cur_in_4k = 0; + __u64 provisoned_cap_4k = 0; + int extend = 0; + + if (nvme_query_cap(fd, 0xffffffff, sizeof(freespace_ctx), &freespace_ctx)) + return -1; + + /* + * capacity illegal check + */ + provisoned_cap_4k = freespace_ctx.phy_space >> + (SFX_PAGE_SHIFT - SECTOR_SHIFT); + if (trg_in_4k < provisoned_cap_4k || + trg_in_4k > ((__u64)provisoned_cap_4k * 4)) { + fprintf(stderr, + "WARNING: Only support 1.0~4.0 x provisioned capacity!\n"); + if (trg_in_4k < provisoned_cap_4k) + fprintf(stderr, + "WARNING: The target capacity is less than 1.0 x provisioned capacity!\n"); + else + fprintf(stderr, + "WARNING: The target capacity is larger than 4.0 x provisioned capacity!\n"); + return -1; + } + if (trg_in_4k > ((__u64)provisoned_cap_4k*4)) { + fprintf(stderr, "WARNING: the target capacity is too large\n"); + return -1; + } + + /* + * check whether mem enough if extend + */ + cur_in_4k = freespace_ctx.user_space >> (SFX_PAGE_SHIFT - SECTOR_SHIFT); + extend = (cur_in_4k <= trg_in_4k); + if (extend) { + 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: Free memory is not enough! Please drop cache or extend more memory and retry\n" + "WARNING: Memory needed is %"PRIu64", free memory is %"PRIu64"\n", + (uint64_t)mem_need, (uint64_t)s_info.freeram); + return -1; + } + } + *shrink = !extend; + + return 0; +} + +/** + * @brief prompt and get user confirm input + * + * @param str, prompt string + * + * @return 0, canceled; 1 confirmed + */ +static int sfx_confirm_change(const char *str) +{ + unsigned char confirm; + + fprintf(stderr, "WARNING: %s.\n" + "Use the force [--force] option to suppress this warning.\n", str); + + fprintf(stderr, "Confirm Y/y, Others cancel:\n"); + confirm = (unsigned char)fgetc(stdin); + if (confirm != 'y' && confirm != 'Y') { + fprintf(stderr, "Canceled.\n"); + return 0; + } + fprintf(stderr, "Sending operation ...\n"); + return 1; +} + +static int change_cap(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + char *desc = "dynamic change capacity"; + 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"; + struct nvme_dev *dev; + __u64 cap_in_4k = 0; + __u64 cap_in_sec = 0; + int shrink = 0; + int err = -1; + + struct config { + __u64 cap_in_byte; + __u32 capacity_in_gb; + bool 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_SUFFIX("cap-byte", 'z', &cfg.cap_in_byte, cap_byte), + OPT_FLAG("force", 'f', &cfg.force, force), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + 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 %"PRIu64"B %"PRIu64" 4K\n", + cfg.capacity_in_gb, (uint64_t)cfg.cap_in_byte, (uint64_t)cap_in_4k); + + if (change_sanity_check(dev_fd(dev), cap_in_4k, &shrink)) { + printf("ScaleFlux change-capacity: fail\n"); + dev_close(dev); + return err; + } + + if (!cfg.force && shrink && !sfx_confirm_change("Changing Cap may irrevocably delete this device's data")) { + dev_close(dev); + return 0; + } + + err = nvme_change_cap(dev_fd(dev), 0xffffffff, cap_in_4k); + if (err < 0) { + perror("sfx-change-cap"); + } else if (err) { + nvme_show_status(err); + } else { + printf("ScaleFlux change-capacity: success\n"); + ioctl(dev_fd(dev), BLKRRPART); + } + dev_close(dev); + return err; +} + +static int sfx_verify_chr(int fd) +{ + static struct stat nvme_stat; + int err = fstat(fd, &nvme_stat); + + if (err < 0) { + perror("fstat"); + return errno; + } + if (!S_ISCHR(nvme_stat.st_mode)) { + fprintf(stderr, + "Error: requesting clean card on non-controller handle\n"); + return -ENOTBLK; + } + return 0; +} + +static int sfx_clean_card(int fd) +{ + int ret; + + ret = sfx_verify_chr(fd); + if (ret) + return ret; + ret = ioctl(fd, NVME_IOCTL_CLR_CARD); + if (ret) + perror("Ioctl Fail."); + else + printf("ScaleFlux clean card success\n"); + + return ret; +} + +char *sfx_feature_to_string(int feature) +{ + switch (feature) { + case SFX_FEAT_ATOMIC: + return "ATOMIC"; + case SFX_FEAT_UP_P_CAP: + return "UPDATE_PROVISION_CAPACITY"; + default: + return "Unknown"; + } +} + +static int sfx_set_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + char *desc = "ScaleFlux internal set features\n" + "feature id 1: ATOMIC\n" + "value 0: Disable atomic write\n" + " 1: Enable atomic write"; + const char *value = "new value of feature (required)"; + const char *feature_id = "hex feature name (required)"; + const char *namespace_id = "desired namespace"; + const char *force = "The \"I know what I'm doing\" flag, skip confirmation before sending command"; + struct nvme_dev *dev; + struct nvme_id_ns ns; + int err = 0; + + struct config { + __u32 namespace_id; + __u32 feature_id; + __u32 value; + bool force; + }; + struct config cfg = { + .namespace_id = 1, + .feature_id = 0, + .value = 0, + .force = 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_FLAG("force", 's', &cfg.force, force), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (!cfg.feature_id) { + fprintf(stderr, "feature-id required param\n"); + dev_close(dev); + return -EINVAL; + } + + if (cfg.feature_id == SFX_FEAT_CLR_CARD) { + /*Warning for clean card*/ + if (!cfg.force && !sfx_confirm_change("Going to clean device's data, confirm umount fs and try again")) { + dev_close(dev); + return 0; + } else { + return sfx_clean_card(dev_fd(dev)); + } + + } + + if (cfg.feature_id == SFX_FEAT_ATOMIC && cfg.value) { + if (cfg.namespace_id != 0xffffffff) { + err = nvme_identify_ns(dev_fd(dev), cfg.namespace_id, + &ns); + if (err) { + if (err < 0) + perror("identify-namespace"); + else + nvme_show_status(err); + dev_close(dev); + 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"); + dev_close(dev); + return -EFAULT; + } + } + } else if (cfg.feature_id == SFX_FEAT_UP_P_CAP) { + if (cfg.value <= 0) { + fprintf(stderr, "Invalid Param\n"); + dev_close(dev); + return -EINVAL; + } + + /*Warning for change pacp by GB*/ + if (!cfg.force && !sfx_confirm_change("Changing physical capacity may irrevocably delete this device's data")) { + dev_close(dev); + return 0; + } + } + + err = nvme_sfx_set_features(dev_fd(dev), cfg.namespace_id, + cfg.feature_id, + cfg.value); + + if (err < 0) { + perror("ScaleFlux-set-feature"); + dev_close(dev); + return errno; + } else if (!err) { + printf("ScaleFlux set-feature:%#02x (%s), value:%d\n", cfg.feature_id, + sfx_feature_to_string(cfg.feature_id), cfg.value); + } else if (err > 0) { + nvme_show_status(err); + } + + dev_close(dev); + return err; +} + +static int sfx_get_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + 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"; + struct nvme_dev *dev; + __u32 result = 0; + int err = 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (!cfg.feature_id) { + fprintf(stderr, "feature-id required param\n"); + dev_close(dev); + return -EINVAL; + } + + err = nvme_sfx_get_features(dev_fd(dev), cfg.namespace_id, + cfg.feature_id, &result); + if (err < 0) { + perror("ScaleFlux-get-feature"); + dev_close(dev); + 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) { + nvme_show_status(err); + } + + dev_close(dev); + return err; + +} + +static int nvme_parse_evtlog(void *pevent_log_info, __u32 log_len, char *output) +{ + __u32 offset = 0; + __u32 length = log_len; + __u16 fw_core; + __u64 fw_time; + __u8 code_level; + __u8 code_type; + char str_buffer[512]; + __u32 str_pos; + FILE *fd; + int err = 0; + + enum sfx_evtlog_level { + sfx_evtlog_level_warning, + sfx_evtlog_level_error, + }; + + const char *sfx_evtlog_warning[4] = { + "RESERVED", + "TOO_MANY_BB", + "LOW_SPACE", + "HIGH_TEMPERATURE" + }; + + const char *sfx_evtlog_error[14] = { + "RESERVED", + "HAS_ASSERT", + "HAS_PANIC_DUMP", + "INVALID_FORMAT_CAPACITY", + "MAT_FAILED", + "FREEZE_DUE_TO_RECOVERY_FAILED", + "RFS_BROKEN", + "MEDIA_ERR_ON_PAGE_IN", + "MEDIA_ERR_ON_MPAGE_HEADER", + "CAPACITOR_BROKEN", + "READONLY_DUE_TO_RECOVERY_FAILED", + "RD_ERR_IN_GSD_RECOVERY", + "RD_ERR_ON_PF_RECOVERY", + "MEDIA_ERR_ON_FULL_RECOVERY" + }; + + struct sfx_nvme_evtlog_info { + __u16 time_stamp[4]; + __u64 magic1; + __u8 reverse[10]; + char evt_name[32]; + __u64 magic2; + char fw_ver[24]; + char bl2_ver[32]; + __u16 code; + __u16 assert_id; + } __packed; + + struct sfx_nvme_evtlog_info *info = NULL; + + fd = fopen(output, "w+"); + if (!fd) { + fprintf(stderr, "Failed to open %s file to write\n", output); + err = ENOENT; + goto ret; + } + + while (length > 0) { + info = (struct sfx_nvme_evtlog_info *)(pevent_log_info + offset); + + if ((info->magic1 == 0x474F4C545645) && + (info->magic2 == 0x38B0B3ABA9BA)) { + + memset(str_buffer, 0, 512); + str_pos = 0; + + fw_core = info->time_stamp[3]; + snprintf(str_buffer + str_pos, 16, "[%d-", fw_core); + str_pos = strlen(str_buffer); + + fw_time = ((__u64)info->time_stamp[2] << 32) + ((__u64)info->time_stamp[1] << 16) + (__u64)info->time_stamp[0]; + convert_ts(fw_time, str_buffer + str_pos); + str_pos = strlen(str_buffer); + + strcpy(str_buffer + str_pos, "] event-log:\n"); + str_pos = strlen(str_buffer); + + snprintf(str_buffer + str_pos, 128, + " > fw_version: %s\n > bl2_version: %s\n", + info->fw_ver, info->bl2_ver); + str_pos = strlen(str_buffer); + + code_level = (info->code & 0x100) >> 8; + code_type = (info->code % 0x100); + if (code_level == sfx_evtlog_level_warning) { + snprintf(str_buffer + str_pos, 128, + " > error_str: [WARNING][%s]\n\n", + sfx_evtlog_warning[code_type]); + } else { + if (info->assert_id) + snprintf(str_buffer + str_pos, 128, + " > error_str: [ERROR][%s]\n > assert_id: %d\n\n", + sfx_evtlog_error[code_type], info->assert_id); + else + snprintf(str_buffer + str_pos, 128, + " > error_str: [ERROR][%s]\n\n", + sfx_evtlog_error[code_type]); + } + str_pos = strlen(str_buffer); + + if (fwrite(str_buffer, 1, str_pos, fd) != str_pos) { + fprintf(stderr, "Failed to write parse result to output file\n"); + goto close_fd; + } + } + + offset++; + length--; + + if (!(offset % (log_len / 100)) || (offset == log_len)) + util_spinner("Parse", (float) (offset) / (float) (log_len)); + } + + printf("\nParse-evtlog: Success\n"); + +close_fd: + fclose(fd); +ret: + return err; +} + +static int nvme_dump_evtlog(struct nvme_dev *dev, __u32 namespace_id, __u32 storage_medium, + char *file, bool parse, char *output) +{ + struct nvme_persistent_event_log *pevent; + void *pevent_log_info; + _cleanup_huge_ struct nvme_mem_huge mh = { 0, }; + __u8 lsp_base; + __u32 offset = 0; + __u32 length = 0; + __u32 log_len; + __u32 single_len; + int err = 0; + FILE *fd = NULL; + struct nvme_get_log_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .lid = NVME_LOG_LID_PERSISTENT_EVENT, + .nsid = namespace_id, + .lpo = NVME_LOG_LPO_NONE, + .lsp = NVME_LOG_LSP_NONE, + .lsi = NVME_LOG_LSI_NONE, + .rae = false, + .uuidx = NVME_UUID_NONE, + .csi = NVME_CSI_NVM, + .ot = false, + .len = 0, + .log = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + + if (!storage_medium) { + lsp_base = 0; + single_len = 64 * 1024 - 4; + } else { + lsp_base = 4; + single_len = 32 * 1024; + } + + pevent = calloc(sizeof(*pevent), sizeof(__u8)); + if (!pevent) { + err = -ENOMEM; + goto ret; + } + + args.lsp = lsp_base + NVME_PEVENT_LOG_RELEASE_CTX; + args.log = pevent; + args.len = sizeof(*pevent); + + err = nvme_get_log(&args); + if (err) { + fprintf(stderr, "Unable to get evtlog lsp=0x%x, ret = 0x%x\n", args.lsp, err); + goto free_pevent; + } + + args.lsp = lsp_base + NVME_PEVENT_LOG_EST_CTX_AND_READ; + err = nvme_get_log(&args); + if (err) { + fprintf(stderr, "Unable to get evtlog lsp=0x%x, ret = 0x%x\n", args.lsp, err); + goto free_pevent; + } + + log_len = le64_to_cpu(pevent->tll); + if (log_len % 4) + log_len = (log_len / 4 + 1) * 4; + + pevent_log_info = nvme_alloc_huge(single_len, &mh); + if (!pevent_log_info) { + err = -ENOMEM; + goto free_pevent; + } + + fd = fopen(file, "wb+"); + if (!fd) { + fprintf(stderr, "Failed to open %s file to write\n", file); + err = ENOENT; + goto free_pevent; + } + + args.lsp = lsp_base + NVME_PEVENT_LOG_READ; + args.log = pevent_log_info; + length = log_len; + while (length > 0) { + args.lpo = offset; + if (length > single_len) { + args.len = single_len; + } else { + memset(args.log, 0, args.len); + args.len = length; + } + err = nvme_get_log(&args); + if (err) { + fprintf(stderr, "Unable to get evtlog offset=0x%x len 0x%x ret = 0x%x\n", offset, args.len, err); + goto close_fd; + } + + if (fwrite(args.log, 1, args.len, fd) != args.len) { + fprintf(stderr, "Failed to write evtlog to file\n"); + goto close_fd; + } + + offset += args.len; + length -= args.len; + util_spinner("Parse", (float) (offset) / (float) (log_len)); + } + + printf("\nDump-evtlog: Success\n"); + + if (parse) { + nvme_free_huge(&mh); + pevent_log_info = nvme_alloc_huge(log_len, &mh); + if (!pevent_log_info) { + fprintf(stderr, "Failed to alloc enough memory 0x%x to parse evtlog\n", log_len); + err = -ENOMEM; + goto close_fd; + } + + fclose(fd); + fd = fopen(file, "rb"); + if (!fd) { + fprintf(stderr, "Failed to open %s file to read\n", file); + err = ENOENT; + goto free_pevent; + } + if (fread(pevent_log_info, 1, log_len, fd) != log_len) { + fprintf(stderr, "Failed to read evtlog to buffer\n"); + goto close_fd; + } + + err = nvme_parse_evtlog(pevent_log_info, log_len, output); + } + +close_fd: + fclose(fd); +free_pevent: + free(pevent); +ret: + return err; +} + +static int sfx_dump_evtlog(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + char *desc = "dump evtlog into file and parse"; + const char *file = "evtlog file(required)"; + const char *namespace_id = "desired namespace"; + const char *storage_medium = "evtlog storage medium\n" + "0: nand(default) 1: nor"; + const char *parse = "parse error & warning evtlog from evtlog file"; + const char *output = "parse result output file"; + struct nvme_dev *dev; + int err = 0; + + struct config { + char *file; + __u32 namespace_id; + __u32 storage_medium; + bool parse; + char *output; + }; + struct config cfg = { + .file = NULL, + .namespace_id = 0xffffffff, + .storage_medium = 0, + .parse = false, + .output = NULL, + }; + + OPT_ARGS(opts) = { + OPT_FILE("file", 'f', &cfg.file, file), + OPT_UINT("namespace_id", 'n', &cfg.namespace_id, namespace_id), + OPT_UINT("storage_medium", 's', &cfg.storage_medium, storage_medium), + OPT_FLAG("parse", 'p', &cfg.parse, parse), + OPT_FILE("output", 'o', &cfg.output, output), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + goto ret; + + if (!cfg.file) { + fprintf(stderr, "file required param\n"); + err = EINVAL; + goto close_dev; + } + + if (cfg.parse && !cfg.output) { + fprintf(stderr, "output file required if evtlog need be parsed\n"); + err = EINVAL; + goto close_dev; + } + + err = nvme_dump_evtlog(dev, cfg.namespace_id, cfg.storage_medium, cfg.file, cfg.parse, cfg.output); + +close_dev: + dev_close(dev); +ret: + return err; +} + +static int nvme_expand_cap(struct nvme_dev *dev, __u32 namespace_id, __u64 namespace_size, + __u64 namespace_cap, __u32 lbaf, __u32 units) +{ + struct dirent **devices; + char dev_name[32] = ""; + int i = 0; + int num = 0; + int err = 0; + + struct sfx_expand_cap_info { + __u64 namespace_size; + __u64 namespace_cap; + __u8 reserve[10]; + __u8 lbaf; + __u8 reserve1[5]; + } __packed; + + if (S_ISCHR(dev->direct.stat.st_mode)) + snprintf(dev_name, 32, "%sn%u", dev->name, namespace_id); + else + strcpy(dev_name, dev->name); + + num = scandir("/dev", &devices, nvme_namespace_filter, alphasort); + if (num <= 0) { + err = num; + goto ret; + } + + if (strcmp(dev_name, devices[num-1]->d_name)) { + fprintf(stderr, "Expand namespace not the last one\n"); + err = EINVAL; + goto free_devices; + } + + if (!units) { + namespace_size = IDEMA_CAP(namespace_size) / (1 << (lbaf * 3)); + namespace_cap = IDEMA_CAP(namespace_cap) / (1 << (lbaf * 3)); + } + + struct sfx_expand_cap_info info = { + .namespace_size = namespace_size, + .namespace_cap = namespace_cap, + .lbaf = lbaf, + }; + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_ns_mgmt, + .nsid = namespace_id, + .addr = (__u64)(uintptr_t)&info, + .data_len = sizeof(info), + .cdw10 = 0x0e, + }; + + err = nvme_submit_admin_passthru(dev_fd(dev), &cmd, NULL); + if (err) { + fprintf(stderr, "Create ns failed\n"); + nvme_show_status(err); + goto free_devices; + } + +free_devices: + for (i = 0; i < num; i++) + free(devices[i]); + free(devices); +ret: + return err; +} + +static int sfx_expand_cap(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + char *desc = "expand capacity"; + const char *namespace_id = "desired namespace"; + const char *namespace_size = "namespace size(required)"; + const char *namespace_cap = "namespace capacity(required)"; + const char *lbaf = "LBA format to apply\n" + "0: 512(default) 1: 4096"; + const char *units = "namespace size/capacity units\n" + "0: GB(default) 1: LBA"; + struct nvme_dev *dev; + int err = 0; + + struct config { + __u32 namespace_id; + __u64 namespace_size; + __u64 namespace_cap; + __u32 lbaf; + __u32 units; + }; + struct config cfg = { + .namespace_id = 0xffffffff, + .lbaf = 0, + .units = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace_id", 'n', &cfg.namespace_id, namespace_id), + OPT_LONG("namespace_size", 's', &cfg.namespace_size, namespace_size), + OPT_LONG("namespace_cap", 'c', &cfg.namespace_cap, namespace_cap), + OPT_UINT("lbaf", 'l', &cfg.lbaf, lbaf), + OPT_UINT("units", 'u', &cfg.units, units), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + goto ret; + + if (cfg.namespace_id == 0xffffffff) { + if (S_ISCHR(dev->direct.stat.st_mode)) { + fprintf(stderr, "namespace_id or blk device required\n"); + err = EINVAL; + goto ret; + } else { + cfg.namespace_id = atoi(&dev->name[strlen(dev->name) - 1]); + } + } + + if (!cfg.namespace_size) { + fprintf(stderr, "namespace_size required param\n"); + err = EINVAL; + goto close_dev; + } + + if (!cfg.namespace_cap) { + fprintf(stderr, "namespace_cap required param\n"); + err = EINVAL; + goto close_dev; + } + + err = nvme_expand_cap(dev, cfg.namespace_id, cfg.namespace_size, cfg.namespace_cap, cfg.lbaf, cfg.units); + if (err) + goto close_dev; + + printf("%s: Success, create nsid:%d\n", cmd->name, cfg.namespace_id); + +close_dev: + dev_close(dev); +ret: + return err; +} diff --git a/plugins/scaleflux/sfx-nvme.h b/plugins/scaleflux/sfx-nvme.h new file mode 100644 index 0000000..53e2217 --- /dev/null +++ b/plugins/scaleflux/sfx-nvme.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#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", NVME_VERSION), + 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) + ENTRY("dump-evtlog", "dump evtlog into file and parse warning & error log", sfx_dump_evtlog) + ENTRY("expand-cap", "expand the last namespace capacity lossless", sfx_expand_cap) + ) +); + +#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..39f0d39 --- /dev/null +++ b/plugins/seagate/seagate-diag.h @@ -0,0 +1,388 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * 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. + * + * Author: Debabrata Bardhan <debabrata.bardhan@seagate.com> + */ + + +#ifndef SEAGATE_NVME_H +#define SEAGATE_NVME_H + +#define SEAGATE_PLUGIN_VERSION_MAJOR 1 +#define SEAGATE_PLUGIN_VERSION_MINOR 2 + +#define SEAGATE_OCP_PLUGIN_VERSION_MAJOR 1 +#define SEAGATE_OCP_PLUGIN_VERSION_MINOR 0 + +#define PERSIST_FILE_SIZE (2764800) +#define ONE_MB (1048576) /* (1024 * 1024) */ +#define PERSIST_CHUNK (65536) /* (1024 * 64) */ +#define FOUR_KB (4096) +#define STX_NUM_LEGACY_DRV (123) + + +const char* stx_jag_pan_mn[STX_NUM_LEGACY_DRV] = {"ST1000KN0002", "ST1000KN0012", "ST2000KN0002", + "ST2000KN0012", "ST4000KN0002", "XP1600HE10002", + "XP1600HE10012", "XP1600HE30002", "XP1600HE30012", + "XP1920LE10002", "XP1920LE10012", "XP1920LE30002", + "XP1920LE30012", "XP3200HE10002", "XP3200HE10012", + "XP3840LE10002", "XP3840LE10012", "XP400HE30002", + "XP400HE30012", "XP400HE30022", "XP400HE30032", + "XP480LE30002", "XP480LE30012", "XP480LE30022", + "XP480LE30032", "XP800HE10002", "XP800HE10012", + "XP800HE30002", "XP800HE30012", "XP800HE30022", + "XP800HE30032", "XP960LE10002", "XP960LE10012", + "XP960LE30002", "XP960LE30012", "XP960LE30022", + "XP960LE30032", "XP256LE30011", "XP256LE30021", + "XP7680LE80002", "XP7680LE80003", "XP15360LE80003", + "XP30720LE80003", "XP7200-1A2048", "XP7200-1A4096", + "XP7201-2A2048", "XP7201-2A4096", "XP7200-1A8192", + "ST1000HM0021", "ST1000HM0031", "ST1000HM0061", + "ST1000HM0071", "ST1000HM0081", "ST1200HM0001", + "ST1600HM0031", "ST1800HM0001", "ST1800HM0011", + "ST2000HM0011", "ST2000HM0031", "ST400HM0061", + "ST400HM0071", "ST500HM0021", "ST500HM0031", + "ST500HM0061", "ST500HM0071", "ST500HM0081", + "ST800HM0061", "ST800HM0071", "ST1600HM0011", + "ST1600KN0001", "ST1600KN0011", "ST1920HM0001", + "ST1920KN0001", "ST1920KN0011", "ST400HM0021", + "ST400KN0001", "ST400KN0011", "ST480HM0001", + "ST480KN0001", "ST480KN0011", "ST800HM0021", + "ST800KN0001", "ST800KN0011", "ST960HM0001", + "ST960KN0001", "ST960KN0011", "XF1441-1AA251024", + "XF1441-1AA252048", "XF1441-1AA25512", "XF1441-1AB251024", + "XF1441-1AB252048", "XF1441-1AB25512", "XF1441-1BA251024", + "XF1441-1BA252048", "XF1441-1BA25512", "XF1441-1BB251024", + "XF1441-1BB252048", "XF1441-1BB25512", "ST400HM0031", + "ST400KN0021", "ST400KN0031", "ST480HM0011", + "ST480KN0021", "ST480KN0031", "ST800HM0031", + "ST800KN0021", "ST800KN0031", "ST960HM0011", + "ST960KN0021", "ST960KN0031", "XM1441-1AA111024", + "XM1441-1AA112048", "XM1441-1AA11512", "XM1441-1AA801024", + "XM1441-1AA80512", "XM1441-1AB111024", "XM1441-1AB112048", + "XM1441-1BA111024", "XM1441-1BA112048", "XM1441-1BA11512", + "XM1441-1BA801024", "XM1441-1BA80512", "XM1441-1BB112048"}; + + +/*************************** +*Supported Log-Pages from FW +***************************/ + +typedef struct __attribute__((__packed__)) 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 __attribute__((__packed__)) 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 +***************************/ +#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 __attribute__((__packed__)) _SmartVendorSpecific +{ + __u8 AttributeNumber; + __u16 SmartStatus; + __u8 NominalValue; + __u8 LifetimeWorstValue; + __u32 Raw0_3; + __u8 RawHigh[3]; +} SmartVendorSpecific; + +typedef struct __attribute__((__packed__)) _EXTENDED_SMART_INFO_T +{ + __u16 Version; + SmartVendorSpecific vendorData[NUMBER_EXTENDED_SMART_ATTRIBUTES]; + __u8 vendor_specific_reserved[6]; +} EXTENDED_SMART_INFO_T; + +typedef struct __attribute__((__packed__)) vendor_smart_attribute_data +{ + __u8 AttributeNumber; + __u8 Rsvd[3]; + __u32 LSDword; + __u32 MSDword; +} vendor_smart_attribute_data; + +struct __attribute__((__packed__)) 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 __attribute__((__packed__)) _U128 +{ + __u64 LS__u64; + __u64 MS__u64; +} U128; + +typedef struct __attribute__((__packed__)) _vendor_log_page_CF_Attr +{ + __u16 SuperCapCurrentTemperature; + __u16 SuperCapMaximumTemperature; + __u8 SuperCapStatus; + __u8 Reserved5to7[3]; + U128 DataUnitsReadToDramNamespace; + U128 DataUnitsWrittenToDramNamespace; + __u64 DramCorrectableErrorCount; + __u64 DramUncorrectableErrorCount; +} vendor_log_page_CF_Attr; + +typedef struct __attribute__((__packed__)) _vendor_log_page_CF +{ + vendor_log_page_CF_Attr AttrCF; + __u8 Vendor_Specific_Reserved[ 456 ]; /* 56-511 */ +} vendor_log_page_CF; + +typedef struct __attribute__((__packed__)) _STX_EXT_SMART_LOG_PAGE_C0 +{ + U128 phyMediaUnitsWrt; + U128 phyMediaUnitsRd; + __u64 badUsrNandBlocks; + __u64 badSysNandBlocks; + __u64 xorRecoveryCnt; + __u64 ucRdEc; + __u64 softEccEc; + __u64 etoeCrrCnt; + __u64 sysDataUsed : 8; + __u64 refreshCount : 56; + __u64 usrDataEraseCnt; + __u16 thermalThrottling; + __u8 dssdSpecVerErrata; + __u16 dssdSpecVerPoint; + __u16 dssdSpecVerMinor; + __u8 dssdSpecVerMajor; + __u64 pcieCorrEc; + __u32 incompleteShutdowns; + __u32 rsvd_116_119; + __u8 freeBlocks; + __u8 rsvd_121_127[7]; + __u16 capHealth; + __u8 nvmeErrataVer; + __u8 rsvd_131_135[5]; + __u64 unalignedIO; + __u64 secVerNum; + __u64 totalNUSE; + U128 plpStartCnt; + U128 enduranceEstimate; + __u64 pcieLinkRetCnt; + __u64 powStateChangeCnt; + __u8 rsvd_208_493[286]; + __u16 logPageVer; + U128 logPageGUID; +} STX_EXT_SMART_LOG_PAGE_C0; + +/* EOF Extended-SMART Information*/ + + +/************************** +* PCIE ERROR INFORMATION +**************************/ +typedef struct __attribute__((__packed__)) 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 */ + +/************************************** +* FW Activation History Log INFORMATION +***************************************/ + +typedef struct __attribute__((__packed__)) _stx_fw_activ_his_ele +{ + __u8 entryVerNum; + __u8 entryLen; + __u16 rev02_03; + __u16 fwActivCnt; + __u64 timeStamp; + __u64 rev14_21; + __u64 powCycleCnt; + __u8 previousFW[8]; + __u8 newFW[8]; + __u8 slotNum; + __u8 commitActionType; + __u16 result; + __u8 rev50_63[14]; +} stx_fw_activ_his_ele; + +typedef struct __attribute__((__packed__)) _stx_fw_activ_history_log_page +{ + __u8 logID; + __u8 rev01_03[3]; + __u32 numValidFwActHisEnt; + stx_fw_activ_his_ele fwActHisEnt[20]; + __u8 rev1288_4077[2790]; + __u16 logPageVer; + __u8 logPageGUID[16]; +} stx_fw_activ_history_log_page; + +/* FW Activation History Log 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 OFFSET : 362-372 bytes */ + VS_ATTR_LIFETIME_WRITES1_TO_FLASH, /* 31 OFFSET : 374-385 bytes */ + VS_ATTR_LIFETIME_WRITES0_FROM_HOST, /* 32 OFFSET : 386-397 bytes */ + VS_ATTR_LIFETIME_WRITES1_FROM_HOST, /* 33 OFFSET : 398-409 bytes */ + VS_ATTR_LIFETIME_READ0_FROM_HOST, /* 34 OFFSET : 410-421 bytes */ + VS_ATTR_LIFETIME_READ1_FROM_HOST, /* 35 OFFSET : 422-433 bytes */ + VS_ATTR_PCIE_PHY_CRC_ERROR, /* 36 OFFSET : 434-445 bytes */ + VS_ATTR_BAD_BLOCK_COUNT_SYSTEM, /* 37 OFFSET : 446-457 bytes */ + VS_ATTR_BAD_BLOCK_COUNT_USER, /* 38 OFFSET : 458-469 bytes */ + VS_ATTR_THERMAL_THROTTLING_STATUS, /* 39 OFFSET : 470-481 bytes */ + VS_ATTR_POWER_CONSUMPTION, /* 40 OFFSET : 482-493 bytes */ + VS_ATTR_MAX_SOC_LIFE_TEMPERATURE, /* 41 OFFSET : 494-505 bytes */ + 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..887e5bc --- /dev/null +++ b/plugins/seagate/seagate-nvme.c @@ -0,0 +1,1968 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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. + * + * Author: Debabrata Bardhan <debabrata.bardhan@seagate.com> + */ + + +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <inttypes.h> +#include <sys/stat.h> +#include <ctype.h> +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-print.h" +#include <time.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"; + case 0x02: + return "SMART_INFORMATION"; + case 0x03: + return "FW_SLOT_INFORMATION"; + case 0x04: + return "CHANGED_NAMESPACE_LIST"; + case 0x05: + return "COMMANDS_SUPPORTED_AND_EFFECTS"; + case 0x06: + return "DEVICE_SELF_TEST"; + case 0x07: + return "TELEMETRY_HOST_INITIATED"; + case 0x08: + return "TELEMETRY_CONTROLLER_INITIATED"; + case 0xC0: + return "VS_MEDIA_SMART_LOG"; + case 0xC1: + return "VS_DEBUG_LOG1"; + case 0xC2: + return "VS_SEC_ERROR_LOG_PAGE"; + case 0xC3: + return "VS_LIFE_TIME_DRIVE_HISTORY"; + case 0xC4: + return "VS_EXTENDED_SMART_INFO"; + case 0xC5: + return "VS_LIST_SUPPORTED_LOG_PAGE"; + case 0xC6: + return "VS_POWER_MONITOR_LOG_PAGE"; + case 0xC7: + return "VS_CRITICAL_EVENT_LOG_PAGE"; + case 0xC8: + return "VS_RECENT_DRIVE_HISTORY"; + case 0xC9: + return "VS_SEC_ERROR_LOG_PAGE"; + case 0xCA: + return "VS_LIFE_TIME_DRIVE_HISTORY"; + case 0xCB: + return "VS_PCIE_ERROR_LOG_PAGE"; + case 0xCF: + return "DRAM Supercap SMART Attributes"; + case 0xD6: + return "VS_OEM2_WORK_LOAD"; + case 0xD7: + return "VS_OEM2_FW_SECURITY"; + case 0xD8: + return "VS_OEM2_REVISION"; + default: + return "UNKNOWN"; + } +} + +static int stx_is_jag_pan(char *devMN) +{ + int match_found = 1; /* found = 0, not_found = 1 */ + + for (int i = 0; i < STX_NUM_LEGACY_DRV; i++) { + match_found = strncmp(devMN, stx_jag_pan_mn[i], strlen(stx_jag_pan_mn[i])); + if (!match_found) + break; + } + + return match_found; +} + + +static void json_log_pages_supp(log_page_map *logPageMap) +{ + struct json_object *root; + struct json_object *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); + json_free_object(root); +} + +static int log_pages_supp(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err = 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"; + struct nvme_dev *dev; + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + err = nvme_get_log_simple(dev_fd(dev), 0xc5, + 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) + nvme_show_status(err); + dev_close(dev); + 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"; + case VS_ATTR_ID_REALLOCATED_SECTOR_COUNT: + return "Bad NAND block count"; + case VS_ATTR_ID_POWER_ON_HOURS: + return "Power On Hours"; + case VS_ATTR_ID_POWER_FAIL_EVENT_COUNT: + return "Power Fail Event Count"; + case VS_ATTR_ID_DEVICE_POWER_CYCLE_COUNT: + return "Device Power Cycle Count"; + case VS_ATTR_ID_RAW_READ_ERROR_RATE: + return "Raw Read Error Count"; + case VS_ATTR_ID_GROWN_BAD_BLOCK_COUNT: + return "Bad NAND block count"; + case VS_ATTR_ID_END_2_END_CORRECTION_COUNT: + return "SSD End to end correction counts"; + case VS_ATTR_ID_MIN_MAX_WEAR_RANGE_COUNT: + return "User data erase counts"; + case VS_ATTR_ID_REFRESH_COUNT: + return "Refresh count"; + case VS_ATTR_ID_BAD_BLOCK_COUNT_USER: + return "User data erase fail count"; + case VS_ATTR_ID_BAD_BLOCK_COUNT_SYSTEM: + return "System area erase fail count"; + case VS_ATTR_ID_THERMAL_THROTTLING_STATUS: + return "Thermal throttling status and count"; + case VS_ATTR_ID_ALL_PCIE_CORRECTABLE_ERROR_COUNT: + return "PCIe Correctable Error count"; + case VS_ATTR_ID_ALL_PCIE_UNCORRECTABLE_ERROR_COUNT: + return "PCIe Uncorrectable Error count"; + case VS_ATTR_ID_INCOMPLETE_SHUTDOWN_COUNT: + return "Incomplete shutdowns"; + case VS_ATTR_ID_GB_ERASED_LSB: + return "LSB of Flash GB erased"; + case VS_ATTR_ID_GB_ERASED_MSB: + return "MSB of Flash GB erased"; + case VS_ATTR_ID_LIFETIME_DEVSLEEP_EXIT_COUNT: + return "LIFETIME_DEV_SLEEP_EXIT_COUNT"; + case VS_ATTR_ID_LIFETIME_ENTERING_PS4_COUNT: + return "LIFETIME_ENTERING_PS4_COUNT"; + case VS_ATTR_ID_LIFETIME_ENTERING_PS3_COUNT: + return "LIFETIME_ENTERING_PS3_COUNT"; + case VS_ATTR_ID_RETIRED_BLOCK_COUNT: + return "Retired block count"; + case VS_ATTR_ID_PROGRAM_FAILURE_COUNT: + return "Program fail count"; + case VS_ATTR_ID_ERASE_FAIL_COUNT: + return "Erase Fail Count"; + case VS_ATTR_ID_AVG_ERASE_COUNT: + return "System data % used"; + case VS_ATTR_ID_UNEXPECTED_POWER_LOSS_COUNT: + return "Unexpected power loss count"; + case VS_ATTR_ID_WEAR_RANGE_DELTA: + return "Wear range delta"; + case VS_ATTR_ID_SATA_INTERFACE_DOWNSHIFT_COUNT: + return "PCIE_INTF_DOWNSHIFT_COUNT"; + case VS_ATTR_ID_END_TO_END_CRC_ERROR_COUNT: + return "E2E_CRC_ERROR_COUNT"; + case VS_ATTR_ID_UNCORRECTABLE_READ_ERRORS: + return "Uncorrectable Read Error Count"; + case VS_ATTR_ID_MAX_LIFE_TEMPERATURE: + return "Max lifetime temperature"; + case VS_ATTR_ID_RAISE_ECC_CORRECTABLE_ERROR_COUNT: + return "RAIS_ECC_CORRECT_ERR_COUNT"; + case VS_ATTR_ID_UNCORRECTABLE_RAISE_ERRORS: + return "Uncorrectable RAISE error count"; + case VS_ATTR_ID_DRIVE_LIFE_PROTECTION_STATUS: + return "DRIVE_LIFE_PROTECTION_STATUS"; + case VS_ATTR_ID_REMAINING_SSD_LIFE: + return "Remaining SSD life"; + case VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB: + return "LSB of Physical (NAND) bytes written"; + case VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB: + return "MSB of Physical (NAND) bytes written"; + case VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB: + return "LSB of Physical (HOST) bytes written"; + case VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB: + return "MSB of Physical (HOST) bytes written"; + case VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB: + return "LSB of Physical (NAND) bytes read"; + case VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB: + return "MSB of Physical (NAND) bytes read"; + case VS_ATTR_ID_FREE_SPACE: + return "Free Space"; + case VS_ATTR_ID_TRIM_COUNT_LSB: + return "LSB of Trim count"; + case VS_ATTR_ID_TRIM_COUNT_MSB: + return "MSB of Trim count"; + case VS_ATTR_ID_OP_PERCENTAGE: + return "OP percentage"; + case VS_ATTR_ID_MAX_SOC_LIFE_TEMPERATURE: + return "Max lifetime SOC temperature"; + 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) && (hideAttr != 1)) { + printf("%-40s", print_ext_smart_id(attr.AttributeNumber)); + printf("%-15d", attr.AttributeNumber); + printf(" 0x%016"PRIx64"\n", (uint64_t)smart_attribute_vs(verNo, attr)); + } + + 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\n", buf); + + 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\n", buf); + + 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\n", buf); + + 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\n", buf); + + 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\n", buf); + + } +} + +static void json_print_smart_log(struct json_object *root, EXTENDED_SMART_INFO_T *ExtdSMARTInfo) +{ + struct json_object *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}; + + 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) { + 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); +} + +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; + printf(" 0x%016"PRIx64"\n", le64_to_cpu(currentTemp)); + + maxTemp = pLogPageCF->AttrCF.SuperCapMaximumTemperature; + printf("%-40s", "Super-cap maximum temperature"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(maxTemp)); + + printf("%-40s", "Super-cap status"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageCF->AttrCF.SuperCapStatus)); + + printf("%-40s", "Data units read to DRAM namespace"); + printf(" 0x%016"PRIx64"%016"PRIx64"\n", le64_to_cpu(pLogPageCF->AttrCF.DataUnitsReadToDramNamespace.MS__u64), + le64_to_cpu(pLogPageCF->AttrCF.DataUnitsReadToDramNamespace.LS__u64)); + + printf("%-40s", "Data units written to DRAM namespace"); + printf(" 0x%016"PRIx64"%016"PRIx64"\n", le64_to_cpu(pLogPageCF->AttrCF.DataUnitsWrittenToDramNamespace.MS__u64), + le64_to_cpu(pLogPageCF->AttrCF.DataUnitsWrittenToDramNamespace.LS__u64)); + + printf("%-40s", "DRAM correctable error count"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageCF->AttrCF.DramCorrectableErrorCount)); + + printf("%-40s", "DRAM uncorrectable error count"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageCF->AttrCF.DramUncorrectableErrorCount)); +} + +static void json_print_smart_log_CF(struct json_object *root, vendor_log_page_CF *pLogPageCF) +{ + struct json_object *logPages; + unsigned int currentTemp, maxTemp; + char buf[40]; + + 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; + 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; + 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); +} + + +static void print_stx_smart_log_C0(STX_EXT_SMART_LOG_PAGE_C0 *pLogPageC0) +{ + printf("\n\nSeagate SMART Health Attributes :\n"); + printf("%-39s %-19s\n", "Description", "Health Attributes"); + + printf("%-40s", "Physical Media Units Written"); + printf(" 0x%016"PRIx64"%016"PRIx64"\n", le64_to_cpu(pLogPageC0->phyMediaUnitsWrt.MS__u64), + le64_to_cpu(pLogPageC0->phyMediaUnitsWrt.LS__u64)); + + printf("%-40s", "Physical Media Units Read"); + printf(" 0x%016"PRIx64"%016"PRIx64"\n", le64_to_cpu(pLogPageC0->phyMediaUnitsRd.MS__u64), + le64_to_cpu(pLogPageC0->phyMediaUnitsRd.LS__u64)); + + printf("%-40s", "Bad User NAND Blocks"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->badUsrNandBlocks)); + + printf("%-40s", "Bad System NAND Blocks"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->badSysNandBlocks)); + + printf("%-40s", "XOR Recovery Count"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->xorRecoveryCnt)); + + printf("%-40s", "Uncorrectable Read Error Count"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->ucRdEc)); + + printf("%-40s", "Soft ECC Error Count"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->softEccEc)); + + printf("%-40s", "End to End Correction Counts"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->etoeCrrCnt)); + + printf("%-40s", "System Data Used in Parcent"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->sysDataUsed)); + + printf("%-40s", "Refresh Counts"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->refreshCount)); + + printf("%-40s", "User Data Erase Counts"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->usrDataEraseCnt)); + + printf("%-40s", "Thermal Throttling Status and Count"); + printf(" 0x%04x\n", le16_to_cpu(pLogPageC0->thermalThrottling)); + + printf("%-40s", "DSSD Specification Version"); + printf(" %d.%d.%d.%d\n", pLogPageC0->dssdSpecVerMajor, + le16_to_cpu(pLogPageC0->dssdSpecVerMinor), + le16_to_cpu(pLogPageC0->dssdSpecVerPoint), + pLogPageC0->dssdSpecVerErrata); + + printf("%-40s", "PCIe Correctable Error Count"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->pcieCorrEc)); + + printf("%-40s", "Incomplete Shutdowns"); + printf(" 0x%08x\n", le32_to_cpu(pLogPageC0->incompleteShutdowns)); + + printf("%-40s", "Free Blocks in Percent"); + printf(" %d\n", pLogPageC0->freeBlocks); + + printf("%-40s", "Capacitor Health"); + printf(" 0x%04x\n", le16_to_cpu(pLogPageC0->capHealth)); + + printf("%-40s", "NVMe Errata Version"); + printf(" %c\n", pLogPageC0->nvmeErrataVer); + + printf("%-40s", "Unaligned IO"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->unalignedIO)); + + printf("%-40s", "Security Version Number"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->secVerNum)); + + printf("%-40s", "Total Namespace Utilization"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->totalNUSE)); + + printf("%-40s", "PLP Start Count"); + printf(" 0x%016"PRIx64"%016"PRIx64"\n", le64_to_cpu(pLogPageC0->plpStartCnt.MS__u64), + le64_to_cpu(pLogPageC0->plpStartCnt.LS__u64)); + + printf("%-40s", "Endurance Estimate"); + printf(" 0x%016"PRIx64"%016"PRIx64"\n", le64_to_cpu(pLogPageC0->enduranceEstimate.MS__u64), + le64_to_cpu(pLogPageC0->enduranceEstimate.LS__u64)); + + printf("%-40s", "PCIe Link Retraining Count"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->pcieLinkRetCnt)); + + printf("%-40s", "Power State Change Count"); + printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->powStateChangeCnt)); + + printf("%-40s", "Log Page Version"); + printf(" 0x%04x\n", le16_to_cpu(pLogPageC0->logPageVer)); + + printf("%-40s", "Log Page GUID"); + printf(" 0x%016"PRIx64"%016"PRIx64"\n", le64_to_cpu(pLogPageC0->logPageGUID.MS__u64), + le64_to_cpu(pLogPageC0->logPageGUID.LS__u64)); +} + +static void json_print_stx_smart_log_C0(struct json_object *root, STX_EXT_SMART_LOG_PAGE_C0 *pLogPageC0) +{ + struct json_object *logPages; + char buf[40]; + + logPages = json_create_array(); + json_object_add_value_array(root, "Seagate SMART Health Attributes", logPages); + + struct json_object *lbaf = json_create_object(); + + json_object_add_value_string(lbaf, "attribute_name", "Physical Media Units Written"); + memset(buf, 0, sizeof(buf)); + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageC0->phyMediaUnitsWrt.MS__u64), + le64_to_cpu(pLogPageC0->phyMediaUnitsWrt.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", "Physical Media Units Read"); + memset(buf, 0, sizeof(buf)); + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageC0->phyMediaUnitsRd.MS__u64), + le64_to_cpu(pLogPageC0->phyMediaUnitsRd.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", "Bad User NAND Blocks"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->badUsrNandBlocks)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Bad System NAND Blocks"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->badSysNandBlocks)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "XOR Recovery Count"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->xorRecoveryCnt)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Uncorrectable Read Error Count"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->ucRdEc)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Soft ECC Error Count"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->softEccEc)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "End to End Correction Counts"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->etoeCrrCnt)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "System Data Used in Parcent"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->sysDataUsed)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Refresh Counts"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->refreshCount)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "User Data Erase Counts"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->usrDataEraseCnt)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Thermal Throttling Status and Count"); + json_object_add_value_int(lbaf, "attribute_value", le16_to_cpu(pLogPageC0->thermalThrottling)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "DSSD Specification Version"); + memset(buf, 0, sizeof(buf)); + sprintf(buf, "%d.%d.%d.%d", pLogPageC0->dssdSpecVerMajor, + le16_to_cpu(pLogPageC0->dssdSpecVerMinor), + le16_to_cpu(pLogPageC0->dssdSpecVerPoint), + pLogPageC0->dssdSpecVerErrata); + 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", "PCIe Correctable Error Count"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->pcieCorrEc)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Incomplete Shutdowns"); + json_object_add_value_int(lbaf, "attribute_value", le32_to_cpu(pLogPageC0->incompleteShutdowns)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Free Blocks in Percent"); + json_object_add_value_int(lbaf, "attribute_value", pLogPageC0->freeBlocks); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Capacitor Health"); + json_object_add_value_int(lbaf, "attribute_value", le16_to_cpu(pLogPageC0->capHealth)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "NVMe Errata Version"); + json_object_add_value_int(lbaf, "attribute_value", pLogPageC0->nvmeErrataVer); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Unaligned IO"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->unalignedIO)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Security Version Number"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->secVerNum)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Total Namespace Utilization"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->totalNUSE)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "PLP Start Count"); + memset(buf, 0, sizeof(buf)); + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageC0->plpStartCnt.MS__u64), + le64_to_cpu(pLogPageC0->plpStartCnt.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", "Endurance Estimate"); + memset(buf, 0, sizeof(buf)); + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageC0->enduranceEstimate.MS__u64), + le64_to_cpu(pLogPageC0->enduranceEstimate.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", "PCIe Link Retraining Count"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->pcieLinkRetCnt)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Power State Change Count"); + json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->powStateChangeCnt)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Log Page Version"); + json_object_add_value_int(lbaf, "attribute_value", le16_to_cpu(pLogPageC0->logPageVer)); + json_array_add_value_object(logPages, lbaf); + + lbaf = json_create_object(); + json_object_add_value_string(lbaf, "attribute_name", "Log Page GUID"); + memset(buf, 0, sizeof(buf)); + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageC0->logPageGUID.MS__u64), + le64_to_cpu(pLogPageC0->logPageGUID.LS__u64)); + json_object_add_value_string(lbaf, "attribute_value", buf); + json_array_add_value_object(logPages, lbaf); +} + +static int vs_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct nvme_id_ctrl ctrl; + char modelNo[40]; + STX_EXT_SMART_LOG_PAGE_C0 ehExtSmart; + EXTENDED_SMART_INFO_T ExtdSMARTInfo; + vendor_log_page_CF logPageCF; + struct json_object *root = json_create_object(); + struct json_object *lbafs = json_create_array(); + struct json_object *lbafs_ExtSmart, *lbafs_DramSmart; + + const char *desc = "Retrieve the Firmware Activation History for Seagate NVMe drives"; + const char *output_format = "output in binary format"; + struct nvme_dev *dev; + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) { + printf("\nDevice not found\n"); + return -1; + } + + if (strcmp(cfg.output_format, "json")) + printf("Seagate Extended SMART Information :\n"); + + + /** + * Here we should identify if the drive is a Panthor or Jaguar. + * Here we need to extract the model no from ctrl-id abd use it + * to determine drive family. + */ + + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (!err) { + memcpy(modelNo, ctrl.mn, sizeof(modelNo)); + } else { + nvme_show_status(err); + return err; + } + + if (!stx_is_jag_pan(modelNo)) { + err = nvme_get_log_simple(dev_fd(dev), 0xC4, 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_simple(dev_fd(dev), 0xCF, sizeof(logPageCF), &logPageCF); + if (!err) { + if (strcmp(cfg.output_format, "json")) { + 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); + json_free_object(root); + } + } else if (err > 0) { + nvme_show_status(err); + } + } else { + err = nvme_get_log_simple(dev_fd(dev), 0xC0, sizeof(ehExtSmart), &ehExtSmart); + + if (!err) { + if (strcmp(cfg.output_format, "json")) { + print_stx_smart_log_C0(&ehExtSmart); + } else { + lbafs_ExtSmart = json_create_object(); + json_print_stx_smart_log_C0(lbafs_ExtSmart, &ehExtSmart); + + json_object_add_value_array(root, "SMART-Attributes", lbafs); + json_array_add_value_object(lbafs, lbafs_ExtSmart); + + json_print_object(root, NULL); + json_free_object(root); + } + } + + if (err > 0) + nvme_show_status(err); + } + + err = nvme_get_log_simple(dev_fd(dev), 0xC4, + 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_simple(dev_fd(dev), 0xCF, + sizeof(logPageCF), &logPageCF); + if (!err) { + if (strcmp(cfg.output_format, "json")) { + 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) { + nvme_show_status(err); + } + + dev_close(dev); + + 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 = 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); +} + +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 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 nvme_dev *dev; + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) { + 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_get_log_smart(dev_fd(dev), 0xffffffff, false, &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_simple(dev_fd(dev), 0xC4, + 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) + nvme_show_status(err); + } + + cf_err = nvme_get_log_simple(dev_fd(dev), 0xCF, + 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); + + dev_close(dev); + return err; +} +/* EOF Temperature Stats information */ + +/*************************************** + * PCIe error-log information + ***************************************/ +static void print_vs_pcie_error_log(pcie_error_log_page pcieErrorLog) +{ + __u32 correctPcieEc = pcieErrorLog.BadDllpErrCnt + pcieErrorLog.BadTlpErrCnt + + pcieErrorLog.RcvrErrCnt + pcieErrorLog.ReplayTOErrCnt + + pcieErrorLog.ReplayNumRolloverErrCnt; + __u32 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 = json_create_object(); + __u32 correctPcieEc = pcieErrorLog.BadDllpErrCnt + pcieErrorLog.BadTlpErrCnt + + pcieErrorLog.RcvrErrCnt + pcieErrorLog.ReplayTOErrCnt + + pcieErrorLog.ReplayNumRolloverErrCnt; + __u32 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); +} + +static int vs_pcie_error_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + pcie_error_log_page pcieErrorLog; + struct nvme_dev *dev; + + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) { + printf("\nDevice not found\n"); + return -1; + } + + if (strcmp(cfg.output_format, "json")) + printf("Seagate PCIe error counters Information :\n"); + + err = nvme_get_log_simple(dev_fd(dev), 0xCB, + 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) { + nvme_show_status(err); + } + + dev_close(dev); + return err; +} +/* EOF PCIE error-log information */ + + +/*************************************** + * FW Activation History log + ***************************************/ +static void print_stx_vs_fw_activate_history(stx_fw_activ_history_log_page fwActivHis) +{ + __u32 i; + char prev_fw[9] = {0}; + char new_fw[9] = {0}; + char buf[80]; + + if (fwActivHis.numValidFwActHisEnt > 0) { + printf("\n\nSeagate FW Activation History :\n"); + printf("%-9s %-21s %-7s %-13s %-9s %-5s %-15s %-9s\n", "Counter ", " Timestamp ", " PCC ", "Previous FW ", "New FW ", "Slot", "Commit Action", "Result"); + + for (i = 0; i < fwActivHis.numValidFwActHisEnt; i++) { + + printf(" %-4d ", fwActivHis.fwActHisEnt[i].fwActivCnt); + + time_t t = fwActivHis.fwActHisEnt[i].timeStamp / 1000; + struct tm ts = *localtime(&t); + + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &ts); + printf(" %-20s ", buf); + printf("%-5" PRId64 " ", + (uint64_t)fwActivHis.fwActHisEnt[i].powCycleCnt); + + memset(prev_fw, 0, sizeof(prev_fw)); + memcpy(prev_fw, fwActivHis.fwActHisEnt[i].previousFW, sizeof(fwActivHis.fwActHisEnt[i].previousFW)); + printf("%-8s ", prev_fw); + + memset(new_fw, 0, sizeof(new_fw)); + memcpy(new_fw, fwActivHis.fwActHisEnt[i].newFW, sizeof(fwActivHis.fwActHisEnt[i].newFW)); + printf("%-8s ", new_fw); + + printf(" %-2d ", fwActivHis.fwActHisEnt[i].slotNum); + printf(" 0x%02x ", fwActivHis.fwActHisEnt[i].commitActionType); + printf(" 0x%02x\n", fwActivHis.fwActHisEnt[i].result); + } + } else { + printf("%s\n", "Do not have valid FW Activation History"); + } +} + +static void json_stx_vs_fw_activate_history(stx_fw_activ_history_log_page fwActivHis) +{ + struct json_object *root = json_create_object(); + __u32 i; + + char buf[80]; + + struct json_object *historyLogPage = json_create_array(); + + json_object_add_value_array(root, "Seagate FW Activation History", historyLogPage); + + if (fwActivHis.numValidFwActHisEnt > 0) { + for (i = 0; i < fwActivHis.numValidFwActHisEnt; i++) { + struct json_object *lbaf = json_create_object(); + char prev_fw[8] = { 0 }; + char new_fw[8] = { 0 }; + + json_object_add_value_int(lbaf, "Counter", fwActivHis.fwActHisEnt[i].fwActivCnt); + + time_t t = fwActivHis.fwActHisEnt[i].timeStamp / 1000; + struct tm ts = *localtime(&t); + + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &ts); + printf(" %-20s ", buf); + json_object_add_value_string(lbaf, "Timestamp", buf); + + json_object_add_value_int(lbaf, "PCC", fwActivHis.fwActHisEnt[i].powCycleCnt); + sprintf(prev_fw, "%s", fwActivHis.fwActHisEnt[i].previousFW); + json_object_add_value_string(lbaf, "Previous_FW", prev_fw); + + sprintf(new_fw, "%s", fwActivHis.fwActHisEnt[i].newFW); + json_object_add_value_string(lbaf, "New_FW", new_fw); + + json_object_add_value_int(lbaf, "Slot", fwActivHis.fwActHisEnt[i].slotNum); + json_object_add_value_int(lbaf, "Commit_Action", fwActivHis.fwActHisEnt[i].commitActionType); + json_object_add_value_int(lbaf, "Result", fwActivHis.fwActHisEnt[i].result); + + json_array_add_value_object(historyLogPage, lbaf); + } + } else { + printf("%s\n", "Do not have valid FW Activation History"); + } + + json_print_object(root, NULL); + json_free_object(root); +} + +static int stx_vs_fw_activate_history(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + stx_fw_activ_history_log_page fwActivHis; + struct nvme_dev *dev; + + const char *desc = "Retrieve FW Activate History for Seagate 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err < 0) { + printf("\nDevice not found\n"); + return -1; + } + + if (strcmp(cfg.output_format, "json")) + printf("Seagate FW Activation History Information :\n"); + + err = nvme_get_log_simple(dev_fd(dev), 0xC2, sizeof(fwActivHis), &fwActivHis); + if (!err) { + if (strcmp(cfg.output_format, "json")) + print_stx_vs_fw_activate_history(fwActivHis); + else + json_stx_vs_fw_activate_history(fwActivHis); + + } else if (err > 0) { + nvme_show_status(err); + } + + dev_close(dev); + return err; +} +/* EOF FW Activation History log information */ + + +static int clear_fw_activate_history(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Clear FW Activation History for the given Seagate device "; + const char *save = "specifies that the controller shall save the attribute"; + int err; + struct nvme_dev *dev; + struct nvme_id_ctrl ctrl; + char modelNo[40]; + __u32 result; + + struct config { + bool save; + }; + + struct config cfg = { + .save = 0, + }; + + OPT_ARGS(opts) = { + OPT_FLAG("save", 's', &cfg.save, save), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err < 0) { + printf("\nDevice not found\n"); + return -1; + } + + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (!err) { + memcpy(modelNo, ctrl.mn, sizeof(modelNo)); + } else { + nvme_show_status(err); + return err; + } + + if (!stx_is_jag_pan(modelNo)) { + printf("\nDevice does not support Clear FW Activation History\n"); + } else { + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = 0xC1, + .nsid = 0, + .cdw11 = 0x80000000, + .cdw12 = 0, + .save = 0, + .uuidx = 0, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_set_features(&args); + if (err) + fprintf(stderr, "%s: couldn't clear PCIe correctable errors\n", + __func__); + } + + if (err < 0) { + perror("set-feature"); + return errno; + } + + dev_close(dev); + return err; +} + + +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"; + + struct nvme_id_ctrl ctrl; + char modelNo[40]; + + struct nvme_dev *dev; + + __u32 result; + int err; + + struct config { + bool save; + }; + + struct config cfg = { + .save = 0, + }; + + OPT_ARGS(opts) = { + OPT_FLAG("save", 's', &cfg.save, save), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) { + printf("\nDevice not found\n"); + return -1; + } + + + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (!err) { + memcpy(modelNo, ctrl.mn, sizeof(modelNo)); + } else { + nvme_show_status(err); + return err; + } + + if (!stx_is_jag_pan(modelNo)) { + err = nvme_set_features_simple(dev_fd(dev), 0xE1, 0, 0xCB, cfg.save, &result); + } else { + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = 0xC3, + .nsid = 0, + .cdw11 = 0x80000000, + .cdw12 = 0, + .save = 0, + .uuidx = 0, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_set_features(&args); + if (err) + fprintf(stderr, "%s: couldn't clear PCIe correctable errors\n", __func__); + } + + err = nvme_set_features_simple(dev_fd(dev), 0xE1, 0, 0xCB, cfg.save, &result); + + if (err < 0) { + perror("set-feature"); + return errno; + } + + dev_close(dev); + 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\n" + "state of the controller at the time the command is processed.\n" + "0 - controller shall not update the Telemetry Host Initiated Data."; + const char *raw = "output in raw format"; + struct nvme_temetry_log_hdr tele_log; + int blkCnt, maxBlk = 0, blksToGet; + struct nvme_dev *dev; + unsigned char *log; + __le64 offset = 0; + int err, dump_fd; + + struct config { + __u32 namespace_id; + __u32 log_id; + bool 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + dump_fd = STDOUT_FILENO; + cfg.log_id = (cfg.log_id << 8) | 0x07; + err = nvme_get_nsid_log(dev_fd(dev), false, cfg.log_id, + cfg.namespace_id, + 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", + dev->name, 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) { + nvme_show_status(err); + } else { + perror("log page"); + } + + blkCnt = 0; + + while (blkCnt < maxBlk) { + unsigned long long bytesToGet; + + blksToGet = ((maxBlk - blkCnt) >= TELEMETRY_BLOCKS_TO_READ) ? TELEMETRY_BLOCKS_TO_READ : (maxBlk - blkCnt); + + if (!blksToGet) { + dev_close(dev); + return err; + } + + bytesToGet = (unsigned long long)blksToGet * 512; + log = malloc(bytesToGet); + + if (!log) { + fprintf(stderr, "could not alloc buffer for log\n"); + dev_close(dev); + return -EINVAL; + } + + memset(log, 0, bytesToGet); + + struct nvme_get_log_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .lid = cfg.log_id, + .nsid = cfg.namespace_id, + .lpo = offset, + .lsp = 0, + .lsi = 0, + .rae = true, + .uuidx = 0, + .csi = NVME_CSI_NVM, + .ot = false, + .len = bytesToGet, + .log = (void *)log, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + err = nvme_get_log(&args); + if (!err) { + offset += (__le64)bytesToGet; + + if (!cfg.raw_binary) { + printf("\nBlock # :%d to %d\n", blkCnt + 1, blkCnt + blksToGet); + + d((unsigned char *)log, bytesToGet, 16, 1); + } else + seaget_d_raw((unsigned char *)log, bytesToGet, dump_fd); + } else if (err > 0) { + nvme_show_status(err); + } else { + perror("log page"); + } + + blkCnt += blksToGet; + + free(log); + } + + dev_close(dev); + 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"; + struct nvme_dev *dev; + int err, 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; + bool 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + dump_fd = STDOUT_FILENO; + + log_id = 0x08; + err = nvme_get_nsid_log(dev_fd(dev), false, log_id, cfg.namespace_id, + 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", + dev->name, 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) { + nvme_show_status(err); + } else { + perror("log page"); + } + + blkCnt = 0; + + while (blkCnt < maxBlk) { + unsigned long long bytesToGet; + + blksToGet = ((maxBlk - blkCnt) >= TELEMETRY_BLOCKS_TO_READ) ? TELEMETRY_BLOCKS_TO_READ : (maxBlk - blkCnt); + + if (!blksToGet) + return err; + + bytesToGet = (unsigned long long)blksToGet * 512; + log = malloc(bytesToGet); + + if (!log) { + fprintf(stderr, "could not alloc buffer for log\n"); + return -EINVAL; + } + + memset(log, 0, bytesToGet); + + struct nvme_get_log_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .lid = log_id, + .nsid = cfg.namespace_id, + .lpo = offset, + .lsp = 0, + .lsi = 0, + .rae = true, + .uuidx = 0, + .csi = NVME_CSI_NVM, + .ot = false, + .len = bytesToGet, + .log = (void *)log, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + err = nvme_get_log(&args); + if (!err) { + offset += (__le64)bytesToGet; + + if (!cfg.raw_binary) { + printf("\nBlock # :%d to %d\n", blkCnt + 1, blkCnt + blksToGet); + + d((unsigned char *)log, bytesToGet, 16, 1); + } else + seaget_d_raw((unsigned char *)log, bytesToGet, dump_fd); + } else if (err > 0) { + nvme_show_status(err); + } else { + perror("log page"); + } + + blkCnt += blksToGet; + + free(log); + } + + dev_close(dev); + return err; +} + +void seaget_d_raw(unsigned char *buf, int len, int fd) +{ + if (write(fd, (void *)buf, len) <= 0) + printf("%s: Write Failed\n", __func__); +} + + +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"; + struct nvme_dev *dev; + int err, dump_fd; + int flags = O_WRONLY | O_CREAT; + int mode = 0664; + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + dump_fd = STDOUT_FILENO; + if (strlen(cfg.file)) { + dump_fd = open(cfg.file, flags, mode); + if (dump_fd < 0) { + perror(cfg.file); + dev_close(dev); + return -EINVAL; + } + } + + log_id = 0x08; + err = nvme_get_nsid_log(dev_fd(dev), false, log_id, cfg.namespace_id, + sizeof(tele_log), (void *)(&tele_log)); + if (!err) { + maxBlk = tele_log.tele_data_area3; + offset += 512; + + seaget_d_raw((unsigned char *)(&tele_log), sizeof(tele_log), dump_fd); + } else if (err > 0) { + nvme_show_status(err); + } else { + perror("log page"); + } + + blkCnt = 0; + + while (blkCnt < maxBlk) { + unsigned long long bytesToGet; + + blksToGet = ((maxBlk - blkCnt) >= TELEMETRY_BLOCKS_TO_READ) ? TELEMETRY_BLOCKS_TO_READ : (maxBlk - blkCnt); + + if (!blksToGet) + goto out; + + bytesToGet = (unsigned long long)blksToGet * 512; + log = malloc(bytesToGet); + + if (!log) { + fprintf(stderr, "could not alloc buffer for log\n"); + err = EINVAL; + goto out; + } + + memset(log, 0, bytesToGet); + + struct nvme_get_log_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .lid = log_id, + .nsid = cfg.namespace_id, + .lpo = offset, + .lsp = 0, + .lsi = 0, + .rae = true, + .uuidx = 0, + .csi = NVME_CSI_NVM, + .ot = false, + .len = bytesToGet, + .log = (void *)log, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + err = nvme_get_log(&args); + if (!err) { + offset += (__le64)bytesToGet; + + seaget_d_raw((unsigned char *)log, bytesToGet, dump_fd); + + } else if (err > 0) { + nvme_show_status(err); + } else { + perror("log page"); + } + + blkCnt += blksToGet; + + free(log); + } +out: + if (strlen(cfg.file)) + close(dump_fd); + + dev_close(dev); + 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 */ + +/*OCP SEAGATE-PLUGIN Version */ +static int stx_ocp_plugin_version(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + printf("Seagate-OCP-Plugin version : %d.%d\n", + SEAGATE_OCP_PLUGIN_VERSION_MAJOR, + SEAGATE_OCP_PLUGIN_VERSION_MINOR); + return 0; +} +/*EOF OCP SEAGATE-PLUGIN Version */ diff --git a/plugins/seagate/seagate-nvme.h b/plugins/seagate/seagate-nvme.h new file mode 100644 index 0000000..99f6327 --- /dev/null +++ b/plugins/seagate/seagate-nvme.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * 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. + * + * Author: Debabrata Bardhan <debabrata.bardhan@seagate.com> + */ + +#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", NVME_VERSION), + 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("vs-fw-activate-history", "Retrieve the Firmware Activation History", stx_vs_fw_activate_history) + ENTRY("clear-fw-activate-history", "Clear Firmware Activation History", clear_fw_activate_history) + ENTRY("plugin-version", "Shows Seagate plugin's version information ", seagate_plugin_version) + ENTRY("cloud-SSD-plugin-version", "Shows OCP Seagate plugin's version information ", stx_ocp_plugin_version) + ) +); + +#endif +#include "define_cmd.h" diff --git a/plugins/sed/meson.build b/plugins/sed/meson.build new file mode 100644 index 0000000..4a2e544 --- /dev/null +++ b/plugins/sed/meson.build @@ -0,0 +1,4 @@ +sources += [ + 'plugins/sed/sed.c', + 'plugins/sed/sedopal_cmd.c', +] diff --git a/plugins/sed/sed.c b/plugins/sed/sed.c new file mode 100644 index 0000000..0471e54 --- /dev/null +++ b/plugins/sed/sed.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> +#include <inttypes.h> +#include <linux/fs.h> +#include <sys/stat.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "nvme-print.h" +#include "sedopal_cmd.h" +#include <linux/sed-opal.h> + +#define CREATE_CMD +#include "sed.h" + +OPT_ARGS(no_opts) = { + OPT_END() +}; + +OPT_ARGS(key_opts) = { + OPT_FLAG("ask-key", 'k', &sedopal_ask_key, + "prompt for SED authentication key"), + OPT_END() +}; + +OPT_ARGS(revert_opts) = { + OPT_FLAG("destructive", 'e', &sedopal_destructive_revert, + "destructive revert"), + OPT_FLAG("psid", 'p', &sedopal_psid_revert, "PSID revert"), + OPT_END() +}; + + +/* + * Open the NVMe device specified on the command line. It must be the + * NVMe block device (e.g. /dev/nvme0n1). + */ +static int sed_opal_open_device(struct nvme_dev **dev, int argc, char **argv, + const char *desc, struct argconfig_commandline_options *opts) +{ + int err; + + err = parse_and_open(dev, argc, argv, desc, opts); + if (err) + return err; + + if (!S_ISBLK((*dev)->direct.stat.st_mode)) { + fprintf(stderr, + "ERROR : The NVMe block device must be specified\n"); + err = -EINVAL; + dev_close(*dev); + } + + return err; +} + +static int sed_opal_discover(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err; + const char *desc = "Query SED device and display locking features"; + struct nvme_dev *dev; + + err = sed_opal_open_device(&dev, argc, argv, desc, no_opts); + if (err) + return err; + + err = sedopal_cmd_discover(dev->direct.fd); + + dev_close(dev); + return err; +} + +static int sed_opal_initialize(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err; + const char *desc = "Initialize a SED device for locking"; + struct nvme_dev *dev; + + err = sed_opal_open_device(&dev, argc, argv, desc, no_opts); + if (err) + return err; + + err = sedopal_cmd_initialize(dev->direct.fd); + if (err != 0) + fprintf(stderr, "initialize: SED error - %s\n", + sedopal_error_to_text(err)); + + dev_close(dev); + return err; +} + +static int sed_opal_revert(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err; + const char *desc = "Revert a SED device from locking state"; + struct nvme_dev *dev; + + err = sed_opal_open_device(&dev, argc, argv, desc, revert_opts); + if (err) + return err; + + err = sedopal_cmd_revert(dev->direct.fd); + if (err != 0) + fprintf(stderr, "revert: SED error - %s\n", + sedopal_error_to_text(err)); + + dev_close(dev); + return err; +} + +static int sed_opal_lock(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err; + const char *desc = "Lock a SED device"; + struct nvme_dev *dev; + + err = sed_opal_open_device(&dev, argc, argv, desc, key_opts); + if (err) + return err; + + err = sedopal_cmd_lock(dev->direct.fd); + if (err != 0) + fprintf(stderr, "lock: SED error - %s\n", + sedopal_error_to_text(err)); + + dev_close(dev); + return err; +} + +static int sed_opal_unlock(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err; + const char *desc = "Unlock a SED device"; + struct nvme_dev *dev; + + err = sed_opal_open_device(&dev, argc, argv, desc, key_opts); + if (err) + return err; + + err = sedopal_cmd_unlock(dev->direct.fd); + if (err != 0) + fprintf(stderr, "unlock: SED error - %s\n", + sedopal_error_to_text(err)); + + dev_close(dev); + return err; +} + +static int sed_opal_password(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err; + const char *desc = "Change the locking password of a SED device"; + struct nvme_dev *dev; + + err = sed_opal_open_device(&dev, argc, argv, desc, no_opts); + if (err) + return err; + + err = sedopal_cmd_password(dev->direct.fd); + if (err != 0) + fprintf(stderr, "password: SED error - %s\n", + sedopal_error_to_text(err)); + + dev_close(dev); + return err; +} diff --git a/plugins/sed/sed.h b/plugins/sed/sed.h new file mode 100644 index 0000000..1618272 --- /dev/null +++ b/plugins/sed/sed.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/sed/sed + +#include "cmd.h" +#include <linux/sed-opal.h> + +PLUGIN(NAME("sed", "SED Opal Command Set", NVME_VERSION), + COMMAND_LIST( + ENTRY("discover", "Discover SED Opal Locking Features", sed_opal_discover, "1") + ENTRY("initialize", "Initialize a SED Opal Device for locking", sed_opal_initialize) + ENTRY("revert", "Revert a SED Opal Device from locking", sed_opal_revert) + ENTRY("lock", "Lock a SED Opal Device", sed_opal_lock) + ENTRY("unlock", "Unlock a SED Opal Device", sed_opal_unlock) + ENTRY("password", "Change the SED Opal Device password", sed_opal_password) + ) +); + +#include "define_cmd.h" diff --git a/plugins/sed/sedopal_cmd.c b/plugins/sed/sedopal_cmd.c new file mode 100644 index 0000000..649e0b2 --- /dev/null +++ b/plugins/sed/sedopal_cmd.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <ctype.h> +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/mount.h> +#include <linux/sed-opal.h> +#include "sedopal_spec.h" +#include "sedopal_cmd.h" + +/* + * ask user for key rather than obtaining it from kernel keyring + */ +bool sedopal_ask_key; + +/* + * initiate dialog to ask for and confirm new password + */ +bool sedopal_ask_new_key; + +/* + * perform a destructive drive revert + */ +bool sedopal_destructive_revert; + +/* + * perform a PSID drive revert + */ +bool sedopal_psid_revert; + +/* + * Map method status codes to error text + */ +static const char * const sedopal_errors[] = { + [SED_STATUS_SUCCESS] = "Success", + [SED_STATUS_NOT_AUTHORIZED] = "Host Not Authorized", + [SED_STATUS_OBSOLETE_1] = "Obsolete", + [SED_STATUS_SP_BUSY] = "SP Session Busy", + [SED_STATUS_SP_FAILED] = "SP Failed", + [SED_STATUS_SP_DISABLED] = "SP Disabled", + [SED_STATUS_SP_FROZEN] = "SP Frozen", + [SED_STATUS_NO_SESSIONS_AVAILABLE] = "No Sessions Available", + [SED_STATUS_UNIQUENESS_CONFLICT] = "Uniqueness Conflict", + [SED_STATUS_INSUFFICIENT_SPACE] = "Insufficient Space", + [SED_STATUS_INSUFFICIENT_ROWS] = "Insufficient Rows", + [SED_STATUS_OBSOLETE_2] = "Obsolete", + [SED_STATUS_INVALID_PARAMETER] = "Invalid Parameter", + [SED_STATUS_OBSOLETE_3] = "Obsolete", + [SED_STATUS_OBSOLETE_4] = "Obsolete", + [SED_STATUS_TPER_MALFUNCTION] = "TPER Malfunction", + [SED_STATUS_TRANSACTION_FAILURE] = "Transaction Failure", + [SED_STATUS_RESPONSE_OVERFLOW] = "Response Overflow", + [SED_STATUS_AUTHORITY_LOCKED_OUT] = "Authority Locked Out", +}; + +const char *sedopal_error_to_text(int code) +{ + if (code == SED_STATUS_FAIL) + return "Failed"; + + if (code == SED_STATUS_NO_METHOD_STATUS) + return "Method returned no status"; + + if (code < SED_STATUS_SUCCESS || + code > SED_STATUS_AUTHORITY_LOCKED_OUT) + return("Unknown Error"); + + return sedopal_errors[code]; +} + +/* + * Read a user entered password and do some basic validity checks. + */ +char *sedopal_get_password(char *prompt) +{ + char *pass; + int len; + + pass = getpass(prompt); + if (pass == NULL) + return NULL; + + len = strlen(pass); + if (len < SEDOPAL_MIN_PASSWORD_LEN) + return NULL; + + if (len > SEDOPAL_MAX_PASSWORD_LEN) + return NULL; + + return pass; +} + +/* + * Initialize a SED Opal key. The key can either specify that the actual + * key should be looked up in the kernel keyring, or it should be + * populated in the key by prompting the user. + */ +int sedopal_set_key(struct opal_key *key) +{ +#if !HAVE_KEY_TYPE + /* + * If key_type isn't avaialable, force key prompt + */ + sedopal_ask_key = true; +#endif + + if (sedopal_ask_key) { + char *pass; + char *prompt; + + /* + * set proper prompt + */ + if (sedopal_ask_new_key) + prompt = SEDOPAL_NEW_PW_PROMPT; + else { + if (sedopal_psid_revert) + prompt = SEDOPAL_PSID_PROMPT; + else + prompt = SEDOPAL_CURRENT_PW_PROMPT; + } + + pass = sedopal_get_password(prompt); + if (pass == NULL) + return -EINVAL; + +#if HAVE_KEY_TYPE + key->key_type = OPAL_INCLUDED; +#endif + key->key_len = strlen(pass); + memcpy(key->key, pass, key->key_len); + + /* + * If getting a new key, ask for it to be re-entered + * and verify the two entries are the same. + */ + if (sedopal_ask_new_key) { + pass = sedopal_get_password(SEDOPAL_REENTER_PW_PROMPT); + if (strncmp((char *)key->key, pass, key->key_len)) { + fprintf(stderr, + "Error: passwords don't match\n"); + return -EINVAL; + } + } + } else { +#if HAVE_KEY_TYPE + key->key_type = OPAL_KEYRING; +#endif + key->key_len = 0; + } + + key->lr = 0; + + return 0; +} + +/* + * Prepare a drive for SED Opal locking. + */ +int sedopal_cmd_initialize(int fd) +{ + int rc; + struct opal_key key; + struct opal_lr_act lr_act = {}; + struct opal_user_lr_setup lr_setup = {}; + + sedopal_ask_key = true; + rc = sedopal_set_key(&key); + if (rc != 0) + return rc; + + /* + * take ownership of the device + */ + rc = ioctl(fd, IOC_OPAL_TAKE_OWNERSHIP, &key); + if (rc != 0) { + fprintf(stderr, + "Error: failed to take device ownership - %d\n", rc); + return rc; + } + + /* + * activate lsp + */ + lr_act.num_lrs = 1; + lr_act.sum = false; + lr_act.key = key; + + rc = ioctl(fd, IOC_OPAL_ACTIVATE_LSP, &lr_act); + if (rc != 0) { + fprintf(stderr, "Error: failed to activate LSP - %d\n", rc); + return rc; + } + + /* + * setup global locking range + */ + lr_setup.range_start = 0; + lr_setup.range_length = 0; + lr_setup.RLE = true; + lr_setup.WLE = true; + + lr_setup.session.opal_key = key; + lr_setup.session.sum = 0; + lr_setup.session.who = OPAL_ADMIN1; + + rc = ioctl(fd, IOC_OPAL_LR_SETUP, &lr_setup); + if (rc != 0) { + fprintf(stderr, + "Error: failed to setup locking range - %d\n", rc); + return rc; + } + + return rc; +} + +/* + * Lock a SED Opal drive + */ +int sedopal_cmd_lock(int fd) +{ + + return sedopal_lock_unlock(fd, OPAL_LK); +} + +/* + * Unlock a SED Opal drive + */ +int sedopal_cmd_unlock(int fd) +{ + + return sedopal_lock_unlock(fd, OPAL_RW); +} + +/* + * Prepare and issue an ioctl to lock/unlock a drive + */ +int sedopal_lock_unlock(int fd, int lock_state) +{ + int rc; + struct opal_lock_unlock opal_lu = {}; + + rc = sedopal_set_key(&opal_lu.session.opal_key); + if (rc != 0) + return rc; + + opal_lu.session.sum = 0; + opal_lu.session.who = OPAL_ADMIN1; + opal_lu.l_state = lock_state; + + rc = ioctl(fd, IOC_OPAL_LOCK_UNLOCK, &opal_lu); + if (rc != 0) + fprintf(stderr, + "Error: failed locking or unlocking - %d\n", rc); + + /* + * If the unlock was successful, force a re-read of the + * partition table. + */ + if (rc == 0) { + rc = ioctl(fd, BLKRRPART, 0); + if (rc != 0) + fprintf(stderr, + "Error: failed re-reading partition\n"); + } + + return rc; +} + +/* + * Confirm a destructive drive so that data is inadvertently erased + */ +static bool sedopal_confirm_revert(void) +{ + int rc; + char ans; + bool confirmed = false; + + /* + * verify that destructive revert is really the intention + */ + fprintf(stdout, + "Destructive revert erases drive data. Continue (y/n)? "); + rc = fscanf(stdin, " %c", &ans); + if ((rc == 1) && (ans == 'y' || ans == 'Y')) { + fprintf(stdout, "Are you sure (y/n)? "); + rc = fscanf(stdin, " %c", &ans); + if ((rc == 1) && (ans == 'y' || ans == 'Y')) + confirmed = true; + } + + return confirmed; +} + +/* + * perform a destructive drive revert + */ +static int sedopal_revert_destructive(int fd) +{ + struct opal_key key; + int rc; + + if (!sedopal_confirm_revert()) { + fprintf(stderr, "Aborting destructive revert\n"); + return -1; + } + + /* + * for destructive revert, require that key is provided + */ + sedopal_ask_key = true; + + rc = sedopal_set_key(&key); + if (rc == 0) + rc = ioctl(fd, IOC_OPAL_REVERT_TPR, &key); + + return rc; +} + +/* + * perform a PSID drive revert + */ +static int sedopal_revert_psid(int fd) +{ +#ifdef IOC_OPAL_PSID_REVERT_TPR + struct opal_key key; + int rc; + + if (!sedopal_confirm_revert()) { + fprintf(stderr, "Aborting PSID revert\n"); + return -1; + } + + rc = sedopal_set_key(&key); + if (rc == 0) { + rc = ioctl(fd, IOC_OPAL_PSID_REVERT_TPR, &key); + if (rc != 0) + fprintf(stderr, "PSID_REVERT_TPR rc %d\n", rc); + } + + return rc; +#else + fprintf(stderr, "ERROR : PSID revert is not supported\n"); + return -EOPNOTSUPP; +#endif /* IOC_OPAL_PSID_REVERT_TPR */ +} + +/* + * revert a drive from the provisioned state to a state where locking + * is disabled. + */ +int sedopal_cmd_revert(int fd) +{ + int rc; + + /* + * for revert, require that key/PSID is provided + */ + sedopal_ask_key = true; + + if (sedopal_psid_revert) { + rc = sedopal_revert_psid(fd); + } else if (sedopal_destructive_revert) { + rc = sedopal_revert_destructive(fd); + } else { +#ifdef IOC_OPAL_REVERT_LSP + struct opal_revert_lsp revert_lsp; + + rc = sedopal_set_key(&revert_lsp.key); + if (rc != 0) + return rc; + + revert_lsp.options = OPAL_PRESERVE; + revert_lsp.__pad = 0; + + rc = ioctl(fd, IOC_OPAL_REVERT_LSP, &revert_lsp); +#else + rc = -EOPNOTSUPP; +#endif + } + + if (rc != 0) + fprintf(stderr, "Error: failed reverting drive - %d\n", rc); + + return rc; +} + +/* + * Change the password of a drive. The existing password must be + * provided and the new password is confirmed by re-entry. + */ +int sedopal_cmd_password(int fd) +{ + int rc; + struct opal_new_pw new_pw = {}; + + new_pw.new_user_pw.who = OPAL_ADMIN1; + new_pw.new_user_pw.opal_key.lr = 0; + new_pw.session.who = OPAL_ADMIN1; + new_pw.session.sum = 0; + new_pw.session.opal_key.lr = 0; + + /* + * get current key + */ + sedopal_ask_key = true; + if (sedopal_set_key(&new_pw.session.opal_key) != 0) + return -EINVAL; + + /* + * get new key + */ + sedopal_ask_new_key = true; + if (sedopal_set_key(&new_pw.new_user_pw.opal_key) != 0) + return -EINVAL; + + rc = ioctl(fd, IOC_OPAL_SET_PW, &new_pw); + if (rc != 0) + fprintf(stderr, "Error: failed setting password - %d\n", rc); + + return rc; +} + +/* + * Print the state of locking features. + */ +void sedopal_print_locking_features(uint8_t features) +{ + printf("Locking Features:\n"); + printf("\tLocking Supported: %s\n", + (features & OPAL_FEATURE_LOCKING_SUPPORTED) ? "Yes" : "No"); + printf("\tLocking Feature Enabled: %s\n", + (features & OPAL_FEATURE_LOCKING_ENABLED) ? "Yes" : "No"); + printf("\tLocked: %s\n", + (features & OPAL_FEATURE_LOCKED) ? "Yes" : "No"); +} + +/* + * Query a drive to determine if it's SED Opal capable and + * it's current locking status. + */ +int sedopal_cmd_discover(int fd) +{ +#ifdef IOC_OPAL_DISCOVERY + int rc; + bool sedopal_locking_supported = false; + struct opal_discovery discover; + struct level_0_discovery_header *dh; + struct level_0_discovery_features *feat; + struct level_0_discovery_features *feat_end; + uint16_t code; + uint8_t locking_flags; + char buf[4096]; + + discover.data = (__u64)buf; + discover.size = sizeof(buf); + + rc = ioctl(fd, IOC_OPAL_DISCOVERY, &discover); + if (rc < 0) { + fprintf(stderr, "Error: ioctl IOC_OPAL_DISCOVERY failed\n"); + return rc; + } + + /* + * The returned buffer contains a level 0 discovery header + * folowed by an array of level 0 feature records. + * + * TCG Opal Specification v2.0.2 section 3.1.1 + */ + dh = (struct level_0_discovery_header *)buf; + feat = (struct level_0_discovery_features *)(dh + 1); + feat_end = (struct level_0_discovery_features *) + (buf + be32toh(dh->parameter_length)); + + /* + * iterate through all the features that were returned + */ + while (feat < feat_end) { + code = be16toh(feat->code); + switch (code) { + case OPAL_FEATURE_CODE_LOCKING: + locking_flags = feat->feature; + break; + case OPAL_FEATURE_CODE_OPALV2: + sedopal_locking_supported = true; + break; + default: + break; + } + + feat++; + } + + rc = 0; + if (!sedopal_locking_supported) { + fprintf(stderr, "Error: device does not support SED Opal\n"); + rc = -1; + } else + sedopal_print_locking_features(locking_flags); + + return rc; +#else /* IOC_OPAL_DISCOVERY */ + fprintf(stderr, "ERROR : NVMe device discovery is not supported\n"); + return -EOPNOTSUPP; +#endif +} diff --git a/plugins/sed/sedopal_cmd.h b/plugins/sed/sedopal_cmd.h new file mode 100644 index 0000000..3b6eae2 --- /dev/null +++ b/plugins/sed/sedopal_cmd.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _SED_OPAL_CMD_H +#define _SED_OPAL_CMD_H + +#define SEDOPAL_CURRENT_PW_PROMPT "Password: " +#define SEDOPAL_NEW_PW_PROMPT "New Password: " +#define SEDOPAL_REENTER_PW_PROMPT "Re-enter New Password: " +#define SEDOPAL_PSID_PROMPT "PSID: " + +#define SEDOPAL_MIN_PASSWORD_LEN 8 +#define SEDOPAL_MAX_PASSWORD_LEN 32 + +#define NVME_DEV_PATH "/dev/nvme" + +extern bool sedopal_ask_key; +extern bool sedopal_ask_new_key; +extern bool sedopal_destructive_revert; +extern bool sedopal_psid_revert; + +/* + * Sub-commands supported by the sedopal command + */ +enum sedopal_cmds { + SEDOPAL_CMD_NOT_SPECIFIED = -1, + SEDOPAL_CMD_INITIALIZE = 0, + SEDOPAL_CMD_LOCK = 1, + SEDOPAL_CMD_UNLOCK = 2, + SEDOPAL_CMD_REVERT = 3, + SEDOPAL_CMD_PASSWORD = 4, + SEDOPAL_CMD_DISCOVER = 5, +}; + +struct cmd_table { + int (*cmd_handler)(int fd); +}; + +/* + * command handlers + */ +int sedopal_cmd_initialize(int fd); +int sedopal_cmd_lock(int fd); +int sedopal_cmd_unlock(int fd); +int sedopal_cmd_revert(int fd); +int sedopal_cmd_password(int fd); +int sedopal_cmd_discover(int fd); + +/* + * utility functions + */ +int sedopal_open_nvme_device(char *device); +int sedopal_lock_unlock(int fd, int lock_state); +const char *sedopal_error_to_text(int code); + +#endif /* _SED_OPAL_CMD_H */ diff --git a/plugins/sed/sedopal_spec.h b/plugins/sed/sedopal_spec.h new file mode 100644 index 0000000..7523060 --- /dev/null +++ b/plugins/sed/sedopal_spec.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _SED_OPAL_SPEC_H +#define _SED_OPAL_SPEC_H + +/* + * TCP Storage Architecture Core Specification Version 2.01 + * section 5.1.5 Method Status Codes + */ +enum sed_status_codes { + SED_STATUS_SUCCESS = 0x00, + SED_STATUS_NOT_AUTHORIZED = 0x01, + SED_STATUS_OBSOLETE_1 = 0x02, + SED_STATUS_SP_BUSY = 0x03, + SED_STATUS_SP_FAILED = 0x04, + SED_STATUS_SP_DISABLED = 0x05, + SED_STATUS_SP_FROZEN = 0x06, + SED_STATUS_NO_SESSIONS_AVAILABLE = 0x07, + SED_STATUS_UNIQUENESS_CONFLICT = 0x08, + SED_STATUS_INSUFFICIENT_SPACE = 0x09, + SED_STATUS_INSUFFICIENT_ROWS = 0x0A, + SED_STATUS_OBSOLETE_2 = 0x0B, + SED_STATUS_INVALID_PARAMETER = 0x0C, + SED_STATUS_OBSOLETE_3 = 0x0D, + SED_STATUS_OBSOLETE_4 = 0x0E, + SED_STATUS_TPER_MALFUNCTION = 0x0F, + SED_STATUS_TRANSACTION_FAILURE = 0x10, + SED_STATUS_RESPONSE_OVERFLOW = 0x11, + SED_STATUS_AUTHORITY_LOCKED_OUT = 0x12, + SED_STATUS_FAIL = 0x3F, + SED_STATUS_NO_METHOD_STATUS = 0x89, +}; + +/* + * Definitions from TCG Opal Specification v2.0.2 + */ + +/* + * level 0 feature codes - section 3.1.1 + */ +#define OPAL_FEATURE_CODE_LOCKING 0x0002 +#define OPAL_FEATURE_CODE_OPALV2 0x0203 + +/* locking features */ +#define OPAL_FEATURE_LOCKING_SUPPORTED 0x01 +#define OPAL_FEATURE_LOCKING_ENABLED 0x02 +#define OPAL_FEATURE_LOCKED 0x04 + + +/* + * discovery header as specified in section 3.1.1.1 + */ +struct level_0_discovery_header { + uint32_t parameter_length; + uint32_t revision; + uint64_t reserved; + uint8_t vendor_specific[32]; +}; + +/* + * level 0 features as specified in section 3.1.1.3 + */ +struct level_0_discovery_features { + uint16_t code; + uint8_t version; + uint8_t length; + uint8_t feature; + uint8_t reserved[11]; +}; + +#endif /* _SED_OPAL_SPEC_H */ diff --git a/plugins/shannon/shannon-nvme.c b/plugins/shannon/shannon-nvme.c new file mode 100644 index 0000000..d4db8c7 --- /dev/null +++ b/plugins/shannon/shannon-nvme.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-print.h" + +#define CREATE_CMD +#include "shannon-nvme.h" + +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, +}; + +#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; + 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 nvme_dev *dev; + struct config { + __u32 namespace_id; + bool raw_binary; + }; + int err; + + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + err = nvme_get_nsid_log(dev_fd(dev), false, 0xca, cfg.namespace_id, + sizeof(smart_log), &smart_log); + if (!err) { + if (!cfg.raw_binary) + show_shannon_smart_log(&smart_log, cfg.namespace_id, dev->name); + else + d_raw((unsigned char *)&smart_log, sizeof(smart_log)); + } else if (err > 0) { + nvme_show_status(err); + } + dev_close(dev); + 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\n" + "specified controller. Operating parameters are grouped\n" + "and identified by Feature Identifiers; each Feature\n" + "Identifier contains one or more attributes that may affect\n" + "behavior of the feature. Each Feature has three possible\n" + "settings: default, saveable, and current. If a Feature is\n" + "saveable, it may be modified by set-feature. Default values\n" + "are vendor-specific and not changeable. Use set-feature to\n" + "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"; + struct nvme_dev *dev; + void *buf = NULL; + __u32 result; + int err; + + struct config { + __u32 namespace_id; + enum nvme_features_id feature_id; + __u8 sel; + __u32 cdw11; + __u32 data_len; + bool raw_binary; + bool 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (cfg.sel > 7) { + fprintf(stderr, "invalid 'select' param:%d\n", cfg.sel); + dev_close(dev); + return -EINVAL; + } + if (!cfg.feature_id) { + fprintf(stderr, "feature-id required param\n"); + dev_close(dev); + return -EINVAL; + } + if (cfg.data_len) { + if (posix_memalign(&buf, getpagesize(), cfg.data_len)) { + dev_close(dev); + exit(ENOMEM); + } + memset(buf, 0, cfg.data_len); + } + + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = cfg.feature_id, + .nsid = cfg.namespace_id, + .sel = cfg.sel, + .cdw11 = cfg.cdw11, + .uuidx = 0, + .data_len = cfg.data_len, + .data = buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_get_features(&args); + if (err > 0) + nvme_show_status(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\n" + "current operating parameters of the controller. Operating\n" + "parameters are grouped and identified by Feature\n" + "Identifiers. Feature settings can be applied to the entire\n" + "controller and all associated namespaces, or to only a few\n" + "namespace(s) associated with the controller. Default values\n" + "for each Feature are vendor-specific and may not be modified.\n" + "Use get-feature to determine which Features are supported by\n" + "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 ffd = STDIN_FILENO; + struct nvme_dev *dev; + void *buf = NULL; + __u32 result; + int err; + + struct config { + char *file; + __u32 namespace_id; + __u32 feature_id; + __u32 value; + __u32 data_len; + bool 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (!cfg.feature_id) { + fprintf(stderr, "feature-id required param\n"); + dev_close(dev); + return -EINVAL; + } + + if (cfg.data_len) { + if (posix_memalign(&buf, getpagesize(), cfg.data_len)) { + fprintf(stderr, "can not allocate feature payload\n"); + dev_close(dev); + 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; + } + } + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = cfg.feature_id, + .nsid = cfg.namespace_id, + .cdw11 = cfg.value, + .cdw12 = 0, + .save = cfg.save, + .uuidx = 0, + .cdw15 = 0, + .data_len = cfg.data_len, + .data = buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_set_features(&args); + if (err < 0) { + perror("set-feature"); + goto free; + } + if (!err) { + if (buf) + d(buf, cfg.data_len, 16, 1); + } else if (err > 0) + nvme_show_status(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..255bb6b --- /dev/null +++ b/plugins/shannon/shannon-nvme.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#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", NVME_VERSION), + COMMAND_LIST( + ENTRY("smart-log-add", "Retrieve Shannon SMART Log, show it", get_additional_smart_log) + ENTRY("set-additioal-feature", "Set additional Shannon feature", set_additional_feature) + ENTRY("get-additional-feature", "Get additional Shannon feature", get_additional_feature) + ENTRY("id-ctrl", "Retrieve Shannon ctrl id, show it", shannon_id_ctrl) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/solidigm/meson.build b/plugins/solidigm/meson.build new file mode 100644 index 0000000..052afa1 --- /dev/null +++ b/plugins/solidigm/meson.build @@ -0,0 +1,16 @@ +sources += [ + 'plugins/solidigm/solidigm-id-ctrl.c', + 'plugins/solidigm/solidigm-util.c', + 'plugins/solidigm/solidigm-smart.c', + 'plugins/solidigm/solidigm-garbage-collection.c', + 'plugins/solidigm/solidigm-latency-tracking.c', + 'plugins/solidigm/solidigm-log-page-dir.c', + 'plugins/solidigm/solidigm-telemetry.c', + 'plugins/solidigm/solidigm-internal-logs.c', + 'plugins/solidigm/solidigm-market-log.c', + 'plugins/solidigm/solidigm-temp-stats.c', + 'plugins/solidigm/solidigm-get-drive-info.c', + 'plugins/solidigm/solidigm-ocp-version.c', +] +subdir('solidigm-telemetry') + diff --git a/plugins/solidigm/solidigm-garbage-collection.c b/plugins/solidigm/solidigm-garbage-collection.c new file mode 100644 index 0000000..a37e9c5 --- /dev/null +++ b/plugins/solidigm/solidigm-garbage-collection.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-print.h" +#include "solidigm-garbage-collection.h" +#include "solidigm-util.h" + +struct __packed gc_item { + __le32 timer_type; + __le64 timestamp; +}; + +#define VU_GC_MAX_ITEMS 100 +struct garbage_control_collection_log { + __le16 version_major; + __le16 version_minor; + struct __packed gc_item item[VU_GC_MAX_ITEMS]; + __u8 reserved[2892]; +}; + +static void vu_gc_log_show_json(struct garbage_control_collection_log *payload, const char *devname) +{ + struct json_object *gc_entries = json_create_array(); + + for (int i = 0; i < VU_GC_MAX_ITEMS; i++) { + struct __packed gc_item item = payload->item[i]; + struct json_object *entry = json_create_object(); + + json_object_add_value_int(entry, "timestamp", le64_to_cpu(item.timestamp)); + json_object_add_value_int(entry, "timer_type", le32_to_cpu(item.timer_type)); + json_array_add_value_object(gc_entries, entry); + } + + json_print_object(gc_entries, NULL); + json_free_object(gc_entries); +} + +static void vu_gc_log_show(struct garbage_control_collection_log *payload, const char *devname, + __u8 uuid_index) +{ + printf("Solidigm Garbage Collection Log for NVME device:%s UUID-idx:%d\n", devname, + uuid_index); + printf("Timestamp Timer Type\n"); + + for (int i = 0; i < VU_GC_MAX_ITEMS; i++) { + struct __packed gc_item item = payload->item[i]; + + printf("%-13" PRIu64 " %d\n", le64_to_cpu(item.timestamp), le32_to_cpu(item.timer_type)); + } +} + +int solidigm_get_garbage_collection_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Get and parse Solidigm vendor specific garbage collection event log."; + enum nvme_print_flags flags; + struct nvme_dev *dev; + int err; + __u8 uuid_index; + + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = validate_output_format(cfg.output_format, &flags); + if (err) { + fprintf(stderr, "Invalid output format '%s'\n", cfg.output_format); + dev_close(dev); + return -EINVAL; + } + + uuid_index = solidigm_get_vu_uuid_index(dev); + + struct garbage_control_collection_log gc_log; + const int solidigm_vu_gc_log_id = 0xfd; + struct nvme_get_log_args args = { + .lpo = 0, + .result = NULL, + .log = &gc_log, + .args_size = sizeof(args), + .fd = dev_fd(dev), + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .lid = solidigm_vu_gc_log_id, + .len = sizeof(gc_log), + .nsid = NVME_NSID_ALL, + .csi = NVME_CSI_NVM, + .lsi = NVME_LOG_LSI_NONE, + .lsp = NVME_LOG_LSP_NONE, + .uuidx = uuid_index, + .rae = false, + .ot = false, + }; + + err = nvme_get_log(&args); + if (!err) { + if (flags & BINARY) + d_raw((unsigned char *)&gc_log, sizeof(gc_log)); + else if (flags & JSON) + vu_gc_log_show_json(&gc_log, dev->name); + else + vu_gc_log_show(&gc_log, dev->name, uuid_index); + } else if (err > 0) { + nvme_show_status(err); + } + + /* Redundant close() to make static code analysis happy */ + close(dev->direct.fd); + dev_close(dev); + return err; +} diff --git a/plugins/solidigm/solidigm-garbage-collection.h b/plugins/solidigm/solidigm-garbage-collection.h new file mode 100644 index 0000000..a3e34b2 --- /dev/null +++ b/plugins/solidigm/solidigm-garbage-collection.h @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +int solidigm_get_garbage_collection_log(int argc, char **argv, struct command *cmd, struct plugin *plugin); diff --git a/plugins/solidigm/solidigm-get-drive-info.c b/plugins/solidigm/solidigm-get-drive-info.c new file mode 100644 index 0000000..21f59bb --- /dev/null +++ b/plugins/solidigm/solidigm-get-drive-info.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Solidigm. + * + * Authors: leonardo.da.cunha@solidigm.com + */ + +#include <errno.h> +#include "nvme-print.h" +#include "nvme-wrap.h" +#include "common.h" + +int sldgm_get_drive_info(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; + const char *desc = "Get drive HW information"; + const char *FTL_unit_size_str = "FTL_unit_size"; + char *output_format = "normal"; + enum nvme_print_flags flags; + nvme_root_t r; + nvme_ctrl_t c; + nvme_ns_t n; + struct nvme_id_ns ns = { 0 }; + __u8 flbaf_inUse; + __u16 lba_size; + __u16 ftl_unit_size; + int err; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &output_format, "normal|json"), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = validate_output_format(output_format, &flags); + if ((err < 0) || !(flags == NORMAL || flags == JSON)) { + nvme_show_error("Invalid output format"); + return err; + } + + r = nvme_scan(NULL); + c = nvme_scan_ctrl(r, dev->name); + n = c ? nvme_ctrl_first_ns(c) : nvme_scan_namespace(dev->name); + if (!n) { + nvme_show_error("solidigm-vs-drive-info: drive missing namespace"); + return -EINVAL; + } + + err = nvme_ns_identify(n, &ns); + if (err) { + nvme_show_error("identify namespace: %s", nvme_strerror(errno)); + return err; + } + + if (!(ns.nsfeat & 0x10)) { + nvme_show_error("solidigm-vs-drive-info: performance options not available"); + return -EINVAL; + } + + nvme_id_ns_flbas_to_lbaf_inuse(ns.flbas, &flbaf_inUse); + lba_size = 1 << ns.lbaf[flbaf_inUse].ds; + ftl_unit_size = (le16_to_cpu(ns.npwg) + 1) * lba_size / 1024; + + if (flags == JSON) { + struct json_object *root = json_create_object(); + + json_object_add_value_int(root, FTL_unit_size_str, ftl_unit_size); + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); + } else { + printf("%s: %d\n", FTL_unit_size_str, ftl_unit_size); + } + + return err; +} diff --git a/plugins/solidigm/solidigm-get-drive-info.h b/plugins/solidigm/solidigm-get-drive-info.h new file mode 100644 index 0000000..ffc1bd2 --- /dev/null +++ b/plugins/solidigm/solidigm-get-drive-info.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +int sldgm_get_drive_info(int argc, char **argv, struct command *cmd, struct plugin *plugin); diff --git a/plugins/solidigm/solidigm-id-ctrl.c b/plugins/solidigm/solidigm-id-ctrl.c new file mode 100644 index 0000000..f45e758 --- /dev/null +++ b/plugins/solidigm/solidigm-id-ctrl.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include <inttypes.h> +#include "common.h" +#include "solidigm-id-ctrl.h" + +struct __packed nvme_vu_id_ctrl_field { /* CDR MR5 */ + __u8 rsvd1[3]; + __u8 ss; + char health[20]; + __u8 cls; + __u8 nlw; + __u8 scap; + __u8 sstat; + char bl[8]; + __u8 rsvd2[38]; + __le64 ww; + char mic_bl[4]; + char mic_fw[4]; +}; + +void sldgm_id_ctrl(uint8_t *vs, struct json_object *root) +{ + // text output aligns nicely with property name up to 10 chars + const char *str_ss = "stripeSize"; + const char *str_health = "health"; + const char *str_cls = "linkSpeed"; + const char *str_nlw = "negLnkWdth"; + const char *str_scap = "secCapab"; + const char *str_sstat = "secStatus"; + const char *str_bl = "bootLoader"; + const char *str_ww = "wwid"; + const char *str_mic_bl = "bwLimGran"; + const char *str_mic_fw = "ioLimGran"; + + struct nvme_vu_id_ctrl_field *id = (struct nvme_vu_id_ctrl_field *)vs; + + const char str_heathy[sizeof(id->health)] = "healthy"; + const char *health = id->health[0] ? id->health : str_heathy; + + if (root == NULL) { + printf("%-10s: %u\n", str_ss, id->ss); + printf("%-10s: %.*s\n", str_health, (int)sizeof(id->health), health); + printf("%-10s: %u\n", str_cls, id->cls); + printf("%-10s: %u\n", str_nlw, id->nlw); + printf("%-10s: %u\n", str_scap, id->scap); + printf("%-10s: %u\n", str_sstat, id->sstat); + printf("%-10s: %.*s\n", str_bl, (int)sizeof(id->bl), id->bl); + printf("%-10s: 0x%016"PRIx64"\n", str_ww, le64_to_cpu(id->ww)); + printf("%-10s: %.*s\n", str_mic_bl, (int)sizeof(id->mic_bl), id->mic_bl); + printf("%-10s: %.*s\n", str_mic_fw, (int)sizeof(id->mic_fw), id->mic_fw); + return; + } + + json_object_add_value_uint(root, str_ss, id->ss); + json_object_object_add(root, str_health, + json_object_new_string_len(health, sizeof(id->health))); + json_object_add_value_uint(root, str_cls, id->cls); + json_object_add_value_uint(root, str_nlw, id->nlw); + json_object_add_value_uint(root, str_scap, id->scap); + json_object_add_value_uint(root, str_sstat, id->sstat); + json_object_object_add(root, str_bl, json_object_new_string_len(id->bl, sizeof(id->bl))); + json_object_add_value_uint64(root, str_ww, le64_to_cpu(id->ww)); + json_object_object_add(root, str_mic_bl, + json_object_new_string_len(id->mic_bl, sizeof(id->mic_bl))); + json_object_object_add(root, str_mic_fw, + json_object_new_string_len(id->mic_fw, sizeof(id->mic_fw))); +} diff --git a/plugins/solidigm/solidigm-id-ctrl.h b/plugins/solidigm/solidigm-id-ctrl.h new file mode 100644 index 0000000..ed6e438 --- /dev/null +++ b/plugins/solidigm/solidigm-id-ctrl.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include <inttypes.h> +#include "util/json.h" +void sldgm_id_ctrl(uint8_t *vs, struct json_object *root); diff --git a/plugins/solidigm/solidigm-internal-logs.c b/plugins/solidigm/solidigm-internal-logs.c new file mode 100644 index 0000000..c604761 --- /dev/null +++ b/plugins/solidigm/solidigm-internal-logs.c @@ -0,0 +1,657 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Solidigm. + * + * Authors: leonardo.da.cunha@solidigm.com + * shankaralingegowda.singonahalli@solidigm.com + */ + +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> +#include <linux/limits.h> +#include <time.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "nvme-print.h" +#include "solidigm-util.h" + +#define DWORD_SIZE 4 + +enum log_type { + NLOG = 0, + EVENTLOG = 1, + ASSERTLOG = 2, +}; + +#pragma pack(push, internal_logs, 1) +struct version { + __u16 major; + __u16 minor; +}; + +struct event_dump_instance { + __u32 numeventdumps; + __u32 coresize; + __u32 coreoffset; + __u32 eventidoffset[16]; + __u8 eventIdValidity[16]; +}; + +struct commom_header { + struct version ver; + __u32 header_size; + __u32 log_size; + __u32 numcores; +}; + +struct event_dump_header { + struct commom_header header; + __u32 eventidsize; + struct event_dump_instance edumps[0]; +}; + +struct assert_dump_core { + __u32 coreoffset; + __u32 assertsize; + __u8 assertdumptype; + __u8 assertvalid; + __u8 reserved[2]; +}; + +struct assert_dump_header { + struct commom_header header; + struct assert_dump_core core[]; +}; + +struct nlog_dump_header_common { + struct version ver; + __u32 logselect; + __u32 totalnlogs; + __u32 nlognum; + char nlogname[4]; + __u32 nlogbytesize; + __u32 nlogprimarybuffsize; + __u32 tickspersecond; + __u32 corecount; +}; + +struct nlog_dump_header3_0 { + struct nlog_dump_header_common common; + __u32 nlogpausestatus; + __u32 selectoffsetref; + __u32 selectnlogpause; + __u32 selectaddedoffset; + __u32 nlogbufnum; + __u32 nlogbufnummax; +}; + +struct nlog_dump_header4_0 { + struct nlog_dump_header_common common; + __u64 nlogpausestatus; + __u32 selectoffsetref; + __u32 selectnlogpause; + __u32 selectaddedoffset; + __u32 nlogbufnum; + __u32 nlogbufnummax; + __u32 coreselected; + __u32 reserved[2]; +}; + +struct nlog_dump_header4_1 { + struct nlog_dump_header_common common; + __u64 nlogpausestatus; + __u32 selectoffsetref; + __u32 selectnlogpause; + __u32 selectaddedoffset; + __u32 nlogbufnum; + __u32 nlogbufnummax; + __u32 coreselected; + __u32 lpaPointer1High; + __u32 lpaPointer1Low; + __u32 lpaPointer2High; + __u32 lpaPointer2Low; +}; + +#pragma pack(pop, internal_logs) + +struct config { + __u32 namespace_id; + char *dir_prefix; + char *type; + bool verbose; +}; + +static void print_nlog_header(__u8 *buffer) +{ + struct nlog_dump_header_common *nlog_header = (struct nlog_dump_header_common *) buffer; + + if (nlog_header->ver.major >= 3) { + printf("Version Major %u\n", nlog_header->ver.major); + printf("Version Minor %u\n", nlog_header->ver.minor); + printf("Log_select %u\n", nlog_header->logselect); + printf("totalnlogs %u\n", nlog_header->totalnlogs); + printf("nlognum %u\n", nlog_header->nlognum); + printf("nlogname %c%c%c%c\n", nlog_header->nlogname[3], nlog_header->nlogname[2], + nlog_header->nlogname[1], nlog_header->nlogname[0]); + printf("nlogbytesize %u\n", nlog_header->nlogbytesize); + printf("nlogprimarybuffsize %u\n", nlog_header->nlogprimarybuffsize); + printf("tickspersecond %u\n", nlog_header->tickspersecond); + printf("corecount %u\n", nlog_header->corecount); + } + if (nlog_header->ver.major >= 4) { + struct nlog_dump_header4_0 *nlog_header = (struct nlog_dump_header4_0 *) buffer; + + printf("nlogpausestatus %"PRIu64"\n", (uint64_t)nlog_header->nlogpausestatus); + printf("selectoffsetref %u\n", nlog_header->selectoffsetref); + printf("selectnlogpause %u\n", nlog_header->selectnlogpause); + printf("selectaddedoffset %u\n", nlog_header->selectaddedoffset); + printf("nlogbufnum %u\n", nlog_header->nlogbufnum); + printf("nlogbufnummax %u\n", nlog_header->nlogbufnummax); + printf("coreselected %u\n\n", nlog_header->coreselected); + } +} + +#define INTERNAL_LOG_MAX_BYTE_TRANSFER 4096 +#define INTERNAL_LOG_MAX_DWORD_TRANSFER (INTERNAL_LOG_MAX_BYTE_TRANSFER / 4) + +static int cmd_dump_repeat(struct nvme_passthru_cmd *cmd, __u32 total_dw_size, + int out_fd, int ioctl_fd, bool force_max_transfer) +{ + int err = 0; + + while (total_dw_size > 0) { + size_t dword_tfer = min(INTERNAL_LOG_MAX_DWORD_TRANSFER, total_dw_size); + + cmd->cdw10 = force_max_transfer ? INTERNAL_LOG_MAX_DWORD_TRANSFER : dword_tfer; + cmd->data_len = dword_tfer * 4; + err = nvme_submit_admin_passthru(ioctl_fd, cmd, NULL); + if (err) + return err; + + if (out_fd > 0) { + err = write(out_fd, (const void *)(uintptr_t)cmd->addr, cmd->data_len); + if (err < 0) { + perror("write failure"); + return err; + } + err = 0; + } + total_dw_size -= dword_tfer; + cmd->cdw13 += dword_tfer; + } + 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, int ioctl_fd) +{ + memset((void *)(uintptr_t)cmd->addr, 0, INTERNAL_LOG_MAX_BYTE_TRANSFER); + return cmd_dump_repeat(cmd, INTERNAL_LOG_MAX_DWORD_TRANSFER, -1, ioctl_fd, false); +} + +static int get_serial_number(char *str, int fd) +{ + struct nvme_id_ctrl ctrl = {0}; + int err; + + err = nvme_identify_ctrl(fd, &ctrl); + if (err) + return err; + + /* Remove trailing spaces */ + for (int i = sizeof(ctrl.sn) - 1; i && ctrl.sn[i] == ' '; i--) + ctrl.sn[i] = '\0'; + sprintf(str, "%-.*s", (int)sizeof(ctrl.sn), ctrl.sn); + return err; +} + +static int dump_assert_logs(struct nvme_dev *dev, struct config cfg) +{ + __u8 buf[INTERNAL_LOG_MAX_BYTE_TRANSFER]; + __u8 head_buf[INTERNAL_LOG_MAX_BYTE_TRANSFER]; + char file_path[PATH_MAX]; + char file_name[] = "AssertLog.bin"; + struct assert_dump_header *ad = (struct assert_dump_header *) head_buf; + struct nvme_passthru_cmd cmd = { + .opcode = 0xd2, + .nsid = cfg.namespace_id, + .addr = (unsigned long)(void *)head_buf, + .cdw12 = ASSERTLOG, + .cdw13 = 0, + }; + int output, err; + + err = read_header(&cmd, dev_fd(dev)); + if (err) + return err; + + snprintf(file_path, sizeof(file_path), "%.*s/%s", + (int) (sizeof(file_path) - sizeof(file_name) - 1), cfg.dir_prefix, file_name); + output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (output < 0) + return -errno; + err = write_header((__u8 *)ad, output, ad->header.header_size * DWORD_SIZE); + if (err) { + perror("write failure"); + close(output); + return err; + } + cmd.addr = (unsigned long)(void *)buf; + + if (cfg.verbose) { + printf("Assert Log, cores: %d log size: %d header size: %d\n", ad->header.numcores, + ad->header.log_size * DWORD_SIZE, ad->header.header_size * DWORD_SIZE); + for (__u32 i = 0; i < ad->header.numcores; i++) + printf("core %d assert size: %d\n", i, ad->core[i].assertsize * DWORD_SIZE); + } + + for (__u32 i = 0; i < ad->header.numcores; i++) { + if (!ad->core[i].assertvalid) + continue; + cmd.cdw13 = ad->core[i].coreoffset; + err = cmd_dump_repeat(&cmd, ad->core[i].assertsize, + output, + dev_fd(dev), false); + if (err) { + close(output); + return err; + } + } + close(output); + printf("Successfully wrote log to %s\n", file_path); + return err; +} + +static int dump_event_logs(struct nvme_dev *dev, struct config cfg) +{ + __u8 buf[INTERNAL_LOG_MAX_BYTE_TRANSFER]; + __u8 head_buf[INTERNAL_LOG_MAX_BYTE_TRANSFER]; + char file_path[PATH_MAX]; + struct event_dump_header *ehdr = (struct event_dump_header *) head_buf; + struct nvme_passthru_cmd cmd = { + .opcode = 0xd2, + .nsid = cfg.namespace_id, + .addr = (unsigned long)(void *)head_buf, + .cdw12 = EVENTLOG, + .cdw13 = 0, + }; + int output; + int core_num, err; + + err = read_header(&cmd, dev_fd(dev)); + if (err) + return err; + snprintf(file_path, sizeof(file_path), "%s/EventLog.bin", cfg.dir_prefix); + output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (output < 0) + return -errno; + err = write_header(head_buf, output, INTERNAL_LOG_MAX_BYTE_TRANSFER); + + core_num = ehdr->header.numcores; + + if (err) { + close(output); + return err; + } + cmd.addr = (unsigned long)(void *)buf; + + if (cfg.verbose) + printf("Event Log, cores: %d log size: %d\n", core_num, ehdr->header.log_size * 4); + + for (__u32 j = 0; j < core_num; j++) { + if (cfg.verbose) { + for (int k = 0 ; k < 16; k++) { + printf("core: %d event: %d ", j, k); + printf("validity: %d ", ehdr->edumps[j].eventIdValidity[k]); + printf("offset: %d\n", ehdr->edumps[j].eventidoffset[k]); + } + } + cmd.cdw13 = ehdr->edumps[j].coreoffset; + err = cmd_dump_repeat(&cmd, ehdr->edumps[j].coresize, + output, dev_fd(dev), false); + if (err) { + close(output); + return err; + } + } + close(output); + printf("Successfully wrote log to %s\n", file_path); + return err; +} + +static size_t get_nlog_header_size(struct nlog_dump_header_common *nlog_header) +{ + switch (nlog_header->ver.major) { + case 3: + return sizeof(struct nlog_dump_header3_0); + case 4: + if (nlog_header->ver.minor == 0) + return sizeof(struct nlog_dump_header4_0); + return sizeof(struct nlog_dump_header4_1); + default: + return INTERNAL_LOG_MAX_BYTE_TRANSFER; + } + +} + +/* dumps nlogs from specified core or all cores when core = -1 */ +static int dump_nlogs(struct nvme_dev *dev, struct config cfg, int core) +{ + int err = 0; + __u32 count, core_num; + __u8 buf[INTERNAL_LOG_MAX_BYTE_TRANSFER]; + char file_path[PATH_MAX]; + struct nlog_dump_header_common *nlog_header = (struct nlog_dump_header_common *)buf; + struct nvme_passthru_cmd cmd = { + .opcode = 0xd2, + .nsid = cfg.namespace_id, + .addr = (unsigned long)(void *)buf + }; + + struct dump_select { + union { + struct { + __u32 selectLog : 3; + __u32 selectCore : 2; + __u32 selectNlog : 8; + }; + __u32 raw; + }; + } log_select; + int output; + bool is_open = false; + size_t header_size = 0; + + log_select.selectCore = core < 0 ? 0 : core; + do { + log_select.selectNlog = 0; + do { + cmd.cdw13 = 0; + cmd.cdw12 = log_select.raw; + err = read_header(&cmd, dev_fd(dev)); + if (err) { + if (is_open) + close(output); + return err; + } + count = nlog_header->totalnlogs; + core_num = core < 0 ? nlog_header->corecount : 0; + if (!header_size) { + snprintf(file_path, sizeof(file_path), "%s/NLog.bin", + cfg.dir_prefix); + output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (output < 0) + return -errno; + header_size = get_nlog_header_size(nlog_header); + is_open = true; + } + err = write_header(buf, output, header_size); + if (err) + break; + if (cfg.verbose) + print_nlog_header(buf); + cmd.cdw13 = 0x400; + err = cmd_dump_repeat(&cmd, nlog_header->nlogbytesize / 4, + output, dev_fd(dev), true); + if (err) + break; + } while (++log_select.selectNlog < count); + if (err) + break; + } while (++log_select.selectCore < core_num); + if (is_open) { + close(output); + printf("Successfully wrote log to %s\n", file_path); + } + return err; +} + +enum telemetry_type { + HOSTGENOLD, + HOSTGENNEW, + CONTROLLER +}; + +static int dump_telemetry(struct nvme_dev *dev, struct config cfg, enum telemetry_type ttype) +{ + _cleanup_free_ struct nvme_telemetry_log *log = NULL; + size_t log_size = 0; + int err = 0; + __u8 *buffer = NULL; + size_t bytes_remaining = 0; + enum nvme_telemetry_da da; + size_t max_data_tx; + char file_path[PATH_MAX]; + char *file_name; + char *log_descr; + struct stat sb; + + _cleanup_file_ int output = -1; + + switch (ttype) { + case HOSTGENNEW: + file_name = "lid_0x07_lsp_0x01_lsi_0x0000.bin"; + log_descr = "Generated Host Initiated"; + break; + case HOSTGENOLD: + file_name = "lid_0x07_lsp_0x00_lsi_0x0000.bin"; + log_descr = "Existing Host Initiated"; + break; + case CONTROLLER: + file_name = "lid_0x08_lsp_0x00_lsi_0x0000.bin"; + log_descr = "Controller Initiated"; + break; + default: + return -EINVAL; + } + err = nvme_get_telemetry_max(dev_fd(dev), &da, &max_data_tx); + if (err) + return err; + + if (max_data_tx > DRIVER_MAX_TX_256K) + max_data_tx = DRIVER_MAX_TX_256K; + + switch (ttype) { + case HOSTGENNEW: + err = nvme_get_telemetry_log(dev_fd(dev), true, false, false, max_data_tx, da, + &log, &log_size); + break; + case HOSTGENOLD: + err = nvme_get_telemetry_log(dev_fd(dev), false, false, false, max_data_tx, da, + &log, &log_size); + break; + case CONTROLLER: + err = nvme_get_telemetry_log(dev_fd(dev), false, true, true, max_data_tx, da, &log, + &log_size); + break; + } + + if (err) + return err; + + snprintf(file_path, sizeof(file_path), "%s/log_pages", cfg.dir_prefix); + if (!(stat(file_path, &sb) == 0 && S_ISDIR(sb.st_mode))) { + if (mkdir(file_path, 777) != 0) { + perror(file_path); + return -errno; + } + } + + snprintf(file_path, sizeof(file_path), "%s/log_pages/%s", cfg.dir_prefix, file_name); + output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (output < 0) + return -errno; + + bytes_remaining = log_size; + buffer = (__u8 *)log; + + while (bytes_remaining) { + ssize_t bytes_written = write(output, buffer, bytes_remaining); + + if (bytes_written < 0) { + err = -errno; + goto tele_close_output; + } + + bytes_remaining -= bytes_written; + buffer += bytes_written; + } + printf("Successfully wrote %s Telemetry log to %s\n", log_descr, file_path); + +tele_close_output: + close(output); + return err; +} + +int solidigm_get_internal_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char folder[PATH_MAX]; + char zip_name[PATH_MAX]; + char *output_path; + char sn_prefix[sizeof(((struct nvme_id_ctrl *)0)->sn)+1]; + int log_count = 0; + int err; + _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; + bool all = false; + time_t t; + struct tm tm; + + const char *desc = "Get Debug Firmware Logs and save them."; + const char *type = + "Log type: ALL, CONTROLLERINITTELEMETRY, HOSTINITTELEMETRY, HOSTINITTELEMETRYNOGEN, NLOG, ASSERT, EVENT. Defaults to ALL."; + const char *prefix = "Output dir prefix; defaults to device serial number."; + const char *verbose = "To print out verbose info."; + const char *namespace_id = "Namespace to get logs from."; + + + struct config cfg = { + .namespace_id = NVME_NSID_ALL, + .dir_prefix = NULL, + .type = NULL, + }; + + OPT_ARGS(opts) = { + OPT_STR("type", 't', &cfg.type, type), + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_FILE("dir-prefix", 'p', &cfg.dir_prefix, prefix), + OPT_FLAG("verbose", 'v', &cfg.verbose, verbose), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (!cfg.dir_prefix) { + err = get_serial_number(sn_prefix, dev_fd(dev)); + if (err) + return err; + cfg.dir_prefix = sn_prefix; + } + t = time(NULL); + tm = *localtime(&t); + snprintf(folder, sizeof(folder), "%s-%d%02d%02d%02d%02d%02d", cfg.dir_prefix, + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + if (mkdir(folder, 0777) != 0) { + perror("mkdir"); + return -errno; + } + cfg.dir_prefix = folder; + output_path = folder; + + if (!cfg.type) + cfg.type = "ALL"; + else { + for (char *p = cfg.type; *p; ++p) + *p = toupper(*p); + } + + if (!strcmp(cfg.type, "ALL")) { + all = true; + } + if (all || !strcmp(cfg.type, "ASSERT")) { + err = dump_assert_logs(dev, cfg); + if (err == 0) + log_count++; + else if (err < 0) + perror("Error retrieving Assert log"); + } + if (all || !strcmp(cfg.type, "EVENT")) { + err = dump_event_logs(dev, cfg); + if (err == 0) + log_count++; + else if (err < 0) + perror("Error retrieving Event log"); + } + if (all || !strcmp(cfg.type, "NLOG")) { + err = dump_nlogs(dev, cfg, -1); + if (err == 0) + log_count++; + else if (err < 0) + perror("Error retrieving Nlog"); + } + if (all || !strcmp(cfg.type, "CONTROLLERINITTELEMETRY")) { + err = dump_telemetry(dev, cfg, CONTROLLER); + if (err == 0) + log_count++; + else if (err < 0) + perror("Error retrieving Telemetry Controller Initiated"); + } + if (all || !strcmp(cfg.type, "HOSTINITTELEMETRYNOGEN")) { + err = dump_telemetry(dev, cfg, HOSTGENOLD); + if (err == 0) + log_count++; + else if (err < 0) + perror("Error retrieving previously existing Telemetry Host Initiated"); + } + if (all || !strcmp(cfg.type, "HOSTINITTELEMETRY")) { + err = dump_telemetry(dev, cfg, HOSTGENNEW); + if (err == 0) + log_count++; + else if (err < 0) + perror("Error retrieving Telemetry Host Initiated"); + } + + if (log_count > 0) { + int ret_cmd; + char cmd[ARG_MAX]; + char *where_err = cfg.verbose ? "" : ">/dev/null 2>&1"; + + snprintf(zip_name, sizeof(zip_name), "%s.zip", cfg.dir_prefix); + snprintf(cmd, sizeof(cmd), "cd \"%s\" && zip -r \"../%s\" ./* %s", cfg.dir_prefix, + zip_name, where_err); + printf("Compressing logs to %s\n", zip_name); + ret_cmd = system(cmd); + if (ret_cmd == -1) + perror(cmd); + else { + output_path = zip_name; + snprintf(cmd, sizeof(cmd), "rm -rf %s", cfg.dir_prefix); + printf("Removing %s\n", cfg.dir_prefix); + if (system(cmd) != 0) + perror("Failed removing logs folder"); + } + } + + if (log_count == 0) { + if (err > 0) + nvme_show_status(err); + } else if ((log_count > 1) || cfg.verbose) + printf("Total: %d log files in %s\n", log_count, output_path); + + return err; +} diff --git a/plugins/solidigm/solidigm-internal-logs.h b/plugins/solidigm/solidigm-internal-logs.h new file mode 100644 index 0000000..801af24 --- /dev/null +++ b/plugins/solidigm/solidigm-internal-logs.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +int solidigm_get_internal_log(int argc, char **argv, struct command *cmd, struct plugin *plugin); diff --git a/plugins/solidigm/solidigm-latency-tracking.c b/plugins/solidigm/solidigm-latency-tracking.c new file mode 100644 index 0000000..66f3c56 --- /dev/null +++ b/plugins/solidigm/solidigm-latency-tracking.c @@ -0,0 +1,474 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-print.h" +#include "solidigm-util.h" + +#define BUCKET_LIST_SIZE_4_0 152 +#define BUCKET_LIST_SIZE_4_1 1216 + +#define BASE_RANGE_BITS_4_0 3 +#define BASE_RANGE_BITS_4_1 6 + +struct latency_statistics { + __u16 version_major; + __u16 version_minor; + __u32 data[BUCKET_LIST_SIZE_4_1]; + __u64 average_latency; +}; + +struct config { + bool enable; + bool disable; + bool read; + bool write; + unsigned char type; + char *output_format; +}; + +struct latency_tracker { + int fd; + __u8 uuid_index; + struct config cfg; + enum nvme_print_flags print_flags; + struct latency_statistics stats; + struct json_object *bucket_list; + __u32 bucket_list_size; + __u8 base_range_bits; + bool has_average_latency_field; +}; + +/* COL_WIDTH controls width of columns in NORMAL output. */ +#define COL_WIDTH 12 +#define BUCKET_LABEL_MAX_SIZE 10 + +#define US_IN_S 1000000 +#define US_IN_MS 1000 + +/* + * Edge buckets may have range [#s, inf) in some + * latency statistics formats. + */ +static void get_time_unit_label(char *label, __u32 microseconds, + bool bonded) +{ + char *string = "us"; + int divisor = 1; + + if (!bonded) { + snprintf(label, BUCKET_LABEL_MAX_SIZE, "%s", "+INF"); + return; + } + + if (microseconds > US_IN_S) { + string = "s"; + divisor = US_IN_S; + } else if (microseconds > US_IN_MS) { + string = "ms"; + divisor = US_IN_MS; + } + + snprintf(label, BUCKET_LABEL_MAX_SIZE, "%4.2f%s", (float) microseconds / divisor, + string); +} + +static void latency_tracker_bucket_parse(const struct latency_tracker *lt, int id, + __u32 lower_us, __u32 upper_us, bool upper_bounded) +{ + char buffer[BUCKET_LABEL_MAX_SIZE] = ""; + __u32 bucket_data = le32_to_cpu(lt->stats.data[id]); + + if (lt->print_flags == NORMAL) { + printf("%-*d", COL_WIDTH, id); + + get_time_unit_label(buffer, lower_us, true); + printf("%-*s", COL_WIDTH, buffer); + + get_time_unit_label(buffer, upper_us, upper_bounded); + printf("%-*s", COL_WIDTH, buffer); + + printf("%-*d\n", COL_WIDTH, bucket_data); + } + + if (lt->print_flags == JSON) { + /* + * Creates a bucket under the "values" json_object. Format is: + * "values" : { + * "bucket" : { + * "id" : #, + * "start" : string, + * "end" : string, + * "value" : 0, + * }, + */ + struct json_object *bucket = json_create_object(); + + json_object_array_add(lt->bucket_list, bucket); + json_object_add_value_int(bucket, "id", id); + + get_time_unit_label(buffer, lower_us, true); + json_object_add_value_string(bucket, "start", buffer); + + get_time_unit_label(buffer, upper_us, upper_bounded); + json_object_add_value_string(bucket, "end", buffer); + + json_object_add_value_int(bucket, "value", bucket_data); + } +} + +static void latency_tracker_parse_linear(const struct latency_tracker *lt, + __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 && !lt->stats.data[i]) + continue; + latency_tracker_bucket_parse(lt, i, us_step * i, us_step * (i + 1), true); + } +} + +/* + * Calculates bucket time slot. Valid starting on 4.0 revision. + */ + +static int latency_tracker_bucket_pos2us(const struct latency_tracker *lt, int i) +{ + __u32 base_val = 1 << lt->base_range_bits; + + if (i < (base_val << 1)) + return i; + + int error_bits = (i >> lt->base_range_bits) - 1; + int base = 1 << (error_bits + lt->base_range_bits); + int k = i % 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 latency_tracker_populate_json_root(const struct latency_tracker *lt, + struct json_object *root) +{ + struct json_object *subroot = json_create_object(); + + json_object_add_value_object(root, "latstats", subroot); + json_object_add_value_string(subroot, "type", lt->cfg.write ? "write" : "read"); + if (lt->has_average_latency_field) + json_object_add_value_uint64(subroot, "average_latency", + le64_to_cpu(lt->stats.average_latency)); + json_object_add_value_object(subroot, "values", lt->bucket_list); +} + +static void latency_tracker_parse_3_0(const struct latency_tracker *lt) +{ + latency_tracker_parse_linear(lt, 4, 131, 4, 32, false); + latency_tracker_parse_linear(lt, 132, 255, 4, 1024, false); + latency_tracker_parse_linear(lt, 256, 379, 4, 32768, false); + latency_tracker_parse_linear(lt, 380, 383, 4, 32, true); + latency_tracker_parse_linear(lt, 384, 387, 4, 32, true); + latency_tracker_parse_linear(lt, 388, 391, 4, 32, true); +} + +static void latency_tracker_parse_4_0(const struct latency_tracker *lt) +{ + for (unsigned int i = 0; i < lt->bucket_list_size; i++) { + int lower_us = latency_tracker_bucket_pos2us(lt, i); + int upper_us = latency_tracker_bucket_pos2us(lt, i + 1); + + latency_tracker_bucket_parse(lt, i, lower_us, upper_us, + i < (lt->bucket_list_size - 1)); + } +} + +static void print_dash_separator(void) +{ + printf("--------------------------------------------------\n"); +} + +static void latency_tracker_pre_parse(struct latency_tracker *lt) +{ + if (lt->print_flags == NORMAL) { + printf("Solidigm IO %s Command Latency Tracking Statistics type %d\n", + lt->cfg.write ? "Write" : "Read", lt->cfg.type); + printf("UUID-idx: %d\n", lt->uuid_index); + printf("Major Revision: %u\nMinor Revision: %u\n", + le16_to_cpu(lt->stats.version_major), le16_to_cpu(lt->stats.version_minor)); + if (lt->has_average_latency_field) + printf("Average Latency: %" PRIu64 "\n", le64_to_cpu(lt->stats.average_latency)); + print_dash_separator(); + printf("%-12s%-12s%-12s%-20s\n", "Bucket", "Start", "End", "Value"); + print_dash_separator(); + } + if (lt->print_flags == JSON) + lt->bucket_list = json_object_new_array(); +} + +static void latency_tracker_post_parse(struct latency_tracker *lt) +{ + if (lt->print_flags == JSON) { + struct json_object *root = json_create_object(); + + latency_tracker_populate_json_root(lt, root); + json_print_object(root, NULL); + json_free_object(root); + printf("\n"); + } +} + +static void latency_tracker_parse(struct latency_tracker *lt) +{ + __u16 version_major = le16_to_cpu(lt->stats.version_major); + __u16 version_minor = le16_to_cpu(lt->stats.version_minor); + + switch (version_major) { + case 3: + latency_tracker_pre_parse(lt); + latency_tracker_parse_3_0(lt); + break; + case 4: + if (version_minor >= 8) + lt->has_average_latency_field = true; + latency_tracker_pre_parse(lt); + if (!version_minor) { + lt->base_range_bits = BASE_RANGE_BITS_4_0; + lt->bucket_list_size = BUCKET_LIST_SIZE_4_0; + } + latency_tracker_parse_4_0(lt); + break; + default: + printf("Unsupported revision (%u.%u)\n", + version_major, version_minor); + break; + } + + latency_tracker_post_parse(lt); +} + +#define LATENCY_TRACKING_FID 0xe2 +#define LATENCY_TRACKING_FID_DATA_LEN 32 + +static int latency_tracking_is_enable(struct latency_tracker *lt, __u32 *enabled) +{ + struct nvme_get_features_args args_get = { + .args_size = sizeof(args_get), + .fd = lt->fd, + .uuidx = lt->uuid_index, + .fid = LATENCY_TRACKING_FID, + .nsid = 0, + .sel = 0, + .cdw11 = 0, + .data_len = LATENCY_TRACKING_FID_DATA_LEN, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = enabled, + }; + return nvme_get_features(&args_get); +} + +static int latency_tracking_enable(struct latency_tracker *lt) +{ + __u32 result; + int err; + + if (!(lt->cfg.enable || lt->cfg.disable)) + return 0; + + if (lt->cfg.enable && lt->cfg.disable) { + fprintf(stderr, "Cannot enable and disable simultaneously.\n"); + return -EINVAL; + } + + struct nvme_set_features_args args_set = { + .args_size = sizeof(args_set), + .fd = lt->fd, + .uuidx = lt->uuid_index, + .fid = LATENCY_TRACKING_FID, + .nsid = 0, + .cdw11 = lt->cfg.enable, + .cdw12 = 0, + .save = 0, + .cdw15 = 0, + .data_len = LATENCY_TRACKING_FID_DATA_LEN, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + err = nvme_set_features(&args_set); + if (err > 0) { + nvme_show_status(err); + } else if (err < 0) { + perror("Enable latency tracking"); + fprintf(stderr, "Command failed while parsing.\n"); + } else { + if (lt->print_flags == NORMAL) { + printf("Successfully set enable bit for UUID-idx:%d FID:0x%X, to %i.\n", + lt->uuid_index, LATENCY_TRACKING_FID, lt->cfg.enable); + } + } + return err; +} + +#define READ_LOG_ID 0xc1 +#define WRITE_LOG_ID 0xc2 + +static int latency_tracker_get_log(struct latency_tracker *lt) +{ + int err; + + if (lt->cfg.read && lt->cfg.write) { + fprintf(stderr, "Cannot capture read and write logs simultaneously.\n"); + return -EINVAL; + } + + if (!(lt->cfg.read || lt->cfg.write)) + return 0; + + struct nvme_get_log_args args = { + .lpo = 0, + .result = NULL, + .log = <->stats, + .args_size = sizeof(args), + .fd = lt->fd, + .uuidx = lt->uuid_index, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .lid = lt->cfg.write ? WRITE_LOG_ID : READ_LOG_ID, + .len = sizeof(lt->stats), + .nsid = NVME_NSID_ALL, + .csi = NVME_CSI_NVM, + .lsi = NVME_LOG_LSI_NONE, + .lsp = lt->cfg.type, + .rae = false, + .ot = false, + }; + + err = nvme_get_log(&args); + if (err) + return err; + + if (lt->print_flags & BINARY) + d_raw((unsigned char *)<->stats, + sizeof(lt->stats)); + else { + latency_tracker_parse(lt); + } + return err; +} + +int solidigm_get_latency_tracking_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "Get and Parse Solidigm Latency Tracking Statistics log."; + struct nvme_dev *dev; + __u32 enabled; + int err; + + struct latency_tracker lt = { + .uuid_index = 0, + .cfg = { + .output_format = "normal", + }, + .base_range_bits = BASE_RANGE_BITS_4_1, + .bucket_list_size = BUCKET_LIST_SIZE_4_1, + .has_average_latency_field = false, + }; + + OPT_ARGS(opts) = { + OPT_FLAG("enable", 'e', <.cfg.enable, "Enable Latency Tracking"), + OPT_FLAG("disable", 'd', <.cfg.disable, "Disable Latency Tracking"), + OPT_FLAG("read", 'r', <.cfg.read, "Get read statistics"), + OPT_FLAG("write", 'w', <.cfg.write, "Get write statistics"), + OPT_BYTE("type", 't', <.cfg.type, "Log type to get"), + OPT_FMT("output-format", 'o', <.cfg.output_format, output_format), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + lt.fd = dev_fd(dev); + + err = validate_output_format(lt.cfg.output_format, <.print_flags); + if (err < 0) { + fprintf(stderr, "Invalid output format '%s'\n", lt.cfg.output_format); + dev_close(dev); + return -EINVAL; + } + + if (lt.cfg.type > 0xf) { + fprintf(stderr, "Invalid Log type value '%d'\n", lt.cfg.type); + dev_close(dev); + return -EINVAL; + } + + if (lt.cfg.type && !(lt.cfg.read || lt.cfg.write)) { + fprintf(stderr, "Log type option valid only when retrieving statistics\n"); + dev_close(dev); + return -EINVAL; + } + + lt.uuid_index = solidigm_get_vu_uuid_index(dev); + + err = latency_tracking_enable(<); + if (err) { + dev_close(dev); + return err; + } + + err = latency_tracker_get_log(<); + if (err) { + dev_close(dev); + return err; + } + + if ((lt.cfg.read || lt.cfg.write || lt.cfg.enable || lt.cfg.disable)) { + dev_close(dev); + return 0; + } + + err = latency_tracking_is_enable(<, &enabled); + if (!err) { + if (lt.print_flags == JSON) { + struct json_object *root = json_create_object(); + + json_object_add_value_int(root, "enabled", enabled); + json_print_object(root, NULL); + json_free_object(root); + printf("\n"); + } else if (lt.print_flags == BINARY) { + putchar(enabled); + } else { + printf("Latency Statistics Tracking (UUID-idx:%d, FID:0x%X) is currently %i.\n", + lt.uuid_index, LATENCY_TRACKING_FID, enabled); + } + } else { + fprintf(stderr, "Could not read feature id 0xE2.\n"); + } + /* Redundant close() to make static code analysis happy */ + close(dev->direct.fd); + dev_close(dev); + return err; +} diff --git a/plugins/solidigm/solidigm-latency-tracking.h b/plugins/solidigm/solidigm-latency-tracking.h new file mode 100644 index 0000000..9a763a9 --- /dev/null +++ b/plugins/solidigm/solidigm-latency-tracking.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +int solidigm_get_latency_tracking_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin); diff --git a/plugins/solidigm/solidigm-log-page-dir.c b/plugins/solidigm/solidigm-log-page-dir.c new file mode 100644 index 0000000..bf272f8 --- /dev/null +++ b/plugins/solidigm/solidigm-log-page-dir.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Solidigm. + * + * Author: karl.dedow@solidigm.com + */ + +#include "solidigm-log-page-dir.h" + +#include <errno.h> +#include <stdio.h> +#include <unistd.h> + +#include "common.h" +#include "nvme-print.h" + +#include "plugins/ocp/ocp-utils.h" + +#define MIN_VENDOR_LID 0xC0 +#define SOLIDIGM_MAX_UUID 2 + +static const char dash[100] = {[0 ... 99] = '-'}; + +struct lid_dir { + struct __packed { + bool supported; + const char *str; + } lid[NVME_LOG_SUPPORTED_LOG_PAGES_MAX]; +}; + +static void init_lid_dir(struct lid_dir *lid_dir) +{ + static const char *unknown_str = "Unknown"; + + for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) { + lid_dir->lid[lid].supported = false; + lid_dir->lid[lid].str = unknown_str; + } +} + +static bool is_invalid_uuid(const struct nvme_id_uuid_list_entry entry) +{ + static const unsigned char ALL_ZERO_UUID[NVME_UUID_LEN] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + return memcmp(ALL_ZERO_UUID, entry.uuid, NVME_UUID_LEN) == 0; +} + +static bool is_solidigm_uuid(const struct nvme_id_uuid_list_entry entry) +{ + static const unsigned char SOLIDIGM_UUID[NVME_UUID_LEN] = { + 0x96, 0x19, 0x58, 0x6e, 0xc1, 0x1b, 0x43, 0xad, + 0xaa, 0xaa, 0x65, 0x41, 0x87, 0xf6, 0xbb, 0xb2 + }; + + return memcmp(SOLIDIGM_UUID, entry.uuid, NVME_UUID_LEN) == 0; +} + +static bool is_ocp_uuid(const struct nvme_id_uuid_list_entry entry) +{ + static const unsigned char OCP_UUID[NVME_UUID_LEN] = { + 0xc1, 0x94, 0xd5, 0x5b, 0xe0, 0x94, 0x47, 0x94, + 0xa2, 0x1d, 0x29, 0x99, 0x8f, 0x56, 0xbe, 0x6f + }; + + return memcmp(OCP_UUID, entry.uuid, NVME_UUID_LEN) == 0; +} + +static int get_supported_log_pages_log(struct nvme_dev *dev, int uuid_index, + struct nvme_supported_log_pages *supported) +{ + static const __u8 LID; + + memset(supported, 0, sizeof(*supported)); + struct nvme_get_log_args args = { + .lpo = 0, + .result = NULL, + .log = supported, + .args_size = sizeof(args), + .fd = dev_fd(dev), + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .lid = LID, + .len = sizeof(*supported), + .nsid = NVME_NSID_ALL, + .csi = NVME_CSI_NVM, + .lsi = NVME_LOG_LSI_NONE, + .lsp = 0, + .uuidx = uuid_index, + .rae = false, + .ot = false, + }; + + return nvme_get_log(&args); +} + +static struct lid_dir *get_standard_lids(struct nvme_supported_log_pages *supported) +{ + static struct lid_dir standard_dir = { 0 }; + + init_lid_dir(&standard_dir); + + for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) { + if (!supported->lid_support[lid] || lid >= MIN_VENDOR_LID) + continue; + + standard_dir.lid[lid].supported = true; + standard_dir.lid[lid].str = nvme_log_to_string(lid); + } + + return &standard_dir; +} + +static void update_vendor_lid_supported(struct nvme_supported_log_pages *supported, + struct lid_dir *lid_dir) +{ + for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) { + if (!supported->lid_support[lid] || lid < MIN_VENDOR_LID) + continue; + + lid_dir->lid[lid].supported = true; + } +} + +static struct lid_dir *get_solidigm_lids(struct nvme_supported_log_pages *supported) +{ + static struct lid_dir solidigm_dir = { 0 }; + + init_lid_dir(&solidigm_dir); + solidigm_dir.lid[0xC1].str = "Read Commands Latency Statistics"; + solidigm_dir.lid[0xC2].str = "Write Commands Latency Statistics"; + solidigm_dir.lid[0xC4].str = "Endurance Manager Statistics"; + solidigm_dir.lid[0xC5].str = "Temperature Statistics"; + solidigm_dir.lid[0xCA].str = "SMART Attributes"; + solidigm_dir.lid[0xCB].str = "VU NVMe IO Queue Metrics Log Page"; + solidigm_dir.lid[0xDD].str = "VU Marketing Description Log Page"; + solidigm_dir.lid[0xEF].str = "Performance Rating and LBA Access Histogram"; + solidigm_dir.lid[0xF2].str = "Get Power Usage Log Page"; + solidigm_dir.lid[0xF6].str = "Vt Histo Get Log Page"; + solidigm_dir.lid[0xF9].str = "Workload Tracker Get Log Page"; + solidigm_dir.lid[0xFD].str = "Garbage Control Collection Log Page"; + solidigm_dir.lid[0xFE].str = "Latency Outlier Log Page"; + + update_vendor_lid_supported(supported, &solidigm_dir); + + return &solidigm_dir; +} + +static struct lid_dir *get_ocp_lids(struct nvme_supported_log_pages *supported) +{ + static struct lid_dir ocp_dir = { 0 }; + + init_lid_dir(&ocp_dir); + ocp_dir.lid[0xC0].str = "OCP SMART / Health Information Extended"; + ocp_dir.lid[0xC1].str = "OCP Error Recovery"; + ocp_dir.lid[0xC2].str = "OCP Firmware Activation History"; + ocp_dir.lid[0xC3].str = "OCP Latency Monitor"; + ocp_dir.lid[0xC4].str = "OCP Device Capabilities"; + ocp_dir.lid[0xC5].str = "OCP Unsupported Requirements"; + + update_vendor_lid_supported(supported, &ocp_dir); + + return &ocp_dir; +} + +static void supported_log_pages_normal(struct lid_dir *lid_dir[SOLIDIGM_MAX_UUID + 1]) +{ + printf("%-5s %-4s %-42s\n", "uuidx", "LID", "Description"); + printf("%-.5s %-.4s %-.42s\n", dash, dash, dash); + + for (int uuid_index = 0; uuid_index <= SOLIDIGM_MAX_UUID; uuid_index++) { + if (!lid_dir[uuid_index]) + continue; + + for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) { + if (!lid_dir[uuid_index]->lid[lid].supported) + continue; + + printf("%-5d 0x%02x %s\n", le32_to_cpu(uuid_index), le32_to_cpu(lid), + lid_dir[uuid_index]->lid[lid].str); + } + } +} + +static void supported_log_pages_json(struct lid_dir *lid_dir[SOLIDIGM_MAX_UUID + 1]) +{ + struct json_object *root = json_create_array(); + + for (int uuid_index = 0; uuid_index <= SOLIDIGM_MAX_UUID; uuid_index++) { + if (!lid_dir[uuid_index]) + continue; + + for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) { + if (!lid_dir[uuid_index]->lid[lid].supported) + continue; + + struct json_object *lid_obj = json_create_object(); + + json_object_add_value_uint(lid_obj, "uuidx", le32_to_cpu(uuid_index)); + json_object_add_value_uint(lid_obj, "lid", le32_to_cpu(lid)); + json_object_add_value_string(lid_obj, "description", + lid_dir[uuid_index]->lid[lid].str); + json_array_add_value_object(root, lid_obj); + } + } + + json_print_object(root, NULL); + json_free_object(root); + printf("\n"); +} + +int solidigm_get_log_page_directory_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const int NO_UUID_INDEX = 0; + const char *description = "Retrieves list of supported log pages for each UUID index."; + char *format = "normal"; + + OPT_ARGS(options) = { + OPT_FMT("output-format", 'o', &format, "output format : normal | json"), + OPT_END() + }; + + struct nvme_dev *dev = NULL; + int err = parse_and_open(&dev, argc, argv, description, options); + + if (err) + return err; + + struct lid_dir *lid_dirs[SOLIDIGM_MAX_UUID + 1] = { 0 }; + struct nvme_id_uuid_list uuid_list = { 0 }; + struct nvme_supported_log_pages supported = { 0 }; + + err = get_supported_log_pages_log(dev, NO_UUID_INDEX, &supported); + + if (!err) { + lid_dirs[NO_UUID_INDEX] = get_standard_lids(&supported); + + // Assume VU logs are the Solidigm log pages if UUID not supported. + if (nvme_identify_uuid(dev_fd(dev), &uuid_list)) { + struct lid_dir *solidigm_lid_dir = get_solidigm_lids(&supported); + + // Transfer supported Solidigm lids to lid directory at UUID index 0 + for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) { + if (solidigm_lid_dir->lid[lid].supported) + lid_dirs[NO_UUID_INDEX]->lid[lid] = solidigm_lid_dir->lid[lid]; + } + } else { + for (int uuid_index = 1; uuid_index <= SOLIDIGM_MAX_UUID; uuid_index++) { + if (is_invalid_uuid(uuid_list.entry[uuid_index - 1])) + break; + else if (get_supported_log_pages_log(dev, uuid_index, &supported)) + continue; + + if (is_solidigm_uuid(uuid_list.entry[uuid_index - 1])) + lid_dirs[uuid_index] = get_solidigm_lids(&supported); + else if (is_ocp_uuid(uuid_list.entry[uuid_index - 1])) + lid_dirs[uuid_index] = get_ocp_lids(&supported); + } + } + } else { + nvme_show_status(err); + } + + if (!err) { + enum nvme_print_flags print_flag; + + err = validate_output_format(format, &print_flag); + if (err < 0) { + fprintf(stderr, "Error: Invalid output format specified: %s.\n", format); + return err; + } + + if (print_flag == NORMAL) { + supported_log_pages_normal(lid_dirs); + } else if (print_flag == JSON) { + supported_log_pages_json(lid_dirs); + } + } + + /* Redundant close() to make static code analysis happy */ + close(dev->direct.fd); + dev_close(dev); + return err; +} diff --git a/plugins/solidigm/solidigm-log-page-dir.h b/plugins/solidigm/solidigm-log-page-dir.h new file mode 100644 index 0000000..48777df --- /dev/null +++ b/plugins/solidigm/solidigm-log-page-dir.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Solidigm. + * + * Authors: karl.dedow@solidigm.com + */ + +#ifndef SOLIDIGM_LOG_PAGE_DIRECTORY_H +#define SOLIDIGM_LOG_PAGE_DIRECTORY_H + +struct command; +struct plugin; + +int solidigm_get_log_page_directory_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin); + +#endif diff --git a/plugins/solidigm/solidigm-market-log.c b/plugins/solidigm/solidigm-market-log.c new file mode 100644 index 0000000..d7d38da --- /dev/null +++ b/plugins/solidigm/solidigm-market-log.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Solidigm. + * + * Authors: leonardo.da.cunha@solidigm.com + * Hardeep.Dhillon@solidigm.com + */ + +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> +#include <linux/limits.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "nvme-print.h" + +#define MARKET_LOG_MAX_SIZE 512 + +int sldgm_get_market_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Get Solidigm Marketing Name log and show it."; + const char *raw = "dump output in binary format"; + struct nvme_dev *dev; + char log[MARKET_LOG_MAX_SIZE]; + int err; + + struct config { + bool raw_binary; + }; + + struct config cfg = { + }; + + OPT_ARGS(opts) = { + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = nvme_get_log_simple(dev_fd(dev), 0xdd, sizeof(log), log); + if (!err) { + if (!cfg.raw_binary) + printf("Solidigm Marketing Name Log:\n%s\n", log); + else + d_raw((unsigned char *)&log, sizeof(log)); + } else if (err > 0) + + nvme_show_status(err); + /* Redundant close() to make static code analysis happy */ + close(dev->direct.fd); + dev_close(dev); + return err; +} diff --git a/plugins/solidigm/solidigm-market-log.h b/plugins/solidigm/solidigm-market-log.h new file mode 100644 index 0000000..6f808c4 --- /dev/null +++ b/plugins/solidigm/solidigm-market-log.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Solidigm. + * + * Author: hardeep.dhillon@solidigm.com + */ + +int sldgm_get_market_log(int argc, char **argv, struct command *cmd, struct plugin *plugin); diff --git a/plugins/solidigm/solidigm-nvme.c b/plugins/solidigm/solidigm-nvme.c new file mode 100644 index 0000000..3fb86f5 --- /dev/null +++ b/plugins/solidigm/solidigm-nvme.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022-2023 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include "nvme.h" + +#define CREATE_CMD +#include "solidigm-nvme.h" + +#include "solidigm-id-ctrl.h" +#include "solidigm-smart.h" +#include "solidigm-internal-logs.h" +#include "solidigm-garbage-collection.h" +#include "solidigm-latency-tracking.h" +#include "solidigm-telemetry.h" +#include "solidigm-log-page-dir.h" +#include "solidigm-market-log.h" +#include "solidigm-temp-stats.h" +#include "solidigm-get-drive-info.h" +#include "solidigm-ocp-version.h" + +#include "plugins/ocp/ocp-clear-features.h" +#include "plugins/ocp/ocp-smart-extended-log.h" +#include "plugins/ocp/ocp-fw-activation-history.h" + +static int id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return __id_ctrl(argc, argv, cmd, plugin, sldgm_id_ctrl); +} + +static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return solidigm_get_additional_smart_log(argc, argv, cmd, plugin); +} + +static int get_internal_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return solidigm_get_internal_log(argc, argv, cmd, plugin); +} + +static int get_garbage_collection_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return solidigm_get_garbage_collection_log(argc, argv, cmd, plugin); +} + +static int get_latency_tracking_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return solidigm_get_latency_tracking_log(argc, argv, cmd, plugin); +} + +static int get_telemetry_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return solidigm_get_telemetry_log(argc, argv, cmd, plugin); +} + +static int clear_fw_update_history(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + return ocp_clear_fw_update_history(argc, argv, cmd, plugin); +} + +static int clear_pcie_correctable_error_counters(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + return ocp_clear_pcie_correctable_errors(argc, argv, cmd, plugin); +} + +static int smart_cloud(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + return ocp_smart_add_log(argc, argv, cmd, plugin); +} + +static int fw_activation_history(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + return ocp_fw_activation_history_log(argc, argv, cmd, plugin); +} + +static int get_log_page_directory_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + return solidigm_get_log_page_directory_log(argc, argv, cmd, plugin); +} + +static int get_market_log(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + return sldgm_get_market_log(argc, argv, cmd, plugin); +} + +static int get_temp_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return sldgm_get_temp_stats_log(argc, argv, cmd, plugin); +} + +static int get_drive_info(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + return sldgm_get_drive_info(argc, argv, cmd, plugin); +} + +static int get_cloud_SSDplugin_version(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + return sldgm_ocp_version(argc, argv, cmd, plugin); +} diff --git a/plugins/solidigm/solidigm-nvme.h b/plugins/solidigm/solidigm-nvme.h new file mode 100644 index 0000000..bee8266 --- /dev/null +++ b/plugins/solidigm/solidigm-nvme.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022-2023 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/solidigm/solidigm-nvme + +#if !defined(SOLIDIGM_NVME) || defined(CMD_HEADER_MULTI_READ) +#define SOLIDIGM_NVME + +#include "cmd.h" + +#define SOLIDIGM_PLUGIN_VERSION "1.1" + +PLUGIN(NAME("solidigm", "Solidigm vendor specific extensions", SOLIDIGM_PLUGIN_VERSION), + COMMAND_LIST( + ENTRY("id-ctrl", "Send NVMe Identify Controller", id_ctrl) + ENTRY("smart-log-add", "Retrieve Solidigm SMART Log", get_additional_smart_log) + ENTRY("vs-smart-add-log", "Get SMART / health extended log (redirects to ocp plug-in)", smart_cloud) + ENTRY("vs-internal-log", "Retrieve Debug log binaries", get_internal_log) + ENTRY("garbage-collect-log", "Retrieve Garbage Collection Log", get_garbage_collection_log) + ENTRY("market-log", "Retrieve Market Log", get_market_log) + ENTRY("latency-tracking-log", "Enable/Retrieve Latency tracking Log", get_latency_tracking_log) + ENTRY("parse-telemetry-log", "Parse Telemetry Log binary", get_telemetry_log) + ENTRY("clear-pcie-correctable-errors ", "Clear PCIe Correctable Error Counters (redirects to ocp plug-in)", clear_pcie_correctable_error_counters) + ENTRY("clear-fw-activate-history", "Clear firmware update history log (redirects to ocp plug-in)", clear_fw_update_history) + ENTRY("vs-fw-activate-history", "Get firmware activation history log (redirects to ocp plug-in)", fw_activation_history) + ENTRY("log-page-directory", "Retrieve log page directory", get_log_page_directory_log) + ENTRY("temp-stats", "Retrieve Temperature Statistics log", get_temp_stats_log) + ENTRY("vs-drive-info", "Retrieve drive information", get_drive_info) + ENTRY("cloud-SSDplugin-version", "Prints plug-in OCP version", get_cloud_SSDplugin_version) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/solidigm/solidigm-ocp-version.c b/plugins/solidigm/solidigm-ocp-version.c new file mode 100644 index 0000000..4048cc1 --- /dev/null +++ b/plugins/solidigm/solidigm-ocp-version.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include <stdio.h> +#include "nvme.h" + +int sldgm_ocp_version(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Prints OCP extensions version of Solidigm plugin"; + + OPT_ARGS(opts) = { + OPT_END() + }; + + int err = argconfig_parse(argc, argv, desc, opts); + + if (!err) + printf("1.0\n"); + + return err; +} diff --git a/plugins/solidigm/solidigm-ocp-version.h b/plugins/solidigm/solidigm-ocp-version.h new file mode 100644 index 0000000..d79452c --- /dev/null +++ b/plugins/solidigm/solidigm-ocp-version.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +int sldgm_ocp_version(int argc, char **argv, struct command *cmd, struct plugin *plugin); diff --git a/plugins/solidigm/solidigm-smart.c b/plugins/solidigm/solidigm-smart.c new file mode 100644 index 0000000..62245fa --- /dev/null +++ b/plugins/solidigm/solidigm-smart.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-print.h" + +#include "solidigm-smart.h" +#include "solidigm-util.h" + +struct __packed nvme_additional_smart_log_item { + __u8 id; + __u8 _kp[2]; + __u8 normalized; + __u8 _np; + union __packed { + __u8 raw[6]; + struct __packed wear_level { + __le16 min; + __le16 max; + __le16 avg; + } wear_level; + struct __packed thermal_throttle { + __u8 pct; + __u32 count; + } thermal_throttle; + }; + __u8 _rp; +}; + +#define VU_SMART_PAGE_SIZE 512 +#define VU_SMART_MAX_ITEMS (VU_SMART_PAGE_SIZE / sizeof(struct nvme_additional_smart_log_item)) +struct vu_smart_log { + struct nvme_additional_smart_log_item item[VU_SMART_MAX_ITEMS]; +}; + +static char *id_to_name(__u8 id) +{ + switch (id) { + case 0x0D: + return "soft_ecc_error_rate"; + case 0x05: + return "relocatable_sector_count"; + case 0xAB: + return "program_fail_count"; + case 0xAC: + return "erase_fail_count"; + case 0xAD: + return "wear_leveling_count"; + case 0xAE: + return "unexpected_power_loss"; + case 0xB8: + return "e2e_error_detect_count"; + case 0xC7: + return "crc_error_count"; + case 0xE2: + return "media_wear_percentage"; + case 0xE3: + return "timed_work_load_host_reads"; + case 0xE4: + return "timed_work_load_timer"; + case 0xE5: + return "read_commands_in_flight_counter"; + case 0xE6: + return "write_commands_in_flight_counter"; + case 0xEA: + return "thermal_throttle_status"; + case 0xEE: + return "re_sku_count"; + case 0xF0: + return "retry_buffer_overflow_counter"; + case 0xF3: + return "pll_lock_loss_counter"; + case 0xF4: + return "nand_bytes_written"; + case 0xF5: + return "host_bytes_written"; + case 0xF6: + return "host_context_wear_used"; + case 0xF7: + return "performance_status_indicator"; + case 0xF8: + return "media_bytes_read"; + case 0xF9: + return "available_fw_downgrades"; + case 0xFA: + return "host_read_collision_count"; + case 0xFB: + return "host_write_collision_count"; + case 0xFC: + return "xor_pass_count"; + case 0xFD: + return "xor_fail_count"; + case 0xFE: + return "xor_invoked_count"; + default: + return "unknown"; + } +} + +static void smart_log_item_print(struct nvme_additional_smart_log_item *item) +{ + if (!item->id) + return; + + printf("%#x %-45s %3d ", + item->id, id_to_name(item->id), item->normalized); + + switch (item->id) { + case 0xAD: + printf("min: %u, max: %u, avg: %u\n", + le16_to_cpu(item->wear_level.min), + le16_to_cpu(item->wear_level.max), + le16_to_cpu(item->wear_level.avg)); + return; + case 0xEA: + printf("%u%%, cnt: %u\n", + item->thermal_throttle.pct, + le32_to_cpu(item->thermal_throttle.count)); + return; + default: + printf("%"PRIu64"\n", int48_to_long(item->raw)); + } +} + +static void smart_log_item_add_json(struct nvme_additional_smart_log_item *item, struct json_object *dev_stats) +{ + struct json_object *entry_stats = json_create_object(); + + if (!item->id) + return; + + json_object_add_value_int(entry_stats, "normalized", item->normalized); + + switch (item->id) { + case 0xAD: + json_object_add_value_int(entry_stats, "min", le16_to_cpu(item->wear_level.min)); + json_object_add_value_int(entry_stats, "max", le16_to_cpu(item->wear_level.max)); + json_object_add_value_int(entry_stats, "avg", le16_to_cpu(item->wear_level.avg)); + break; + case 0xEA: + json_object_add_value_int(entry_stats, "percentage", item->thermal_throttle.pct); + json_object_add_value_int(entry_stats, "count", le32_to_cpu(item->thermal_throttle.count)); + break; + default: + json_object_add_value_int(entry_stats, "raw", int48_to_long(item->raw)); + } + json_object_add_value_object(dev_stats, id_to_name(item->id), entry_stats); +} + +static void vu_smart_log_show_json(struct vu_smart_log *payload, unsigned int nsid, const char *devname) +{ + struct json_object *dev_stats = json_create_object(); + struct nvme_additional_smart_log_item *item = payload->item; + struct json_object *root; + + for (int i = 0; i < VU_SMART_MAX_ITEMS; i++) + smart_log_item_add_json(&item[i], dev_stats); + + root = json_create_object(); + json_object_add_value_string(root, "Solidigm SMART log", devname); + json_object_add_value_object(root, "Device stats", dev_stats); + + json_print_object(root, NULL); + json_free_object(root); +} + +static void vu_smart_log_show(struct vu_smart_log *payload, unsigned int nsid, const char *devname, + __u8 uuid_index) +{ + struct nvme_additional_smart_log_item *item = payload->item; + + printf("Additional Smart Log for NVMe device:%s namespace-id:%x UUID-idx:%d\n", + devname, nsid, uuid_index); + printf("ID KEY Normalized Raw\n"); + + for (int i = 0; i < VU_SMART_MAX_ITEMS; i++) + smart_log_item_print(&item[i]); +} + +int solidigm_get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = + "Get Solidigm vendor specific smart log (optionally, for the specified namespace), and show it."; + const int solidigm_vu_smart_log_id = 0xCA; + struct vu_smart_log smart_log_payload; + enum nvme_print_flags flags; + struct nvme_dev *dev; + int err; + __u8 uuid_index; + + struct config { + __u32 namespace_id; + char *output_format; + }; + + struct config cfg = { + .namespace_id = NVME_NSID_ALL, + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, "(optional) desired namespace"), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = validate_output_format(cfg.output_format, &flags); + if (err < 0) { + fprintf(stderr, "Invalid output format '%s'\n", cfg.output_format); + dev_close(dev); + return err; + } + + uuid_index = solidigm_get_vu_uuid_index(dev); + + struct nvme_get_log_args args = { + .lpo = 0, + .result = NULL, + .log = &smart_log_payload, + .args_size = sizeof(args), + .fd = dev_fd(dev), + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .lid = solidigm_vu_smart_log_id, + .len = sizeof(smart_log_payload), + .nsid = NVME_NSID_ALL, + .csi = NVME_CSI_NVM, + .lsi = NVME_LOG_LSI_NONE, + .lsp = NVME_LOG_LSP_NONE, + .uuidx = uuid_index, + .rae = false, + .ot = false, + }; + + err = nvme_get_log(&args); + if (!err) { + if (flags & JSON) + vu_smart_log_show_json(&smart_log_payload, + cfg.namespace_id, dev->name); + else if (flags & BINARY) + d_raw((unsigned char *)&smart_log_payload, sizeof(smart_log_payload)); + else + vu_smart_log_show(&smart_log_payload, cfg.namespace_id, + dev->name, uuid_index); + } else if (err > 0) { + nvme_show_status(err); + } + + /* Redundant close() to make static code analysis happy */ + close(dev->direct.fd); + dev_close(dev); + return err; +} + diff --git a/plugins/solidigm/solidigm-smart.h b/plugins/solidigm/solidigm-smart.h new file mode 100644 index 0000000..e19ebe5 --- /dev/null +++ b/plugins/solidigm/solidigm-smart.h @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +int solidigm_get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin); diff --git a/plugins/solidigm/solidigm-telemetry.c b/plugins/solidigm/solidigm-telemetry.c new file mode 100644 index 0000000..2bebccc --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "nvme-print.h" +#include "solidigm-telemetry.h" +#include "solidigm-telemetry/telemetry-log.h" +#include "solidigm-telemetry/cod.h" +#include "solidigm-telemetry/header.h" +#include "solidigm-telemetry/config.h" +#include "solidigm-telemetry/data-area.h" +#include "solidigm-util.h" + +static int read_file2buffer(char *file_name, char **buffer, size_t *length) +{ + FILE *fd = fopen(file_name, "rb"); + + if (!fd) + return errno; + + fseek(fd, 0, SEEK_END); + size_t length_bytes = ftell(fd); + + fseek(fd, 0, SEEK_SET); + + *buffer = malloc(length_bytes); + if (!*buffer) { + fclose(fd); + return errno; + } + *length = fread(*buffer, 1, length_bytes, fd); + fclose(fd); + return 0; +} + +struct config { + __u32 host_gen; + bool ctrl_init; + int data_area; + char *cfg_file; + bool is_input_file; +}; + +int solidigm_get_telemetry_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Parse Solidigm Telemetry log"; + const char *hgen = "Controls when to generate new host initiated report. Default value '1' generates new host initiated report, value '0' causes retrieval of existing log."; + const char *cgen = "Gather report generated by the controller."; + const char *dgen = "Pick which telemetry data area to report. Default is 3 to fetch areas 1-3. Valid options are 1, 2, 3, 4."; + const char *cfile = "JSON configuration file"; + const char *sfile = "data source <device> is binary file containing log dump instead of block or character device"; + struct nvme_dev *dev; + + struct telemetry_log tl = { + .root = json_create_object(), + .log = NULL, + }; + + struct config cfg = { + .host_gen = 1, + .ctrl_init = false, + .data_area = -1, + .cfg_file = NULL, + .is_input_file = false, + }; + + OPT_ARGS(opts) = { + OPT_UINT("host-generate", 'g', &cfg.host_gen, hgen), + OPT_FLAG("controller-init", 'c', &cfg.ctrl_init, cgen), + OPT_UINT("data-area", 'd', &cfg.data_area, dgen), + OPT_FILE("config-file", 'j', &cfg.cfg_file, cfile), + OPT_FLAG("source-file", 's', &cfg.is_input_file, sfile), + OPT_END() + }; + + int err = argconfig_parse(argc, argv, desc, opts); + + if (err) + goto ret; + + /* When not selected on the command line, get minimum data area required */ + if (cfg.data_area == -1) + cfg.data_area = cfg.cfg_file ? 3 : 1; + + if (cfg.is_input_file) { + if (optind >= argc) { + err = errno = EINVAL; + perror(argv[0]); + goto ret; + } + char *binary_file_name = argv[optind]; + + err = read_file2buffer(binary_file_name, (char **)&tl.log, &tl.log_size); + } else { + err = parse_and_open(&dev, argc, argv, desc, opts); + } + if (err) + goto ret; + + if (cfg.host_gen > 1) { + SOLIDIGM_LOG_WARNING("Invalid host-generate value '%d'", cfg.host_gen); + err = EINVAL; + goto close_fd; + } + + if (cfg.cfg_file) { + char *conf_str = NULL; + size_t length = 0; + + err = read_file2buffer(cfg.cfg_file, &conf_str, &length); + if (err) { + SOLIDIGM_LOG_WARNING("Failed to open JSON configuration file %s: %s!", + cfg.cfg_file, strerror(err)); + goto close_fd; + } + struct json_tokener *jstok = json_tokener_new(); + + tl.configuration = json_tokener_parse_ex(jstok, conf_str, length); + free(conf_str); + if (jstok->err != json_tokener_success) { + SOLIDIGM_LOG_WARNING("Parsing error on JSON configuration file %s: %s (at offset %d)", + cfg.cfg_file, + json_tokener_error_desc(jstok->err), + jstok->char_offset); + json_tokener_free(jstok); + err = EINVAL; + goto close_fd; + } + json_tokener_free(jstok); + } + + if (!cfg.is_input_file) { + size_t max_data_tx; + + err = nvme_get_telemetry_max(dev_fd(dev), NULL, &max_data_tx); + if (err < 0) { + SOLIDIGM_LOG_WARNING("identify_ctrl: %s", + nvme_strerror(errno)); + goto close_fd; + } else if (err > 0) { + nvme_show_status(err); + SOLIDIGM_LOG_WARNING("Failed to acquire identify ctrl %d!", err); + goto close_fd; + } + if (max_data_tx > DRIVER_MAX_TX_256K) + max_data_tx = DRIVER_MAX_TX_256K; + + err = nvme_get_telemetry_log(dev_fd(dev), cfg.host_gen, cfg.ctrl_init, true, + max_data_tx, cfg.data_area, &tl.log, &tl.log_size); + if (err < 0) { + SOLIDIGM_LOG_WARNING("get-telemetry-log: %s", + nvme_strerror(errno)); + goto close_fd; + } else if (err > 0) { + nvme_show_status(err); + SOLIDIGM_LOG_WARNING("Failed to acquire telemetry log %d!", err); + goto close_fd; + } + } + solidigm_telemetry_log_data_areas_parse(&tl, cfg.data_area); + + json_print_object(tl.root, NULL); + json_free_object(tl.root); + printf("\n"); + +close_fd: + if (!cfg.is_input_file) { + /* Redundant close() to make static code analysis happy */ + close(dev->direct.fd); + dev_close(dev); + } +ret: + json_free_object(tl.configuration); + free(tl.log); + return err; +} diff --git a/plugins/solidigm/solidigm-telemetry.h b/plugins/solidigm/solidigm-telemetry.h new file mode 100644 index 0000000..971ee2a --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry.h @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +int solidigm_get_telemetry_log(int argc, char **argv, struct command *cmd, struct plugin *plugin); diff --git a/plugins/solidigm/solidigm-telemetry/cod.c b/plugins/solidigm/solidigm-telemetry/cod.c new file mode 100644 index 0000000..363822a --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/cod.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ +#include "common.h" +#include "cod.h" + +const char *oemDataMapDesc[] = { + "Media Read Count", //Uid 0x00 + "Host Read count", //Uid 0x01 + "Media Write Count", //Uid 0x02 + "Host Write Count", //Uid 0x03 + "Device Model", // 0x04 + "Serial Number", // 0x05 + "Firmware Revision", // 0x06 + "Drive Status", // 0x07 + "Minimum Temperature", // 0x08 + "Maximum Temperature", // 0x09 + "Power Loss Protection Status", // 0x0a + "Lifetime Unsafe Shutdown Count", // 0x0b + "Lifetime Power Cycle Count", // 0x0c + "Minimum Read Latency", // 0x0d + "Maximum Read Latency", // 0x0e + "Average Read Latency", // 0x0f + "Minimum Write Latency", // 0x10 + "Maximum Write Latency", // 0x11 + "Average Write Latency", // 0x12 + "Grown Defects Count", // 0x13 + "DQS Recovery Count", // 0x14 + "Program Fail Count", // 0x15 + "Erase Fail Count", // 0x16 + "Defrag Writes in Progress Count", // 0x17 + "Total Defrag Writes Count", // 0x18 + "Max Die Offline Number", // 0x19 + "Current Die Offline Number", // 0x1A + "XOR Enable Status", // 0x1B + "Media Life Used", // 0x1C + "Uncorrectable Error Count", // 0x1D + "Current Wear Range Delta", // 0x1E + "Read Errors Corrected by XOR", // 0x1F + "Background Data Refresh", // 0x20 + "Pmic Vin History Data 1 Min", // 0x21 + "Pmic Vin History Data 1 Max", // 0x22 + "Pmic Vin History Data 1 Avg", // 0x23 + "Pmic Vin History Data 2 Min", // 0x24 + "Pmic Vin History Data 2 Max", // 0x25 + "Pmic Vin History Data 2 Avg", // 0x26 + "Pmic Vin History Data Total Readings", // 0x27 + "All Time Current Max Wear Level", // 0x28 + "Media Wear Remaining", // 0x29 + "Total Non-Defrag Writes", // 0x2A + "Media Health Relocations" //Uid 0x2B = 43 +}; + +static const char *getOemDataMapDescription(uint32_t id) +{ + if (id < ARRAY_SIZE(oemDataMapDesc)) + return oemDataMapDesc[id]; + return "unknown"; +} + +#define OEMSIGNATURE 0x504D4443 + +#pragma pack(push, cod, 1) +struct cod_header { + uint32_t versionMajor; + uint32_t versionMinor; + uint32_t Signature; //!Fixed signature value (0x504D4443) for identification and validation + uint32_t MapSizeInBytes; //!Total size of the map data structure in bytes + uint32_t EntryCount; //!Total number of entries in the entry list + uint8_t Reserved[12]; +}; + +struct cod_item { + uint32_t DataFieldMapUid; //!The data field unique identifier value + uint32_t reserved1 : 8; + uint32_t dataFieldType : 8; + uint32_t issigned : 1; + uint32_t bigEndian : 1; + uint32_t dataInvalid : 1; + uint32_t reserved2 : 13; + uint32_t DataFieldSizeInBytes; + uint8_t Reserved1[4]; + uint64_t DataFieldOffset; + uint8_t Reserved2[8]; +}; + +struct cod_map { + struct cod_header header; + struct cod_item items[]; +}; + +#pragma pack(pop, cod) + +void solidigm_telemetry_log_cod_parse(struct telemetry_log *tl) +{ + enum cod_field_type { + INTEGER, + FLOAT, + STRING, + TWO_BYTE_ASCII, + FOUR_BYTE_ASCII, + + UNKNOWN = 0xFF, + }; + struct json_object *telemetry_header = NULL; + struct json_object *COD_offset = NULL; + struct json_object *reason_id = NULL; + + if (!json_object_object_get_ex(tl->root, "telemetryHeader", &telemetry_header)) + return; + if (!json_object_object_get_ex(telemetry_header, "reasonIdentifier", &reason_id)) + return; + if (!json_object_object_get_ex(reason_id, "oemDataMapOffset", &COD_offset)) + return; + + uint64_t offset = json_object_get_int(COD_offset); + + if (!offset) + return; + + if ((offset + sizeof(struct cod_header)) > tl->log_size) { + SOLIDIGM_LOG_WARNING("Warning: COD map header out of bounds."); + return; + } + + const struct cod_map *data = (struct cod_map *) (((uint8_t *)tl->log) + offset); + + uint32_t signature = be32_to_cpu(data->header.Signature); + + if (signature != OEMSIGNATURE) { + SOLIDIGM_LOG_WARNING("Warning: Unsupported COD data signature %x!", signature); + return; + } + if ((offset + data->header.MapSizeInBytes) > tl->log_size) { + SOLIDIGM_LOG_WARNING("Warning: COD map data out of bounds."); + return; + } + + struct json_object *cod = json_create_object(); + + json_object_object_add(tl->root, "cod", cod); + + for (uint64_t i = 0; i < data->header.EntryCount; i++) { + if ((offset + sizeof(struct cod_header) + (i + 1) * sizeof(struct cod_item)) > + tl->log_size) { + SOLIDIGM_LOG_WARNING("Warning: COD data out of bounds at item %"PRIu64"!", + i); + return; + } + struct cod_item item = data->items[i]; + + if (item.DataFieldOffset + item.DataFieldOffset > tl->log_size) + continue; + if (item.dataInvalid) + continue; + uint8_t *val = ((uint8_t *)tl->log) + item.DataFieldOffset; + const char *key = getOemDataMapDescription(item.DataFieldMapUid); + + switch (item.dataFieldType) { + case INTEGER: + if (item.issigned) + json_object_object_add(cod, key, + json_object_new_int64(le64_to_cpu(*(uint64_t *)val))); + else + json_object_add_value_uint64(cod, key, le64_to_cpu(*(uint64_t *)val)); + break; + case FLOAT: + json_object_add_value_float(cod, key, *(float *)val); + break; + case STRING: + json_object_object_add(cod, key, + json_object_new_string_len((const char *)val, item.DataFieldSizeInBytes)); + break; + case TWO_BYTE_ASCII: + json_object_object_add(cod, key, + json_object_new_string_len((const char *)val, 2)); + break; + case FOUR_BYTE_ASCII: + json_object_object_add(cod, key, + json_object_new_string_len((const char *)val, 4)); + break; + default: + SOLIDIGM_LOG_WARNING("Warning: Unknown COD field type (%d)", item.DataFieldMapUid); + break; + } + } +} diff --git a/plugins/solidigm/solidigm-telemetry/cod.h b/plugins/solidigm/solidigm-telemetry/cod.h new file mode 100644 index 0000000..032ccdc --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/cod.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include "telemetry-log.h" +void solidigm_telemetry_log_cod_parse(struct telemetry_log *tl); diff --git a/plugins/solidigm/solidigm-telemetry/config.c b/plugins/solidigm/solidigm-telemetry/config.c new file mode 100644 index 0000000..eceeede --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/config.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include <stdio.h> +#include <string.h> +#include "config.h" + +// max 16 bit unsigned integer number 65535 +#define MAX_16BIT_NUM_AS_STRING_SIZE 6 + +#define OBJ_NAME_PREFIX "UID_" +#define NLOG_OBJ_PREFIX OBJ_NAME_PREFIX "NLOG_" + +static bool config_get_by_version(const struct json_object *obj, int version_major, + int version_minor, struct json_object **value) +{ + char str_key[MAX_16BIT_NUM_AS_STRING_SIZE]; + char str_subkey[MAX_16BIT_NUM_AS_STRING_SIZE]; + + snprintf(str_key, sizeof(str_key), "%d", version_major); + snprintf(str_subkey, sizeof(str_subkey), "%d", version_minor); + struct json_object *major_obj = NULL; + + if (!json_object_object_get_ex(obj, str_key, &major_obj)) + return false; + if (!json_object_object_get_ex(major_obj, str_subkey, value)) + return false; + return value != NULL; +} + +bool solidigm_config_get_struct_by_token_version(const struct json_object *config, int token_id, + int version_major, int version_minor, + struct json_object **value) +{ + struct json_object *token = NULL; + char str_key[MAX_16BIT_NUM_AS_STRING_SIZE]; + + snprintf(str_key, sizeof(str_key), "%d", token_id); + if (!json_object_object_get_ex(config, str_key, &token)) + return false; + if (!config_get_by_version(token, version_major, version_minor, value)) + return false; + return value != NULL; +} + +const char *solidigm_config_get_nlog_obj_name(const struct json_object *config, uint32_t token) +{ + struct json_object *nlog_names = NULL; + struct json_object *obj_name; + char hex_header[STR_HEX32_SIZE]; + const char *name; + + if (!json_object_object_get_ex(config, "TELEMETRY_OBJECT_UIDS", &nlog_names)) + return NULL; + snprintf(hex_header, STR_HEX32_SIZE, "0x%08X", token); + + if (!json_object_object_get_ex(nlog_names, hex_header, &obj_name)) + return NULL; + name = json_object_get_string(obj_name); + if (strncmp(NLOG_OBJ_PREFIX, name, strlen(NLOG_OBJ_PREFIX))) + return NULL; + + return &name[strlen(OBJ_NAME_PREFIX)]; +} + +struct json_object *solidigm_config_get_nlog_formats(const struct json_object *config) +{ + struct json_object *nlog_formats = NULL; + + json_object_object_get_ex(config, "NLOG_FORMATS", &nlog_formats); + return nlog_formats; +} diff --git a/plugins/solidigm/solidigm-telemetry/config.h b/plugins/solidigm/solidigm-telemetry/config.h new file mode 100644 index 0000000..4e56ba3 --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/config.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ +#include <stdbool.h> +#include "util/json.h" + +#define STR_HEX32_SIZE sizeof("0x00000000") + +bool solidigm_config_get_struct_by_token_version(const struct json_object *obj, + int key, int subkey, + int subsubkey, + struct json_object **value); + +const char *solidigm_config_get_nlog_obj_name(const struct json_object *config, uint32_t token); +struct json_object *solidigm_config_get_nlog_formats(const struct json_object *config); + diff --git a/plugins/solidigm/solidigm-telemetry/data-area.c b/plugins/solidigm/solidigm-telemetry/data-area.c new file mode 100644 index 0000000..14d612b --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/data-area.c @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include "common.h" +#include "header.h" +#include "cod.h" +#include "data-area.h" +#include "config.h" +#include "nlog.h" +#include <ctype.h> + +#define SIGNED_INT_PREFIX "int" +#define BITS_IN_BYTE 8 + +#define MAX_WARNING_SIZE 1024 +#define MAX_ARRAY_RANK 16 + +static bool telemetry_log_get_value(const struct telemetry_log *tl, + uint64_t offset_bit, uint32_t size_bit, + bool is_signed, struct json_object **val_obj) +{ + uint32_t offset_bit_from_byte; + uint32_t additional_size_byte; + uint32_t offset_byte; + uint64_t val; + + if (!size_bit) { + char err_msg[MAX_WARNING_SIZE]; + + snprintf(err_msg, MAX_WARNING_SIZE, + "Value with size_bit=0 not supported."); + *val_obj = json_object_new_string(err_msg); + + return false; + } + additional_size_byte = (size_bit - 1) ? (size_bit - 1) / BITS_IN_BYTE : 0; + offset_byte = (uint32_t)offset_bit / BITS_IN_BYTE; + + if (offset_byte > (tl->log_size - additional_size_byte)) { + char err_msg[MAX_WARNING_SIZE]; + + snprintf(err_msg, MAX_WARNING_SIZE, + "Value offset greater than binary size (%u > %zu).", + offset_byte, tl->log_size); + *val_obj = json_object_new_string(err_msg); + + return false; + } + + offset_bit_from_byte = (uint32_t) (offset_bit - ((uint64_t)offset_byte * BITS_IN_BYTE)); + + if ((size_bit + offset_bit_from_byte) > (sizeof(uint64_t) * BITS_IN_BYTE)) { + char err_msg[MAX_WARNING_SIZE]; + + snprintf(err_msg, MAX_WARNING_SIZE, + "Value crossing 64 bit, byte aligned boundary, not supported. size_bit=%u, offset_bit_from_byte=%u.", + size_bit, offset_bit_from_byte); + *val_obj = json_object_new_string(err_msg); + + return false; + } + + val = *(uint64_t *)(((char *)tl->log) + offset_byte); + val >>= offset_bit_from_byte; + if (size_bit < 64) + val &= (1ULL << size_bit) - 1; + if (is_signed) { + if (val >> (size_bit - 1)) + val |= (0ULL - 1) << size_bit; + *val_obj = json_object_new_int64(val); + } else { + *val_obj = json_object_new_uint64(val); + } + + return true; +} + +static int telemetry_log_structure_parse(const struct telemetry_log *tl, + struct json_object *struct_def, + uint64_t parent_offset_bit, + struct json_object *output, + struct json_object *metadata) +{ + struct json_object *obj_arraySizeArray = NULL; + struct json_object *obj = NULL; + struct json_object *obj_memberList; + struct json_object *major_dimension = NULL; + struct json_object *sub_output; + bool is_enumeration = false; + bool has_member_list; + const char *type = ""; + const char *name; + size_t array_rank; + uint64_t offset_bit; + uint32_t size_bit; + uint64_t linear_array_pos_bit; + uint32_t array_size_dimension[MAX_ARRAY_RANK]; + + if (!json_object_object_get_ex(struct_def, "name", &obj)) { + SOLIDIGM_LOG_WARNING("Warning: Structure definition missing property 'name': %s", + json_object_to_json_string(struct_def)); + return -1; + } + + name = json_object_get_string(obj); + + if (metadata) { + json_object_get(obj); + json_object_object_add(metadata, "objName", obj); + } + + if (json_object_object_get_ex(struct_def, "type", &obj)) + type = json_object_get_string(obj); + + if (!json_object_object_get_ex(struct_def, "offsetBit", &obj)) { + SOLIDIGM_LOG_WARNING( + "Warning: Structure definition missing property 'offsetBit': %s", + json_object_to_json_string(struct_def)); + return -1; + } + + offset_bit = json_object_get_uint64(obj); + + if (!json_object_object_get_ex(struct_def, "sizeBit", &obj)) { + SOLIDIGM_LOG_WARNING( + "Warning: Structure definition missing property 'sizeBit': %s", + json_object_to_json_string(struct_def)); + return -1; + } + + size_bit = (uint32_t)json_object_get_uint64(obj); + + if (json_object_object_get_ex(struct_def, "enum", &obj)) + is_enumeration = json_object_get_boolean(obj); + + has_member_list = json_object_object_get_ex(struct_def, + "memberList", + &obj_memberList); + + if (!json_object_object_get_ex(struct_def, "arraySize", + &obj_arraySizeArray)) { + SOLIDIGM_LOG_WARNING( + "Warning: Structure definition missing property 'arraySize': %s", + json_object_to_json_string(struct_def)); + return -1; + } + + array_rank = json_object_array_length(obj_arraySizeArray); + if (!array_rank) { + SOLIDIGM_LOG_WARNING( + "Warning: Structure property 'arraySize' don't support flexible array: %s", + json_object_to_json_string(struct_def)); + return -1; + } + if (array_rank > MAX_ARRAY_RANK) { + SOLIDIGM_LOG_WARNING( + "Warning: Structure property 'arraySize' don't support more than %d dimensions: %s", + MAX_ARRAY_RANK, json_object_to_json_string(struct_def)); + return -1; + } + + for (size_t i = 0; i < array_rank; i++) { + struct json_object *dimension = json_object_array_get_idx(obj_arraySizeArray, i); + + array_size_dimension[i] = json_object_get_int(dimension); + major_dimension = dimension; + } + if (array_rank > 1) { + uint32_t linear_pos_per_index = array_size_dimension[0]; + uint32_t prev_index_offset_bit = 0; + struct json_object *dimension_output; + + for (unsigned int i = 1; i < (array_rank - 1); i++) + linear_pos_per_index *= array_size_dimension[i]; + + dimension_output = json_create_array(); + if (json_object_get_type(output) == json_type_array) + json_object_array_add(output, dimension_output); + else + json_object_add_value_array(output, name, dimension_output); + + /* + * Make sure major_dimension object will not be + * deleted from memory when deleted from array + */ + json_object_get(major_dimension); + json_object_array_del_idx(obj_arraySizeArray, array_rank - 1, 1); + + for (unsigned int i = 0 ; i < array_size_dimension[0]; i++) { + struct json_object *sub_array = json_create_array(); + uint64_t offset; + + offset = parent_offset_bit + prev_index_offset_bit; + + json_object_array_add(dimension_output, sub_array); + telemetry_log_structure_parse(tl, struct_def, + offset, sub_array, NULL); + prev_index_offset_bit += linear_pos_per_index * size_bit; + } + + json_object_array_put_idx(obj_arraySizeArray, array_rank - 1, + major_dimension); + + return 0; + } + + linear_array_pos_bit = 0; + sub_output = output; + + if (array_size_dimension[0] > 1) { + sub_output = json_create_array(); + if (json_object_get_type(output) == json_type_array) + json_object_array_add(output, sub_output); + else + json_object_add_value_array(output, name, sub_output); + } + + for (uint32_t j = 0; j < array_size_dimension[0]; j++) { + if (is_enumeration || !has_member_list) { + bool is_signed = !strncmp(type, SIGNED_INT_PREFIX, sizeof(SIGNED_INT_PREFIX)-1); + struct json_object *val_obj; + uint64_t offset; + + offset = parent_offset_bit + offset_bit + linear_array_pos_bit; + if (telemetry_log_get_value(tl, offset, size_bit, is_signed, &val_obj)) { + if (array_size_dimension[0] > 1) + json_object_array_put_idx(sub_output, j, val_obj); + else + json_object_object_add(sub_output, name, val_obj); + } else { + SOLIDIGM_LOG_WARNING( + "Warning: %s From property '%s', array index %u, structure definition: %s", + json_object_get_string(val_obj), name, j, + json_object_to_json_string(struct_def)); + json_free_object(val_obj); + } + } else { + struct json_object *sub_sub_output = json_create_object(); + int num_members; + + if (array_size_dimension[0] > 1) + json_object_array_put_idx(sub_output, j, sub_sub_output); + else + json_object_add_value_object(sub_output, name, sub_sub_output); + + num_members = json_object_array_length(obj_memberList); + for (int k = 0; k < num_members; k++) { + struct json_object *member = json_object_array_get_idx(obj_memberList, k); + uint64_t offset; + + offset = parent_offset_bit + offset_bit + linear_array_pos_bit; + telemetry_log_structure_parse(tl, member, offset, + sub_sub_output, NULL); + } + } + linear_array_pos_bit += size_bit; + } + return 0; +} + +static int telemetry_log_data_area_get_offset(const struct telemetry_log *tl, + enum nvme_telemetry_da da, + uint32_t *offset, uint32_t *size) +{ + uint32_t offset_blocks = 1; + uint32_t last_block = tl->log->dalb1; + uint32_t last; + + switch (da) { + case NVME_TELEMETRY_DA_1: + offset_blocks = 1; + last_block = tl->log->dalb1; + break; + case NVME_TELEMETRY_DA_2: + offset_blocks = tl->log->dalb1 + 1; + last_block = tl->log->dalb2; + break; + case NVME_TELEMETRY_DA_3: + offset_blocks = tl->log->dalb2 + 1; + last_block = tl->log->dalb3; + break; + case NVME_TELEMETRY_DA_4: + offset_blocks = tl->log->dalb3 + 1; + last_block = tl->log->dalb4; + break; + default: + return -1; + } + + *offset = offset_blocks * NVME_LOG_TELEM_BLOCK_SIZE; + last = (last_block + 1) * NVME_LOG_TELEM_BLOCK_SIZE; + *size = last - *offset; + if ((*offset > tl->log_size) || (last > tl->log_size) || (last <= *offset)) { + SOLIDIGM_LOG_WARNING("Warning: Data Area %d don't fit this Telemetry log.", da); + return -1; + } + + return 0; +} + +static int telemetry_log_nlog_parse(const struct telemetry_log *tl, struct json_object *formats, + uint64_t nlog_file_offset, uint64_t nlog_size, + struct json_object *output, struct json_object *metadata) +{ + /* boundary check */ + if (tl->log_size < (nlog_file_offset + nlog_size)) { + const char *name = ""; + int media_bank = -1; + struct json_object *jobj; + + if (json_object_object_get_ex(metadata, "objName", &jobj)) + name = json_object_get_string(jobj); + if (json_object_object_get_ex(metadata, "mediaBankId", &jobj)) + media_bank = json_object_get_int(jobj); + SOLIDIGM_LOG_WARNING("%s:%d do not fit this log dump.", name, media_bank); + return -1; + } + return solidigm_nlog_parse(((char *) tl->log) + nlog_file_offset, + nlog_size, formats, metadata, output); +} + +struct toc_item { + uint32_t OffsetBytes; + uint32_t ContentSizeBytes; +}; + +struct data_area_header { + uint8_t versionMajor; + uint8_t versionMinor; + uint16_t TableOfContentsCount; + uint32_t DataAreaSize; + uint8_t Reserved[8]; +}; + +struct table_of_contents { + struct data_area_header header; + struct toc_item items[]; +}; + +struct telemetry_object_header { + uint16_t versionMajor; + uint16_t versionMinor; + uint32_t Token; + uint8_t CoreId; + uint8_t Reserved[3]; +}; + +static void telemetry_log_data_area_toc_parse(const struct telemetry_log *tl, + enum nvme_telemetry_da da, + struct json_object *toc_array, + struct json_object *tele_obj_array) +{ + + const struct telemetry_object_header *header; + const struct table_of_contents *toc; + char *payload; + uint32_t da_offset; + uint32_t da_size; + struct json_object *nlog_formats; + + if (telemetry_log_data_area_get_offset(tl, da, &da_offset, &da_size)) + return; + + toc = (struct table_of_contents *)(((char *)tl->log) + da_offset); + payload = (char *) tl->log; + nlog_formats = solidigm_config_get_nlog_formats(tl->configuration); + + for (int i = 0; i < toc->header.TableOfContentsCount; i++) { + struct json_object *structure_definition = NULL; + struct json_object *toc_item; + uint32_t obj_offset; + bool has_struct; + const char *nlog_name = NULL; + uint32_t header_offset = sizeof(const struct telemetry_object_header); + + if ((char *)&toc->items[i] > + (((char *)toc) + da_size - sizeof(const struct toc_item))) { + SOLIDIGM_LOG_WARNING( + "Warning: Data Area %d, Table of Contents item %d crossed Data Area size.", + da, i); + return; + } + + obj_offset = toc->items[i].OffsetBytes; + if ((obj_offset + sizeof(const struct telemetry_object_header)) > da_size) { + SOLIDIGM_LOG_WARNING( + "Warning: Data Area %d, item %d data, crossed Data Area size.", da, i); + continue; + } + + toc_item = json_create_object(); + json_object_array_add(toc_array, toc_item); + json_object_add_value_uint(toc_item, "dataArea", da); + json_object_add_value_uint(toc_item, "dataAreaIndex", i); + json_object_add_value_uint(toc_item, "dataAreaOffset", obj_offset); + json_object_add_value_uint(toc_item, "fileOffset", obj_offset + da_offset); + json_object_add_value_uint(toc_item, "size", toc->items[i].ContentSizeBytes); + + header = (const struct telemetry_object_header *) (payload + da_offset + obj_offset); + json_object_add_value_uint(toc_item, "telemMajor", header->versionMajor); + json_object_add_value_uint(toc_item, "telemMinor", header->versionMinor); + json_object_add_value_uint(toc_item, "objectId", header->Token); + json_object_add_value_uint(toc_item, "mediaBankId", header->CoreId); + + has_struct = solidigm_config_get_struct_by_token_version(tl->configuration, + header->Token, + header->versionMajor, + header->versionMinor, + &structure_definition); + if (!has_struct) { + if (!nlog_formats) + continue; + nlog_name = solidigm_config_get_nlog_obj_name(tl->configuration, + header->Token); + if (!nlog_name) + continue; + } + struct json_object *tele_obj_item = json_create_object(); + + json_object_array_add(tele_obj_array, tele_obj_item); + json_object_get(toc_item); + json_object_add_value_object(tele_obj_item, "metadata", toc_item); + struct json_object *parsed_struct = json_create_object(); + + json_object_add_value_object(tele_obj_item, "objectData", parsed_struct); + struct json_object *obj_hasTelemObjHdr = NULL; + uint64_t object_file_offset; + + if (json_object_object_get_ex(structure_definition, + "hasTelemObjHdr", + &obj_hasTelemObjHdr)) { + bool hasHeader = json_object_get_boolean(obj_hasTelemObjHdr); + + if (hasHeader) + header_offset = 0; + } + object_file_offset = ((uint64_t)da_offset) + obj_offset + header_offset; + if (has_struct) { + telemetry_log_structure_parse(tl, structure_definition, + BITS_IN_BYTE * object_file_offset, + parsed_struct, toc_item); + } else if (nlog_formats) { + json_object_object_add(toc_item, "objName", + json_object_new_string(nlog_name)); + telemetry_log_nlog_parse(tl, nlog_formats, object_file_offset, + toc->items[i].ContentSizeBytes - header_offset, + parsed_struct, toc_item); + } + } +} + +int solidigm_telemetry_log_data_areas_parse(struct telemetry_log *tl, + enum nvme_telemetry_da last_da) +{ + struct json_object *tele_obj_array = json_create_array(); + struct json_object *toc_array = json_create_array(); + + solidigm_telemetry_log_header_parse(tl); + solidigm_telemetry_log_cod_parse(tl); + if (tl->configuration) { + json_object_add_value_array(tl->root, "tableOfContents", toc_array); + json_object_add_value_array(tl->root, "telemetryObjects", tele_obj_array); + + for (enum nvme_telemetry_da da = NVME_TELEMETRY_DA_1; da <= last_da; da++) + telemetry_log_data_area_toc_parse(tl, da, toc_array, tele_obj_array); + } + return 0; +} diff --git a/plugins/solidigm/solidigm-telemetry/data-area.h b/plugins/solidigm/solidigm-telemetry/data-area.h new file mode 100644 index 0000000..6b690d8 --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/data-area.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ +#include "telemetry-log.h" + +int solidigm_telemetry_log_data_areas_parse(struct telemetry_log *tl, + enum nvme_telemetry_da last_da); diff --git a/plugins/solidigm/solidigm-telemetry/header.c b/plugins/solidigm/solidigm-telemetry/header.c new file mode 100644 index 0000000..866ebff --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/header.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include "common.h" +#include "header.h" + +#pragma pack(push, reason_indentifier, 1) +struct reason_indentifier_1_0 { + uint16_t versionMajor; + uint16_t versionMinor; + uint32_t reasonCode; //! 0 denotes no issue. All other values denote a potential issue. + char DriveStatus[20]; //! Drive Status String (for example: "Healthy", "*BAD_CONTEXT_2020") + char FirmwareVersion[12]; //! Similar to IdentifyController.FR + char BootloaderVersion[12]; //! Bootloader version string + char SerialNumber[20]; //! Device serial number + uint8_t Reserved[56]; //! Reserved for future usage +}; +static_assert(sizeof(const struct reason_indentifier_1_0) == + MEMBER_SIZE(struct nvme_telemetry_log, rsnident), + "Size mismatch for reason_indentifier_1_0"); + +struct reason_indentifier_1_1 { + uint16_t versionMajor; + uint16_t versionMinor; + uint32_t reasonCode; //! 0 denotes no issue. All other values denote a potential issue. + char DriveStatus[20]; //! Drive Status String (for example: "Healthy", "*BAD_CONTEXT_2020") + char FirmwareVersion[12]; //! Similar to IdentifyController.FR + char BootloaderVersion[12]; //! Bootloader version string + char SerialNumber[20]; //! Device serial number + uint64_t OemDataMapOffset; //! Customer Data Map Object Log Offset + uint8_t TelemetryMajorVersion; //! Shadow of version in TOC + uint8_t TelemetryMinorVersion; //! Shadow of version in TOC + uint8_t Reserved[46]; //! Reserved for future usage +}; +static_assert(sizeof(const struct reason_indentifier_1_1) == + MEMBER_SIZE(struct nvme_telemetry_log, rsnident), + "Size mismatch for reason_indentifier_1_1"); + +struct reason_indentifier_1_2 { + uint16_t versionMajor; + uint16_t versionMinor; + uint32_t reasonCode; //! 0 denotes no issue. All other values denote a potential issue. + char DriveStatus[20]; //! Drive Status String (for example: "Healthy", "*BAD_CONTEXT_2020") + uint8_t Reserved1[24]; //! pad over Fields removed from version 1.1 + char SerialNumber[20]; //! Device serial number + uint64_t OemDataMapOffset; //! Customer Data Map Object Log Offset + uint8_t TelemetryMajorVersion; //! Shadow of version in TOC + uint8_t TelemetryMinorVersion; //! Shadow of version in TOC + uint8_t ProductFamilyId; + uint8_t Reserved2[5]; //! Reserved for future usage + uint8_t DualPortReserved[40]; //! Reserved for dual port +}; +static_assert(sizeof(const struct reason_indentifier_1_2) == + MEMBER_SIZE(struct nvme_telemetry_log, rsnident), + "Size mismatch for reason_indentifier_1_2"); +#pragma pack(pop, reason_indentifier) + +static void telemetry_log_reason_id_parse1_0_ext(const struct telemetry_log *tl, + struct json_object *reason_id) +{ + const struct reason_indentifier_1_0 *ri; + struct json_object *reserved; + + ri = (struct reason_indentifier_1_0 *) tl->log->rsnident; + json_object_object_add(reason_id, "firmwareVersion", + json_object_new_string_len(ri->FirmwareVersion, + sizeof(ri->FirmwareVersion))); + json_object_object_add(reason_id, "bootloaderVersion", + json_object_new_string_len(ri->BootloaderVersion, + sizeof(ri->BootloaderVersion))); + json_object_object_add(reason_id, "serialNumber", + json_object_new_string_len(ri->SerialNumber, + sizeof(ri->SerialNumber))); + + reserved = json_create_array(); + json_object_add_value_array(reason_id, "reserved", reserved); + for (int i = 0; i < sizeof(ri->Reserved); i++) { + struct json_object *val = json_object_new_int(ri->Reserved[i]); + + json_object_array_add(reserved, val); + } +} + +static void telemetry_log_reason_id_parse1_1_ext(const struct telemetry_log *tl, + struct json_object *reason_id) +{ + const struct reason_indentifier_1_1 *ri; + struct json_object *reserved; + + ri = (struct reason_indentifier_1_1 *) tl->log->rsnident; + json_object_object_add(reason_id, "firmwareVersion", + json_object_new_string_len(ri->FirmwareVersion, + sizeof(ri->FirmwareVersion))); + json_object_object_add(reason_id, "bootloaderVersion", + json_object_new_string_len(ri->BootloaderVersion, + sizeof(ri->BootloaderVersion))); + json_object_object_add(reason_id, "serialNumber", + json_object_new_string_len(ri->SerialNumber, + sizeof(ri->SerialNumber))); + json_object_add_value_uint64(reason_id, "oemDataMapOffset", + le64_to_cpu(ri->OemDataMapOffset)); + json_object_add_value_uint(reason_id, "telemetryMajorVersion", + le16_to_cpu(ri->TelemetryMajorVersion)); + json_object_add_value_uint(reason_id, "telemetryMinorVersion", + le16_to_cpu(ri->TelemetryMinorVersion)); + + reserved = json_create_array(); + json_object_add_value_array(reason_id, "reserved", reserved); + for (int i = 0; i < sizeof(ri->Reserved); i++) { + struct json_object *val = json_object_new_int(ri->Reserved[i]); + + json_object_array_add(reserved, val); + } +} + +static void telemetry_log_reason_id_parse1_2_ext(const struct telemetry_log *tl, + struct json_object *reason_id) +{ + const struct reason_indentifier_1_2 *ri; + struct json_object *dp_reserved; + struct json_object *reserved; + + ri = (struct reason_indentifier_1_2 *) tl->log->rsnident; + + json_object_object_add(reason_id, "serialNumber", + json_object_new_string_len(ri->SerialNumber, + sizeof(ri->SerialNumber))); + json_object_add_value_uint64(reason_id, "oemDataMapOffset", + le64_to_cpu(ri->OemDataMapOffset)); + json_object_add_value_uint(reason_id, "telemetryMajorVersion", + le16_to_cpu(ri->TelemetryMajorVersion)); + json_object_add_value_uint(reason_id, "telemetryMinorVersion", + le16_to_cpu(ri->TelemetryMinorVersion)); + json_object_add_value_uint(reason_id, "productFamilyId", ri->ProductFamilyId); + + reserved = json_create_array(); + json_object_add_value_array(reason_id, "reserved2", reserved); + for (int i = 0; i < sizeof(ri->Reserved2); i++) { + struct json_object *val = json_object_new_int(ri->Reserved2[i]); + + json_object_array_add(reserved, val); + } + + dp_reserved = json_create_array(); + json_object_add_value_array(reason_id, "dualPortReserved", dp_reserved); + for (int i = 0; i < sizeof(ri->DualPortReserved); i++) { + struct json_object *val = json_object_new_int(ri->DualPortReserved[i]); + + json_object_array_add(dp_reserved, val); + } +} + +static void solidigm_telemetry_log_reason_id_parse(const struct telemetry_log *tl, struct json_object *reason_id) +{ + const struct reason_indentifier_1_0 *ri1_0 = + (struct reason_indentifier_1_0 *) tl->log->rsnident; + uint16_t version_major = le16_to_cpu(ri1_0->versionMajor); + uint16_t version_minor = le16_to_cpu(ri1_0->versionMinor); + + json_object_add_value_uint(reason_id, "versionMajor", version_major); + json_object_add_value_uint(reason_id, "versionMinor", version_minor); + json_object_add_value_uint(reason_id, "reasonCode", le32_to_cpu(ri1_0->reasonCode)); + json_object_add_value_object(reason_id, "driveStatus", + json_object_new_string_len(ri1_0->DriveStatus, + sizeof(ri1_0->DriveStatus))); + if (version_major == 1) { + switch (version_minor) { + case 0: + telemetry_log_reason_id_parse1_0_ext(tl, reason_id); + break; + case 1: + telemetry_log_reason_id_parse1_1_ext(tl, reason_id); + break; + default: + telemetry_log_reason_id_parse1_2_ext(tl, reason_id); + break; + } + } +} + +bool solidigm_telemetry_log_header_parse(const struct telemetry_log *tl) +{ + const struct nvme_telemetry_log *log; + struct json_object *ieee_oui_id; + struct json_object *reason_id; + struct json_object *header; + + if (tl->log_size < sizeof(const struct nvme_telemetry_log)) { + SOLIDIGM_LOG_WARNING("Telemetry log too short."); + return false; + } + + header = json_create_object(); + + json_object_object_add(tl->root, "telemetryHeader", header); + log = tl->log; + + json_object_add_value_uint(header, "logIdentifier", log->lpi); + ieee_oui_id = json_create_array(); + + json_object_object_add(header, "ieeeOuiIdentifier", ieee_oui_id); + for (int i = 0; i < sizeof(log->ieee); i++) { + struct json_object *val = json_object_new_int(log->ieee[i]); + + json_object_array_add(ieee_oui_id, val); + } + json_object_add_value_uint(header, "dataArea1LastBlock", log->dalb1); + json_object_add_value_uint(header, "dataArea2LastBlock", log->dalb2); + json_object_add_value_uint(header, "dataArea3LastBlock", log->dalb3); + json_object_add_value_uint(header, "hostInitiatedDataGeneration", log->hostdgn); + json_object_add_value_uint(header, "controllerInitiatedDataAvailable", log->ctrlavail); + json_object_add_value_uint(header, "controllerInitiatedDataGeneration", log->ctrldgn); + + reason_id = json_create_object(); + json_object_add_value_object(header, "reasonIdentifier", reason_id); + solidigm_telemetry_log_reason_id_parse(tl, reason_id); + + return true; +} diff --git a/plugins/solidigm/solidigm-telemetry/header.h b/plugins/solidigm/solidigm-telemetry/header.h new file mode 100644 index 0000000..027af55 --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/header.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include "telemetry-log.h" +bool solidigm_telemetry_log_header_parse(const struct telemetry_log *tl); diff --git a/plugins/solidigm/solidigm-telemetry/meson.build b/plugins/solidigm/solidigm-telemetry/meson.build new file mode 100644 index 0000000..96273b7 --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/meson.build @@ -0,0 +1,7 @@ +sources += [ + 'plugins/solidigm/solidigm-telemetry/cod.c', + 'plugins/solidigm/solidigm-telemetry/header.c', + 'plugins/solidigm/solidigm-telemetry/config.c', + 'plugins/solidigm/solidigm-telemetry/data-area.c', + 'plugins/solidigm/solidigm-telemetry/nlog.c', +] diff --git a/plugins/solidigm/solidigm-telemetry/nlog.c b/plugins/solidigm/solidigm-telemetry/nlog.c new file mode 100644 index 0000000..926772b --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/nlog.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (c) 2023 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include "nlog.h" +#include "config.h" +#include <string.h> +#include <stdio.h> + +#include "ccan/ilog/ilog.h" + +#define LOG_ENTRY_HEADER_SIZE 1 +#define LOG_ENTRY_TIMESTAMP_SIZE 2 +#define LOG_ENTRY_NUM_ARGS_MAX 8 +#define LOG_ENTRY_MAX_SIZE (LOG_ENTRY_HEADER_SIZE + LOG_ENTRY_TIMESTAMP_SIZE + \ + LOG_ENTRY_NUM_ARGS_MAX) +#define NUM_ARGS_MASK ((1 << ((int)STATIC_ILOG_32(LOG_ENTRY_NUM_ARGS_MAX))) - 1) +#define MAX_HEADER_MISMATCH_TRACK 10 + +static int formats_find(struct json_object *formats, uint32_t val, struct json_object **format) +{ + char hex_header[STR_HEX32_SIZE]; + + snprintf(hex_header, STR_HEX32_SIZE, "0x%08X", val); + return json_object_object_get_ex(formats, hex_header, format); +} + +static uint32_t nlog_get_pos(const uint32_t *nlog, const uint32_t nlog_size, int pos) +{ + return nlog[pos % nlog_size]; +} + +static uint32_t nlog_get_events(const uint32_t *nlog, const uint32_t nlog_size, int start_offset, + struct json_object *formats, struct json_object *events, uint32_t *tail_mismatches) +{ + uint32_t event_count = 0; + int last_bad_header_pos = nlog_size + 1; // invalid nlog offset + uint32_t tail_count = 0; + + for (int i = nlog_size - start_offset - 1; i >= -start_offset; i--) { + struct json_object *format; + uint32_t header = nlog_get_pos(nlog, nlog_size, i); + uint32_t num_data; + + if (header == 0 || !formats_find(formats, header, &format)) { + if (event_count > 0) { + //check if fould circular buffer tail + if (i != (last_bad_header_pos - 1)) { + if (tail_mismatches && + (tail_count < MAX_HEADER_MISMATCH_TRACK)) + tail_mismatches[tail_count] = header; + tail_count++; + } + last_bad_header_pos = i; + } + continue; + } + num_data = header & NUM_ARGS_MASK; + if (events) { + struct json_object *event = json_object_new_array(); + struct json_object *param = json_object_new_array(); + uint32_t val = nlog_get_pos(nlog, nlog_size, i - 1); + + json_object_array_add(events, event); + json_object_array_add(event, json_object_new_int64(val)); + val = nlog_get_pos(nlog, nlog_size, i - 2); + json_object_array_add(event, json_object_new_int64(val)); + json_object_array_add(event, json_object_new_int64(header)); + json_object_array_add(event, param); + for (uint32_t j = 0; j < num_data; j++) { + val = nlog_get_pos(nlog, nlog_size, i - 3 - j); + json_object_array_add(param, json_object_new_int64(val)); + } + json_object_get(format); + json_object_array_add(event, format); + } + i -= 2 + num_data; + event_count++; + } + return tail_count; +} + +int solidigm_nlog_parse(const char *buffer, uint64_t buff_size, struct json_object *formats, + struct json_object *metadata, struct json_object *output) +{ + uint32_t smaller_tail_count = UINT32_MAX; + int best_offset = 0; + uint32_t offset_tail_mismatches[LOG_ENTRY_MAX_SIZE][MAX_HEADER_MISMATCH_TRACK]; + struct json_object *events = json_object_new_array(); + const uint32_t *nlog = (uint32_t *)buffer; + const uint32_t nlog_size = buff_size / sizeof(uint32_t); + + for (int i = 0; i < LOG_ENTRY_MAX_SIZE; i++) { + uint32_t tail_count = nlog_get_events(nlog, nlog_size, i, formats, NULL, + offset_tail_mismatches[i]); + if (tail_count < smaller_tail_count) { + best_offset = i; + smaller_tail_count = tail_count; + } + if (tail_count == 0) + break; + } + if (smaller_tail_count > 1) { + const char *name = ""; + int media_bank = -1; + char str_mismatches[(STR_HEX32_SIZE + 1) * MAX_HEADER_MISMATCH_TRACK]; + int pos = 0; + int show_mismatch_num = smaller_tail_count < MAX_HEADER_MISMATCH_TRACK ? + smaller_tail_count : MAX_HEADER_MISMATCH_TRACK; + struct json_object *jobj; + + if (json_object_object_get_ex(metadata, "objName", &jobj)) + name = json_object_get_string(jobj); + if (json_object_object_get_ex(metadata, "mediaBankId", &jobj)) + media_bank = json_object_get_int(jobj); + + for (int i = 0; i < show_mismatch_num; i++) + pos += snprintf(&str_mismatches[pos], STR_HEX32_SIZE + 1, "0x%08X ", + offset_tail_mismatches[best_offset][i]); + + SOLIDIGM_LOG_WARNING("%s:%d with %d header mismatches ( %s). Configuration file may be missing format headers.", + name, media_bank, smaller_tail_count, str_mismatches); + } + nlog_get_events(nlog, nlog_size, best_offset, formats, events, NULL); + + json_object_object_add(output, "events", events); + return 0; +} diff --git a/plugins/solidigm/solidigm-telemetry/nlog.h b/plugins/solidigm/solidigm-telemetry/nlog.h new file mode 100644 index 0000000..f33aa45 --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/nlog.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2023 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ +#include "telemetry-log.h" + +int solidigm_nlog_parse(const char *buffer, uint64_t bufer_size, + struct json_object *formats, struct json_object *metadata, + struct json_object *output); diff --git a/plugins/solidigm/solidigm-telemetry/telemetry-log.h b/plugins/solidigm/solidigm-telemetry/telemetry-log.h new file mode 100644 index 0000000..e9eff73 --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/telemetry-log.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#ifndef _SOLIDIGM_TELEMETRY_LOG_H +#define _SOLIDIGM_TELEMETRY_LOG_H + +#include "libnvme.h" +#include "util/json.h" +#include <assert.h> + +#if !defined __cplusplus +#define static_assert _Static_assert +#endif + +#define VA_ARGS(...), ##__VA_ARGS__ +#define SOLIDIGM_LOG_WARNING(format, ...) fprintf(stderr, format"\n" VA_ARGS(__VA_ARGS__)) + +#define MEMBER_SIZE(type, member) sizeof(((type *)0)->member) + +struct telemetry_log { + struct nvme_telemetry_log *log; + size_t log_size; + struct json_object *root; + struct json_object *configuration; +}; + +#endif /* _SOLIDIGM_TELEMETRY_LOG_H */ diff --git a/plugins/solidigm/solidigm-temp-stats.c b/plugins/solidigm/solidigm-temp-stats.c new file mode 100644 index 0000000..85a3c37 --- /dev/null +++ b/plugins/solidigm/solidigm-temp-stats.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include <errno.h> + +#include "common.h" +#include "nvme-print.h" +#include "solidigm-util.h" + +#define SLDGM_TEMP_STATS_LID 0xC5 + +struct 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 temp_stats *stats) +{ + 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)); +} + +int sldgm_get_temp_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + unsigned char buffer[4096] = {0}; + struct nvme_dev *dev; + __u8 uuid_idx; + int err; + + const char *desc = "Get/show Temperature Statistics log."; + const char *raw = "dump output in binary format"; + struct config { + bool raw_binary; + }; + + struct config cfg = { + .raw_binary = false, + }; + + OPT_ARGS(opts) = { + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + uuid_idx = solidigm_get_vu_uuid_index(dev); + + struct nvme_get_log_args args = { + .lpo = 0, + .result = NULL, + .log = buffer, + .args_size = sizeof(args), + .fd = dev_fd(dev), + .uuidx = uuid_idx, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .lid = SLDGM_TEMP_STATS_LID, + .len = sizeof(buffer), + .nsid = NVME_NSID_ALL, + .csi = NVME_CSI_NVM, + .lsi = NVME_LOG_LSI_NONE, + .lsp = NVME_LOG_LSP_NONE, + .rae = false, + .ot = false, + }; + + err = nvme_get_log(&args); + if (!err) { + uint64_t *guid = (uint64_t *)&buffer[4080]; + + if (guid[1] == 0xC7BB98B7D0324863 && guid[0] == 0xBB2C23990E9C722F) { + fprintf(stderr, "Error: Log page has 'OCP unsupported Requirements' GUID\n"); + err = -EBADMSG; + goto closefd; + } + if (!cfg.raw_binary) + show_temp_stats((struct temp_stats *) buffer); + else + d_raw(buffer, sizeof(struct temp_stats)); + } else if (err > 0) { + nvme_show_status(err); + } + +closefd: + /* Redundant close() to make static code analysis happy */ + close(dev->direct.fd); + dev_close(dev); + return err; +} diff --git a/plugins/solidigm/solidigm-temp-stats.h b/plugins/solidigm/solidigm-temp-stats.h new file mode 100644 index 0000000..58d5a86 --- /dev/null +++ b/plugins/solidigm/solidigm-temp-stats.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +int sldgm_get_temp_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin); diff --git a/plugins/solidigm/solidigm-util.c b/plugins/solidigm/solidigm-util.c new file mode 100644 index 0000000..0171a49 --- /dev/null +++ b/plugins/solidigm/solidigm-util.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include "plugins/ocp/ocp-utils.h" +#include "solidigm-util.h" + +__u8 solidigm_get_vu_uuid_index(struct nvme_dev *dev) +{ + int ocp_uuid_index = 0; + + if (ocp_get_uuid_index(dev, &ocp_uuid_index) == 0) + if (ocp_uuid_index == 2) + return 1; + + return 0; +} diff --git a/plugins/solidigm/solidigm-util.h b/plugins/solidigm/solidigm-util.h new file mode 100644 index 0000000..fa5032f --- /dev/null +++ b/plugins/solidigm/solidigm-util.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include "nvme.h" + +#define DRIVER_MAX_TX_256K (256 * 1024) + +__u8 solidigm_get_vu_uuid_index(struct nvme_dev *dev); diff --git a/plugins/toshiba/toshiba-nvme.c b/plugins/toshiba/toshiba-nvme.c new file mode 100644 index 0000000..4927012 --- /dev/null +++ b/plugins/toshiba/toshiba-nvme.c @@ -0,0 +1,573 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#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 "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-print.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; +static const __u32 DW10_SCT_COMMAND_TRANSFER = 0x1; + +static const __u32 DW11_SCT_STATUS_COMMAND; +static const __u32 DW11_SCT_COMMAND_TRANSFER; + +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 */ +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 +}; + +/* Internal device codes */ +enum { + CODE_0 = 0x0D, + CODE_1 = 0x10 +}; + + +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; + __u32 result; + + return nvme_admin_passthru(fd, opcode, flags, rsvd, namespace_id, cdw2, cdw3, cdw10, cdw11, + cdw12, cdw13, cdw14, cdw15, data_len, data, metadata_len, + metadata, timeout, &result); +} + +static int nvme_get_sct_status(int fd, __u32 device_mask) +{ + int err; + void *data = NULL; + size_t data_len = 512; + unsigned char *status; + __u32 supported = 0; + + 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) { + switch (status[1]) { + case CODE_0: + supported = (device_mask & MASK_0); + break; + case CODE_1: + supported = (device_mask & MASK_1); + break; + default: + break; + }; + + if (!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); + free(data); + 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 int 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; + __u32 pages_chunk; + /* + * 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;) { + 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(struct nvme_dev *dev, __u32 nsid, + struct nvme_xdn_smart_log_c0 *smart) +{ + printf("Vendor Log Page Directory 0xC0 for NVME device:%s namespace-id:%x\n", + dev->name, 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(struct nvme_dev *dev, __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(dev_fd(dev), MASK_0 | MASK_1); + if (err) + goto end; + err = nvme_get_nsid_log(dev_fd(dev), false, log_page, namespace_id, + 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(dev, namespace_id, 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) +{ + 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 nvme_dev *dev; + int err; + + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) { + 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(dev, 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) + nvme_show_status(err); + dev_close(dev); + return err; +} + +static int internal_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + 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 nvme_dev *dev; + int err; + + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) { + 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(dev_fd(dev), cfg.output_file, + !cfg.prev_log); + if (err < 0) + fprintf(stderr, "%s: couldn't get fw log\n", __func__); + if (err > 0) + nvme_show_status(err); + + dev_close(dev); + return err; +} + +static int clear_correctable_errors(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + 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; + struct nvme_dev *dev; + __u32 result; + int err; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) { + fprintf(stderr, "%s: failed to parse arguments\n", __func__); + return -EINVAL; + } + + /* Check device supported */ + err = nvme_get_sct_status(dev_fd(dev), MASK_0 | MASK_1); + if (err) + goto end; + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = feature_id, + .nsid = namespace_id, + .cdw11 = value, + .cdw12 = cdw12, + .save = save, + .uuidx = 0, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_set_features(&args); + if (err) + fprintf(stderr, "%s: couldn't clear PCIe correctable errors\n", + __func__); +end: + if (err > 0) + nvme_show_status(err); + dev_close(dev); + return err; +} diff --git a/plugins/toshiba/toshiba-nvme.h b/plugins/toshiba/toshiba-nvme.h new file mode 100644 index 0000000..6208f5d --- /dev/null +++ b/plugins/toshiba/toshiba-nvme.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#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", NVME_VERSION), + 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..547fbf4 --- /dev/null +++ b/plugins/transcend/transcend-nvme.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> + +#include "nvme.h" +#include "libnvme.h" +#include "plugin.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 percent_used = 0, healthvalue = 0; + struct nvme_dev *dev; + int result; + + OPT_ARGS(opts) = { + OPT_END() + }; + + result = parse_and_open(&dev, argc, argv, desc, opts); + if (result) { + printf("\nDevice not found\n"); + return -1; + } + result = nvme_get_log_smart(dev_fd(dev), 0xffffffff, false, &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); + } + } + dev_close(dev); + return result; +} + +static int getBadblock(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + + char *desc = "Get nvme bad block number."; + struct nvme_dev *dev; + int result; + + OPT_ARGS(opts) = { + OPT_END() + }; + + result = parse_and_open(&dev, argc, argv, desc, opts); + if (result) { + 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(dev_fd(dev), &nvmecmd, NULL); + if (!result) { + int badblock = data[0]; + + printf("Transcend NVME badblock count: %d\n", badblock); + } + dev_close(dev); + return result; +} diff --git a/plugins/transcend/transcend-nvme.h b/plugins/transcend/transcend-nvme.h new file mode 100644 index 0000000..9c89883 --- /dev/null +++ b/plugins/transcend/transcend-nvme.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#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", NVME_VERSION), + 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..0ba4b15 --- /dev/null +++ b/plugins/virtium/virtium-nvme.c @@ -0,0 +1,1051 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#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 "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "util/types.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 time_stamp; + struct nvme_id_ctrl raw_ctrl; + struct nvme_firmware_slot raw_fw; +}; + +struct vtview_smart_log_entry { + char path[256]; + long 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 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; + __u8 lba_index; + + nvme_id_ns_flbas_to_lbaf_inuse(smart->raw_ns.flbas, &lba_index); + + curlocale = setlocale(LC_ALL, NULL); + templocale = strdup(curlocale); + + if (!templocale) + printf("Cannot malloc buffer\n"); + + setlocale(LC_ALL, "C"); + + unsigned long long lba = 1ULL << smart->raw_ns.lbaf[lba_index].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;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.data_units_read))); + strcat(text, tempbuff); + snprintf(tempbuff, sizeof(tempbuff), "Data_Units_Written;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.data_units_written))); + strcat(text, tempbuff); + snprintf(tempbuff, sizeof(tempbuff), "Host_Read_Commands;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.host_reads))); + strcat(text, tempbuff); + snprintf(tempbuff, sizeof(tempbuff), "Host_Write_Commands;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.host_writes))); + strcat(text, tempbuff); + snprintf(tempbuff, sizeof(tempbuff), "Controller_Busy_Time;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.ctrl_busy_time))); + strcat(text, tempbuff); + snprintf(tempbuff, sizeof(tempbuff), "Power_Cycles;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.power_cycles))); + strcat(text, tempbuff); + snprintf(tempbuff, sizeof(tempbuff), "Power_On_Hours;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.power_on_hours))); + strcat(text, tempbuff); + snprintf(tempbuff, sizeof(tempbuff), "Unsafe_Shutdowns;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.unsafe_shutdowns))); + strcat(text, tempbuff); + snprintf(tempbuff, sizeof(tempbuff), "Media_Errors;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.media_errors))); + strcat(text, tempbuff); + snprintf(tempbuff, sizeof(tempbuff), "Num_Err_Log_Entries;%s;", uint128_t_to_string(le128_to_cpu(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 (!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 (!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) + return; + + i = size - 1; + while (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; + const char *filename; + int ret = 0; + unsigned int nsid = 0; + + memset(smart.path, 0, sizeof(smart.path)); + strncpy(smart.path, path, sizeof(smart.path) - 1); + if (!cfg->output_file) + filename = vt_default_log_file_name; + else + filename = cfg->output_file; + + smart.time_stamp = time(NULL); + ret = nvme_get_nsid(fd, &nsid); + + if (ret < 0) { + printf("Cannot read namespace-id\n"); + return -1; + } + + ret = nvme_identify_ns(fd, nsid, &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_get_log_smart(fd, NVME_NSID_ALL, false, &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; + const char *filename; + int ret = 0; + + vt_initialize_header_buffer(&header); + if (strlen(path) > sizeof(header.path)) { + printf("filename too long\n"); + errno = EINVAL; + return -1; + } + strcpy(header.path, path); + + if (!cfg->test_name) { + strcpy(header.test_name, DEFAULT_TEST_NAME); + } else { + if (strlen(cfg->test_name) > sizeof(header.test_name)) { + printf("test name too long\n"); + errno = EINVAL; + return -1; + } + strcpy(header.test_name, cfg->test_name); + } + + if (!cfg->output_file) + filename = vt_default_log_file_name; + else + 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_get_log_fw_slot(fd, false, &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].ips; + 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].apws; + 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 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)) || ((i + 1) == pbuffsize)) { + printf(" "); + if (!((i + 1) % 32)) { + 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)) + printf(" "); + + for (j = ((i + 1) % 32); j < 32; j++) { + printf(" "); + if (!((j + 1) % 8)) + 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 *ICSVSCCtable[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->icsvscc; + 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, ICSVSCCtable, 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 ret, err = 0; + long total_time = 0; + long freq_time = 0; + long cur_time = 0; + long remain_time = 0; + long start_time = 0; + long 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 nvme_dev *dev; + + 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) { + if (strlen(argv[1]) > sizeof(path) - 1) { + printf("Filename too long\n"); + return -1; + } + strcpy(path, argv[1]); + } + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) { + printf("Error parse and open (err = %d)\n", err); + return err; + } + + 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(dev_fd(dev), path, &cfg); + if (ret) { + err = EINVAL; + dev_close(dev); + 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) + 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(dev_fd(dev), 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); + } + + dev_close(dev); + return err; +} + +static int vt_show_identify(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int ret, err = 0; + struct nvme_id_ctrl ctrl; + struct nvme_dev *dev; + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) { + printf("Error parse and open (err = %d)\n", err); + return err; + } + + ret = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (ret) { + printf("Cannot read identify device\n"); + dev_close(dev); + return -1; + } + + vt_process_string(ctrl.sn, sizeof(ctrl.sn)); + vt_process_string(ctrl.mn, sizeof(ctrl.mn)); + vt_parse_detail_identify(&ctrl); + + dev_close(dev); + return err; +} diff --git a/plugins/virtium/virtium-nvme.h b/plugins/virtium/virtium-nvme.h new file mode 100644 index 0000000..0a04a35 --- /dev/null +++ b/plugins/virtium/virtium-nvme.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#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", NVME_VERSION), + 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..8cbcf2e --- /dev/null +++ b/plugins/wdc/wdc-nvme.c @@ -0,0 +1,12486 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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> + * Brandon Paupore <brandon.paupore@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 "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "util/cleanup.h" +#include "util/types.h" +#include "nvme-print.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 + +/* Enclosure */ +#define WDC_OPENFLEX_MI_DEVICE_MODEL "OpenFlex" +#define WDC_RESULT_MORE_DATA 0x80000000 +#define WDC_RESULT_NOT_AVAILABLE 0x7FFFFFFF + +/* 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_ZN540_DEV_ID 0x2600 +#define WDC_NVME_SN540_DEV_ID 0x2610 +#define WDC_NVME_SN650_DEV_ID 0x2700 +#define WDC_NVME_SN650_DEV_ID_1 0x2701 +#define WDC_NVME_SN650_DEV_ID_2 0x2702 +#define WDC_NVME_SN650_DEV_ID_3 0x2720 +#define WDC_NVME_SN650_DEV_ID_4 0x2721 +#define WDC_NVME_SN655_DEV_ID 0x2722 +#define WDC_NVME_SN860_DEV_ID 0x2730 +#define WDC_NVME_SN660_DEV_ID 0x2704 +#define WDC_NVME_SN560_DEV_ID_1 0x2712 +#define WDC_NVME_SN560_DEV_ID_2 0x2713 +#define WDC_NVME_SN560_DEV_ID_3 0x2714 +#define WDC_NVME_SN861_DEV_ID 0x2750 +#define WDC_NVME_SN861_DEV_ID_1 0x2751 + +/* This id's are no longer supported, delete ?? */ +#define WDC_NVME_SN550_DEV_ID 0x2708 + +#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_SN530_DEV_ID_1 0x5007 +#define WDC_NVME_SN530_DEV_ID_2 0x5008 +#define WDC_NVME_SN530_DEV_ID_3 0x5009 +#define WDC_NVME_SN530_DEV_ID_4 0x500b +#define WDC_NVME_SN530_DEV_ID_5 0x501d + +#define WDC_NVME_SN350_DEV_ID 0x5019 + +#define WDC_NVME_SN570_DEV_ID 0x501A + +#define WDC_NVME_SN850X_DEV_ID 0x5030 + +#define WDC_NVME_SN5000_DEV_ID_1 0x5034 +#define WDC_NVME_SN5000_DEV_ID_2 0x5035 +#define WDC_NVME_SN5000_DEV_ID_3 0x5036 +#define WDC_NVME_SN5000_DEV_ID_4 0x504A + +#define WDC_NVME_SN7000S_DEV_ID_1 0x5039 + +#define WDC_NVME_SN7150_DEV_ID_1 0x503b +#define WDC_NVME_SN7150_DEV_ID_2 0x503c +#define WDC_NVME_SN7150_DEV_ID_3 0x503d +#define WDC_NVME_SN7150_DEV_ID_4 0x503e +#define WDC_NVME_SN7150_DEV_ID_5 0x503f + +#define WDC_NVME_SN7100_DEV_ID_1 0x5043 +#define WDC_NVME_SN7100_DEV_ID_2 0x5044 +#define WDC_NVME_SN7100_DEV_ID_3 0x5045 + +#define WDC_NVME_SN8000S_DEV_ID 0x5049 + +#define WDC_NVME_SN720_DEV_ID 0x5002 +#define WDC_NVME_SN730_DEV_ID 0x5006 +#define WDC_NVME_SN740_DEV_ID 0x5015 +#define WDC_NVME_SN740_DEV_ID_1 0x5016 +#define WDC_NVME_SN740_DEV_ID_2 0x5017 +#define WDC_NVME_SN740_DEV_ID_3 0x5025 +#define WDC_NVME_SN340_DEV_ID 0x500d +#define WDC_NVME_ZN350_DEV_ID 0x5010 +#define WDC_NVME_ZN350_DEV_ID_1 0x5018 +#define WDC_NVME_SN810_DEV_ID 0x5011 +#define WDC_NVME_SN820CL_DEV_ID 0x5037 + +#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_C0_LOG_PAGE 0x0000000000100000 +#define WDC_DRIVE_CAP_TEMP_STATS 0x0000000000200000 +#define WDC_DRIVE_CAP_VUC_CLEAR_PCIE 0x0000000000400000 +#define WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE 0x0000000000800000 +#define WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2 0x0000000001000000 +#define WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY 0x0000000002000000 +#define WDC_DRIVE_CAP_CLOUD_SSD_VERSION 0x0000000004000000 +#define WDC_DRIVE_CAP_PCIE_STATS 0x0000000008000000 +#define WDC_DRIVE_CAP_HW_REV_LOG_PAGE 0x0000000010000000 +#define WDC_DRIVE_CAP_C3_LOG_PAGE 0x0000000020000000 +#define WDC_DRIVE_CAP_CLOUD_BOOT_SSD_VERSION 0x0000000040000000 +#define WDC_DRIVE_CAP_CLOUD_LOG_PAGE 0x0000000080000000 + +#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_DUI 0x0000000800000000 +#define WDC_DRIVE_CAP_PURGE 0x0000001000000000 +#define WDC_DRIVE_CAP_OCP_C1_LOG_PAGE 0x0000002000000000 +#define WDC_DRIVE_CAP_OCP_C4_LOG_PAGE 0x0000004000000000 +#define WDC_DRIVE_CAP_OCP_C5_LOG_PAGE 0x0000008000000000 +#define WDC_DRIVE_CAP_DEVICE_WAF 0x0000010000000000 +#define WDC_DRIVE_CAP_SET_LATENCY_MONITOR 0x0000020000000000 + +#define WDC_DRIVE_CAP_SMART_LOG_MASK (WDC_DRIVE_CAP_C0_LOG_PAGE | \ + WDC_DRIVE_CAP_C1_LOG_PAGE | \ + WDC_DRIVE_CAP_CA_LOG_PAGE | \ + WDC_DRIVE_CAP_D0_LOG_PAGE) +#define WDC_DRIVE_CAP_CLEAR_PCIE_MASK (WDC_DRIVE_CAP_CLEAR_PCIE | \ + WDC_DRIVE_CAP_VUC_CLEAR_PCIE | \ + WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE) +#define WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_MASK (WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY | \ + WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2) +#define WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY_MASK (WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY | \ + WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY) +#define WDC_DRIVE_CAP_INTERNAL_LOG_MASK (WDC_DRIVE_CAP_INTERNAL_LOG | \ + WDC_DRIVE_CAP_DUI | \ + WDC_DRIVE_CAP_DUI_DATA | \ + WDC_SN730B_CAP_VUC_LOG) + +/* SN730 Get Log Capabilities */ +#define SN730_NVME_GET_LOG_OPCODE 0xc2 +#define SN730_GET_FULL_LOG_LENGTH 0x00080009 +#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_BD 0x1009 + +#define WDC_CUSTOMER_ID_0x1005 0x1005 + +#define WDC_CUSTOMER_ID_0x1004 0x1004 +#define WDC_CUSTOMER_ID_0x1008 0x1008 +#define WDC_CUSTOMER_ID_0x1304 0x1304 +#define WDC_INVALID_CUSTOMER_ID -1 + +#define WDC_ALL_PAGE_MASK 0xFFFF +#define WDC_C0_PAGE_MASK 0x0001 +#define WDC_C1_PAGE_MASK 0x0002 +#define WDC_CA_PAGE_MASK 0x0004 +#define WDC_D0_PAGE_MASK 0x0008 + +/* 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 + +/* VS PCIE Stats */ +#define WDC_NVME_PCIE_STATS_OPCODE 0xD1 + +/* 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 +#define WDC_NVME_SN730_SECTOR_SIZE 512 + +/* 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 +#define WDC_NVME_CLEAR_FW_ACT_HIST_VU_FID 0xC1 + +/* 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_ID 0xC2 +#define WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID_C8 0xC8 +#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 +/* Added 4 padding bytes to resolve build warning messages */ +#define WDC_BD_CA_LOG_BUF_LEN 0xA0 + +/* C0 EOL Status Log Page */ +#define WDC_NVME_GET_EOL_STATUS_LOG_OPCODE 0xC0 +#define WDC_NVME_EOL_STATUS_LOG_LEN 0x200 +#define WDC_NVME_SMART_CLOUD_ATTR_LEN 0x200 + +/* C0 SMART Cloud Attributes Log Page*/ +#define WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID 0xC0 + +/* 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 + +/* C2 - FW Activation History Log Page */ +#define WDC_NVME_GET_FW_ACT_HISTORY_C2_LOG_ID 0xC2 +#define WDC_FW_ACT_HISTORY_C2_LOG_BUF_LEN 0x1000 +#define WDC_MAX_NUM_ACT_HIST_ENTRIES 20 +#define WDC_C2_GUID_LENGTH 16 + +/* C3 Latency Monitor Log Page */ +#define WDC_LATENCY_MON_LOG_BUF_LEN 0x200 +#define WDC_LATENCY_MON_LOG_ID 0xC3 +#define WDC_LATENCY_MON_VERSION 0x0001 + +#define WDC_C3_GUID_LENGTH 16 +static __u8 wdc_lat_mon_guid[WDC_C3_GUID_LENGTH] = { + 0x92, 0x7a, 0xc0, 0x8c, 0xd0, 0x84, 0x6c, 0x9c, + 0x70, 0x43, 0xe6, 0xd4, 0x58, 0x5e, 0xd4, 0x85 +}; + +/* 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_C1 0xC1 +#define WDC_LOG_ID_C2 WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID +#define WDC_LOG_ID_C3 0xC3 +#define WDC_LOG_ID_C4 0xC4 +#define WDC_LOG_ID_C5 0xC5 +#define WDC_LOG_ID_C6 0xC6 +#define WDC_LOG_ID_C8 WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID_C8 +#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_D1 0xD1 +#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 +#define WDC_NVME_CLEAR_PCIE_CORR_OPCODE_VUC 0xD2 +#define WDC_NVME_CLEAR_PCIE_CORR_FEATURE_ID 0xC3 +/* 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_NVME_ADMIN_ENC_MGMT_SND 0xC9 +#define WDC_NVME_ADMIN_ENC_MGMT_RCV 0xCA + +#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 + +#define NVME_ID_CTRL_MODEL_NUMBER_SIZE 40 +#define NVME_ID_CTRL_SERIAL_NUMBER_SIZE 20 + +/* Enclosure log */ +#define WDC_NVME_ENC_LOG_SIZE_CHUNK 0x1000 +#define WDC_NVME_ENC_NIC_LOG_SIZE 0x400000 + +/* Enclosure nic crash dump get-log id */ +#define WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_1 0xD1 +#define WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_2 0xD2 +#define WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_3 0xD3 +#define WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_4 0xD4 +#define WDC_ENC_CRASH_DUMP_ID 0xE4 +#define WDC_ENC_LOG_DUMP_ID 0xE2 + +/* OCP Log Page Directory Data Structure */ +#define BYTE_TO_BIT(byte) ((byte) * 8) + +/* Set latency monitor feature */ +#define NVME_FEAT_OCP_LATENCY_MONITOR 0xC5 + +enum _NVME_FEATURES_SELECT { + FS_CURRENT = 0, + FS_DEFAULT = 1, + FS_SAVED = 2, + FS_SUPPORTED_CAPBILITIES = 3 +}; + +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 +}; + +/* WDC UUID value */ +const uint8_t WDC_UUID[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0xb9, 0x8c, 0x52, 0x0c, 0x4c, + 0x5a, 0x15, 0xab, 0xe6, 0x33, 0x29, 0x9a, 0x70, 0xdf, 0xd0 +}; + +/* WDC_UUID value for SN640_3 devices */ +const uint8_t WDC_UUID_SN640_3[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22 +}; + +/* UUID field with value of 0 indicates end of UUID List*/ +const uint8_t UUID_END[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +enum WDC_DRIVE_ESSENTIAL_TYPE { + 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, +}; + +#define WDC_C0_GUID_LENGTH 16 +#define WDC_SCA_V1_NAND_STATS 0x1 +#define WDC_SCA_V1_ALL 0xF +enum { + SCAO_V1_PMUWT = 0, /* Physical media units written TLC */ + SCAO_V1_PMUWS = 16, /* Physical media units written SLC */ + SCAO_V1_BUNBN = 32, /* Bad user nand blocks normalized */ + SCAO_V1_BUNBR = 34, /* Bad user nand blocks raw */ + SCAO_V1_XRC = 40, /* XOR recovery count */ + SCAO_V1_UREC = 48, /* Uncorrectable read error count */ + SCAO_V1_EECE = 56, /* End to end corrected errors */ + SCAO_V1_EEDE = 64, /* End to end detected errors */ + SCAO_V1_EEUE = 72, /* End to end uncorrected errors */ + SCAO_V1_SDPU = 80, /* System data percent used */ + SCAO_V1_MNUDEC = 84, /* Min User data erase counts (TLC) */ + SCAO_V1_MXUDEC = 92, /* Max User data erase counts (TLC) */ + SCAO_V1_AVUDEC = 100, /* Average User data erase counts (TLC) */ + SCAO_V1_MNEC = 108, /* Min Erase counts (SLC) */ + SCAO_V1_MXEC = 116, /* Max Erase counts (SLC) */ + SCAO_V1_AVEC = 124, /* Average Erase counts (SLC) */ + SCAO_V1_PFCN = 132, /* Program fail count normalized */ + SCAO_V1_PFCR = 134, /* Program fail count raw */ + SCAO_V1_EFCN = 140, /* Erase fail count normalized */ + SCAO_V1_EFCR = 142, /* Erase fail count raw */ + SCAO_V1_PCEC = 148, /* PCIe correctable error count */ + SCAO_V1_PFBU = 156, /* Percent free blocks (User) */ + SCAO_V1_SVN = 160, /* Security Version Number */ + SCAO_V1_PFBS = 168, /* Percent free blocks (System) */ + SCAO_V1_DCC = 172, /* Deallocate Commands Completed */ + SCAO_V1_TNU = 188, /* Total Namespace Utilization */ + SCAO_V1_FCC = 196, /* Format NVM Commands Completed */ + SCAO_V1_BBPG = 198, /* Background Back-Pressure Gauge */ + SCAO_V1_SEEC = 202, /* Soft ECC error count */ + SCAO_V1_RFSC = 210, /* Refresh count */ + SCAO_V1_BSNBN = 218, /* Bad system nand blocks normalized */ + SCAO_V1_BSNBR = 220, /* Bad system nand blocks raw */ + SCAO_V1_EEST = 226, /* Endurance estimate */ + SCAO_V1_TTC = 242, /* Thermal throttling count */ + SCAO_V1_UIO = 244, /* Unaligned I/O */ + SCAO_V1_PMUR = 252, /* Physical media units read */ + SCAO_V1_RTOC = 268, /* Read command timeout count */ + SCAO_V1_WTOC = 272, /* Write command timeout count */ + SCAO_V1_TTOC = 276, /* Trim command timeout count */ + SCAO_V1_PLRC = 284, /* PCIe Link Retraining Count */ + SCAO_V1_PSCC = 292, /* Power State Change Count */ + SCAO_V1_MAVF = 300, /* Boot SSD major version field */ + SCAO_V1_MIVF = 302, /* Boot SSD minor version field */ + SCAO_V1_PVF = 304, /* Boot SSD point version field */ + SCAO_V1_EVF = 306, /* Boot SSD errata version field */ + SCAO_V1_FTLUS = 308, /* FTL Unit Size */ + SCAO_V1_TCGOS = 312, /* TCG Ownership Status */ + + SCAO_V1_LPV = 494, /* Log page version - 0x0001 */ + SCAO_V1_LPG = 496, /* Log page GUID */ +}; + +static __u8 ext_smart_guid[WDC_C0_GUID_LENGTH] = { + 0x65, 0x43, 0x88, 0x78, 0xAC, 0xD8, 0x78, 0xA1, + 0x66, 0x42, 0x1E, 0x0F, 0x92, 0xD7, 0x6D, 0xC4 +}; + +struct __packed wdc_nvme_ext_smart_log { + __u8 ext_smart_pmuwt[16]; /* 000 Physical media units written TLC */ + __u8 ext_smart_pmuws[16]; /* 016 Physical media units written SLC */ + __u8 ext_smart_bunbc[8]; /* 032 Bad user nand block count */ + __u64 ext_smart_xrc; /* 040 XOR recovery count */ + __u64 ext_smart_urec; /* 048 Uncorrectable read error count */ + __u64 ext_smart_eece; /* 056 End to end corrected errors */ + __u64 ext_smart_eede; /* 064 End to end detected errors */ + __u64 ext_smart_eeue; /* 072 End to end uncorrected errors */ + __u8 ext_smart_sdpu; /* 080 System data percent used */ + __u8 ext_smart_rsvd1[3]; /* 081 reserved */ + __u64 ext_smart_mnudec; /* 084 Min User data erase counts (TLC) */ + __u64 ext_smart_mxudec; /* 092 Max User data erase counts (TLC) */ + __u64 ext_smart_avudec; /* 100 Average User data erase counts (TLC) */ + __u64 ext_smart_mnec; /* 108 Min Erase counts (SLC) */ + __u64 ext_smart_mxec; /* 116 Max Erase counts (SLC) */ + __u64 ext_smart_avec; /* 124 Average Erase counts (SLC) */ + __u8 ext_smart_pfc[8]; /* 132 Program fail count */ + __u8 ext_smart_efc[8]; /* 140 Erase fail count */ + __u64 ext_smart_pcec; /* 148 PCIe correctable error count */ + __u8 ext_smart_pfbu; /* 156 Percent free blocks (User) */ + __u8 ext_smart_rsvd2[3]; /* 157 reserved */ + __u64 ext_smart_svn; /* 160 Security Version Number */ + __u8 ext_smart_pfbs; /* 168 Percent free blocks (System) */ + __u8 ext_smart_rsvd3[3]; /* 169 reserved */ + __u8 ext_smart_dcc[16]; /* 172 Deallocate Commands Completed */ + __u64 ext_smart_tnu; /* 188 Total Namespace Utilization */ + __u16 ext_smart_fcc; /* 196 Format NVM Commands Completed */ + __u8 ext_smart_bbpg; /* 198 Background Back-Pressure Gauge */ + __u8 ext_smart_rsvd4[3]; /* 199 reserved */ + __u64 ext_smart_seec; /* 202 Soft ECC error count */ + __u64 ext_smart_rfsc; /* 210 Refresh count */ + __u8 ext_smart_bsnbc[8]; /* 218 Bad system nand block count */ + __u8 ext_smart_eest[16]; /* 226 Endurance estimate */ + __u16 ext_smart_ttc; /* 242 Thermal throttling count */ + __u64 ext_smart_uio; /* 244 Unaligned I/O */ + __u8 ext_smart_pmur[16]; /* 252 Physical media units read */ + __u32 ext_smart_rtoc; /* 268 Read command timeout count */ + __u32 ext_smart_wtoc; /* 272 Write command timeout count */ + __u32 ext_smart_ttoc; /* 276 Trim command timeout count */ + __u8 ext_smart_rsvd5[4]; /* 280 reserved */ + __u64 ext_smart_plrc; /* 284 PCIe Link Retraining Count */ + __u64 ext_smart_pscc; /* 292 Power State Change Count */ + __u16 ext_smart_maj; /* 300 Boot SSD major version field */ + __u16 ext_smart_min; /* 302 Boot SSD minor version field */ + __u16 ext_smart_pt; /* 304 Boot SSD point version field */ + __u16 ext_smart_err; /* 306 Boot SSD errata version field */ + __u32 ext_smart_ftlus; /* 308 FTL Unit Size */ + __u32 ext_smart_tcgos; /* 312 TCG Ownership Status */ + __u8 ext_smart_rsvd6[178]; /* 316 reserved */ + __u16 ext_smart_lpv; /* 494 Log page version - 0x0001 */ + __u8 ext_smart_lpg[16]; /* 496 Log page GUID */ +}; + +enum { + SCAO_PMUW = 0, /* Physical media units written */ + SCAO_PMUR = 16, /* Physical media units read */ + SCAO_BUNBR = 32, /* Bad user nand blocks raw */ + SCAO_BUNBN = 38, /* Bad user nand blocks normalized */ + SCAO_BSNBR = 40, /* Bad system nand blocks raw */ + SCAO_BSNBN = 46, /* Bad system nand blocks normalized */ + SCAO_XRC = 48, /* XOR recovery count */ + SCAO_UREC = 56, /* Uncorrectable read error count */ + SCAO_SEEC = 64, /* Soft ecc error count */ + SCAO_EECE = 72, /* End to end corrected errors */ + SCAO_EEDC = 76, /* End to end detected errors */ + SCAO_SDPU = 80, /* System data percent used */ + SCAO_RFSC = 81, /* Refresh counts */ + SCAO_MXUDEC = 88, /* Max User data erase counts */ + SCAO_MNUDEC = 92, /* Min User data erase counts */ + SCAO_NTTE = 96, /* Number of Thermal throttling events */ + SCAO_CTS = 97, /* Current throttling status */ + SCAO_EVF = 98, /* Errata Version Field */ + SCAO_PVF = 99, /* Point Version Field */ + SCAO_MIVF = 101, /* Minor Version Field */ + SCAO_MAVF = 103, /* Major Version Field */ + SCAO_PCEC = 104, /* PCIe correctable error count */ + SCAO_ICS = 112, /* Incomplete shutdowns */ + SCAO_PFB = 120, /* Percent free blocks */ + SCAO_CPH = 128, /* Capacitor health */ + SCAO_NEV = 130, /* NVMe Errata Version */ + SCAO_UIO = 136, /* Unaligned I/O */ + SCAO_SVN = 144, /* Security Version Number */ + SCAO_NUSE = 152, /* NUSE - Namespace utilization */ + SCAO_PSC = 160, /* PLP start count */ + SCAO_EEST = 176, /* Endurance estimate */ + SCAO_PLRC = 192, /* PCIe Link Retraining Count */ + SCAO_PSCC = 200, /* Power State Change Count */ + SCAO_LPV = 494, /* Log page version */ + SCAO_LPG = 496, /* Log page GUID */ +}; + +struct ocp_bad_nand_block_count { + __u64 raw : 48; + __u16 normalized : 16; +}; + +struct ocp_e2e_correction_count { + __u32 detected; + __u32 corrected; +}; + +struct ocp_user_data_erase_count { + __u32 maximum; + __u32 minimum; +}; + +struct ocp_thermal_status { + __u8 num_events; + __u8 current_status; +}; + +struct __packed ocp_dssd_specific_ver { + __u8 errata_ver; + __u16 point_ver; + __u16 minor_ver; + __u8 major_ver; +}; + +struct ocp_cloud_smart_log { + __u8 physical_media_units_written[16]; + __u8 physical_media_units_read[16]; + struct ocp_bad_nand_block_count bad_user_nand_blocks; + struct ocp_bad_nand_block_count bad_system_nand_blocks; + __u64 xor_recovery_count; + __u64 uncorrectable_read_error_count; + __u64 soft_ecc_error_count; + struct ocp_e2e_correction_count e2e_correction_counts; + __u8 system_data_percent_used; + __u64 refresh_counts : 56; + struct ocp_user_data_erase_count user_data_erase_counts; + struct ocp_thermal_status thermal_status; + struct ocp_dssd_specific_ver dssd_specific_ver; + __u64 pcie_correctable_error_count; + __u32 incomplete_shutdowns; + __u8 rsvd116[4]; + __u8 percent_free_blocks; + __u8 rsvd121[7]; + __u16 capacitor_health; + __u8 nvme_errata_ver; + __u8 rsvd131[5]; + __u64 unaligned_io; + __u64 security_version_number; + __u64 total_nuse; + __u8 plp_start_count[16]; + __u8 endurance_estimate[16]; + __u64 pcie_link_retraining_cnt; + __u64 power_state_change_cnt; + __u8 rsvd208[286]; + __u16 log_page_version; + __u8 log_page_guid[16]; +}; + +static __u8 scao_guid[WDC_C0_GUID_LENGTH] = { + 0xC5, 0xAF, 0x10, 0x28, 0xEA, 0xBF, 0xF2, 0xA4, + 0x9C, 0x4F, 0x6F, 0x7C, 0xC9, 0x14, 0xD5, 0xAF +}; + +enum { + EOL_RBC = 76, /* Realloc Block Count */ + EOL_ECCR = 80, /* ECC Rate */ + EOL_WRA = 84, /* Write Amp */ + EOL_PLR = 88, /* Percent Life Remaining */ + EOL_RSVBC = 92, /* Reserved Block Count */ + EOL_PFC = 96, /* Program Fail Count */ + EOL_EFC = 100, /* Erase Fail Count */ + EOL_RRER = 108, /* Raw Read Error Rate */ +}; + +#define WDC_NVME_C6_GUID_LENGTH 16 +#define WDC_NVME_GET_HW_REV_LOG_OPCODE 0xc6 +#define WDC_NVME_HW_REV_LOG_PAGE_LEN 512 + +struct __packed wdc_nvme_hw_rev_log { + __u8 hw_rev_gdr; /* 0 Global Device HW Revision */ + __u8 hw_rev_ar; /* 1 ASIC HW Revision */ + __u8 hw_rev_pbc_mc; /* 2 PCB Manufacturer Code */ + __u8 hw_rev_dram_mc; /* 3 DRAM Manufacturer Code */ + __u8 hw_rev_nand_mc; /* 4 NAND Manufacturer Code */ + __u8 hw_rev_pmic1_mc; /* 5 PMIC 1 Manufacturer Code */ + __u8 hw_rev_pmic2_mc; /* 6 PMIC 2 Manufacturer Code */ + __u8 hw_rev_c1_mc; /* 7 Other Component 1 Manf Code */ + __u8 hw_rev_c2_mc; /* 8 Other Component 2 Manf Code */ + __u8 hw_rev_c3_mc; /* 9 Other Component 3 Manf Code */ + __u8 hw_rev_c4_mc; /* 10 Other Component 4 Manf Code */ + __u8 hw_rev_c5_mc; /* 11 Other Component 5 Manf Code */ + __u8 hw_rev_c6_mc; /* 12 Other Component 6 Manf Code */ + __u8 hw_rev_c7_mc; /* 13 Other Component 7 Manf Code */ + __u8 hw_rev_c8_mc; /* 14 Other Component 8 Manf Code */ + __u8 hw_rev_c9_mc; /* 15 Other Component 9 Manf Code */ + __u8 hw_rev_rsrvd1[48]; /* 16 Reserved 48 bytes */ + __u8 hw_rev_dev_mdi[16]; /* 64 Device Manf Detailed Info */ + __u8 hw_rev_asic_di[16]; /* 80 ASIC Detailed Info */ + __u8 hw_rev_pcb_di[16]; /* 96 PCB Detailed Info */ + __u8 hw_rev_dram_di[16]; /* 112 DRAM Detailed Info */ + __u8 hw_rev_nand_di[16]; /* 128 NAND Detailed Info */ + __u8 hw_rev_pmic1_di[16]; /* 144 PMIC1 Detailed Info */ + __u8 hw_rev_pmic2_di[16]; /* 160 PMIC2 Detailed Info */ + __u8 hw_rev_c1_di[16]; /* 176 Component 1 Detailed Info */ + __u8 hw_rev_c2_di[16]; /* 192 Component 2 Detailed Info */ + __u8 hw_rev_c3_di[16]; /* 208 Component 3 Detailed Info */ + __u8 hw_rev_c4_di[16]; /* 224 Component 4 Detailed Info */ + __u8 hw_rev_c5_di[16]; /* 240 Component 5 Detailed Info */ + __u8 hw_rev_c6_di[16]; /* 256 Component 6 Detailed Info */ + __u8 hw_rev_c7_di[16]; /* 272 Component 7 Detailed Info */ + __u8 hw_rev_c8_di[16]; /* 288 Component 8 Detailed Info */ + __u8 hw_rev_c9_di[16]; /* 304 Component 9 Detailed Info */ + __u8 hw_rev_sn[32]; /* 320 Serial Number */ + __u8 hw_rev_rsrvd2[142]; /* 352 Reserved 143 bytes */ + __u16 hw_rev_version; /* 494 Log Page Version */ + __u8 hw_rev_guid[16]; /* 496 Log Page GUID */ +}; + +static __u8 hw_rev_log_guid[WDC_NVME_C6_GUID_LENGTH] = { + 0xAA, 0xB0, 0x05, 0xF5, 0x13, 0x5E, 0x48, 0x15, + 0xAB, 0x89, 0x05, 0xBA, 0x8B, 0xE2, 0xBF, 0x3C +}; + +struct __packed WDC_DE_VU_FILE_META_DATA { + __u8 fileName[WDC_DE_FILE_NAME_SIZE]; + __u16 fileID; + __u64 fileSize; +}; + +struct WDC_DRIVE_ESSENTIALS { + struct __packed WDC_DE_VU_FILE_META_DATA metaData; + enum WDC_DRIVE_ESSENTIAL_TYPE essentialType; +}; + +struct WDC_DE_VU_LOG_DIRECTORY { + struct WDC_DRIVE_ESSENTIALS *logEntry; /* Caller to allocate memory */ + __u32 maxNumLogEntries; /* Caller to input memory allocated */ + __u32 numOfValidLogEntries; /* API will output this value */ +}; + +struct WDC_DE_CSA_FEATURE_ID_LIST { + enum NVME_FEATURE_IDENTIFIERS featureId; + __u8 featureName[WDC_DE_GENERIC_BUFFER_SIZE]; +}; + +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]; +}; + +static struct 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"}, +}; + +enum NVME_VU_DE_LOGPAGE_NAMES { + NVME_DE_LOGPAGE_E3 = 0x01, + NVME_DE_LOGPAGE_C0 = 0x02 +}; + +struct NVME_VU_DE_LOGPAGE_LIST { + enum NVME_VU_DE_LOGPAGE_NAMES logPageName; + __u32 logPageId; + __u32 logPageLen; + char logPageIdStr[5]; +}; + +struct WDC_NVME_DE_VU_LOGPAGES { + enum NVME_VU_DE_LOGPAGE_NAMES vuLogPageReqd; + __u32 numOfVULogPages; +}; + +static struct NVME_VU_DE_LOGPAGE_LIST deVULogPagesList[] = { + { NVME_DE_LOGPAGE_E3, 0xE3, 1072, "0xe3"}, + { NVME_DE_LOGPAGE_C0, 0xC0, 512, "0xc0"} +}; + +enum { + WDC_NVME_ADMIN_VUC_OPCODE_D2 = 0xD2, + WDC_VUC_SUBOPCODE_VS_DRIVE_INFO_D2 = 0x0000010A, + WDC_VUC_SUBOPCODE_LOG_PAGE_DIR_D2 = 0x00000105, +}; + +enum { + NVME_LOG_NS_BASE = 0x80, + NVME_LOG_VS_BASE = 0xC0, +}; + +/*drive_info struct*/ +struct ocp_drive_info { + __u32 hw_revision; + __u32 ftl_unit_size; +}; + +/*get log page directory struct*/ +struct log_page_directory { + __u64 supported_lid_bitmap; + __u64 rsvd; + __u64 supported_ns_lid_bitmap; + __u64 supported_vs_lid_bitmap; +}; + +/*set latency monitor feature */ +struct __packed feature_latency_monitor { + __u16 active_bucket_timer_threshold; + __u8 active_threshold_a; + __u8 active_threshold_b; + __u8 active_threshold_c; + __u8 active_threshold_d; + __u16 active_latency_config; + __u8 active_latency_minimum_window; + __u16 debug_log_trigger_enable; + __u8 discard_debug_log; + __u8 latency_monitor_feature_enable; + __u8 reserved[4083]; +}; + +static int wdc_get_serial_name(struct nvme_dev *dev, char *file, size_t len, const char *suffix); +static int wdc_create_log_file(char *file, __u8 *drive_log_data, __u32 drive_log_length); +static int wdc_do_clear_dump(struct nvme_dev *dev, __u8 opcode, __u32 cdw12); +static int wdc_do_dump(struct nvme_dev *dev, __u32 opcode, __u32 data_len, __u32 cdw12, char *file, + __u32 xfer_size); +static int wdc_do_crash_dump(struct nvme_dev *dev, char *file, int type); +static int wdc_crash_dump(struct nvme_dev *dev, 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(struct nvme_dev *dev, 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(nvme_root_t r, struct nvme_dev *dev, __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(nvme_root_t r, struct nvme_dev *dev, 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(struct nvme_dev *dev, 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(struct nvme_dev *dev, __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(struct nvme_dev *dev, char *file, int log_id); +static int wdc_save_reason_id(struct nvme_dev *dev, __u8 *rsn_ident, int size); +static int wdc_clear_reason_id(struct nvme_dev *dev); +static int wdc_log_page_directory(int argc, char **argv, struct command *command, + struct plugin *plugin); +static int wdc_do_drive_info(struct nvme_dev *dev, __u32 *result); +static int wdc_vs_drive_info(int argc, char **argv, struct command *command, struct plugin *plugin); +static int wdc_vs_temperature_stats(int argc, char **argv, struct command *command, + struct plugin *plugin); +static __u64 wdc_get_enc_drive_capabilities(nvme_root_t r, struct nvme_dev *dev); +static int wdc_enc_get_nic_log(struct nvme_dev *dev, __u8 log_id, __u32 xfer_size, __u32 data_len, + FILE *out); +static int wdc_enc_submit_move_data(struct nvme_dev *dev, char *cmd, int len, int xfer_size, + FILE *out, int data_id, int cdw14, int cdw15); +static bool get_dev_mgment_cbs_data(nvme_root_t r, struct nvme_dev *dev, __u8 log_id, + void **cbs_data); +static __u32 wdc_get_fw_cust_id(nvme_root_t r, struct nvme_dev *dev); + +/* 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 reserved; + __le32 section_size; +}; + +/* DUI log header V2 */ +struct __packed wdc_dui_log_section_v2 { + __le16 section_type; + __le16 data_area_id; + __le64 section_size; +}; + +/* DUI log header V4 */ +struct wdc_dui_log_section_v4 { + __le16 section_type; + __u8 data_area_id; + __u8 reserved; + __le32 section_size_sectors; +}; + +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 __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 __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]; +}; + +struct __packed wdc_dui_log_hdr_v4 { + __u8 telemetry_hdr[512]; + __u8 hdr_version; + __u8 product_id; + __le16 section_count; + __le32 log_size_sectors; + struct wdc_dui_log_section_v4 log_section[WDC_NVME_DUI_MAX_SECTION]; + __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 __packed wdc_bd_ca_log_format { + __u8 field_id; + __u8 reserved1[2]; + __u8 normalized_value; + __u8 raw_value[8]; +}; + +#define LATENCY_LOG_BUCKET_READ 3 +#define LATENCY_LOG_BUCKET_WRITE 2 +#define LATENCY_LOG_BUCKET_TRIM 1 +#define LATENCY_LOG_BUCKET_RESERVED 0 + +#define LATENCY_LOG_MEASURED_LAT_READ 2 +#define LATENCY_LOG_MEASURED_LAT_WRITE 1 +#define LATENCY_LOG_MEASURED_LAT_TRIM 0 + +struct __packed wdc_ssd_latency_monitor_log { + __u8 feature_status; /* 0x00 */ + __u8 rsvd1; /* 0x01 */ + __le16 active_bucket_timer; /* 0x02 */ + __le16 active_bucket_timer_threshold; /* 0x04 */ + __u8 active_threshold_a; /* 0x06 */ + __u8 active_threshold_b; /* 0x07 */ + __u8 active_threshold_c; /* 0x08 */ + __u8 active_threshold_d; /* 0x09 */ + __le16 active_latency_config; /* 0x0A */ + __u8 active_latency_min_window; /* 0x0C */ + __u8 rsvd2[0x13]; /* 0x0D */ + + __le32 active_bucket_counter[4][4]; /* 0x20 - 0x5F */ + __le64 active_latency_timestamp[4][3]; /* 0x60 - 0xBF */ + __le16 active_measured_latency[4][3]; /* 0xC0 - 0xD7 */ + __le16 active_latency_stamp_units; /* 0xD8 */ + __u8 rsvd3[0x16]; /* 0xDA */ + + __le32 static_bucket_counter[4][4] ; /* 0xF0 - 0x12F */ + __le64 static_latency_timestamp[4][3]; /* 0x130 - 0x18F */ + __le16 static_measured_latency[4][3]; /* 0x190 - 0x1A7 */ + __le16 static_latency_stamp_units; /* 0x1A8 */ + __u8 rsvd4[0x16]; /* 0x1AA */ + + __le16 debug_log_trigger_enable; /* 0x1C0 */ + __le16 debug_log_measured_latency; /* 0x1C2 */ + __le64 debug_log_latency_stamp; /* 0x1C4 */ + __le16 debug_log_ptr; /* 0x1CC */ + __le16 debug_log_counter_trigger; /* 0x1CE */ + __u8 debug_log_stamp_units; /* 0x1D0 */ + __u8 rsvd5[0x1D]; /* 0x1D1 */ + + __le16 log_page_version; /* 0x1EE */ + __u8 log_page_guid[0x10]; /* 0x1F0 */ +}; + +struct __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 __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 */ +}; + +#define WDC_OCP_C1_GUID_LENGTH 16 +#define WDC_ERROR_REC_LOG_BUF_LEN 512 +#define WDC_ERROR_REC_LOG_ID 0xC1 +#define WDC_ERROR_REC_LOG_VERSION1 0001 +#define WDC_ERROR_REC_LOG_VERSION2 0002 + +struct __packed wdc_ocp_c1_error_recovery_log { + __le16 panic_reset_wait_time; /* 000 - Panic Reset Wait Time */ + __u8 panic_reset_action; /* 002 - Panic Reset Action */ + __u8 dev_recovery_action1; /* 003 - Device Recovery Action 1 */ + __le64 panic_id; /* 004 - Panic ID */ + __le32 dev_capabilities; /* 012 - Device Capabilities */ + __u8 vs_recovery_opc; /* 016 - Vendor Specific Recovery Opcode */ + __u8 rsvd1[3]; /* 017 - 3 Reserved Bytes */ + __le32 vs_cmd_cdw12; /* 020 - Vendor Specific Command CDW12 */ + __le32 vs_cmd_cdw13; /* 024 - Vendor Specific Command CDW13 */ + __u8 vs_cmd_to; /* 028 - Vendor Specific Command Timeout V2 */ + __u8 dev_recovery_action2; /* 029 - Device Recovery Action 2 V2 */ + __u8 dev_recovery_action2_to; /* 030 - Device Recovery Action 2 Timeout V2 */ + __u8 rsvd2[463]; /* 031 - 463 Reserved Bytes */ + __le16 log_page_version; /* 494 - Log Page Version */ + __u8 log_page_guid[WDC_OCP_C1_GUID_LENGTH]; /* 496 - Log Page GUID */ +}; + +static __u8 wdc_ocp_c1_guid[WDC_OCP_C1_GUID_LENGTH] = { 0x44, 0xD9, 0x31, 0x21, 0xFE, 0x30, 0x34, 0xAE, + 0xAB, 0x4D, 0xFD, 0x3D, 0xBA, 0x83, 0x19, 0x5A }; + +/* NAND Stats */ +struct __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[442]; + __u16 log_page_version; +}; + +struct __packed wdc_nand_stats_V3 { + __u8 nand_write_tlc[16]; + __u8 nand_write_slc[16]; + __u8 bad_nand_block_count[8]; + __le64 xor_recovery_count; + __le64 uecc_read_error_count; + __u8 ssd_correction_counts[16]; + __u8 percent_life_used; + __le64 user_data_erase_counts[4]; + __u8 program_fail_count[8]; + __u8 erase_fail_count[8]; + __le64 correctable_error_count; + __u8 percent_free_blocks_user; + __le64 security_version_number; + __u8 percent_free_blocks_system; + __u8 trim_completions[25]; + __u8 back_pressure_guage; + __le64 soft_ecc_error_count; + __le64 refresh_count; + __u8 bad_sys_nand_block_count[8]; + __u8 endurance_estimate[16]; + __u8 thermal_throttling_st_ct[2]; + __le64 unaligned_IO; + __u8 physical_media_units[16]; + __u8 reserved[279]; + __u16 log_page_version; +}; + +struct wdc_vs_pcie_stats { + __le64 unsupportedRequestErrorCount; + __le64 ecrcErrorStatusCount; + __le64 malformedTlpStatusCount; + __le64 receiverOverflowStatusCount; + __le64 unexpectedCmpltnStatusCount; + __le64 completeAbortStatusCount; + __le64 cmpltnTimoutStatusCount; + __le64 flowControlErrorStatusCount; + __le64 poisonedTlpStatusCount; + __le64 dLinkPrtclErrorStatusCount; + __le64 advsryNFatalErrStatusCount; + __le64 replayTimerToStatusCount; + __le64 replayNumRolloverStCount; + __le64 badDllpStatusCount; + __le64 badTlpStatusCount; + __le64 receiverErrStatusCount; + __u8 reserved1[384]; +}; + +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]; +}; + +struct __packed wdc_fw_act_history_log_entry_c2 { + __u8 entry_version_num; + __u8 entry_len; + __le16 reserved; + __le16 fw_act_hist_entries; + __le64 timestamp; + __u8 reserved2[8]; + __le64 power_cycle_count; + __le64 previous_fw_version; + __le64 current_fw_version; + __u8 slot_number; + __u8 commit_action_type; + __le16 result; + __u8 reserved3[14]; +}; + +struct __packed wdc_fw_act_history_log_format_c2 { + __u8 log_identifier; + __u8 reserved[3]; + __le32 num_entries; + struct wdc_fw_act_history_log_entry_c2 entry[WDC_MAX_NUM_ACT_HIST_ENTRIES]; + __u8 reserved2[2790]; + __le16 log_page_version; + __u8 log_page_guid[WDC_C2_GUID_LENGTH]; +}; + +#define WDC_OCP_C4_GUID_LENGTH 16 +#define WDC_DEV_CAP_LOG_BUF_LEN 4096 +#define WDC_DEV_CAP_LOG_ID 0xC4 +#define WDC_DEV_CAP_LOG_VERSION 0001 +#define WDC_OCP_C4_NUM_PS_DESCR 127 + +struct __packed wdc_ocp_C4_dev_cap_log { + __le16 num_pcie_ports; /* 0000 - Number of PCI Express Ports */ + __le16 oob_mgmt_support; /* 0002 - OOB Management Interfaces Supported */ + __le16 wrt_zeros_support; /* 0004 - Write Zeros Command Support */ + __le16 sanitize_support; /* 0006 - Sanitize Command Support */ + __le16 dsm_support; /* 0008 - Dataset Management Command Support */ + __le16 wrt_uncor_support; /* 0010 - Write Uncorrectable Command Support */ + __le16 fused_support; /* 0012 - Fused Operation Support */ + __le16 min_dssd_ps; /* 0014 - Minimum Valid DSSD Power State */ + __u8 rsvd1; /* 0016 - Reserved must be cleared to zero */ + __u8 dssd_ps_descr[WDC_OCP_C4_NUM_PS_DESCR];/* 0017 - DSSD Power State Descriptors */ + __u8 rsvd2[3934]; /* 0144 - Reserved must be cleared to zero */ + __le16 log_page_version; /* 4078 - Log Page Version */ + __u8 log_page_guid[WDC_OCP_C4_GUID_LENGTH]; /* 4080 - Log Page GUID */ +}; + +static __u8 wdc_ocp_c4_guid[WDC_OCP_C4_GUID_LENGTH] = { + 0x97, 0x42, 0x05, 0x0D, 0xD1, 0xE1, 0xC9, 0x98, + 0x5D, 0x49, 0x58, 0x4B, 0x91, 0x3C, 0x05, 0xB7 +}; + +#define WDC_OCP_C5_GUID_LENGTH 16 +#define WDC_UNSUPPORTED_REQS_LOG_BUF_LEN 4096 +#define WDC_UNSUPPORTED_REQS_LOG_ID 0xC5 +#define WDC_UNSUPPORTED_REQS_LOG_VERSION 0001 +#define WDC_NUM_UNSUPPORTED_REQ_ENTRIES 253 + +struct __packed wdc_ocp_C5_unsupported_reqs { + __le16 unsupported_count; /* 0000 - Number of Unsupported Requirement IDs */ + __u8 rsvd1[14]; /* 0002 - Reserved must be cleared to zero */ + __u8 unsupported_req_list[WDC_NUM_UNSUPPORTED_REQ_ENTRIES][16]; /* 0016 - Unsupported Requirements List */ + __u8 rsvd2[14]; /* 4064 - Reserved must be cleared to zero */ + __le16 log_page_version; /* 4078 - Log Page Version */ + __u8 log_page_guid[WDC_OCP_C5_GUID_LENGTH]; /* 4080 - Log Page GUID */ +}; + +static __u8 wdc_ocp_c5_guid[WDC_OCP_C5_GUID_LENGTH] = { 0x2F, 0x72, 0x9C, 0x0E, 0x99, 0x23, 0x2C, 0xBB, + 0x63, 0x48, 0x32, 0xD0, 0xB7, 0x98, 0xBB, 0xC7 }; + +#define WDC_REASON_INDEX_MAX 16 +#define WDC_REASON_ID_ENTRY_LEN 128 +#define WDC_REASON_ID_PATH_NAME "/usr/local/nvmecli" + +const char *log_page_name[256] = { + [NVME_LOG_LID_ERROR] = "Error Information", + [NVME_LOG_LID_SMART] = "SMART / Health Information", + [NVME_LOG_LID_FW_SLOT] = "Firmware Slot Information", + [NVME_LOG_LID_CHANGED_NS] = "Changed Namespace List", + [NVME_LOG_LID_CMD_EFFECTS] = "Command Supported and Effects", + [NVME_LOG_LID_TELEMETRY_HOST] = "Telemetry Host-Initiated", + [NVME_LOG_LID_TELEMETRY_CTRL] = "Telemetry Controller-Initiated", + [NVME_LOG_LID_SANITIZE] = "Sanitize Status", + [WDC_LOG_ID_C0] = "Extended SMART Information", + [WDC_LOG_ID_C2] = "Firmware Activation History", + [WDC_LOG_ID_C3] = "Latency Monitor", + [WDC_LOG_ID_C4] = "Device Capabilities", + [WDC_LOG_ID_C5] = "Unsupported Requirements", +}; + +static double safe_div_fp(double numerator, double denominator) +{ + 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 int wdc_get_pci_ids(nvme_root_t r, struct nvme_dev *dev, + uint32_t *device_id, uint32_t *vendor_id) +{ + char vid[256], did[256], id[32]; + nvme_ctrl_t c = NULL; + nvme_ns_t n = NULL; + int fd, ret; + + c = nvme_scan_ctrl(r, dev->name); + if (c) { + snprintf(vid, sizeof(vid), "%s/device/vendor", + nvme_ctrl_get_sysfs_dir(c)); + snprintf(did, sizeof(did), "%s/device/device", + nvme_ctrl_get_sysfs_dir(c)); + nvme_free_ctrl(c); + } else { + n = nvme_scan_namespace(dev->name); + if (!n) { + fprintf(stderr, "Unable to find %s\n", dev->name); + return -1; + } + + snprintf(vid, sizeof(vid), "%s/device/device/vendor", + nvme_ns_get_sysfs_dir(n)); + snprintf(did, sizeof(did), "%s/device/device/device", + nvme_ns_get_sysfs_dir(n)); + nvme_free_ns(n); + } + + fd = open(vid, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "ERROR: WDC: %s : Open vendor file failed\n", __func__); + return -1; + } + + ret = read(fd, id, 32); + close(fd); + + if (ret < 0) { + fprintf(stderr, "%s: Read of pci vendor id failed\n", __func__); + return -1; + } + id[ret < 32 ? ret : 31] = '\0'; + if (id[strlen(id) - 1] == '\n') + id[strlen(id) - 1] = '\0'; + + *vendor_id = strtol(id, NULL, 0); + ret = 0; + + fd = open(did, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "ERROR: WDC: %s : Open device file failed\n", __func__); + return -1; + } + + ret = read(fd, id, 32); + close(fd); + + if (ret < 0) { + fprintf(stderr, "%s: Read of pci device id failed\n", __func__); + return -1; + } + id[ret < 32 ? ret : 31] = '\0'; + if (id[strlen(id) - 1] == '\n') + id[strlen(id) - 1] = '\0'; + + *device_id = strtol(id, NULL, 0); + return 0; +} + +static int wdc_get_vendor_id(struct nvme_dev *dev, uint32_t *vendor_id) +{ + int ret; + struct nvme_id_ctrl ctrl; + + memset(&ctrl, 0, sizeof(struct nvme_id_ctrl)); + ret = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (ret) { + fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed 0x%x\n", ret); + return -1; + } + + *vendor_id = (uint32_t) ctrl.vid; + + return ret; +} + +static bool wdc_is_sn861(__u32 device_id) +{ + if ((device_id == WDC_NVME_SN861_DEV_ID) || + (device_id == WDC_NVME_SN861_DEV_ID_1)) + return true; + else + return false; +} + + +static bool wdc_is_sn640(__u32 device_id) +{ + if ((device_id == WDC_NVME_SN640_DEV_ID) || + (device_id == WDC_NVME_SN640_DEV_ID_1) || + (device_id == WDC_NVME_SN640_DEV_ID_2)) + return true; + else + return false; +} + +static bool wdc_is_sn640_3(__u32 device_id) +{ + if (device_id == WDC_NVME_SN640_DEV_ID_3) + return true; + else + return false; +} + +static bool wdc_is_sn650_u2(__u32 device_id) +{ + if (device_id == WDC_NVME_SN650_DEV_ID_3) + return true; + else + return false; +} + +static bool wdc_is_sn650_e1l(__u32 device_id) +{ + if (device_id == WDC_NVME_SN650_DEV_ID_4) + return true; + else + return false; +} + +static bool needs_c2_log_page_check(__u32 device_id) +{ + if ((wdc_is_sn640(device_id)) || + (wdc_is_sn650_u2(device_id)) || + (wdc_is_sn650_e1l(device_id))) + return true; + else + return false; +} + +static bool wdc_check_power_of_2(int num) +{ + return num && (!(num & (num-1))); +} + +static int wdc_get_model_number(struct nvme_dev *dev, char *model) +{ + int ret, i; + struct nvme_id_ctrl ctrl; + + memset(&ctrl, 0, sizeof(struct nvme_id_ctrl)); + ret = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (ret) { + fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed 0x%x\n", ret); + return -1; + } + + memcpy(model, ctrl.mn, NVME_ID_CTRL_MODEL_NUMBER_SIZE); + /* get rid of the padded spaces */ + i = NVME_ID_CTRL_MODEL_NUMBER_SIZE-1; + while (model[i] == ' ') + i--; + model[i+1] = 0; + + return ret; +} + +static bool wdc_check_device(nvme_root_t r, struct nvme_dev *dev) +{ + int ret; + bool supported; + uint32_t read_device_id = -1, read_vendor_id = -1; + + ret = wdc_get_pci_ids(r, dev, &read_device_id, &read_vendor_id); + if (ret < 0) { + /* Use the identify nvme command to get vendor id due to NVMeOF device. */ + if (wdc_get_vendor_id(dev, &read_vendor_id) < 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 bool wdc_enc_check_model(struct nvme_dev *dev) +{ + int ret; + bool supported; + char model[NVME_ID_CTRL_MODEL_NUMBER_SIZE+1]; + + ret = wdc_get_model_number(dev, model); + if (ret < 0) + return false; + + supported = false; + model[NVME_ID_CTRL_MODEL_NUMBER_SIZE] = 0; /* forced termination */ + if (strstr(model, WDC_OPENFLEX_MI_DEVICE_MODEL)) + supported = true; + else + fprintf(stderr, "ERROR: WDC: unsupported WDC enclosure, Model = %s\n", model); + + return supported; +} + +static __u64 wdc_get_drive_capabilities(nvme_root_t r, struct nvme_dev *dev) +{ + int ret; + uint32_t read_device_id = -1, read_vendor_id = -1; + __u64 capabilities = 0; + __u32 cust_id; + + ret = wdc_get_pci_ids(r, dev, &read_device_id, &read_vendor_id); + if (ret < 0) { + if (wdc_get_vendor_id(dev, &read_vendor_id) < 0) + return capabilities; + } + + /* below check condition is added due in NVMeOF device we dont have device_id so we need to use only vendor_id*/ + if (read_device_id == -1 && read_vendor_id != -1) { + capabilities = wdc_get_enc_drive_capabilities(r, dev); + 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 | + WDC_DRIVE_CAP_PURGE); + 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 | + WDC_DRIVE_CAP_PURGE); + + /* verify the 0xCA log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, + WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE)) + capabilities |= WDC_DRIVE_CAP_CA_LOG_PAGE; + + /* verify the 0xC1 log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, + WDC_NVME_ADD_LOG_OPCODE)) + 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: + fallthrough; + 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(r, dev, + WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE)) + capabilities |= WDC_DRIVE_CAP_CA_LOG_PAGE; + + /* verify the 0xD0 log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, + WDC_NVME_GET_VU_SMART_LOG_OPCODE)) + capabilities |= WDC_DRIVE_CAP_D0_LOG_PAGE; + break; + + case WDC_NVME_SN640_DEV_ID: + fallthrough; + case WDC_NVME_SN640_DEV_ID_1: + fallthrough; + case WDC_NVME_SN640_DEV_ID_2: + fallthrough; + case WDC_NVME_SN640_DEV_ID_3: + fallthrough; + case WDC_NVME_SN560_DEV_ID_1: + fallthrough; + case WDC_NVME_SN560_DEV_ID_2: + fallthrough; + case WDC_NVME_SN560_DEV_ID_3: + fallthrough; + case WDC_NVME_SN660_DEV_ID: + /* verify the 0xC0 log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, + WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID) + == true) { + capabilities |= WDC_DRIVE_CAP_C0_LOG_PAGE; + } + + capabilities |= (WDC_DRIVE_CAP_CAP_DIAG | WDC_DRIVE_CAP_INTERNAL_LOG | + WDC_DRIVE_CAP_DRIVE_STATUS | WDC_DRIVE_CAP_CLEAR_ASSERT | + WDC_DRIVE_CAP_RESIZE | WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY | + WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG | WDC_DRIVE_CAP_REASON_ID | + WDC_DRIVE_CAP_LOG_PAGE_DIR); + + /* verify the 0xC1 (OCP Error Recovery) log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, + WDC_ERROR_REC_LOG_ID)) + capabilities |= WDC_DRIVE_CAP_OCP_C1_LOG_PAGE; + + /* verify the 0xC3 (OCP Latency Monitor) log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, + WDC_LATENCY_MON_LOG_ID)) + capabilities |= WDC_DRIVE_CAP_C3_LOG_PAGE; + + /* verify the 0xC4 (OCP Device Capabilities) log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, + WDC_DEV_CAP_LOG_ID)) + capabilities |= WDC_DRIVE_CAP_OCP_C4_LOG_PAGE; + + /* verify the 0xC5 (OCP Unsupported Requirements) log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, + WDC_UNSUPPORTED_REQS_LOG_ID)) + capabilities |= WDC_DRIVE_CAP_OCP_C5_LOG_PAGE; + + /* verify the 0xCA log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, + WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE)) + capabilities |= WDC_DRIVE_CAP_CA_LOG_PAGE; + + /* verify the 0xD0 log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, + WDC_NVME_GET_VU_SMART_LOG_OPCODE)) + capabilities |= WDC_DRIVE_CAP_D0_LOG_PAGE; + + cust_id = wdc_get_fw_cust_id(r, dev); + if (cust_id == WDC_INVALID_CUSTOMER_ID) { + fprintf(stderr, "%s: ERROR: WDC: invalid customer id\n", __func__); + return -1; + } + + if ((cust_id == WDC_CUSTOMER_ID_0x1004) || (cust_id == WDC_CUSTOMER_ID_0x1008) || + (cust_id == WDC_CUSTOMER_ID_0x1005) || (cust_id == WDC_CUSTOMER_ID_0x1304)) + capabilities |= (WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY | WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE | + WDC_DRIVE_CAP_INFO | WDC_DRIVE_CAP_CLOUD_SSD_VERSION); + else + capabilities |= (WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY | WDC_DRIVE_CAP_CLEAR_PCIE); + + break; + + case WDC_NVME_SN840_DEV_ID: + fallthrough; + case WDC_NVME_SN840_DEV_ID_1: + fallthrough; + case WDC_NVME_SN860_DEV_ID: + /* verify the 0xC0 log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, + WDC_NVME_GET_EOL_STATUS_LOG_OPCODE)) + capabilities |= WDC_DRIVE_CAP_C0_LOG_PAGE; + fallthrough; + case WDC_NVME_ZN540_DEV_ID: + fallthrough; + case WDC_NVME_SN540_DEV_ID: + 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); + + /* verify the 0xCA log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, + WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE)) + capabilities |= WDC_DRIVE_CAP_CA_LOG_PAGE; + + /* verify the 0xD0 log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, + WDC_NVME_GET_VU_SMART_LOG_OPCODE)) + capabilities |= WDC_DRIVE_CAP_D0_LOG_PAGE; + break; + + case WDC_NVME_SN650_DEV_ID: + fallthrough; + case WDC_NVME_SN650_DEV_ID_1: + fallthrough; + case WDC_NVME_SN650_DEV_ID_2: + fallthrough; + case WDC_NVME_SN650_DEV_ID_3: + fallthrough; + case WDC_NVME_SN650_DEV_ID_4: + fallthrough; + case WDC_NVME_SN655_DEV_ID: + fallthrough; + case WDC_NVME_SN550_DEV_ID: + /* verify the 0xC0 log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, + WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID)) + capabilities |= WDC_DRIVE_CAP_C0_LOG_PAGE; + + /* verify the 0xC1 (OCP Error Recovery) log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, WDC_ERROR_REC_LOG_ID)) + capabilities |= WDC_DRIVE_CAP_OCP_C1_LOG_PAGE; + + /* verify the 0xC3 (OCP Latency Monitor) log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, WDC_LATENCY_MON_LOG_ID)) + capabilities |= WDC_DRIVE_CAP_C3_LOG_PAGE; + + /* verify the 0xC4 (OCP Device Capabilities) log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, WDC_DEV_CAP_LOG_ID)) + capabilities |= WDC_DRIVE_CAP_OCP_C4_LOG_PAGE; + + /* verify the 0xC5 (OCP Unsupported Requirements) log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, WDC_UNSUPPORTED_REQS_LOG_ID)) + capabilities |= WDC_DRIVE_CAP_OCP_C5_LOG_PAGE; + + capabilities |= (WDC_DRIVE_CAP_CAP_DIAG | WDC_DRIVE_CAP_INTERNAL_LOG | + WDC_DRIVE_CAP_DRIVE_STATUS | WDC_DRIVE_CAP_CLEAR_ASSERT | + WDC_DRIVE_CAP_RESIZE | WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY | + WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG | + WDC_DRIVE_CAP_REASON_ID | WDC_DRIVE_CAP_LOG_PAGE_DIR); + + cust_id = wdc_get_fw_cust_id(r, dev); + if (cust_id == WDC_INVALID_CUSTOMER_ID) { + fprintf(stderr, "%s: ERROR: WDC: invalid customer id\n", __func__); + return -1; + } + + if ((cust_id == WDC_CUSTOMER_ID_0x1004) || + (cust_id == WDC_CUSTOMER_ID_0x1008) || + (cust_id == WDC_CUSTOMER_ID_0x1005) || + (cust_id == WDC_CUSTOMER_ID_0x1304)) + capabilities |= (WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY | + WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE | + WDC_DRIVE_CAP_INFO | + WDC_DRIVE_CAP_CLOUD_SSD_VERSION); + else + capabilities |= (WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY | + WDC_DRIVE_CAP_CLEAR_PCIE); + + break; + + case WDC_NVME_SN861_DEV_ID: + fallthrough; + case WDC_NVME_SN861_DEV_ID_1: + capabilities |= (WDC_DRIVE_CAP_C0_LOG_PAGE | + WDC_DRIVE_CAP_C3_LOG_PAGE | + WDC_DRIVE_CAP_CA_LOG_PAGE | + WDC_DRIVE_CAP_OCP_C4_LOG_PAGE | + WDC_DRIVE_CAP_OCP_C5_LOG_PAGE | + WDC_DRIVE_CAP_INTERNAL_LOG | + WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2 | + WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE | + WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY | + WDC_DRIVE_CAP_INFO | + WDC_DRIVE_CAP_CLOUD_SSD_VERSION | + WDC_DRIVE_CAP_LOG_PAGE_DIR | + WDC_DRIVE_CAP_SET_LATENCY_MONITOR); + break; + + default: + capabilities = 0; + } + 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: + fallthrough; + case WDC_NVME_SN520_DEV_ID_1: + fallthrough; + case WDC_NVME_SN520_DEV_ID_2: + fallthrough; + case WDC_NVME_SN810_DEV_ID: + capabilities = WDC_DRIVE_CAP_DUI_DATA; + break; + + case WDC_NVME_SN820CL_DEV_ID: + capabilities = WDC_DRIVE_CAP_DUI_DATA | + WDC_DRIVE_CAP_CLOUD_BOOT_SSD_VERSION | + WDC_DRIVE_CAP_CLOUD_LOG_PAGE | WDC_DRIVE_CAP_C0_LOG_PAGE | + WDC_DRIVE_CAP_HW_REV_LOG_PAGE | WDC_DRIVE_CAP_INFO | + WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE | WDC_DRIVE_CAP_NAND_STATS | + WDC_DRIVE_CAP_DEVICE_WAF | WDC_DRIVE_CAP_TEMP_STATS; + 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_SN730_DEV_ID: + capabilities = WDC_DRIVE_CAP_DUI | WDC_DRIVE_CAP_NAND_STATS | + WDC_DRIVE_CAP_INFO | WDC_DRIVE_CAP_TEMP_STATS | + WDC_DRIVE_CAP_VUC_CLEAR_PCIE | WDC_DRIVE_CAP_PCIE_STATS; + break; + + case WDC_NVME_SN530_DEV_ID_1: + fallthrough; + case WDC_NVME_SN530_DEV_ID_2: + fallthrough; + case WDC_NVME_SN530_DEV_ID_3: + fallthrough; + case WDC_NVME_SN530_DEV_ID_4: + fallthrough; + case WDC_NVME_SN530_DEV_ID_5: + fallthrough; + case WDC_NVME_SN350_DEV_ID: + fallthrough; + case WDC_NVME_SN570_DEV_ID: + fallthrough; + case WDC_NVME_SN850X_DEV_ID: + fallthrough; + case WDC_NVME_SN5000_DEV_ID_1: + fallthrough; + case WDC_NVME_SN5000_DEV_ID_2: + fallthrough; + case WDC_NVME_SN5000_DEV_ID_3: + fallthrough; + case WDC_NVME_SN5000_DEV_ID_4: + fallthrough; + case WDC_NVME_SN7000S_DEV_ID_1: + fallthrough; + case WDC_NVME_SN7150_DEV_ID_1: + fallthrough; + case WDC_NVME_SN7150_DEV_ID_2: + fallthrough; + case WDC_NVME_SN7150_DEV_ID_3: + fallthrough; + case WDC_NVME_SN7150_DEV_ID_4: + fallthrough; + case WDC_NVME_SN7150_DEV_ID_5: + fallthrough; + case WDC_NVME_SN7100_DEV_ID_1: + fallthrough; + case WDC_NVME_SN7100_DEV_ID_2: + fallthrough; + case WDC_NVME_SN7100_DEV_ID_3: + fallthrough; + case WDC_NVME_SN8000S_DEV_ID: + fallthrough; + case WDC_NVME_SN740_DEV_ID: + fallthrough; + case WDC_NVME_SN740_DEV_ID_1: + fallthrough; + case WDC_NVME_SN740_DEV_ID_2: + fallthrough; + case WDC_NVME_SN740_DEV_ID_3: + fallthrough; + case WDC_NVME_SN340_DEV_ID: + capabilities = WDC_DRIVE_CAP_DUI; + break; + + case WDC_NVME_ZN350_DEV_ID: + fallthrough; + case WDC_NVME_ZN350_DEV_ID_1: + capabilities = WDC_DRIVE_CAP_DUI_DATA | WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE | + WDC_DRIVE_CAP_C0_LOG_PAGE | + WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY | + WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2 | WDC_DRIVE_CAP_INFO | + WDC_DRIVE_CAP_CLOUD_SSD_VERSION | WDC_DRIVE_CAP_LOG_PAGE_DIR; + break; + + default: + capabilities = 0; + } + break; + default: + capabilities = 0; + } + + return capabilities; +} + +static __u64 wdc_get_enc_drive_capabilities(nvme_root_t r, + struct nvme_dev *dev) +{ + int ret; + uint32_t read_vendor_id; + __u64 capabilities = 0; + __u32 cust_id; + + ret = wdc_get_vendor_id(dev, &read_vendor_id); + if (ret < 0) + return capabilities; + + switch (read_vendor_id) { + case WDC_NVME_VID: + 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(r, dev, 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(r, dev, WDC_NVME_ADD_LOG_OPCODE) == true) + capabilities |= WDC_DRIVE_CAP_C1_LOG_PAGE; + break; + case WDC_NVME_VID_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); + + /* verify the 0xC3 log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, WDC_LATENCY_MON_LOG_ID) == true) + capabilities |= WDC_DRIVE_CAP_C3_LOG_PAGE; + + /* verify the 0xCB log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID) == true) + capabilities |= WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY; + + /* verify the 0xCA log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, 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(r, dev, WDC_NVME_GET_VU_SMART_LOG_OPCODE) == true) + capabilities |= WDC_DRIVE_CAP_D0_LOG_PAGE; + + cust_id = wdc_get_fw_cust_id(r, dev); + if (cust_id == WDC_INVALID_CUSTOMER_ID) { + fprintf(stderr, "%s: ERROR: WDC: invalid customer id\n", __func__); + return -1; + } + + if ((cust_id == WDC_CUSTOMER_ID_0x1004) || (cust_id == WDC_CUSTOMER_ID_0x1008) || + (cust_id == WDC_CUSTOMER_ID_0x1005) || (cust_id == WDC_CUSTOMER_ID_0x1304)) + capabilities |= (WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY | WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE); + else + capabilities |= (WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY | WDC_DRIVE_CAP_CLEAR_PCIE); + + break; + case WDC_NVME_SNDK_VID: + capabilities = WDC_DRIVE_CAP_DRIVE_ESSENTIALS; + break; + default: + capabilities = 0; + } + + return capabilities; +} + +static int wdc_get_serial_name(struct nvme_dev *dev, char *file, size_t len, + const char *suffix) +{ + int i; + int ret; + int res_len = 0; + char orig[PATH_MAX] = {0}; + struct nvme_id_ctrl ctrl; + int ctrl_sn_len = sizeof(ctrl.sn); + + i = sizeof(ctrl.sn) - 1; + strncpy(orig, file, PATH_MAX - 1); + memset(file, 0, len); + memset(&ctrl, 0, sizeof(struct nvme_id_ctrl)); + ret = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (ret) { + fprintf(stderr, "ERROR: 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) { + 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)); + close(fd); + 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)); + close(fd); + return -1; + } + + if (fsync(fd) < 0) { + fprintf(stderr, "ERROR: WDC: fsync: %s\n", strerror(errno)); + close(fd); + return -1; + } + close(fd); + return 0; +} + +bool wdc_get_dev_mng_log_entry(__u32 log_length, __u32 entry_id, + struct wdc_c2_log_page_header *p_log_hdr, + struct wdc_c2_log_subpage_header **p_p_found_log_entry) +{ + __u32 remaining_len = 0; + __u32 log_entry_hdr_size = sizeof(struct wdc_c2_log_subpage_header) - 1; + __u32 log_entry_size = 0; + __u32 size = 0; + bool valid_log; + __u32 current_data_offset = 0; + struct wdc_c2_log_subpage_header *p_next_log_entry = NULL; + + if (!*p_p_found_log_entry) { + fprintf(stderr, "ERROR: WDC - %s: No ppLogEntry pointer.\n", __func__); + return false; + } + + *p_p_found_log_entry = NULL; + + /* Ensure log data is large enough for common header */ + if (log_length < sizeof(struct wdc_c2_log_page_header)) { + fprintf(stderr, + "ERROR: WDC - %s: Buffer is not large enough for the common header. BufSize: 0x%x HdrSize: %"PRIxPTR"\n", + __func__, log_length, sizeof(struct wdc_c2_log_page_header)); + return false; + } + + /* Get pointer to first log Entry */ + size = sizeof(struct wdc_c2_log_page_header); + current_data_offset = size; + p_next_log_entry = (struct wdc_c2_log_subpage_header *)((__u8 *)p_log_hdr + current_data_offset); + remaining_len = log_length - size; + valid_log = false; + + /* + * Walk the entire structure. Perform a sanity check to make sure this is a + * standard version of the structure. This means making sure each entry looks + * valid. But allow for the data to overflow the allocated + * buffer (we don't want a false negative because of a FW formatting error) + */ + + /* Proceed only if there is at least enough data to read an entry header */ + while (remaining_len >= log_entry_hdr_size) { + /* Get size of the next entry */ + log_entry_size = p_next_log_entry->length; + + /* + * If log entry size is 0 or the log entry goes past the end + * of the data, we must be at the end of the data + */ + if (!log_entry_size || log_entry_size > remaining_len) { + fprintf(stderr, "ERROR: WDC: %s: Detected unaligned end of the data. ", + __func__); + fprintf(stderr, "Data Offset: 0x%x Entry Size: 0x%x, ", + current_data_offset, log_entry_size); + fprintf(stderr, "Remaining Log Length: 0x%x Entry Id: 0x%x\n", + remaining_len, p_next_log_entry->entry_id); + + /* Force the loop to end */ + remaining_len = 0; + } else if (!p_next_log_entry->entry_id || p_next_log_entry->entry_id > 200) { + /* Invalid entry - fail the search */ + fprintf(stderr, "ERROR: WDC: %s: Invalid entry found at offset: 0x%x ", + __func__, current_data_offset); + fprintf(stderr, "Entry Size: 0x%x, Remaining Log Length: 0x%x ", + log_entry_size, remaining_len); + fprintf(stderr, "Entry Id: 0x%x\n", p_next_log_entry->entry_id); + + /* Force the loop to end */ + remaining_len = 0; + valid_log = false; + + /* The structure is invalid, so any match that was found is invalid. */ + *p_p_found_log_entry = NULL; + } else { + /* Structure must have at least one valid entry to be considered valid */ + valid_log = true; + if (p_next_log_entry->entry_id == entry_id) + /* A potential match. */ + *p_p_found_log_entry = p_next_log_entry; + + remaining_len -= log_entry_size; + + if (remaining_len > 0) { + /* Increment the offset counter */ + current_data_offset += log_entry_size; + + /* Get the next entry */ + p_next_log_entry = (struct wdc_c2_log_subpage_header *)(((__u8 *)p_log_hdr) + current_data_offset); + } + } + } + + return valid_log; +} + +static bool get_dev_mgmt_log_page_lid_data(struct nvme_dev *dev, + void **cbs_data, + __u8 lid, + __u8 log_id, + __u8 uuid_ix) +{ + void *data; + struct wdc_c2_log_page_header *hdr_ptr; + struct wdc_c2_log_subpage_header *sph; + __u32 length = 0; + int ret = 0; + bool found = false; + + data = (__u8 *)malloc(sizeof(__u8) * WDC_C2_LOG_BUF_LEN); + if (!data) { + 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 */ + struct nvme_get_log_args args_len = { + .args_size = sizeof(args_len), + .fd = dev_fd(dev), + .lid = lid, + .nsid = 0xFFFFFFFF, + .lpo = 0, + .lsp = NVME_LOG_LSP_NONE, + .lsi = 0, + .rae = false, + .uuidx = uuid_ix, + .csi = NVME_CSI_NVM, + .ot = false, + .len = WDC_C2_LOG_BUF_LEN, + .log = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + ret = nvme_get_log(&args_len); + if (ret) { + fprintf(stderr, + "ERROR: WDC: Unable to get 0x%x Log Page length with uuid %d, ret = 0x%x\n", + lid, uuid_ix, ret); + goto end; + } + + hdr_ptr = (struct wdc_c2_log_page_header *)data; + length = le32_to_cpu(hdr_ptr->length); + + if (length > WDC_C2_LOG_BUF_LEN) { + /* Log Page buffer too small, free and reallocate the necessary size */ + free(data); + data = calloc(length, sizeof(__u8)); + if (!data) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + goto end; + } + + /* get the log page data with the increased length */ + struct nvme_get_log_args args_data = { + .args_size = sizeof(args_data), + .fd = dev_fd(dev), + .lid = lid, + .nsid = 0xFFFFFFFF, + .lpo = 0, + .lsp = NVME_LOG_LSP_NONE, + .lsi = 0, + .rae = false, + .uuidx = uuid_ix, + .csi = NVME_CSI_NVM, + .ot = false, + .len = length, + .log = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + ret = nvme_get_log(&args_data); + + if (ret) { + fprintf(stderr, + "ERROR: WDC: Unable to read 0x%x Log Page data with uuid %d, ret = 0x%x\n", + lid, uuid_ix, ret); + goto end; + } + } + + /* Check the log data to see if the WD version of log page ID's is found */ + length = sizeof(struct wdc_c2_log_page_header); + hdr_ptr = (struct wdc_c2_log_page_header *)data; + sph = (struct wdc_c2_log_subpage_header *)(data + length); + found = wdc_get_dev_mng_log_entry(le32_to_cpu(hdr_ptr->length), log_id, hdr_ptr, &sph); + if (found) { + *cbs_data = calloc(le32_to_cpu(sph->length), sizeof(__u8)); + if (!*cbs_data) { + fprintf(stderr, "ERROR: WDC: calloc: %s\n", strerror(errno)); + found = false; + goto end; + } + memcpy((void *)*cbs_data, (void *)&sph->data, le32_to_cpu(sph->length)); + } else { + fprintf(stderr, "ERROR: WDC: C2 log id 0x%x not found with uuid index %d\n", + log_id, uuid_ix); + } + +end: + free(data); + return found; +} + +static bool get_dev_mgment_cbs_data(nvme_root_t r, struct nvme_dev *dev, + __u8 log_id, void **cbs_data) +{ + int ret = -1; + bool found = false; + __u8 uuid_ix = 0; + __u8 lid = 0; + *cbs_data = NULL; + __u32 device_id, read_vendor_id; + bool uuid_present = false; + int index = 0, uuid_index = 0; + struct nvme_id_uuid_list uuid_list; + + ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id); + if (ret == 0) { + if (device_id == WDC_NVME_ZN350_DEV_ID || device_id == WDC_NVME_ZN350_DEV_ID_1) { + lid = WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID_C8; + uuid_ix = 0; + } else { + lid = WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID; + } + } else { + fprintf(stderr, "ERROR: WDC: get pci ids: %d\n", ret); + return false; + } + + typedef struct nvme_id_uuid_list_entry *uuid_list_entry; + + memset(&uuid_list, 0, sizeof(struct nvme_id_uuid_list)); + if (wdc_CheckUuidListSupport(dev, &uuid_list)) { + uuid_list_entry uuid_list_entry_ptr = (uuid_list_entry)&uuid_list.entry[0]; + + while (index <= NVME_ID_UUID_LIST_MAX && + !wdc_UuidEqual(uuid_list_entry_ptr, (uuid_list_entry)UUID_END)) { + + if (wdc_UuidEqual(uuid_list_entry_ptr, + (uuid_list_entry)WDC_UUID)) { + uuid_present = true; + break; + } else if (wdc_UuidEqual(uuid_list_entry_ptr, + (uuid_list_entry)WDC_UUID_SN640_3) && + wdc_is_sn640_3(device_id)) { + uuid_present = true; + break; + } + index++; + uuid_list_entry_ptr = (uuid_list_entry)&uuid_list.entry[index]; + } + if (uuid_present) + uuid_index = index + 1; + } + + if (!uuid_index && needs_c2_log_page_check(device_id)) { + /* In certain devices that don't support UUID lists, there are multiple + * definitions of the C2 logpage. In those cases, the code + * needs to try two UUID indexes and use an identification algorithm + * to determine which is returning the correct log page data. + */ + uuid_ix = 1; + } + + found = get_dev_mgmt_log_page_lid_data(dev, cbs_data, lid, log_id, uuid_ix); + + if (!found) { + /* not found with uuid = 1 try with uuid = 0 */ + uuid_ix = 0; + fprintf(stderr, "Not found, requesting log page with uuid_index %d\n", uuid_index); + + found = get_dev_mgmt_log_page_lid_data(dev, cbs_data, lid, log_id, uuid_ix); + } + + return found; +} + +static bool wdc_nvme_check_supported_log_page(nvme_root_t r, struct nvme_dev *dev, __u8 log_id) +{ + int i; + bool found = false; + struct wdc_c2_cbs_data *cbs_data = NULL; + + if (get_dev_mgment_cbs_data(r, dev, WDC_C2_LOG_PAGES_SUPPORTED_ID, (void *)&cbs_data)) { + if (cbs_data) { + 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 + free(cbs_data); + } 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(nvme_root_t r, struct nvme_dev *dev, __le32 *ret_data, + __u8 log_id) +{ + __u32 *cbs_data = NULL; + + if (get_dev_mgment_cbs_data(r, dev, log_id, (void *)&cbs_data)) { + if (cbs_data) { + memcpy((void *)ret_data, (void *)cbs_data, 4); + free(cbs_data); + + return true; + } + } + + *ret_data = 0; + return false; +} + +static int wdc_do_clear_dump(struct nvme_dev *dev, __u8 opcode, __u32 cdw12) +{ + int ret; + struct nvme_passthru_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd)); + admin_cmd.opcode = opcode; + admin_cmd.cdw12 = cdw12; + ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL); + if (ret) + fprintf(stdout, "ERROR: WDC: Crash dump erase failed\n"); + nvme_show_status(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_passthru_cmd admin_cmd; + + l = (struct wdc_log_size *) buf; + memset(&admin_cmd, 0, sizeof(struct nvme_passthru_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, NULL); + if (ret) { + l->log_size = 0; + ret = -1; + fprintf(stderr, "ERROR: WDC: reading dump length failed\n"); + nvme_show_status(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_passthru_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof(struct nvme_passthru_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, NULL); + if (ret) { + fprintf(stderr, "ERROR: WDC: reading dump length failed\n"); + nvme_show_status(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_passthru_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof(struct nvme_passthru_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, NULL); + if (ret) { + fprintf(stderr, "ERROR: WDC: reading DUI data failed\n"); + nvme_show_status(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_passthru_cmd admin_cmd; + __u64 offset_lo, offset_hi; + + memset(&admin_cmd, 0, sizeof(struct nvme_passthru_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); + offset_lo = offset & 0x00000000FFFFFFFF; + offset_hi = ((offset & 0xFFFFFFFF00000000) >> 32); + admin_cmd.cdw12 = (__u32)offset_lo; + admin_cmd.cdw13 = (__u32)offset_hi; + + 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, NULL); + if (ret) { + fprintf(stderr, "ERROR: WDC: reading DUI data V2 failed\n"); + nvme_show_status(ret); + } + + return ret; +} + +static int wdc_do_dump(struct nvme_dev *dev, __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_passthru_cmd admin_cmd; + __u32 dump_length = data_len; + + dump_data = (__u8 *)malloc(sizeof(__u8) * dump_length); + if (!dump_data) { + 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_passthru_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(dev_fd(dev), &admin_cmd, + NULL); + if (ret) { + nvme_show_status(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, (unsigned long)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) { + nvme_show_status(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_passthru_cmd admin_cmd; + + /* if data_len is not 4 byte aligned */ + if (data_len & 0x00000003) { + /* Round down to the next 4 byte aligned value */ + fprintf(stderr, "%s: INFO: data_len 0x%x not 4 byte aligned.\n", + __func__, data_len); + fprintf(stderr, "%s: INFO: Round down to 0x%x.\n", + __func__, (data_len &= 0xFFFFFFFC)); + data_len &= 0xFFFFFFFC; + } + + dump_data = (__u8 *)malloc(sizeof(__u8) * data_len); + + if (!dump_data) { + 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_passthru_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; + + /* subtract off the header size since that was already copied into the buffer */ + log_size = (data_len - curr_data_offset); + 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, NULL); + if (ret) { + nvme_show_status(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, (unsigned long)admin_cmd.addr); + break; + } + + log_size -= xfer_size; + curr_data_offset += xfer_size; + i++; + } + + if (!ret) { + fprintf(stderr, "%s: INFO: ", __func__); + nvme_show_status(ret); + } else { + fprintf(stderr, "%s: FAILURE: ", __func__); + nvme_show_status(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(struct nvme_dev *dev, char *file, + __u32 bs, int type, int data_area) +{ + struct nvme_telemetry_log *log; + size_t full_size = 0; + int err = 0, output; + __u32 host_gen = 1; + int ctrl_init = 0; + __u32 result; + void *buf = NULL; + __u8 *data_ptr = NULL; + int data_written = 0, data_remaining = 0; + struct nvme_id_ctrl ctrl; + __u64 capabilities = 0; + nvme_root_t r; + + memset(&ctrl, 0, sizeof(struct nvme_id_ctrl)); + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (err) { + fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed 0x%x\n", err); + return err; + } + + if (!(ctrl.lpa & 0x8)) { + fprintf(stderr, "Telemetry Host-Initiated and Telemetry Controller-Initiated log pages not supported\n"); + return -EINVAL; + } + + r = nvme_scan(NULL); + capabilities = wdc_get_drive_capabilities(r, dev); + + if (type == WDC_TELEMETRY_TYPE_HOST) { + host_gen = 1; + ctrl_init = 0; + } else if (type == WDC_TELEMETRY_TYPE_CONTROLLER) { + if ((capabilities & WDC_DRIVE_CAP_INTERNAL_LOG) == WDC_DRIVE_CAP_INTERNAL_LOG) { + /* Verify the Controller Initiated Option is enabled */ + err = nvme_get_features_data(dev_fd(dev), + WDC_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID, + 0, 4, buf, &result); + if (!err) { + if (!result) { + /* enabled */ + host_gen = 0; + ctrl_init = 1; + } else { + fprintf(stderr, "%s: Controller initiated option telemetry log page disabled\n", __func__); + return -EINVAL; + } + } else { + fprintf(stderr, "ERROR: WDC: Get telemetry option feature failed."); + nvme_show_status(err); + return -EPERM; + } + } else { + host_gen = 0; + ctrl_init = 1; + } + } else { + fprintf(stderr, "%s: Invalid type parameter; type = %d\n", __func__, type); + return -EINVAL; + } + + if (!file) { + fprintf(stderr, "%s: Please provide an output file!\n", __func__); + return -EINVAL; + } + + output = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (output < 0) { + fprintf(stderr, "%s: Failed to open output file %s: %s!\n", + __func__, file, strerror(errno)); + return output; + } + + if (ctrl_init) + err = nvme_get_ctrl_telemetry(dev_fd(dev), true, &log, + data_area, &full_size); + else if (host_gen) + err = nvme_get_new_host_telemetry(dev_fd(dev), &log, + data_area, &full_size); + else + err = nvme_get_host_telemetry(dev_fd(dev), &log, data_area, + &full_size); + + if (err < 0) { + perror("get-telemetry-log"); + goto close_output; + } else if (err > 0) { + nvme_show_status(err); + fprintf(stderr, "%s: Failed to acquire telemetry header!\n", __func__); + goto close_output; + } + + /* + *Continuously pull data until the offset hits the end of the last + *block. + */ + data_written = 0; + data_remaining = full_size; + data_ptr = (__u8 *)log; + + while (data_remaining) { + data_written = write(output, data_ptr, data_remaining); + + if (data_written < 0) { + data_remaining = data_written; + break; + } else if (data_written <= data_remaining) { + data_remaining -= data_written; + data_ptr += data_written; + } else { + /* Unexpected overwrite */ + fprintf(stderr, "Failure: Unexpected telemetry log overwrite - data_remaining = 0x%x, data_written = 0x%x\n", + data_remaining, data_written); + break; + } + } + + if (fsync(output) < 0) { + fprintf(stderr, "ERROR: %s: fsync: %s\n", __func__, strerror(errno)); + err = -1; + } + + free(log); +close_output: + close(output); + return err; +} + +static int wdc_do_cap_diag(nvme_root_t r, struct nvme_dev *dev, 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) { + 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(dev_fd(dev), + 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) { + fprintf(stderr, "INFO: WDC: Capture Diagnostics log is empty\n"); + } else { + ret = wdc_do_dump_e6(dev_fd(dev), + 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(dev, 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_v1(int fd, char *file, __u32 xfer_size, int data_area, int verbose, + struct wdc_dui_log_hdr *log_hdr, __s64 *total_size) +{ + __s32 log_size = 0; + __u32 cap_dui_length = le32_to_cpu(log_hdr->log_size); + __u32 curr_data_offset = 0; + __u8 *buffer_addr; + __u8 *dump_data = NULL; + bool last_xfer = false; + int err; + int i; + int j; + int output; + int ret = 0; + + 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); + fprintf(stderr, "INFO: WDC: DUI section count = 0x%x\n", log_hdr->section_count); + fprintf(stderr, "INFO: WDC: DUI log size = 0x%x\n", log_hdr->log_size); + } + + if (!cap_dui_length) { + fprintf(stderr, "INFO: WDC: Capture V1 Device Unit Info log is empty\n"); + return 0; + } + + /* 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 < log_hdr->section_count; j++) { + log_size += log_hdr->log_section[j].section_size; + if (verbose) + fprintf(stderr, + "%s: section size 0x%x, total size = 0x%x\n", + __func__, + (unsigned int)log_hdr->log_section[j].section_size, + (unsigned int)log_size); + + } + } else { + log_size = cap_dui_length; + } + + *total_size = log_size; + + dump_data = (__u8 *)malloc(sizeof(__u8) * xfer_size); + if (!dump_data) { + fprintf(stderr, "%s: ERROR: dump data V1 malloc failed : status %s, size = 0x%x\n", + __func__, strerror(errno), (unsigned int)xfer_size); + return -1; + } + 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)); + free(dump_data); + return output; + } + + /* 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) { + fprintf(stderr, + "%s: ERROR: WDC: Get chunk %d, size = 0x%"PRIx64", offset = 0x%x, addr = %p\n", + __func__, i, (uint64_t)log_size, curr_data_offset, buffer_addr); + fprintf(stderr, "%s: ERROR: WDC: ", __func__); + nvme_show_status(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); + ret = -1; + goto free_mem; + } + + curr_data_offset += xfer_size; + i++; + } + +free_mem: + close(output); + free(dump_data); + return ret; +} + +static int wdc_do_cap_dui_v2_v3(int fd, char *file, __u32 xfer_size, int data_area, int verbose, + struct wdc_dui_log_hdr *log_hdr, __s64 *total_size, __u64 file_size, + __u64 offset) +{ + __u64 cap_dui_length_v3; + __u64 curr_data_offset = 0; + __s64 log_size = 0; + __u64 xfer_size_long = (__u64)xfer_size; + __u8 *buffer_addr; + __u8 *dump_data = NULL; + bool last_xfer = false; + int err; + int i; + int j; + int output; + int ret = 0; + struct wdc_dui_log_hdr_v3 *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->hdr_version & 0xFF) == 0x03) + fprintf(stderr, "INFO: WDC: DUI Product ID = 0x%x/%c\n", + log_hdr_v3->product_id, log_hdr_v3->product_id); + } + + if (!cap_dui_length_v3) { + fprintf(stderr, "INFO: WDC: Capture V2 or V3 Device Unit Info log is empty\n"); + return 0; + } + + /* 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) { + 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%"PRIx64"\n", + __func__, log_hdr_v3->log_section[j].data_area_id, + (unsigned int)log_hdr_v3->log_section[j].section_size, + (uint64_t)log_size); + } else { + if (verbose) + fprintf(stderr, "%s: break, total size = 0x%"PRIx64"\n", + __func__, (uint64_t)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); + return -1; + } + + dump_data = (__u8 *)malloc(sizeof(__u8) * xfer_size_long); + if (!dump_data) { + fprintf(stderr, + "%s: ERROR: dump data v3 malloc failed : status %s, size = 0x%"PRIx64"\n", + __func__, strerror(errno), (uint64_t)xfer_size_long); + return -1; + } + 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)); + free(dump_data); + return output; + } + + curr_data_offset = 0; + + if (file_size) { + /* 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) { + fprintf(stderr, + "%s: ERROR: WDC: Get chunk %d, size = 0x%"PRIx64", offset = 0x%"PRIx64", addr = %p\n", + __func__, i, (uint64_t)*total_size, (uint64_t)curr_data_offset, + buffer_addr); + fprintf(stderr, "%s: ERROR: WDC: ", __func__); + nvme_show_status(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%"PRIx64"\n", + __func__, i, err, (uint64_t)xfer_size_long); + ret = -1; + goto free_mem; + } + + curr_data_offset += xfer_size_long; + i++; + } + +free_mem: + close(output); + free(dump_data); + return ret; +} + +static int wdc_do_cap_dui_v4(int fd, char *file, __u32 xfer_size, int data_area, int verbose, + struct wdc_dui_log_hdr *log_hdr, __s64 *total_size, __u64 file_size, + __u64 offset) +{ + __s64 log_size = 0; + __s64 section_size_bytes = 0; + __s64 xfer_size_long = (__s64)xfer_size; + __u64 cap_dui_length_v4; + __u64 curr_data_offset = 0; + __u8 *buffer_addr; + __u8 *dump_data = NULL; + int err; + int i; + int j; + int output; + int ret = 0; + bool last_xfer = false; + struct wdc_dui_log_hdr_v4 *log_hdr_v4 = (struct wdc_dui_log_hdr_v4 *)log_hdr; + + cap_dui_length_v4 = le64_to_cpu(log_hdr_v4->log_size_sectors) * WDC_NVME_SN730_SECTOR_SIZE; + + if (verbose) { + fprintf(stderr, "INFO: WDC: Capture V4 Device Unit Info log, data area = %d\n", data_area); + fprintf(stderr, "INFO: WDC: DUI Header Version = 0x%x\n", log_hdr_v4->hdr_version); + fprintf(stderr, "INFO: WDC: DUI Product ID = 0x%x/%c\n", log_hdr_v4->product_id, log_hdr_v4->product_id); + fprintf(stderr, "INFO: WDC: DUI log size sectors = 0x%x\n", log_hdr_v4->log_size_sectors); + fprintf(stderr, "INFO: WDC: DUI cap_dui_length = 0x%"PRIx64"\n", (uint64_t)cap_dui_length_v4); + } + + if (!cap_dui_length_v4) { + fprintf(stderr, "INFO: WDC: Capture V4 Device Unit Info log is empty\n"); + return 0; + } + + /* 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_v4->log_section[j].data_area_id <= data_area && + log_hdr_v4->log_section[j].data_area_id) { + section_size_bytes = ((__s64)log_hdr_v4->log_section[j].section_size_sectors * WDC_NVME_SN730_SECTOR_SIZE); + log_size += section_size_bytes; + if (verbose) + fprintf(stderr, + "%s: Data area ID %d : section size 0x%x sectors, section size 0x%"PRIx64" bytes, total size = 0x%"PRIx64"\n", + __func__, log_hdr_v4->log_section[j].data_area_id, + log_hdr_v4->log_section[j].section_size_sectors, + (uint64_t)section_size_bytes, (uint64_t)log_size); + } else { + if (verbose) + fprintf(stderr, "%s: break, total size = 0x%"PRIx64"\n", __func__, (uint64_t)log_size); + break; + } + } + } else { + log_size = cap_dui_length_v4; + } + + *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); + return -1; + } + + dump_data = (__u8 *)malloc(sizeof(__u8) * xfer_size_long); + if (!dump_data) { + fprintf(stderr, "%s: ERROR: dump data V4 malloc failed : status %s, size = 0x%x\n", + __func__, strerror(errno), (unsigned int)xfer_size_long); + return -1; + } + 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)); + free(dump_data); + return output; + } + + curr_data_offset = 0; + + if (file_size) { + /* 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) { + fprintf(stderr, + "%s: ERROR: WDC: Get chunk %d, size = 0x%"PRIx64", offset = 0x%"PRIx64", addr = %p\n", + __func__, i, (uint64_t)log_size, (uint64_t)curr_data_offset, + buffer_addr); + fprintf(stderr, "%s: ERROR: WDC:", __func__); + nvme_show_status(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_long = 0x%"PRIx64"\n", + __func__, i, err, (uint64_t)xfer_size_long); + ret = -1; + goto free_mem; + } + + curr_data_offset += xfer_size_long; + i++; + } + +free_mem: + close(output); + free(dump_data); + 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; + __s64 total_size = 0; + bool last_xfer = false; + + log_hdr = (struct wdc_dui_log_hdr *)malloc(dui_log_hdr_size); + if (!log_hdr) { + 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) { + fprintf(stderr, "%s: ERROR: WDC: Get DUI headers failed\n", __func__); + fprintf(stderr, "%s: ERROR: WDC: ", __func__); + nvme_show_status(ret); + goto out; + } + + /* Check the Log Header version */ + if ((log_hdr->hdr_version & 0xFF) == 0x00 || (log_hdr->hdr_version & 0xFF) == 0x01) { + ret = wdc_do_cap_dui_v1(fd, file, xfer_size, data_area, verbose, log_hdr, + &total_size); + if (ret) + goto out; + } else if ((log_hdr->hdr_version & 0xFF) == 0x02 || + (log_hdr->hdr_version & 0xFF) == 0x03) { + /* Process Version 2 or 3 header */ + ret = wdc_do_cap_dui_v2_v3(fd, file, xfer_size, data_area, verbose, log_hdr, + &total_size, file_size, offset); + if (ret) + goto out; + } else if ((log_hdr->hdr_version & 0xFF) == 0x04) { + ret = wdc_do_cap_dui_v4(fd, file, xfer_size, data_area, verbose, log_hdr, + &total_size, file_size, offset); + if (ret) + goto out; + } else { + fprintf(stderr, "INFO: WDC: Unsupported header version = 0x%x\n", + log_hdr->hdr_version); + goto out; + } + + nvme_show_status(ret); + if (verbose) + fprintf(stderr, "INFO: WDC: Capture Device Unit Info log, length = 0x%"PRIx64"\n", + (uint64_t)total_size); + +out: + free(log_hdr); + return ret; +} + +static int wdc_cap_diag(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + nvme_root_t r; + char *desc = "Capture Diagnostics Log."; + char *file = "Output file pathname."; + char *size = "Data retrieval transfer size."; + __u64 capabilities = 0; + char f[PATH_MAX] = {0}; + struct nvme_dev *dev; + __u32 xfer_size = 0; + int ret = 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + + if (cfg.file) + strncpy(f, cfg.file, PATH_MAX - 1); + if (cfg.xfer_size) + xfer_size = cfg.xfer_size; + ret = wdc_get_serial_name(dev, f, PATH_MAX, "cap_diag"); + if (ret) { + fprintf(stderr, "ERROR: WDC: failed to generate file name\n"); + goto out; + } + if (!cfg.file) { + if (strlen(f) > PATH_MAX - 5) { + fprintf(stderr, "ERROR: WDC: file name overflow\n"); + ret = -1; + goto out; + } + strcat(f, ".bin"); + } + + capabilities = wdc_get_drive_capabilities(r, dev); + if ((capabilities & WDC_DRIVE_CAP_CAP_DIAG) == WDC_DRIVE_CAP_CAP_DIAG) + ret = wdc_do_cap_diag(r, dev, f, xfer_size, 0, 0); + else + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +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_passthru_cmd admin_cmd; + + output = (uint32_t *)malloc(sizeof(uint32_t)); + if (!output) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + return -1; + } + memset(output, 0, sizeof(uint32_t)); + memset(&admin_cmd, 0, sizeof(struct nvme_passthru_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, NULL); + if (!ret) + *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_passthru_cmd admin_cmd; + + output = (uint8_t *)calloc(SN730_LOG_CHUNK_SIZE, sizeof(uint8_t)); + if (!output) { + fprintf(stderr, "ERROR: WDC: calloc: %s\n", strerror(errno)); + return -1; + } + memset(&admin_cmd, 0, sizeof(struct nvme_passthru_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, NULL); + 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; + + chunk_buf = (uint8_t *)malloc(sizeof(uint8_t) * SN730_LOG_CHUNK_SIZE); + if (!chunk_buf) { + 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; + struct tarfile_metadata *tarInfo = NULL; + + tarInfo = (struct tarfile_metadata *)malloc(sizeof(struct tarfile_metadata)); + if (!tarInfo) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + ret = -1; + goto free_buf; + } + memset(tarInfo, 0, sizeof(struct 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) { + 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) { + nvme_show_status(ret); + goto free_buf; + } + ret = wdc_do_get_sn730_log_len(fd, &key_log_len, SN730_GET_KEY_LOG_LENGTH); + if (ret) { + nvme_show_status(ret); + goto free_buf; + } + ret = wdc_do_get_sn730_log_len(fd, &core_dump_log_len, SN730_GET_COREDUMP_LOG_LENGTH); + if (ret) { + nvme_show_status(ret); + goto free_buf; + } + ret = wdc_do_get_sn730_log_len(fd, &extended_log_len, SN730_GET_EXTENDED_LOG_LENGTH); + if (ret) { + nvme_show_status(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) { + nvme_show_status(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) { + nvme_show_status(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) { + nvme_show_status(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) { + nvme_show_status(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 dump_internal_logs(struct nvme_dev *dev, char *dir_name, int verbose) +{ + char file_path[128]; + void *telemetry_log; + const size_t bs = 512; + struct nvme_telemetry_log *hdr; + size_t full_size, offset = bs; + int err, output; + + if (verbose) + printf("NVMe Telemetry log...\n"); + + hdr = malloc(bs); + telemetry_log = malloc(bs); + if (!hdr || !telemetry_log) { + fprintf(stderr, "Failed to allocate %zu bytes for log: %s\n", bs, strerror(errno)); + err = -ENOMEM; + goto free_mem; + } + memset(hdr, 0, bs); + + sprintf(file_path, "%s/telemetry.bin", dir_name); + output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (output < 0) { + fprintf(stderr, "Failed to open output file %s: %s!\n", file_path, strerror(errno)); + err = output; + goto free_mem; + } + + struct nvme_get_log_args args = { + .lpo = 0, + .result = NULL, + .log = hdr, + .args_size = sizeof(args), + .fd = dev_fd(dev), + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .lid = NVME_LOG_LID_TELEMETRY_HOST, + .len = bs, + .nsid = NVME_NSID_ALL, + .csi = NVME_CSI_NVM, + .lsi = NVME_LOG_LSI_NONE, + .lsp = NVME_LOG_TELEM_HOST_LSP_CREATE, + .uuidx = NVME_UUID_NONE, + .rae = true, + .ot = false, + }; + + err = nvme_get_log(&args); + if (err < 0) + perror("get-telemetry-log"); + else if (err > 0) { + nvme_show_status(err); + fprintf(stderr, "Failed to acquire telemetry header %d!\n", err); + goto close_output; + } + + err = write(output, (void *)hdr, bs); + if (err != bs) { + fprintf(stderr, "Failed to flush all data to file!\n"); + goto close_output; + } + + full_size = (le16_to_cpu(hdr->dalb3) * bs) + offset; + + while (offset != full_size) { + args.log = telemetry_log; + args.lpo = offset; + args.lsp = NVME_LOG_LSP_NONE; + err = nvme_get_log(&args); + if (err < 0) { + perror("get-telemetry-log"); + break; + } else if (err > 0) { + fprintf(stderr, "Failed to acquire full telemetry log!\n"); + nvme_show_status(err); + break; + } + + err = write(output, (void *)telemetry_log, bs); + if (err != bs) { + fprintf(stderr, "Failed to flush all data to file!\n"); + break; + } + err = 0; + offset += bs; + } + +close_output: + close(output); +free_mem: + free(hdr); + free(telemetry_log); + + return err; +} + +static int wdc_vs_internal_fw_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + 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, SN730, 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 SN530, SN640, SN730, SN740, SN810, SN840 and ZN350 devices."; + char *verbose = "Display more debug messages."; + char f[PATH_MAX] = {0}; + char fb[PATH_MAX/2] = {0}; + char fileSuffix[PATH_MAX] = {0}; + struct nvme_dev *dev; + nvme_root_t r; + __u32 xfer_size = 0; + int telemetry_type = 0, telemetry_data_area = 0; + UtilsTimeInfo timeInfo; + __u8 timeStamp[MAX_PATH_LEN]; + __u64 capabilities = 0; + __u32 device_id, read_vendor_id; + char file_path[PATH_MAX/2] = {0}; + char cmd_buf[PATH_MAX] = {0}; + int ret = -1; + + struct config { + char *file; + __u32 xfer_size; + int data_area; + __u64 file_size; + __u64 offset; + char *type; + bool verbose; + }; + + struct config cfg = { + .file = NULL, + .xfer_size = 0x10000, + .data_area = 0, + .file_size = 0, + .offset = 0, + .type = NULL, + .verbose = false, + }; + + OPT_ARGS(opts) = { + OPT_FILE("output-file", 'o', &cfg.file, file), + OPT_UINT("transfer-size", 's', &cfg.xfer_size, size), + OPT_UINT("data-area", 'd', &cfg.data_area, data_area), + OPT_LONG("file-size", 'f', &cfg.file_size, file_size), + OPT_LONG("offset", 'e', &cfg.offset, offset), + OPT_FILE("type", 't', &cfg.type, type), + OPT_FLAG("verbose", 'v', &cfg.verbose, verbose), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + if (!wdc_check_device(r, dev)) + goto out; + + if (cfg.xfer_size) { + xfer_size = cfg.xfer_size; + } else { + fprintf(stderr, "ERROR: WDC: Invalid length\n"); + goto out; + } + + ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id); + + if (!wdc_is_sn861(device_id)) { + if (cfg.file) { + int verify_file; + + /* verify file name and path is valid before getting dump data */ + verify_file = open(cfg.file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (verify_file < 0) { + fprintf(stderr, "ERROR: WDC: open: %s\n", strerror(errno)); + goto out; + } + close(verify_file); + strncpy(f, cfg.file, PATH_MAX - 1); + } else { + wdc_UtilsGetTime(&timeInfo); + memset(timeStamp, 0, sizeof(timeStamp)); + wdc_UtilsSnprintf((char *)timeStamp, MAX_PATH_LEN, + "%02u%02u%02u_%02u%02u%02u", timeInfo.year, + timeInfo.month, timeInfo.dayOfMonth, + timeInfo.hour, timeInfo.minute, + timeInfo.second); + snprintf(fileSuffix, PATH_MAX, "_internal_fw_log_%s", (char *)timeStamp); + + ret = wdc_get_serial_name(dev, f, PATH_MAX, fileSuffix); + if (ret) { + fprintf(stderr, "ERROR: WDC: failed to generate file name\n"); + goto out; + } + } + + if (!cfg.file) { + if (strlen(f) > PATH_MAX - 5) { + fprintf(stderr, "ERROR: WDC: file name overflow\n"); + ret = -1; + goto out; + } + strcat(f, ".bin"); + } + fprintf(stderr, "%s: filename = %s\n", __func__, f); + + if (cfg.data_area) { + if (cfg.data_area > 5 || cfg.data_area < 1) { + fprintf(stderr, "ERROR: WDC: Data area must be 1-5\n"); + ret = -1; + goto out; + } + } + + if (!cfg.type || !strcmp(cfg.type, "NONE") || !strcmp(cfg.type, "none")) { + telemetry_type = WDC_TELEMETRY_TYPE_NONE; + data_area = 0; + } else if (!strcmp(cfg.type, "HOST") || !strcmp(cfg.type, "host")) { + telemetry_type = WDC_TELEMETRY_TYPE_HOST; + telemetry_data_area = cfg.data_area; + } else if (!strcmp(cfg.type, "CONTROLLER") || !strcmp(cfg.type, "controller")) { + telemetry_type = WDC_TELEMETRY_TYPE_CONTROLLER; + telemetry_data_area = cfg.data_area; + } else { + fprintf(stderr, + "ERROR: WDC: Invalid type - Must be NONE, HOST or CONTROLLER\n"); + ret = -1; + goto out; + } + } else { + if (cfg.file) { + strncpy(fb, cfg.file, PATH_MAX/2 - 8); + } else { + wdc_UtilsGetTime(&timeInfo); + memset(timeStamp, 0, sizeof(timeStamp)); + wdc_UtilsSnprintf((char *)timeStamp, MAX_PATH_LEN, + "%02u%02u%02u_%02u%02u%02u", timeInfo.year, + timeInfo.month, timeInfo.dayOfMonth, + timeInfo.hour, timeInfo.minute, + timeInfo.second); + snprintf(fileSuffix, PATH_MAX, "_internal_fw_log_%s", (char *)timeStamp); + + ret = wdc_get_serial_name(dev, fb, PATH_MAX/2 - 7, fileSuffix); + if (ret) { + fprintf(stderr, "ERROR: WDC: failed to generate file name\n"); + goto out; + } + + if (strlen(fb) > PATH_MAX/2 - 7) { + fprintf(stderr, "ERROR: WDC: file name overflow\n"); + ret = -1; + goto out; + } + } + fprintf(stderr, "%s: filename = %s.tar.gz\n", __func__, fb); + + + memset(file_path, 0, sizeof(file_path)); + if (snprintf(file_path, PATH_MAX/2 - 8, "%s.tar.gz", fb) >= PATH_MAX/2 - 8) { + fprintf(stderr, "File path is too long!\n"); + ret = -1; + goto out; + } + if (access(file_path, F_OK) != -1) { + fprintf(stderr, "Output file already exists!\n"); + ret = -EEXIST; + goto out; + } + } + + capabilities = wdc_get_drive_capabilities(r, dev); + if ((capabilities & WDC_DRIVE_CAP_INTERNAL_LOG) == WDC_DRIVE_CAP_INTERNAL_LOG) { + if (!wdc_is_sn861(device_id)) { + /* Set the default DA to 3 if not specified */ + if (!telemetry_data_area) + telemetry_data_area = 3; + + ret = wdc_do_cap_diag(r, dev, f, xfer_size, + telemetry_type, telemetry_data_area); + } else { + if (cfg.verbose) + printf("Creating temp directory...\n"); + + ret = mkdir(fb, 0666); + if (ret) { + fprintf(stderr, "Failed to create directory!\n"); + goto out; + } + + ret = dump_internal_logs(dev, fb, cfg.verbose); + if (ret < 0) + perror("vs-internal-log"); + + if (cfg.verbose) + printf("Archiving...\n"); + + if (snprintf(cmd_buf, PATH_MAX, + "tar --remove-files -czf %s %s", + file_path, fb) >= PATH_MAX) { + fprintf(stderr, "Command buffer is too long!\n"); + ret = -1; + goto out; + } + + ret = system(cmd_buf); + if (ret) + fprintf(stderr, "Failed to create an archive file!\n"); + } + goto out; + } + if ((capabilities & WDC_DRIVE_CAP_DUI) == WDC_DRIVE_CAP_DUI) { + if ((telemetry_type == WDC_TELEMETRY_TYPE_HOST) || + (telemetry_type == WDC_TELEMETRY_TYPE_CONTROLLER)) { + if (!telemetry_data_area) + telemetry_data_area = 3; /* Set the default DA to 3 if not specified */ + /* Get the desired telemetry log page */ + ret = wdc_do_cap_telemetry_log(dev, f, xfer_size, + telemetry_type, telemetry_data_area); + goto out; + } else { + if (!cfg.data_area) + cfg.data_area = 1; + + /* FW requirement - xfer size must be 256k for data area 4 */ + if (cfg.data_area >= 4) + xfer_size = 0x40000; + ret = wdc_do_cap_dui(dev_fd(dev), f, xfer_size, + cfg.data_area, + cfg.verbose, cfg.file_size, + cfg.offset); + goto out; + } + } + if ((capabilities & WDC_DRIVE_CAP_DUI_DATA) == WDC_DRIVE_CAP_DUI_DATA) { + if ((telemetry_type == WDC_TELEMETRY_TYPE_HOST) || + (telemetry_type == WDC_TELEMETRY_TYPE_CONTROLLER)) { + if (!telemetry_data_area) + telemetry_data_area = 3; /* Set the default DA to 3 if not specified */ + /* Get the desired telemetry log page */ + ret = wdc_do_cap_telemetry_log(dev, f, xfer_size, + telemetry_type, telemetry_data_area); + goto out; + } else { + ret = wdc_do_cap_dui(dev_fd(dev), f, xfer_size, + WDC_NVME_DUI_MAX_DATA_AREA, + cfg.verbose, 0, 0); + goto out; + } + } + if ((capabilities & WDC_SN730B_CAP_VUC_LOG) == WDC_SN730B_CAP_VUC_LOG) { + ret = wdc_do_sn730_get_and_tar(dev_fd(dev), f); + } else { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + } +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_do_crash_dump(struct nvme_dev *dev, 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(dev_fd(dev), + 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) { + 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(dev, + opcode, + crash_dump_length, + cdw12, + file, + crash_dump_length); + + if (!ret) + ret = wdc_do_clear_dump(dev, WDC_NVME_CLEAR_DUMP_OPCODE, + cdw12_clear); + } + return ret; +} + +static int wdc_crash_dump(struct nvme_dev *dev, char *file, int type) +{ + char f[PATH_MAX] = {0}; + const char *dump_type; + int ret; + + if (file) + strncpy(f, file, PATH_MAX - 1); + + if (type == WDC_NVME_PFAIL_DUMP_TYPE) + dump_type = "_pfail_dump"; + else + dump_type = "_crash_dump"; + + ret = wdc_get_serial_name(dev, f, PATH_MAX, dump_type); + if (ret) + fprintf(stderr, "ERROR: WDC: failed to generate file name\n"); + else + ret = wdc_do_crash_dump(dev, f, type); + return ret; +} + +static int wdc_do_drive_log(struct nvme_dev *dev, char *file) +{ + int ret; + __u8 *drive_log_data; + __u32 drive_log_length; + struct nvme_passthru_cmd admin_cmd; + + ret = wdc_dump_length(dev_fd(dev), 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) { + 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_passthru_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(dev_fd(dev), &admin_cmd, NULL); + nvme_show_status(ret); + if (!ret) + 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}; + struct nvme_dev *dev; + int ret; + nvme_root_t r; + __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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + + if (!wdc_check_device(r, dev)) { + nvme_free_tree(r); + dev_close(dev); + return -1; + } + capabilities = wdc_get_drive_capabilities(r, dev); + + if (!(capabilities & WDC_DRIVE_CAP_DRIVE_LOG)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + } else { + if (cfg.file) + strncpy(f, cfg.file, PATH_MAX - 1); + ret = wdc_get_serial_name(dev, f, PATH_MAX, "drive_log"); + if (ret) + fprintf(stderr, "ERROR: WDC: failed to generate file name\n"); + else + ret = wdc_do_drive_log(dev, f); + } + nvme_free_tree(r); + dev_close(dev); + 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."; + __u64 capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret; + + struct config { + char *file; + }; + + struct config cfg = { + .file = NULL, + }; + + OPT_ARGS(opts) = { + OPT_FILE("output-file", 'o', &cfg.file, file), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + + if (!wdc_check_device(r, dev)) { + nvme_free_tree(r); + dev_close(dev); + return -1; + + } + + capabilities = wdc_get_drive_capabilities(r, dev); + + if (!(capabilities & WDC_DRIVE_CAP_CRASH_DUMP)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + } else { + ret = wdc_crash_dump(dev, cfg.file, WDC_NVME_CRASH_DUMP_TYPE); + if (ret) + fprintf(stderr, "ERROR: WDC: failed to read crash dump\n"); + } + nvme_free_tree(r); + dev_close(dev); + 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."; + __u64 capabilities = 0; + struct nvme_dev *dev; + struct config { + char *file; + }; + nvme_root_t r; + int ret; + + struct config cfg = { + .file = NULL, + }; + + OPT_ARGS(opts) = { + OPT_FILE("output-file", 'o', &cfg.file, file), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + + if (!wdc_check_device(r, dev)) { + nvme_free_tree(r); + dev_close(dev); + return -1; + } + + capabilities = wdc_get_drive_capabilities(r, dev); + if (!(capabilities & WDC_DRIVE_CAP_PFAIL_DUMP)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + } else { + ret = wdc_crash_dump(dev, cfg.file, WDC_NVME_PFAIL_DUMP_TYPE); + if (ret) + fprintf(stderr, "ERROR: WDC: failed to read pfail crash dump\n"); + } + nvme_free_tree(r); + dev_close(dev); + 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\n" + "or reset interruption. Other commands may be rejected until\n" + "Purge 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."; + struct nvme_passthru_cmd admin_cmd; + __u64 capabilities = 0; + struct nvme_dev *dev; + char *err_str; + nvme_root_t r; + int ret; + + OPT_ARGS(opts) = { + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + + if (!wdc_check_device(r, dev)) { + nvme_free_tree(r); + dev_close(dev); + return -1; + } + + capabilities = wdc_get_drive_capabilities(r, dev); + if (!(capabilities & WDC_DRIVE_CAP_PURGE)) { + ret = -1; + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + } else { + err_str = ""; + memset(&admin_cmd, 0, sizeof(admin_cmd)); + admin_cmd.opcode = WDC_NVME_PURGE_CMD_OPCODE; + + ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, + NULL); + 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); + nvme_show_status(ret); + } + nvme_free_tree(r); + dev_close(dev); + 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."; + __u8 output[WDC_NVME_PURGE_MONITOR_DATA_LEN]; + double progress_percent; + struct nvme_passthru_cmd admin_cmd; + struct wdc_nvme_purge_monitor_data *mon; + struct nvme_dev *dev; + __u64 capabilities; + nvme_root_t r; + int ret; + + OPT_ARGS(opts) = { + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + if (!wdc_check_device(r, dev)) { + nvme_free_tree(r); + dev_close(dev); + return -1; + } + + capabilities = wdc_get_drive_capabilities(r, dev); + if (!(capabilities & WDC_DRIVE_CAP_PURGE)) { + ret = -1; + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + } else { + memset(output, 0, sizeof(output)); + memset(&admin_cmd, 0, sizeof(struct nvme_passthru_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; + + ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, + NULL); + if (!ret) { + 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); + } + } + + nvme_show_status(ret); + } + nvme_free_tree(r); + dev_close(dev); + 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 = 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 int wdc_print_latency_monitor_log_normal(struct nvme_dev *dev, + struct wdc_ssd_latency_monitor_log *log_data) +{ + printf("Latency Monitor/C3 Log Page Data\n"); + printf(" Controller : %s\n", dev->name); + int err = -1, i, j; + struct nvme_id_ctrl ctrl; + char ts_buf[128]; + + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (!err) { + printf(" Serial Number: %-.*s\n", (int)sizeof(ctrl.sn), ctrl.sn); + } else { + fprintf(stderr, "ERROR: WDC: latency monitor read id ctrl failure, err = %d\n", err); + return err; + } + + printf(" Feature Status 0x%x\n", log_data->feature_status); + printf(" Active Bucket Timer %d min\n", 5*le16_to_cpu(log_data->active_bucket_timer)); + printf(" Active Bucket Timer Threshold %d min\n", 5*le16_to_cpu(log_data->active_bucket_timer_threshold)); + printf(" Active Threshold A %d ms\n", 5*(le16_to_cpu(log_data->active_threshold_a+1))); + printf(" Active Threshold B %d ms\n", 5*(le16_to_cpu(log_data->active_threshold_b+1))); + printf(" Active Threshold C %d ms\n", 5*(le16_to_cpu(log_data->active_threshold_c+1))); + printf(" Active Threshold D %d ms\n", 5*(le16_to_cpu(log_data->active_threshold_d+1))); + printf(" Active Latency Config 0x%x\n", le16_to_cpu(log_data->active_latency_config)); + printf(" Active Latency Minimum Window %d ms\n", 100*log_data->active_latency_min_window); + printf(" Active Latency Stamp Units %d\n", le16_to_cpu(log_data->active_latency_stamp_units)); + printf(" Static Latency Stamp Units %d\n", le16_to_cpu(log_data->static_latency_stamp_units)); + printf(" Debug Log Trigger Enable %d\n", le16_to_cpu(log_data->debug_log_trigger_enable)); + + printf(" Read Write Deallocate/Trim\n"); + for (i = 0; i <= 3; i++) + printf(" Active Bucket Counter: Bucket %d %27d %27d %27d\n", + i, le32_to_cpu(log_data->active_bucket_counter[i][LATENCY_LOG_BUCKET_READ]), + le32_to_cpu(log_data->active_bucket_counter[i][LATENCY_LOG_BUCKET_WRITE]), + le32_to_cpu(log_data->active_bucket_counter[i][LATENCY_LOG_BUCKET_TRIM])); + + for (i = 3; i >= 0; i--) + printf(" Active Measured Latency: Bucket %d %27d ms %27d ms %27d ms\n", + 3-i, le16_to_cpu(log_data->active_measured_latency[i][LATENCY_LOG_MEASURED_LAT_READ]), + le16_to_cpu(log_data->active_measured_latency[i][LATENCY_LOG_MEASURED_LAT_WRITE]), + le16_to_cpu(log_data->active_measured_latency[i][LATENCY_LOG_MEASURED_LAT_TRIM])); + + for (i = 3; i >= 0; i--) { + printf(" Active Latency Time Stamp: Bucket %d ", 3-i); + for (j = 2; j >= 0; j--) { + if (le64_to_cpu(log_data->active_latency_timestamp[i][j]) == -1) { + printf(" N/A "); + } else { + convert_ts(le64_to_cpu(log_data->active_latency_timestamp[i][j]), ts_buf); + printf("%s ", ts_buf); + } + } + printf("\n"); + } + + for (i = 0; i <= 3; i++) + printf(" Static Bucket Counter: Bucket %d %27d %27d %27d\n", + i, le32_to_cpu(log_data->static_bucket_counter[i][LATENCY_LOG_BUCKET_READ]), + le32_to_cpu(log_data->static_bucket_counter[i][LATENCY_LOG_BUCKET_WRITE]), + le32_to_cpu(log_data->static_bucket_counter[i][LATENCY_LOG_BUCKET_TRIM])); + + for (i = 3; i >= 0; i--) + printf(" Static Measured Latency: Bucket %d %27d ms %27d ms %27d ms\n", + 3-i, le16_to_cpu(log_data->static_measured_latency[i][LATENCY_LOG_MEASURED_LAT_READ]), + le16_to_cpu(log_data->static_measured_latency[i][LATENCY_LOG_MEASURED_LAT_WRITE]), + le16_to_cpu(log_data->static_measured_latency[i][LATENCY_LOG_MEASURED_LAT_TRIM])); + + for (i = 3; i >= 0; i--) { + printf(" Static Latency Time Stamp: Bucket %d ", 3-i); + for (j = 2; j >= 0; j--) { + if (le64_to_cpu(log_data->static_latency_timestamp[i][j]) == -1) { + printf(" N/A "); + } else { + convert_ts(le64_to_cpu(log_data->static_latency_timestamp[i][j]), ts_buf); + printf("%s ", ts_buf); + } + } + printf("\n"); + } + + return 0; +} + +static void wdc_print_latency_monitor_log_json(struct wdc_ssd_latency_monitor_log *log_data) +{ + int i, j; + char buf[128]; + char *operation[3] = {"Read", "Write", "Trim"}; + struct json_object *root = json_create_object(); + + json_object_add_value_int(root, "Feature Status", log_data->feature_status); + json_object_add_value_int(root, "Active Bucket Timer", 5*le16_to_cpu(log_data->active_bucket_timer)); + json_object_add_value_int(root, "Active Bucket Timer Threshold", 5*le16_to_cpu(log_data->active_bucket_timer_threshold)); + json_object_add_value_int(root, "Active Threshold A", 5*le16_to_cpu(log_data->active_threshold_a+1)); + json_object_add_value_int(root, "Active Threshold B", 5*le16_to_cpu(log_data->active_threshold_b+1)); + json_object_add_value_int(root, "Active Threshold C", 5*le16_to_cpu(log_data->active_threshold_c+1)); + json_object_add_value_int(root, "Active Threshold D", 5*le16_to_cpu(log_data->active_threshold_d+1)); + json_object_add_value_int(root, "Active Latency Config", le16_to_cpu(log_data->active_latency_config)); + json_object_add_value_int(root, "Active Lantency Minimum Window", 100*log_data->active_latency_min_window); + json_object_add_value_int(root, "Active Latency Stamp Units", le16_to_cpu(log_data->active_latency_stamp_units)); + json_object_add_value_int(root, "Static Latency Stamp Units", le16_to_cpu(log_data->static_latency_stamp_units)); + json_object_add_value_int(root, "Debug Log Trigger Enable", le16_to_cpu(log_data->debug_log_trigger_enable)); + + for (i = 0; i <= 3; i++) { + for (j = 2; j >= 0; j--) { + sprintf(buf, "Active Bucket Counter: Bucket %d %s", i, operation[2-j]); + json_object_add_value_int(root, buf, le32_to_cpu(log_data->active_bucket_counter[i][j+1])); + } + } + for (i = 3; i >= 0; i--) { + for (j = 2; j >= 0; j--) { + sprintf(buf, "Active Measured Latency: Bucket %d %s", 3-i, operation[2-j]); + json_object_add_value_int(root, buf, le16_to_cpu(log_data->active_measured_latency[i][j])); + } + } + for (i = 3; i >= 0; i--) { + for (j = 2; j >= 0; j--) { + sprintf(buf, "Active Latency Time Stamp: Bucket %d %s", 3-i, operation[2-j]); + json_object_add_value_int(root, buf, le64_to_cpu(log_data->active_latency_timestamp[i][j])); + } + } + for (i = 0; i <= 3; i++) { + for (j = 2; j >= 0; j--) { + sprintf(buf, "Static Bucket Counter: Bucket %d %s", i, operation[2-j]); + json_object_add_value_int(root, buf, le32_to_cpu(log_data->static_bucket_counter[i][j+1])); + } + } + for (i = 3; i >= 0; i--) { + for (j = 2; j >= 0; j--) { + sprintf(buf, "Static Measured Latency: Bucket %d %s", 3-i, operation[2-j]); + json_object_add_value_int(root, buf, le16_to_cpu(log_data->static_measured_latency[i][j])); + } + } + for (i = 3; i >= 0; i--) { + for (j = 2; j >= 0; j--) { + sprintf(buf, "Static Latency Time Stamp: Bucket %d %s", 3-i, operation[2-j]); + json_object_add_value_int(root, buf, le64_to_cpu(log_data->static_latency_timestamp[i][j])); + } + } + + json_print_object(root, NULL); + printf("\n"); + + json_free_object(root); +} + +static void wdc_print_error_rec_log_normal(struct wdc_ocp_c1_error_recovery_log *log_data) +{ + int j; + + printf("Error Recovery/C1 Log Page Data\n"); + + printf(" Panic Reset Wait Time : 0x%x\n", le16_to_cpu(log_data->panic_reset_wait_time)); + printf(" Panic Reset Action : 0x%x\n", log_data->panic_reset_action); + printf(" Device Recovery Action 1 : 0x%x\n", log_data->dev_recovery_action1); + printf(" Panic ID : 0x%" PRIu64 "\n", le64_to_cpu(log_data->panic_id)); + printf(" Device Capabilities : 0x%x\n", le32_to_cpu(log_data->dev_capabilities)); + printf(" Vendor Specific Recovery Opcode : 0x%x\n", log_data->vs_recovery_opc); + printf(" Vendor Specific Command CDW12 : 0x%x\n", le32_to_cpu(log_data->vs_cmd_cdw12)); + printf(" Vendor Specific Command CDW13 : 0x%x\n", le32_to_cpu(log_data->vs_cmd_cdw13)); + if (le16_to_cpu(log_data->log_page_version) == WDC_ERROR_REC_LOG_VERSION2) { + printf(" Vendor Specific Command Timeout : 0x%x\n", log_data->vs_cmd_to); + printf(" Device Recovery Action 2 : 0x%x\n", log_data->dev_recovery_action2); + printf(" Device Recovery Action 2 Timeout : 0x%x\n", log_data->dev_recovery_action2_to); + } + printf(" Log Page Version : 0x%x\n", le16_to_cpu(log_data->log_page_version)); + printf(" Log page GUID : 0x"); + for (j = 0; j < WDC_OCP_C1_GUID_LENGTH; j++) + printf("%x", log_data->log_page_guid[j]); + printf("\n"); +} + +static void wdc_print_error_rec_log_json(struct wdc_ocp_c1_error_recovery_log *log_data) +{ + struct json_object *root = json_create_object(); + + json_object_add_value_int(root, "Panic Reset Wait Time", le16_to_cpu(log_data->panic_reset_wait_time)); + json_object_add_value_int(root, "Panic Reset Action", log_data->panic_reset_wait_time); + json_object_add_value_int(root, "Device Recovery Action 1", log_data->dev_recovery_action1); + json_object_add_value_int(root, "Panic ID", le64_to_cpu(log_data->panic_id)); + json_object_add_value_int(root, "Device Capabilities", le32_to_cpu(log_data->dev_capabilities)); + json_object_add_value_int(root, "Vendor Specific Recovery Opcode", log_data->vs_recovery_opc); + json_object_add_value_int(root, "Vendor Specific Command CDW12", le32_to_cpu(log_data->vs_cmd_cdw12)); + json_object_add_value_int(root, "Vendor Specific Command CDW13", le32_to_cpu(log_data->vs_cmd_cdw13)); + if (le16_to_cpu(log_data->log_page_version) == WDC_ERROR_REC_LOG_VERSION2) { + json_object_add_value_int(root, "Vendor Specific Command Timeout", log_data->vs_cmd_to); + json_object_add_value_int(root, "Device Recovery Action 2", log_data->dev_recovery_action2); + json_object_add_value_int(root, "Device Recovery Action 2 Timeout", log_data->dev_recovery_action2_to); + } + json_object_add_value_int(root, "Log Page Version", le16_to_cpu(log_data->log_page_version)); + + char guid[40]; + + memset((void *)guid, 0, 40); + sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[8]), + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[0])); + json_object_add_value_string(root, "Log page GUID", guid); + + json_print_object(root, NULL); + printf("\n"); + + json_free_object(root); +} + +static void wdc_print_dev_cap_log_normal(struct wdc_ocp_C4_dev_cap_log *log_data) +{ + int j; + + printf("Device Capabilities/C4 Log Page Data\n"); + + printf(" Number PCIE Ports : 0x%x\n", le16_to_cpu(log_data->num_pcie_ports)); + printf(" Number OOB Management Interfaces : 0x%x\n", le16_to_cpu(log_data->oob_mgmt_support)); + printf(" Write Zeros Command Support : 0x%x\n", le16_to_cpu(log_data->wrt_zeros_support)); + printf(" Sanitize Command Support : 0x%x\n", le16_to_cpu(log_data->sanitize_support)); + printf(" DSM Command Support : 0x%x\n", le16_to_cpu(log_data->dsm_support)); + printf(" Write Uncorr Command Support : 0x%x\n", le16_to_cpu(log_data->wrt_uncor_support)); + printf(" Fused Command Support : 0x%x\n", le16_to_cpu(log_data->fused_support)); + printf(" Minimum DSSD Power State : 0x%x\n", le16_to_cpu(log_data->min_dssd_ps)); + + for (j = 0; j < WDC_OCP_C4_NUM_PS_DESCR; j++) + printf(" DSSD Power State %d Descriptor : 0x%x\n", j, log_data->dssd_ps_descr[j]); + + printf(" Log Page Version : 0x%x\n", le16_to_cpu(log_data->log_page_version)); + printf(" Log page GUID : 0x"); + for (j = 0; j < WDC_OCP_C4_GUID_LENGTH; j++) + printf("%x", log_data->log_page_guid[j]); + printf("\n"); +} + +static void wdc_print_dev_cap_log_json(struct wdc_ocp_C4_dev_cap_log *log_data) +{ + int j; + struct json_object *root = json_create_object(); + + json_object_add_value_int(root, "Number PCIE Ports", le16_to_cpu(log_data->num_pcie_ports)); + json_object_add_value_int(root, "Number OOB Management Interfaces", le16_to_cpu(log_data->num_pcie_ports)); + json_object_add_value_int(root, "Write Zeros Command Support", le16_to_cpu(log_data->num_pcie_ports)); + json_object_add_value_int(root, "Sanitize Command Support", le16_to_cpu(log_data->num_pcie_ports)); + json_object_add_value_int(root, "DSM Command Support", le16_to_cpu(log_data->num_pcie_ports)); + json_object_add_value_int(root, "Write Uncorr Command Support", le16_to_cpu(log_data->num_pcie_ports)); + json_object_add_value_int(root, "Fused Command Support", le16_to_cpu(log_data->num_pcie_ports)); + json_object_add_value_int(root, "Minimum DSSD Power State", le16_to_cpu(log_data->num_pcie_ports)); + + char dssd_descr_str[40]; + + memset((void *)dssd_descr_str, 0, 40); + for (j = 0; j < WDC_OCP_C4_NUM_PS_DESCR; j++) { + sprintf((char *)dssd_descr_str, "DSSD Power State %d Descriptor", j); + json_object_add_value_int(root, dssd_descr_str, log_data->dssd_ps_descr[j]); + } + + json_object_add_value_int(root, "Log Page Version", le16_to_cpu(log_data->log_page_version)); + char guid[40]; + + memset((void *)guid, 0, 40); + sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[8]), + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[0])); + json_object_add_value_string(root, "Log page GUID", guid); + + json_print_object(root, NULL); + printf("\n"); + + json_free_object(root); +} + +static void wdc_print_unsupported_reqs_log_normal(struct wdc_ocp_C5_unsupported_reqs *log_data) +{ + int j; + + printf("Unsupported Requirements/C5 Log Page Data\n"); + + printf(" Number Unsupported Req IDs : 0x%x\n", + le16_to_cpu(log_data->unsupported_count)); + + for (j = 0; j < le16_to_cpu(log_data->unsupported_count); j++) + printf(" Unsupported Requirement List %d : %s\n", j, + log_data->unsupported_req_list[j]); + + printf(" Log Page Version : 0x%x\n", le16_to_cpu(log_data->log_page_version)); + printf(" Log page GUID : 0x"); + for (j = 0; j < WDC_OCP_C5_GUID_LENGTH; j++) + printf("%x", log_data->log_page_guid[j]); + printf("\n"); +} + +static void wdc_print_unsupported_reqs_log_json(struct wdc_ocp_C5_unsupported_reqs *log_data) +{ + int j; + struct json_object *root = json_create_object(); + + json_object_add_value_int(root, "Number Unsupported Req IDs", le16_to_cpu(log_data->unsupported_count)); + + char unsup_req_list_str[40]; + + memset((void *)unsup_req_list_str, 0, 40); + for (j = 0; j < le16_to_cpu(log_data->unsupported_count); j++) { + sprintf((char *)unsup_req_list_str, "Unsupported Requirement List %d", j); + json_object_add_value_string(root, unsup_req_list_str, (char *)log_data->unsupported_req_list[j]); + } + + json_object_add_value_int(root, "Log Page Version", + le16_to_cpu(log_data->log_page_version)); + char guid[40]; + + memset((void *)guid, 0, 40); + sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[8]), + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[0])); + json_object_add_value_string(root, "Log page GUID", guid); + + json_print_object(root, NULL); + printf("\n"); + + json_free_object(root); +} + +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 = json_create_object(); + uint64_t converted = 0; + + 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(struct nvme_dev *dev, void *data) +{ + struct wdc_bd_ca_log_format *bd_data = (struct wdc_bd_ca_log_format *)data; + __u64 *raw; + __u16 *word_raw1, *word_raw2, *word_raw3; + __u32 *dword_raw; + __u8 *byte_raw; + + if (bd_data->field_id == 0x00) { + raw = (__u64 *)&bd_data->raw_value[0]; + printf("Additional Smart Log for NVME device:%s namespace-id:%x\n", dev->name, + WDC_DE_GLOBAL_NSID); + printf("key normalized raw\n"); + printf("program_fail_count : %3"PRIu8"%% %"PRIu64"\n", + bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x01) { + raw = (__u64 *)&bd_data->raw_value[0]; + printf("erase_fail_count : %3"PRIu8"%% %"PRIu64"\n", + bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x02) { + word_raw1 = (__u16 *)&bd_data->raw_value[1]; + word_raw2 = (__u16 *)&bd_data->raw_value[3]; + word_raw3 = (__u16 *)&bd_data->raw_value[5]; + printf("wear_leveling : %3"PRIu8"%% min: %"PRIu16", max: %"PRIu16", avg: %"PRIu16"\n", + bd_data->normalized_value, + le16_to_cpu(*word_raw1), + le16_to_cpu(*word_raw2), + le16_to_cpu(*word_raw3)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x03) { + raw = (__u64 *)&bd_data->raw_value[0]; + printf("end_to_end_error_detection_count: %3"PRIu8"%% %"PRIu64"\n", + bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x04) { + raw = (__u64 *)&bd_data->raw_value[0]; + printf("crc_error_count : %3"PRIu8"%% %"PRIu64"\n", + bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x05) { + raw = (__u64 *)&bd_data->raw_value[0]; + printf("timed_workload_media_wear : %3"PRIu8"%% %-.3f%%\n", + bd_data->normalized_value, 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[0]; + printf("timed_workload_host_reads : %3"PRIu8"%% %"PRIu64"%%\n", + bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x07) { + raw = (__u64 *)&bd_data->raw_value[0]; + printf("timed_workload_timer : %3"PRIu8"%% %"PRIu64"\n", + bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x08) { + byte_raw = (__u8 *)&bd_data->raw_value[1]; + dword_raw = (__u32 *)&bd_data->raw_value[2]; + printf("thermal_throttle_status : %3"PRIu8"%% %"PRIu16"%%, cnt: %"PRIu16"\n", + bd_data->normalized_value, *byte_raw, le32_to_cpu(*dword_raw)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x09) { + raw = (__u64 *)&bd_data->raw_value[0]; + printf("retry_buffer_overflow_count : %3"PRIu8"%% %"PRIu64"\n", + bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x0A) { + raw = (__u64 *)&bd_data->raw_value[0]; + printf("pll_lock_loss_count : %3"PRIu8"%% %"PRIu64"\n", + bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x0B) { + raw = (__u64 *)&bd_data->raw_value[0]; + printf("nand_bytes_written : %3"PRIu8"%% sectors: %.f\n", + bd_data->normalized_value, safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 0xFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x0C) { + raw = (__u64 *)&bd_data->raw_value[0]; + printf("host_bytes_written : %3"PRIu8"%% sectors: %.f\n", + bd_data->normalized_value, safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 0xFFFF)); + } 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 = json_create_object(); + + if (bd_data->field_id == 0x00) { + raw = (__u64 *)&bd_data->raw_value[0]; + json_object_add_value_int(root, "program_fail_count normalized", + bd_data->normalized_value); + json_object_add_value_int(root, "program_fail_count raw", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x01) { + raw = (__u64 *)&bd_data->raw_value[0]; + json_object_add_value_int(root, "erase_fail_count normalized", + bd_data->normalized_value); + json_object_add_value_int(root, "erase_fail_count raw", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x02) { + word_raw = (__u16 *)&bd_data->raw_value[1]; + json_object_add_value_int(root, "wear_leveling normalized", bd_data->normalized_value); + json_object_add_value_int(root, "wear_leveling min", le16_to_cpu(*word_raw)); + word_raw = (__u16 *)&bd_data->raw_value[3]; + json_object_add_value_int(root, "wear_leveling max", le16_to_cpu(*word_raw)); + word_raw = (__u16 *)&bd_data->raw_value[5]; + json_object_add_value_int(root, "wear_leveling avg", le16_to_cpu(*word_raw)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x03) { + raw = (__u64 *)&bd_data->raw_value[0]; + json_object_add_value_int(root, "end_to_end_error_detection_count normalized", + bd_data->normalized_value); + json_object_add_value_int(root, "end_to_end_error_detection_count raw", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x04) { + raw = (__u64 *)&bd_data->raw_value[0]; + json_object_add_value_int(root, "crc_error_count normalized", + bd_data->normalized_value); + json_object_add_value_int(root, "crc_error_count raw", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x05) { + raw = (__u64 *)&bd_data->raw_value[0]; + json_object_add_value_int(root, "timed_workload_media_wear normalized", + bd_data->normalized_value); + json_object_add_value_double(root, "timed_workload_media_wear raw", + 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[0]; + json_object_add_value_int(root, "timed_workload_host_reads normalized", + bd_data->normalized_value); + json_object_add_value_int(root, "timed_workload_host_reads raw", + le64_to_cpu(*raw & 0x00000000000000FF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x07) { + raw = (__u64 *)&bd_data->raw_value[0]; + json_object_add_value_int(root, "timed_workload_timer normalized", + bd_data->normalized_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[1]; + json_object_add_value_int(root, "thermal_throttle_status normalized", + bd_data->normalized_value); + json_object_add_value_int(root, "thermal_throttle_status", *byte_raw); + dword_raw = (__u32 *)&bd_data->raw_value[2]; + json_object_add_value_int(root, "thermal_throttle_cnt", le32_to_cpu(*dword_raw)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x09) { + raw = (__u64 *)&bd_data->raw_value[0]; + json_object_add_value_int(root, "retry_buffer_overflow_count normalized", + bd_data->normalized_value); + json_object_add_value_int(root, "retry_buffer_overflow_count raw", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x0A) { + raw = (__u64 *)&bd_data->raw_value[0]; + json_object_add_value_int(root, "pll_lock_loss_count normalized", + bd_data->normalized_value); + json_object_add_value_int(root, "pll_lock_loss_count raw", + le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x0B) { + raw = (__u64 *)&bd_data->raw_value[0]; + json_object_add_value_int(root, "nand_bytes_written normalized", + bd_data->normalized_value); + json_object_add_value_double(root, "nand_bytes_written raw", + safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 0xFFFF)); + } else { + goto invalid_id; + } + bd_data++; + if (bd_data->field_id == 0x0C) { + raw = (__u64 *)&bd_data->raw_value[0]; + json_object_add_value_int(root, "host_bytes_written normalized", + bd_data->normalized_value); + json_object_add_value_double(root, "host_bytes_written raw", + safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 0xFFFF)); + } else { + goto invalid_id; + } + + goto done; + +invalid_id: + printf(" Invalid Field ID = %d\n", bd_data->field_id); + +done: + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); + + 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 = 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(__u8 *data, int num_entries, + __u32 cust_id, __u32 vendor_id, + __u32 device_id) +{ + int i, j; + char previous_fw[9]; + char new_fw[9]; + char commit_action_bin[8]; + char time_str[11]; + __u16 oldestEntryIdx = 0, entryIdx = 0; + char *null_fw = "--------"; + + memset((void *)time_str, 0, 11); + + if (data[0] == WDC_NVME_GET_FW_ACT_HISTORY_C2_LOG_ID) { + printf(" Firmware Activate History Log\n"); + if (cust_id == WDC_CUSTOMER_ID_0x1005 || + vendor_id == WDC_NVME_SNDK_VID || + wdc_is_sn861(device_id)) { + printf(" Power on Hour Power Cycle Previous New\n"); + printf(" Entry hh:mm:ss Count Firmware Firmware Slot Action Result\n"); + printf(" ----- ----------------- ----------------- --------- --------- ----- ------ -------\n"); + } else { + printf(" Power Cycle Previous New\n"); + printf(" Entry Timestamp Count Firmware Firmware Slot Action Result\n"); + printf(" ----- ----------------- ----------------- --------- --------- ----- ------ -------\n"); + } + + struct wdc_fw_act_history_log_format_c2 *fw_act_history_entry = (struct wdc_fw_act_history_log_format_c2 *)(data); + + oldestEntryIdx = WDC_MAX_NUM_ACT_HIST_ENTRIES; + if (num_entries == WDC_MAX_NUM_ACT_HIST_ENTRIES) { + /* find lowest/oldest entry */ + for (i = 0; i < num_entries; i++) { + j = (i+1 == WDC_MAX_NUM_ACT_HIST_ENTRIES) ? 0 : i+1; + if (le16_to_cpu(fw_act_history_entry->entry[i].fw_act_hist_entries) > + le16_to_cpu(fw_act_history_entry->entry[j].fw_act_hist_entries)) { + oldestEntryIdx = j; + break; + } + } + } + if (oldestEntryIdx == WDC_MAX_NUM_ACT_HIST_ENTRIES) + entryIdx = 0; + else + entryIdx = oldestEntryIdx; + + for (i = 0; i < num_entries; i++) { + memset((void *)previous_fw, 0, 9); + memset((void *)new_fw, 0, 9); + memset((void *)commit_action_bin, 0, 8); + + memcpy(previous_fw, (char *)&(fw_act_history_entry->entry[entryIdx].previous_fw_version), 8); + if (strlen((char *)&(fw_act_history_entry->entry[entryIdx].current_fw_version)) > 1) + memcpy(new_fw, (char *)&(fw_act_history_entry->entry[entryIdx].current_fw_version), 8); + else + memcpy(new_fw, null_fw, 8); + + printf("%5"PRIu16"", (uint16_t)le16_to_cpu(fw_act_history_entry->entry[entryIdx].fw_act_hist_entries)); + if (cust_id == WDC_CUSTOMER_ID_0x1005) { + printf(" "); + memset((void *)time_str, 0, 9); + sprintf((char *)time_str, "%04d:%02d:%02d", (int)(le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp)/3600), + (int)((le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp%3600)/60)), + (int)(le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp%60))); + + printf("%s", time_str); + printf(" "); + } else if (vendor_id == WDC_NVME_SNDK_VID) { + printf(" "); + uint64_t timestamp = (0x0000FFFFFFFFFFFF & le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp)); + + memset((void *)time_str, 0, 9); + sprintf((char *)time_str, "%04d:%02d:%02d", (int)((timestamp/(3600*1000))%24), (int)((timestamp/(1000*60))%60), + (int)((timestamp/1000)%60)); + printf("%s", time_str); + printf(" "); + } else if (wdc_is_sn861(device_id)) { + printf(" "); + char timestamp[20]; + __u64 hour; + __u8 min; + __u8 sec; + __u64 timestamp_sec; + + timestamp_sec = + le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp) + / 1000; + hour = timestamp_sec / 3600; + min = (timestamp_sec % 3600) / 60; + sec = timestamp_sec % 60; + + sprintf(timestamp, + "%"PRIu64":%02"PRIu8":%02"PRIu8, + (uint64_t)hour, min, sec); + printf("%-11s", timestamp); + printf(" "); + } else { + printf(" "); + uint64_t timestamp = (0x0000FFFFFFFFFFFF & le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp)); + + printf("%16"PRIu64"", timestamp); + printf(" "); + } + + printf("%16"PRIu64"", (uint64_t)le64_to_cpu(fw_act_history_entry->entry[entryIdx].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->entry[entryIdx].slot_number); + printf(" "); + wdc_get_commit_action_bin( + fw_act_history_entry->entry[entryIdx].commit_action_type, + (char *)&commit_action_bin); + printf(" %s", (char *)commit_action_bin); + printf(" "); + if (!le16_to_cpu(fw_act_history_entry->entry[entryIdx].result)) + printf("pass"); + else + printf("fail #%d", (uint16_t)le16_to_cpu(fw_act_history_entry->entry[entryIdx].result)); + printf("\n"); + + entryIdx++; + if (entryIdx >= WDC_MAX_NUM_ACT_HIST_ENTRIES) + entryIdx = 0; + } + } else { + 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"); + + struct wdc_fw_act_history_log_entry *fw_act_history_entry = (struct wdc_fw_act_history_log_entry *)(data + sizeof(struct wdc_fw_act_history_log_hdr)); + + oldestEntryIdx = WDC_MAX_NUM_ACT_HIST_ENTRIES; + if (num_entries == WDC_MAX_NUM_ACT_HIST_ENTRIES) { + /* find lowest/oldest entry */ + for (i = 0; i < num_entries; i++) { + if (le32_to_cpu(fw_act_history_entry[i].entry_num) > le32_to_cpu(fw_act_history_entry[i+1].entry_num)) { + oldestEntryIdx = i+1; + break; + } + } + } + + if (oldestEntryIdx == WDC_MAX_NUM_ACT_HIST_ENTRIES) + entryIdx = 0; + else + entryIdx = oldestEntryIdx; + + for (i = 0; i < num_entries; i++) { + memset((void *)previous_fw, 0, 9); + memset((void *)new_fw, 0, 9); + memset((void *)commit_action_bin, 0, 8); + + memcpy(previous_fw, (char *)&(fw_act_history_entry[entryIdx].previous_fw_version), 8); + if (strlen((char *)&(fw_act_history_entry[entryIdx].new_fw_version)) > 1) + memcpy(new_fw, (char *)&(fw_act_history_entry[entryIdx].new_fw_version), 8); + else + memcpy(new_fw, null_fw, 8); + + printf("%5"PRIu32"", (uint32_t)le32_to_cpu(fw_act_history_entry[entryIdx].entry_num)); + printf(" "); + printf("%04d:%02d:%02d", (int)(le64_to_cpu(fw_act_history_entry[entryIdx].power_on_seconds)/3600), + (int)((le64_to_cpu(fw_act_history_entry[entryIdx].power_on_seconds)%3600)/60), + (int)(le64_to_cpu(fw_act_history_entry[entryIdx].power_on_seconds)%60)); + printf(" "); + printf("%16"PRIu32"", (uint32_t)le32_to_cpu(fw_act_history_entry[entryIdx].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[entryIdx].slot_number); + printf(" "); + wdc_get_commit_action_bin(fw_act_history_entry[entryIdx].commit_action_type, + (char *)&commit_action_bin); + printf(" %s", (char *)commit_action_bin); + printf(" "); + if (!le16_to_cpu(fw_act_history_entry[entryIdx].result)) + printf("pass"); + else + printf("fail #%d", (uint16_t)le16_to_cpu(fw_act_history_entry[entryIdx].result)); + + printf("\n"); + + entryIdx++; + if (entryIdx >= WDC_MAX_NUM_ACT_HIST_ENTRIES) + entryIdx = 0; + } + } +} + +static void wdc_print_fw_act_history_log_json(__u8 *data, int num_entries, + __u32 cust_id, __u32 vendor_id, + __u32 device_id) +{ + struct json_object *root = json_create_object(); + int i, j; + char previous_fw[9]; + char new_fw[9]; + char commit_action_bin[8]; + char fail_str[32]; + char time_str[11]; + char ext_time_str[20]; + + memset((void *)previous_fw, 0, 9); + memset((void *)new_fw, 0, 9); + memset((void *)commit_action_bin, 0, 8); + memset((void *)time_str, 0, 11); + memset((void *)ext_time_str, 0, 20); + memset((void *)fail_str, 0, 11); + char *null_fw = "--------"; + __u16 oldestEntryIdx = 0, entryIdx = 0; + + if (data[0] == WDC_NVME_GET_FW_ACT_HISTORY_C2_LOG_ID) { + struct wdc_fw_act_history_log_format_c2 *fw_act_history_entry = (struct wdc_fw_act_history_log_format_c2 *)(data); + + oldestEntryIdx = WDC_MAX_NUM_ACT_HIST_ENTRIES; + if (num_entries == WDC_MAX_NUM_ACT_HIST_ENTRIES) { + /* find lowest/oldest entry */ + for (i = 0; i < num_entries; i++) { + j = (i+1 == WDC_MAX_NUM_ACT_HIST_ENTRIES) ? 0 : i+1; + if (le16_to_cpu(fw_act_history_entry->entry[i].fw_act_hist_entries) > + le16_to_cpu(fw_act_history_entry->entry[j].fw_act_hist_entries)) { + oldestEntryIdx = j; + break; + } + } + } + if (oldestEntryIdx == WDC_MAX_NUM_ACT_HIST_ENTRIES) + entryIdx = 0; + else + entryIdx = oldestEntryIdx; + + for (i = 0; i < num_entries; i++) { + memcpy(previous_fw, + (char *)&(fw_act_history_entry->entry[entryIdx].previous_fw_version), + 8); + if (strlen((char *)&(fw_act_history_entry->entry[entryIdx].current_fw_version)) > 1) + memcpy(new_fw, + (char *)&(fw_act_history_entry->entry[entryIdx].current_fw_version), + 8); + else + memcpy(new_fw, null_fw, 8); + + json_object_add_value_int(root, "Entry", + le16_to_cpu(fw_act_history_entry->entry[entryIdx].fw_act_hist_entries)); + + if (cust_id == WDC_CUSTOMER_ID_0x1005) { + sprintf((char *)time_str, "%04d:%02d:%02d", (int)(le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp)/3600), + (int)((le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp%3600)/60)), + (int)(le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp%60))); + + json_object_add_value_string(root, "Power on Hour", time_str); + + } else if (vendor_id == WDC_NVME_SNDK_VID) { + uint64_t timestamp = (0x0000FFFFFFFFFFFF & le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp)); + + sprintf((char *)time_str, "%04d:%02d:%02d", (int)((timestamp/(3600*1000))%24), (int)((timestamp/(1000*60))%60), + (int)((timestamp/1000)%60)); + json_object_add_value_string(root, "Power on Hour", time_str); + } else if (wdc_is_sn861(device_id)) { + __u64 timestamp_sec = + le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp) + / 1000; + + sprintf((char *)ext_time_str, + "%"PRIu64":%02"PRIu8":%02"PRIu8, + (uint64_t)(__u64)(timestamp_sec/3600), + (__u8)((timestamp_sec%3600)/60), + (__u8)(timestamp_sec%60)); + json_object_add_value_string(root, "Power on Hour", ext_time_str); + } else { + uint64_t timestamp = (0x0000FFFFFFFFFFFF & le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp)); + + json_object_add_value_uint64(root, "Timestamp", timestamp); + } + + json_object_add_value_int(root, "Power Cycle Count", + le64_to_cpu(fw_act_history_entry->entry[entryIdx].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->entry[entryIdx].slot_number); + + wdc_get_commit_action_bin( + fw_act_history_entry->entry[entryIdx].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->entry[entryIdx].result)) { + json_object_add_value_string(root, "Result", "pass"); + } else { + sprintf((char *)fail_str, "fail #%d", (int)(le16_to_cpu(fw_act_history_entry->entry[entryIdx].result))); + json_object_add_value_string(root, "Result", fail_str); + } + + json_print_object(root, NULL); + printf("\n"); + + entryIdx++; + if (entryIdx >= WDC_MAX_NUM_ACT_HIST_ENTRIES) + entryIdx = 0; + } + } else { + struct wdc_fw_act_history_log_entry *fw_act_history_entry = (struct wdc_fw_act_history_log_entry *)(data + sizeof(struct wdc_fw_act_history_log_hdr)); + + oldestEntryIdx = WDC_MAX_NUM_ACT_HIST_ENTRIES; + if (num_entries == WDC_MAX_NUM_ACT_HIST_ENTRIES) { + /* find lowest/oldest entry */ + for (i = 0; i < num_entries; i++) { + if (le32_to_cpu(fw_act_history_entry[i].entry_num) > le32_to_cpu(fw_act_history_entry[i+1].entry_num)) { + oldestEntryIdx = i+1; + break; + } + } + } + if (oldestEntryIdx == WDC_MAX_NUM_ACT_HIST_ENTRIES) + entryIdx = 0; + else + entryIdx = oldestEntryIdx; + + for (i = 0; i < num_entries; i++) { + memcpy(previous_fw, + (char *)&(fw_act_history_entry[entryIdx].previous_fw_version), 8); + if (strlen((char *)&(fw_act_history_entry[entryIdx].new_fw_version)) > 1) + memcpy(new_fw, + (char *)&(fw_act_history_entry[entryIdx].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[entryIdx].entry_num)); + + sprintf((char *)time_str, "%04d:%02d:%02d", (int)(le64_to_cpu(fw_act_history_entry[entryIdx].power_on_seconds)/3600), + (int)((le64_to_cpu(fw_act_history_entry[entryIdx].power_on_seconds)%3600)/60), + (int)(le64_to_cpu(fw_act_history_entry[entryIdx].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[entryIdx].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[entryIdx].slot_number); + + wdc_get_commit_action_bin(fw_act_history_entry[entryIdx].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[entryIdx].result)) { + json_object_add_value_string(root, "Result", "pass"); + } else { + sprintf((char *)fail_str, "fail #%d", (int)(le16_to_cpu(fw_act_history_entry[entryIdx].result))); + json_object_add_value_string(root, "Result", fail_str); + } + + json_print_object(root, NULL); + printf("\n"); + + entryIdx++; + if (entryIdx >= WDC_MAX_NUM_ACT_HIST_ENTRIES) + entryIdx = 0; + } + } + + json_free_object(root); +} + +static int nvme_get_ext_smart_cloud_log(int fd, __u8 **data, int uuid_index, __u32 namespace_id) +{ + int ret, i; + __u8 *log_ptr = NULL; + + log_ptr = (__u8 *)malloc(sizeof(__u8) * WDC_NVME_SMART_CLOUD_ATTR_LEN); + if (!log_ptr) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + return -1; + } + + /* Get the 0xC0 log data */ + struct nvme_get_log_args args = { + .args_size = sizeof(args), + .fd = fd, + .lid = WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID, + .nsid = namespace_id, + .lpo = 0, + .lsp = NVME_LOG_LSP_NONE, + .lsi = 0, + .rae = false, + .uuidx = uuid_index, + .csi = NVME_CSI_NVM, + .ot = false, + .len = WDC_NVME_SMART_CLOUD_ATTR_LEN, + .log = log_ptr, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + ret = nvme_get_log(&args); + + if (!ret) { + /* Verify GUID matches */ + for (i = 0; i < WDC_C0_GUID_LENGTH; i++) { + if (ext_smart_guid[i] != *&log_ptr[SCAO_V1_LPG + i]) { + fprintf(stderr, "ERROR: WDC: Unknown GUID in C0 Log Page V1 data\n"); + int j; + + fprintf(stderr, "ERROR: WDC: Expected GUID: 0x"); + for (j = 0; j < WDC_C0_GUID_LENGTH; j++) + fprintf(stderr, "%x", ext_smart_guid[j]); + fprintf(stderr, "\nERROR: WDC: Actual GUID: 0x"); + for (j = 0; j < WDC_C0_GUID_LENGTH; j++) + fprintf(stderr, "%x", *&log_ptr[SCAO_V1_LPG + j]); + fprintf(stderr, "\n"); + + ret = -1; + break; + } + } + } + + *data = log_ptr; + + return ret; +} + + +static int nvme_get_hw_rev_log(int fd, __u8 **data, int uuid_index, __u32 namespace_id) +{ + int ret, i; + struct wdc_nvme_hw_rev_log *log_ptr = NULL; + + log_ptr = (struct wdc_nvme_hw_rev_log *)malloc(sizeof(__u8) * WDC_NVME_HW_REV_LOG_PAGE_LEN); + if (!log_ptr) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + return -1; + } + + /* Get the 0xC0 log data */ + struct nvme_get_log_args args = { + .args_size = sizeof(args), + .fd = fd, + .lid = WDC_NVME_GET_HW_REV_LOG_OPCODE, + .nsid = namespace_id, + .lpo = 0, + .lsp = NVME_LOG_LSP_NONE, + .lsi = 0, + .rae = false, + .uuidx = uuid_index, + .csi = NVME_CSI_NVM, + .ot = false, + .len = WDC_NVME_HW_REV_LOG_PAGE_LEN, + .log = log_ptr, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + ret = nvme_get_log(&args); + + if (!ret) { + /* Verify GUID matches */ + for (i = 0; i < WDC_NVME_C6_GUID_LENGTH; i++) { + if (hw_rev_log_guid[i] != log_ptr->hw_rev_guid[i]) { + fprintf(stderr, "ERROR: WDC: Unknown GUID in HW Revision Log Page data\n"); + int j; + + fprintf(stderr, "ERROR: WDC: Expected GUID: 0x"); + for (j = 0; j < WDC_NVME_C6_GUID_LENGTH; j++) + fprintf(stderr, "%x", hw_rev_log_guid[j]); + fprintf(stderr, "\nERROR: WDC: Actual GUID: 0x"); + for (j = 0; j < WDC_NVME_C6_GUID_LENGTH; j++) + fprintf(stderr, "%x", log_ptr->hw_rev_guid[j]); + fprintf(stderr, "\n"); + + ret = -1; + break; + } + } + } + + *data = (__u8 *)log_ptr; + + return ret; +} + + +static void wdc_print_hw_rev_log_normal(void *data) +{ + int i; + struct wdc_nvme_hw_rev_log *log_data = (struct wdc_nvme_hw_rev_log *)data; + + printf(" Hardware Revision Log:-\n"); + + printf(" Global Device HW Revision : %d\n", + log_data->hw_rev_gdr); + printf(" ASIC HW Revision : %d\n", + log_data->hw_rev_ar); + printf(" PCB Manufacturer Code : %d\n", + log_data->hw_rev_pbc_mc); + printf(" DRAM Manufacturer Code : %d\n", + log_data->hw_rev_dram_mc); + printf(" NAND Manufacturer Code : %d\n", + log_data->hw_rev_nand_mc); + printf(" PMIC 1 Manufacturer Code : %d\n", + log_data->hw_rev_pmic1_mc); + printf(" PMIC 2 Manufacturer Code : %d\n", + log_data->hw_rev_pmic2_mc); + printf(" Other Component 1 Manf Code : %d\n", + log_data->hw_rev_c1_mc); + printf(" Other Component 2 Manf Code : %d\n", + log_data->hw_rev_c2_mc); + printf(" Other Component 3 Manf Code : %d\n", + log_data->hw_rev_c3_mc); + printf(" Other Component 4 Manf Code : %d\n", + log_data->hw_rev_c4_mc); + printf(" Other Component 5 Manf Code : %d\n", + log_data->hw_rev_c5_mc); + printf(" Other Component 6 Manf Code : %d\n", + log_data->hw_rev_c6_mc); + printf(" Other Component 7 Manf Code : %d\n", + log_data->hw_rev_c7_mc); + printf(" Other Component 8 Manf Code : %d\n", + log_data->hw_rev_c8_mc); + printf(" Other Component 9 Manf Code : %d\n", + log_data->hw_rev_c9_mc); + + printf(" Device Manf Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_dev_mdi[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" ASIC Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_asic_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" PCB Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_pcb_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" DRAM Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_dram_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" NAND Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_nand_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" PMIC 1 Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_pmic1_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" PMIC 2 Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_pmic2_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" Component 1 Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_c1_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" Component 2 Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_c2_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" Component 3 Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_c3_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" Component 4 Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_c4_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" Component 5 Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_c5_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" Component 6 Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_c6_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" Component 7 Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_c7_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" Component 8 Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_c8_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" Component 9 Detailed Info : 0x"); + for (i = 0; i < 16; i++) { + printf("%02x", log_data->hw_rev_c9_di[i]); + if (i == 7) + printf(" 0x"); + } + printf("\n"); + printf(" Serial Number : 0x"); + for (i = 0; i < 32; i++) { + if ((i > 1) & !(i % 8)) + printf(" 0x"); + printf("%02x", log_data->hw_rev_sn[i]); + } + printf("\n"); + + printf(" Log Page Version : %d\n", log_data->hw_rev_version); + printf(" Log page GUID : 0x"); + printf("%"PRIx64"%"PRIx64"\n", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_guid[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_guid[0])); + printf("\n"); +} + +static void wdc_print_hw_rev_log_json(void *data) +{ + struct wdc_nvme_hw_rev_log *log_data = (struct wdc_nvme_hw_rev_log *)data; + struct json_object *root = json_create_object(); + char json_data[80]; + + json_object_add_value_uint(root, "Global Device HW Revision", + log_data->hw_rev_gdr); + json_object_add_value_uint(root, "ASIC HW Revision", + log_data->hw_rev_ar); + json_object_add_value_uint(root, "PCB Manufacturer Code", + log_data->hw_rev_pbc_mc); + json_object_add_value_uint(root, "DRAM Manufacturer Code", + log_data->hw_rev_dram_mc); + json_object_add_value_uint(root, "NAND Manufacturer Code", + log_data->hw_rev_nand_mc); + json_object_add_value_uint(root, "PMIC 1 Manufacturer Code", + log_data->hw_rev_pmic1_mc); + json_object_add_value_uint(root, "PMIC 2 Manufacturer Code", + log_data->hw_rev_pmic2_mc); + json_object_add_value_uint(root, "Other Component 1 Manf Code", + log_data->hw_rev_c1_mc); + json_object_add_value_uint(root, "Other Component 2 Manf Code", + log_data->hw_rev_c2_mc); + json_object_add_value_uint(root, "Other Component 3 Manf Code", + log_data->hw_rev_c3_mc); + json_object_add_value_uint(root, "Other Component 4 Manf Code", + log_data->hw_rev_c4_mc); + json_object_add_value_uint(root, "Other Component 5 Manf Code", + log_data->hw_rev_c5_mc); + json_object_add_value_uint(root, "Other Component 6 Manf Code", + log_data->hw_rev_c6_mc); + json_object_add_value_uint(root, "Other Component 7 Manf Code", + log_data->hw_rev_c7_mc); + json_object_add_value_uint(root, "Other Component 8 Manf Code", + log_data->hw_rev_c8_mc); + json_object_add_value_uint(root, "Other Component 9 Manf Code", + log_data->hw_rev_c9_mc); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_dev_mdi[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_dev_mdi[0])); + json_object_add_value_string(root, "Device Manf Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_asic_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_asic_di[0])); + json_object_add_value_string(root, "ASIC Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_pcb_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_pcb_di[0])); + json_object_add_value_string(root, "PCB Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_dram_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_dram_di[0])); + json_object_add_value_string(root, "DRAM Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_nand_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_nand_di[0])); + json_object_add_value_string(root, "NAND Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_pmic1_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_pmic1_di[0])); + json_object_add_value_string(root, "PMIC 1 Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_pmic2_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_pmic2_di[0])); + json_object_add_value_string(root, "PMIC 2 Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c1_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c1_di[0])); + json_object_add_value_string(root, "Component 1 Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c2_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c2_di[0])); + json_object_add_value_string(root, "Component 2 Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c3_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c3_di[0])); + json_object_add_value_string(root, "Component 3 Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c4_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c4_di[0])); + json_object_add_value_string(root, "Component 4 Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c5_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c5_di[0])); + json_object_add_value_string(root, "Component 5 Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c6_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c6_di[0])); + json_object_add_value_string(root, "Component 6 Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c7_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c7_di[0])); + json_object_add_value_string(root, "Component 7 Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c8_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c8_di[0])); + json_object_add_value_string(root, "Component 8 Detailed Info", json_data); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c9_di[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c9_di[0])); + json_object_add_value_string(root, "Component 9 Detailed Info", json_data); + + memset((void *)json_data, 0, 80); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"%"PRIx64"%"PRIx64"", + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_sn[0]), le64_to_cpu(*(uint64_t *)&log_data->hw_rev_sn[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_sn[16]), le64_to_cpu(*(uint64_t *)&log_data->hw_rev_sn[24])); + json_object_add_value_string(root, "Serial Number", json_data); + + json_object_add_value_uint(root, "Log Page Version", + le16_to_cpu(log_data->hw_rev_version)); + + memset((void *)json_data, 0, 40); + sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_guid[8]), + le64_to_cpu(*(uint64_t *)&log_data->hw_rev_guid[0])); + json_object_add_value_string(root, "Log Page GUID", json_data); + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static void wdc_print_ext_smart_cloud_log_normal(void *data, int mask) +{ + int i; + struct __packed wdc_nvme_ext_smart_log * ext_smart_log_ptr = (struct __packed wdc_nvme_ext_smart_log *)data; + + if (mask == WDC_SCA_V1_NAND_STATS) + printf(" NAND Statistics :-\n"); + else + printf(" SMART Cloud Attributes :-\n"); + + printf(" Physical Media Units Written TLC (Bytes): %s\n", + uint128_t_to_string(le128_to_cpu( + ext_smart_log_ptr->ext_smart_pmuwt))); + printf(" Physical Media Units Written SLC (Bytes): %s\n", + uint128_t_to_string(le128_to_cpu( + ext_smart_log_ptr->ext_smart_pmuws))); + printf(" Bad User NAND Block Count (Normalized) (Int) : %d\n", + le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_bunbc)); + printf(" Bad User NAND Block Count (Raw) (Int) : %"PRIu64"\n", + le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_bunbc & 0xFFFFFFFFFFFF0000)); + printf(" XOR Recovery Count (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_xrc)); + printf(" Uncorrectable Read Error Count (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_urec)); + if (mask == WDC_SCA_V1_ALL) { + printf(" SSD End to End correction counts (Corrected Errors) (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_eece)); + printf(" SSD End to End correction counts (Detected Errors) (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_eede)); + printf(" SSD End to End correction counts (Uncorrected E2E Errors) (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_eeue)); + printf(" System Data %% life-used : %d %%\n", + ext_smart_log_ptr->ext_smart_sdpu); + } + printf(" User data erase counts (Minimum TLC) (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_mnudec)); + printf(" User data erase counts (Maximum TLC) (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_mxudec)); + printf(" User data erase counts (Minimum SLC) (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_mnec)); + printf(" User data erase counts (Maximum SLC) (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_mxec)); + printf(" User data erase counts (Average SLC) (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_avec)); + printf(" User data erase counts (Average TLC) (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_avudec)); + printf(" Program Fail Count (Normalized) (Int) : %d\n", + le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_pfc)); + printf(" Program Fail Count (Raw) (Int) : %"PRIu64"\n", + le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_pfc & 0xFFFFFFFFFFFF0000)); + printf(" Erase Fail Count (Normalized) (Int) : %d\n", + le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_efc)); + printf(" Erase Fail Count (Raw) (Int) : %"PRIu64"\n", + le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_efc & 0xFFFFFFFFFFFF0000)); + if (mask == WDC_SCA_V1_ALL) { + printf(" PCIe Correctable Error Count (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_pcec)); + printf(" %% Free Blocks (User) (Int) : %d %%\n", + ext_smart_log_ptr->ext_smart_pfbu); + printf(" Security Version Number (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_svn)); + printf(" %% Free Blocks (System) (Int) : %d %%\n", + ext_smart_log_ptr->ext_smart_pfbs); + printf(" NVMe Stats (# Data Set Management/TRIM Commands Completed) (Int): %s\n", + uint128_t_to_string(le128_to_cpu( + ext_smart_log_ptr->ext_smart_dcc))); + printf(" Total Namespace Utilization (nvme0n1 NUSE) (Bytes) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_tnu)); + printf(" NVMe Stats (# NVMe Format Commands Completed) (Int) : %d\n", + le16_to_cpu(ext_smart_log_ptr->ext_smart_fcc)); + printf(" Background Back-Pressure Gauge(%%) (Int) : %d\n", + ext_smart_log_ptr->ext_smart_bbpg); + } + printf(" Total # of Soft ECC Error Count (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_seec)); + if (mask == WDC_SCA_V1_ALL) { + printf(" Total # of Read Refresh Count (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_rfsc)); + } + printf(" Bad System NAND Block Count (Normalized) (Int) : %d\n", + le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_bsnbc)); + printf(" Bad System NAND Block Count (Raw) (Int) : %"PRIu64"\n", + le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_bsnbc & 0xFFFFFFFFFFFF0000)); + printf(" Endurance Estimate (Total Writable Lifetime Bytes) (Bytes) : %s\n", + uint128_t_to_string( + le128_to_cpu(ext_smart_log_ptr->ext_smart_eest))); + if (mask == WDC_SCA_V1_ALL) { + printf(" Thermal Throttling Status & Count (Number of thermal throttling events) (Int) : %d\n", + le16_to_cpu(ext_smart_log_ptr->ext_smart_ttc)); + printf(" Total # Unaligned I/O (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_uio)); + } + printf(" Total Physical Media Units Read (Bytes) (Int) : %s\n", + uint128_t_to_string( + le128_to_cpu(ext_smart_log_ptr->ext_smart_pmur))); + if (mask == WDC_SCA_V1_ALL) { + printf(" Command Timeout (# of READ Commands > 5 Seconds) (Int) : %"PRIu32"\n", + le32_to_cpu(ext_smart_log_ptr->ext_smart_rtoc)); + printf(" Command Timeout (# of WRITE Commands > 5 Seconds) (Int) : %"PRIu32"\n", + le32_to_cpu(ext_smart_log_ptr->ext_smart_wtoc)); + printf(" Command Timeout (# of TRIM Commands > 5 Seconds) (Int) : %"PRIu32"\n", + le32_to_cpu(ext_smart_log_ptr->ext_smart_ttoc)); + printf(" Total PCIe Link Retraining Count (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_plrc)); + printf(" Active Power State Change Count (Int) : %"PRIu64"\n", + le64_to_cpu(ext_smart_log_ptr->ext_smart_pscc)); + } + printf(" Cloud Boot SSD Spec Version (Int) : %d.%d.%d.%d\n", + le16_to_cpu(ext_smart_log_ptr->ext_smart_maj), + le16_to_cpu(ext_smart_log_ptr->ext_smart_min), + le16_to_cpu(ext_smart_log_ptr->ext_smart_pt), + le16_to_cpu(ext_smart_log_ptr->ext_smart_err)); + printf(" Cloud Boot SSD HW Revision (Int) : %d.%d.%d.%d\n", + 0, 0, 0, 0); + if (mask == WDC_SCA_V1_ALL) { + printf(" FTL Unit Size : %"PRIu32"\n", + le32_to_cpu(ext_smart_log_ptr->ext_smart_ftlus)); + printf(" TCG Ownership Status : %"PRIu32"\n", + le32_to_cpu(ext_smart_log_ptr->ext_smart_tcgos)); + printf(" Log Page Version (Int) : %d\n", + le16_to_cpu(ext_smart_log_ptr->ext_smart_lpv)); + printf(" Log page GUID (Hex) : 0x"); + for (i = WDC_C0_GUID_LENGTH; i > 0; i--) + printf("%02x", ext_smart_log_ptr->ext_smart_lpg[i-1]); + printf("\n"); + } + printf("\n"); +} + +static void wdc_print_ext_smart_cloud_log_json(void *data, int mask) +{ + struct __packed wdc_nvme_ext_smart_log * ext_smart_log_ptr = + (struct __packed wdc_nvme_ext_smart_log *)data; + struct json_object *root = json_create_object(); + + json_object_add_value_uint128(root, "physical_media_units_bytes_tlc", + le128_to_cpu(ext_smart_log_ptr->ext_smart_pmuwt)); + json_object_add_value_uint128(root, "physical_media_units_bytes_slc", + le128_to_cpu(ext_smart_log_ptr->ext_smart_pmuws)); + json_object_add_value_uint(root, "bad_user_blocks_normalized", + le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_bunbc)); + json_object_add_value_uint64(root, "bad_user_blocks_raw", + le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_bunbc & 0xFFFFFFFFFFFF0000)); + json_object_add_value_uint64(root, "xor_recovery_count", + le64_to_cpu(ext_smart_log_ptr->ext_smart_xrc)); + json_object_add_value_uint64(root, "uncorrectable_read_errors", + le64_to_cpu(ext_smart_log_ptr->ext_smart_urec)); + if (mask == WDC_SCA_V1_ALL) { + json_object_add_value_uint64(root, "corrected_e2e_errors", + le64_to_cpu(ext_smart_log_ptr->ext_smart_eece)); + json_object_add_value_uint64(root, "detected_e2e_errors", + le64_to_cpu(ext_smart_log_ptr->ext_smart_eede)); + json_object_add_value_uint64(root, "uncorrected_e2e_errors", + le64_to_cpu(ext_smart_log_ptr->ext_smart_eeue)); + json_object_add_value_uint(root, "system_data_life_used_pct", + (__u8)ext_smart_log_ptr->ext_smart_sdpu); + } + json_object_add_value_uint64(root, "min_slc_user_data_erase_count", + le64_to_cpu(ext_smart_log_ptr->ext_smart_mnec)); + json_object_add_value_uint64(root, "min_tlc_user_data_erase_count", + le64_to_cpu(ext_smart_log_ptr->ext_smart_mnudec)); + json_object_add_value_uint64(root, "max_slc_user_data_erase_count", + le64_to_cpu(ext_smart_log_ptr->ext_smart_mxec)); + json_object_add_value_uint64(root, "max_tlc_user_data_erase_count", + le64_to_cpu(ext_smart_log_ptr->ext_smart_mxudec)); + json_object_add_value_uint64(root, "avg_slc_user_data_erase_count", + le64_to_cpu(ext_smart_log_ptr->ext_smart_avec)); + json_object_add_value_uint64(root, "avg_tlc_user_data_erase_count", + le64_to_cpu(ext_smart_log_ptr->ext_smart_avudec)); + json_object_add_value_uint(root, "program_fail_count_normalized", + le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_pfc)); + json_object_add_value_uint64(root, "program_fail_count_raw", + le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_pfc & 0xFFFFFFFFFFFF0000)); + json_object_add_value_uint(root, "erase_fail_count_normalized", + le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_efc)); + json_object_add_value_uint64(root, "erase_fail_count_raw", + le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_efc & 0xFFFFFFFFFFFF0000)); + if (mask == WDC_SCA_V1_ALL) { + json_object_add_value_uint64(root, "pcie_correctable_errors", + le64_to_cpu(ext_smart_log_ptr->ext_smart_pcec)); + json_object_add_value_uint(root, "pct_free_blocks_user", + (__u8)ext_smart_log_ptr->ext_smart_pfbu); + json_object_add_value_uint64(root, "security_version", + le64_to_cpu(ext_smart_log_ptr->ext_smart_svn)); + json_object_add_value_uint(root, "pct_free_blocks_system", + (__u8)ext_smart_log_ptr->ext_smart_pfbs); + json_object_add_value_uint128(root, "num_of_trim_commands", + le128_to_cpu(ext_smart_log_ptr->ext_smart_dcc)); + json_object_add_value_uint64(root, "total_nuse_bytes", + le64_to_cpu(ext_smart_log_ptr->ext_smart_tnu)); + json_object_add_value_uint(root, "num_of_format_commands", + le16_to_cpu(ext_smart_log_ptr->ext_smart_fcc)); + json_object_add_value_uint(root, "background_pressure_gauge", + (__u8)ext_smart_log_ptr->ext_smart_bbpg); + } + json_object_add_value_uint64(root, "soft_ecc_error_count", + le64_to_cpu(ext_smart_log_ptr->ext_smart_seec)); + if (mask == WDC_SCA_V1_ALL) + json_object_add_value_uint64(root, "read_refresh_count", + le64_to_cpu(ext_smart_log_ptr->ext_smart_rfsc)); + json_object_add_value_uint(root, "bad_system_block_normalized", + le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_bsnbc)); + json_object_add_value_uint64(root, "bad_system_block_raw", + le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_bsnbc & 0xFFFFFFFFFFFF0000)); + json_object_add_value_uint128(root, "endurance_est_bytes", + le128_to_cpu(ext_smart_log_ptr->ext_smart_eest)); + if (mask == WDC_SCA_V1_ALL) { + json_object_add_value_uint(root, "num_throttling_events", + le16_to_cpu(ext_smart_log_ptr->ext_smart_ttc)); + json_object_add_value_uint64(root, "total_unaligned_io", + le64_to_cpu(ext_smart_log_ptr->ext_smart_uio)); + } + json_object_add_value_uint128(root, "physical_media_units_read_bytes", + le128_to_cpu(ext_smart_log_ptr->ext_smart_pmur)); + if (mask == WDC_SCA_V1_ALL) { + json_object_add_value_uint(root, "num_read_timeouts", + le32_to_cpu(ext_smart_log_ptr->ext_smart_rtoc)); + json_object_add_value_uint(root, "num_write_timeouts", + le32_to_cpu(ext_smart_log_ptr->ext_smart_wtoc)); + json_object_add_value_uint(root, "num_trim_timeouts", + le32_to_cpu(ext_smart_log_ptr->ext_smart_ttoc)); + json_object_add_value_uint64(root, "pcie_link_retrain_count", + le64_to_cpu(ext_smart_log_ptr->ext_smart_plrc)); + json_object_add_value_uint64(root, "active_power_state_change_count", + le64_to_cpu(ext_smart_log_ptr->ext_smart_pscc)); + } + char vers_str[40]; + + memset((void *)vers_str, 0, 40); + sprintf((char *)vers_str, "%d.%d.%d.%d", + le16_to_cpu(ext_smart_log_ptr->ext_smart_maj), + le16_to_cpu(ext_smart_log_ptr->ext_smart_min), + le16_to_cpu(ext_smart_log_ptr->ext_smart_pt), + le16_to_cpu(ext_smart_log_ptr->ext_smart_err)); + json_object_add_value_string(root, "cloud_boot_ssd_spec_ver", vers_str); + memset((void *)vers_str, 0, 40); + sprintf((char *)vers_str, "%d.%d.%d.%d", 0, 0, 0, 0); + json_object_add_value_string(root, "cloud_boot_ssd_hw_ver", vers_str); + + if (mask == WDC_SCA_V1_ALL) { + json_object_add_value_uint(root, "ftl_unit_size", + le32_to_cpu(ext_smart_log_ptr->ext_smart_ftlus)); + json_object_add_value_uint(root, "tcg_ownership_status", + le32_to_cpu(ext_smart_log_ptr->ext_smart_tcgos)); + json_object_add_value_uint(root, "log_page_ver", + le16_to_cpu(ext_smart_log_ptr->ext_smart_lpv)); + char guid[40]; + + memset((void *)guid, 0, 40); + sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"", + le64_to_cpu(*(uint64_t *)&ext_smart_log_ptr->ext_smart_lpg[8]), + le64_to_cpu(*(uint64_t *)&ext_smart_log_ptr->ext_smart_lpg[0])); + json_object_add_value_string(root, "log_page_guid", guid); + } + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static void wdc_print_smart_cloud_attr_C0_normal(void *data) +{ + __u8 *log_data = (__u8 *)data; + uint16_t smart_log_ver = 0; + + printf(" SMART Cloud Attributes :-\n"); + + printf(" Physical media units written : %s\n", + uint128_t_to_string(le128_to_cpu(&log_data[SCAO_PMUW]))); + printf(" Physical media units read : %s\n", + uint128_t_to_string(le128_to_cpu(&log_data[SCAO_PMUR]))); + printf(" Bad user nand blocks Raw : %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BUNBR] & 0x0000FFFFFFFFFFFF)); + printf(" Bad user nand blocks Normalized : %d\n", + (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BUNBN])); + printf(" Bad system nand blocks Raw : %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BSNBR] & 0x0000FFFFFFFFFFFF)); + printf(" Bad system nand blocks Normalized : %d\n", + (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BSNBN])); + printf(" XOR recovery count : %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_XRC])); + printf(" Uncorrectable read error count : %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UREC])); + printf(" Soft ecc error count : %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SEEC])); + printf(" End to end corrected errors : %"PRIu32"\n", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EECE])); + printf(" End to end detected errors : %"PRIu32"\n", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EEDC])); + printf(" System data percent used : %d\n", (__u8)log_data[SCAO_SDPU]); + printf(" Refresh counts : %"PRIu64"\n", + (uint64_t)(le64_to_cpu(*(uint64_t *)&log_data[SCAO_RFSC]) & 0x00FFFFFFFFFFFFFF)); + printf(" Max User data erase counts : %"PRIu32"\n", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MXUDEC])); + printf(" Min User data erase counts : %"PRIu32"\n", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MNUDEC])); + printf(" Number of Thermal throttling events : %d\n", (__u8)log_data[SCAO_NTTE]); + printf(" Current throttling status : 0x%x\n", (__u8)log_data[SCAO_CTS]); + printf(" PCIe correctable error count : %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PCEC])); + printf(" Incomplete shutdowns : %"PRIu32"\n", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_ICS])); + printf(" Percent free blocks : %d\n", (__u8)log_data[SCAO_PFB]); + printf(" Capacitor health : %"PRIu16"\n", + (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_CPH])); + printf(" Unaligned I/O : %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UIO])); + printf(" Security Version Number : %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SVN])); + printf(" NUSE Namespace utilization : %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_NUSE])); + printf(" PLP start count : %s\n", + uint128_t_to_string(le128_to_cpu(&log_data[SCAO_PSC]))); + printf(" Endurance estimate : %s\n", + uint128_t_to_string(le128_to_cpu(&log_data[SCAO_EEST]))); + smart_log_ver = (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_LPV]); + printf(" Log page version : %"PRIu16"\n", smart_log_ver); + printf(" Log page GUID : 0x"); + printf("%"PRIx64"%"PRIx64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG + 8]), + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG])); + if (smart_log_ver > 2) { + printf(" Errata Version Field : %d\n", + (__u8)log_data[SCAO_EVF]); + printf(" Point Version Field : %"PRIu16"\n", + (uint16_t)log_data[SCAO_PVF]); + printf(" Minor Version Field : %"PRIu16"\n", + (uint16_t)log_data[SCAO_MIVF]); + printf(" Major Version Field : %d\n", + (__u8)log_data[SCAO_MAVF]); + printf(" NVMe Errata Version : %d\n", + (__u8)log_data[SCAO_NEV]); + printf(" PCIe Link Retraining Count : %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PLRC])); + } + if (smart_log_ver > 3) { + printf(" Power State Change Count : %"PRIu64"\n", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PSCC])); + } + printf("\n"); +} + +static void wdc_print_smart_cloud_attr_C0_json(void *data) +{ + __u8 *log_data = (__u8 *)data; + struct json_object *root = json_create_object(); + uint16_t smart_log_ver = 0; + + json_object_add_value_uint128(root, "Physical media units written", + le128_to_cpu(&log_data[SCAO_PMUW])); + json_object_add_value_uint128(root, "Physical media units read", + le128_to_cpu(&log_data[SCAO_PMUR])); + json_object_add_value_uint64(root, "Bad user nand blocks - Raw", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BUNBR] & 0x0000FFFFFFFFFFFF)); + json_object_add_value_uint(root, "Bad user nand blocks - Normalized", + (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BUNBN])); + json_object_add_value_uint64(root, "Bad system nand blocks - Raw", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BSNBR] & 0x0000FFFFFFFFFFFF)); + json_object_add_value_uint(root, "Bad system nand blocks - Normalized", + (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BSNBN])); + json_object_add_value_uint64(root, "XOR recovery count", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_XRC])); + json_object_add_value_uint64(root, "Uncorrectable read error count", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UREC])); + json_object_add_value_uint64(root, "Soft ecc error count", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SEEC])); + json_object_add_value_uint(root, "End to end corrected errors", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EECE])); + json_object_add_value_uint(root, "End to end detected errors", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EEDC])); + json_object_add_value_uint(root, "System data percent used", + (__u8)log_data[SCAO_SDPU]); + json_object_add_value_uint64(root, "Refresh counts", + (uint64_t)(le64_to_cpu(*(uint64_t *)&log_data[SCAO_RFSC]) & 0x00FFFFFFFFFFFFFF)); + json_object_add_value_uint(root, "Max User data erase counts", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MXUDEC])); + json_object_add_value_uint(root, "Min User data erase counts", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MNUDEC])); + json_object_add_value_uint(root, "Number of Thermal throttling events", + (__u8)log_data[SCAO_NTTE]); + json_object_add_value_uint(root, "Current throttling status", + (__u8)log_data[SCAO_CTS]); + json_object_add_value_uint64(root, "PCIe correctable error count", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PCEC])); + json_object_add_value_uint(root, "Incomplete shutdowns", + (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_ICS])); + json_object_add_value_uint(root, "Percent free blocks", + (__u8)log_data[SCAO_PFB]); + json_object_add_value_uint(root, "Capacitor health", + (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_CPH])); + json_object_add_value_uint64(root, "Unaligned I/O", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UIO])); + json_object_add_value_uint64(root, "Security Version Number", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SVN])); + json_object_add_value_uint64(root, "NUSE - Namespace utilization", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_NUSE])); + json_object_add_value_uint128(root, "PLP start count", + le128_to_cpu(&log_data[SCAO_PSC])); + json_object_add_value_uint128(root, "Endurance estimate", + le128_to_cpu(&log_data[SCAO_EEST])); + smart_log_ver = (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_LPV]); + json_object_add_value_uint(root, "Log page version", smart_log_ver); + char guid[40]; + + memset((void *)guid, 0, 40); + sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG + 8]), + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG])); + json_object_add_value_string(root, "Log page GUID", guid); + if (smart_log_ver > 2) { + json_object_add_value_uint(root, "Errata Version Field", + (__u8)log_data[SCAO_EVF]); + json_object_add_value_uint(root, "Point Version Field", + (uint16_t)log_data[SCAO_PVF]); + json_object_add_value_uint(root, "Minor Version Field", + (uint16_t)log_data[SCAO_MIVF]); + json_object_add_value_uint(root, "Major Version Field", + (__u8)log_data[SCAO_MAVF]); + json_object_add_value_uint(root, "NVMe Errata Version", + (__u8)log_data[SCAO_NEV]); + json_object_add_value_uint64(root, "PCIe Link Retraining Count", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PLRC])); + } + if (smart_log_ver > 3) { + json_object_add_value_uint64(root, "Power State Change Count", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PSCC])); + } + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static void wdc_print_eol_c0_normal(void *data) +{ + + __u8 *log_data = (__u8 *)data; + + printf(" End of Life Log Page 0xC0 :-\n"); + + printf(" Realloc Block Count %"PRIu32"\n", + (uint32_t)le32_to_cpu(log_data[EOL_RBC])); + printf(" ECC Rate %"PRIu32"\n", + (uint32_t)le32_to_cpu(log_data[EOL_ECCR])); + printf(" Write Amp %"PRIu32"\n", + (uint32_t)le32_to_cpu(log_data[EOL_WRA])); + printf(" Percent Life Remaining %"PRIu32"\n", + (uint32_t)le32_to_cpu(log_data[EOL_PLR])); + printf(" Program Fail Count %"PRIu32"\n", + (uint32_t)le32_to_cpu(log_data[EOL_PFC])); + printf(" Erase Fail Count %"PRIu32"\n", + (uint32_t)le32_to_cpu(log_data[EOL_EFC])); + printf(" Raw Read Error Rate %"PRIu32"\n", + (uint32_t)le32_to_cpu(log_data[EOL_RRER])); + +} + +static void wdc_print_eol_c0_json(void *data) +{ + __u8 *log_data = (__u8 *)data; + struct json_object *root = json_create_object(); + + json_object_add_value_uint(root, "Realloc Block Count", + (uint32_t)le32_to_cpu(log_data[EOL_RBC])); + json_object_add_value_uint(root, "ECC Rate", + (uint32_t)le32_to_cpu(log_data[EOL_ECCR])); + json_object_add_value_uint(root, "Write Amp", + (uint32_t)le32_to_cpu(log_data[EOL_WRA])); + json_object_add_value_uint(root, "Percent Life Remaining", + (uint32_t)le32_to_cpu(log_data[EOL_PLR])); + json_object_add_value_uint(root, "Program Fail Count", + (uint32_t)le32_to_cpu(log_data[EOL_PFC])); + json_object_add_value_uint(root, "Erase Fail Count", + (uint32_t)le32_to_cpu(log_data[EOL_EFC])); + json_object_add_value_uint(root, "Raw Read Error Rate", + (uint32_t)le32_to_cpu(log_data[EOL_RRER])); + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static int wdc_print_ext_smart_cloud_log(void *data, int fmt) +{ + if (!data) { + fprintf(stderr, "ERROR: WDC: Invalid buffer to read 0xC0 V1 log\n"); + return -1; + } + switch (fmt) { + case NORMAL: + wdc_print_ext_smart_cloud_log_normal(data, WDC_SCA_V1_ALL); + break; + case JSON: + wdc_print_ext_smart_cloud_log_json(data, WDC_SCA_V1_ALL); + break; + } + return 0; +} + +static int wdc_print_c0_cloud_attr_log(void *data, int fmt) +{ + if (!data) { + fprintf(stderr, "ERROR: WDC: Invalid buffer to read 0xC0 log\n"); + return -1; + } + switch (fmt) { + case NORMAL: + wdc_print_smart_cloud_attr_C0_normal(data); + break; + case JSON: + wdc_print_smart_cloud_attr_C0_json(data); + break; + } + return 0; +} + +static int wdc_print_c0_eol_log(void *data, int fmt) +{ + if (!data) { + fprintf(stderr, "ERROR: WDC: Invalid buffer to read 0xC0 log\n"); + return -1; + } + switch (fmt) { + case NORMAL: + wdc_print_eol_c0_normal(data); + break; + case JSON: + wdc_print_eol_c0_json(data); + break; + } + return 0; +} + +static int wdc_get_c0_log_page_sn_customer_id_0x100X(struct nvme_dev *dev, int uuid_index, + char *format, __u32 namespace_id, int fmt) +{ + int ret; + __u8 *data; + int i; + + if (!uuid_index) { + data = (__u8 *)malloc(sizeof(__u8) * WDC_NVME_SMART_CLOUD_ATTR_LEN); + if (!data) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + return -1; + } + + if (namespace_id == NVME_NSID_ALL) { + ret = nvme_get_nsid(dev_fd(dev), &namespace_id); + if (ret < 0) + namespace_id = NVME_NSID_ALL; + } + + /* Get the 0xC0 log data */ + struct nvme_get_log_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .lid = WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID, + .nsid = namespace_id, + .lpo = 0, + .lsp = NVME_LOG_LSP_NONE, + .lsi = 0, + .rae = false, + .uuidx = uuid_index, + .csi = NVME_CSI_NVM, + .ot = false, + .len = WDC_NVME_SMART_CLOUD_ATTR_LEN, + .log = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + ret = nvme_get_log(&args); + + if (strcmp(format, "json")) + nvme_show_status(ret); + + if (!ret) { + /* Verify GUID matches */ + for (i = 0; i < 16; i++) { + if (scao_guid[i] != data[SCAO_LPG + i]) { + fprintf(stderr, "ERROR: WDC: Unknown GUID in C0 Log Page data\n"); + int j; + + fprintf(stderr, "ERROR: WDC: Expected GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", scao_guid[j]); + fprintf(stderr, "\nERROR: WDC: Actual GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", data[SCAO_LPG + j]); + fprintf(stderr, "\n"); + + ret = -1; + break; + } + } + + if (!ret) + /* parse the data */ + wdc_print_c0_cloud_attr_log(data, fmt); + } else { + fprintf(stderr, "ERROR: WDC: Unable to read C0 Log Page data\n"); + ret = -1; + } + + free(data); + } else if (uuid_index == 1) { + data = (__u8 *)malloc(sizeof(__u8) * WDC_NVME_EOL_STATUS_LOG_LEN); + if (!data) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + return -1; + } + + /* Get the 0xC0 log data */ + struct nvme_get_log_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .lid = WDC_NVME_GET_EOL_STATUS_LOG_OPCODE, + .nsid = NVME_NSID_ALL, + .lpo = 0, + .lsp = NVME_LOG_LSP_NONE, + .lsi = 0, + .rae = false, + .uuidx = uuid_index, + .csi = NVME_CSI_NVM, + .ot = false, + .len = WDC_NVME_EOL_STATUS_LOG_LEN, + .log = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + ret = nvme_get_log(&args); + + if (strcmp(format, "json")) + nvme_show_status(ret); + + if (!ret) { + /* parse the data */ + wdc_print_c0_eol_log(data, fmt); + } else { + fprintf(stderr, "ERROR: WDC: Unable to read C0 Log Page data\n"); + ret = -1; + } + + free(data); + } else { + fprintf(stderr, "ERROR: WDC: Unknown uuid index\n"); + ret = -1; + } + + return ret; +} + +static int wdc_get_c0_log_page_sn(nvme_root_t r, struct nvme_dev *dev, int uuid_index, char *format, + __u32 namespace_id, int fmt) +{ + int ret = 0; + __u32 cust_id; + __u8 *data; + + cust_id = wdc_get_fw_cust_id(r, dev); + if (cust_id == WDC_INVALID_CUSTOMER_ID) { + fprintf(stderr, "%s: ERROR: WDC: invalid customer id\n", __func__); + return -1; + } + + if ((cust_id == WDC_CUSTOMER_ID_0x1004) || (cust_id == WDC_CUSTOMER_ID_0x1008) || + (cust_id == WDC_CUSTOMER_ID_0x1005)) { + ret = wdc_get_c0_log_page_sn_customer_id_0x100X(dev, uuid_index, format, + namespace_id, fmt); + } else { + data = (__u8 *)malloc(sizeof(__u8) * WDC_NVME_EOL_STATUS_LOG_LEN); + if (!data) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + return -1; + } + + /* Get the 0xC0 log data */ + ret = nvme_get_log_simple(dev_fd(dev), + WDC_NVME_GET_EOL_STATUS_LOG_OPCODE, + WDC_NVME_EOL_STATUS_LOG_LEN, + data); + + if (strcmp(format, "json")) + nvme_show_status(ret); + + if (!ret) { + /* parse the data */ + wdc_print_c0_eol_log(data, fmt); + } else { + fprintf(stderr, "ERROR: WDC: Unable to read C0 Log Page data\n"); + ret = -1; + } + + free(data); + } + + return ret; +} + +static int wdc_get_c0_log_page(nvme_root_t r, struct nvme_dev *dev, char *format, int uuid_index, + __u32 namespace_id) +{ + uint32_t device_id, read_vendor_id; + enum nvme_print_flags fmt; + int ret; + __u8 *data; + __u8 log_id; + __u32 length; + + if (!wdc_check_device(r, dev)) + return -1; + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: invalid output format\n"); + return ret; + } + + ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id); + + switch (device_id) { + case WDC_NVME_SN640_DEV_ID: + fallthrough; + case WDC_NVME_SN640_DEV_ID_1: + fallthrough; + case WDC_NVME_SN640_DEV_ID_2: + fallthrough; + case WDC_NVME_SN640_DEV_ID_3: + fallthrough; + case WDC_NVME_SN840_DEV_ID: + fallthrough; + case WDC_NVME_SN840_DEV_ID_1: + fallthrough; + case WDC_NVME_SN860_DEV_ID: + fallthrough; + case WDC_NVME_SN560_DEV_ID_1: + fallthrough; + case WDC_NVME_SN560_DEV_ID_2: + fallthrough; + case WDC_NVME_SN560_DEV_ID_3: + fallthrough; + case WDC_NVME_SN550_DEV_ID: + ret = wdc_get_c0_log_page_sn(r, dev, uuid_index, format, namespace_id, fmt); + break; + + case WDC_NVME_SN650_DEV_ID: + fallthrough; + case WDC_NVME_SN650_DEV_ID_1: + fallthrough; + case WDC_NVME_SN650_DEV_ID_2: + fallthrough; + case WDC_NVME_SN650_DEV_ID_3: + fallthrough; + case WDC_NVME_SN650_DEV_ID_4: + fallthrough; + case WDC_NVME_SN655_DEV_ID: + if (uuid_index == 0) { + log_id = WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID; + length = WDC_NVME_SMART_CLOUD_ATTR_LEN; + } else { + log_id = WDC_NVME_GET_EOL_STATUS_LOG_OPCODE; + length = WDC_NVME_EOL_STATUS_LOG_LEN; + } + + data = (__u8 *)malloc(sizeof(__u8) * length); + if (!data) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + return -1; + } + + if (namespace_id == NVME_NSID_ALL) { + ret = nvme_get_nsid(dev_fd(dev), &namespace_id); + if (ret < 0) + namespace_id = NVME_NSID_ALL; + } + + /* Get the 0xC0 log data */ + struct nvme_get_log_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .lid = log_id, + .nsid = namespace_id, + .lpo = 0, + .lsp = NVME_LOG_LSP_NONE, + .lsi = 0, + .rae = false, + .uuidx = uuid_index, + .csi = NVME_CSI_NVM, + .ot = false, + .len = length, + .log = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + ret = nvme_get_log(&args); + + if (strcmp(format, "json")) + nvme_show_status(ret); + + if (!ret) { + /* parse the data */ + if (uuid_index == 0) + wdc_print_c0_cloud_attr_log(data, fmt); + else + wdc_print_c0_eol_log(data, fmt); + } else { + fprintf(stderr, "ERROR: WDC: Unable to read C0 Log Page data "); + fprintf(stderr, "with uuid index %d\n", uuid_index); + ret = -1; + } + free(data); + break; + + case WDC_NVME_ZN350_DEV_ID: + fallthrough; + case WDC_NVME_ZN350_DEV_ID_1: + data = (__u8 *)malloc(sizeof(__u8) * WDC_NVME_SMART_CLOUD_ATTR_LEN); + if (!data) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + return -1; + } + + /* Get the 0xC0 log data */ + ret = nvme_get_log_simple(dev_fd(dev), + WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID, + WDC_NVME_SMART_CLOUD_ATTR_LEN, data); + + if (strcmp(format, "json")) + nvme_show_status(ret); + + if (!ret) { + /* parse the data */ + wdc_print_c0_cloud_attr_log(data, fmt); + } else { + fprintf(stderr, "ERROR: WDC: Unable to read C0 Log Page data\n"); + ret = -1; + } + + free(data); + break; + case WDC_NVME_SN820CL_DEV_ID: + /* Get the 0xC0 Extended Smart Cloud Attribute log data */ + data = NULL; + ret = nvme_get_ext_smart_cloud_log(dev_fd(dev), &data, + uuid_index, namespace_id); + + if (strcmp(format, "json")) + nvme_show_status(ret); + + if (!ret) { + /* parse the data */ + wdc_print_ext_smart_cloud_log(data, fmt); + } else { + fprintf(stderr, "ERROR: WDC: Unable to read C0 Log Page V1 data\n"); + ret = -1; + } + + if (data) + free(data); + break; + default: + fprintf(stderr, "ERROR: WDC: Unknown device id - 0x%x\n", device_id); + ret = -1; + break; + + } + + return ret; +} + +static int wdc_print_latency_monitor_log(struct nvme_dev *dev, + struct wdc_ssd_latency_monitor_log *log_data, + int fmt) +{ + if (!log_data) { + fprintf(stderr, "ERROR: WDC: Invalid C3 log data buffer\n"); + return -1; + } + switch (fmt) { + case NORMAL: + wdc_print_latency_monitor_log_normal(dev, log_data); + break; + case JSON: + wdc_print_latency_monitor_log_json(log_data); + break; + } + return 0; +} + +static int wdc_print_error_rec_log(struct wdc_ocp_c1_error_recovery_log *log_data, int fmt) +{ + if (!log_data) { + fprintf(stderr, "ERROR: WDC: Invalid C1 log data buffer\n"); + return -1; + } + switch (fmt) { + case NORMAL: + wdc_print_error_rec_log_normal(log_data); + break; + case JSON: + wdc_print_error_rec_log_json(log_data); + break; + } + return 0; +} + +static int wdc_print_dev_cap_log(struct wdc_ocp_C4_dev_cap_log *log_data, int fmt) +{ + if (!log_data) { + fprintf(stderr, "ERROR: WDC: Invalid C4 log data buffer\n"); + return -1; + } + switch (fmt) { + case NORMAL: + wdc_print_dev_cap_log_normal(log_data); + break; + case JSON: + wdc_print_dev_cap_log_json(log_data); + break; + } + return 0; +} + +static int wdc_print_unsupported_reqs_log(struct wdc_ocp_C5_unsupported_reqs *log_data, int fmt) +{ + if (!log_data) { + fprintf(stderr, "ERROR: WDC: Invalid C5 log data buffer\n"); + return -1; + } + switch (fmt) { + case NORMAL: + wdc_print_unsupported_reqs_log_normal(log_data); + break; + case JSON: + wdc_print_unsupported_reqs_log_json(log_data); + break; + } + return 0; +} + +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(struct nvme_dev *dev, 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(dev, bd_data); + break; + case JSON: + wdc_print_bd_ca_log_json(bd_data); + break; + default: + fprintf(stderr, "ERROR: WDC: Unknown format - %d\n", fmt); + return -1; + } + 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(__u8 *data, int num_entries, int fmt, + __u32 cust_id, __u32 vendor_id, + __u32 device_id) +{ + if (!data) { + fprintf(stderr, "ERROR: WDC: Invalid buffer to read fw activate history entries\n"); + return -1; + } + + switch (fmt) { + case NORMAL: + wdc_print_fw_act_history_log_normal(data, num_entries, cust_id, + vendor_id, device_id); + break; + case JSON: + wdc_print_fw_act_history_log_json(data, num_entries, cust_id, + vendor_id, device_id); + break; + } + return 0; +} + +static int wdc_get_ca_log_page(nvme_root_t r, struct nvme_dev *dev, char *format) +{ + uint32_t read_device_id, read_vendor_id; + struct wdc_ssd_ca_perf_stats *perf; + enum nvme_print_flags fmt; + __u32 cust_id; + __u8 *data; + int ret; + + if (!wdc_check_device(r, dev)) + return -1; + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: invalid output format\n"); + return ret; + } + + /* verify the 0xCA log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE) == false) { + fprintf(stderr, "ERROR: WDC: 0xCA Log Page not supported\n"); + return -1; + } + + /* get the FW customer id */ + cust_id = wdc_get_fw_cust_id(r, dev); + if (cust_id == WDC_INVALID_CUSTOMER_ID) { + fprintf(stderr, "%s: ERROR: WDC: invalid customer id\n", __func__); + return -1; + } + + ret = wdc_get_pci_ids(r, dev, &read_device_id, &read_vendor_id); + + switch (read_device_id) { + case WDC_NVME_SN200_DEV_ID: + if (cust_id == WDC_CUSTOMER_ID_0x1005) { + data = (__u8 *)malloc(sizeof(__u8) * WDC_FB_CA_LOG_BUF_LEN); + if (!data) { + 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_simple(dev_fd(dev), + WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE, + WDC_FB_CA_LOG_BUF_LEN, data); + if (strcmp(format, "json")) + nvme_show_status(ret); + + if (!ret) { + /* 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 = 0x%x\n", cust_id); + return -1; + } + break; + case WDC_NVME_SN640_DEV_ID: + fallthrough; + case WDC_NVME_SN640_DEV_ID_1: + fallthrough; + case WDC_NVME_SN640_DEV_ID_2: + fallthrough; + case WDC_NVME_SN640_DEV_ID_3: + fallthrough; + case WDC_NVME_SN840_DEV_ID: + fallthrough; + case WDC_NVME_SN840_DEV_ID_1: + fallthrough; + case WDC_NVME_SN860_DEV_ID: + if (cust_id == WDC_CUSTOMER_ID_0x1005) { + data = (__u8 *)malloc(sizeof(__u8) * WDC_FB_CA_LOG_BUF_LEN); + if (!data) { + 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_simple(dev_fd(dev), + WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE, + WDC_FB_CA_LOG_BUF_LEN, data); + if (strcmp(format, "json")) + nvme_show_status(ret); + + if (!ret) { + /* 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) || + (cust_id == WDC_CUSTOMER_ID_BD)) { + data = (__u8 *)malloc(sizeof(__u8) * WDC_BD_CA_LOG_BUF_LEN); + if (!data) { + 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_simple(dev_fd(dev), + WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE, + WDC_BD_CA_LOG_BUF_LEN, data); + if (strcmp(format, "json")) + nvme_show_status(ret); + + if (!ret) { + /* parse the data */ + ret = wdc_print_bd_ca_log(dev, data, 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 = 0x%x\n", cust_id); + return -1; + } + break; + default: + fprintf(stderr, "ERROR: WDC: Log page 0xCA not supported for this device\n"); + return -1; + } + + free(data); + return ret; +} + +static int wdc_get_c1_log_page(nvme_root_t r, struct nvme_dev *dev, + char *format, uint8_t interval) +{ + struct wdc_log_page_subpage_header *sph; + struct wdc_ssd_perf_stats *perf; + struct wdc_log_page_header *l; + enum nvme_print_flags fmt; + int total_subpages; + int skip_cnt = 4; + __u8 *data; + __u8 *p; + int i; + int ret; + + if (!wdc_check_device(r, dev)) + return -1; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: invalid output format\n"); + return ret; + } + + if (interval < 1 || interval > 15) { + fprintf(stderr, "ERROR: WDC: interval out of range [1-15]\n"); + return -1; + } + + data = (__u8 *)malloc(sizeof(__u8) * WDC_ADD_LOG_BUF_LEN); + if (!data) { + 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_simple(dev_fd(dev), WDC_NVME_ADD_LOG_OPCODE, + WDC_ADD_LOG_BUF_LEN, data); + if (strcmp(format, "json")) + nvme_show_status(ret); + if (!ret) { + 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_c3_log_page(nvme_root_t r, struct nvme_dev *dev, char *format) +{ + struct wdc_ssd_latency_monitor_log *log_data; + enum nvme_print_flags fmt; + __u8 *data; + int ret; + int i; + + if (!wdc_check_device(r, dev)) + return -1; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: invalid output format\n"); + return ret; + } + + data = (__u8 *)malloc(sizeof(__u8) * WDC_LATENCY_MON_LOG_BUF_LEN); + if (!data) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + return -1; + } + memset(data, 0, sizeof(__u8) * WDC_LATENCY_MON_LOG_BUF_LEN); + + ret = nvme_get_log_simple(dev_fd(dev), WDC_LATENCY_MON_LOG_ID, + WDC_LATENCY_MON_LOG_BUF_LEN, data); + + if (strcmp(format, "json")) + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret, false), ret); + + if (!ret) { + log_data = (struct wdc_ssd_latency_monitor_log *)data; + + /* check log page version */ + if (log_data->log_page_version != WDC_LATENCY_MON_VERSION) { + fprintf(stderr, "ERROR: WDC: invalid latency monitor version\n"); + ret = -1; + goto out; + } + + /* check log page guid */ + /* Verify GUID matches */ + for (i = 0; i < 16; i++) { + if (wdc_lat_mon_guid[i] != log_data->log_page_guid[i]) { + fprintf(stderr, "ERROR: WDC: Unknown GUID in C3 Log Page data\n"); + int j; + + fprintf(stderr, "ERROR: WDC: Expected GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", wdc_lat_mon_guid[j]); + fprintf(stderr, "\nERROR: WDC: Actual GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", log_data->log_page_guid[j]); + fprintf(stderr, "\n"); + + ret = -1; + goto out; + } + } + + /* parse the data */ + wdc_print_latency_monitor_log(dev, log_data, fmt); + } else { + fprintf(stderr, "ERROR: WDC: Unable to read C3 data from buffer\n"); + } + +out: + free(data); + return ret; + +} + +static int wdc_get_ocp_c1_log_page(nvme_root_t r, struct nvme_dev *dev, char *format) +{ + struct wdc_ocp_c1_error_recovery_log *log_data; + enum nvme_print_flags fmt; + __u8 *data; + int ret; + int i; + + if (!wdc_check_device(r, dev)) + return -1; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: invalid output format\n"); + return ret; + } + + data = (__u8 *)malloc(sizeof(__u8) * WDC_ERROR_REC_LOG_BUF_LEN); + if (!data) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + return -1; + } + memset(data, 0, sizeof(__u8) * WDC_ERROR_REC_LOG_BUF_LEN); + + ret = nvme_get_log_simple(dev_fd(dev), WDC_ERROR_REC_LOG_ID, + WDC_ERROR_REC_LOG_BUF_LEN, data); + + if (strcmp(format, "json")) + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret, false), ret); + + if (!ret) { + log_data = (struct wdc_ocp_c1_error_recovery_log *)data; + + /* check log page version */ + if ((log_data->log_page_version != WDC_ERROR_REC_LOG_VERSION1) && + (log_data->log_page_version != WDC_ERROR_REC_LOG_VERSION2)) { + fprintf(stderr, "ERROR: WDC: invalid error recovery log version - %d\n", log_data->log_page_version); + ret = -1; + goto out; + } + + /* Verify GUID matches */ + for (i = 0; i < WDC_OCP_C1_GUID_LENGTH; i++) { + if (wdc_ocp_c1_guid[i] != log_data->log_page_guid[i]) { + fprintf(stderr, "ERROR: WDC: Unknown GUID in C1 Log Page data\n"); + int j; + + fprintf(stderr, "ERROR: WDC: Expected GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", wdc_ocp_c1_guid[j]); + fprintf(stderr, "\nERROR: WDC: Actual GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", log_data->log_page_guid[j]); + fprintf(stderr, "\n"); + + ret = -1; + goto out; + } + } + + /* parse the data */ + wdc_print_error_rec_log(log_data, fmt); + } else { + fprintf(stderr, "ERROR: WDC: Unable to read error recovery (C1) data from buffer\n"); + } + +out: + free(data); + return ret; +} + +static int wdc_get_ocp_c4_log_page(nvme_root_t r, struct nvme_dev *dev, char *format) +{ + struct wdc_ocp_C4_dev_cap_log *log_data; + enum nvme_print_flags fmt; + __u8 *data; + int ret; + int i; + + if (!wdc_check_device(r, dev)) + return -1; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: invalid output format\n"); + return ret; + } + + data = (__u8 *)malloc(sizeof(__u8) * WDC_DEV_CAP_LOG_BUF_LEN); + if (!data) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + return -1; + } + memset(data, 0, sizeof(__u8) * WDC_DEV_CAP_LOG_BUF_LEN); + + ret = nvme_get_log_simple(dev_fd(dev), WDC_DEV_CAP_LOG_ID, + WDC_DEV_CAP_LOG_BUF_LEN, data); + + if (strcmp(format, "json")) + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret, false), ret); + + if (!ret) { + log_data = (struct wdc_ocp_C4_dev_cap_log *)data; + + /* check log page version */ + if (log_data->log_page_version != WDC_DEV_CAP_LOG_VERSION) { + fprintf(stderr, "ERROR: WDC: invalid device capabilities log version - %d\n", log_data->log_page_version); + ret = -1; + goto out; + } + + /* Verify GUID matches */ + for (i = 0; i < WDC_OCP_C4_GUID_LENGTH; i++) { + if (wdc_ocp_c4_guid[i] != log_data->log_page_guid[i]) { + fprintf(stderr, "ERROR: WDC: Unknown GUID in C4 Log Page data\n"); + int j; + + fprintf(stderr, "ERROR: WDC: Expected GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", wdc_ocp_c4_guid[j]); + fprintf(stderr, "\nERROR: WDC: Actual GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", log_data->log_page_guid[j]); + fprintf(stderr, "\n"); + + ret = -1; + goto out; + } + } + + /* parse the data */ + wdc_print_dev_cap_log(log_data, fmt); + } else { + fprintf(stderr, "ERROR: WDC: Unable to read device capabilities (C4) data from buffer\n"); + } + +out: + free(data); + return ret; +} + +static int wdc_get_ocp_c5_log_page(nvme_root_t r, struct nvme_dev *dev, char *format) +{ + struct wdc_ocp_C5_unsupported_reqs *log_data; + enum nvme_print_flags fmt; + int ret; + __u8 *data; + int i; + + if (!wdc_check_device(r, dev)) + return -1; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: invalid output format\n"); + return ret; + } + + data = (__u8 *)malloc(sizeof(__u8) * WDC_UNSUPPORTED_REQS_LOG_BUF_LEN); + if (!data) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + return -1; + } + memset(data, 0, sizeof(__u8) * WDC_UNSUPPORTED_REQS_LOG_BUF_LEN); + + ret = nvme_get_log_simple(dev_fd(dev), WDC_UNSUPPORTED_REQS_LOG_ID, + WDC_UNSUPPORTED_REQS_LOG_BUF_LEN, data); + + if (strcmp(format, "json")) + fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret, false), ret); + + if (!ret) { + log_data = (struct wdc_ocp_C5_unsupported_reqs *)data; + + /* check log page version */ + if (log_data->log_page_version != WDC_UNSUPPORTED_REQS_LOG_VERSION) { + fprintf(stderr, "ERROR: WDC: invalid unsupported requirements log version - %d\n", log_data->log_page_version); + ret = -1; + goto out; + } + + /* Verify GUID matches */ + for (i = 0; i < WDC_OCP_C5_GUID_LENGTH; i++) { + if (wdc_ocp_c5_guid[i] != log_data->log_page_guid[i]) { + fprintf(stderr, "ERROR: WDC: Unknown GUID in C5 Log Page data\n"); + int j; + + fprintf(stderr, "ERROR: WDC: Expected GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", wdc_ocp_c5_guid[j]); + fprintf(stderr, "\nERROR: WDC: Actual GUID: 0x"); + for (j = 0; j < 16; j++) + fprintf(stderr, "%x", log_data->log_page_guid[j]); + fprintf(stderr, "\n"); + + ret = -1; + goto out; + } + } + + /* parse the data */ + wdc_print_unsupported_reqs_log(log_data, fmt); + } else { + fprintf(stderr, "ERROR: WDC: Unable to read unsupported requirements (C5) data from buffer\n"); + } + +out: + free(data); + return ret; +} + +static int wdc_get_d0_log_page(nvme_root_t r, struct nvme_dev *dev, char *format) +{ + struct wdc_ssd_d0_smart_log *perf; + enum nvme_print_flags fmt; + int ret = 0; + __u8 *data; + + if (!wdc_check_device(r, dev)) + return -1; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: invalid output format\n"); + return ret; + } + + /* verify the 0xD0 log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, WDC_NVME_GET_VU_SMART_LOG_OPCODE) == false) { + fprintf(stderr, "ERROR: WDC: 0xD0 Log Page not supported\n"); + return -1; + } + + data = (__u8 *)malloc(sizeof(__u8) * WDC_NVME_VU_SMART_LOG_LEN); + if (!data) { + 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_simple(dev_fd(dev), + WDC_NVME_GET_VU_SMART_LOG_OPCODE, + WDC_NVME_VU_SMART_LOG_LEN, data); + if (strcmp(format, "json")) + nvme_show_status(ret); + + if (!ret) { + /* 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 long double le_to_float(__u8 *data, int byte_len) +{ + long double result = 0; + int i; + + for (i = 0; i < byte_len; i++) { + result *= 256; + result += data[15 - i]; + } + + return result; +} + +static void stringify_log_page_guid(__u8 *guid, char *buf) +{ + char *ptr = buf; + int i; + + memset(buf, 0, sizeof(char) * 19); + + ptr += sprintf(ptr, "0x"); + for (i = 0; i < 16; i++) + ptr += sprintf(ptr, "%x", guid[15 - i]); +} + +static const char *const cloud_smart_log_thermal_status[] = { + [0x00] = "unthrottled", + [0x01] = "first_level", + [0x02] = "second_level", + [0x03] = "third_level", +}; + +static const char *stringify_cloud_smart_log_thermal_status(__u8 status) +{ + if (status < ARRAY_SIZE(cloud_smart_log_thermal_status) && + cloud_smart_log_thermal_status[status]) + return cloud_smart_log_thermal_status[status]; + return "unrecognized"; +} + +static void show_cloud_smart_log_json(struct ocp_cloud_smart_log *log) +{ + struct json_object *root; + struct json_object *bad_user_nand_blocks; + struct json_object *bad_system_nand_blocks; + struct json_object *e2e_correction_counts; + struct json_object *user_data_erase_counts; + struct json_object *thermal_status; + struct json_object *dssd_specific_ver; + char buf[2 * sizeof(log->log_page_guid) + 3]; + + bad_user_nand_blocks = json_create_object(); + json_object_add_value_uint(bad_user_nand_blocks, "normalized", + le16_to_cpu(log->bad_user_nand_blocks.normalized)); + json_object_add_value_uint(bad_user_nand_blocks, "raw", + le64_to_cpu(log->bad_user_nand_blocks.raw)); + + bad_system_nand_blocks = json_create_object(); + json_object_add_value_uint(bad_system_nand_blocks, "normalized", + le16_to_cpu(log->bad_system_nand_blocks.normalized)); + json_object_add_value_uint(bad_system_nand_blocks, "raw", + le64_to_cpu(log->bad_system_nand_blocks.raw)); + + e2e_correction_counts = json_create_object(); + json_object_add_value_uint(e2e_correction_counts, "corrected", + le32_to_cpu(log->e2e_correction_counts.corrected)); + json_object_add_value_uint(e2e_correction_counts, "detected", + le32_to_cpu(log->e2e_correction_counts.detected)); + + user_data_erase_counts = json_create_object(); + json_object_add_value_uint(user_data_erase_counts, "minimum", + le32_to_cpu(log->user_data_erase_counts.minimum)); + json_object_add_value_uint(user_data_erase_counts, "maximum", + le32_to_cpu(log->user_data_erase_counts.maximum)); + + thermal_status = json_create_object(); + json_object_add_value_string(thermal_status, "current_status", + stringify_cloud_smart_log_thermal_status(log->thermal_status.current_status)); + json_object_add_value_uint(thermal_status, "num_events", + log->thermal_status.num_events); + + dssd_specific_ver = json_create_object(); + json_object_add_value_uint(dssd_specific_ver, "major_ver", + log->dssd_specific_ver.major_ver); + json_object_add_value_uint(dssd_specific_ver, "minor_ver", + le16_to_cpu(log->dssd_specific_ver.minor_ver)); + json_object_add_value_uint(dssd_specific_ver, "point_ver", + le16_to_cpu(log->dssd_specific_ver.point_ver)); + json_object_add_value_uint(dssd_specific_ver, "errata_ver", + log->dssd_specific_ver.errata_ver); + + root = json_create_object(); + json_object_add_value_uint64(root, "physical_media_units_written", + le_to_float(log->physical_media_units_written, 16)); + json_object_add_value_uint64(root, "physical_media_units_read", + le_to_float(log->physical_media_units_read, 16)); + json_object_add_value_object(root, "bad_user_nand_blocks", + bad_user_nand_blocks); + json_object_add_value_object(root, "bad_system_nand_blocks", + bad_system_nand_blocks); + json_object_add_value_uint(root, "xor_recovery_count", + le64_to_cpu(log->xor_recovery_count)); + json_object_add_value_uint(root, "uncorrectable_read_error_count", + le64_to_cpu(log->uncorrectable_read_error_count)); + json_object_add_value_uint(root, "soft_ecc_error_count", + le64_to_cpu(log->soft_ecc_error_count)); + json_object_add_value_object(root, "e2e_correction_counts", + e2e_correction_counts); + json_object_add_value_uint(root, "system_data_percent_used", + log->system_data_percent_used); + json_object_add_value_uint(root, "refresh_counts", + le64_to_cpu(log->refresh_counts)); + json_object_add_value_object(root, "user_data_erase_counts", + user_data_erase_counts); + json_object_add_value_object(root, "thermal_status", thermal_status); + json_object_add_value_object(root, "dssd_specific_ver", + dssd_specific_ver); + json_object_add_value_uint(root, "pcie_correctable_error_count", + le64_to_cpu(log->pcie_correctable_error_count)); + json_object_add_value_uint(root, "incomplete_shutdowns", + le32_to_cpu(log->incomplete_shutdowns)); + json_object_add_value_uint(root, "percent_free_blocks", + log->percent_free_blocks); + json_object_add_value_uint(root, "capacitor_health", + le16_to_cpu(log->capacitor_health)); + sprintf(buf, "%c", log->nvme_errata_ver); + json_object_add_value_string(root, "nvme_errata_version", buf); + json_object_add_value_uint(root, "unaligned_io", + le64_to_cpu(log->unaligned_io)); + json_object_add_value_uint(root, "security_version_number", + le64_to_cpu(log->security_version_number)); + json_object_add_value_uint(root, "total_nuse", + le64_to_cpu(log->total_nuse)); + json_object_add_value_uint64(root, "plp_start_count", + le_to_float(log->plp_start_count, 16)); + json_object_add_value_uint64(root, "endurance_estimate", + le_to_float(log->endurance_estimate, 16)); + json_object_add_value_uint(root, "pcie_link_retraining_count", + le64_to_cpu(log->pcie_link_retraining_cnt)); + json_object_add_value_uint(root, "power_state_change_count", + le64_to_cpu(log->power_state_change_cnt)); + json_object_add_value_uint(root, "log_page_version", + le16_to_cpu(log->log_page_version)); + stringify_log_page_guid(log->log_page_guid, buf); + json_object_add_value_string(root, "log_page_guid", buf); + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static void show_cloud_smart_log_normal(struct ocp_cloud_smart_log *log, struct nvme_dev *dev) +{ + char buf[2 * sizeof(log->log_page_guid) + 3]; + + printf("Smart Extended Log for NVME device:%s\n", dev->name); + printf("Physical Media Units Written : %'.0Lf\n", + le_to_float(log->physical_media_units_written, 16)); + printf("Physical Media Units Read : %'.0Lf\n", + le_to_float(log->physical_media_units_read, 16)); + printf("Bad User NAND Blocks (Normalized) : %" PRIu16 "%%\n", + le16_to_cpu(log->bad_user_nand_blocks.normalized)); + printf("Bad User NAND Blocks (Raw) : %" PRIu64 "\n", + le64_to_cpu(log->bad_user_nand_blocks.raw)); + printf("Bad System NAND Blocks (Normalized) : %" PRIu16 "%%\n", + le16_to_cpu(log->bad_system_nand_blocks.normalized)); + printf("Bad System NAND Blocks (Raw) : %" PRIu64 "\n", + le64_to_cpu(log->bad_system_nand_blocks.raw)); + printf("XOR Recovery Count : %" PRIu64 "\n", + le64_to_cpu(log->xor_recovery_count)); + printf("Uncorrectable Read Error Count : %" PRIu64 "\n", + le64_to_cpu(log->uncorrectable_read_error_count)); + printf("Soft ECC Error Count : %" PRIu64 "\n", + le64_to_cpu(log->soft_ecc_error_count)); + printf("End to End Correction Counts (Corrected) : %" PRIu32 "\n", + le32_to_cpu(log->e2e_correction_counts.corrected)); + printf("End to End Correction Counts (Detected) : %" PRIu32 "\n", + le32_to_cpu(log->e2e_correction_counts.detected)); + printf("System Data %% Used : %" PRIu8 "%%\n", + log->system_data_percent_used); + printf("Refresh Counts : %" PRIu64 "\n", + le64_to_cpu(log->refresh_counts)); + printf("User Data Erase Counts (Minimum) : %" PRIu32 "\n", + le32_to_cpu(log->user_data_erase_counts.minimum)); + printf("User Data Erase Counts (Maximum) : %" PRIu32 "\n", + le32_to_cpu(log->user_data_erase_counts.maximum)); + printf("Thermal Throttling Status (Current Status) : %s\n", + stringify_cloud_smart_log_thermal_status(log->thermal_status.current_status)); + printf("Thermal Throttling Status (Number of Events) : %" PRIu8 "\n", + log->thermal_status.num_events); + printf("NVMe Major Version : %" PRIu8 "\n", + log->dssd_specific_ver.major_ver); + printf(" Minor Version : %" PRIu16 "\n", + le16_to_cpu(log->dssd_specific_ver.minor_ver)); + printf(" Point Version : %" PRIu16 "\n", + le16_to_cpu(log->dssd_specific_ver.point_ver)); + printf(" Errata Version : %" PRIu8 "\n", + log->dssd_specific_ver.errata_ver); + printf("PCIe Correctable Error Count : %" PRIu64 "\n", + le64_to_cpu(log->pcie_correctable_error_count)); + printf("Incomplete Shutdowns : %" PRIu32 "\n", + le32_to_cpu(log->incomplete_shutdowns)); + printf("%% Free Blocks : %" PRIu8 "%%\n", + log->percent_free_blocks); + printf("Capacitor Health : %" PRIu16 "%%\n", + le16_to_cpu(log->capacitor_health)); + printf("NVMe Errata Version : %c\n", + log->nvme_errata_ver); + printf("Unaligned IO : %" PRIu64 "\n", + le64_to_cpu(log->unaligned_io)); + printf("Security Version Number : %" PRIu64 "\n", + le64_to_cpu(log->security_version_number)); + printf("Total NUSE : %" PRIu64 "\n", + le64_to_cpu(log->total_nuse)); + printf("PLP Start Count : %'.0Lf\n", + le_to_float(log->plp_start_count, 16)); + printf("Endurance Estimate : %'.0Lf\n", + le_to_float(log->endurance_estimate, 16)); + printf("PCIe Link Retraining Count : %" PRIu64 "\n", + le64_to_cpu(log->pcie_link_retraining_cnt)); + printf("Power State Change Count : %" PRIu64 "\n", + le64_to_cpu(log->power_state_change_cnt)); + printf("Log Page Version : %" PRIu16 "\n", + le16_to_cpu(log->log_page_version)); + stringify_log_page_guid(log->log_page_guid, buf); + printf("Log Page GUID : %s\n", buf); + printf("\n\n"); +} + +static int wdc_vs_smart_add_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve additional performance statistics."; + const char *interval = "Interval to read the statistics from [1, 15]."; + const char *log_page_version = "Log Page Version: 0 = vendor, 1 = WDC"; + const char *log_page_mask = "Log Page Mask, comma separated list: 0xC0, 0xC1, 0xCA, 0xD0"; + const char *namespace_id = "desired namespace id"; + enum nvme_print_flags fmt; + struct nvme_dev *dev; + nvme_root_t r; + int ret = 0; + int uuid_index = 0; + int page_mask = 0, num, i; + int log_page_list[16]; + __u64 capabilities = 0; + __u32 device_id, read_vendor_id; + + struct config { + uint8_t interval; + char *output_format; + __u8 log_page_version; + char *log_page_mask; + __u32 namespace_id; + }; + + struct config cfg = { + .interval = 14, + .output_format = "normal", + .log_page_version = 0, + .log_page_mask = "", + .namespace_id = NVME_NSID_ALL, + }; + + OPT_ARGS(opts) = { + OPT_UINT("interval", 'i', &cfg.interval, interval), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_BYTE("log-page-version", 'l', &cfg.log_page_version, log_page_version), + OPT_LIST("log-page-mask", 'p', &cfg.log_page_mask, log_page_mask), + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + if (!cfg.log_page_version) { + uuid_index = 0; + } else if (cfg.log_page_version == 1) { + uuid_index = 1; + } else { + fprintf(stderr, "ERROR: WDC: unsupported log page version for this command\n"); + ret = -1; + goto out; + } + + num = argconfig_parse_comma_sep_array(cfg.log_page_mask, log_page_list, 16); + + if (num == -1) { + fprintf(stderr, "ERROR: WDC: log page list is malformed\n"); + ret = -1; + goto out; + } + + if (!num) { + page_mask |= WDC_ALL_PAGE_MASK; + } else { + for (i = 0; i < num; i++) { + if (log_page_list[i] == 0xc0) + page_mask |= WDC_C0_PAGE_MASK; + if (log_page_list[i] == 0xc1) + page_mask |= WDC_C1_PAGE_MASK; + if (log_page_list[i] == 0xca) + page_mask |= WDC_CA_PAGE_MASK; + if (log_page_list[i] == 0xd0) + page_mask |= WDC_D0_PAGE_MASK; + } + } + + if (!page_mask) + fprintf(stderr, "ERROR: WDC: Unknown log page mask - %s\n", cfg.log_page_mask); + + ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id); + + capabilities = wdc_get_drive_capabilities(r, dev); + if (!(capabilities & WDC_DRIVE_CAP_SMART_LOG_MASK)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + if (((capabilities & WDC_DRIVE_CAP_C0_LOG_PAGE) == WDC_DRIVE_CAP_C0_LOG_PAGE) && + (page_mask & WDC_C0_PAGE_MASK)) { + /* Get 0xC0 log page if possible. */ + if (!wdc_is_sn861(device_id)) { + ret = wdc_get_c0_log_page(r, dev, cfg.output_format, + uuid_index, cfg.namespace_id); + if (ret) + fprintf(stderr, + "ERROR: WDC: Failure reading the C0 Log Page, ret = %d\n", + ret); + } else { + struct ocp_cloud_smart_log log; + char buf[2 * sizeof(log.log_page_guid) + 3]; + + ret = validate_output_format(cfg.output_format, &fmt); + if (ret < 0) { + fprintf(stderr, "Invalid output format: %s\n", cfg.output_format); + goto out; + } + + ret = nvme_get_log_simple(dev_fd(dev), + WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID, + sizeof(log), &log); + if (!ret) { + char *ptr = buf; + int i; + __u8 *guid = log.log_page_guid; + + memset(buf, 0, sizeof(char) * 19); + + ptr += sprintf(ptr, "0x"); + for (i = 0; i < 16; i++) + ptr += sprintf(ptr, "%x", guid[15 - i]); + if (strcmp(buf, "0xafd514c97c6f4f9ca4f2bfea2810afc5")) + fprintf(stderr, "Invalid GUID: %s\n", buf); + else { + if (fmt == BINARY) + d_raw((unsigned char *)&log, sizeof(log)); + else if (fmt == JSON) + show_cloud_smart_log_json(&log); + else + show_cloud_smart_log_normal(&log, dev); + } + } else if (ret > 0) { + nvme_show_status(ret); + } else { + perror("vs-smart-add-log"); + } + } + } + if (((capabilities & (WDC_DRIVE_CAP_CA_LOG_PAGE)) == (WDC_DRIVE_CAP_CA_LOG_PAGE)) && + (page_mask & WDC_CA_PAGE_MASK) && + (!wdc_is_sn861(device_id))) { + /* Get the CA Log Page */ + ret = wdc_get_ca_log_page(r, dev, 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) && + (page_mask & WDC_C1_PAGE_MASK)) { + /* Get the C1 Log Page */ + ret = wdc_get_c1_log_page(r, dev, 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) && + (page_mask & WDC_D0_PAGE_MASK)) { + /* Get the D0 Log Page */ + ret = wdc_get_d0_log_page(r, dev, cfg.output_format); + if (ret) + fprintf(stderr, "ERROR: WDC: Failure reading the D0 Log Page, ret = %d\n", ret); + } + +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_vs_cloud_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve Cloud Log Smart/Health Information"; + const char *namespace_id = "desired namespace id"; + enum nvme_print_flags fmt; + __u64 capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret; + __u8 *data; + + struct config { + char *output_format; + __u32 namespace_id; + }; + + struct config cfg = { + .output_format = "normal", + .namespace_id = NVME_NSID_ALL, + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"), + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + + capabilities = wdc_get_drive_capabilities(r, dev); + + if (!(capabilities & WDC_DRIVE_CAP_CLOUD_LOG_PAGE)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + data = NULL; + ret = nvme_get_ext_smart_cloud_log(dev_fd(dev), &data, 0, + cfg.namespace_id); + + if (strcmp(cfg.output_format, "json")) + nvme_show_status(ret); + + if (!ret) { + ret = validate_output_format(cfg.output_format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC %s: invalid output format\n", __func__); + } else { + /* parse the data */ + wdc_print_ext_smart_cloud_log(data, fmt); + } + } else { + fprintf(stderr, "ERROR: WDC: Unable to read C0 Log Page V1 data\n"); + ret = -1; + } + + if (data) + free(data); + +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_vs_hw_rev_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve Hardware Revision Log Information"; + const char *namespace_id = "desired namespace id"; + enum nvme_print_flags fmt; + __u64 capabilities = 0; + struct nvme_dev *dev; + int ret; + __u8 *data = NULL; + nvme_root_t r; + + struct config { + char *output_format; + __u32 namespace_id; + }; + + struct config cfg = { + .output_format = "normal", + .namespace_id = NVME_NSID_ALL, + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"), + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + + capabilities = wdc_get_drive_capabilities(r, dev); + + if (!(capabilities & WDC_DRIVE_CAP_HW_REV_LOG_PAGE)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + ret = nvme_get_hw_rev_log(dev_fd(dev), &data, 0, cfg.namespace_id); + + if (strcmp(cfg.output_format, "json")) + nvme_show_status(ret); + + if (!ret) { + ret = validate_output_format(cfg.output_format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC %s: invalid output format\n", __func__); + goto free_buf; + } + + if (!data) { + fprintf(stderr, "ERROR: WDC: Invalid buffer to read Hardware Revision log\n"); + ret = -1; + goto out; + } + switch (fmt) { + case NORMAL: + wdc_print_hw_rev_log_normal(data); + break; + case JSON: + wdc_print_hw_rev_log_json(data); + break; + default: + break; + } + } else { + fprintf(stderr, "ERROR: WDC: Unable to read Hardware Revision Log Page data\n"); + ret = -1; + } + +free_buf: + if (data) + free(data); + +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_vs_device_waf(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve Device Write Amplication Factor"; + const char *namespace_id = "desired namespace id"; + struct nvme_smart_log smart_log; + enum nvme_print_flags fmt; + struct nvme_dev *dev; + __u8 *data; + nvme_root_t r; + int ret = 0; + __u64 capabilities = 0; + struct __packed wdc_nvme_ext_smart_log * ext_smart_log_ptr; + long double data_units_written = 0, + phys_media_units_written_tlc = 0, + phys_media_units_written_slc = 0; + struct json_object *root = NULL; + char tlc_waf_str[32] = { 0 }, + slc_waf_str[32] = { 0 }; + + struct config { + char *output_format; + __u32 namespace_id; + }; + + struct config cfg = { + .output_format = "normal", + .namespace_id = NVME_NSID_ALL, + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"), + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + + capabilities = wdc_get_drive_capabilities(r, dev); + + if (!(capabilities & WDC_DRIVE_CAP_DEVICE_WAF)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + /* get data units written from the smart log page */ + ret = nvme_get_log_smart(dev_fd(dev), cfg.namespace_id, false, + &smart_log); + if (!ret) { + data_units_written = int128_to_double(smart_log.data_units_written); + } else if (ret > 0) { + nvme_show_status(ret); + ret = -1; + goto out; + } else { + fprintf(stderr, "smart log: %s\n", nvme_strerror(errno)); + ret = -1; + goto out; + } + + /* get Physical Media Units Written from extended smart/C0 log page */ + data = NULL; + ret = nvme_get_ext_smart_cloud_log(dev_fd(dev), &data, 0, + cfg.namespace_id); + + if (!ret) { + ext_smart_log_ptr = (struct __packed wdc_nvme_ext_smart_log *)data; + phys_media_units_written_tlc = int128_to_double(ext_smart_log_ptr->ext_smart_pmuwt); + phys_media_units_written_slc = int128_to_double(ext_smart_log_ptr->ext_smart_pmuws); + + if (data) + free(data); + } else { + fprintf(stderr, "ERROR: WDC %s: get smart cloud log failure\n", __func__); + ret = -1; + goto out; + } + + if (strcmp(cfg.output_format, "json")) + nvme_show_status(ret); + + ret = validate_output_format(cfg.output_format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC %s: invalid output format\n", __func__); + goto out; + } + + if (!data_units_written) { + fprintf(stderr, "ERROR: WDC %s: 0 data units written\n", __func__); + ret = -1; + goto out; + } + + if (fmt == NORMAL) { + printf("Device Write Amplification Factor TLC : %4.2Lf\n", + (phys_media_units_written_tlc/data_units_written)); + printf("Device Write Amplification Factor SLC : %4.2Lf\n", + (phys_media_units_written_slc/data_units_written)); + } else if (fmt == JSON) { + root = json_create_object(); + sprintf(tlc_waf_str, "%4.2Lf", (phys_media_units_written_tlc/data_units_written)); + sprintf(slc_waf_str, "%4.2Lf", (phys_media_units_written_slc/data_units_written)); + + json_object_add_value_string(root, "Device Write Amplification Factor TLC", tlc_waf_str); + json_object_add_value_string(root, "Device Write Amplification Factor SLC", slc_waf_str); + + json_print_object(root, NULL); + printf("\n"); + + json_free_object(root); + } + +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_get_latency_monitor_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve latency monitor log data."; + __u64 capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret = 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + capabilities = wdc_get_drive_capabilities(r, dev); + + if (!(capabilities & WDC_DRIVE_CAP_C3_LOG_PAGE)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + ret = wdc_get_c3_log_page(r, dev, cfg.output_format); + if (ret) + fprintf(stderr, "ERROR: WDC: Failure reading the Latency Monitor (C3) Log Page, ret = %d\n", ret); + +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_get_error_recovery_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve error recovery log data."; + __u64 capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret = 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + capabilities = wdc_get_drive_capabilities(r, dev); + + if (!(capabilities & WDC_DRIVE_CAP_OCP_C1_LOG_PAGE)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + ret = wdc_get_ocp_c1_log_page(r, dev, cfg.output_format); + if (ret) + fprintf(stderr, "ERROR: WDC: Failure reading the Error Recovery (C1) Log Page, ret = 0x%x\n", ret); + +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_get_dev_capabilities_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve device capabilities log data."; + __u64 capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret = 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + capabilities = wdc_get_drive_capabilities(r, dev); + + if (!(capabilities & WDC_DRIVE_CAP_OCP_C4_LOG_PAGE)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + ret = wdc_get_ocp_c4_log_page(r, dev, cfg.output_format); + if (ret) + fprintf(stderr, "ERROR: WDC: Failure reading the Device Capabilities (C4) Log Page, ret = 0x%x\n", ret); + +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_get_unsupported_reqs_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve unsupported requirements log data."; + __u64 capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret = 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + capabilities = wdc_get_drive_capabilities(r, dev); + + if (!(capabilities & WDC_DRIVE_CAP_OCP_C5_LOG_PAGE)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + ret = wdc_get_ocp_c5_log_page(r, dev, cfg.output_format); + if (ret) + fprintf(stderr, "ERROR: WDC: Failure reading the Unsupported Requirements (C5) Log Page, ret = 0x%x\n", ret); + +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_do_clear_pcie_correctable_errors(int fd) +{ + int ret; + struct nvme_passthru_cmd admin_cmd; + + 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, NULL); + nvme_show_status(ret); + return ret; +} + +static int wdc_do_clear_pcie_correctable_errors_vuc(int fd) +{ + int ret; + struct nvme_passthru_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof(admin_cmd)); + admin_cmd.opcode = WDC_NVME_CLEAR_PCIE_CORR_OPCODE_VUC; + + ret = nvme_submit_admin_passthru(fd, &admin_cmd, NULL); + nvme_show_status(ret); + return ret; +} + +static int wdc_do_clear_pcie_correctable_errors_fid(int fd) +{ + int ret; + __u32 result; + __u32 value = 1 << 31; /* Bit 31 - clear PCIe correctable count */ + + ret = nvme_set_features_simple(fd, WDC_NVME_CLEAR_PCIE_CORR_FEATURE_ID, 0, value, + false, &result); + + nvme_show_status(ret); + 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."; + __u64 capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret; + + OPT_ARGS(opts) = { + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + if (!wdc_check_device(r, dev)) { + ret = -1; + goto out; + } + + capabilities = wdc_get_drive_capabilities(r, dev); + if (!(capabilities & WDC_DRIVE_CAP_CLEAR_PCIE_MASK)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + if (capabilities & WDC_DRIVE_CAP_CLEAR_PCIE) + ret = wdc_do_clear_pcie_correctable_errors(dev_fd(dev)); + else if (capabilities & WDC_DRIVE_CAP_VUC_CLEAR_PCIE) + ret = wdc_do_clear_pcie_correctable_errors_vuc(dev_fd(dev)); + else + ret = wdc_do_clear_pcie_correctable_errors_fid(dev_fd(dev)); + +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_drive_status(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char *desc = "Get Drive Status."; + struct nvme_dev *dev; + int ret = 0; + nvme_root_t r; + __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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + capabilities = wdc_get_drive_capabilities(r, dev); + 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(r, dev, + WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID) == 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(r, dev, &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(r, dev, &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(r, dev, &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(r, dev, &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(r, dev, &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(r, dev, &format_corrupt_reason, + WDC_C2_FORMAT_CORRUPT_REASON_ID)) + fprintf(stderr, "ERROR: WDC: Get Format Corrupt Reason Failed\n"); + + printf(" Drive Status :-\n"); + if ((int)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: + nvme_free_tree(r); + dev_close(dev); + 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."; + struct nvme_dev *dev; + int ret = -1; + nvme_root_t r; + __le32 assert_status = cpu_to_le32(0xFFFFFFFF); + __u64 capabilities = 0; + struct nvme_passthru_cmd admin_cmd; + + OPT_ARGS(opts) = { + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + capabilities = wdc_get_drive_capabilities(r, dev); + 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(r, dev, &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(dev_fd(dev), &admin_cmd, + NULL); + nvme_show_status(ret); + } else + fprintf(stderr, "INFO: WDC: No Assert Dump Present\n"); + +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_get_fw_act_history(nvme_root_t r, struct nvme_dev *dev, + char *format) +{ + struct wdc_fw_act_history_log_hdr *fw_act_history_hdr; + enum nvme_print_flags fmt; + int ret; + __u8 *data; + + if (!wdc_check_device(r, dev)) + return -1; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: invalid output format\n"); + return ret; + } + + /* verify the FW Activate History log page is supported */ + if (!wdc_nvme_check_supported_log_page(r, dev, WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID)) { + fprintf(stderr, "ERROR: WDC: %d Log Page not supported\n", + WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID); + return -1; + } + + data = (__u8 *)malloc(sizeof(__u8) * WDC_FW_ACT_HISTORY_LOG_BUF_LEN); + if (!data) { + 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_simple(dev_fd(dev), + WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID, + WDC_FW_ACT_HISTORY_LOG_BUF_LEN, data); + + if (strcmp(format, "json")) + nvme_show_status(ret); + + if (!ret) { + /* parse the data */ + fw_act_history_hdr = (struct wdc_fw_act_history_log_hdr *)(data); + + if ((fw_act_history_hdr->num_entries > 0) && + (fw_act_history_hdr->num_entries <= WDC_MAX_NUM_ACT_HIST_ENTRIES)) { + ret = wdc_print_fw_act_history_log(data, fw_act_history_hdr->num_entries, + fmt, 0, 0, 0); + } else if (!fw_act_history_hdr->num_entries) { + fprintf(stderr, "INFO: WDC: No FW Activate History entries found.\n"); + ret = 0; + } else { + fprintf(stderr, + "ERROR: WDC: Invalid number entries found in FW Activate History Log Page - %d\n", + fw_act_history_hdr->num_entries); + ret = -1; + } + } else { + fprintf(stderr, "ERROR: WDC: Unable to read FW Activate History Log Page data\n"); + ret = -1; + } + + free(data); + return ret; +} + +static __u32 wdc_get_fw_cust_id(nvme_root_t r, struct nvme_dev *dev) +{ + + __u32 cust_id = WDC_INVALID_CUSTOMER_ID; + __u32 *cust_id_ptr = NULL; + + if (!get_dev_mgment_cbs_data(r, dev, WDC_C2_CUSTOMER_ID_ID, (void *)&cust_id_ptr)) + fprintf(stderr, "%s: ERROR: WDC: 0xC2 Log Page entry ID 0x%x not found\n", + __func__, WDC_C2_CUSTOMER_ID_ID); + else + cust_id = *cust_id_ptr; + + free(cust_id_ptr); + return cust_id; +} + +static int wdc_get_fw_act_history_C2(nvme_root_t r, struct nvme_dev *dev, + char *format) +{ + struct wdc_fw_act_history_log_format_c2 *fw_act_history_log; + __u32 tot_entries = 0, num_entries = 0; + __u32 vendor_id = 0, device_id = 0; + __u32 cust_id = 0; + enum nvme_print_flags fmt; + __u8 *data; + int ret; + + if (!wdc_check_device(r, dev)) + return -1; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: invalid output format\n"); + return ret; + } + + ret = wdc_get_pci_ids(r, dev, &device_id, &vendor_id); + + data = (__u8 *)malloc(sizeof(__u8) * WDC_FW_ACT_HISTORY_C2_LOG_BUF_LEN); + if (!data) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + return -1; + } + + memset(data, 0, sizeof(__u8) * WDC_FW_ACT_HISTORY_C2_LOG_BUF_LEN); + + ret = nvme_get_log_simple(dev_fd(dev), + WDC_NVME_GET_FW_ACT_HISTORY_C2_LOG_ID, + WDC_FW_ACT_HISTORY_C2_LOG_BUF_LEN, data); + + if (strcmp(format, "json")) + nvme_show_status(ret); + + if (!ret) { + /* parse the data */ + fw_act_history_log = (struct wdc_fw_act_history_log_format_c2 *)(data); + tot_entries = le32_to_cpu(fw_act_history_log->num_entries); + + if (tot_entries > 0) { + /* get the FW customer id */ + if (!wdc_is_sn861(device_id)) { + cust_id = wdc_get_fw_cust_id(r, dev); + if (cust_id == WDC_INVALID_CUSTOMER_ID) { + fprintf(stderr, + "%s: ERROR: WDC: invalid customer id\n", + __func__); + ret = -1; + goto freeData; + } + } + num_entries = (tot_entries < WDC_MAX_NUM_ACT_HIST_ENTRIES) ? tot_entries : + WDC_MAX_NUM_ACT_HIST_ENTRIES; + ret = wdc_print_fw_act_history_log(data, num_entries, + fmt, cust_id, vendor_id, device_id); + } else { + fprintf(stderr, "INFO: WDC: No FW Activate History entries found.\n"); + ret = 0; + } + } else { + fprintf(stderr, "ERROR: WDC: Unable to read FW Activate History Log Page data\n"); + ret = -1; + } + +freeData: + free(data); + return ret; +} + +static int wdc_vs_fw_activate_history(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve FW activate history table."; + __u64 capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret; + + 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + capabilities = wdc_get_drive_capabilities(r, dev); + if (!(capabilities & WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_MASK)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + if (capabilities & WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY) { + int uuid_index = 0; + bool c0GuidMatch = false; + __u8 *data; + int i; + + /* + * check for the GUID in the 0xC0 log page to determine which log page to use to + * retrieve fw activate history data + */ + data = (__u8 *)malloc(sizeof(__u8) * WDC_NVME_SMART_CLOUD_ATTR_LEN); + if (!data) { + fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno)); + ret = -1; + goto out; + } + + /* Get the 0xC0 log data */ + struct nvme_get_log_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .lid = WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID, + .nsid = 0xFFFFFFFF, + .lpo = 0, + .lsp = NVME_LOG_LSP_NONE, + .lsi = 0, + .rae = false, + .uuidx = uuid_index, + .csi = NVME_CSI_NVM, + .ot = false, + .len = WDC_NVME_SMART_CLOUD_ATTR_LEN, + .log = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + ret = nvme_get_log(&args); + + if (!ret) { + /* Verify GUID matches */ + for (i = 0; i < 16; i++) { + if (scao_guid[i] != data[SCAO_LPG + i]) { + c0GuidMatch = false; + break; + } + } + + if (i == 16) + c0GuidMatch = true; + } + + free(data); + if (c0GuidMatch) + ret = wdc_get_fw_act_history_C2(r, dev, cfg.output_format); + else + ret = wdc_get_fw_act_history(r, dev, cfg.output_format); + } else { + ret = wdc_get_fw_act_history_C2(r, dev, cfg.output_format); + } + + if (ret) + fprintf(stderr, "ERROR: WDC: Failure reading the FW Activate History, ret = %d\n", ret); +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_do_clear_fw_activate_history_vuc(int fd) +{ + int ret = -1; + struct nvme_passthru_cmd admin_cmd; + + 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, NULL); + nvme_show_status(ret); + + return ret; +} + +static int wdc_do_clear_fw_activate_history_fid(int fd) +{ + int ret = -1; + __u32 result; + __u32 value = 1 << 31; /* Bit 31 - Clear Firmware Update History Log */ + + ret = nvme_set_features_simple(fd, WDC_NVME_CLEAR_FW_ACT_HIST_VU_FID, 0, value, + false, &result); + + nvme_show_status(ret); + 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."; + __u64 capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret; + + OPT_ARGS(opts) = { + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + capabilities = wdc_get_drive_capabilities(r, dev); + if (!(capabilities & WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY_MASK)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + if (capabilities & WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY) + ret = wdc_do_clear_fw_activate_history_vuc(dev_fd(dev)); + else + ret = wdc_do_clear_fw_activate_history_fid(dev_fd(dev)); + +out: + nvme_free_tree(r); + dev_close(dev); + 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."; + __u64 capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + __u32 result; + int ret = -1; + + + struct config { + bool disable; + bool enable; + bool status; + }; + + struct config cfg = { + .disable = false, + .enable = false, + .status = false, + }; + + 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + capabilities = wdc_get_drive_capabilities(r, dev); + 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_features_simple(dev_fd(dev), + WDC_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID, + 0, 1, false, &result); + + wdc_clear_reason_id(dev); + } else { + if (cfg.enable) { + ret = nvme_set_features_simple(dev_fd(dev), + WDC_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID, + 0, 0, false, &result); + } else if (cfg.status) { + ret = nvme_get_features_simple(dev_fd(dev), + WDC_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID, + 0, &result); + if (!ret) { + if (result) + fprintf(stderr, "Controller Option Telemetry Log Page State: Disabled\n"); + else + fprintf(stderr, "Controller Option Telemetry Log Page State: Enabled\n"); + } else { + nvme_show_status(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: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + + +static int wdc_get_serial_and_fw_rev(struct nvme_dev *dev, 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(dev_fd(dev), &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(struct nvme_dev *dev, __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(dev_fd(dev), &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(struct nvme_dev *dev, __u32 fileId, __u16 spiDestn, __u32 *logSize) +{ + int ret = WDC_STATUS_FAILURE; + struct nvme_passthru_cmd cmd; + + if (!dev || !logSize) { + ret = WDC_STATUS_INVALID_PARAMETER; + goto end; + } + + memset(&cmd, 0, sizeof(struct nvme_passthru_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(dev_fd(dev), &cmd, NULL); + + if (!ret && logSize) + *logSize = cmd.result; + if (ret != WDC_STATUS_SUCCESS) { + fprintf(stderr, "ERROR: WDC: VUReadSize() failed, "); + nvme_show_status(ret); + } + +end: + return ret; +} + +static int wdc_de_VU_read_buffer(struct nvme_dev *dev, __u32 fileId, __u16 spiDestn, + __u32 offsetInDwords, __u8 *dataBuffer, __u32 *bufferSize) +{ + int ret = WDC_STATUS_FAILURE; + struct nvme_passthru_cmd cmd; + __u32 noOfDwordExpected = 0; + + if (!dev || !dataBuffer || !bufferSize) { + ret = WDC_STATUS_INVALID_PARAMETER; + goto end; + } + + memset(&cmd, 0, sizeof(struct nvme_passthru_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(dev_fd(dev), &cmd, NULL); + + if (ret != WDC_STATUS_SUCCESS) { + fprintf(stderr, "ERROR: WDC: VUReadBuffer() failed, "); + nvme_show_status(ret); + } + +end: + return ret; +} + +static int wdc_get_log_dir_max_entries(struct nvme_dev *dev, __u32 *maxNumOfEntries) +{ + int ret = WDC_STATUS_FAILURE; + __u32 headerPayloadSize = 0; + __u8 *fileIdOffsetsBuffer = NULL; + __u32 fileIdOffsetsBufferSize = 0; + __u32 fileNum = 0; + __u16 fileOffset = 0; + + + if (!dev || !maxNumOfEntries) { + ret = WDC_STATUS_INVALID_PARAMETER; + return ret; + } + /* 1.Get log directory first four bytes */ + ret = wdc_de_VU_read_size(dev, 0, 5, (__u32 *)&headerPayloadSize); + if (ret != WDC_STATUS_SUCCESS) { + fprintf(stderr, + "ERROR: WDC: %s: Failed to get headerPayloadSize from file directory 0x%x\n", + __func__, ret); + return ret; + } + + fileIdOffsetsBufferSize = + WDC_DE_FILE_HEADER_SIZE + (headerPayloadSize * WDC_DE_FILE_OFFSET_SIZE); + fileIdOffsetsBuffer = (__u8 *)calloc(1, fileIdOffsetsBufferSize); + + /* 2.Read to get file offsets */ + ret = wdc_de_VU_read_buffer(dev, 0, 5, 0, fileIdOffsetsBuffer, &fileIdOffsetsBufferSize); + if (ret != WDC_STATUS_SUCCESS) { + 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: + free(fileIdOffsetsBuffer); + return ret; +} + +static enum WDC_DRIVE_ESSENTIAL_TYPE wdc_get_essential_type(__u8 fileName[]) +{ + enum WDC_DRIVE_ESSENTIAL_TYPE essentialType = WDC_DE_TYPE_NONE; + + if (!wdc_UtilsStrCompare((char *)fileName, WDC_DE_CORE_DUMP_FILE_NAME)) + essentialType = WDC_DE_TYPE_DUMPSNAPSHOT; + else if (!wdc_UtilsStrCompare((char *)fileName, WDC_DE_EVENT_LOG_FILE_NAME)) + essentialType = WDC_DE_TYPE_EVENTLOG; + else if (!wdc_UtilsStrCompare((char *)fileName, WDC_DE_MANUFACTURING_INFO_PAGE_FILE_NAME)) + essentialType = WDC_DE_TYPE_NVME_MANF_INFO; + + return essentialType; +} + +static int wdc_fetch_log_directory(struct nvme_dev *dev, struct WDC_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 (!dev || !directory) { + ret = WDC_STATUS_INVALID_PARAMETER; + goto end; + } + + ret = wdc_de_VU_read_size(dev, 0, 5, &fileDirectorySize); + if (ret != WDC_STATUS_SUCCESS) { + 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(dev, 0, 5, 0, fileDirectory, &fileDirectorySize); + if (ret != WDC_STATUS_SUCCESS) { + 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) { + 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 (!fileOffsetTemp) + continue; + + memset(&directory->logEntry[entryId], 0, sizeof(struct WDC_DRIVE_ESSENTIALS)); + memcpy(&directory->logEntry[entryId].metaData, fileOffset, sizeof(struct __packed 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 (!directory->logEntry[entryId].metaData.fileID) + continue; + + directory->logEntry[entryId].essentialType = wdc_get_essential_type(directory->logEntry[entryId].metaData.fileName); + entryId++; + } + + directory->numOfValidLogEntries = entryId; + +end: + if (fileDirectory) + free(fileDirectory); + return ret; +} + +static int wdc_fetch_log_file_from_device(struct nvme_dev *dev, __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 (!dev || !dataBuffer || !fileSize) { + ret = WDC_STATUS_INVALID_PARAMETER; + goto end; + } + + if (wdc_get_max_transfer_len(dev, &maximumTransferLength) < 0) { + ret = WDC_STATUS_FAILURE; + goto end; + } + + /* 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(dev, 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, (unsigned long)fileSize); + break; + } + } + } else { + buffSize = (__u32)fileSize; + ret = wdc_de_VU_read_buffer(dev, 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, (unsigned long)fileSize); + } + } + +end: + return ret; +} + +static int wdc_de_get_dump_trace(struct nvme_dev *dev, 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 (!dev || !binFileName || !filePath) { + ret = WDC_STATUS_INVALID_PARAMETER; + return ret; + } + + if (wdc_get_max_transfer_len(dev, &maximumTransferLength) < 0) + return WDC_STATUS_FAILURE; + + do { + /* Get dumptrace size */ + ret = wdc_de_VU_read_size(dev, 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) { + 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(dev, 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; +} + +int wdc_fetch_vu_file_directory(struct nvme_dev *dev, + struct WDC_DE_VU_LOG_DIRECTORY deEssentialsList, + __s8 *bufferFolderPath, __u8 *serialNo, __u8 *timeString) +{ + int ret = wdc_fetch_log_directory(dev, &deEssentialsList); + __u32 listIdx; + char *dataBuffer; + char fileName[MAX_PATH_LEN]; + + if (ret != WDC_STATUS_SUCCESS) { + fprintf(stderr, "WDC: wdc_fetch_log_directory failed, ret = %d\n", ret); + return ret; + } + + /* Get Debug Data Files */ + for (listIdx = 0; listIdx < deEssentialsList.numOfValidLogEntries; listIdx++) { + if (!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(dev, + 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); + } + } + + return ret; +} + +int wdc_read_debug_directory(struct nvme_dev *dev, __s8 *bufferFolderPath, __u8 *serialNo, + __u8 *timeString) +{ + __u32 maxNumOfVUFiles = 0; + int ret = wdc_get_log_dir_max_entries(dev, &maxNumOfVUFiles); + struct WDC_DE_VU_LOG_DIRECTORY deEssentialsList; + + if (ret != WDC_STATUS_SUCCESS) { + fprintf(stderr, "WDC: wdc_get_log_dir_max_entries failed, ret = %d\n", ret); + return ret; + } + + memset(&deEssentialsList, 0, sizeof(deEssentialsList)); + deEssentialsList.logEntry = + (struct WDC_DRIVE_ESSENTIALS *)calloc(1, sizeof(struct WDC_DRIVE_ESSENTIALS) * maxNumOfVUFiles); + deEssentialsList.maxNumLogEntries = maxNumOfVUFiles; + + ret = wdc_fetch_vu_file_directory(dev, deEssentialsList, bufferFolderPath, serialNo, + timeString); + + free(deEssentialsList.logEntry); + deEssentialsList.logEntry = NULL; + + return ret; +} + +static int wdc_do_drive_essentials(nvme_root_t r, struct nvme_dev *dev, + 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; + 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_slot fw_log; + struct WDC_NVME_DE_VU_LOGPAGES *vuLogInput = NULL; + + 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)); + + if (wdc_get_serial_and_fw_rev(dev, (char *)idSerialNo, (char *)idFwRev)) { + fprintf(stderr, "ERROR: WDC: get serial # and fw revision failed\n"); + return -1; + } + + 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) { + 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) { + 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) { + fprintf(stderr, "ERROR: WDC: create directory failed, ret = %d, dir = %s\n", ret, bufferFolderPath); + return -1; + } + + 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(dev_fd(dev), &ctrl); + if (ret) { + fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed, ret = %d\n", ret); + return -1; + } + + 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(dev_fd(dev), 1, &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_get_log_error(dev_fd(dev), elogNumEntries, false, + 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_get_log_smart(dev_fd(dev), NVME_NSID_ALL, false, + &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_slot)); + ret = nvme_get_log_fw_slot(dev_fd(dev), false, &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_slot)); + } + + /* Get VU log pages */ + /* define inputs for vendor unique log pages */ + vuLogInput = (struct WDC_NVME_DE_VU_LOGPAGES *)calloc(1, sizeof(struct WDC_NVME_DE_VU_LOGPAGES)); + vuLogInput->numOfVULogPages = ARRAY_SIZE(deVULogPagesList); + + for (vuLogIdx = 0; vuLogIdx < vuLogInput->numOfVULogPages; vuLogIdx++) { + dataBufferSize = deVULogPagesList[vuLogIdx].logPageLen; + dataBuffer = calloc(1, dataBufferSize); + memset(dataBuffer, 0, dataBufferSize); + + ret = nvme_get_log_simple(dev_fd(dev), + deVULogPagesList[vuLogIdx].logPageId, + 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 < ARRAY_SIZE(deFeatureIdList); 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_features_data(dev_fd(dev), + (enum nvme_features_id)deFeatureIdList[listIdx].featureId, + WDC_DE_GLOBAL_NSID, + 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)); + } + } + + ret = wdc_read_debug_directory(dev, bufferFolderPath, serialNo, timeString); + + /* 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); + ret = wdc_de_get_dump_trace(dev, (char *)bufferFolderPath, 0, fileName); + if (ret != WDC_STATUS_SUCCESS) + 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) + 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"); + nvme_free_tree(r); + 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}; + __u64 capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + char *d_ptr; + int ret; + + struct config { + char *dirName; + }; + + struct config cfg = { + .dirName = NULL, + }; + + OPT_ARGS(opts) = { + OPT_STRING("dir-name", 'd', "DIRECTORY", &cfg.dirName, dirName), + OPT_END() + }; + + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + capabilities = wdc_get_drive_capabilities(r, dev); + if ((capabilities & WDC_DRIVE_CAP_DRIVE_ESSENTIALS) != WDC_DRIVE_CAP_DRIVE_ESSENTIALS) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + if (cfg.dirName) { + strncpy(d, cfg.dirName, PATH_MAX - 1); + d_ptr = d; + } else { + d_ptr = NULL; + } + + ret = wdc_do_drive_essentials(r, dev, d_ptr, k); +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_do_drive_resize(struct nvme_dev *dev, uint64_t new_size) +{ + int ret; + struct nvme_passthru_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof(struct nvme_passthru_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(dev_fd(dev), &admin_cmd, NULL); + return ret; +} + +static int wdc_do_namespace_resize(struct nvme_dev *dev, __u32 nsid, __u32 op_option) +{ + int ret; + struct nvme_passthru_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd)); + admin_cmd.opcode = WDC_NVME_NAMESPACE_RESIZE_OPCODE; + admin_cmd.nsid = nsid; + admin_cmd.cdw10 = op_option; + + ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL); + return ret; +} + +static int wdc_do_drive_info(struct nvme_dev *dev, __u32 *result) +{ + int ret; + struct nvme_passthru_cmd admin_cmd; + + memset(&admin_cmd, 0, sizeof(struct nvme_passthru_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(dev_fd(dev), &admin_cmd, NULL); + + 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; + struct nvme_dev *dev; + nvme_root_t r; + int ret; + + struct config { + uint64_t size; + }; + + struct config cfg = { + .size = 0, + }; + + OPT_ARGS(opts) = { + OPT_UINT("size", 's', &cfg.size, size), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + wdc_check_device(r, dev); + capabilities = wdc_get_drive_capabilities(r, dev); + if ((capabilities & WDC_DRIVE_CAP_RESIZE) == WDC_DRIVE_CAP_RESIZE) { + ret = wdc_do_drive_resize(dev, 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); + + nvme_show_status(ret); + nvme_free_tree(r); + dev_close(dev); + 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; + struct nvme_dev *dev; + nvme_root_t r; + int 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + 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"); + dev_close(dev); + return -1; + } + + r = nvme_scan(NULL); + wdc_check_device(r, dev); + capabilities = wdc_get_drive_capabilities(r, dev); + if ((capabilities & WDC_DRIVE_CAP_NS_RESIZE) == WDC_DRIVE_CAP_NS_RESIZE) { + ret = wdc_do_namespace_resize(dev, cfg.namespace_id, + cfg.op_option); + + if (ret) + 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; + } + + nvme_show_status(ret); + nvme_free_tree(r); + dev_close(dev); + 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"; + struct nvme_dev *dev; + nvme_root_t r; + 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + + if (ret) + return ret; + + r = nvme_scan(NULL); + + if (cfg.log_id != NVME_LOG_LID_TELEMETRY_HOST && + cfg.log_id != NVME_LOG_LID_TELEMETRY_CTRL) { + fprintf(stderr, "ERROR: WDC: Invalid Log ID. It must be 7 (Host) or 8 (Controller)\n"); + ret = -1; + goto close_dev; + } + + if (cfg.file) { + 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_dev; + } + 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_LID_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(dev, f, PATH_MAX, fileSuffix) == -1) { + fprintf(stderr, "ERROR: WDC: failed to generate file name\n"); + ret = -1; + goto close_dev; + } + if (strlen(f) > PATH_MAX - 5) { + fprintf(stderr, "ERROR: WDC: file name overflow\n"); + ret = -1; + goto close_dev; + } + strcat(f, ".bin"); + } + + fprintf(stderr, "%s: filename = %s\n", __func__, f); + + capabilities = wdc_get_drive_capabilities(r, dev); + if ((capabilities & WDC_DRIVE_CAP_REASON_ID) == WDC_DRIVE_CAP_REASON_ID) { + ret = wdc_do_get_reason_id(dev, f, cfg.log_id); + } else { + fprintf(stderr, "ERROR: WDC:unsupported device for this command\n"); + ret = -1; + } + + nvme_show_status(ret); + +close_dev: + dev_close(dev); + nvme_free_tree(r); + return ret; +} + +static const char *nvme_log_id_to_string(__u8 log_id) +{ + switch (log_id) { + case NVME_LOG_LID_ERROR: + return "Error Information Log ID"; + case NVME_LOG_LID_SMART: + return "Smart/Health Information Log ID"; + case NVME_LOG_LID_FW_SLOT: + return "Firmware Slot Information Log ID"; + case NVME_LOG_LID_CHANGED_NS: + return "Namespace Changed Log ID"; + case NVME_LOG_LID_CMD_EFFECTS: + return "Commamds Supported and Effects Log ID"; + case NVME_LOG_LID_DEVICE_SELF_TEST: + return "Device Self Test Log ID"; + case NVME_LOG_LID_TELEMETRY_HOST: + return "Telemetry Host Initiated Log ID"; + case NVME_LOG_LID_TELEMETRY_CTRL: + return "Telemetry Controller Generated Log ID"; + case NVME_LOG_LID_ENDURANCE_GROUP: + return "Endurance Group Log ID"; + case NVME_LOG_LID_ANA: + return "ANA Log ID"; + case NVME_LOG_LID_PERSISTENT_EVENT: + return "Persistent Event Log ID"; + case NVME_LOG_LID_DISCOVER: + return "Discovery Log ID"; + case NVME_LOG_LID_RESERVATION: + return "Reservation Notification Log ID"; + case NVME_LOG_LID_SANITIZE: + return "Sanitize Status Log ID"; + case WDC_LOG_ID_C0: + return "WDC Vendor Unique Log ID C0"; + case WDC_LOG_ID_C1: + return "WDC Vendor Unique Log ID C1"; + case WDC_LOG_ID_C2: + return "WDC Vendor Unique Log ID C2"; + case WDC_LOG_ID_C3: + return "WDC Vendor Unique Log ID C3"; + case WDC_LOG_ID_C4: + return "WDC Vendor Unique Log ID C4"; + case WDC_LOG_ID_C5: + return "WDC Vendor Unique Log ID C5"; + case WDC_LOG_ID_C6: + return "WDC Vendor Unique Log ID C6"; + case WDC_LOG_ID_C8: + return "WDC Vendor Unique Log ID C8"; + case WDC_LOG_ID_CA: + return "WDC Vendor Unique Log ID CA"; + case WDC_LOG_ID_CB: + return "WDC Vendor Unique Log ID CB"; + case WDC_LOG_ID_D0: + return "WDC Vendor Unique Log ID D0"; + case WDC_LOG_ID_D1: + return "WDC Vendor Unique Log ID D1"; + case WDC_LOG_ID_D6: + return "WDC Vendor Unique Log ID D6"; + case WDC_LOG_ID_D7: + return "WDC Vendor Unique Log ID D7"; + case WDC_LOG_ID_D8: + return "WDC Vendor Unique Log ID D8"; + case WDC_LOG_ID_DE: + return "WDC Vendor Unique Log ID DE"; + case WDC_LOG_ID_F0: + return "WDC Vendor Unique Log ID F0"; + case WDC_LOG_ID_F1: + return "WDC Vendor Unique Log ID F1"; + case WDC_LOG_ID_F2: + return "WDC Vendor Unique Log ID F2"; + case WDC_LOG_ID_FA: + return "WDC Vendor Unique Log ID FA"; + default: + return "Unknown Log ID"; + } +} + +static void __json_log_page_directory(struct log_page_directory *directory) +{ + __u32 bitmap_idx; + __u8 log_id; + struct json_object *root; + struct json_object *entries; + + root = json_create_object(); + + entries = json_create_array(); + json_object_add_value_array(root, "Entries", entries); + + for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) { + log_id = bitmap_idx; + if (!log_page_name[log_id]) + continue; + if (directory->supported_lid_bitmap & (1ULL << bitmap_idx)) { + struct json_object *json_entry = json_create_object(); + + json_object_add_value_uint(json_entry, "Log ID", log_id); + json_object_add_value_string(json_entry, "Log Page Name", + log_page_name[log_id]); + + json_array_add_value_object(entries, json_entry); + } + } + + for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) { + log_id = NVME_LOG_NS_BASE + bitmap_idx; + if (!log_page_name[log_id]) + continue; + if (directory->supported_ns_lid_bitmap & (1ULL << bitmap_idx)) { + struct json_object *json_entry = json_create_object(); + + json_object_add_value_uint(json_entry, "Log ID", log_id); + json_object_add_value_string(json_entry, "Log Page Name", + log_page_name[log_id]); + + json_array_add_value_object(entries, json_entry); + } + } + + for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) { + log_id = NVME_LOG_VS_BASE + bitmap_idx; + if (!log_page_name[log_id]) + continue; + if (directory->supported_vs_lid_bitmap & (1ULL << bitmap_idx)) { + struct json_object *json_entry = json_create_object(); + + json_object_add_value_uint(json_entry, "Log ID", log_id); + json_object_add_value_string(json_entry, "Log Page Name", + log_page_name[log_id]); + + json_array_add_value_object(entries, json_entry); + } + } + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + + +static void __show_log_page_directory(struct log_page_directory *directory) +{ + __u32 bitmap_idx; + __u8 log_id; + + for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) { + if (directory->supported_lid_bitmap & (1ULL << bitmap_idx)) { + log_id = bitmap_idx; + if (log_page_name[log_id]) + printf("0x%02X: %s\n", log_id, log_page_name[log_id]); + } + } + + for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) { + if (directory->supported_ns_lid_bitmap & (1ULL << bitmap_idx)) { + log_id = NVME_LOG_NS_BASE + bitmap_idx; + if (log_page_name[log_id]) + printf("0x%02X: %s\n", log_id, log_page_name[log_id]); + } + } + + for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) { + if (directory->supported_vs_lid_bitmap & (1ULL << bitmap_idx)) { + log_id = NVME_LOG_VS_BASE + bitmap_idx; + if (log_page_name[log_id]) + printf("0x%02X: %s\n", log_id, log_page_name[log_id]); + } + } +} + +static int wdc_log_page_directory(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve Log Page Directory."; + enum nvme_print_flags fmt; + struct nvme_dev *dev; + int ret = 0; + nvme_root_t r; + __u64 capabilities = 0; + struct wdc_c2_cbs_data *cbs_data = NULL; + int i; + __u8 log_id = 0; + __u32 device_id, read_vendor_id; + bool uuid_supported = false; + struct nvme_id_uuid_list uuid_list; + + 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + ret = validate_output_format(cfg.output_format, &fmt); + if (ret < 0) { + fprintf(stderr, "%s: ERROR: WDC: invalid output format\n", __func__); + dev_close(dev); + return ret; + } + + r = nvme_scan(NULL); + capabilities = wdc_get_drive_capabilities(r, dev); + + if (!(capabilities & WDC_DRIVE_CAP_LOG_PAGE_DIR)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + } else { + + memset(&uuid_list, 0, sizeof(struct nvme_id_uuid_list)); + if (wdc_CheckUuidListSupport(dev, &uuid_list)) + uuid_supported = true; + + if (uuid_supported) + fprintf(stderr, "WDC: UUID lists supported\n"); + else + fprintf(stderr, "WDC: UUID lists NOT supported\n"); + + + ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id); + log_id = (device_id == WDC_NVME_ZN350_DEV_ID || + device_id == WDC_NVME_ZN350_DEV_ID_1) ? + WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID_C8 : + WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID; + + if (!wdc_is_sn861(device_id)) { + /* verify the 0xC2 Device Manageability log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, log_id) == false) { + fprintf(stderr, + "%s: ERROR: WDC: 0x%x Log Page not supported\n", + __func__, log_id); + ret = -1; + goto out; + } + + if (!get_dev_mgment_cbs_data(r, dev, + WDC_C2_LOG_PAGES_SUPPORTED_ID, + (void *)&cbs_data)) { + fprintf(stderr, + "%s: ERROR: WDC: 0xC2 Log Page entry ID 0x%x not found\n", + __func__, WDC_C2_LOG_PAGES_SUPPORTED_ID); + ret = -1; + goto out; + } + if (!cbs_data) { + fprintf(stderr, "%s: ERROR: WDC: NULL_data ptr\n", __func__); + ret = -1; + goto out; + } + printf("Log Page Directory\n"); + /* print the supported pages */ + if (!strcmp(cfg.output_format, "normal")) { + for (i = 0; i < le32_to_cpu(cbs_data->length); i++) + printf("0x%x - %s\n", cbs_data->data[i], + nvme_log_id_to_string(cbs_data->data[i])); + } else if (!strcmp(cfg.output_format, "binary")) { + d((__u8 *)cbs_data->data, + le32_to_cpu(cbs_data->length), 16, 1); + } else if (!strcmp(cfg.output_format, "json")) { + struct json_object *root = json_create_object(); + + for (i = 0; i < le32_to_cpu(cbs_data->length); i++) { + json_object_add_value_int(root, + nvme_log_id_to_string(cbs_data->data[i]), + cbs_data->data[i]); + } + + 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); + } + + free(cbs_data); + } else { + struct log_page_directory *dir; + void *data = NULL; + __u32 result; + + if (posix_memalign(&data, getpagesize(), 512)) { + fprintf(stderr, + "can not allocate log page directory payload\n"); + ret = ENOMEM; + goto out; + } + + dir = (struct log_page_directory *)data; + ret = nvme_admin_passthru(dev_fd(dev), WDC_NVME_ADMIN_VUC_OPCODE_D2, 0, 0, + 0, 0, 0, 8, + 0, WDC_VUC_SUBOPCODE_LOG_PAGE_DIR_D2, 0, 0, 0, + 32, data, 0, NULL, + 0, &result); + + if (!ret) { + switch (fmt) { + case BINARY: + d_raw((unsigned char *)data, 32); + break; + case JSON: + __json_log_page_directory(dir); + break; + default: + __show_log_page_directory(dir); + } + } else { + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(ret, false), ret); + } + } + } + +out: + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_get_drive_reason_id(struct nvme_dev *dev, 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(dev_fd(dev), &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(struct nvme_dev *dev, __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(dev, 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) { + if (mkdir(reason_id_path, 0700) < 0) { + fprintf(stderr, "%s: ERROR: failed to mkdir %s: %s\n", + __func__, reason_id_path, strerror(errno)); + return -1; + } + } + + 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(struct nvme_dev *dev) +{ + int ret = -1; + int verify_file; + char *reason_id_file; + char drive_reason_id[PATH_MAX] = {0}; + + if (wdc_get_drive_reason_id(dev, 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_dump_telemetry_hdr(struct nvme_dev *dev, int log_id, struct nvme_telemetry_log *log_hdr) +{ + int ret = 0; + + if (log_id == NVME_LOG_LID_TELEMETRY_HOST) + ret = nvme_get_log_create_telemetry_host(dev_fd(dev), log_hdr); + else + ret = nvme_get_log_telemetry_ctrl(dev_fd(dev), false, 0, 512, + (void *)log_hdr); + + 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 int wdc_do_get_reason_id(struct nvme_dev *dev, char *file, int log_id) +{ + int ret; + struct nvme_telemetry_log *log_hdr; + __u32 log_hdr_size = sizeof(struct nvme_telemetry_log); + __u32 reason_id_size = 0; + + log_hdr = (struct nvme_telemetry_log *)malloc(log_hdr_size); + if (!log_hdr) { + 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(dev, log_id, log_hdr); + if (ret) { + 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_LID_TELEMETRY_CTRL) + wdc_save_reason_id(dev, 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 void wdc_print_nand_stats_normal(__u16 version, void *data) +{ + struct wdc_nand_stats *nand_stats = (struct wdc_nand_stats *)(data); + struct wdc_nand_stats_V3 *nand_stats_v3 = (struct wdc_nand_stats_V3 *)(data); + __u64 temp_raw; + __u16 temp_norm; + __u64 *temp_ptr = NULL; + + switch (version) { + case 0: + printf(" NAND Statistics :-\n"); + printf(" NAND Writes TLC (Bytes) %s\n", + uint128_t_to_string( + le128_to_cpu(nand_stats->nand_write_tlc))); + printf(" NAND Writes SLC (Bytes) %s\n", + uint128_t_to_string( + le128_to_cpu(nand_stats->nand_write_slc))); + printf(" NAND Program Failures %"PRIu32"\n", + (uint32_t)le32_to_cpu(nand_stats->nand_prog_failure)); + printf(" NAND Erase Failures %"PRIu32"\n", + (uint32_t)le32_to_cpu(nand_stats->nand_erase_failure)); + printf(" Bad Block Count %"PRIu32"\n", + (uint32_t)le32_to_cpu(nand_stats->bad_block_count)); + printf(" NAND XOR/RAID Recovery Trigger Events %"PRIu64"\n", + le64_to_cpu(nand_stats->nand_rec_trigger_event)); + printf(" E2E Error Counter %"PRIu64"\n", + le64_to_cpu(nand_stats->e2e_error_counter)); + printf(" Number Successful NS Resizing Events %"PRIu64"\n", + le64_to_cpu(nand_stats->successful_ns_resize_event)); + printf(" log page version %"PRIu16"\n", + le16_to_cpu(nand_stats->log_page_version)); + break; + case 3: + printf(" NAND Statistics V3:-\n"); + printf(" TLC Units Written %s\n", + uint128_t_to_string( + le128_to_cpu(nand_stats_v3->nand_write_tlc))); + printf(" SLC Units Written %s\n", + uint128_t_to_string( + le128_to_cpu(nand_stats_v3->nand_write_slc))); + temp_ptr = (__u64 *)nand_stats_v3->bad_nand_block_count; + temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF); + temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16); + printf(" Bad NAND Blocks Count - Normalized %"PRIu16"\n", + le16_to_cpu(temp_norm)); + printf(" Bad NAND Blocks Count - Raw %"PRIu64"\n", + le64_to_cpu(temp_raw)); + printf(" NAND XOR Recovery count %"PRIu64"\n", + le64_to_cpu(nand_stats_v3->xor_recovery_count)); + printf(" UECC Read Error count %"PRIu64"\n", + le64_to_cpu(nand_stats_v3->uecc_read_error_count)); + printf(" SSD End to End corrected errors %"PRIu64"\n", + le64_to_cpu(nand_stats_v3->ssd_correction_counts[0])); + printf(" SSD End to End detected errors %"PRIu32"\n", + le32_to_cpu(nand_stats_v3->ssd_correction_counts[8])); + printf(" SSD End to End uncorrected E2E errors %"PRIu32"\n", + le32_to_cpu(nand_stats_v3->ssd_correction_counts[12])); + printf(" System data %% life-used %u\n", + nand_stats_v3->percent_life_used); + printf(" User Data Erase Counts - TLC Min %"PRIu64"\n", + le64_to_cpu(nand_stats_v3->user_data_erase_counts[0])); + printf(" User Data Erase Counts - TLC Max %"PRIu64"\n", + le64_to_cpu(nand_stats_v3->user_data_erase_counts[1])); + printf(" User Data Erase Counts - SLC Min %"PRIu64"\n", + le64_to_cpu(nand_stats_v3->user_data_erase_counts[2])); + printf(" User Data Erase Counts - SLC Max %"PRIu64"\n", + le64_to_cpu(nand_stats_v3->user_data_erase_counts[3])); + temp_ptr = (__u64 *)nand_stats_v3->program_fail_count; + temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF); + temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16); + printf(" Program Fail Count - Normalized %"PRIu16"\n", + le16_to_cpu(temp_norm)); + printf(" Program Fail Count - Raw %"PRIu64"\n", + le64_to_cpu(temp_raw)); + temp_ptr = (__u64 *)nand_stats_v3->erase_fail_count; + temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF); + temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16); + printf(" Erase Fail Count - Normalized %"PRIu16"\n", + le16_to_cpu(temp_norm)); + printf(" Erase Fail Count - Raw %"PRIu64"\n", + le64_to_cpu(temp_raw)); + printf(" PCIe Correctable Error Count %"PRIu16"\n", + le16_to_cpu(nand_stats_v3->correctable_error_count)); + printf(" %% Free Blocks (User) %u\n", + nand_stats_v3->percent_free_blocks_user); + printf(" Security Version Number %"PRIu64"\n", + le64_to_cpu(nand_stats_v3->security_version_number)); + printf(" %% Free Blocks (System) %u\n", + nand_stats_v3->percent_free_blocks_system); + printf(" Data Set Management Commands %s\n", + uint128_t_to_string( + le128_to_cpu(nand_stats_v3->trim_completions))); + printf(" Estimate of Incomplete Trim Data %"PRIu64"\n", + le64_to_cpu(nand_stats_v3->trim_completions[16])); + printf(" %% of completed trim %u\n", + nand_stats_v3->trim_completions[24]); + printf(" Background Back-Pressure-Guage %u\n", + nand_stats_v3->back_pressure_guage); + printf(" Soft ECC Error Count %"PRIu64"\n", + le64_to_cpu(nand_stats_v3->soft_ecc_error_count)); + printf(" Refresh Count %"PRIu64"\n", + le64_to_cpu(nand_stats_v3->refresh_count)); + temp_ptr = (__u64 *)nand_stats_v3->bad_sys_nand_block_count; + temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF); + temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16); + printf(" Bad System Nand Block Count - Normalized %"PRIu16"\n", + le16_to_cpu(temp_norm)); + printf(" Bad System Nand Block Count - Raw %"PRIu64"\n", + le64_to_cpu(temp_raw)); + printf(" Endurance Estimate %s\n", + uint128_t_to_string( + le128_to_cpu(nand_stats_v3->endurance_estimate))); + printf(" Thermal Throttling Count %u\n", + nand_stats_v3->thermal_throttling_st_ct[0]); + printf(" Thermal Throttling Status %u\n", + nand_stats_v3->thermal_throttling_st_ct[1]); + printf(" Unaligned I/O %"PRIu64"\n", + le64_to_cpu(nand_stats_v3->unaligned_IO)); + printf(" Physical Media Units Read %s\n", + uint128_t_to_string( + le128_to_cpu(nand_stats_v3->physical_media_units))); + printf(" log page version %"PRIu16"\n", + le16_to_cpu(nand_stats_v3->log_page_version)); + break; + + default: + fprintf(stderr, "WDC: Nand Stats ERROR: Invalid version\n"); + break; + + } +} + +static void wdc_print_nand_stats_json(__u16 version, void *data) +{ + struct wdc_nand_stats *nand_stats = (struct wdc_nand_stats *)(data); + struct wdc_nand_stats_V3 *nand_stats_v3 = (struct wdc_nand_stats_V3 *)(data); + struct json_object *root = json_create_object(); + __u64 temp_raw; + __u16 temp_norm; + __u64 *temp_ptr = NULL; + + switch (version) { + case 0: + json_object_add_value_uint128(root, "NAND Writes TLC (Bytes)", + le128_to_cpu(nand_stats->nand_write_tlc)); + json_object_add_value_uint128(root, "NAND Writes SLC (Bytes)", + le128_to_cpu(nand_stats->nand_write_slc)); + json_object_add_value_uint(root, "NAND Program Failures", + le32_to_cpu(nand_stats->nand_prog_failure)); + json_object_add_value_uint(root, "NAND Erase Failures", + le32_to_cpu(nand_stats->nand_erase_failure)); + json_object_add_value_uint(root, "Bad Block Count", + le32_to_cpu(nand_stats->bad_block_count)); + json_object_add_value_uint64(root, "NAND XOR/RAID Recovery Trigger Events", + le64_to_cpu(nand_stats->nand_rec_trigger_event)); + json_object_add_value_uint64(root, "E2E Error Counter", + le64_to_cpu(nand_stats->e2e_error_counter)); + json_object_add_value_uint64(root, "Number Successful NS Resizing Events", + le64_to_cpu(nand_stats->successful_ns_resize_event)); + + json_print_object(root, NULL); + printf("\n"); + break; + case 3: + json_object_add_value_uint128(root, "NAND Writes TLC (Bytes)", + le128_to_cpu(nand_stats_v3->nand_write_tlc)); + json_object_add_value_uint128(root, "NAND Writes SLC (Bytes)", + le128_to_cpu(nand_stats_v3->nand_write_slc)); + temp_ptr = (__u64 *)nand_stats_v3->bad_nand_block_count; + temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF); + temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16); + json_object_add_value_uint(root, "Bad NAND Blocks Count - Normalized", + le16_to_cpu(temp_norm)); + json_object_add_value_uint64(root, "Bad NAND Blocks Count - Raw", + le64_to_cpu(temp_raw)); + json_object_add_value_uint64(root, "NAND XOR Recovery count", + le64_to_cpu(nand_stats_v3->xor_recovery_count)); + json_object_add_value_uint64(root, "UECC Read Error count", + le64_to_cpu(nand_stats_v3->uecc_read_error_count)); + json_object_add_value_uint64(root, "SSD End to End corrected errors", + le64_to_cpu(nand_stats_v3->ssd_correction_counts[0])); + json_object_add_value_uint(root, "SSD End to End detected errors", + le32_to_cpu(nand_stats_v3->ssd_correction_counts[8])); + json_object_add_value_uint(root, "SSD End to End uncorrected E2E errors", + le32_to_cpu(nand_stats_v3->ssd_correction_counts[12])); + json_object_add_value_uint(root, "System data % life-used", + nand_stats_v3->percent_life_used); + json_object_add_value_uint64(root, "User Data Erase Counts - SLC Min", + le64_to_cpu(nand_stats_v3->user_data_erase_counts[0])); + json_object_add_value_uint64(root, "User Data Erase Counts - SLC Max", + le64_to_cpu(nand_stats_v3->user_data_erase_counts[1])); + json_object_add_value_uint64(root, "User Data Erase Counts - TLC Min", + le64_to_cpu(nand_stats_v3->user_data_erase_counts[2])); + json_object_add_value_uint64(root, "User Data Erase Counts - TLC Max", + le64_to_cpu(nand_stats_v3->user_data_erase_counts[3])); + temp_ptr = (__u64 *)nand_stats_v3->program_fail_count; + temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF); + temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16); + json_object_add_value_uint(root, "Program Fail Count - Normalized", + le16_to_cpu(temp_norm)); + json_object_add_value_uint64(root, "Program Fail Count - Raw", + le64_to_cpu(temp_raw)); + temp_ptr = (__u64 *)nand_stats_v3->erase_fail_count; + temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF); + temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16); + json_object_add_value_uint(root, "Erase Fail Count - Normalized", + le16_to_cpu(temp_norm)); + json_object_add_value_uint64(root, "Erase Fail Count - Raw", + le64_to_cpu(temp_raw)); + json_object_add_value_uint(root, "PCIe Correctable Error Count", + le16_to_cpu(nand_stats_v3->correctable_error_count)); + json_object_add_value_uint(root, "% Free Blocks (User)", + nand_stats_v3->percent_free_blocks_user); + json_object_add_value_uint64(root, "Security Version Number", + le64_to_cpu(nand_stats_v3->security_version_number)); + json_object_add_value_uint(root, "% Free Blocks (System)", + nand_stats_v3->percent_free_blocks_system); + json_object_add_value_uint128(root, "Data Set Management Commands", + le128_to_cpu(nand_stats_v3->trim_completions)); + json_object_add_value_uint64(root, "Estimate of Incomplete Trim Data", + le64_to_cpu(nand_stats_v3->trim_completions[16])); + json_object_add_value_uint(root, "%% of completed trim", + nand_stats_v3->trim_completions[24]); + json_object_add_value_uint(root, "Background Back-Pressure-Guage", + nand_stats_v3->back_pressure_guage); + json_object_add_value_uint64(root, "Soft ECC Error Count", + le64_to_cpu(nand_stats_v3->soft_ecc_error_count)); + json_object_add_value_uint64(root, "Refresh Count", + le64_to_cpu(nand_stats_v3->refresh_count)); + temp_ptr = (__u64 *)nand_stats_v3->bad_sys_nand_block_count; + temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF); + temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16); + json_object_add_value_uint(root, "Bad System Nand Block Count - Normalized", + le16_to_cpu(temp_norm)); + json_object_add_value_uint64(root, "Bad System Nand Block Count - Raw", + le64_to_cpu(temp_raw)); + json_object_add_value_uint128(root, "Endurance Estimate", + le128_to_cpu(nand_stats_v3->endurance_estimate)); + json_object_add_value_uint(root, "Thermal Throttling Status", + nand_stats_v3->thermal_throttling_st_ct[0]); + json_object_add_value_uint(root, "Thermal Throttling Count", + nand_stats_v3->thermal_throttling_st_ct[1]); + json_object_add_value_uint64(root, "Unaligned I/O", + le64_to_cpu(nand_stats_v3->unaligned_IO)); + json_object_add_value_uint128(root, "Physical Media Units Read", + le128_to_cpu(nand_stats_v3->physical_media_units)); + json_object_add_value_uint(root, "log page version", + le16_to_cpu(nand_stats_v3->log_page_version)); + + json_print_object(root, NULL); + printf("\n"); + break; + default: + printf("%s: Invalid Stats Version = %d\n", __func__, version); + break; + } + + json_free_object(root); + +} + +static void wdc_print_pcie_stats_normal(struct wdc_vs_pcie_stats *pcie_stats) +{ + printf(" PCIE Statistics :-\n"); + printf(" Unsupported Request Error Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->unsupportedRequestErrorCount)); + printf(" ECRC Error Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->ecrcErrorStatusCount)); + printf(" Malformed TLP Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->malformedTlpStatusCount)); + printf(" Receiver Overflow Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->receiverOverflowStatusCount)); + printf(" Unexpected Completion Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->unexpectedCmpltnStatusCount)); + printf(" Complete Abort Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->completeAbortStatusCount)); + printf(" Completion Timeout Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->cmpltnTimoutStatusCount)); + printf(" Flow Control Error Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->flowControlErrorStatusCount)); + printf(" Poisoned TLP Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->poisonedTlpStatusCount)); + printf(" Dlink Protocol Error Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->dLinkPrtclErrorStatusCount)); + printf(" Advisory Non Fatal Error Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->advsryNFatalErrStatusCount)); + printf(" Replay Timer TO Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->replayTimerToStatusCount)); + printf(" Replay Number Rollover Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->replayNumRolloverStCount)); + printf(" Bad DLLP Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->badDllpStatusCount)); + printf(" Bad TLP Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->badTlpStatusCount)); + printf(" Receiver Error Status Counter %20"PRIu64"\n", + le64_to_cpu(pcie_stats->receiverErrStatusCount)); + +} + +static void wdc_print_pcie_stats_json(struct wdc_vs_pcie_stats *pcie_stats) +{ + struct json_object *root = json_create_object(); + + json_object_add_value_uint64(root, "Unsupported Request Error Counter", + le64_to_cpu(pcie_stats->unsupportedRequestErrorCount)); + json_object_add_value_uint64(root, "ECRC Error Status Counter", + le64_to_cpu(pcie_stats->ecrcErrorStatusCount)); + json_object_add_value_uint64(root, "Malformed TLP Status Counter", + le64_to_cpu(pcie_stats->malformedTlpStatusCount)); + + json_object_add_value_uint64(root, "Receiver Overflow Status Counter", + le64_to_cpu(pcie_stats->receiverOverflowStatusCount)); + json_object_add_value_uint64(root, "Unexpected Completion Status Counter", + le64_to_cpu(pcie_stats->unexpectedCmpltnStatusCount)); + json_object_add_value_uint64(root, "Complete Abort Status Counter", + le64_to_cpu(pcie_stats->completeAbortStatusCount)); + json_object_add_value_uint64(root, "Completion Timeout Status Counter", + le64_to_cpu(pcie_stats->cmpltnTimoutStatusCount)); + json_object_add_value_uint64(root, "Flow Control Error Status Counter", + le64_to_cpu(pcie_stats->flowControlErrorStatusCount)); + json_object_add_value_uint64(root, "Poisoned TLP Status Counter", + le64_to_cpu(pcie_stats->poisonedTlpStatusCount)); + json_object_add_value_uint64(root, "Dlink Protocol Error Status Counter", + le64_to_cpu(pcie_stats->dLinkPrtclErrorStatusCount)); + json_object_add_value_uint64(root, "Advisory Non Fatal Error Status Counter", + le64_to_cpu(pcie_stats->advsryNFatalErrStatusCount)); + json_object_add_value_uint64(root, "Replay Timer TO Status Counter", + le64_to_cpu(pcie_stats->replayTimerToStatusCount)); + json_object_add_value_uint64(root, "Replay Number Rollover Status Counter", + le64_to_cpu(pcie_stats->replayNumRolloverStCount)); + json_object_add_value_uint64(root, "Bad DLLP Status Counter", + le64_to_cpu(pcie_stats->badDllpStatusCount)); + json_object_add_value_uint64(root, "Bad TLP Status Counter", + le64_to_cpu(pcie_stats->badTlpStatusCount)); + json_object_add_value_uint64(root, "Receiver Error Status Counter", + le64_to_cpu(pcie_stats->receiverErrStatusCount)); + + json_print_object(root, NULL); + printf("\n"); + + json_free_object(root); +} + +static int wdc_do_vs_nand_stats_sn810_2(struct nvme_dev *dev, char *format) +{ + enum nvme_print_flags fmt; + uint8_t *data = NULL; + int ret; + + data = NULL; + ret = nvme_get_ext_smart_cloud_log(dev_fd(dev), &data, 0, + NVME_NSID_ALL); + + if (ret) { + fprintf(stderr, "ERROR: WDC: %s : Failed to retrieve NAND stats\n", __func__); + goto out; + } else { + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: %s : invalid output format\n", __func__); + goto out; + } + + /* parse the data */ + switch (fmt) { + case NORMAL: + wdc_print_ext_smart_cloud_log_normal(data, WDC_SCA_V1_NAND_STATS); + break; + case JSON: + wdc_print_ext_smart_cloud_log_json(data, WDC_SCA_V1_NAND_STATS); + break; + default: + break; + } + } + +out: + if (data) + free(data); + return ret; +} + +static int wdc_do_vs_nand_stats(struct nvme_dev *dev, char *format) +{ + enum nvme_print_flags fmt; + uint8_t *output = NULL; + __u16 version = 0; + int ret; + + output = (uint8_t *)calloc(WDC_NVME_NAND_STATS_SIZE, sizeof(uint8_t)); + if (!output) { + fprintf(stderr, "ERROR: WDC: calloc: %s\n", strerror(errno)); + ret = -1; + goto out; + } + + ret = nvme_get_log_simple(dev_fd(dev), WDC_NVME_NAND_STATS_LOG_ID, + WDC_NVME_NAND_STATS_SIZE, (void *)output); + if (ret) { + fprintf(stderr, "ERROR: WDC: %s : Failed to retrieve NAND stats\n", __func__); + goto out; + } else { + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: invalid output format\n"); + goto out; + } + + version = output[WDC_NVME_NAND_STATS_SIZE - 2]; + + /* parse the data */ + switch (fmt) { + case NORMAL: + wdc_print_nand_stats_normal(version, output); + break; + case JSON: + wdc_print_nand_stats_json(version, output); + break; + default: + 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."; + struct nvme_dev *dev; + nvme_root_t r; + __u64 capabilities = 0; + uint32_t read_device_id = 0, read_vendor_id = 0; + int ret; + + 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + capabilities = wdc_get_drive_capabilities(r, dev); + + if (!(capabilities & WDC_DRIVE_CAP_NAND_STATS)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + } else { + ret = wdc_get_pci_ids(r, dev, &read_device_id, &read_vendor_id); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: %s: failure to get pci ids, ret = %d\n", __func__, ret); + return -1; + } + + switch (read_device_id) { + case WDC_NVME_SN820CL_DEV_ID: + ret = wdc_do_vs_nand_stats_sn810_2(dev, + cfg.output_format); + break; + default: + ret = wdc_do_vs_nand_stats(dev, cfg.output_format); + break; + } + } + + if (ret) + fprintf(stderr, "ERROR: WDC: Failure reading NAND statistics, ret = %d\n", ret); + + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_do_vs_pcie_stats(struct nvme_dev *dev, + struct wdc_vs_pcie_stats *pcieStatsPtr) +{ + int ret; + struct nvme_passthru_cmd admin_cmd; + int pcie_stats_size = sizeof(struct wdc_vs_pcie_stats); + + memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd)); + admin_cmd.opcode = WDC_NVME_PCIE_STATS_OPCODE; + admin_cmd.addr = (__u64)(uintptr_t)pcieStatsPtr; + admin_cmd.data_len = pcie_stats_size; + + ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL); + + return ret; +} + +static int wdc_vs_pcie_stats(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve PCIE statistics."; + enum nvme_print_flags fmt; + struct nvme_dev *dev; + nvme_root_t r; + int ret; + __u64 capabilities = 0; + _cleanup_huge_ struct nvme_mem_huge mh = { 0, }; + struct wdc_vs_pcie_stats *pcieStatsPtr = NULL; + int pcie_stats_size = sizeof(struct wdc_vs_pcie_stats); + + 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + ret = validate_output_format(cfg.output_format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: invalid output format\n"); + goto out; + } + + pcieStatsPtr = nvme_alloc_huge(pcie_stats_size, &mh); + if (!pcieStatsPtr) { + fprintf(stderr, "ERROR: WDC: PCIE Stats alloc: %s\n", strerror(errno)); + ret = -1; + goto out; + } + + memset((void *)pcieStatsPtr, 0, pcie_stats_size); + + capabilities = wdc_get_drive_capabilities(r, dev); + + if (!(capabilities & WDC_DRIVE_CAP_PCIE_STATS)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + } else { + ret = wdc_do_vs_pcie_stats(dev, pcieStatsPtr); + if (ret) { + fprintf(stderr, "ERROR: WDC: Failure reading PCIE statistics, ret = 0x%x\n", ret); + } else { + /* parse the data */ + switch (fmt) { + case NORMAL: + wdc_print_pcie_stats_normal(pcieStatsPtr); + break; + case JSON: + wdc_print_pcie_stats_json(pcieStatsPtr); + break; + default: + break; + } + } + } +out: + nvme_free_tree(r); + dev_close(dev); + 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."; + enum nvme_print_flags fmt; + nvme_root_t r; + uint64_t capabilities = 0; + struct nvme_dev *dev; + int ret; + __le32 result; + __u16 size; + double rev; + struct nvme_id_ctrl ctrl; + char vsData[32] = {0}; + char major_rev = 0, minor_rev = 0; + __u8 *data = NULL; + __u32 ftl_unit_size = 0, tcg_dev_ownership = 0; + __u16 boot_spec_major = 0, boot_spec_minor = 0; + struct json_object *root = NULL; + char formatter[41] = { 0 }; + char rev_str[16] = { 0 }; + uint32_t read_device_id = -1, read_vendor_id = -1; + struct __packed wdc_nvme_ext_smart_log * ext_smart_log_ptr = NULL; + struct ocp_drive_info info; + __u32 data_len = 0; + unsigned int num_dwords = 0; + + struct config { + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + ret = validate_output_format(cfg.output_format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC %s invalid output format\n", __func__); + dev_close(dev); + return ret; + } + + /* get the id ctrl data used to fill in drive info below */ + ret = nvme_identify_ctrl(dev_fd(dev), &ctrl); + + if (ret) { + fprintf(stderr, "ERROR: WDC %s: Identify Controller failed\n", __func__); + dev_close(dev); + return ret; + } + + r = nvme_scan(NULL); + wdc_check_device(r, dev); + capabilities = wdc_get_drive_capabilities(r, dev); + if ((capabilities & WDC_DRIVE_CAP_INFO) == WDC_DRIVE_CAP_INFO) { + ret = wdc_get_pci_ids(r, dev, &read_device_id, &read_vendor_id); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: %s: failure to get pci ids, ret = %d\n", __func__, ret); + goto out; + } + + switch (read_device_id) { + 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_SN650_DEV_ID: + case WDC_NVME_SN650_DEV_ID_1: + case WDC_NVME_SN650_DEV_ID_2: + case WDC_NVME_SN650_DEV_ID_3: + case WDC_NVME_SN650_DEV_ID_4: + case WDC_NVME_SN655_DEV_ID: + case WDC_NVME_SN560_DEV_ID_1: + case WDC_NVME_SN560_DEV_ID_2: + case WDC_NVME_SN560_DEV_ID_3: + case WDC_NVME_SN550_DEV_ID: + case WDC_NVME_ZN350_DEV_ID: + case WDC_NVME_ZN350_DEV_ID_1: + ret = wdc_do_drive_info(dev, &result); + + if (!ret) { + size = (__u16)((cpu_to_le32(result) & 0xffff0000) >> 16); + rev = (double)(cpu_to_le32(result) & 0x0000ffff); + + if (fmt == NORMAL) { + printf("Drive HW Revision: %4.1f\n", (.1 * rev)); + printf("FTL Unit Size: 0x%x KB\n", size); + printf("Customer SN: %-.*s\n", (int)sizeof(ctrl.sn), &ctrl.sn[0]); + } else if (fmt == JSON) { + root = json_create_object(); + sprintf(rev_str, "%4.1f", (.1 * rev)); + json_object_add_value_string(root, "Drive HW Revision", rev_str); + + json_object_add_value_int(root, "FTL Unit Size", le16_to_cpu(size)); + wdc_StrFormat(formatter, sizeof(formatter), &ctrl.sn[0], sizeof(ctrl.sn)); + json_object_add_value_string(root, "Customer SN", formatter); + + json_print_object(root, NULL); + printf("\n"); + + json_free_object(root); + } + } + break; + case WDC_NVME_SN730_DEV_ID: + memcpy(vsData, &ctrl.vs[0], 32); + + major_rev = ctrl.sn[12]; + minor_rev = ctrl.sn[13]; + + if (fmt == NORMAL) { + printf("Drive HW Revision: %c.%c\n", major_rev, minor_rev); + printf("Customer SN: %-.*s\n", 14, &ctrl.sn[0]); + } else if (fmt == JSON) { + root = json_create_object(); + sprintf(rev_str, "%c.%c", major_rev, minor_rev); + json_object_add_value_string(root, "Drive HW Revison", rev_str); + wdc_StrFormat(formatter, sizeof(formatter), &ctrl.sn[0], 14); + json_object_add_value_string(root, "Customer SN", formatter); + + json_print_object(root, NULL); + printf("\n"); + + json_free_object(root); + } + break; + case WDC_NVME_SN820CL_DEV_ID: + /* Get the Drive HW Rev from the C6 Log page */ + ret = nvme_get_hw_rev_log(dev_fd(dev), &data, 0, + NVME_NSID_ALL); + if (!ret) { + struct wdc_nvme_hw_rev_log *log_data = (struct wdc_nvme_hw_rev_log *)data; + + major_rev = log_data->hw_rev_gdr; + + free(data); + data = NULL; + } else { + fprintf(stderr, "ERROR: WDC: %s: failure to get hw revision log\n", __func__); + ret = -1; + goto out; + } + + /* Get the Smart C0 log page */ + if (!(capabilities & WDC_DRIVE_CAP_CLOUD_LOG_PAGE)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + ret = nvme_get_ext_smart_cloud_log(dev_fd(dev), &data, + 0, NVME_NSID_ALL); + + if (!ret) { + ext_smart_log_ptr = (struct __packed wdc_nvme_ext_smart_log *)data; + + /* Set the FTL Unit size */ + ftl_unit_size = le32_to_cpu(ext_smart_log_ptr->ext_smart_ftlus); + + /* Set the Boot Spec Version */ + boot_spec_major = le16_to_cpu(ext_smart_log_ptr->ext_smart_maj); + boot_spec_minor = le16_to_cpu(ext_smart_log_ptr->ext_smart_min); + + /* Set the Drive Ownership Status */ + tcg_dev_ownership = le32_to_cpu(ext_smart_log_ptr->ext_smart_tcgos); + free(data); + } else { + fprintf(stderr, "ERROR: WDC: %s: failure to get extended smart cloud log\n", __func__); + ret = -1; + goto out; + } + + if (fmt == NORMAL) { + printf("Drive HW Revision: %2d\n", major_rev); + printf("FTL Unit Size: %d\n", ftl_unit_size); + printf("HyperScale Boot Version Spec: %d.%d\n", boot_spec_major, boot_spec_minor); + printf("TCG Device Ownership Status: %2d\n", tcg_dev_ownership); + + } else if (fmt == JSON) { + root = json_create_object(); + + json_object_add_value_int(root, "Drive HW Revison", major_rev); + json_object_add_value_int(root, "FTL Unit Size", ftl_unit_size); + sprintf(rev_str, "%d.%d", boot_spec_major, boot_spec_minor); + json_object_add_value_string(root, "HyperScale Boot Version Spec", rev_str); + json_object_add_value_int(root, "TCG Device Ownership Status", tcg_dev_ownership); + + json_print_object(root, NULL); + printf("\n"); + + json_free_object(root); + } + + break; + case WDC_NVME_SN861_DEV_ID: + fallthrough; + case WDC_NVME_SN861_DEV_ID_1: + data_len = sizeof(info); + num_dwords = data_len / 4; + if (data_len % 4 != 0) + num_dwords += 1; + + ret = nvme_admin_passthru(dev_fd(dev), + WDC_NVME_ADMIN_VUC_OPCODE_D2, + 0, 0, 0, 0, 0, num_dwords, 0, + WDC_VUC_SUBOPCODE_VS_DRIVE_INFO_D2, + 0, 0, 0, data_len, &info, 0, + NULL, 0, NULL); + + if (!ret) { + __u16 hw_rev_major, hw_rev_minor; + + hw_rev_major = le32_to_cpu(info.hw_revision) / 10; + hw_rev_minor = le32_to_cpu(info.hw_revision) % 10; + if (fmt == NORMAL) { + printf("HW Revision : %" PRIu32 ".%" PRIu32 "\n", + hw_rev_major, hw_rev_minor); + printf("FTL Unit Size : %" PRIu32 "\n", + le32_to_cpu(info.ftl_unit_size)); + } else if (fmt == JSON) { + char buf[20]; + + root = json_create_object(); + + memset((void *)buf, 0, 20); + sprintf(buf, "%" PRIu32 ".%" PRIu32, + hw_rev_major, hw_rev_minor); + + json_object_add_value_string(root, + "hw_revision", buf); + json_object_add_value_uint(root, + "ftl_unit_size", + le32_to_cpu(info.ftl_unit_size)); + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); + } + } + break; + default: + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + break; + } + } else { + fprintf(stderr, "ERROR: WDC: capability not supported by this device\n"); + ret = -1; + } + +out: + nvme_show_status(ret); + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_vs_temperature_stats(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + const char *desc = "Send a vs-temperature-stats command."; + struct nvme_smart_log smart_log; + struct nvme_id_ctrl id_ctrl; + enum nvme_print_flags fmt; + struct nvme_dev *dev; + nvme_root_t r; + uint64_t capabilities = 0; + __u32 hctm_tmt; + int temperature, temp_tmt1, temp_tmt2; + int ret; + + 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() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + ret = validate_output_format(cfg.output_format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: WDC: invalid output format\n"); + goto out; + } + + /* check if command is supported */ + wdc_check_device(r, dev); + capabilities = wdc_get_drive_capabilities(r, dev); + if ((capabilities & WDC_DRIVE_CAP_TEMP_STATS) != WDC_DRIVE_CAP_TEMP_STATS) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + /* get the temperature stats or report errors */ + ret = nvme_identify_ctrl(dev_fd(dev), &id_ctrl); + if (ret) + goto out; + ret = nvme_get_log_smart(dev_fd(dev), NVME_NSID_ALL, false, + &smart_log); + if (ret) + goto out; + + /* convert from kelvins to degrees Celsius */ + temperature = ((smart_log.temperature[1] << 8) | smart_log.temperature[0]) - 273; + + /* retrieve HCTM Thermal Management Temperatures */ + nvme_get_features_simple(dev_fd(dev), 0x10, 0, &hctm_tmt); + temp_tmt1 = ((hctm_tmt >> 16) & 0xffff) ? ((hctm_tmt >> 16) & 0xffff) - 273 : 0; + temp_tmt2 = (hctm_tmt & 0xffff) ? (hctm_tmt & 0xffff) - 273 : 0; + + if (fmt == NORMAL) { + /* print the temperature stats */ + printf("Temperature Stats for NVME device:%s namespace-id:%x\n", + dev->name, WDC_DE_GLOBAL_NSID); + + printf("Current Composite Temperature : %d °C\n", temperature); + printf("WCTEMP : %"PRIu16" °C\n", id_ctrl.wctemp - 273); + printf("CCTEMP : %"PRIu16" °C\n", id_ctrl.cctemp - 273); + printf("DITT support : 0\n"); + printf("HCTM support : %"PRIu16"\n", id_ctrl.hctma); + + printf("HCTM Light (TMT1) : %"PRIu16" °C\n", temp_tmt1); + printf("TMT1 Transition Counter : %"PRIu32"\n", smart_log.thm_temp1_trans_count); + printf("TMT1 Total Time : %"PRIu32"\n", smart_log.thm_temp1_total_time); + + printf("HCTM Heavy (TMT2) : %"PRIu16" °C\n", temp_tmt2); + printf("TMT2 Transition Counter : %"PRIu32"\n", smart_log.thm_temp2_trans_count); + printf("TMT2 Total Time : %"PRIu32"\n", smart_log.thm_temp2_total_time); + printf("Thermal Shutdown Threshold : 95 °C\n"); + } else if (fmt == JSON) { + struct json_object *root; + + root = json_create_object(); + + json_object_add_value_int(root, "Current Composite Temperature", le32_to_cpu(temperature)); + json_object_add_value_int(root, "WCTEMP", le16_to_cpu(id_ctrl.wctemp - 273)); + json_object_add_value_int(root, "CCTEMP", le16_to_cpu(id_ctrl.cctemp - 273)); + json_object_add_value_int(root, "DITT support", 0); + json_object_add_value_int(root, "HCTM support", le16_to_cpu(id_ctrl.hctma)); + + json_object_add_value_int(root, "HCTM Light (TMT1)", le16_to_cpu(temp_tmt1)); + json_object_add_value_int(root, "TMT1 Transition Counter", le32_to_cpu(smart_log.thm_temp1_trans_count)); + json_object_add_value_int(root, "TMT1 Total Time", le32_to_cpu(smart_log.thm_temp1_total_time)); + + json_object_add_value_int(root, "HCTM Light (TMT2)", le16_to_cpu(temp_tmt2)); + json_object_add_value_int(root, "TMT2 Transition Counter", le32_to_cpu(smart_log.thm_temp2_trans_count)); + json_object_add_value_int(root, "TMT2 Total Time", le32_to_cpu(smart_log.thm_temp2_total_time)); + json_object_add_value_int(root, "Thermal Shutdown Threshold", 95); + + json_print_object(root, NULL); + printf("\n"); + + json_free_object(root); + } else { + printf("%s: Invalid format\n", __func__); + } + +out: + nvme_show_status(ret); + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_capabilities(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + const char *desc = "Send a capabilities command."; + uint64_t capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret; + + OPT_ARGS(opts) = { + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + /* get capabilities */ + r = nvme_scan(NULL); + wdc_check_device(r, dev); + capabilities = wdc_get_drive_capabilities(r, dev); + + /* print command and supported status */ + printf("WDC Plugin Capabilities for NVME device:%s\n", dev->name); + printf("cap-diag : %s\n", + capabilities & WDC_DRIVE_CAP_CAP_DIAG ? "Supported" : "Not Supported"); + printf("drive-log : %s\n", + capabilities & WDC_DRIVE_CAP_DRIVE_LOG ? "Supported" : "Not Supported"); + printf("get-crash-dump : %s\n", + capabilities & WDC_DRIVE_CAP_CRASH_DUMP ? "Supported" : "Not Supported"); + printf("get-pfail-dump : %s\n", + capabilities & WDC_DRIVE_CAP_PFAIL_DUMP ? "Supported" : "Not Supported"); + printf("id-ctrl : Supported\n"); + printf("purge : %s\n", + capabilities & WDC_DRIVE_CAP_PURGE ? "Supported" : "Not Supported"); + printf("purge-monitor : %s\n", + capabilities & WDC_DRIVE_CAP_PURGE ? "Supported" : "Not Supported"); + printf("vs-internal-log : %s\n", + capabilities & WDC_DRIVE_CAP_INTERNAL_LOG_MASK ? "Supported" : "Not Supported"); + printf("vs-nand-stats : %s\n", + capabilities & WDC_DRIVE_CAP_NAND_STATS ? "Supported" : "Not Supported"); + printf("vs-smart-add-log : %s\n", + capabilities & WDC_DRIVE_CAP_SMART_LOG_MASK ? "Supported" : "Not Supported"); + printf("--C0 Log Page : %s\n", + capabilities & WDC_DRIVE_CAP_C0_LOG_PAGE ? "Supported" : "Not Supported"); + printf("--C1 Log Page : %s\n", + capabilities & WDC_DRIVE_CAP_C1_LOG_PAGE ? "Supported" : "Not Supported"); + printf("--C3 Log Page : %s\n", + capabilities & WDC_DRIVE_CAP_C3_LOG_PAGE ? "Supported" : "Not Supported"); + printf("--CA Log Page : %s\n", + capabilities & WDC_DRIVE_CAP_CA_LOG_PAGE ? "Supported" : "Not Supported"); + printf("--D0 Log Page : %s\n", + capabilities & WDC_DRIVE_CAP_D0_LOG_PAGE ? "Supported" : "Not Supported"); + printf("clear-pcie-correctable-errors : %s\n", + capabilities & WDC_DRIVE_CAP_CLEAR_PCIE_MASK ? "Supported" : "Not Supported"); + printf("drive-essentials : %s\n", + capabilities & WDC_DRIVE_CAP_DRIVE_ESSENTIALS ? "Supported" : "Not Supported"); + printf("get-drive-status : %s\n", + capabilities & WDC_DRIVE_CAP_DRIVE_STATUS ? "Supported" : "Not Supported"); + printf("clear-assert-dump : %s\n", + capabilities & WDC_DRIVE_CAP_CLEAR_ASSERT ? "Supported" : "Not Supported"); + printf("drive-resize : %s\n", + capabilities & WDC_DRIVE_CAP_RESIZE ? "Supported" : "Not Supported"); + printf("vs-fw-activate-history : %s\n", + capabilities & WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_MASK ? "Supported" : "Not Supported"); + printf("clear-fw-activate-history : %s\n", + capabilities & WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY_MASK ? "Supported" : "Not Supported"); + printf("vs-telemetry-controller-option: %s\n", + capabilities & WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG ? "Supported" : "Not Supported"); + printf("vs-error-reason-identifier : %s\n", + capabilities & WDC_DRIVE_CAP_REASON_ID ? "Supported" : "Not Supported"); + printf("log-page-directory : %s\n", + capabilities & WDC_DRIVE_CAP_LOG_PAGE_DIR ? "Supported" : "Not Supported"); + printf("namespace-resize : %s\n", + capabilities & WDC_DRIVE_CAP_NS_RESIZE ? "Supported" : "Not Supported"); + printf("vs-drive-info : %s\n", + capabilities & WDC_DRIVE_CAP_INFO ? "Supported" : "Not Supported"); + printf("vs-temperature-stats : %s\n", + capabilities & WDC_DRIVE_CAP_TEMP_STATS ? "Supported" : "Not Supported"); + printf("cloud-SSD-plugin-version : %s\n", + capabilities & WDC_DRIVE_CAP_CLOUD_SSD_VERSION ? "Supported" : "Not Supported"); + printf("vs-pcie-stats : %s\n", + capabilities & WDC_DRIVE_CAP_PCIE_STATS ? "Supported" : "Not Supported"); + printf("get-error-recovery-log : %s\n", + capabilities & WDC_DRIVE_CAP_OCP_C1_LOG_PAGE ? "Supported" : "Not Supported"); + printf("get-dev-capabilities-log : %s\n", + capabilities & WDC_DRIVE_CAP_OCP_C4_LOG_PAGE ? "Supported" : "Not Supported"); + printf("get-unsupported-reqs-log : %s\n", + capabilities & WDC_DRIVE_CAP_OCP_C5_LOG_PAGE ? "Supported" : "Not Supported"); + printf("get-latency-monitor-log : %s\n", + capabilities & WDC_DRIVE_CAP_C3_LOG_PAGE ? "Supported" : "Not Supported"); + printf("cloud-boot-SSD-version : %s\n", + capabilities & WDC_DRIVE_CAP_CLOUD_BOOT_SSD_VERSION ? "Supported" : "Not Supported"); + printf("vs-cloud-log : %s\n", + capabilities & WDC_DRIVE_CAP_CLOUD_LOG_PAGE ? "Supported" : "Not Supported"); + printf("vs-hw-rev-log : %s\n", + capabilities & WDC_DRIVE_CAP_HW_REV_LOG_PAGE ? "Supported" : "Not Supported"); + printf("vs-device_waf : %s\n", + capabilities & WDC_DRIVE_CAP_DEVICE_WAF ? "Supported" : "Not Supported"); + printf("set-latency-monitor-feature : %s\n", + capabilities & WDC_DRIVE_CAP_SET_LATENCY_MONITOR ? "Supported" : "Not Supported"); + printf("capabilities : Supported\n"); + nvme_free_tree(r); + dev_close(dev); + return 0; +} + +static int wdc_cloud_ssd_plugin_version(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Get Cloud SSD Plugin Version command."; + uint64_t capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret; + + OPT_ARGS(opts) = { + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + /* get capabilities */ + r = nvme_scan(NULL); + wdc_check_device(r, dev); + capabilities = wdc_get_drive_capabilities(r, dev); + + if ((capabilities & WDC_DRIVE_CAP_CLOUD_SSD_VERSION) == WDC_DRIVE_CAP_CLOUD_SSD_VERSION) { + /* print command and supported status */ + printf("WDC Cloud SSD Plugin Version: 1.0\n"); + } else { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + } + + nvme_free_tree(r); + dev_close(dev); + return 0; +} + +static int wdc_cloud_boot_SSD_version(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Get Cloud Boot SSD Version command."; + const char *namespace_id = "desired namespace id"; + nvme_root_t r; + uint64_t capabilities = 0; + struct nvme_dev *dev; + int ret; + int major = 0, minor = 0; + __u8 *data = NULL; + struct __packed wdc_nvme_ext_smart_log * ext_smart_log_ptr = NULL; + + struct config { + __u32 namespace_id; + }; + + struct config cfg = { + .namespace_id = NVME_NSID_ALL, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + /* get capabilities */ + r = nvme_scan(NULL); + wdc_check_device(r, dev); + capabilities = wdc_get_drive_capabilities(r, dev); + + if ((capabilities & WDC_DRIVE_CAP_CLOUD_BOOT_SSD_VERSION) == WDC_DRIVE_CAP_CLOUD_BOOT_SSD_VERSION) { + /* Get the 0xC0 Smart Cloud Attribute V1 log data */ + ret = nvme_get_ext_smart_cloud_log(dev_fd(dev), &data, 0, + cfg.namespace_id); + + ext_smart_log_ptr = (struct __packed wdc_nvme_ext_smart_log *)data; + if (!ret) { + major = le16_to_cpu(ext_smart_log_ptr->ext_smart_maj); + minor = le16_to_cpu(ext_smart_log_ptr->ext_smart_min); + + /* print the version returned from the log page */ + printf("HyperScale Boot Version: %d.%d\n", major, minor); + } else { + fprintf(stderr, "ERROR: WDC: Unable to read Extended Smart/C0 Log Page data\n"); + ret = -1; + } + + if (data) + free(data); + } else { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + } + + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int wdc_enc_get_log(int argc, char **argv, struct command *command, struct plugin *plugin) +{ + char *desc = "Get Enclosure Log."; + char *file = "Output file pathname."; + char *size = "Data retrieval transfer size."; + char *log = "Enclosure Log Page ID."; + struct nvme_dev *dev; + FILE *output_fd; + int xfer_size = 0; + int len; + int err = 0; + + struct config { + char *file; + __u32 xfer_size; + __u32 log_id; + }; + + struct config cfg = { + .file = NULL, + .xfer_size = 0, + .log_id = 0xffffffff, + }; + + OPT_ARGS(opts) = { + OPT_FILE("output-file", 'o', &cfg.file, file), + OPT_UINT("transfer-size", 's', &cfg.xfer_size, size), + OPT_UINT("log-id", 'l', &cfg.log_id, log), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + goto ret; + + if (!wdc_enc_check_model(dev)) { + err = -EINVAL; + goto closed_fd; + } + + if (cfg.log_id > 0xff) { + fprintf(stderr, + "Invalid log identifier: %d. Valid 0xd1, 0xd2, 0xd3, 0xd4, 0xe2, 0xe4\n", + cfg.log_id); + goto closed_fd; + } + + if (cfg.xfer_size) { + xfer_size = cfg.xfer_size; + if (!wdc_check_power_of_2(cfg.xfer_size)) { + fprintf(stderr, "%s: ERROR: xfer-size (%d) must be a power of 2\n", + __func__, cfg.xfer_size); + err = -EINVAL; + goto closed_fd; + } + } + + /* Log IDs are only for specific enclosures */ + if (cfg.log_id) { + xfer_size = (xfer_size) ? xfer_size : WDC_NVME_ENC_LOG_SIZE_CHUNK; + len = !cfg.file ? 0 : strlen(cfg.file); + if (len > 0) { + output_fd = fopen(cfg.file, "wb"); + if (!output_fd) { + fprintf(stderr, "%s: ERROR: opening:%s: %s\n", __func__, cfg.file, + strerror(errno)); + err = -EINVAL; + goto closed_fd; + } + } else { + output_fd = stdout; + } + if (cfg.log_id == WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_1 || + cfg.log_id == WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_2 || + cfg.log_id == WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_3 || + cfg.log_id == WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_4) { + fprintf(stderr, "args - sz:%x logid:%x of:%s\n", xfer_size, cfg.log_id, + cfg.file); + err = wdc_enc_get_nic_log(dev, cfg.log_id, xfer_size, + WDC_NVME_ENC_NIC_LOG_SIZE, output_fd); + } else { + fprintf(stderr, "args - sz:%x logid:%x of:%s\n", xfer_size, cfg.log_id, + cfg.file); + err = wdc_enc_submit_move_data(dev, NULL, 0, xfer_size, output_fd, + cfg.log_id, 0, 0); + } + + if (err == WDC_RESULT_NOT_AVAILABLE) { + fprintf(stderr, "No Log/Crashdump available\n"); + err = 0; + } else if (err) { + fprintf(stderr, "ERROR: 0x%x Failed to collect log-id:%x\n", err, + cfg.log_id); + } + } +closed_fd: + dev_close(dev); +ret: + return err; +} + +static int wdc_enc_submit_move_data(struct nvme_dev *dev, char *cmd, int len, + int xfer_size, FILE *out, int log_id, + int cdw14, int cdw15) +{ + struct timespec time; + uint32_t response_size, more; + int err; + int handle; + uint32_t offset = 0; + char *buf; + + buf = (char *)malloc(sizeof(__u8) * xfer_size); + if (!buf) { + fprintf(stderr, "%s: ERROR: malloc: %s\n", __func__, strerror(errno)); + return -1; + } + /* send something no matter what */ + cmd = (len) ? cmd : buf; + len = (len) ? len : 0x20; + + struct nvme_passthru_cmd nvme_cmd = { + .opcode = WDC_NVME_ADMIN_ENC_MGMT_SND, + .nsid = 0, + .addr = (__u64)(uintptr_t) cmd, + .data_len = ((len + sizeof(uint32_t) - 1) / sizeof(uint32_t)) * sizeof(uint32_t), + .cdw10 = len, + .cdw12 = log_id, + .cdw13 = 0, + .cdw14 = cdw14, + .cdw15 = cdw15, + }; + + clock_gettime(CLOCK_REALTIME, &time); + srand(time.tv_nsec); + handle = random(); /* Handle to associate send request with receive request */ + nvme_cmd.cdw11 = handle; + +#ifdef WDC_NVME_CLI_DEBUG + unsigned char *d = (unsigned char *)nvme_cmd.addr; + unsigned char *md = (unsigned char *)nvme_cmd.metadata; + + printf("NVME_ADMIN_COMMAND:\n"); + printf("opcode: 0x%02x, flags: 0x%02x, rsvd: 0x%04x, nsid: 0x%08x, cdw2: 0x%08x, ", + nvme_cmd.opcode, nvme_cmd.flags, nvme_cmd.rsvd1, nvme_cmd.nsid, nvme_cmd.cdw2); + printf("cdw3: 0x%08x, metadata_len: 0x%08x, data_len: 0x%08x, cdw10: 0x%08x, " + nvme_cmd.cdw3, nvme_cmd.metadata_len, nvme_cmd.data_len, nvme_cmd.cdw10); + printf("cdw11: 0x%08x, cdw12: 0x%08x, cdw13: 0x%08x, cdw14: 0x%08x, cdw15: 0x%08x, " + nvme_cmd.cdw11, nvme_cmd.cdw12, nvme_cmd.cdw13, nvme_cmd.cdw14, nvme_cmd.cdw15); + printf("timeout_ms: 0x%08x, result: 0x%08x, metadata: %s, data: %s\n", + nvme_cmd.timeout_ms, nvme_cmd.result, md, d); +#endif + nvme_cmd.result = 0; + err = nvme_submit_admin_passthru(dev_fd(dev), &nvme_cmd, NULL); + if (nvme_status_equals(err, NVME_STATUS_TYPE_NVME, NVME_SC_INTERNAL)) { + fprintf(stderr, "%s: WARNING : WDC: No log ID:x%x available\n", __func__, log_id); + } else if (err) { + fprintf(stderr, "%s: ERROR: WDC: NVMe Snd Mgmt\n", __func__); + nvme_show_status(err); + } else { + if (nvme_cmd.result == WDC_RESULT_NOT_AVAILABLE) { + free(buf); + return WDC_RESULT_NOT_AVAILABLE; + } + + do { + /* Sent request, now go retrieve response */ + nvme_cmd.flags = 0; + nvme_cmd.opcode = WDC_NVME_ADMIN_ENC_MGMT_RCV; + nvme_cmd.addr = (__u64)(uintptr_t) buf; + nvme_cmd.data_len = xfer_size; + nvme_cmd.cdw10 = xfer_size / sizeof(uint32_t); + nvme_cmd.cdw11 = handle; + nvme_cmd.cdw12 = log_id; + nvme_cmd.cdw13 = offset / sizeof(uint32_t); + nvme_cmd.cdw14 = cdw14; + nvme_cmd.cdw15 = cdw15; + nvme_cmd.result = 0; /* returned result !=0 indicates more data available */ + err = nvme_submit_admin_passthru(dev_fd(dev), + &nvme_cmd, NULL); + if (err) { + more = 0; + fprintf(stderr, "%s: ERROR: WDC: NVMe Rcv Mgmt ", __func__); + nvme_show_status(err); + } else { + more = nvme_cmd.result & WDC_RESULT_MORE_DATA; + response_size = nvme_cmd.result & ~WDC_RESULT_MORE_DATA; + fwrite(buf, response_size, 1, out); + offset += response_size; + if (more && (response_size & (sizeof(uint32_t)-1))) { + fprintf(stderr, "%s: ERROR: WDC: NVMe Rcv Mgmt response size:x%x not LW aligned\n", + __func__, response_size); + } + } + } while (more); + } + + free(buf); + return err; +} + +static int wdc_enc_get_nic_log(struct nvme_dev *dev, __u8 log_id, __u32 xfer_size, __u32 data_len, FILE *out) +{ + __u8 *dump_data; + __u32 curr_data_offset, curr_data_len; + int i, ret = -1; + struct nvme_passthru_cmd admin_cmd; + __u32 dump_length = data_len; + __u32 numd; + __u16 numdu, numdl; + + dump_data = (__u8 *)malloc(sizeof(__u8) * dump_length); + if (!dump_data) { + 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_passthru_cmd)); + curr_data_offset = 0; + curr_data_len = xfer_size; + i = 0; + + numd = (curr_data_len >> 2) - 1; + numdu = numd >> 16; + numdl = numd & 0xffff; + admin_cmd.opcode = nvme_admin_get_log_page; + admin_cmd.nsid = curr_data_offset; + admin_cmd.addr = (__u64)(uintptr_t) dump_data; + admin_cmd.data_len = curr_data_len; + admin_cmd.cdw10 = log_id | (numdl << 16); + admin_cmd.cdw11 = numdu; + + while (curr_data_offset < data_len) { +#ifdef WDC_NVME_CLI_DEBUG + fprintf(stderr, + "nsid 0x%08x addr 0x%08llx, data_len 0x%08x, cdw10 0x%08x, cdw11 0x%08x, cdw12 0x%08x, cdw13 0x%08x, cdw14 0x%08x\n", + admin_cmd.nsid, admin_cmd.addr, admin_cmd.data_len, admin_cmd.cdw10, + admin_cmd.cdw11, admin_cmd.cdw12, admin_cmd.cdw13, admin_cmd.cdw14); +#endif + ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL); + if (ret) { + nvme_show_status(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, (unsigned long)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; + numd = (curr_data_len >> 2) - 1; + numdu = numd >> 16; + numdl = numd & 0xffff; + admin_cmd.addr = (__u64)(uintptr_t)dump_data + (__u64)curr_data_offset; + admin_cmd.nsid = curr_data_offset; + admin_cmd.data_len = curr_data_len; + admin_cmd.cdw10 = log_id | (numdl << 16); + admin_cmd.cdw11 = numdu; + i++; + } + fwrite(dump_data, data_len, 1, out); + free(dump_data); + return ret; +} + +//------------------------------------------------------------------------------------ +// Description: set latency monitor feature +// +int wdc_set_latency_monitor_feature(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + const char *desc = "Set Latency Monitor feature."; + + uint64_t capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret; + __u32 result; + struct feature_latency_monitor buf = {0,}; + + const char *active_bucket_timer_threshold = + "This is the value that loads the Active Bucket Timer Threshold."; + const char *active_threshold_a = + "This is the value that loads into the Active Threshold A."; + const char *active_threshold_b = + "This is the value that loads into the Active Threshold B."; + const char *active_threshold_c = + "This is the value that loads into the Active Threshold C."; + const char *active_threshold_d = + "This is the value that loads into the Active Threshold D."; + const char *active_latency_config = + "This is the value that loads into the Active Latency Configuration."; + const char *active_latency_minimum_window = + "This is the value that loads into the Active Latency Minimum Window."; + const char *debug_log_trigger_enable = + "This is the value that loads into the Debug Log Trigger Enable."; + const char *discard_debug_log = "Discard Debug Log."; + const char *latency_monitor_feature_enable = "Latency Monitor Feature Enable."; + + struct config { + __u16 active_bucket_timer_threshold; + __u8 active_threshold_a; + __u8 active_threshold_b; + __u8 active_threshold_c; + __u8 active_threshold_d; + __u16 active_latency_config; + __u8 active_latency_minimum_window; + __u16 debug_log_trigger_enable; + __u8 discard_debug_log; + __u8 latency_monitor_feature_enable; + }; + + struct config cfg = { + .active_bucket_timer_threshold = 0x7E0, + .active_threshold_a = 0x5, + .active_threshold_b = 0x13, + .active_threshold_c = 0x1E, + .active_threshold_d = 0x2E, + .active_latency_config = 0xFFF, + .active_latency_minimum_window = 0xA, + .debug_log_trigger_enable = 0, + .discard_debug_log = 0, + .latency_monitor_feature_enable = 0x7, + }; + + OPT_ARGS(opts) = { + OPT_UINT("active_bucket_timer_threshold", 't', + &cfg.active_bucket_timer_threshold, + active_bucket_timer_threshold), + OPT_UINT("active_threshold_a", 'a', &cfg.active_threshold_a, + active_threshold_a), + OPT_UINT("active_threshold_b", 'b', &cfg.active_threshold_b, + active_threshold_b), + OPT_UINT("active_threshold_c", 'c', &cfg.active_threshold_c, + active_threshold_c), + OPT_UINT("active_threshold_d", 'd', &cfg.active_threshold_d, + active_threshold_d), + OPT_UINT("active_latency_config", 'f', + &cfg.active_latency_config, active_latency_config), + OPT_UINT("active_latency_minimum_window", 'w', + &cfg.active_latency_minimum_window, + active_latency_minimum_window), + OPT_UINT("debug_log_trigger_enable", 'r', + &cfg.debug_log_trigger_enable, debug_log_trigger_enable), + OPT_UINT("discard_debug_log", 'l', &cfg.discard_debug_log, + discard_debug_log), + OPT_UINT("latency_monitor_feature_enable", 'e', + &cfg.latency_monitor_feature_enable, + latency_monitor_feature_enable), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + + if (ret < 0) + return ret; + + /* get capabilities */ + r = nvme_scan(NULL); + wdc_check_device(r, dev); + capabilities = wdc_get_drive_capabilities(r, dev); + + if (!(capabilities & WDC_DRIVE_CAP_SET_LATENCY_MONITOR)) { + fprintf(stderr, "ERROR: WDC: unsupported device for this command\n"); + return -1; + } + + memset(&buf, 0, sizeof(struct feature_latency_monitor)); + + buf.active_bucket_timer_threshold = cfg.active_bucket_timer_threshold; + buf.active_threshold_a = cfg.active_threshold_a; + buf.active_threshold_b = cfg.active_threshold_b; + buf.active_threshold_c = cfg.active_threshold_c; + buf.active_threshold_d = cfg.active_threshold_d; + buf.active_latency_config = cfg.active_latency_config; + buf.active_latency_minimum_window = cfg.active_latency_minimum_window; + buf.debug_log_trigger_enable = cfg.debug_log_trigger_enable; + buf.discard_debug_log = cfg.discard_debug_log; + buf.latency_monitor_feature_enable = cfg.latency_monitor_feature_enable; + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = NVME_FEAT_OCP_LATENCY_MONITOR, + .nsid = 0, + .cdw12 = 0, + .save = 1, + .data_len = sizeof(struct feature_latency_monitor), + .data = (void *)&buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + ret = nvme_set_features(&args); + + if (ret < 0) { + perror("set-feature"); + } else if (!ret) { + printf("NVME_FEAT_OCP_LATENCY_MONITOR: 0x%02x\n", + NVME_FEAT_OCP_LATENCY_MONITOR); + printf("active bucket timer threshold: 0x%x\n", + buf.active_bucket_timer_threshold); + printf("active threshold a: 0x%x\n", buf.active_threshold_a); + printf("active threshold b: 0x%x\n", buf.active_threshold_b); + printf("active threshold c: 0x%x\n", buf.active_threshold_c); + printf("active threshold d: 0x%x\n", buf.active_threshold_d); + printf("active latency config: 0x%x\n", buf.active_latency_config); + printf("active latency minimum window: 0x%x\n", + buf.active_latency_minimum_window); + printf("debug log trigger enable: 0x%x\n", + buf.debug_log_trigger_enable); + printf("discard debug log: 0x%x\n", buf.discard_debug_log); + printf("latency monitor feature enable: 0x%x\n", + buf.latency_monitor_feature_enable); + } else if (ret > 0) + fprintf(stderr, "NVMe Status:%s(%x)\n", + nvme_status_to_string(ret, false), ret); + + return ret; +} diff --git a/plugins/wdc/wdc-nvme.h b/plugins/wdc/wdc-nvme.h new file mode 100644 index 0000000..d3692bc --- /dev/null +++ b/plugins/wdc/wdc-nvme.h @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/wdc/wdc-nvme + +#if !defined(WDC_NVME) || defined(CMD_HEADER_MULTI_READ) +#define WDC_NVME + +#define WDC_PLUGIN_VERSION "2.7.0" +#include "cmd.h" + +PLUGIN(NAME("wdc", "Western Digital vendor specific extensions", WDC_PLUGIN_VERSION), + 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("enc-get-log", "WDC Get Enclosure Log", wdc_enc_get_log) + 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) + ENTRY("vs-temperature-stats", "WDC Get Temperature Stats", + wdc_vs_temperature_stats) + ENTRY("capabilities", "WDC Device Capabilities", + wdc_capabilities) + ENTRY("cloud-SSD-plugin-version", + "WDC Cloud SSD Plugin Version", + wdc_cloud_ssd_plugin_version) + ENTRY("vs-pcie-stats", "WDC VS PCIE Statistics", + wdc_vs_pcie_stats) + ENTRY("get-latency-monitor-log", + "WDC Get Latency Monitor Log Page", + wdc_get_latency_monitor_log) + ENTRY("get-error-recovery-log", + "WDC Get Error Recovery Log Page", + wdc_get_error_recovery_log) + ENTRY("get-dev-capabilities-log", + "WDC Get Device Capabilities Log Page", + wdc_get_dev_capabilities_log) + ENTRY("get-unsupported-reqs-log", + "WDC Get Unsupported Requirements Log Page", + wdc_get_unsupported_reqs_log) + ENTRY("cloud-boot-SSD-version", + "WDC Get the Cloud Boot SSD Version", + wdc_cloud_boot_SSD_version) + ENTRY("vs-cloud-log", "WDC Get the Cloud Log Page", + wdc_vs_cloud_log) + ENTRY("vs-hw-rev-log", "WDC Get the Hardware Revision Log Page", + wdc_vs_hw_rev_log) + ENTRY("vs-device-waf", + "WDC Calculate Device Write Amplication Factor", + wdc_vs_device_waf) + ENTRY("set-latency-monitor-feature", + "WDC set Latency Monitor feature", + wdc_set_latency_monitor_feature) + ) +); + +#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..414a06a --- /dev/null +++ b/plugins/wdc/wdc-utils.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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 "nvme.h" +#include "libnvme.h" +#include "nvme-print.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; +#if defined(__GLIBC__) && !defined(__UCLIBC__) && !defined(__MUSL__) + timeInfo->zone = -currTimeInfo.tm_gmtoff / 60; +#else + timeInfo->zone = -1 * (timezone / SECONDS_IN_MIN); +#endif + + 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 comparing. + * @param pcDst Points to a null terminated string for comparing. + * + * @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; +} + +void wdc_StrFormat(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--; + } +} + +bool wdc_CheckUuidListSupport(struct nvme_dev *dev, struct nvme_id_uuid_list *uuid_list) +{ + int err; + struct nvme_id_ctrl ctrl; + + memset(&ctrl, 0, sizeof(struct nvme_id_ctrl)); + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (err) { + fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed 0x%x\n", err); + return false; + } + + if ((ctrl.ctratt & NVME_CTRL_CTRATT_UUID_LIST) == NVME_CTRL_CTRATT_UUID_LIST) { + err = nvme_identify_uuid(dev_fd(dev), uuid_list); + if (!err) + return true; + else if (err > 0) + nvme_show_status(err); + else + nvme_show_error("identify UUID list: %s", nvme_strerror(errno)); + } + + return false; +} + +bool wdc_UuidEqual(struct nvme_id_uuid_list_entry *entry1, struct nvme_id_uuid_list_entry *entry2) +{ + return !memcmp(entry1, entry2, NVME_UUID_LEN); +} diff --git a/plugins/wdc/wdc-utils.h b/plugins/wdc/wdc-utils.h new file mode 100644 index 0000000..3fc47ca --- /dev/null +++ b/plugins/wdc/wdc-utils.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * 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 <stdbool.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); +void wdc_StrFormat(char *formatter, size_t fmt_sz, char *tofmt, size_t tofmtsz); +bool wdc_CheckUuidListSupport(struct nvme_dev *dev, struct nvme_id_uuid_list *uuid_list); +bool wdc_UuidEqual(struct nvme_id_uuid_list_entry *entry1, struct nvme_id_uuid_list_entry *entry2); diff --git a/plugins/ymtc/ymtc-nvme.c b/plugins/ymtc/ymtc-nvme.c new file mode 100644 index 0000000..5568c80 --- /dev/null +++ b/plugins/ymtc/ymtc-nvme.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-print.h" + +#define CREATE_CMD +#include "ymtc-nvme.h" +#include "ymtc-utils.h" + +static void get_ymtc_smart_info(struct nvme_ymtc_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 int show_ymtc_smart_log(struct nvme_dev *dev, __u32 nsid, + struct nvme_ymtc_smart_log *smart) +{ + struct nvme_id_ctrl ctrl; + char fw_ver[10]; + int err = 0; + + u8 *nm = malloc(NM_SIZE * sizeof(u8)); + u8 *raw = malloc(RAW_SIZE * sizeof(u8)); + + if (!nm) { + if (raw) + free(raw); + return -1; + } + if (!raw) { + free(nm); + return -1; + } + err = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (err) { + free(nm); + free(raw); + 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]); + + /* Table Title */ + printf("Additional Smart Log for NVME device:%s namespace-id:%x\n", + dev->name, nsid); + /* Column Name*/ + printf("key normalized raw\n"); + /* 00 SI_VD_PROGRAM_FAIL */ + get_ymtc_smart_info(smart, SI_VD_PROGRAM_FAIL, nm, raw); + printf("program_fail_count : %3d%% %"PRIu64"\n", *nm, int48_to_long(raw)); + /* 01 SI_VD_ERASE_FAIL */ + get_ymtc_smart_info(smart, SI_VD_ERASE_FAIL, nm, raw); + printf("erase_fail_count : %3d%% %"PRIu64"\n", *nm, int48_to_long(raw)); + /* 02 SI_VD_WEARLEVELING_COUNT */ + get_ymtc_smart_info(smart, SI_VD_WEARLEVELING_COUNT, nm, raw); + printf("wear_leveling : %3d%% min: %u, max: %u, avg: %u\n", *nm, + *(uint16_t *)raw, *(uint16_t *)(raw+2), *(uint16_t *)(raw+4)); + /* 03 SI_VD_E2E_DECTECTION_COUNT */ + get_ymtc_smart_info(smart, SI_VD_E2E_DECTECTION_COUNT, nm, raw); + printf("end_to_end_error_detection_count: %3d%% %"PRIu64"\n", *nm, int48_to_long(raw)); + /* 04 SI_VD_PCIE_CRC_ERR_COUNT */ + get_ymtc_smart_info(smart, SI_VD_PCIE_CRC_ERR_COUNT, nm, raw); + printf("crc_error_count : %3d%% %"PRIu32"\n", *nm, *(uint32_t *)raw); + /* 08 SI_VD_THERMAL_THROTTLE_STATUS */ + get_ymtc_smart_info(smart, SI_VD_THERMAL_THROTTLE_STATUS, nm, raw); + printf("thermal_throttle_status : %3d%% %d%%, cnt: %"PRIu32"\n", *nm, + *raw, *(uint32_t *)(raw+1)); + /* 11 SI_VD_TOTAL_WRITE */ + get_ymtc_smart_info(smart, SI_VD_TOTAL_WRITE, nm, raw); + printf("nand_bytes_written : %3d%% sectors: %"PRIu64"\n", *nm, int48_to_long(raw)); + /* 12 SI_VD_HOST_WRITE */ + get_ymtc_smart_info(smart, SI_VD_HOST_WRITE, nm, raw); + printf("host_bytes_written : %3d%% sectors: %"PRIu64"\n", *nm, int48_to_long(raw)); + /* 14 SI_VD_TOTAL_READ */ + get_ymtc_smart_info(smart, SI_VD_TOTAL_READ, nm, raw); + printf("nand_bytes_read : %3d%% %"PRIu64"\n", *nm, int48_to_long(raw)); + /* 15 SI_VD_TEMPT_SINCE_BORN */ + get_ymtc_smart_info(smart, SI_VD_TEMPT_SINCE_BORN, nm, raw); + printf("tempt_since_born : %3d%% max: %u, min: %u, curr: %u\n", *nm, + *(uint16_t *)raw-273, *(uint16_t *)(raw+2)-273, *(int16_t *)(raw+4)-273); + /* 16 SI_VD_POWER_CONSUMPTION */ + get_ymtc_smart_info(smart, SI_VD_POWER_CONSUMPTION, nm, raw); + printf("power_consumption : %3d%% max: %u, min: %u, curr: %u\n", *nm, + *(uint16_t *)raw, *(uint16_t *)(raw+2), *(uint16_t *)(raw+4)); + /* 17 SI_VD_TEMPT_SINCE_BOOTUP */ + get_ymtc_smart_info(smart, SI_VD_TEMPT_SINCE_BOOTUP, nm, raw); + printf("tempt_since_bootup : %3d%% max: %u, min: %u, curr: %u\n", *nm, + *(uint16_t *)raw-273, *(uint16_t *)(raw+2)-273, *(uint16_t *)(raw+4)-273); + /* 18 SI_VD_POWER_LOSS_PROTECTION */ + get_ymtc_smart_info(smart, SI_VD_POWER_LOSS_PROTECTION, nm, raw); + printf("power_loss_protection : %3d%% %"PRIu64"\n", *nm, int48_to_long(raw)); + /* 19 SI_VD_READ_FAIL */ + get_ymtc_smart_info(smart, SI_VD_READ_FAIL, nm, raw); + printf("read_fail : %3d%% %"PRIu64"\n", *nm, int48_to_long(raw)); + /* 20 SI_VD_THERMAL_THROTTLE_TIME */ + get_ymtc_smart_info(smart, SI_VD_THERMAL_THROTTLE_TIME, nm, raw); + printf("thermal_throttle_time : %3d%% %u, time: %"PRIu32"\n", *nm, + *raw, *(uint32_t *)(raw+1)); + /* 21 SI_VD_FLASH_MEDIA_ERROR */ + get_ymtc_smart_info(smart, SI_VD_FLASH_MEDIA_ERROR, nm, raw); + printf("flash_error_media_count : %3d%% %"PRIu64"\n", *nm, int48_to_long(raw)); + + free(nm); + free(raw); + + return err; +} + +static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct nvme_ymtc_smart_log smart_log; + char *desc = + "Get Ymtc 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 nvme_dev *dev; + struct config { + __u32 namespace_id; + bool raw_binary; + }; + int err; + + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = nvme_get_nsid_log(dev_fd(dev), false, 0xca, cfg.namespace_id, + sizeof(smart_log), &smart_log); + if (!err) { + if (!cfg.raw_binary) + err = show_ymtc_smart_log(dev, cfg.namespace_id, &smart_log); + else + d_raw((unsigned char *)&smart_log, sizeof(smart_log)); + } + if (err > 0) + nvme_show_status(err); + + dev_close(dev); + return err; +} diff --git a/plugins/ymtc/ymtc-nvme.h b/plugins/ymtc/ymtc-nvme.h new file mode 100644 index 0000000..df5d598 --- /dev/null +++ b/plugins/ymtc/ymtc-nvme.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/ymtc/ymtc-nvme + +#if !defined(YMTC_NVME) || defined(CMD_HEADER_MULTI_READ) +#define YMTC_NVME + +#include "cmd.h" +#include "common.h" + +#include <ctype.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +PLUGIN(NAME("ymtc", "Ymtc vendor specific extensions", NVME_VERSION), + COMMAND_LIST( + ENTRY("smart-log-add", "Retrieve Ymtc SMART Log, show it", get_additional_smart_log) + ) +); + +#endif + +#include "define_cmd.h" + diff --git a/plugins/ymtc/ymtc-utils.h b/plugins/ymtc/ymtc-utils.h new file mode 100644 index 0000000..e9a3572 --- /dev/null +++ b/plugins/ymtc/ymtc-utils.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __YMTC_UTILS_H__ +#define __YMTC_UTILS_H__ + +#define SMART_INFO_SIZE 4096 + +#define ID_SIZE 3 +#define NM_SIZE 2 +#define RAW_SIZE 7 + +typedef unsigned char u8; + +/* Additional smart external ID */ +#define SI_VD_PROGRAM_FAIL_ID 0xAB +#define SI_VD_ERASE_FAIL_ID 0xAC +#define SI_VD_WEARLEVELING_COUNT_ID 0xAD +#define SI_VD_E2E_DECTECTION_COUNT_ID 0xB8 +#define SI_VD_PCIE_CRC_ERR_COUNT_ID 0xC7 +#define SI_VD_TIMED_WORKLOAD_MEDIA_WEAR_ID 0xE2 +#define SI_VD_TIMED_WORKLOAD_HOST_READ_ID 0xE3 +#define SI_VD_TIMED_WORKLOAD_TIMER_ID 0xE4 +#define SI_VD_THERMAL_THROTTLE_STATUS_ID 0xEA +#define SI_VD_RETRY_BUFF_OVERFLOW_COUNT_ID 0xF0 +#define SI_VD_PLL_LOCK_LOSS_COUNT_ID 0xF3 +#define SI_VD_TOTAL_WRITE_ID 0xF4 +#define SI_VD_HOST_WRITE_ID 0xF5 +#define SI_VD_SYSTEM_AREA_LIFE_LEFT_ID 0xF6 +#define SI_VD_TOTAL_READ_ID 0xFA +#define SI_VD_TEMPT_SINCE_BORN_ID 0xE7 +#define SI_VD_POWER_CONSUMPTION_ID 0xE8 +#define SI_VD_TEMPT_SINCE_BOOTUP_ID 0xAF +#define SI_VD_POWER_LOSS_PROTECTION_ID 0xEC +#define SI_VD_READ_FAIL_ID 0xF2 +#define SI_VD_THERMAL_THROTTLE_TIME_ID 0xEB +#define SI_VD_FLASH_MEDIA_ERROR_ID 0xED + +/* Additional smart internal ID */ +typedef enum +{ + /* smart attr following intel */ + SI_VD_PROGRAM_FAIL = 0, /* 0xAB */ + SI_VD_ERASE_FAIL = 1, /* 0xAC */ + SI_VD_WEARLEVELING_COUNT = 2,/* 0xAD */ + SI_VD_E2E_DECTECTION_COUNT = 3, /* 0xB8 */ + SI_VD_PCIE_CRC_ERR_COUNT = 4, /* 0xC7, 2 port data in one attribute */ + SI_VD_THERMAL_THROTTLE_STATUS = 8, /* 0xEA */ + SI_VD_TOTAL_WRITE = 11, /* 0xF4, unit is 32MiB */ + SI_VD_HOST_WRITE = 12, /* 0xF5, unit is 32MiB */ + SI_VD_TOTAL_READ = 14, /* 0xFA, unit is 32MiB */ + + /* smart attr self defined */ + SI_VD_TEMPT_SINCE_BORN = 15, /* 0xE7 */ + SI_VD_POWER_CONSUMPTION = 16, /* 0xE8 */ + SI_VD_TEMPT_SINCE_BOOTUP = 17, /* 0xAF */ + SI_VD_POWER_LOSS_PROTECTION = 18, /* 0xEC */ + SI_VD_READ_FAIL = 19, /* 0xF2 */ + SI_VD_THERMAL_THROTTLE_TIME = 20, /* 0xEB */ + SI_VD_FLASH_MEDIA_ERROR = 21, /* 0xED */ + NR_SMART_ITEMS, +} si_vendor_smart_item_e; + +// Intel Format +struct nvme_ymtc_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_ymtc_smart_log +{ + struct nvme_ymtc_smart_log_item itemArr[NR_SMART_ITEMS]; + + u8 resv[SMART_INFO_SIZE - sizeof(struct nvme_ymtc_smart_log_item) * NR_SMART_ITEMS]; +}; + +#endif // __YMTC_UTILS_H__ + diff --git a/plugins/zns/zns.c b/plugins/zns/zns.c new file mode 100644 index 0000000..a7a3766 --- /dev/null +++ b/plugins/zns/zns.c @@ -0,0 +1,1274 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> +#include <linux/fs.h> +#include <sys/stat.h> + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "nvme-print.h" +#include "util/cleanup.h" + +#define CREATE_CMD +#include "zns.h" + +static const char *namespace_id = "Namespace identifier to use"; +static const char dash[100] = { [0 ... 99] = '-' }; + +static int detect_zns(nvme_ns_t ns, int *out_supported) +{ + int err = 0; + char *zoned; + + *out_supported = 0; + + zoned = nvme_get_attr(nvme_ns_get_sysfs_dir(ns), "queue/zoned"); + if (!zoned) { + *out_supported = 0; + return err; + } + + *out_supported = strcmp("host-managed", zoned) == 0; + free(zoned); + + return err; +} + +static int print_zns_list_ns(nvme_ns_t ns) +{ + int supported; + int err = 0; + + err = detect_zns(ns, &supported); + if (err) { + perror("Failed to enumerate namespace"); + return err; + } + + if (supported) + nvme_show_list_item(ns); + + return err; +} + +static int print_zns_list(nvme_root_t nvme_root) +{ + int err = 0; + nvme_host_t h; + nvme_subsystem_t s; + nvme_ctrl_t c; + nvme_ns_t n; + + nvme_for_each_host(nvme_root, h) { + nvme_for_each_subsystem(h, s) { + nvme_subsystem_for_each_ns(s, n) { + err = print_zns_list_ns(n); + if (err) + return err; + } + + nvme_subsystem_for_each_ctrl(s, c) { + nvme_ctrl_for_each_ns(c, n) { + err = print_zns_list_ns(n); + if (err) + return err; + } + } + } + } + + return err; +} + +static int list(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err = 0; + nvme_root_t nvme_root; + + printf("%-21s %-20s %-40s %-9s %-26s %-16s %-8s\n", "Node", "SN", + "Model", "Namespace", "Usage", "Format", "FW Rev"); + printf("%-.21s %-.20s %-.40s %-.9s %-.26s %-.16s %-.8s\n", dash, dash, + dash, dash, dash, dash, dash); + + nvme_root = nvme_scan(NULL); + if (nvme_root) { + err = print_zns_list(nvme_root); + nvme_free_tree(nvme_root); + } else { + fprintf(stderr, "Failed to scan nvme subsystems\n"); + err = -errno; + } + + return err; +} + +static int id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Send a ZNS specific Identify Controller command to\n" + "the given device and report information about the specified\n" + "controller in various formats."; + + enum nvme_print_flags flags; + struct nvme_zns_id_ctrl ctrl; + struct nvme_dev *dev; + int err = -1; + + 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() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return errno; + + err = validate_output_format(cfg.output_format, &flags); + if (err < 0) + goto close_dev; + + err = nvme_zns_identify_ctrl(dev_fd(dev), &ctrl); + if (!err) + nvme_show_zns_id_ctrl(&ctrl, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("zns identify controller"); +close_dev: + dev_close(dev); + return err; +} + +static int id_ns(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Send a ZNS specific Identify Namespace command to\n" + "the given device and report information about the specified\n" + "namespace in varios formats."; + const char *vendor_specific = "dump binary vendor fields"; + const char *human_readable = "show identify in readable format"; + + enum nvme_print_flags flags; + struct nvme_zns_id_ns ns; + struct nvme_id_ns id_ns; + struct nvme_dev *dev; + int err = -1; + + struct config { + char *output_format; + __u32 namespace_id; + bool human_readable; + bool vendor_specific; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_FLAG("vendor-specific", 'v', &cfg.vendor_specific, vendor_specific), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("human-readable", 'H', &cfg.human_readable, human_readable), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return errno; + + err = validate_output_format(cfg.output_format, &flags); + if (err < 0) + goto close_dev; + if (cfg.vendor_specific) + flags |= VS; + if (cfg.human_readable) + flags |= VERBOSE; + + if (!cfg.namespace_id) { + err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); + if (err < 0) { + perror("get-namespace-id"); + goto close_dev; + } + } + + err = nvme_identify_ns(dev_fd(dev), cfg.namespace_id, &id_ns); + if (err) { + nvme_show_status(err); + goto close_dev; + } + + err = nvme_zns_identify_ns(dev_fd(dev), cfg.namespace_id, &ns); + if (!err) + nvme_show_zns_id_ns(&ns, &id_ns, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("zns identify namespace"); +close_dev: + dev_close(dev); + return err; +} + +static int zns_mgmt_send(int argc, char **argv, struct command *cmd, struct plugin *plugin, + const char *desc, enum nvme_zns_send_action zsa) +{ + const char *zslba = "starting LBA of the zone for this command"; + const char *select_all = "send command to all zones"; + const char *timeout = "timeout value, in milliseconds"; + struct nvme_dev *dev; + int err, zcapc = 0; + char *command; + __u32 result; + + struct config { + __u64 zslba; + __u32 namespace_id; + bool select_all; + __u32 timeout; + }; + + struct config cfg = {}; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_SUFFIX("start-lba", 's', &cfg.zslba, zslba), + OPT_FLAG("select-all", 'a', &cfg.select_all, select_all), + OPT_UINT("timeout", 't', &cfg.timeout, timeout), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + goto ret; + + err = asprintf(&command, "%s-%s", plugin->name, cmd->name); + if (err < 0) + goto close_dev; + + if (!cfg.namespace_id) { + err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); + if (err < 0) { + perror("get-namespace-id"); + goto free; + } + } + + struct nvme_zns_mgmt_send_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .nsid = cfg.namespace_id, + .slba = cfg.zslba, + .zsa = zsa, + .select_all = cfg.select_all, + .zsaso = 0, + .data_len = 0, + .data = NULL, + .timeout = cfg.timeout, + .result = &result, + }; + err = nvme_zns_mgmt_send(&args); + if (!err) { + if (zsa == NVME_ZNS_ZSA_RESET) + zcapc = result & 0x1; + + printf("%s: Success, action:%d zone:%"PRIx64" all:%d zcapc:%u nsid:%d\n", + command, zsa, (uint64_t)cfg.zslba, (int)cfg.select_all, + zcapc, cfg.namespace_id); + } else if (err > 0) { + nvme_show_status(err); + } else { + perror(desc); + } +free: + free(command); +close_dev: + dev_close(dev); +ret: + return err; +} + +static int get_zdes_bytes(int fd, __u32 nsid) +{ + struct nvme_zns_id_ns ns; + struct nvme_id_ns id_ns; + __u8 lbaf; + int err; + + err = nvme_identify_ns(fd, nsid, &id_ns); + if (err > 0) { + nvme_show_status(err); + return -1; + } else if (err < 0) { + perror("identify namespace"); + return -1; + } + + err = nvme_zns_identify_ns(fd, nsid, &ns); + if (err > 0) { + nvme_show_status(err); + return -1; + } else if (err < 0) { + perror("zns identify namespace"); + return -1; + } + + nvme_id_ns_flbas_to_lbaf_inuse(id_ns.flbas, &lbaf); + return ns.lbafe[lbaf].zdes << 6; +} + +static int zone_mgmt_send(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Zone Management Send"; + const char *zslba = + "starting LBA of the zone for this command(for flush action, last lba to flush)"; + const char *zsaso = "Zone Send Action Specific Option"; + const char *select_all = "send command to all zones"; + const char *zsa = "zone send action"; + const char *data_len = "buffer length if data required"; + const char *data = "optional file for data (default stdin)"; + const char *timeout = "timeout value, in milliseconds"; + + int ffd = STDIN_FILENO, err = -1; + struct nvme_dev *dev; + void *buf = NULL; + + struct config { + __u64 zslba; + __u32 namespace_id; + bool zsaso; + bool select_all; + __u8 zsa; + int data_len; + char *file; + __u32 timeout; + }; + + struct config cfg = {}; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_SUFFIX("start-lba", 's', &cfg.zslba, zslba), + OPT_FLAG("zsaso", 'o', &cfg.zsaso, zsaso), + OPT_FLAG("select-all", 'a', &cfg.select_all, select_all), + OPT_BYTE("zsa", 'z', &cfg.zsa, zsa), + OPT_UINT("data-len", 'l', &cfg.data_len, data_len), + OPT_FILE("data", 'd', &cfg.file, data), + OPT_UINT("timeout", 't', &cfg.timeout, timeout), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return errno; + + if (!cfg.namespace_id) { + err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); + if (err < 0) { + perror("get-namespace-id"); + goto close_dev; + } + } + + if (!cfg.zsa) { + fprintf(stderr, "zone send action must be specified\n"); + err = -EINVAL; + goto close_dev; + } + + if (cfg.zsa == NVME_ZNS_ZSA_SET_DESC_EXT) { + if (!cfg.data_len) { + int data_len = get_zdes_bytes(dev_fd(dev), + cfg.namespace_id); + + if (data_len == 0) { + fprintf(stderr, "Zone Descriptor Extensions are not supported\n"); + goto close_dev; + } else if (data_len < 0) { + err = data_len; + goto close_dev; + } + cfg.data_len = data_len; + } + if (posix_memalign(&buf, getpagesize(), cfg.data_len)) { + fprintf(stderr, "can not allocate feature payload\n"); + goto close_dev; + } + memset(buf, 0, cfg.data_len); + + if (cfg.file) { + ffd = open(cfg.file, O_RDONLY); + if (ffd < 0) { + perror(cfg.file); + goto free; + } + } + + err = read(ffd, (void *)buf, cfg.data_len); + if (err < 0) { + perror("read"); + goto close_ffd; + } + } else { + if (cfg.file || cfg.data_len) { + fprintf(stderr, "data, data_len only valid with set extended descriptor\n"); + err = -EINVAL; + goto close_dev; + } + } + + struct nvme_zns_mgmt_send_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .nsid = cfg.namespace_id, + .slba = cfg.zslba, + .zsa = cfg.zsa, + .select_all = cfg.select_all, + .zsaso = cfg.zsaso, + .data_len = cfg.data_len, + .data = buf, + .timeout = cfg.timeout, + .result = NULL, + }; + err = nvme_zns_mgmt_send(&args); + if (!err) + printf("zone-mgmt-send: Success, action:%d zone:%"PRIx64" all:%d nsid:%d\n", + cfg.zsa, (uint64_t)cfg.zslba, (int)cfg.select_all, cfg.namespace_id); + else if (err > 0) + nvme_show_status(err); + else + perror("zns zone-mgmt-send"); + +close_ffd: + if (cfg.file) + close(ffd); +free: + free(buf); +close_dev: + dev_close(dev); + return err; +} + +static int close_zone(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Close zones\n"; + + return zns_mgmt_send(argc, argv, cmd, plugin, desc, NVME_ZNS_ZSA_CLOSE); +} + +static int finish_zone(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Finish zones\n"; + + return zns_mgmt_send(argc, argv, cmd, plugin, desc, NVME_ZNS_ZSA_FINISH); +} + +static int open_zone(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Open zones\n"; + const char *zslba = "starting LBA of the zone for this command"; + const char *zrwaa = "Allocate Zone Random Write Area to zone"; + const char *select_all = "send command to all zones"; + const char *timeout = "timeout value, in milliseconds"; + struct nvme_dev *dev; + int err; + + struct config { + __u64 zslba; + __u32 namespace_id; + bool zrwaa; + bool select_all; + __u32 timeout; + }; + + struct config cfg = { + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_SUFFIX("start-lba", 's', &cfg.zslba, zslba), + OPT_FLAG("zrwaa", 'r', &cfg.zrwaa, zrwaa), + OPT_FLAG("select-all", 'a', &cfg.select_all, select_all), + OPT_UINT("timeout", 't', &cfg.timeout, timeout), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return errno; + + if (!cfg.namespace_id) { + err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); + if (err < 0) { + perror("get-namespace-id"); + goto close_dev; + } + } + + struct nvme_zns_mgmt_send_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .nsid = cfg.namespace_id, + .slba = cfg.zslba, + .zsa = NVME_ZNS_ZSA_OPEN, + .select_all = cfg.select_all, + .zsaso = cfg.zrwaa, + .data_len = 0, + .data = NULL, + .timeout = cfg.timeout, + .result = NULL, + }; + err = nvme_zns_mgmt_send(&args); + if (!err) + printf("zns-open-zone: Success zone slba:%"PRIx64" nsid:%d\n", + (uint64_t)cfg.zslba, cfg.namespace_id); + else + nvme_show_status(err); +close_dev: + dev_close(dev); + return err; +} + +static int reset_zone(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Reset zones\n"; + + return zns_mgmt_send(argc, argv, cmd, plugin, desc, NVME_ZNS_ZSA_RESET); +} + +static int offline_zone(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Offline zones\n"; + + return zns_mgmt_send(argc, argv, cmd, plugin, desc, NVME_ZNS_ZSA_OFFLINE); +} + +static int set_zone_desc(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Set Zone Descriptor Extension\n"; + const char *zslba = "starting LBA of the zone for this command"; + const char *zrwaa = "Allocate Zone Random Write Area to zone"; + const char *data = "optional file for zone extension data (default stdin)"; + const char *timeout = "timeout value, in milliseconds"; + + int ffd = STDIN_FILENO, err; + struct nvme_dev *dev; + void *buf = NULL; + int data_len; + + struct config { + __u64 zslba; + bool zrwaa; + __u32 namespace_id; + char *file; + __u32 timeout; + }; + + struct config cfg = {}; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_SUFFIX("start-lba", 's', &cfg.zslba, zslba), + OPT_FLAG("zrwaa", 'r', &cfg.zrwaa, zrwaa), + OPT_FILE("data", 'd', &cfg.file, data), + OPT_UINT("timeout", 't', &cfg.timeout, timeout), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return errno; + + if (!cfg.namespace_id) { + err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); + if (err < 0) { + perror("get-namespace-id"); + goto close_dev; + } + } + + data_len = get_zdes_bytes(dev_fd(dev), cfg.namespace_id); + + if (!data_len || data_len < 0) { + fprintf(stderr, + "zone format does not provide descriptor extension\n"); + errno = EINVAL; + err = -1; + goto close_dev; + } + + buf = calloc(1, data_len); + if (!buf) { + perror("could not alloc memory for zone desc"); + err = -ENOMEM; + goto close_dev; + } + + if (cfg.file) { + ffd = open(cfg.file, O_RDONLY); + if (ffd < 0) { + perror(cfg.file); + err = -1; + goto free; + } + } + + err = read(ffd, (void *)buf, data_len); + if (err < 0) { + perror("read"); + goto close_ffd; + } + + struct nvme_zns_mgmt_send_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .nsid = cfg.namespace_id, + .slba = cfg.zslba, + .zsa = NVME_ZNS_ZSA_SET_DESC_EXT, + .select_all = 0, + .zsaso = cfg.zrwaa, + .data_len = data_len, + .data = buf, + .timeout = cfg.timeout, + .result = NULL, + }; + err = nvme_zns_mgmt_send(&args); + if (!err) + printf("set-zone-desc: Success, zone:%"PRIx64" nsid:%d\n", + (uint64_t)cfg.zslba, cfg.namespace_id); + else if (err > 0) + nvme_show_status(err); + else + perror("zns set-zone-desc"); +close_ffd: + if (cfg.file) + close(ffd); +free: + free(buf); +close_dev: + dev_close(dev); + return err; +} + + +static int zrwa_flush_zone(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Flush Explicit ZRWA Range"; + const char *slba = "LBA to flush up to"; + const char *timeout = "timeout value, in milliseconds"; + struct nvme_dev *dev; + int err; + + struct config { + __u64 lba; + __u32 namespace_id; + __u32 timeout; + }; + + struct config cfg = {}; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_SUFFIX("lba", 'l', &cfg.lba, slba), + OPT_UINT("timeout", 't', &cfg.timeout, timeout), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return errno; + + if (!cfg.namespace_id) { + err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); + if (err < 0) { + perror("get-namespace-id"); + goto close_dev; + } + } + + struct nvme_zns_mgmt_send_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .nsid = cfg.namespace_id, + .slba = cfg.lba, + .zsa = NVME_ZNS_ZSA_ZRWA_FLUSH, + .select_all = 0, + .zsaso = 0, + .data_len = 0, + .data = NULL, + .timeout = cfg.timeout, + .result = NULL, + }; + err = nvme_zns_mgmt_send(&args); + if (!err) + printf("zrwa-flush-zone: Success, lba:%"PRIx64" nsid:%d\n", + (uint64_t)cfg.lba, cfg.namespace_id); + else + nvme_show_status(err); +close_dev: + dev_close(dev); + return err; +} + +static int zone_mgmt_recv(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Zone Management Receive"; + const char *zslba = "starting LBA of the zone"; + const char *zra = "Zone Receive Action"; + const char *zrasf = "Zone Receive Action Specific Field(Reporting Options)"; + const char *partial = "Zone Receive Action Specific Features(Partial Report)"; + const char *data_len = "length of data in bytes"; + + enum nvme_print_flags flags; + struct nvme_dev *dev; + void *data = NULL; + int err = -1; + + struct config { + char *output_format; + __u64 zslba; + __u32 namespace_id; + __u8 zra; + __u8 zrasf; + bool partial; + __u32 data_len; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_SUFFIX("start-lba", 's', &cfg.zslba, zslba), + OPT_BYTE("zra", 'z', &cfg.zra, zra), + OPT_BYTE("zrasf", 'S', &cfg.zrasf, zrasf), + OPT_FLAG("partial", 'p', &cfg.partial, partial), + OPT_UINT("data-len", 'l', &cfg.data_len, data_len), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return errno; + + err = validate_output_format(cfg.output_format, &flags); + if (err < 0) + goto close_dev; + + if (!cfg.namespace_id) { + err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); + if (err < 0) { + perror("get-namespace-id"); + goto close_dev; + } + } + + if (cfg.zra == NVME_ZNS_ZRA_REPORT_ZONES && !cfg.data_len) { + fprintf(stderr, "error: data len is needed for NVME_ZRA_ZONE_REPORT\n"); + err = -EINVAL; + goto close_dev; + } + if (cfg.data_len) { + data = calloc(1, cfg.data_len); + if (!data) { + perror("could not alloc memory for zone mgmt receive data"); + err = -ENOMEM; + goto close_dev; + } + } + + struct nvme_zns_mgmt_recv_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .nsid = cfg.namespace_id, + .slba = cfg.zslba, + .zra = cfg.zra, + .zrasf = cfg.zrasf, + .zras_feat = cfg.partial, + .data_len = cfg.data_len, + .data = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + err = nvme_zns_mgmt_recv(&args); + if (!err) + printf("zone-mgmt-recv: Success, action:%d zone:%"PRIx64" nsid:%d\n", + cfg.zra, (uint64_t)cfg.zslba, cfg.namespace_id); + else if (err > 0) + nvme_show_status(err); + else + perror("zns zone-mgmt-recv"); + + free(data); +close_dev: + dev_close(dev); + return err; +} + +static int report_zones(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Retrieve the Report Zones data structure"; + const char *zslba = "starting LBA of the zone"; + const char *num_descs = "number of descriptors to retrieve (default: all of them)"; + const char *state = "state of zones to list"; + const char *ext = "set to use the extended report zones"; + const char *part = "set to use the partial report"; + const char *verbose = "show report zones verbosity"; + + enum nvme_print_flags flags; + int zdes = 0, err = -1; + struct nvme_dev *dev; + __u32 report_size; + struct nvme_zone_report *report, *buff; + _cleanup_huge_ struct nvme_mem_huge mh = { 0, }; + + unsigned int nr_zones_chunks = 1024, /* 1024 entries * 64 bytes per entry = 64k byte transfer */ + nr_zones_retrieved = 0, + nr_zones, + log_len; + __u64 offset; + int total_nr_zones = 0; + struct nvme_zns_id_ns id_zns; + struct nvme_id_ns id_ns; + uint8_t lbaf; + __le64 zsze; + struct json_object *zone_list = NULL; + + struct config { + char *output_format; + __u64 zslba; + __u32 namespace_id; + int num_descs; + int state; + bool verbose; + bool extended; + bool partial; + }; + + struct config cfg = { + .output_format = "normal", + .num_descs = -1, + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_SUFFIX("start-lba", 's', &cfg.zslba, zslba), + OPT_UINT("descs", 'd', &cfg.num_descs, num_descs), + OPT_UINT("state", 'S', &cfg.state, state), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("verbose", 'v', &cfg.verbose, verbose), + OPT_FLAG("extended", 'e', &cfg.extended, ext), + OPT_FLAG("partial", 'p', &cfg.partial, part), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return errno; + + err = validate_output_format(cfg.output_format, &flags); + if (err < 0) + goto close_dev; + if (cfg.verbose) + flags |= VERBOSE; + + if (!cfg.namespace_id) { + err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); + if (err < 0) { + perror("get-namespace-id"); + goto close_dev; + } + } + + if (cfg.extended) { + zdes = get_zdes_bytes(dev_fd(dev), cfg.namespace_id); + if (zdes < 0) { + err = zdes; + goto close_dev; + } + } + + err = nvme_identify_ns(dev_fd(dev), cfg.namespace_id, &id_ns); + if (err) { + nvme_show_status(err); + goto close_dev; + } + + err = nvme_zns_identify_ns(dev_fd(dev), cfg.namespace_id, &id_zns); + if (!err) { + /* get zsze field from zns id ns data - needed for offset calculation */ + nvme_id_ns_flbas_to_lbaf_inuse(id_ns.flbas, &lbaf); + zsze = le64_to_cpu(id_zns.lbafe[lbaf].zsze); + } else { + nvme_show_status(err); + goto close_dev; + } + + log_len = sizeof(struct nvme_zone_report); + buff = calloc(1, log_len); + if (!buff) { + err = -ENOMEM; + goto close_dev; + } + + err = nvme_zns_report_zones(dev_fd(dev), cfg.namespace_id, 0, + cfg.state, false, false, + log_len, buff, + NVME_DEFAULT_IOCTL_TIMEOUT, NULL); + if (err > 0) { + nvme_show_status(err); + goto free_buff; + } else if (err < 0) { + perror("zns report-zones"); + goto free_buff; + } + + total_nr_zones = le64_to_cpu(buff->nr_zones); + + if (cfg.num_descs == -1) + cfg.num_descs = total_nr_zones; + + nr_zones = cfg.num_descs; + if (nr_zones < nr_zones_chunks) + nr_zones_chunks = nr_zones; + + log_len = sizeof(struct nvme_zone_report) + ((sizeof(struct nvme_zns_desc) * nr_zones_chunks) + (nr_zones_chunks * zdes)); + report_size = log_len; + + report = nvme_alloc_huge(report_size, &mh); + if (!report) { + perror("alloc"); + err = -ENOMEM; + goto close_dev; + } + + offset = cfg.zslba; + + nvme_zns_start_zone_list(total_nr_zones, &zone_list, flags); + + while (nr_zones_retrieved < nr_zones) { + if (nr_zones_retrieved >= nr_zones) + break; + + if (nr_zones_retrieved + nr_zones_chunks > nr_zones) { + nr_zones_chunks = nr_zones - nr_zones_retrieved; + log_len = sizeof(struct nvme_zone_report) + ((sizeof(struct nvme_zns_desc) * nr_zones_chunks) + (nr_zones_chunks * zdes)); + } + + err = nvme_zns_report_zones(dev_fd(dev), cfg.namespace_id, + offset, + cfg.state, cfg.extended, + cfg.partial, log_len, report, + NVME_DEFAULT_IOCTL_TIMEOUT, NULL); + if (err > 0) { + nvme_show_status(err); + break; + } + + if (!err) + nvme_show_zns_report_zones(report, nr_zones_chunks, + zdes, log_len, zone_list, flags); + + nr_zones_retrieved += nr_zones_chunks; + offset = le64_to_cpu(report->entries[nr_zones_chunks-1].zslba) + zsze; + } + + nvme_zns_finish_zone_list(total_nr_zones, zone_list, flags); + +free_buff: + free(buff); +close_dev: + dev_close(dev); + return err; +} + +static int zone_append(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "The zone append command is used to write to a zone\n" + "using the slba of the zone, and the write will be appended from the\n" + "write pointer of the zone"; + const char *zslba = "starting LBA of the zone"; + const char *data = "file containing data to write"; + const char *metadata = "file with metadata to be written"; + const char *limited_retry = "limit media access attempts"; + const char *fua = "force unit access"; + const char *prinfo = "protection information action and checks field"; + const char *piremap = "protection information remap (for type 1 PI)"; + const char *ref_tag = "reference tag for end-to-end PI"; + const char *lbat = "logical block application tag for end-to-end PI"; + const char *lbatm = "logical block application tag mask for end-to-end PI"; + const char *metadata_size = "size of metadata in bytes"; + const char *data_size = "size of data in bytes"; + const char *latency = "output latency statistics"; + + int err = -1, dfd = STDIN_FILENO, mfd = STDIN_FILENO; + unsigned int lba_size, meta_size; + void *buf = NULL, *mbuf = NULL; + __u16 nblocks, control = 0; + struct nvme_dev *dev; + __u64 result; + __u8 lba_index; + struct timeval start_time, end_time; + + struct nvme_id_ns ns; + + struct config { + char *data; + char *metadata; + __u64 zslba; + __u64 data_size; + __u64 metadata_size; + bool limited_retry; + bool fua; + __u32 namespace_id; + __u64 ref_tag; + __u16 lbat; + __u16 lbatm; + __u8 prinfo; + bool piremap; + bool latency; + }; + + struct config cfg = {}; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_SUFFIX("zslba", 's', &cfg.zslba, zslba), + OPT_SUFFIX("data-size", 'z', &cfg.data_size, data_size), + OPT_SUFFIX("metadata-size", 'y', &cfg.metadata_size, metadata_size), + OPT_FILE("data", 'd', &cfg.data, data), + OPT_FILE("metadata", 'M', &cfg.metadata, metadata), + OPT_FLAG("limited-retry", 'l', &cfg.limited_retry, limited_retry), + OPT_FLAG("force-unit-access", 'f', &cfg.fua, fua), + OPT_SUFFIX("ref-tag", 'r', &cfg.ref_tag, ref_tag), + OPT_SHRT("app-tag-mask", 'm', &cfg.lbatm, lbatm), + OPT_SHRT("app-tag", 'a', &cfg.lbat, lbat), + OPT_BYTE("prinfo", 'p', &cfg.prinfo, prinfo), + OPT_FLAG("piremap", 'P', &cfg.piremap, piremap), + OPT_FLAG("latency", 't', &cfg.latency, latency), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return errno; + + if (!cfg.data_size) { + fprintf(stderr, "Append size not provided\n"); + errno = EINVAL; + goto close_dev; + } + + if (!cfg.namespace_id) { + err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); + if (err < 0) { + perror("get-namespace-id"); + goto close_dev; + } + } + + err = nvme_identify_ns(dev_fd(dev), cfg.namespace_id, &ns); + if (err) { + nvme_show_status(err); + goto close_dev; + } + + nvme_id_ns_flbas_to_lbaf_inuse(ns.flbas, &lba_index); + lba_size = 1 << ns.lbaf[lba_index].ds; + if (cfg.data_size & (lba_size - 1)) { + fprintf(stderr, + "Data size:%#"PRIx64" not aligned to lba size:%#x\n", + (uint64_t)cfg.data_size, lba_size); + errno = EINVAL; + goto close_dev; + } + + meta_size = ns.lbaf[lba_index].ms; + if (meta_size && !(meta_size == 8 && (cfg.prinfo & 0x8)) && + (!cfg.metadata_size || cfg.metadata_size % meta_size)) { + fprintf(stderr, + "Metadata size:%#"PRIx64" not aligned to metadata size:%#x\n", + (uint64_t)cfg.metadata_size, meta_size); + errno = EINVAL; + goto close_dev; + } + + if (cfg.prinfo > 0xf) { + fprintf(stderr, "Invalid value for prinfo:%#x\n", cfg.prinfo); + errno = EINVAL; + goto close_dev; + } + + if (cfg.data) { + dfd = open(cfg.data, O_RDONLY); + if (dfd < 0) { + perror(cfg.data); + goto close_dev; + } + } + + if (posix_memalign(&buf, getpagesize(), cfg.data_size)) { + fprintf(stderr, "No memory for data size:%"PRIx64"\n", + (uint64_t)cfg.data_size); + goto close_dfd; + } + + memset(buf, 0, cfg.data_size); + err = read(dfd, buf, cfg.data_size); + if (err < 0) { + perror("read-data"); + goto free_data; + } + + if (cfg.metadata) { + mfd = open(cfg.metadata, O_RDONLY); + if (mfd < 0) { + perror(cfg.metadata); + err = -1; + goto free_data; + } + } + + if (cfg.metadata_size) { + if (posix_memalign(&mbuf, getpagesize(), meta_size)) { + fprintf(stderr, "No memory for metadata size:%d\n", + meta_size); + err = -1; + goto close_mfd; + } + + memset(mbuf, 0, cfg.metadata_size); + err = read(mfd, mbuf, cfg.metadata_size); + if (err < 0) { + perror("read-metadata"); + goto free_meta; + } + } + + nblocks = (cfg.data_size / lba_size) - 1; + control |= (cfg.prinfo << 10); + if (cfg.limited_retry) + control |= NVME_IO_LR; + if (cfg.fua) + control |= NVME_IO_FUA; + if (cfg.piremap) + control |= NVME_IO_ZNS_APPEND_PIREMAP; + + struct nvme_zns_append_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .nsid = cfg.namespace_id, + .zslba = cfg.zslba, + .nlb = nblocks, + .control = control, + .ilbrt_u64 = cfg.ref_tag, + .lbat = cfg.lbat, + .lbatm = cfg.lbatm, + .data_len = cfg.data_size, + .data = buf, + .metadata_len = cfg.metadata_size, + .metadata = mbuf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + gettimeofday(&start_time, NULL); + err = nvme_zns_append(&args); + gettimeofday(&end_time, NULL); + if (cfg.latency) + printf(" latency: zone append: %llu us\n", + elapsed_utime(start_time, end_time)); + + if (!err) + printf("Success appended data to LBA %"PRIx64"\n", (uint64_t)result); + else if (err > 0) + nvme_show_status(err); + else + perror("zns zone-append"); + +free_meta: + free(mbuf); +close_mfd: + if (cfg.metadata) + close(mfd); +free_data: + free(buf); +close_dfd: + if (cfg.data) + close(dfd); +close_dev: + dev_close(dev); + return err; +} + +static int changed_zone_list(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Retrieve Changed Zone log for the given device"; + const char *rae = "retain an asynchronous event"; + + struct nvme_zns_changed_zone_log log; + enum nvme_print_flags flags; + struct nvme_dev *dev; + int err = -1; + + struct config { + char *output_format; + __u32 namespace_id; + bool rae; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_FMT("output-format", 'o', &cfg.output_format, output_format), + OPT_FLAG("rae", 'r', &cfg.rae, rae), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return errno; + + err = validate_output_format(cfg.output_format, &flags); + if (err < 0) + goto close_dev; + + if (!cfg.namespace_id) { + err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); + if (err < 0) { + perror("get-namespace-id"); + goto close_dev; + } + } + + err = nvme_get_log_zns_changed_zones(dev_fd(dev), cfg.namespace_id, + cfg.rae, &log); + if (!err) + nvme_show_zns_changed(&log, flags); + else if (err > 0) + nvme_show_status(err); + else + perror("zns changed-zone-list"); + +close_dev: + dev_close(dev); + return err; +} diff --git a/plugins/zns/zns.h b/plugins/zns/zns.h new file mode 100644 index 0000000..43c4ecd --- /dev/null +++ b/plugins/zns/zns.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/zns/zns + +#if !defined(ZNS_NVME) || defined(CMD_HEADER_MULTI_READ) +#define ZNS_NVME + +#include "cmd.h" + +PLUGIN(NAME("zns", "Zoned Namespace Command Set", NVME_VERSION), + COMMAND_LIST( + ENTRY("list", "List all NVMe devices with Zoned Namespace Command Set support", list) + ENTRY("id-ctrl", "Send NVMe Identify Zoned Namespace Controller, display structure", id_ctrl) + ENTRY("id-ns", "Send NVMe Identify Zoned Namespace Namespace, display structure", id_ns) + ENTRY("report-zones", "Report zones associated to a Zoned Namespace", report_zones) + ENTRY("reset-zone", "Reset one or more zones", reset_zone) + ENTRY("close-zone", "Close one or more zones", close_zone) + ENTRY("finish-zone", "Finish one or more zones", finish_zone) + ENTRY("open-zone", "Open one or more zones", open_zone) + ENTRY("offline-zone", "Offline one or more zones", offline_zone) + ENTRY("set-zone-desc", "Attach zone descriptor extension data to a zone", set_zone_desc) + ENTRY("zrwa-flush-zone", "Flush LBAs associated with a ZRWA to a zone.", zrwa_flush_zone) + ENTRY("changed-zone-list", "Retrieve the changed zone list log", changed_zone_list) + ENTRY("zone-mgmt-recv", "Send the zone management receive command", zone_mgmt_recv) + ENTRY("zone-mgmt-send", "Send the zone management send command", zone_mgmt_send) + ENTRY("zone-append", "Append data and metadata (if applicable) to a zone", zone_append) + ) +); + +#endif + +#include "define_cmd.h" |