From 1cc8413aaf5f8fa6595aece1933462c096e88639 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 29 Apr 2024 06:41:05 +0200 Subject: Adding upstream version 2.4+really2.3. Signed-off-by: Daniel Baumann --- plugins/amzn/amzn-nvme.c | 54 + plugins/amzn/amzn-nvme.h | 18 + plugins/dell/dell-nvme.c | 54 + plugins/dell/dell-nvme.h | 18 + plugins/dera/dera-nvme.c | 212 + plugins/dera/dera-nvme.h | 18 + plugins/fdp/fdp.c | 537 + plugins/fdp/fdp.h | 24 + plugins/huawei/huawei-nvme.c | 389 + plugins/huawei/huawei-nvme.h | 19 + plugins/innogrit/innogrit-nvme.c | 430 + plugins/innogrit/innogrit-nvme.h | 20 + plugins/innogrit/typedef.h | 78 + plugins/inspur/inspur-nvme.c | 235 + plugins/inspur/inspur-nvme.h | 18 + plugins/inspur/inspur-utils.h | 175 + plugins/intel/intel-nvme.c | 1740 +++ plugins/intel/intel-nvme.h | 25 + plugins/memblaze/memblaze-nvme.c | 1227 ++ plugins/memblaze/memblaze-nvme.h | 26 + plugins/memblaze/memblaze-utils.h | 221 + plugins/meson.build | 29 + plugins/micron/micron-nvme.c | 3394 ++++++ plugins/micron/micron-nvme.h | 36 + plugins/netapp/netapp-nvme.c | 654 ++ plugins/netapp/netapp-nvme.h | 19 + plugins/nvidia/nvidia-nvme.c | 53 + plugins/nvidia/nvidia-nvme.h | 18 + plugins/ocp/meson.build | 6 + plugins/ocp/ocp-clear-fw-update-history.c | 73 + plugins/ocp/ocp-clear-fw-update-history.h | 9 + plugins/ocp/ocp-nvme.c | 774 ++ plugins/ocp/ocp-nvme.h | 28 + plugins/ocp/ocp-utils.c | 30 + plugins/ocp/ocp-utils.h | 18 + plugins/scaleflux/sfx-nvme.c | 1213 ++ plugins/scaleflux/sfx-nvme.h | 24 + plugins/seagate/seagate-diag.h | 388 + plugins/seagate/seagate-nvme.c | 2042 ++++ plugins/seagate/seagate-nvme.h | 51 + plugins/shannon/shannon-nvme.c | 404 + plugins/shannon/shannon-nvme.h | 21 + plugins/solidigm/meson.build | 7 + plugins/solidigm/solidigm-garbage-collection.c | 115 + plugins/solidigm/solidigm-garbage-collection.h | 8 + plugins/solidigm/solidigm-latency-tracking.c | 475 + plugins/solidigm/solidigm-latency-tracking.h | 9 + plugins/solidigm/solidigm-nvme.c | 43 + plugins/solidigm/solidigm-nvme.h | 32 + plugins/solidigm/solidigm-smart.c | 253 + plugins/solidigm/solidigm-smart.h | 8 + plugins/solidigm/solidigm-telemetry.c | 183 + plugins/solidigm/solidigm-telemetry.h | 8 + plugins/solidigm/solidigm-telemetry/cod.c | 194 + plugins/solidigm/solidigm-telemetry/cod.h | 9 + plugins/solidigm/solidigm-telemetry/config.c | 44 + plugins/solidigm/solidigm-telemetry/config.h | 10 + plugins/solidigm/solidigm-telemetry/data-area.c | 424 + plugins/solidigm/solidigm-telemetry/data-area.h | 11 + plugins/solidigm/solidigm-telemetry/header.c | 199 + plugins/solidigm/solidigm-telemetry/header.h | 9 + plugins/solidigm/solidigm-telemetry/meson.build | 6 + .../solidigm/solidigm-telemetry/telemetry-log.h | 31 + plugins/toshiba/toshiba-nvme.c | 583 + plugins/toshiba/toshiba-nvme.h | 21 + plugins/transcend/transcend-nvme.c | 90 + plugins/transcend/transcend-nvme.h | 21 + plugins/virtium/virtium-nvme.c | 1049 ++ plugins/virtium/virtium-nvme.h | 22 + plugins/wdc/wdc-nvme.c | 11100 +++++++++++++++++++ plugins/wdc/wdc-nvme.h | 53 + plugins/wdc/wdc-utils.c | 166 + plugins/wdc/wdc-utils.h | 79 + plugins/ymtc/ymtc-nvme.c | 161 + plugins/ymtc/ymtc-nvme.h | 25 + plugins/ymtc/ymtc-utils.h | 81 + plugins/zns/zns.c | 1290 +++ plugins/zns/zns.h | 32 + 78 files changed, 31673 insertions(+) create mode 100644 plugins/amzn/amzn-nvme.c create mode 100644 plugins/amzn/amzn-nvme.h create mode 100644 plugins/dell/dell-nvme.c create mode 100644 plugins/dell/dell-nvme.h create mode 100644 plugins/dera/dera-nvme.c create mode 100644 plugins/dera/dera-nvme.h create mode 100644 plugins/fdp/fdp.c create mode 100644 plugins/fdp/fdp.h create mode 100644 plugins/huawei/huawei-nvme.c create mode 100644 plugins/huawei/huawei-nvme.h create mode 100644 plugins/innogrit/innogrit-nvme.c create mode 100644 plugins/innogrit/innogrit-nvme.h create mode 100644 plugins/innogrit/typedef.h create mode 100644 plugins/inspur/inspur-nvme.c create mode 100644 plugins/inspur/inspur-nvme.h create mode 100644 plugins/inspur/inspur-utils.h create mode 100644 plugins/intel/intel-nvme.c create mode 100644 plugins/intel/intel-nvme.h create mode 100644 plugins/memblaze/memblaze-nvme.c create mode 100644 plugins/memblaze/memblaze-nvme.h create mode 100644 plugins/memblaze/memblaze-utils.h create mode 100644 plugins/meson.build create mode 100644 plugins/micron/micron-nvme.c create mode 100644 plugins/micron/micron-nvme.h create mode 100644 plugins/netapp/netapp-nvme.c create mode 100644 plugins/netapp/netapp-nvme.h create mode 100644 plugins/nvidia/nvidia-nvme.c create mode 100644 plugins/nvidia/nvidia-nvme.h create mode 100644 plugins/ocp/meson.build create mode 100644 plugins/ocp/ocp-clear-fw-update-history.c create mode 100644 plugins/ocp/ocp-clear-fw-update-history.h create mode 100644 plugins/ocp/ocp-nvme.c create mode 100644 plugins/ocp/ocp-nvme.h create mode 100644 plugins/ocp/ocp-utils.c create mode 100644 plugins/ocp/ocp-utils.h create mode 100644 plugins/scaleflux/sfx-nvme.c create mode 100644 plugins/scaleflux/sfx-nvme.h create mode 100644 plugins/seagate/seagate-diag.h create mode 100644 plugins/seagate/seagate-nvme.c create mode 100644 plugins/seagate/seagate-nvme.h create mode 100644 plugins/shannon/shannon-nvme.c create mode 100644 plugins/shannon/shannon-nvme.h create mode 100644 plugins/solidigm/meson.build create mode 100644 plugins/solidigm/solidigm-garbage-collection.c create mode 100644 plugins/solidigm/solidigm-garbage-collection.h create mode 100644 plugins/solidigm/solidigm-latency-tracking.c create mode 100644 plugins/solidigm/solidigm-latency-tracking.h create mode 100644 plugins/solidigm/solidigm-nvme.c create mode 100644 plugins/solidigm/solidigm-nvme.h create mode 100644 plugins/solidigm/solidigm-smart.c create mode 100644 plugins/solidigm/solidigm-smart.h create mode 100644 plugins/solidigm/solidigm-telemetry.c create mode 100644 plugins/solidigm/solidigm-telemetry.h create mode 100644 plugins/solidigm/solidigm-telemetry/cod.c create mode 100644 plugins/solidigm/solidigm-telemetry/cod.h create mode 100644 plugins/solidigm/solidigm-telemetry/config.c create mode 100644 plugins/solidigm/solidigm-telemetry/config.h create mode 100644 plugins/solidigm/solidigm-telemetry/data-area.c create mode 100644 plugins/solidigm/solidigm-telemetry/data-area.h create mode 100644 plugins/solidigm/solidigm-telemetry/header.c create mode 100644 plugins/solidigm/solidigm-telemetry/header.h create mode 100644 plugins/solidigm/solidigm-telemetry/meson.build create mode 100644 plugins/solidigm/solidigm-telemetry/telemetry-log.h create mode 100644 plugins/toshiba/toshiba-nvme.c create mode 100644 plugins/toshiba/toshiba-nvme.h create mode 100644 plugins/transcend/transcend-nvme.c create mode 100644 plugins/transcend/transcend-nvme.h create mode 100644 plugins/virtium/virtium-nvme.c create mode 100644 plugins/virtium/virtium-nvme.h create mode 100644 plugins/wdc/wdc-nvme.c create mode 100644 plugins/wdc/wdc-nvme.h create mode 100644 plugins/wdc/wdc-utils.c create mode 100644 plugins/wdc/wdc-utils.h create mode 100644 plugins/ymtc/ymtc-nvme.c create mode 100644 plugins/ymtc/ymtc-nvme.h create mode 100644 plugins/ymtc/ymtc-utils.h create mode 100644 plugins/zns/zns.c create mode 100644 plugins/zns/zns.h (limited to 'plugins') diff --git a/plugins/amzn/amzn-nvme.c b/plugins/amzn/amzn-nvme.c new file mode 100644 index 0000000..e04aa53 --- /dev/null +++ b/plugins/amzn/amzn-nvme.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include + +#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..9408e50 --- /dev/null +++ b/plugins/dera/dera-nvme.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + } + + const char* dev_status[] = { + "Normal", + "Quick Rebuilding", + "Full Rebuilding", + "Raw Rebuilding", + "Card Read Only", + "Fatal Error", + "Busy", + "Low Level Format", + "Firmware Committing", + "Over Temperature" }; + + const char *volt_status[] = { + "Normal", + "Initial Low", + "Runtime Low", + }; + + err = nvme_dera_get_device_status(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..9fef271 --- /dev/null +++ b/plugins/fdp/fdp.c @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = flags = validate_output_format(cfg.output_format); + 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 = flags = validate_output_format(cfg.output_format); + 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 = flags = validate_output_format(cfg.output_format); + 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 = flags = validate_output_format(cfg.output_format); + 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 = flags = validate_output_format(cfg.output_format); + 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 = 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"; + + 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; + }; + + struct config cfg = { + .enable = 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_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, + .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..572086c --- /dev/null +++ b/plugins/huawei/huawei-nvme.c @@ -0,0 +1,389 @@ +// 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, + * Yang Feng + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 nsid; + struct nvme_id_ns ns; + unsigned block; + char ns_name[NS_NAME_LEN]; + char array_name[ARRAY_NAME_LEN]; + bool huawei_device; +}; + +struct huawei_list_element_len { + unsigned int node; + unsigned int ns_name; + unsigned int nguid; + unsigned int ns_id; + unsigned int usage; + unsigned int array_name; +}; + +static int huawei_get_nvme_info(int fd, struct huawei_list_item *item, const char *node) +{ + int err; + int len; + struct stat nvme_stat_info; + + memset(item, 0, sizeof(*item)); + + err = nvme_identify_ctrl(fd, &item->ctrl); + if (err) + return err; + + /*identify huawei device*/ + if (strstr(item->ctrl.mn, "Huawei") == NULL && + le16_to_cpu(item->ctrl.vid) != HW_SSD_PCI_VENDOR_ID) { + item->huawei_device = false; + return 0; + } + + item->huawei_device = true; + 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 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 int 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 len, unsigned default_len) +{ + int i; + unsigned int min_len = default_len; + + for (i = 0 ; i < len ; i++) + min_len = choose_len(min_len , strlen(list_items->ns_name), default_len); + + return min_len; +} + +static int huawei_get_array_len(struct huawei_list_item *list_items, unsigned len, unsigned default_len) +{ + int i; + int min_len = default_len; + + for (i = 0 ; i < len ; i++) + min_len = choose_len(min_len , strlen(list_items->array_name), default_len); + + return min_len; +} + +static void huawei_print_list_items(struct huawei_list_item *list_items, unsigned len) +{ + unsigned i; + struct huawei_list_element_len element_len; + + element_len.node = 16; + element_len.nguid = 2 * sizeof(list_items->ns.nguid) + 1; + element_len.ns_id = 9; + element_len.usage = 26; + element_len.ns_name = huawei_get_ns_len(list_items, len, MIN_NS_NAME_LEN); + element_len.array_name = huawei_get_array_len(list_items, len, MIN_ARRAY_NAME_LEN); + + huawei_print_list_head(element_len); + + for (i = 0 ; i < len ; i++) + huawei_print_list_item(&list_items[i], element_len); +} + +static int huawei_list(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + char path[264]; + struct dirent **devices; + struct huawei_list_item *list_items; + unsigned int i, n, ret; + unsigned int huawei_num = 0; + int fmt; + const char *desc = "Retrieve basic information for the given huawei device"; + struct config { + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"), + OPT_END() + }; + + ret = argconfig_parse(argc, argv, desc, opts); + if (ret) + return ret; + + fmt = validate_output_format(cfg.output_format); + if (fmt != JSON && fmt != NORMAL) + return -EINVAL; + + 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..b5d40dd --- /dev/null +++ b/plugins/innogrit/innogrit-nvme.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include +#include + +#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.cdw12 = cdw12; + cmd.cdw13 = cdw13; + cmd.cdw14 = cdw14; + cmd.cdw15 = cdw15; + cmd.nsid = 0; + 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; + 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; + + 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); + 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; + 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); + + ipackindex = 0; + memset(data, 0, 4096); + if (nvme_vucmd(dev_fd(dev), NVME_VSC_GET, VSC_FN_GET_CDUMP, 0x00, + (SRB_SIGNATURE >> 32), (SRB_SIGNATURE & 0xFFFFFFFF), + (char *)data, 4096) == 0) { + 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) + 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) + 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..a97a008 --- /dev/null +++ b/plugins/innogrit/typedef.h @@ -0,0 +1,78 @@ +/* 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 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..9d7bb4d --- /dev/null +++ b/plugins/inspur/inspur-nvme.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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), 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..f660b84 --- /dev/null +++ b/plugins/intel/intel-nvme.c @@ -0,0 +1,1740 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include + +#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 __attribute__((packed)) nvme_additional_smart_log_item { + __u8 key; + __u8 _kp[2]; + __u8 norm; + __u8 _np; + union __attribute__((packed)) { + __u8 raw[6]; + struct __attribute__((packed)) wear_level { + __le16 min; + __le16 max; + __le16 avg; + } wear_level; + struct __attribute__((packed)) thermal_throttle { + __u8 pct; + __u32 count; + } thermal_throttle; + } ; + __u8 _rp; +} ; + +struct nvme_additional_smart_log { + struct nvme_additional_smart_log_item program_fail_cnt; + struct nvme_additional_smart_log_item erase_fail_cnt; + struct nvme_additional_smart_log_item wear_leveling_cnt; + struct nvme_additional_smart_log_item e2e_err_cnt; + struct nvme_additional_smart_log_item crc_err_cnt; + struct nvme_additional_smart_log_item timed_workload_media_wear; + struct nvme_additional_smart_log_item timed_workload_host_reads; + struct nvme_additional_smart_log_item timed_workload_timer; + struct nvme_additional_smart_log_item thermal_throttle_status; + struct nvme_additional_smart_log_item retry_buffer_overflow_cnt; + struct nvme_additional_smart_log_item pll_lock_loss_cnt; + struct nvme_additional_smart_log_item nand_bytes_written; + struct nvme_additional_smart_log_item host_bytes_written; + struct nvme_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 __attribute__((__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, (long unsigned int)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", (long unsigned int)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_fd; + + 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, "Quering thresholds failed. "); + nvme_show_status(err); + goto close_fd; + } + + /* 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_fd: + 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, + }; + + const 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..fb46841 --- /dev/null +++ b/plugins/memblaze/memblaze-nvme.c @@ -0,0 +1,1227 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include +#include + +#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 (0 == 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 Kelvin since last factory reset : %u\n", + le16_to_cpu(smart->items[TEMPT_SINCE_RESET].temperature.max)); + printf("Minimum temperature in Kelvin since last factory reset : %u\n", + le16_to_cpu(smart->items[TEMPT_SINCE_RESET].temperature.min)); + if (compare_fw_version(fw_ver, "0.09.0300") != 0) { + printf("Maximum temperature in Kelvin since power on : %u\n", + le16_to_cpu(smart->items[TEMPT_SINCE_BOOTUP].temperature_p.max)); + printf("Minimum temperature in Kelvin since power on : %u\n", + le16_to_cpu(smart->items[TEMPT_SINCE_BOOTUP].temperature_p.min)); + } + printf("Current temperature in Kelvin : %u\n", + le16_to_cpu(smart->items[TEMPT_SINCE_RESET].temperature.curr)); + + printf("Maximum power in watt since power on : %u\n", + le16_to_cpu(smart->items[POWER_CONSUMPTION].power.max)); + printf("Minimum power in watt since power on : %u\n", + le16_to_cpu(smart->items[POWER_CONSUMPTION].power.min)); + printf("Current power in watt : %u\n", + le16_to_cpu(smart->items[POWER_CONSUMPTION].power.curr)); + + item = &smart->items[POWER_LOSS_PROTECTION]; + if (item_id_2_u32(item) == 0xEC) + printf("Power loss protection normalized value : %u\n", + item->power_loss_protection.curr); + + item = &smart->items[WEARLEVELING_COUNT]; + if (item_id_2_u32(item) == 0xAD) { + printf("Percentage of wearleveling count left : %u\n", + le16_to_cpu(item->nmval)); + printf("Wearleveling count min erase cycle : %u\n", + le16_to_cpu(item->wearleveling_count.min)); + printf("Wearleveling count max erase cycle : %u\n", + le16_to_cpu(item->wearleveling_count.max)); + printf("Wearleveling count avg erase cycle : %u\n", + le16_to_cpu(item->wearleveling_count.avg)); + } + + item = &smart->items[HOST_WRITE]; + if (item_id_2_u32(item) == 0xF5) + printf("Total host write in GiB since device born : %llu\n", + (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval))); + + item = &smart->items[THERMAL_THROTTLE_CNT]; + if (item_id_2_u32(item) == 0xEB) + printf("Thermal throttling count since device born : %u\n", + item->thermal_throttle_cnt.cnt); + + item = &smart->items[CORRECT_PCIE_PORT0]; + if (item_id_2_u32(item) == 0xED) + printf("PCIE Correctable Error Count of Port0 : %llu\n", + (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval))); + + item = &smart->items[CORRECT_PCIE_PORT1]; + if (item_id_2_u32(item) == 0xEE) + printf("PCIE Correctable Error Count of Port1 : %llu\n", + (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval))); + + item = &smart->items[REBUILD_FAIL]; + if (item_id_2_u32(item) == 0xEF) + printf("End-to-End Error Detection Count : %llu\n", + (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval))); + + item = &smart->items[ERASE_FAIL]; + if (item_id_2_u32(item) == 0xF0) + printf("Erase Fail Count : %llu\n", + (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval))); + + item = &smart->items[PROGRAM_FAIL]; + if (item_id_2_u32(item) == 0xF1) + printf("Program Fail Count : %llu\n", + (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval))); + + item = &smart->items[READ_FAIL]; + if (item_id_2_u32(item) == 0xF2) + printf("Read Fail Count : %llu\n", + (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval))); + + if ( IS_PAPAYA(fw_ver_local) ) { + struct nvme_p4_smart_log *s = (struct nvme_p4_smart_log *)smart; + __u8 *nm = malloc(NM_SIZE * sizeof(__u8)); + __u8 *raw = malloc(RAW_SIZE * sizeof(__u8)); + + 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 == NULL) { + printf("No enough parameters. abort...\n"); + va_end(argp); + return 1; + } + + if (isalnum((int)*c) == 0) { + 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 definiton + * Total 32 bytes + */ +typedef struct +{ + __u8 port; + __u8 revision; + __u16 rsvd; + __u8 opcode; + __u8 sqe; + __u16 cid; + __u32 nsid; + __u32 latency; + __u64 sLBA; + __u16 numLBA; + __u16 timestampH; + __u32 timestampL; +} log_page_high_latency_t; /* 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) +{ + log_page_high_latency_t *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(log_page_high_latency_t); + for (i = 0; i < buflen; i += entrySize) + { + logEntry = (log_page_high_latency_t *)(buf + i); + + if (logEntry->latency == 0 && logEntry->revision == 0) + { + return 1; + } + + if (0 == logEntry->timestampH) // generate host time string + { + snprintf(string, sizeof(string), "%d", logEntry->timestampL); + } + else // sort + { + timestamp = logEntry->timestampH - 1; + 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 (NULL != 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 " + "select which firmware binary to update for 9200 devices. This requires a power cycle once the " + "update completes. The options available are: \n\n" + "OOB - This updates the OOB and main firmware\n" + "EEP - This updates the eeprom and main firmware\n" + "ALL - This updates the eeprom, OOB, and main firmware"; + const char *fw = "firmware file (required)"; + const char *select = "FW Select (e.g., --select=OOB, EEP, ALL)"; + int xfer = 4096; + void *fw_buf; + int 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) == 0) { + selectNo = 18; + } else if (strncmp(cfg.select,"EEP", 3) == 0) { + selectNo = 10; + } else if (strncmp(cfg.select,"ALL", 3) == 0) { + selectNo = 26; + } else { + fprintf(stderr, "Invalid select flag\n"); + err = EINVAL; + goto out; + } + + fw_fd = open(cfg.fw, O_RDONLY); + if (fw_fd < 0) { + fprintf(stderr, "no firmware file provided\n"); + err = EINVAL; + goto out; + } + + err = fstat(fw_fd, &sb); + if (err < 0) { + perror("fstat"); + err = errno; + 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, + }; + + const 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; +} diff --git a/plugins/memblaze/memblaze-nvme.h b/plugins/memblaze/memblaze-nvme.h new file mode 100644 index 0000000..e214ca6 --- /dev/null +++ b/plugins/memblaze/memblaze-nvme.h @@ -0,0 +1,26 @@ +/* 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) + ) +); + +#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..f64a115 --- /dev/null +++ b/plugins/memblaze/memblaze-utils.h @@ -0,0 +1,221 @@ +/* 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 Addtional smart internal ID */ +typedef enum +{ + /* smart attr following intel */ + RAISIN_SI_VD_PROGRAM_FAIL = 0, /* 0xAB */ + RAISIN_SI_VD_ERASE_FAIL = 1, /* 0xAC */ + RAISIN_SI_VD_WEARLEVELING_COUNT = 2,/* 0xAD */ + RAISIN_SI_VD_E2E_DECTECTION_COUNT = 3, /* 0xB8 */ + RAISIN_SI_VD_PCIE_CRC_ERR_COUNT = 4, /* 0xC7, 2 port data in one attribute */ + RAISIN_SI_VD_TIMED_WORKLOAD_MEDIA_WEAR = 5, /* 0xE2 , unknown definition*/ + RAISIN_SI_VD_TIMED_WORKLOAD_HOST_READ = 6, /* 0xE3 , unknown definition */ + RAISIN_SI_VD_TIMED_WORKLOAD_TIMER = 7, /* 0xE4 , unknown definition */ + RAISIN_SI_VD_THERMAL_THROTTLE_STATUS = 8, /* 0xEA */ + RAISIN_SI_VD_RETRY_BUFF_OVERFLOW_COUNT = 9, /* 0xF0, unknown definition*/ + RAISIN_SI_VD_PLL_LOCK_LOSS_COUNT = 10, /* 0xF3, unknown definition*/ + RAISIN_SI_VD_TOTAL_WRITE = 11, /* 0xF4, unit is 32MiB */ + RAISIN_SI_VD_HOST_WRITE = 12, /* 0xF5, unit is 32MiB */ + RAISIN_SI_VD_SYSTEM_AREA_LIFE_LEFT = 13, /* 0xF6, unknown definition*/ + RAISIN_SI_VD_TOTAL_READ = 14, /* 0xFA, unit is 32MiB */ + + /* smart attr self defined */ + RAISIN_SI_VD_TEMPT_SINCE_BORN = 15, /* 0xE7 */ + RAISIN_SI_VD_POWER_CONSUMPTION = 16, /* 0xE8 */ + RAISIN_SI_VD_TEMPT_SINCE_BOOTUP = 17, /* 0xAF */ + RAISIN_SI_VD_POWER_LOSS_PROTECTION = 18, /* 0xEC */ + RAISIN_SI_VD_READ_FAIL = 19, /* 0xF2 */ + RAISIN_SI_VD_THERMAL_THROTTLE_TIME = 20, /* 0xEB */ + RAISIN_SI_VD_FLASH_MEDIA_ERROR = 21, /* 0xED */ + RAISIN_SI_VD_SMART_INFO_ITEMS_MAX, +} RAISIN_si_vendor_smart_item_e; + +// Memblaze Format & old format +enum { + /*0*/TOTAL_WRITE = 0, + /*1*/TOTAL_READ, + /*2*/THERMAL_THROTTLE, + /*3*/TEMPT_SINCE_RESET, + /*4*/POWER_CONSUMPTION, + /*5*/TEMPT_SINCE_BOOTUP, + /*6*/POWER_LOSS_PROTECTION, + /*7*/WEARLEVELING_COUNT, + /*8*/HOST_WRITE, + /*9*/THERMAL_THROTTLE_CNT, + /*10*/CORRECT_PCIE_PORT0, + /*11*/CORRECT_PCIE_PORT1, + /*12*/REBUILD_FAIL, + /*13*/ERASE_FAIL, + /*14*/PROGRAM_FAIL, + /*15*/READ_FAIL, + /*16*/NR_SMART_ITEMS = RAISIN_SI_VD_SMART_INFO_ITEMS_MAX, +}; + +// Memblaze Format & old format +#pragma pack(push, 1) +struct nvme_memblaze_smart_log_item { + __u8 id[3]; + union { + __u8 __nmval[2]; + __le16 nmval; + }; + union { + __u8 rawval[6]; + struct temperature { + __le16 max; + __le16 min; + __le16 curr; + } temperature; + struct power { + __le16 max; + __le16 min; + __le16 curr; + } power; + struct thermal_throttle_mb { + __u8 on; + __u32 count; + } thermal_throttle; + struct temperature_p { + __le16 max; + __le16 min; + } temperature_p; + struct power_loss_protection { + __u8 curr; + } power_loss_protection; + struct wearleveling_count { + __le16 min; + __le16 max; + __le16 avg; + } wearleveling_count; + struct thermal_throttle_cnt { + __u8 active; + __le32 cnt; + } thermal_throttle_cnt; + }; + __u8 resv; +}; +#pragma pack(pop) + +struct nvme_memblaze_smart_log { + struct nvme_memblaze_smart_log_item items[NR_SMART_ITEMS]; + __u8 resv[SMART_INFO_OLD_SIZE - sizeof(struct nvme_memblaze_smart_log_item) * NR_SMART_ITEMS]; +}; + +// Intel Format & new format +struct nvme_p4_smart_log_item +{ + /* Item identifier */ + __u8 id[ID_SIZE]; + /* Normalized value or percentage. In the range from 0 to 100. */ + __u8 nmVal[NM_SIZE]; + /* raw value */ + __u8 rawVal[RAW_SIZE]; +}; + +struct nvme_p4_smart_log +{ + struct nvme_p4_smart_log_item itemArr[NR_SMART_ITEMS]; + + /** + * change 512 to 4096. + * because micron's getlogpage request,the size of many commands have changed to 4k. + * request size > user malloc size,casuing parameters that are closed in momery are dirty. + */ + __u8 resv[SMART_INFO_NEW_SIZE - sizeof(struct nvme_p4_smart_log_item) * NR_SMART_ITEMS]; +}; + +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include +#include "linux/types.h" +#include "nvme-print.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 ! + */ +typedef enum { M5410 = 0, M51AX, M51BX, M51CX, M5407, M5411, UNKNOWN_MODEL } eDriveModel; + +#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; + +typedef struct _LogPageHeader_t { + unsigned char numDwordsInLogPageHeaderLo; + unsigned char logPageHeaderFormatVersion; + unsigned char logPageId; + unsigned char numDwordsInLogPageHeaderHi; + unsigned int numValidDwordsInPayload; + unsigned int numDwordsInEntireLogPage; +} LogPageHeader_t; + +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); + if ((fpOutFile = fopen(tempFolder, "ab+")) != NULL) { + 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 eDriveModel GetDriveModel(int idx) +{ + eDriveModel eModel = UNKNOWN_MODEL; + char path[512]; + + sprintf(path, fvendorid1, idx); + if (ReadSysFile(path, &vendor_id) < 0) { + sprintf(path, fvendorid2, idx); + ReadSysFile(path, &vendor_id); + } + sprintf(path, fdeviceid1, idx); + if (ReadSysFile(path, &device_id) < 0) { + sprintf(path, fdeviceid2, idx); + ReadSysFile(path, &device_id); + } + if (vendor_id == MICRON_VENDOR_ID) { + switch (device_id) { + case 0x5196: + case 0x51A0: + case 0x51A1: + case 0x51A2: + eModel = M51AX; + break; + case 0x51B0: + case 0x51B1: + case 0x51B2: + eModel = M51BX; + break; + case 0x51C0: + case 0x51C1: + case 0x51C2: + case 0x51C3: + eModel = M51CX; + break; + case 0x5405: + case 0x5406: + 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, '/') != NULL) { + fileName = strrchr(strFilePath, '\\'); + if (fileName == NULL) { + fileName = strrchr(strFilePath, '/'); + } + + if (fileName != NULL) { + if (!strcmp(fileName, "/")) { + goto exit_status; + } + + while (strFilePath[nIndex] != '\0') { + if ('\\' == strFilePath[nIndex] && '\\' == strFilePath[nIndex + 1]) { + goto exit_status; + } + nIndex++; + } + + length = (int)strlen(strFilePath) - (int)strlen(fileName); + + if (fileName == strFilePath) { + length = 1; + } + + if ((fileLocation = (char *)malloc(length + 1)) == NULL) { + 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]) { + if ((strTemp = (char *)malloc(length + 2)) == NULL) { + free(fileLocation); + goto exit_status; + } + strcpy(strTemp, fileLocation); + strcat(strTemp, "/"); + free(fileLocation); + + length = (int)strlen(strTemp); + if ((fileLocation = (char *)malloc(length + 1)) == NULL) { + free(strTemp); + goto exit_status; + } + + memcpy(fileLocation, strTemp, length + 1); + free(strTemp); + } + + if (stat(fileLocation, &st) != 0) { + free(fileLocation); + goto exit_status; + } + free(fileLocation); + } else { + goto exit_status; + } + } + + nIndex = 0; + for (i = 0; i < (int)strlen(strSN); i++) { + if (strSN[i] != ' ' && strSN[i] != '\n' && strSN[i] != '\t' && strSN[i] != '\r') { + strMainDirName[nIndex++] = strSN[i]; + } + } + strMainDirName[nIndex] = '\0'; + + j = 1; + while (stat(strMainDirName, &dirStat) == 0) { + strMainDirName[nIndex] = '\0'; + sprintf(strAppend, "-%d", j); + strcat(strMainDirName, strAppend); + j++; + } + + if (mkdir(strMainDirName, 0777) < 0) { + err = -1; + goto exit_status; + } + + if (strOSDirName != NULL) { + sprintf(strOSDirName, "%s/%s", strMainDirName, "OS"); + if (mkdir(strOSDirName, 0777) < 0) { + rmdir(strMainDirName); + err = -1; + goto exit_status; + } + } + if (strCtrlDirName != NULL) { + sprintf(strCtrlDirName, "%s/%s", strMainDirName, "Controller"); + if (mkdir(strCtrlDirName, 0777) < 0) { + if (strOSDirName != NULL) + 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 }; + LogPageHeader_t *pLogHeader = NULL; + + if (ucLogID == 0xC1 || ucLogID == 0xC2 || ucLogID == 0xC4) { + err = nvme_get_log_simple(nFD, ucLogID, + CommonChunkSize, pTmpBuf); + if (err == 0) { + pLogHeader = (LogPageHeader_t *) pTmpBuf; + LogPageHeader_t *pLogHeader1 = (LogPageHeader_t *) pLogHeader; + *nLogSize = (int)(pLogHeader1->numDwordsInEntireLogPage) * 4; + if (pLogHeader1->logPageHeaderFormatVersion == 0) { + 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 == 0 && (ucLogID == 0xE6 || ucLogID == 0xE7)) { + uiMaxChunk = 4096; + } else if (uiMaxChunk > 16 * 1024) { + uiMaxChunk = 16 * 1024; + } + + uiNumChunks = uiNumDwords / uiMaxChunk; + if (uiNumDwords % uiMaxChunk > 0) { + uiNumChunks += 1; + } + + for (unsigned int i = 0; i < uiNumChunks; i++) { + memset(&cmd, 0, sizeof(cmd)); + uiXferDwords = uiMaxChunk; + if (i == uiNumChunks - 1 && uiNumDwords % uiMaxChunk > 0) { + uiXferDwords = uiNumDwords % uiMaxChunk; + } + + cmd.opcode = ucOpCode; + cmd.cdw10 |= ucLogID; + cmd.cdw10 |= ((uiXferDwords - 1) & 0x0000FFFF) << 16; + + if (ucLogID == 0x7) { + cmd.cdw10 |= 0x80; + } + if (ullBytesRead == 0 && (ucLogID == 0xE6 || ucLogID == 0xE7)) { + cmd.cdw11 = 1; + } + if (ullBytesRead > 0 && !(ucLogID == 0xE6 || ucLogID == 0xE7)) { + unsigned long long ullOffset = ullBytesRead; + cmd.cdw12 = ullOffset & 0xFFFFFFFF; + cmd.cdw13 = (ullOffset >> 32) & 0xFFFFFFFF; + } + + cmd.addr = (__u64) (uintptr_t) pTempPtr; + cmd.nsid = 0xFFFFFFFF; + cmd.data_len = uiXferDwords * 4; + err = nvme_submit_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; + + if ((pBuffer = (unsigned int *)calloc(1, nBufferSize)) == NULL) + return err; + + while (err == 0 && llMaxSize > 0) { + err = NVMEGetLogPage(nFD, ucLogID, (unsigned char *)pBuffer, nBufferSize); + if (err) { + 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, + const struct argconfig_commandline_options *opts, + eDriveModel *modelp) +{ + int idx = 0; + int err = parse_and_open(dev, argc, argv, desc, opts); + + if (err) { + perror("open"); + return -1; + } + + if (modelp) { + sscanf(argv[optind], "/dev/nvme%d", &idx); + *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 " + "select which firmware binary to update for 9200 devices. This requires " + "a power cycle once the update completes. The options available are: \n\n" + "OOB - This updates the OOB and main firmware\n" + "EEP - This updates the eeprom and main firmware\n" + "ALL - This updates the eeprom, OOB, and main firmware"; + const char *fw = "firmware file (required)"; + const char *select = "FW Select (e.g., --select=ALL)"; + int xfer = 4096; + void *fw_buf; + int 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) == 0) { + selectNo = 18; + } else if (strncmp(cfg.select, "EEP", 3) == 0) { + selectNo = 10; + } else if (strncmp(cfg.select, "ALL", 3) == 0) { + selectNo = 26; + } else { + fprintf(stderr, "Invalid select flag\n"); + 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 != 0) { + 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; + 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 == 0) { + 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 == 0) { + 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 == 0) { + 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") == 0) + 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] != 0; 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] != 0; 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] != 0; 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 = 0, domain = 0, device = 0, function = 0, 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 }; + 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 */ + sscanf(argv[optind], "/dev/nvme%d", &ctrlIdx); + if ((eModel = GetDriveModel(ctrlIdx)) == UNKNOWN_MODEL) { + printf ("Unsupported drive model for vs-pcie-stats command\n"); + goto out; + } + + if (strcmp(cfg.fmt, "normal") == 0) + 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, '/'); + sscanf(businfo, "/%x:%x:%x.%x", &domain, &bus, &device, &function); + sprintf(command, "setpci -s %x:%x.%x ECAP_AER+10.L", bus, device, + function); + fp = popen(command, "r"); + if (fp == NULL) { + printf("Failed to retrieve error count\n"); + goto out; + } + res = fgets(correctable, sizeof(correctable), fp); + if (res == NULL) { + 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 == NULL) { + printf("Failed to retrieve error count\n"); + goto out; + } + res = fgets(uncorrectable, sizeof(uncorrectable), fp); + if (res == NULL) { + 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 < sizeof(pcie_correctable_errors) / sizeof(pcie_correctable_errors[0]); 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 < sizeof(pcie_uncorrectable_errors) / sizeof(pcie_uncorrectable_errors[0]); 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 < sizeof(pcie_correctable_errors) / sizeof(pcie_correctable_errors[0]); i++) { + printf("%-42s : %-1hu\n", pcie_correctable_errors[i].err, + *(__u16 *)(pcounter + pcie_correctable_errors[i].val)); + } + for (i = 0; i < sizeof(pcie_uncorrectable_errors) / sizeof(pcie_uncorrectable_errors[0]); 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 < sizeof(pcie_correctable_errors) / sizeof(pcie_correctable_errors[0]); i++) { + printf("%-42s : %-1d\n", pcie_correctable_errors[i].err, + ((correctable_errors >> pcie_correctable_errors[i].bit) & 1)); + } + for (i = 0; i < sizeof(pcie_uncorrectable_errors) / sizeof(pcie_uncorrectable_errors[0]); 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 = 0, domain = 0, device = 0, function = 0; + 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; + 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 == 0 && (err = (int)result) == 0) { + 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 == 0) { + printf("Device correctable error counters are cleared!\n"); + goto out; + } else { + /* proceed to clear status bits using sysfs interface + printf("Error clearing PCIe correctable errors = 0x%x\n", err); */ + } + } + + 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, '/'); + sscanf(businfo, "/%x:%x:%x.%x", &domain, &bus, &device, &function); + sprintf(command, "setpci -s %x:%x.%x ECAP_AER+0x10.L=0xffffffff", bus, + device, function); + err = -1; + fp = popen(command, "r"); + if (fp == NULL) { + 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 == NULL) { + printf("Failed to retrieve error count\n"); + goto out; + } + res = fgets(correctable, sizeof(correctable), fp); + if (res == NULL) { + 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 != 0) + 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 */ +static void print_micron_vs_logs( + __u8 *buf, /* raw log data */ + struct micron_vs_logpage *log_page, /* format of the data */ + int field_count, /* log field count */ + struct json_object *stats, /* json object to add fields */ + __u8 spec /* ocp spec index */ +) +{ + __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 == 0) ? log_page[field].size : log_page[field].size2; + if (size == 0) 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 != NULL) { + 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 = sizeof(ocp_c0_log_page)/sizeof(ocp_c0_log_page[0]); + + 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 = sizeof(fb_log_page)/sizeof(fb_log_page[0]); + + 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 != NULL) { + 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 = false; /* 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 }; + 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") == 0) + 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 */ + sscanf(argv[optind], "/dev/nvme%d", &ctrlIdx); + 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 = (0 == 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 = (0 == err); + } + + nsze = (ctrl.vs[987] == 0x12); + if (nsze == 0 && 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 = sizeof(e1_log_page)/sizeof(e1_log_page[0]); + + 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 }; + eDriveModel eModel = UNKNOWN_MODEL; + int err = 0, ctrlIdx = 0; + 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") == 0) + is_json = false; + + sscanf(argv[optind], "/dev/nvme%d", &ctrlIdx); + if ((eModel = GetDriveModel(ctrlIdx)) != 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 == NULL) + 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) == 0) { + 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 == NULL) + return; + + if (nvme_get_log_error(fd, entries, false, error_log) == 0) { + 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; + void *pevent_log_info = NULL; + __u32 log_len = 0; + int err = 0 ; + bool huge = false; + + /* get self test log */ + if (nvme_get_log_device_self_test(fd, &self_test_log) == 0) { + 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) == 0) { + 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) == 0) { + 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(log_len, &huge); + 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 == 0) { + WriteData((__u8*)pevent_log_info, log_len, dir, + "persistent_event_log.bin", "persistent event log"); + } + nvme_free(pevent_log_info, huge); + return; +} + +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) == 0) { + 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 (NULL != 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 == NULL) + 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 != 0) { + fprintf(stderr, "Failed to get telemetry log header for 0x%X\n", type); + if (buffer != NULL) { + 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] == 0) { + fprintf(stderr, "Requested telemetry data for 0x%X is empty\n", type); + if (buffer != NULL) { + free(buffer); + buffer = NULL; + } + return -1; + } + + *logSize = data_area[da] * bs; + offset = bs; + err = 0; + if ((buffer = (unsigned char *)realloc(buffer, (size_t)(*logSize))) != NULL) { + while (err == 0 && 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 == 0 && buffer != NULL) { + *data = buffer; + } else { + fprintf(stderr, "Failed to get telemetry data for 0x%x\n", type); + if (buffer != NULL) + 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)(sizeof(tmap)/sizeof(tmap[0])); i++) { + err = micron_telemetry_log(fd, tmap[i].log, &buffer, &logSize, 0); + if (err == 0 && logSize > 0 && buffer != NULL) { + 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)(sizeof(fmap)/sizeof(fmap[0])); 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 == 0) { + sprintf(msg, "feature: 0x%X", fmap[i].id); + WriteData((__u8*)&attrVal, sizeof(attrVal), dir, fmap[i].file, msg); + if (bufp != NULL) { + 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 == sizeof(fmap)/sizeof(fmap[0])); +} + +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 }; + 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") == 0) + 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, "%hhu.%hhu", 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, "%hhu KB", dinfo.ftl_unit_size); + json_object_add_value_string(pinfo, "FTL_unit_size", tempstr); + } + + if (dinfo.bs_ver_major != 0 || dinfo.bs_ver_minor != 0) { + sprintf(tempstr, "%hhu.%hhu", 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: %hhu.%hhu\n", + dinfo.hw_ver_major, dinfo.hw_ver_minor); + + if (dinfo.ftl_unit_size) + printf("FTL_unit_size: %hhu KB\n", dinfo.ftl_unit_size); + + if (dinfo.bs_ver_major != 0 || dinfo.bs_ver_minor != 0) { + printf("Boot Spec.Version: %hhu.%hhu\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 __attribute__((__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 __attribute__((__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]; +}; + +/* header to be printed field widths = 10 | 12 | 10 | 11 | 12 | 9 | 9 | 9 */ + +const char *fw_activation_history_table_header = "\ +__________________________________________________________________________________\n\ + | | | | | | | \n\ +Firmware | Power On | Power | Previous | New FW | Slot | Commit | Result \n\ +Activation| Hour | cycle | firmware | activated | number | Action | \n\ +Counter | | count | | | | Type | \n\ +__________|___________|_________|__________|___________|________|________|________\n"; + +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; + char *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) { + /*fprintf(stderr, "unsupported entry ! version: %x with length: %d\n", + entry->version, entry->length); */ + 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":%hhu:%hhu", (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 charecters with spaces */ + ptr = formatted_entry; + while (index < entry_size) { + if (ptr[index] == '\0') + ptr[index] = ' '; + index++; + } + return 0; +} + + +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 }; + 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") != 0) { + 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 atleast 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 == 0) { + fprintf(stderr, "No entries were found in fw activation history log\n"); + goto out; + } + + printf("%s", fw_activation_history_table_header); + 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) == 0) + { + 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; + 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 != 0) { + 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 == 0) { + 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; + } + /* timing mask is in terms of 10ms units, so min allowed is 10ms */ + else if ((opt.threshold % 10) != 0) { + 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 == 0) { + printf("Successfully %sd latency monitoring for %s commands with %dms threshold\n", + opt.option, opt.command, opt.threshold == 0 ? 800 : opt.threshold * 10); + } else { + printf("Failed to %s latency monitoring for %s commands with %dms threshold\n", + opt.option, opt.command, opt.threshold == 0 ? 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]; + 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, 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|trim" + "default is all"; + int err = 0; + struct nvme_dev *dev; + 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; + 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") == 0) + 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; + + if ((err = nvme_identify_ctrl(dev_fd(dev), &ctrl)) == 0) + 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 == 0 && 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 commmand\n"); + err = -1; + goto out; + } + + err = nvme_get_log_simple(dev_fd(dev), 0xC0, C0_log_size, logC0); + if (err == 0) { + 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; + 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 == 0) 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 options" + "1 - save (persistent), 0 - non-persistent and for " + "status options: 0 - current, 1 - default, 2-saved"; + int fid = MICRON_FEATURE_TELEMETRY_CONTROL_OPTION; + 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 == 0) { + 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 == 0) { + 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 == 0) { + 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 %hhu with error: 0x%x\n", id, ret); + return ret; + } + + if (hdr.id != id || + hdr.log_size == 0 || + hdr.max_size == 0 || + hdr.write_pointer < sizeof(hdr)) + { + fprintf(stderr, "invalid log data for LOG: 0x%X, id: 0x%X, size: %u, " + "max: %u, wp: %u, flags: %hhu, 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 == NULL) { + 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 == NULL) { + 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 == 0) { + 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 == NULL) { + 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 }, + }; + + 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) != 0) { + 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) == 0) { + 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 */ + sscanf(argv[optind], "/dev/nvme%d", &ctrlIdx); + if ((eModel = GetDriveModel(ctrlIdx)) == 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 == 0 && logSize > 0 && buffer != NULL) { + 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 += sizeof(aM51XXLogs)/sizeof(aM51XXLogs[0]); + 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)(sizeof(aVendorLogs) / sizeof(aVendorLogs[0])) && + aVendorLogs[i].ucLogPage != 0; i++) { + err = -1; + switch (aVendorLogs[i].ucLogPage) { + case 0xE1: + case 0xE5: + case 0xE9: + err = 1; + break; + + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE8: + case 0xEA: + err = get_common_log(dev_fd(dev), aVendorLogs[i].ucLogPage, + &dataBuffer, &bSize); + break; + + case 0xC1: + case 0xC2: + case 0xC4: + err = GetLogPageSize(dev_fd(dev), aVendorLogs[i].ucLogPage, + &bSize); + if (err == 0 && bSize > 0) + err = GetCommonLogPage(dev_fd(dev), aVendorLogs[i].ucLogPage, + &dataBuffer, bSize); + break; + + case 0xE6: + case 0xE7: + puiIDDBuf = (unsigned int *)&ctrl; + uiMask = puiIDDBuf[1015]; + if (uiMask == 0 || (aVendorLogs[i].ucLogPage == 0xE6 && uiMask == 2) || + (aVendorLogs[i].ucLogPage == 0xE7 && uiMask == 1)) { + bSize = 0; + } else { + bSize = (int)puiIDDBuf[1023]; + if (bSize % (16 * 1024)) { + bSize += (16 * 1024) - (bSize % (16 * 1024)); + } + } + if (bSize != 0 && (dataBuffer = (unsigned char *)malloc(bSize)) != NULL) { + 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: + case 0xF9: + case 0xFC: + 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 == NULL) { + 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 == 0 && 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 == 0 && dataBuffer != NULL && ((unsigned int *)dataBuffer)[0] != 0xdeadbeef) { + sprintf(msg, "log 0x%x", aVendorLogs[i].ucLogPage); + WriteData(dataBuffer, bSize, strCtrlDirName, aVendorLogs[i].strFileName, msg); + } + + if (dataBuffer != NULL) { + 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"; + 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 < sizeof(log_list)/sizeof(log_list[0]); 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/netapp/netapp-nvme.c b/plugins/netapp/netapp-nvme.c new file mode 100644 index 0000000..f5cb073 --- /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 +#include +#include +#include +#include +#include +#include +#include + +#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 nsid; + struct nvme_id_ctrl ctrl; + struct nvme_id_ns ns; + char dev[265]; +}; + +struct ontapdevice_info { + unsigned 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 int 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 +#include +#include +#include +#include +#include + +#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..a4e5d20 --- /dev/null +++ b/plugins/ocp/meson.build @@ -0,0 +1,6 @@ +sources += [ + 'plugins/ocp/ocp-utils.c', + 'plugins/ocp/ocp-nvme.c', + 'plugins/ocp/ocp-clear-fw-update-history.c', +] + diff --git a/plugins/ocp/ocp-clear-fw-update-history.c b/plugins/ocp/ocp-clear-fw-update-history.c new file mode 100644 index 0000000..fef09cf --- /dev/null +++ b/plugins/ocp/ocp-clear-fw-update-history.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Authors: haro.panosyan@solidigm.com + * leonardo.da.cunha@solidigm.com + */ + +#include +#include "ocp-utils.h" +#include "nvme-print.h" + +static const __u8 OCP_FID_CLEAR_FW_ACTIVATION_HISTORY = 0xC1; + +int ocp_clear_fw_update_history(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "OCP Clear Firmware Update History"; + __u32 result = 0; + __u32 clear_fw_history = 1 << 31; + struct nvme_dev *dev; + int uuid_index = 0; + bool no_uuid = false; + int err; + + OPT_ARGS(opts) = { + OPT_FLAG("no-uuid", 'n', &no_uuid, + "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 (no_uuid == false) { + // OCP 2.0 requires UUID index support + err = ocp_get_uuid_index(dev, &uuid_index); + if (err || uuid_index == 0) { + 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_fw_history, + .cdw12 = 0, + .cdw13 = 0, + .cdw15 = 0, + .data_len = 0, + .save = 0, + .uuidx = uuid_index, + .fid = OCP_FID_CLEAR_FW_ACTIVATION_HISTORY, + }; + + 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; +} diff --git a/plugins/ocp/ocp-clear-fw-update-history.h b/plugins/ocp/ocp-clear-fw-update-history.h new file mode 100644 index 0000000..25fb6b1 --- /dev/null +++ b/plugins/ocp/ocp-clear-fw-update-history.h @@ -0,0 +1,9 @@ +/* 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); diff --git a/plugins/ocp/ocp-nvme.c b/plugins/ocp/ocp-nvme.c new file mode 100644 index 0000000..14a5f30 --- /dev/null +++ b/plugins/ocp/ocp-nvme.c @@ -0,0 +1,774 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (c) 2022 Meta Platforms, Inc. + * + * Authors: Arthur Shau , + * Wei Zhang , + * Venkat Ramesh + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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-clear-fw-update-history.h" + +#define CREATE_CMD +#include "ocp-nvme.h" + +/* C0 SCAO Log Page */ +#define C0_SMART_CLOUD_ATTR_LEN 0x200 +#define C0_SMART_CLOUD_ATTR_OPCODE 0xC0 +#define C0_GUID_LENGTH 16 +#define C0_ACTIVE_BUCKET_TIMER_INCREMENT 5 +#define C0_ACTIVE_THRESHOLD_INCREMENT 5 +#define C0_MINIMUM_WINDOW_INCREMENT 100 + +static __u8 scao_guid[C0_GUID_LENGTH] = { 0xC5, 0xAF, 0x10, 0x28, 0xEA, 0xBF, + 0xF2, 0xA4, 0x9C, 0x4F, 0x6F, 0x7C, 0xC9, 0x14, 0xD5, 0xAF }; + +/* C3 Latency Monitor Log Page */ +#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 +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 0 +#define WRITE 1 +#define TRIM 2 +#define RESERVED 3 + +typedef 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_LPV = 494, /* Log page version */ + SCAO_LPG = 496, /* Log page GUID */ +} SMART_CLOUD_ATTRIBUTE_OFFSETS; + +struct __attribute__((__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] ; /* 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 */ +}; + +static int convert_ts(time_t time, char *ts_buf) +{ + struct tm gmTimeInfo; + time_t time_Human, time_ms; + char buf[80]; + + time_Human = time/1000; + time_ms = time % 1000; + + gmtime_r((const time_t *)&time_Human, &gmTimeInfo); + + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &gmTimeInfo); + sprintf(ts_buf, "%s.%03ld GMT", buf, time_ms); + + return 0; +} + +static void ocp_print_C0_log_normal(void *data) +{ + __u8 *log_data = (__u8*)data; + uint16_t smart_log_ver = 0; + + 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 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])); + } + printf("\n"); +} + +static void ocp_print_C0_log_json(void *data) +{ + __u8 *log_data = (__u8*)data; + struct json_object *root; + struct json_object *pmuw; + struct json_object *pmur; + uint16_t smart_log_ver = 0; + + 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 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_uint(root, "PCIe Link Retraining Count", + (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PLRC])); + } + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static int get_c0_log_page(int fd, char *format) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + int i; + + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : OCP : invalid output format\n"); + return fmt; + } + + if ((data = (__u8 *) malloc(sizeof(__u8) * C0_SMART_CLOUD_ATTR_LEN)) == NULL) { + 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]) { + fprintf(stderr, "ERROR : OCP : Unknown GUID in C0 Log Page data\n"); + int j; + 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; + } + } else { + fprintf(stderr, "ERROR : OCP : Unable to read C0 data from buffer\n"); + } + +out: + free(data); + return ret; +} + +static int ocp_smart_add_log(int argc, char **argv, struct command *cmd, + 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_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; +} + +static int ocp_print_C3_log_normal(struct nvme_dev *dev, + struct ssd_latency_monitor_log *log_data) +{ + printf("-Latency Monitor/C3 Log Page Data- \n"); + printf(" Controller : %s\n", dev->name); + int i, j; + int pos = 0; + char ts_buf[128]; + + printf(" Feature Status 0x%x \n", + log_data->feature_status); + printf(" Active Bucket Timer %d min \n", + C0_ACTIVE_BUCKET_TIMER_INCREMENT * + le16_to_cpu(log_data->active_bucket_timer)); + printf(" Active Bucket Timer Threshold %d min \n", + C0_ACTIVE_BUCKET_TIMER_INCREMENT * + le16_to_cpu(log_data->active_bucket_timer_threshold)); + printf(" Active Threshold A %d ms \n", + C0_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_a+1)); + printf(" Active Threshold B %d ms \n", + C0_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_b+1)); + printf(" Active Threshold C %d ms \n", + C0_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_c+1)); + printf(" Active Threshold D %d ms \n", + C0_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_d+1)); + printf(" Active Latency Minimum Window %d ms \n", + C0_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(" Read Write Deallocate/Trim \n"); + for (i = 0; i <= 3; i++) { + printf(" Active Latency Mode: Bucket %d %27d %27d %27d \n", + i, + log_data->active_latency_config & (1 << pos), + log_data->active_latency_config & (1 << pos), + log_data->active_latency_config & (1 << pos)); + } + printf("\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][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 <= 3; i++) { + printf(" Active Measured Latency: Bucket %d %27d ms %27d ms %27d ms \n", + i, + le16_to_cpu(log_data->active_measured_latency[i][READ]), + le16_to_cpu(log_data->active_measured_latency[i][WRITE]), + le16_to_cpu(log_data->active_measured_latency[i][TRIM])); + } + + for (i = 0; i <= 3; i++) { + printf(" Active Latency Time Stamp: Bucket %d ", i); + for (j = 0; j <= 2; 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][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 <= 3; i++) { + printf(" Static Measured Latency: Bucket %d %27d ms %27d ms %27d ms \n", + i, + le16_to_cpu(log_data->static_measured_latency[i][READ]), + le16_to_cpu(log_data->static_measured_latency[i][WRITE]), + le16_to_cpu(log_data->static_measured_latency[i][TRIM])); + } + + for (i = 0; i <= 3; i++) { + printf(" Static Latency Time Stamp: Bucket %d ", i); + for (j = 0; j <= 2; 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 ocp_print_C3_log_json(struct ssd_latency_monitor_log *log_data) +{ + int i, j; + int pos = 0; + char buf[128]; + char ts_buf[128]; + char *operation[3] = {"Read", "Write", "Trim"}; + struct json_object *root; + 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", + C0_ACTIVE_BUCKET_TIMER_INCREMENT * + le16_to_cpu(log_data->active_bucket_timer)); + json_object_add_value_uint(root, "Active Bucket Timer Threshold", + C0_ACTIVE_BUCKET_TIMER_INCREMENT * + le16_to_cpu(log_data->active_bucket_timer_threshold)); + json_object_add_value_uint(root, "Active Threshold A", + C0_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_a+1)); + json_object_add_value_uint(root, "Active Threshold B", + C0_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_b+1)); + json_object_add_value_uint(root, "Active Threshold C", + C0_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_c+1)); + json_object_add_value_uint(root, "Active Threshold D", + C0_ACTIVE_THRESHOLD_INCREMENT * + le16_to_cpu(log_data->active_threshold_d+1)); + json_object_add_value_uint(root, "Active Lantency Minimum Window", + C0_MINIMUM_WINDOW_INCREMENT * + le16_to_cpu(log_data->active_latency_min_window)); + json_object_add_value_uint(root, "Active Latency Stamp Units", + le16_to_cpu(log_data->active_latency_stamp_units)); + 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)); + + for (i = 0; i <= 3; i++) { + struct json_object *bucket; + bucket = json_create_object(); + sprintf(buf, "Active Latency Mode: Bucket %d", i); + for (j = 0; j <= 2; j++) { + json_object_add_value_uint(bucket, operation[j], + log_data->active_latency_config & (1 << pos)); + } + json_object_add_value_object(root, buf, bucket); + } + for (i = 0; i <= 3; i++) { + struct json_object *bucket; + bucket = json_create_object(); + sprintf(buf, "Active Bucket Counter: Bucket %d", i); + for (j = 0; j <= 2; j++) { + json_object_add_value_uint(bucket, operation[j], + le32_to_cpu(log_data->active_bucket_counter[i][j])); + } + json_object_add_value_object(root, buf, bucket); + } + for (i = 0; i <= 3; i++) { + struct json_object *bucket; + bucket = json_create_object(); + sprintf(buf, "Active Measured Latency: Bucket %d", i); + for (j = 0; j <= 2; j++) { + json_object_add_value_uint(bucket, operation[j], + le16_to_cpu(log_data->active_measured_latency[i][j])); + } + json_object_add_value_object(root, buf, bucket); + } + for (i = 0; i <= 3; i++) { + struct json_object *bucket; + bucket = json_create_object(); + sprintf(buf, "Active Latency Time Stamp: Bucket %d", i); + for (j = 0; j <= 2; j++) { + if (le64_to_cpu(log_data->active_latency_timestamp[i][j]) == -1) + json_object_add_value_string(bucket, operation[j], "NA"); + else { + convert_ts(le64_to_cpu(log_data->active_latency_timestamp[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 <= 3; i++) { + struct json_object *bucket; + bucket = json_create_object(); + sprintf(buf, "Static Bucket Counter: Bucket %d", i); + for (j = 0; j <= 2; j++) { + json_object_add_value_uint(bucket, operation[j], + le32_to_cpu(log_data->static_bucket_counter[i][j])); + } + json_object_add_value_object(root, buf, bucket); + } + for (i = 0; i <= 3; i++) { + struct json_object *bucket; + bucket = json_create_object(); + sprintf(buf, "Static Measured Latency: Bucket %d", i); + for (j = 0; j <= 2; j++) { + json_object_add_value_uint(bucket, operation[j], + le16_to_cpu(log_data->static_measured_latency[i][j])); + } + json_object_add_value_object(root, buf, bucket); + } + for (i = 0; i <= 3; i++) { + struct json_object *bucket; + bucket = json_create_object(); + sprintf(buf, "Static Latency Time Stamp: Bucket %d", i); + for (j = 0; j <= 2; j++) { + if (le64_to_cpu(log_data->static_latency_timestamp[i][j]) == -1) + json_object_add_value_string(bucket, operation[j], "NA"); + else { + convert_ts(le64_to_cpu(log_data->static_latency_timestamp[i][j]), ts_buf); + json_object_add_value_string(bucket, operation[j], ts_buf); + } + } + json_object_add_value_object(root, buf, bucket); + } + + json_print_object(root, NULL); + printf("\n"); + + json_free_object(root); +} + +static int get_c3_log_page(struct nvme_dev *dev, char *format) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + int i; + struct ssd_latency_monitor_log *log_data; + + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : OCP : invalid output format\n"); + return fmt; + } + + if ((data = (__u8 *) malloc(sizeof(__u8) * C3_LATENCY_MON_LOG_BUF_LEN)) == NULL) { + 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 == 0) { + 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]) { + fprintf(stderr,"ERROR : OCP : Unknown GUID in C3 Log Page data\n"); + int j; + 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; + } + } 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; +} + +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); +} diff --git a/plugins/ocp/ocp-nvme.h b/plugins/ocp/ocp-nvme.h new file mode 100644 index 0000000..c20646a --- /dev/null +++ b/plugins/ocp/ocp-nvme.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2022 Meta Platforms, Inc. + * + * Authors: Arthur Shau , + * Wei Zhang , + * Venkat Ramesh + */ +#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", ocp_smart_add_log) + ENTRY("latency-monitor-log", "Get Latency Monitor Log Page", + ocp_latency_monitor_log) + ENTRY("clear-fw-activate-history", "Clear firmware update history log", + clear_fw_update_history) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/ocp/ocp-utils.c b/plugins/ocp/ocp-utils.c new file mode 100644 index 0000000..9294c05 --- /dev/null +++ b/plugins/ocp/ocp-utils.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include "ocp-utils.h" + +const unsigned char ocp_uuid[NVME_UUID_LEN] = { + 0x6f, 0xbe, 0x56, 0x8f, 0x99, 0x29, 0x1d, 0xa2, 0x94, 0x47, + 0x94, 0xe0, 0x5b, 0xd5, 0x94, 0xc1 }; + +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..44d0af4 --- /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 ponter 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..4bcfbf6 --- /dev/null +++ b/plugins/scaleflux/sfx-nvme.c @@ -0,0 +1,1213 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-print.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; +}; + +struct nvme_capacity_info { + __u64 lba_sec_sz; + __u64 phy_sec_sz; + __u64 used_space; + __u64 free_space; +}; + +struct __attribute__((packed)) nvme_additional_smart_log_item { + __u8 key; + __u8 _kp[2]; + __u8 norm; + __u8 _np; + union __attribute__((packed)) { + __u8 raw[6]; + struct __attribute__((packed)) wear_level { + __le16 min; + __le16 max; + __le16 avg; + } wear_level; + struct __attribute__((packed)) thermal_throttle { + __u8 pct; + __u32 count; + } thermal_throttle; + } ; + __u8 _rp; +} ; + +struct nvme_additional_smart_log { + struct nvme_additional_smart_log_item program_fail_cnt; + struct nvme_additional_smart_log_item erase_fail_cnt; + struct nvme_additional_smart_log_item wear_leveling_cnt; + struct nvme_additional_smart_log_item e2e_err_cnt; + struct nvme_additional_smart_log_item crc_err_cnt; + struct nvme_additional_smart_log_item timed_workload_media_wear; + struct nvme_additional_smart_log_item timed_workload_host_reads; + struct nvme_additional_smart_log_item timed_workload_timer; + struct nvme_additional_smart_log_item thermal_throttle_status; + struct nvme_additional_smart_log_item retry_buffer_overflow_cnt; + struct nvme_additional_smart_log_item pll_lock_loss_cnt; + struct nvme_additional_smart_log_item nand_bytes_written; + struct nvme_additional_smart_log_item host_bytes_written; + struct nvme_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 == 0 ? 0 : nvme_submit_admin_passthru(fd, &cmd, NULL); +} +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 __attribute__((__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 __attribute__((__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 __attribute__((__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 != 0) { + 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)); +} + +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 provisoned 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, cancled; 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, "Cancled.\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 != 0) + 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 != 0) { + 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; + +} diff --git a/plugins/scaleflux/sfx-nvme.h b/plugins/scaleflux/sfx-nvme.h new file mode 100644 index 0000000..0b95d92 --- /dev/null +++ b/plugins/scaleflux/sfx-nvme.h @@ -0,0 +1,24 @@ +/* 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) + ) +); + +#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 + */ + + +#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..1bd30a5 --- /dev/null +++ b/plugins/seagate/seagate-nvme.c @@ -0,0 +1,2042 @@ +// 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 + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-print.h" +#include + +#define CREATE_CMD + +#include "seagate-nvme.h" +#include "seagate-diag.h" + + +/*************************************** + * Command for "log-pages-supp" + ***************************************/ +static char *log_pages_supp_print(__u32 pageID) +{ + switch (pageID) { + case 0x01: + return "ERROR_INFORMATION"; + break; + case 0x02: + return "SMART_INFORMATION"; + break; + case 0x03: + return "FW_SLOT_INFORMATION"; + break; + case 0x04: + return "CHANGED_NAMESPACE_LIST"; + break; + case 0x05: + return "COMMANDS_SUPPORTED_AND_EFFECTS"; + break; + case 0x06: + return "DEVICE_SELF_TEST"; + break; + case 0x07: + return "TELEMETRY_HOST_INITIATED"; + break; + case 0x08: + return "TELEMETRY_CONTROLLER_INITIATED"; + break; + case 0xC0: + return "VS_MEDIA_SMART_LOG"; + break; + case 0xC1: + return "VS_DEBUG_LOG1"; + break; + case 0xC2: + return "VS_SEC_ERROR_LOG_PAGE"; + break; + case 0xC3: + return "VS_LIFE_TIME_DRIVE_HISTORY"; + break; + case 0xC4: + return "VS_EXTENDED_SMART_INFO"; + break; + case 0xC5: + return "VS_LIST_SUPPORTED_LOG_PAGE"; + break; + case 0xC6: + return "VS_POWER_MONITOR_LOG_PAGE"; + break; + case 0xC7: + return "VS_CRITICAL_EVENT_LOG_PAGE"; + break; + case 0xC8: + return "VS_RECENT_DRIVE_HISTORY"; + break; + case 0xC9: + return "VS_SEC_ERROR_LOG_PAGE"; + break; + case 0xCA: + return "VS_LIFE_TIME_DRIVE_HISTORY"; + break; + case 0xCB: + return "VS_PCIE_ERROR_LOG_PAGE"; + break; + case 0xCF: + return "DRAM Supercap SMART Attributes"; + break; + case 0xD6: + return "VS_OEM2_WORK_LOAD"; + break; + case 0xD7: + return "VS_OEM2_FW_SECURITY"; + break; + case 0xD8: + return "VS_OEM2_REVISION"; + break; + default: + return "UNKNOWN"; + break; + } +} + +static 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 == 0) { + 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"; + break; + case VS_ATTR_ID_REALLOCATED_SECTOR_COUNT: + return "Bad NAND block count"; + break; + case VS_ATTR_ID_POWER_ON_HOURS: + return "Power On Hours"; + break; + case VS_ATTR_ID_POWER_FAIL_EVENT_COUNT: + return "Power Fail Event Count"; + break; + case VS_ATTR_ID_DEVICE_POWER_CYCLE_COUNT: + return "Device Power Cycle Count"; + break; + case VS_ATTR_ID_RAW_READ_ERROR_RATE: + return "Raw Read Error Count"; + break; + case VS_ATTR_ID_GROWN_BAD_BLOCK_COUNT: + return "Bad NAND block count"; + break; + case VS_ATTR_ID_END_2_END_CORRECTION_COUNT: + return "SSD End to end correction counts"; + break; + case VS_ATTR_ID_MIN_MAX_WEAR_RANGE_COUNT: + return "User data erase counts"; + break; + case VS_ATTR_ID_REFRESH_COUNT: + return "Refresh count"; + break; + case VS_ATTR_ID_BAD_BLOCK_COUNT_USER: + return "User data erase fail count"; + break; + case VS_ATTR_ID_BAD_BLOCK_COUNT_SYSTEM: + return "System area erase fail count"; + break; + case VS_ATTR_ID_THERMAL_THROTTLING_STATUS: + return "Thermal throttling status and count"; + break; + case VS_ATTR_ID_ALL_PCIE_CORRECTABLE_ERROR_COUNT: + return "PCIe Correctable Error count"; + break; + case VS_ATTR_ID_ALL_PCIE_UNCORRECTABLE_ERROR_COUNT: + return "PCIe Uncorrectable Error count"; + break; + case VS_ATTR_ID_INCOMPLETE_SHUTDOWN_COUNT: + return "Incomplete shutdowns"; + break; + case VS_ATTR_ID_GB_ERASED_LSB: + return "LSB of Flash GB erased"; + break; + case VS_ATTR_ID_GB_ERASED_MSB: + return "MSB of Flash GB erased"; + break; + case VS_ATTR_ID_LIFETIME_DEVSLEEP_EXIT_COUNT: + return "LIFETIME_DEV_SLEEP_EXIT_COUNT"; + break; + case VS_ATTR_ID_LIFETIME_ENTERING_PS4_COUNT: + return "LIFETIME_ENTERING_PS4_COUNT"; + break; + case VS_ATTR_ID_LIFETIME_ENTERING_PS3_COUNT: + return "LIFETIME_ENTERING_PS3_COUNT"; + break; + case VS_ATTR_ID_RETIRED_BLOCK_COUNT: + return "Retired block count"; + break; + case VS_ATTR_ID_PROGRAM_FAILURE_COUNT: + return "Program fail count"; + break; + case VS_ATTR_ID_ERASE_FAIL_COUNT: + return "Erase Fail Count"; + break; + case VS_ATTR_ID_AVG_ERASE_COUNT: + return "System data % used"; + break; + case VS_ATTR_ID_UNEXPECTED_POWER_LOSS_COUNT: + return "Unexpected power loss count"; + break; + case VS_ATTR_ID_WEAR_RANGE_DELTA: + return "Wear range delta"; + break; + case VS_ATTR_ID_SATA_INTERFACE_DOWNSHIFT_COUNT: + return "PCIE_INTF_DOWNSHIFT_COUNT"; + break; + case VS_ATTR_ID_END_TO_END_CRC_ERROR_COUNT: + return "E2E_CRC_ERROR_COUNT"; + break; + case VS_ATTR_ID_UNCORRECTABLE_READ_ERRORS: + return "Uncorrectable Read Error Count"; + break; + case VS_ATTR_ID_MAX_LIFE_TEMPERATURE: + return "Max lifetime temperature"; + break; + case VS_ATTR_ID_RAISE_ECC_CORRECTABLE_ERROR_COUNT: + return "RAIS_ECC_CORRECT_ERR_COUNT"; + break; + case VS_ATTR_ID_UNCORRECTABLE_RAISE_ERRORS: + return "Uncorrectable RAISE error count"; + break; + case VS_ATTR_ID_DRIVE_LIFE_PROTECTION_STATUS: + return "DRIVE_LIFE_PROTECTION_STATUS"; + break; + case VS_ATTR_ID_REMAINING_SSD_LIFE: + return "Remaining SSD life"; + break; + case VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB: + return "LSB of Physical (NAND) bytes written"; + break; + case VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB: + return "MSB of Physical (NAND) bytes written"; + break; + case VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB: + return "LSB of Physical (HOST) bytes written"; + break; + case VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB: + return "MSB of Physical (HOST) bytes written"; + break; + case VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB: + return "LSB of Physical (NAND) bytes read"; + break; + case VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB: + return "MSB of Physical (NAND) bytes read"; + break; + case VS_ATTR_ID_FREE_SPACE: + return "Free Space"; + break; + case VS_ATTR_ID_TRIM_COUNT_LSB: + return "LSB of Trim count"; + break; + case VS_ATTR_ID_TRIM_COUNT_MSB: + return "MSB of Trim count"; + break; + case VS_ATTR_ID_OP_PERCENTAGE: + return "OP percentage"; + break; + case VS_ATTR_ID_MAX_SOC_LIFE_TEMPERATURE: + return "Max lifetime SOC temperature"; + break; + default: + return "Un-Known"; + } +} + +static __u64 smart_attribute_vs(__u16 verNo, SmartVendorSpecific attr) +{ + __u64 val = 0; + vendor_smart_attribute_data *attrVendor; + + /** + * These are all Vendor A specific attributes. + */ + if (verNo >= EXTENDED_SMART_VERSION_VENDOR1) { + attrVendor = (vendor_smart_attribute_data *)&attr; + memcpy(&val, &(attrVendor->LSDword), sizeof(val)); + return val; + } else + return le32_to_cpu(attr.Raw0_3); +} + +static void print_smart_log(__u16 verNo, SmartVendorSpecific attr, int lastAttr) +{ + static __u64 lsbGbErased = 0, msbGbErased = 0, lsbLifWrtToFlash = 0, msbLifWrtToFlash = 0, + lsbLifWrtFrmHost = 0, msbLifWrtFrmHost = 0, lsbLifRdToHost = 0, msbLifRdToHost = 0, lsbTrimCnt = 0, msbTrimCnt = 0; + char buf[40] = {0}; + char strBuf[35] = {0}; + int hideAttr = 0; + + if (attr.AttributeNumber == VS_ATTR_ID_GB_ERASED_LSB) { + lsbGbErased = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_GB_ERASED_MSB) { + msbGbErased = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB) { + lsbLifWrtToFlash = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB) { + msbLifWrtToFlash = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB) { + lsbLifWrtFrmHost = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB) { + msbLifWrtFrmHost = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB) { + lsbLifRdToHost = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB) { + msbLifRdToHost = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_TRIM_COUNT_LSB) { + lsbTrimCnt = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if (attr.AttributeNumber == VS_ATTR_ID_TRIM_COUNT_MSB) { + msbTrimCnt = smart_attribute_vs(verNo, attr); + hideAttr = 1; + } + + if ((attr.AttributeNumber != 0) && (hideAttr != 1)) { + printf("%-40s", print_ext_smart_id(attr.AttributeNumber)); + printf("%-15d", attr.AttributeNumber); + printf(" 0x%016"PRIx64"\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 != 0) { + json_object_add_value_string(lbaf, "attribute_name", print_ext_smart_id(ExtdSMARTInfo->vendorData[index].AttributeNumber)); + json_object_add_value_int(lbaf, "attribute_id", ExtdSMARTInfo->vendorData[index].AttributeNumber); + json_object_add_value_int(lbaf, "attribute_value", smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index])); + json_array_add_value_object(lbafs, lbaf); + + if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_GB_ERASED_LSB) + lsbGbErased = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_GB_ERASED_MSB) + msbGbErased = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB) + lsbLifWrtToFlash = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB) + msbLifWrtToFlash = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB) + lsbLifWrtFrmHost = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB) + msbLifWrtFrmHost = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB) + lsbLifRdToHost = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB) + msbLifRdToHost = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_TRIM_COUNT_LSB) + lsbTrimCnt = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + + if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_TRIM_COUNT_MSB) + msbTrimCnt = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]); + } + } + + struct json_object *lbaf = json_create_object(); + + json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_GB_ERASED_LSB) + 7)); + + json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_GB_ERASED_MSB << 8 | VS_ATTR_ID_GB_ERASED_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbGbErased, (uint64_t)lsbGbErased); + json_object_add_value_string(lbaf, "attribute_value", buf); + json_array_add_value_object(lbafs, lbaf); + + + lbaf = json_create_object(); + + json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB) + 7)); + + json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB << 8 | VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifWrtToFlash, (uint64_t)lsbLifWrtToFlash); + json_object_add_value_string(lbaf, "attribute_value", buf); + json_array_add_value_object(lbafs, lbaf); + + + lbaf = json_create_object(); + + json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB) + 7)); + + json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB << 8 | VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifWrtFrmHost, (uint64_t)lsbLifWrtFrmHost); + json_object_add_value_string(lbaf, "attribute_value", buf); + json_array_add_value_object(lbafs, lbaf); + + + lbaf = json_create_object(); + + json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB) + 7)); + + json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB << 8 | VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifRdToHost, (uint64_t)lsbLifRdToHost); + json_object_add_value_string(lbaf, "attribute_value", buf); + json_array_add_value_object(lbafs, lbaf); + + + lbaf = json_create_object(); + + json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_TRIM_COUNT_LSB) + 7)); + + json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_TRIM_COUNT_MSB << 8 | VS_ATTR_ID_TRIM_COUNT_LSB); + + sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbTrimCnt, (uint64_t)lsbTrimCnt); + json_object_add_value_string(lbaf, "attribute_value", buf); + json_array_add_value_object(lbafs, lbaf); +} + +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 deternine 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) == 0) { + + 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; + 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 = 0; + __u32 uncorrectPcieEc = 0; + correctPcieEc = pcieErrorLog.BadDllpErrCnt + pcieErrorLog.BadTlpErrCnt + + pcieErrorLog.RcvrErrCnt + pcieErrorLog.ReplayTOErrCnt + + pcieErrorLog.ReplayNumRolloverErrCnt; + + uncorrectPcieEc = pcieErrorLog.FCProtocolErrCnt + pcieErrorLog.DllpProtocolErrCnt + + pcieErrorLog.CmpltnTOErrCnt + pcieErrorLog.RcvrQOverflowErrCnt + + pcieErrorLog.UnexpectedCplTlpErrCnt + pcieErrorLog.CplTlpURErrCnt + + pcieErrorLog.CplTlpCAErrCnt + pcieErrorLog.ReqCAErrCnt + + pcieErrorLog.ReqURErrCnt + pcieErrorLog.EcrcErrCnt + + pcieErrorLog.MalformedTlpErrCnt + pcieErrorLog.CplTlpPoisonedErrCnt + + pcieErrorLog.MemRdTlpPoisonedErrCnt; + + printf("%-45s : %u\n", "PCIe Correctable Error Count", correctPcieEc); + printf("%-45s : %u\n", "PCIe Un-Correctable Error Count", uncorrectPcieEc); + printf("%-45s : %u\n", "Unsupported Request Error Status (URES)", pcieErrorLog.ReqURErrCnt); + printf("%-45s : %u\n", "ECRC Error Status (ECRCES)", pcieErrorLog.EcrcErrCnt); + printf("%-45s : %u\n", "Malformed TLP Status (MTS)", pcieErrorLog.MalformedTlpErrCnt); + printf("%-45s : %u\n", "Receiver Overflow Status (ROS)", pcieErrorLog.RcvrQOverflowErrCnt); + printf("%-45s : %u\n", "Unexpected Completion Status(UCS)", pcieErrorLog.UnexpectedCplTlpErrCnt); + printf("%-45s : %u\n", "Completion Timeout Status (CTS)", pcieErrorLog.CmpltnTOErrCnt); + printf("%-45s : %u\n", "Flow Control Protocol Error Status (FCPES)", pcieErrorLog.FCProtocolErrCnt); + printf("%-45s : %u\n", "Poisoned TLP Status (PTS)", pcieErrorLog.MemRdTlpPoisonedErrCnt); + printf("%-45s : %u\n", "Data Link Protocol Error Status(DLPES)", pcieErrorLog.DllpProtocolErrCnt); + printf("%-45s : %u\n", "Replay Timer Timeout Status(RTS)", pcieErrorLog.ReplayTOErrCnt); + printf("%-45s : %u\n", "Replay_NUM Rollover Status(RRS)", pcieErrorLog.ReplayNumRolloverErrCnt); + printf("%-45s : %u\n", "Bad DLLP Status (BDS)", pcieErrorLog.BadDllpErrCnt); + printf("%-45s : %u\n", "Bad TLP Status (BTS)", pcieErrorLog.BadTlpErrCnt); + printf("%-45s : %u\n", "Receiver Error Status (RES)", pcieErrorLog.RcvrErrCnt); + printf("%-45s : %u\n", "Cpl TLP Unsupported Request Error Count", pcieErrorLog.CplTlpURErrCnt); + printf("%-45s : %u\n", "Cpl TLP Completion Abort Error Count", pcieErrorLog.CplTlpCAErrCnt); + printf("%-45s : %u\n", "Cpl TLP Poisoned Error Count", pcieErrorLog.CplTlpPoisonedErrCnt); + printf("%-45s : %u\n", "Request Completion Abort Error Count", pcieErrorLog.ReqCAErrCnt); + printf("%-45s : %s\n", "Advisory Non-Fatal Error Status(ANFES)", "Not Supported"); + printf("%-45s : %s\n", "Completer Abort Status (CAS)", "Not Supported"); +} + +static void json_vs_pcie_error_log(pcie_error_log_page pcieErrorLog) +{ + struct json_object *root; + root = json_create_object(); + __u32 correctPcieEc = 0; + __u32 uncorrectPcieEc = 0; + correctPcieEc = pcieErrorLog.BadDllpErrCnt + pcieErrorLog.BadTlpErrCnt + + pcieErrorLog.RcvrErrCnt + pcieErrorLog.ReplayTOErrCnt + + pcieErrorLog.ReplayNumRolloverErrCnt; + + uncorrectPcieEc = pcieErrorLog.FCProtocolErrCnt + pcieErrorLog.DllpProtocolErrCnt + + pcieErrorLog.CmpltnTOErrCnt + pcieErrorLog.RcvrQOverflowErrCnt + + pcieErrorLog.UnexpectedCplTlpErrCnt + pcieErrorLog.CplTlpURErrCnt + + pcieErrorLog.CplTlpCAErrCnt + pcieErrorLog.ReqCAErrCnt + + pcieErrorLog.ReqURErrCnt + pcieErrorLog.EcrcErrCnt + + pcieErrorLog.MalformedTlpErrCnt + pcieErrorLog.CplTlpPoisonedErrCnt + + pcieErrorLog.MemRdTlpPoisonedErrCnt; + + json_object_add_value_int(root, "PCIe Correctable Error Count", correctPcieEc); + json_object_add_value_int(root, "PCIe Un-Correctable Error Count", uncorrectPcieEc); + json_object_add_value_int(root, "Unsupported Request Error Status (URES)", pcieErrorLog.ReqURErrCnt); + json_object_add_value_int(root, "ECRC Error Status (ECRCES)", pcieErrorLog.EcrcErrCnt); + json_object_add_value_int(root, "Malformed TLP Status (MTS)", pcieErrorLog.MalformedTlpErrCnt); + json_object_add_value_int(root, "Receiver Overflow Status (ROS)", pcieErrorLog.RcvrQOverflowErrCnt); + json_object_add_value_int(root, "Unexpected Completion Status(UCS)", pcieErrorLog.UnexpectedCplTlpErrCnt); + json_object_add_value_int(root, "Completion Timeout Status (CTS)", pcieErrorLog.CmpltnTOErrCnt); + json_object_add_value_int(root, "Flow Control Protocol Error Status (FCPES)", pcieErrorLog.FCProtocolErrCnt); + json_object_add_value_int(root, "Poisoned TLP Status (PTS)", pcieErrorLog.MemRdTlpPoisonedErrCnt); + json_object_add_value_int(root, "Data Link Protocol Error Status(DLPES)", pcieErrorLog.DllpProtocolErrCnt); + json_object_add_value_int(root, "Replay Timer Timeout Status(RTS)", pcieErrorLog.ReplayTOErrCnt); + json_object_add_value_int(root, "Replay_NUM Rollover Status(RRS)", pcieErrorLog.ReplayNumRolloverErrCnt); + json_object_add_value_int(root, "Bad DLLP Status (BDS)", pcieErrorLog.BadDllpErrCnt); + json_object_add_value_int(root, "Bad TLP Status (BTS)", pcieErrorLog.BadTlpErrCnt); + json_object_add_value_int(root, "Receiver Error Status (RES)", pcieErrorLog.RcvrErrCnt); + json_object_add_value_int(root, "Cpl TLP Unsupported Request Error Count", pcieErrorLog.CplTlpURErrCnt); + json_object_add_value_int(root, "Cpl TLP Completion Abort Error Count", pcieErrorLog.CplTlpCAErrCnt); + json_object_add_value_int(root, "Cpl TLP Poisoned Error Count", pcieErrorLog.CplTlpPoisonedErrCnt); + json_object_add_value_int(root, "Request Completion Abort Error Count", pcieErrorLog.ReqCAErrCnt); + json_print_object(root, NULL); +} + +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 Histry :\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; + 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; + root = json_create_object(); + __u32 i; + + char buf[80]; + + struct json_object *historyLogPage; + 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; + 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 Histry 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) == 0) { + 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) == 0) { + 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 " \ + "state of the controller at the time the command is processed. " \ + "0 - controller shall not update the Telemetry Host Initiated Data."; + const char *raw = "output in raw format"; + 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 == 0) { + 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 == 0) + 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", __FUNCTION__); +} + + +static int vs_internal_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Capture the Telemetry Controller-Initiated Data in " \ + "binary format"; + const char *namespace_id = "desired namespace"; + + const char *file = "dump file"; + struct nvme_dev *dev; + int err, dump_fd; + int flags = O_WRONLY | O_CREAT; + int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; + struct nvme_temetry_log_hdr tele_log; + __le64 offset = 0; + __u16 log_id; + int blkCnt, maxBlk = 0, blksToGet; + unsigned char *log; + + struct config { + __u32 namespace_id; + char *file; + }; + + struct config cfg = { + .namespace_id = 0xffffffff, + .file = "", + }; + + OPT_ARGS(opts) = { + OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id), + OPT_FILE("dump-file", 'f', &cfg.file, file), + OPT_END() + }; + + 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 == 0) { + 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 + */ + +#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/shannon/shannon-nvme.c b/plugins/shannon/shannon-nvme.c new file mode 100644 index 0000000..424b3f7 --- /dev/null +++ b/plugins/shannon/shannon-nvme.c @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include + +#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" + +typedef enum { + PROGRAM_FAIL_CNT, + ERASE_FAIL_CNT, + WEARLEVELING_COUNT, + E2E_ERR_CNT, + CRC_ERR_CNT, + TIME_WORKLOAD_MEDIA_WEAR, + TIME_WORKLOAD_HOST_READS, + TIME_WORKLOAD_TIMER, + THERMAL_THROTTLE, + RETRY_BUFFER_OVERFLOW, + PLL_LOCK_LOSS, + NAND_WRITE, + HOST_WRITE, + SRAM_ERROR_CNT, + ADD_SMART_ITEMS, +}addtional_smart_items; + +#pragma pack(push,1) +struct nvme_shannon_smart_log_item { + __u8 rsv1[3]; + __u8 norm; + __u8 rsv2; + union { + __u8 item_val[6]; + struct wear_level { + __le16 min; + __le16 max; + __le16 avg; + } wear_level ; + struct thermal_throttle { + __u8 st; + __u32 count; + } thermal_throttle; + }; + __u8 _resv; +}; +#pragma pack(pop) + +struct nvme_shannon_smart_log { + struct nvme_shannon_smart_log_item items[ADD_SMART_ITEMS]; + __u8 vend_spec_resv; +}; + +static void show_shannon_smart_log(struct nvme_shannon_smart_log *smart, + unsigned int nsid, const char *devname) +{ + printf("Additional Smart Log for NVME device:%s namespace-id:%x\n", + devname, nsid); + printf("key normalized value\n"); + printf("program_fail_count : %3d%% %"PRIu64"\n", + smart->items[PROGRAM_FAIL_CNT].norm, + int48_to_long(smart->items[PROGRAM_FAIL_CNT].item_val)); + printf("erase_fail_count : %3d%% %"PRIu64"\n", + smart->items[ERASE_FAIL_CNT].norm, + int48_to_long(smart->items[ERASE_FAIL_CNT].item_val)); + printf("wear_leveling : %3d%% min: %u, max: %u, avg: %u\n", + smart->items[WEARLEVELING_COUNT].norm, + le16_to_cpu(smart->items[WEARLEVELING_COUNT].wear_level.min), + le16_to_cpu(smart->items[WEARLEVELING_COUNT].wear_level.max), + le16_to_cpu(smart->items[WEARLEVELING_COUNT].wear_level.avg)); + printf("end_to_end_error_detection_count: %3d%% %"PRIu64"\n", + smart->items[E2E_ERR_CNT].norm, + int48_to_long(smart->items[E2E_ERR_CNT].item_val)); + printf("crc_error_count : %3d%% %"PRIu64"\n", + smart->items[CRC_ERR_CNT].norm, + int48_to_long(smart->items[CRC_ERR_CNT].item_val)); + printf("timed_workload_media_wear : %3d%% %.3f%%\n", + smart->items[TIME_WORKLOAD_MEDIA_WEAR].norm, + ((float)int48_to_long(smart->items[TIME_WORKLOAD_MEDIA_WEAR].item_val)) / 1024); + printf("timed_workload_host_reads : %3d%% %"PRIu64"%%\n", + smart->items[TIME_WORKLOAD_HOST_READS].norm, + int48_to_long(smart->items[TIME_WORKLOAD_HOST_READS].item_val)); + printf("timed_workload_timer : %3d%% %"PRIu64" min\n", + smart->items[TIME_WORKLOAD_TIMER].norm, + int48_to_long(smart->items[TIME_WORKLOAD_TIMER].item_val)); + printf("thermal_throttle_status : %3d%% CurTTSta: %u%%, TTCnt: %u\n", + smart->items[THERMAL_THROTTLE].norm, + smart->items[THERMAL_THROTTLE].thermal_throttle.st, + smart->items[THERMAL_THROTTLE].thermal_throttle.count); + printf("retry_buffer_overflow_count : %3d%% %"PRIu64"\n", + smart->items[RETRY_BUFFER_OVERFLOW].norm, + int48_to_long(smart->items[RETRY_BUFFER_OVERFLOW].item_val)); + printf("pll_lock_loss_count : %3d%% %"PRIu64"\n", + smart->items[PLL_LOCK_LOSS].norm, + int48_to_long(smart->items[PLL_LOCK_LOSS].item_val)); + printf("nand_bytes_written : %3d%% sectors: %"PRIu64"\n", + smart->items[NAND_WRITE].norm, + int48_to_long(smart->items[NAND_WRITE].item_val)); + printf("host_bytes_written : %3d%% sectors: %"PRIu64"\n", + smart->items[HOST_WRITE].norm, + int48_to_long(smart->items[HOST_WRITE].item_val)); + printf("sram_error_count : %3d%% %"PRIu64"\n", + smart->items[RETRY_BUFFER_OVERFLOW].norm, + int48_to_long(smart->items[SRAM_ERROR_CNT].item_val)); +} + + +static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + struct nvme_shannon_smart_log smart_log; + 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 "\ + "specified controller. Operating parameters are grouped "\ + "and identified by Feature Identifiers; each Feature "\ + "Identifier contains one or more attributes that may affect "\ + "behavior of the feature. Each Feature has three possible "\ + "settings: default, saveable, and current. If a Feature is "\ + "saveable, it may be modified by set-feature. Default values "\ + "are vendor-specific and not changeable. Use set-feature to "\ + "change saveable Features.\n\n"\ + "Available additional feature id:\n"\ + "0x02: Shannon power management\n"; + const char *raw = "show infos in binary format"; + const char *namespace_id = "identifier of desired namespace"; + const char *feature_id = "hexadecimal feature name"; + const char *sel = "[0-3]: curr./default/saved/supp."; + const char *data_len = "buffer len (if) data is returned"; + const char *cdw11 = "dword 11 for interrupt vector config"; + const char *human_readable = "show infos in readable format"; + 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) { +#if 0 + printf("get-feature:0x%02x (%s), %s value: %#08x\n", cfg.feature_id, + nvme_feature_to_string(cfg.feature_id), + nvme_select_to_string(cfg.sel), result); + if (cfg.human_readable) + nvme_feature_show_fields(cfg.feature_id, result, buf); + else { + if (buf) { + if (!cfg.raw_binary) + d(buf, cfg.data_len, 16, 1); + else + d_raw(buf, cfg.data_len); + } + } +#endif + } else 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 "\ + "current operating parameters of the controller. Operating "\ + "parameters are grouped and identified by Feature "\ + "Identifiers. Feature settings can be applied to the entire "\ + "controller and all associated namespaces, or to only a few "\ + "namespace(s) associated with the controller. Default values "\ + "for each Feature are vendor-specific and may not be modified."\ + "Use get-feature to determine which Features are supported by "\ + "the controller and are saveable/changeable.\n\n"\ + "Available additional feature id:\n"\ + "0x02: Shannon power management\n"; + const char *namespace_id = "desired namespace"; + const char *feature_id = "hex feature name (required)"; + const char *data_len = "buffer length if data required"; + const char *data = "optional file for feature data (default stdin)"; + const char *value = "new value of feature (required)"; + const char *save = "specifies that the controller shall save the attribute"; + int 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 0 + printf("set-feature:%02x (%s), value:%#08x\n", cfg.feature_id, + nvme_feature_to_string(cfg.feature_id), cfg.value); +#endif + 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..bca13bb --- /dev/null +++ b/plugins/solidigm/meson.build @@ -0,0 +1,7 @@ +sources += [ + 'plugins/solidigm/solidigm-smart.c', + 'plugins/solidigm/solidigm-garbage-collection.c', + 'plugins/solidigm/solidigm-latency-tracking.c', + 'plugins/solidigm/solidigm-telemetry.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..8e2eccc --- /dev/null +++ b/plugins/solidigm/solidigm-garbage-collection.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include +#include +#include +#include +#include +#include + +#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" + +typedef struct __attribute__((packed)) gc_item { + __le32 timer_type; + __le64 timestamp; +} gc_item_t; + +#define VU_GC_MAX_ITEMS 100 +typedef struct garbage_control_collection_log { + __le16 version_major; + __le16 version_minor; + gc_item_t item[VU_GC_MAX_ITEMS]; + __u8 reserved[2892]; +} garbage_control_collection_log_t; + +static void vu_gc_log_show_json(garbage_control_collection_log_t *payload, const char *devname) +{ + struct json_object *gc_entries = json_create_array(); + + for (int i = 0; i < VU_GC_MAX_ITEMS; i++) { + gc_item_t 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(garbage_control_collection_log_t *payload, const char *devname) +{ + printf("Solidigm Garbage Collection Log for NVME device: %s\n", devname); + printf("Timestamp Timer Type\n"); + + for (int i = 0; i < VU_GC_MAX_ITEMS; i++) { + gc_item_t 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."; + struct nvme_dev *dev; + 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) + return err; + + enum nvme_print_flags flags = validate_output_format(cfg.output_format); + if (flags == -EINVAL) { + fprintf(stderr, "Invalid output format '%s'\n", cfg.output_format); + dev_close(dev); + return EINVAL; + } + + garbage_control_collection_log_t gc_log; + const int solidigm_vu_gc_log_id = 0xfd; + + err = nvme_get_log_simple(dev_fd(dev), solidigm_vu_gc_log_id, + sizeof(gc_log), &gc_log); + 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); + } + } + 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-latency-tracking.c b/plugins/solidigm/solidigm-latency-tracking.c new file mode 100644 index 0000000..1013ae8 --- /dev/null +++ b/plugins/solidigm/solidigm-latency-tracking.c @@ -0,0 +1,475 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include +#include +#include +#include +#include + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-print.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; + 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] == 0) + 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() +{ + 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("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 == 0){ + 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, + .fid = LATENCY_TRACKING_FID, + .nsid = 0, + .sel = 0, + .cdw11 = 0, + .uuidx = 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, + .fid = LATENCY_TRACKING_FID, + .nsid = 0, + .cdw11 = lt->cfg.enable, + .cdw12 = 0, + .save = 0, + .uuidx = 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 FID (0x%X) to %i.\n", + 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, + .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, + .uuidx = NVME_UUID_NONE, + .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 = { + .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); + + lt.print_flags = validate_output_format(lt.cfg.output_format); + if (lt.print_flags == -EINVAL) { + 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; + } + + 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 (FID 0x%X) is currently (%i).\n", + 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-nvme.c b/plugins/solidigm/solidigm-nvme.c new file mode 100644 index 0000000..b547035 --- /dev/null +++ b/plugins/solidigm/solidigm-nvme.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include "nvme.h" + +#define CREATE_CMD +#include "solidigm-nvme.h" + +#include "solidigm-smart.h" +#include "solidigm-garbage-collection.h" +#include "solidigm-latency-tracking.h" +#include "solidigm-telemetry.h" +#include "plugins/ocp/ocp-clear-fw-update-history.h" + +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_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); +} diff --git a/plugins/solidigm/solidigm-nvme.h b/plugins/solidigm/solidigm-nvme.h new file mode 100644 index 0000000..778dbf9 --- /dev/null +++ b/plugins/solidigm/solidigm-nvme.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 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 "0.7" + +PLUGIN(NAME("solidigm", "Solidigm vendor specific extensions", SOLIDIGM_PLUGIN_VERSION), + COMMAND_LIST( + ENTRY("smart-log-add", "Retrieve Solidigm SMART Log", get_additional_smart_log) + ENTRY("garbage-collect-log", "Retrieve Garbage Collection Log", get_garbage_collection_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-fw-activate-history", + "Clear firmware update history log (redirects to ocp plug-in)", + clear_fw_update_history) + ) +); + +#endif + +#include "define_cmd.h" diff --git a/plugins/solidigm/solidigm-smart.c b/plugins/solidigm/solidigm-smart.c new file mode 100644 index 0000000..77c26ac --- /dev/null +++ b/plugins/solidigm/solidigm-smart.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include +#include +#include +#include +#include + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.h" +#include "nvme-print.h" + +#include "solidigm-smart.h" + +struct __attribute__((packed)) nvme_additional_smart_log_item { + __u8 id; + __u8 _kp[2]; + __u8 normalized; + __u8 _np; + union __attribute__((packed)) { + __u8 raw[6]; + struct __attribute__((packed)) wear_level { + __le16 min; + __le16 max; + __le16 avg; + } wear_level; + struct __attribute__((packed)) thermal_throttle { + __u8 pct; + __u32 count; + } thermal_throttle; + } ; + __u8 _rp; +} ; +typedef struct nvme_additional_smart_log_item smart_log_item_t; + +#define VU_SMART_PAGE_SIZE 512 +#define VU_SMART_MAX_ITEMS VU_SMART_PAGE_SIZE / sizeof(smart_log_item_t) +typedef struct vu_smart_log { + smart_log_item_t item[VU_SMART_MAX_ITEMS]; +} vu_smart_log_t; + +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 "host_reads"; + case 0xE4: + return "timed_work_load"; + case 0xE5: + return "read_commands_in_flight_counter"; + case 0xE6: + return "write_commands_in_flight_counter"; + case 0xEA: + return "thermal_throttle_status"; + 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(smart_log_item_t *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(smart_log_item_t *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(vu_smart_log_t *payload, unsigned int nsid, const char *devname) +{ + struct json_object *dev_stats = json_create_object(); + smart_log_item_t *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(vu_smart_log_t *payload, unsigned int nsid, const char *devname) +{ + smart_log_item_t *item = payload->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 < 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; + vu_smart_log_t smart_log_payload; + enum nvme_print_flags flags; + struct nvme_dev *dev; + int err; + + 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; + + flags = validate_output_format(cfg.output_format); + if (flags == -EINVAL) { + fprintf(stderr, "Invalid output format '%s'\n", cfg.output_format); + dev_close(dev); + return flags; + } + + err = nvme_get_log_simple(dev_fd(dev), solidigm_vu_smart_log_id, + sizeof(smart_log_payload), &smart_log_payload); + 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); + } + } 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..84a4e2a --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include +#include +#include +#include +#include + +#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" + +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 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 = 3, + .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; + + 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 = 0; + 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; + } + json_tokener * jstok = json_tokener_new(); + + tl.configuration = json_tokener_parse_ex(jstok, conf_str, length); + 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) { + if (cfg.ctrl_init) + err = nvme_get_ctrl_telemetry(dev_fd(dev), true, + &tl.log, cfg.data_area, + &tl.log_size); + else if (cfg.host_gen) + err = nvme_get_new_host_telemetry(dev_fd(dev), &tl.log, + cfg.data_area, + &tl.log_size); + else + err = nvme_get_host_telemetry(dev_fd(dev), &tl.log, + cfg.data_area, + &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_header_parse(&tl); + if (cfg.cfg_file) + solidigm_telemetry_log_data_areas_parse(&tl, cfg.data_area); + else + solidigm_telemetry_log_cod_parse(&tl); + + 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..be5685b --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/cod.c @@ -0,0 +1,194 @@ +// 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 + "Number of sectors relocated in reaction to an error" //Uid 0x2B = 43 +}; + +static const char * getOemDataMapDescription(__u32 id) +{ + if (id < (sizeof(oemDataMapDesc) / sizeof(oemDataMapDesc[0]))) { + 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, + }; + json_object *telemetry_header = NULL; + json_object *COD_offset = NULL; + 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; + + __u64 offset = json_object_get_int(COD_offset); + + if (offset == 0) { + 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 *) (((__u8 *)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; + } + + json_object *cod = json_create_object(); + json_object_object_add(tl->root, "cod", cod); + + for (int 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 %d!", 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); + + } + } +} 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..781d786 --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/config.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ +#include +#include "util/json.h" +#include + +// max 16 bit unsigned integer nummber 65535 +#define MAX_16BIT_NUM_AS_STRING_SIZE 6 + +static bool config_get_by_version(const json_object *obj, int version_major, + int version_minor, 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); + 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_by_token_version(const json_object *obj, int token_id, + int version_major, int version_minor, + json_object **value) +{ + json_object *token_obj = 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(obj, str_key, &token_obj)) + return false; + if (!config_get_by_version(token_obj, version_major, version_minor, value)) + return false; + return value != NULL; +} diff --git a/plugins/solidigm/solidigm-telemetry/config.h b/plugins/solidigm/solidigm-telemetry/config.h new file mode 100644 index 0000000..bea84fb --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/config.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ +#include +#include "util/json.h" + +bool solidigm_config_get_by_token_version(const json_object *obj, int key, int subkey, int subsubkey, json_object **value); diff --git a/plugins/solidigm/solidigm-telemetry/data-area.c b/plugins/solidigm/solidigm-telemetry/data-area.c new file mode 100644 index 0000000..7233e8f --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/data-area.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include "common.h" +#include "data-area.h" +#include "config.h" +#include + +#define SIGNED_INT_PREFIX "int" +#define BITS_IN_BYTE 8 + +#define MAX_WARNING_SIZE 1024 + +static bool telemetry_log_get_value(const struct telemetry_log *tl, + uint32_t offset_bit, uint32_t size_bit, + bool is_signed, json_object **val_obj) +{ + uint32_t offset_bit_from_byte; + uint32_t additional_size_byte; + uint32_t offset_byte; + uint32_t val; + + if (size_bit == 0) { + 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 = 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 = offset_bit - (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 bounday, " + "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 |= -1ULL << 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, + json_object *struct_def, + size_t parent_offset_bit, + json_object *output, + json_object *metadata) +{ + json_object *obj_arraySizeArray = NULL; + json_object *obj = NULL; + json_object *obj_memberList; + json_object *major_dimension; + json_object *sub_output; + bool is_enumeration = false; + bool has_member_list; + const char *type = ""; + const char *name; + size_t array_rank; + size_t offset_bit; + size_t size_bit; + uint32_t linear_array_pos_bit; + + 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 = 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 == 0) { + SOLIDIGM_LOG_WARNING("Warning: Structure property 'arraySize' " + "don't support flexible array: %s", + json_object_to_json_string(struct_def)); + return -1; + } + uint32_t array_size_dimension[array_rank]; + + for (size_t i = 0; i < array_rank; i++) { + json_object *dimension = json_object_array_get_idx(obj_arraySizeArray, i); + + array_size_dimension[i] = json_object_get_uint64(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; + json_object *dimension_output; + + for (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 (int i = 0 ; i < array_size_dimension[0]; i++) { + json_object *sub_array = json_create_array(); + size_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); + json_object *val_obj; + size_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 { + json_object *sub_sub_output = json_object_new_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++) { + json_object *member = json_object_array_get_idx(obj_memberList, k); + size_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; +} + +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, + json_object *toc_array, + 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; + + 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; + + for (int i = 0; i < toc->header.TableOfContentsCount; i++) { + json_object *structure_definition = NULL; + json_object *toc_item; + uint32_t obj_offset; + bool has_struct; + + 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_by_token_version(tl->configuration, + header->Token, + header->versionMajor, + header->versionMinor, + &structure_definition); + + if (has_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); + json_object *parsed_struct = json_object_new_object(); + + json_object_add_value_object(tele_obj_item, "objectData", parsed_struct); + json_object *obj_hasTelemObjHdr = NULL; + uint32_t header_offset = sizeof(const struct telemetry_object_header); + uint32_t 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; + } + + file_offset = da_offset + obj_offset + header_offset; + telemetry_log_structure_parse(tl, structure_definition, + BITS_IN_BYTE * file_offset, + parsed_struct, toc_item); + } + } +} + +int solidigm_telemetry_log_data_areas_parse(const struct telemetry_log *tl, + enum nvme_telemetry_da last_da) +{ + json_object *tele_obj_array = json_create_array(); + json_object *toc_array = json_create_array(); + + 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..095eb64 --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/data-area.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2022 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ +#include "common.h" +#include "telemetry-log.h" + +int solidigm_telemetry_log_data_areas_parse(const 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..72b2d97 --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/header.c @@ -0,0 +1,199 @@ +// 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, + json_object *reason_id) +{ + const struct reason_indentifier_1_0 *ri; + 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++) { + 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, + json_object *reason_id) +{ + const struct reason_indentifier_1_1 *ri; + 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++) { + 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, + json_object *reason_id) +{ + const struct reason_indentifier_1_2 *ri; + json_object *dp_reserved; + 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++) { + 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++) { + 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, json_object *reason_id) +{ + const struct reason_indentifier_1_0 *ri1_0 = + (struct reason_indentifier_1_0 *) tl->log->rsnident; + __u16 version_major = le16_to_cpu(ri1_0->versionMajor); + __u16 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); + } + } +} + +bool solidigm_telemetry_log_header_parse(const struct telemetry_log *tl) +{ + const struct nvme_telemetry_log *log; + json_object *ieee_oui_id; + json_object *reason_id; + 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++) { + 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..53ab452 --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/meson.build @@ -0,0 +1,6 @@ +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', +] diff --git a/plugins/solidigm/solidigm-telemetry/telemetry-log.h b/plugins/solidigm/solidigm-telemetry/telemetry-log.h new file mode 100644 index 0000000..ef4ec5d --- /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 + +#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; + json_object *root; + json_object *configuration; +}; + +#endif /* _SOLIDIGM_TELEMETRY_LOG_H */ \ No newline at end of file diff --git a/plugins/toshiba/toshiba-nvme.c b/plugins/toshiba/toshiba-nvme.c new file mode 100644 index 0000000..5540fea --- /dev/null +++ b/plugins/toshiba/toshiba-nvme.c @@ -0,0 +1,583 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = 0x0; +static const __u32 DW10_SCT_COMMAND_TRANSFER = 0x1; + +static const __u32 DW11_SCT_STATUS_COMMAND = 0x0; +static const __u32 DW11_SCT_COMMAND_TRANSFER = 0x0; + +static const __u16 INTERNAL_LOG_ACTION_CODE = 0xFFFB; +static const __u16 CURRENT_LOG_FUNCTION_CODE = 0x0001; +static const __u16 SAVED_LOG_FUNCTION_CODE = 0x0002; + +/* A bitmask field for supported devices */ +typedef enum { + MASK_0 = 1 << 0, + MASK_1 = 1 << 1, + /* + * Future devices can use the remaining 31 bits from this field + * and should use 1 << 2, 1 << 3, etc. + */ + MASK_IGNORE = 0 +} DeviceMask; + +/* Internal device codes */ +typedef enum { + CODE_0 = 0x0D, + CODE_1 = 0x10 +} DeviceCode; + + +static int nvme_sct_op(int fd, __u32 opcode, __u32 cdw10, __u32 cdw11, void* data, __u32 data_len ) +{ + void *metadata = NULL; + const __u32 cdw2 = 0; + const __u32 cdw3 = 0; + const __u32 cdw12 = 0; + const __u32 cdw13 = 0; + const __u32 cdw14 = 0; + const __u32 cdw15 = 0; + const __u32 timeout = 0; + const __u32 metadata_len = 0; + const __u32 namespace_id = 0x0; + const __u32 flags = 0; + const __u32 rsvd = 0; + int err = 0; + + __u32 result; + err = nvme_admin_passthru(fd, opcode, flags, rsvd, + namespace_id, cdw2, cdw3, cdw10, + cdw11, cdw12, cdw13, cdw14, cdw15, + data_len, data, metadata_len, metadata, + timeout, &result); + return err; +} + +static int nvme_get_sct_status(int fd, __u32 device_mask) +{ + int err; + void* data = NULL; + size_t data_len = 512; + unsigned char *status; + + if (posix_memalign(&data, getpagesize(), data_len)) + return ENOMEM; + + memset(data, 0, data_len); + err = nvme_sct_op(fd, OP_SCT_STATUS, DW10_SCT_STATUS_COMMAND, DW11_SCT_STATUS_COMMAND, data, data_len); + if (err) { + fprintf(stderr, "%s: SCT status failed :%d\n", __func__, err); + goto end; + } + + status = data; + if (status[0] != 1U) { + /* Eek, wrong version in status header */ + fprintf(stderr, "%s: unexpected value in SCT status[0]:(%x)\n", __func__, status[0]); + err = -1; + errno = EINVAL; + goto end; + } + + /* Check if device is supported */ + if (device_mask != MASK_IGNORE) { + __u32 supported = 0; + switch (status[1]) { + case CODE_0: + supported = (device_mask & MASK_0); + break; + + case CODE_1: + supported = (device_mask & MASK_1); + break; + + default: + break; + }; + + if (0 == supported) { + fprintf(stderr, "%s: command unsupported on this device: (0x%x)\n",__func__, status[1]); + err = -1; + errno = EINVAL; + goto end; + } + } +end: + if (data) + free(data); + return err; +} + +static int nvme_sct_command_transfer_log(int fd, bool current) +{ + int err; + void *data = NULL; + size_t data_len = 512; + __u16 function_code, action_code = INTERNAL_LOG_ACTION_CODE; + + if (current) + function_code = CURRENT_LOG_FUNCTION_CODE; + else + function_code = SAVED_LOG_FUNCTION_CODE; + + if (posix_memalign(&data, getpagesize(), data_len)) + return ENOMEM; + + memset(data, 0, data_len); + memcpy(data, &action_code, sizeof(action_code)); + memcpy(data + 2, &function_code, sizeof(function_code)); + + err = nvme_sct_op(fd, OP_SCT_COMMAND_TRANSFER, DW10_SCT_COMMAND_TRANSFER, DW11_SCT_COMMAND_TRANSFER, data, data_len); + 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 len, int fd) +{ + int written = 0; + int remaining = len; + + while (remaining) { + written = write(fd, buf, remaining); + if (written < 0) { + remaining = written; + break; + } else if (written <= remaining) { + remaining -= written; + } else { + /* Unexpected overwrite */ + break; + } + } + + /* return 0 on success or remaining/error */ + return remaining; +} + +/* Display progress (incoming 0->1.0) */ +static void progress_runner(float progress) +{ + const size_t barWidth = 70; + size_t i, pos; + + fprintf(stdout, "["); + pos = barWidth * progress; + for (i = 0; i < barWidth; ++i) { + if (i <= pos) + fprintf(stdout, "="); + else + fprintf(stdout, " "); + } + + fprintf(stdout, "] %d %%\r",(int)(progress * 100.0)); + fflush(stdout); +} + +static int nvme_get_internal_log(int fd, const char* const filename, bool current) +{ + int err; + int o_fd = -1; + void* page_data = NULL; + const size_t page_sector_len = 32; + const size_t page_data_len = page_sector_len * 512; /* 32 sectors per page */ + uint32_t* area1_last_page; + uint32_t* area2_last_page; + uint32_t* area3_last_page; + uint32_t log_sectors = 0; + size_t pages; + + /* + * By trial and error it seems that the largest transfer chunk size + * is 128 * 32 = 4k sectors = 2MB + */ + const __u32 max_pages = 128; + size_t i; + unsigned int j; + float progress = 0.0; + + err = nvme_sct_command_transfer_log(fd, current); + if (err) { + fprintf(stderr, "%s: SCT command transfer failed\n", __func__); + goto end; + } + + if (posix_memalign(&page_data, getpagesize(), max_pages * page_data_len)) { + err = ENOMEM; + goto end; + } + memset(page_data, 0, max_pages * page_data_len); + + /* Read the header to get the last log page - offsets 8->11, 12->15, 16->19 */ + err = nvme_sct_data_transfer(fd, page_data, page_data_len, 0); + if (err) { + fprintf(stderr, "%s: SCT data transfer failed, page 0\n",__func__); + goto end; + } + + area1_last_page = (uint32_t *) (page_data + 8); + area2_last_page = (uint32_t *) (page_data + 12); + area3_last_page = (uint32_t *) (page_data + 16); + + /* The number of total log sectors is the maximum + 1; */ + if (*area1_last_page > log_sectors) + log_sectors = *area1_last_page; + if (*area2_last_page > log_sectors) + log_sectors = *area2_last_page; + if (*area3_last_page > log_sectors) + log_sectors = *area3_last_page; + + ++log_sectors; + pages = log_sectors / page_sector_len; + if (filename == NULL) { + fprintf(stdout, "Page: %u of %zu\n", 0u, pages); + d(page_data, page_data_len, 16, 1); + } else { + progress_runner(progress); + o_fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (o_fd < 0) { + fprintf(stderr, "%s: couldn't output file %s\n", __func__, filename); + err = EINVAL; + goto end; + } + err = d_raw_to_fd(page_data, page_data_len, o_fd); + if (err) { + fprintf(stderr, "%s: couldn't write all data to output file\n", __func__); + goto end; + } + } + + /* Now read the rest */ + for (i = 1; i < pages;) { + __u32 pages_chunk = max_pages; + if (pages_chunk + i >= pages) + pages_chunk = pages - i; + + err = nvme_sct_data_transfer(fd, page_data, + pages_chunk * page_data_len, + i * page_sector_len); + if (err) { + fprintf(stderr, "%s: SCT data transfer command failed\n", __func__); + goto end; + } + + progress = (float) (i) / (float) (pages); + progress_runner(progress); + if (filename == NULL) { + for (j = 0; j < pages_chunk; ++j) { + fprintf(stdout, "Page: %zu of %zu\n", i + j, pages); + d(page_data + (j * page_data_len), page_data_len, 16, 1); + } + } else { + progress_runner(progress); + err = d_raw_to_fd(page_data, pages_chunk * page_data_len, o_fd); + if (err) { + fprintf(stderr, "%s: couldn't write all data to output file\n", + __func__); + goto end; + } + } + i += pages_chunk; + } + progress = 1.0f; + progress_runner(progress); + fprintf(stdout,"\n"); + err = nvme_get_sct_status(fd, MASK_IGNORE); + if (err) { + fprintf(stderr, "%s: bad SCT status\n", __func__); + goto end; + } +end: + if (o_fd >= 0) { + close(o_fd); + } + if (page_data) { + free(page_data); + } + return err; +} + +static int nvme_get_internal_log_file(int fd, const char* const filename, bool current) +{ + int err; + + /* Check device supported */ + err = nvme_get_sct_status(fd, MASK_0 | MASK_1); + if (!err) + err = nvme_get_internal_log(fd, filename, current); + return err; +} + +enum LOG_PAGE_C0 { + ERROR_LOG_C0 = 0, + SMART_HEALTH_LOG_C0, + FIRMWARE_SLOT_INFO_C0, + COMMAND_EFFECTS_C0, + DEVICE_SELF_TEST_C0, + LOG_PAGE_DIRECTORY_C0, + SMART_ATTRIBUTES_C0, + NR_SMART_ITEMS_C0, +}; + +struct nvme_xdn_smart_log_c0 { + __u8 items[NR_SMART_ITEMS_C0]; + __u8 resv[512 - NR_SMART_ITEMS_C0]; +}; + +static void default_show_vendor_log_c0(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..024351f --- /dev/null +++ b/plugins/transcend/transcend-nvme.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include + +#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..c8df126 --- /dev/null +++ b/plugins/virtium/virtium-nvme.c @@ -0,0 +1,1049 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 int time_stamp; + struct nvme_id_ctrl raw_ctrl; + struct nvme_firmware_slot raw_fw; +}; + +struct vtview_smart_log_entry { + char path[256]; + long int time_stamp; + struct nvme_id_ns raw_ns; + struct nvme_id_ctrl raw_ctrl; + struct nvme_smart_log raw_smart; +}; + +struct vtview_save_log_settings { + double run_time_hrs; + double log_record_frequency_hrs; + const char* output_file; + const char* test_name; +}; + +static 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 (NULL == templocale) + printf("Cannot malloc buffer\n"); + + setlocale(LC_ALL, "C"); + + unsigned long long int 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 (0 == temp) { + snprintf(tempbuff, sizeof(tempbuff), "Temperature_Sensor_%d;NC;", i); + strcat(text, tempbuff); + continue; + } + snprintf(tempbuff, sizeof(tempbuff), "Temperature_Sensor_%d;%d;", i, temp - 273); + strcat(text, tempbuff); + } + + snprintf(tempbuff, sizeof(tempbuff), "Thermal_Management_T1_Trans_Count;%u;", le32_to_cpu(smart->raw_smart.thm_temp1_trans_count)); + strcat(text, tempbuff); + snprintf(tempbuff, sizeof(tempbuff), "Thermal_Management_T2_Trans_Count;%u;", le32_to_cpu(smart->raw_smart.thm_temp2_trans_count)); + strcat(text, tempbuff); + snprintf(tempbuff, sizeof(tempbuff), "Thermal_Management_T1_Total_Time;%u;", le32_to_cpu(smart->raw_smart.thm_temp1_total_time)); + strcat(text, tempbuff); + snprintf(tempbuff, sizeof(tempbuff), "Thermal_Management_T2_Total_Time;%u;", le32_to_cpu(smart->raw_smart.thm_temp2_total_time)); + strcat(text, tempbuff); + + snprintf(tempbuff, sizeof(tempbuff), "NandWrites;%d;\n", 0); + strcat(text, tempbuff); + + setlocale(LC_ALL, templocale); + free(templocale); +} + +static void vt_header_to_string(const struct vtview_log_header *header, char *text) +{ + char timebuff[50] = ""; + char tempbuff[MAX_HEADER_BUFF] = ""; + char identext[16384] = ""; + char fwtext[2048] = ""; + + strftime(timebuff, 50, "%Y-%m-%d %H:%M:%S", localtime(&(header->time_stamp))); + snprintf(tempbuff, MAX_HEADER_BUFF, "header;{\"session\":{\"testName\":\"%s\",\"dateTime\":\"%s\"},", + header->test_name, timebuff); + strcpy(text, tempbuff); + + vt_convert_data_buffer_to_hex_string((unsigned char *)&(header->raw_ctrl), sizeof(header->raw_ctrl), false, identext); + vt_convert_data_buffer_to_hex_string((unsigned char *)&(header->raw_fw), sizeof(header->raw_fw), false, fwtext); + snprintf(tempbuff, MAX_HEADER_BUFF, + "\"devices\":[{\"model\":\"%s\",\"port\":\"%s\",\"SN\":\"%s\",\"type\":\"NVMe\",\"identify\":\"%s\",\"firmwareSlot\":\"%s\"}]}\n", + header->raw_ctrl.mn, header->path, header->raw_ctrl.sn, identext, fwtext); + strcat(text, tempbuff); +} + +static int vt_append_text_file(const char *text, const char *filename) +{ + FILE *f; + + f = fopen(filename, "a"); + if(NULL == f) { + printf("Cannot open %s\n", filename); + return -1; + } + + fprintf(f, "%s", text); + fclose(f); + return 0; +} + +static int vt_append_log(struct vtview_smart_log_entry *smart, const char *filename) +{ + char sm_log_text[MAX_LOG_BUFF] = ""; + + vt_convert_smart_data_to_human_readable_format(smart, sm_log_text); + return vt_append_text_file(sm_log_text, filename); +} + +static int vt_append_header(const struct vtview_log_header *header, const char *filename) +{ + char header_text[MAX_HEADER_BUFF] = ""; + + vt_header_to_string(header, header_text); + return vt_append_text_file(header_text, filename); +} + +static void vt_process_string(char *str, const size_t size) +{ + size_t i; + + if (size == 0) + return; + + i = size - 1; + while ((0 != i) && (' ' == str[i])) { + str[i] = 0; + i--; + } +} + +static int vt_add_entry_to_log(const int fd, const char *path, const struct vtview_save_log_settings *cfg) +{ + struct vtview_smart_log_entry smart; + const char *filename; + int ret = 0; + unsigned nsid = 0; + + memset(smart.path, 0, sizeof(smart.path)); + strncpy(smart.path, path, sizeof(smart.path) - 1); + if(NULL == 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 (NULL == 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(NULL == 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 int i, j; + + textbuf[32] = '\0'; + printf("[%08X] ", 0); + for (i = 0; i < pbuffsize; i++) { + printf("%02X ", pbuff[i]); + + if (pbuff[i] >= ' ' && pbuff[i] <= '~') + textbuf[i % 32] = pbuff[i]; + else + textbuf[i % 32] = '.'; + + if ((((i + 1) % 8) == 0) || ((i + 1) == pbuffsize)) { + printf(" "); + if ((i + 1) % 32 == 0) { + printf(" %s\n", textbuf); + if((i + 1) != pbuffsize) + printf("[%08lX] ", (i + 1)); + } + else if (i + 1 == pbuffsize) { + textbuf[(i + 1) % 32] = '\0'; + if(((i + 1) % 8) == 0) + printf(" "); + + for (j = ((i + 1) % 32); j < 32; j++) { + printf(" "); + if(((j + 1) % 8) == 0) + printf(" "); + } + + printf("%s\n", textbuf); + } + } + } +} + +static void vt_parse_detail_identify(const struct nvme_id_ctrl *ctrl) +{ + unsigned char *buf; + unsigned int temp, pos; + char s[1024] = ""; + + const char *CMICtable[6] = {"0 = the NVM subsystem contains only a single NVM subsystem port", + "1 = the NVM subsystem may contain more than one subsystem ports", + "0 = the NVM subsystem contains only a single controller", + "1 = the NVM subsystem may contain two or more controllers (see section 1.4.1)", + "0 = the controller is associated with a PCI Function or a Fabrics connection", + "1 = the controller is associated with an SR-IOV Virtual Function"}; + + const char *OAEStable[20] = {"Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "0 = does not support sending the Namespace Attribute Notices event nor the associated Changed Namespace List log page", + "1 = supports sending the Namespace Attribute Notices & the associated Changed Namespace List log page", + "0 = does not support sending Firmware Activation Notices event", + "1 = supports sending Firmware Activation Notices"}; + + const char *CTRATTtable[4] = {"0 = does not support a 128-bit Host Identifier", + "1 = supports a 128-bit Host Identifier", + "0 = does not support Non-Operational Power State Permissive Mode", + "1 = supports Non-Operational Power State Permissive Mode"}; + + const char *OACStable[18] = {"0 = does not support the Security Send and Security Receive commands", + "1 = supports the Security Send and Security Receive commands", + "0 = does not support the Format NVM command", + "1 = supports the Format NVM command", + "0 = does not support the Firmware Commit and Firmware Image Download commands", + "1 = supports the Firmware Commit and Firmware Image Download commands", + "0 = does not support the Namespace Management capability", + "1 = supports the Namespace Management capability", + "0 = does not support the Device Self-test command", + "1 = supports the Device Self-test command", + "0 = does not support Directives", + "1 = supports Directive Send & Directive Receive commands", + "0 = does not support the NVMe-MI Send and NVMe-MI Receive commands", + "1 = supports the NVMe-MI Send and NVMe-MI Receive commands", + "0 = does not support the Virtualization Management command", + "1 = supports the Virtualization Management command", + "0 = does not support the Doorbell Buffer Config command", + "1 = supports the Doorbell Buffer Config command"}; + + const char *FRMWtable[10] = {"0 = the 1st firmware slot (slot 1) is read/write", + "1 = the 1st firmware slot (slot 1) is read only", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "0 = requires a reset for firmware to be activated", + "1 = supports firmware activation without a reset"}; + + const char *LPAtable[8] = {"0 = does not support the SMART / Health information log page on a per namespace basis", + "1 = supports the SMART / Health information log page on a per namespace basis", + "0 = does not support the Commands Supported & Effects log page", + "1 = supports the Commands Supported Effects log page", + "0 = does not support extended data for Get Log Page", + "1 = supports extended data for Get Log Page (including extended Number of Dwords and Log Page Offset fields)", + "0 = does not support the Telemetry Host-Initiated and Telemetry Controller-Initiated log pages and Telemetry Log Notices events", + "1 = supports the Telemetry Host-Initiated and Telemetry Controller-Initiated log pages and sending Telemetry Log Notices" }; + + const char *AVSCCtable[2] = {"0 = the format of all Admin Vendor Specific Commands are vendor specific", + "1 = all Admin Vendor Specific Commands use the format defined in NVM Express specification"}; + + const char *APSTAtable[2] = {"0 = does not support autonomous power state transitions", + "1 = supports autonomous power state transitions"}; + + const char *DSTOtable[2] = {"0 = the NVM subsystem supports one device self-test operation per controller at a time", + "1 = the NVM subsystem supports only one device self-test operation in progress at a time"}; + + const char *HCTMAtable[2] = {"0 = does not support host controlled thermal management", + "1 = supports host controlled thermal management. Supports Set Features & Get Features commands with the Feature Identifier field set to 10h"}; + + const char *SANICAPtable[6] = {"0 = does not support the Crypto Erase sanitize operation", + "1 = supports the Crypto Erase sanitize operation", + "0 = does not support the Block Erase sanitize operation", + "1 = supports the Block Erase sanitize operation", + "0 = does not support the Overwrite sanitize operation", + "1 = supports the Overwrite sanitize operation"}; + + const char *ONCStable[14] = {"0 = does not support the Compare command", + "1 = supports the Compare command", + "0 = does not support the Write Uncorrectable command", + "1 = supports the Write Uncorrectable command", + "0 = does not support the Dataset Management command", + "1 = supports the Dataset Management command", + "0 = does not support the Write Zeroes command", + "1 = supports the Write Zeroes command", + "0 = does not support the Save field set to a non-zero value in the Set Features and the Get Features commands", + "1 = supports the Save field set to a non-zero value in the Set Features and the Get Features commands", \ + "0 = does not support reservations", + "1 = supports reservations", + "0 = does not support the Timestamp feature (refer to section 5.21.1.14)", + "1 = supports the Timestamp feature"}; + + const char *FUSEStable[2] = {"0 = does not support the Compare and Write fused operation", + "1 = supports the Compare and Write fused operation"}; + + const char *FNAtable[6] = {"0 = supports format on a per namespace basis", + "1 = all namespaces shall be configured with the same attributes and a format (excluding secure erase) of any namespace results in a format of all namespaces in an NVM subsystem", + "0 = any secure erase performed as part of a format results in a secure erase of a particular namespace specified", + "1 = any secure erase performed as part of a format operation results in a secure erase of all namespaces in the NVM subsystem", + "0 = cryptographic erase is not supported", + "1 = cryptographic erase is supported as part of the secure erase functionality"}; + + const char *VWCtable[2] = {"0 = a volatile write cache is not present", + "1 = a volatile write cache is present"}; + + const char *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 int total_time = 0; + long int freq_time = 0; + long int cur_time = 0; + long int remain_time = 0; + long int start_time = 0; + long int end_time = 0; + char path[256] = ""; + char *desc = "Save SMART data into log file with format that is easy to analyze (comma delimited). Maximum log file will be 4K.\n\n" + "Typical usages:\n\n" + "Temperature characterization: \n" + "\tvirtium save-smart-to-vtview-log /dev/yourDevice --run-time=100 --record-frequency=0.25 --test-name=burn-in-at-(-40)\n\n" + "Endurance testing : \n" + "\tvirtium save-smart-to-vtview-log /dev/yourDevice --run-time=100 --record-frequency=1 --test-name=Endurance-test-JEDEG-219-workload\n\n" + "Just logging :\n" + "\tvirtium save-smart-to-vtview-log /dev/yourDevice"; + + const char *run_time = "(optional) Number of hours to log data (default = 20 hours)"; + const char *freq = "(optional) How often you want to log SMART data (0.25 = 15' , 0.5 = 30' , 1 = 1 hour, 2 = 2 hours, etc.). Default = 10 hours."; + const char *output_file = "(optional) Name of the log file (give it a name that easy for you to remember what the test is). You can leave it blank too, we will take care it for you."; + const char *test_name = "(optional) Name of the test you are doing. We use this as part of the name of the log file."; + struct 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 == 0) + freq_time = 1; + + start_time = time(NULL); + end_time = start_time + total_time; + + fflush(stdout); + + while (1) { + cur_time = time(NULL); + if(cur_time >= end_time) + break; + + ret = vt_add_entry_to_log(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..cf185be --- /dev/null +++ b/plugins/wdc/wdc-nvme.c @@ -0,0 +1,11100 @@ +// 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 , + * Dong Ho , + * Jeff Lien + * Brandon Paupore + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "plugin.h" +#include "linux/types.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 + +/* This id's are no longer supported, delete ?? */ +#define WDC_NVME_SN550_DEV_ID 0x2708 +#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_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 0x5009 +#define WDC_NVME_SN720_DEV_ID 0x5002 +#define WDC_NVME_SN730A_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_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_OPCODE 0xC2 +#define WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE_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 +#define WDC_BD_CA_LOG_BUF_LEN 0xA0 /* Added 4 padding bytes to resolve build warning messages */ + +/* 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_OPCODE +#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_OPCODE_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 + +typedef enum _NVME_FEATURES_SELECT +{ + FS_CURRENT = 0, + FS_DEFAULT = 1, + FS_SAVED = 2, + FS_SUPPORTED_CAPBILITIES = 3 +} NVME_FEATURES_SELECT; + +typedef enum _NVME_FEATURE_IDENTIFIERS +{ + FID_ARBITRATION = 0x01, + FID_POWER_MANAGEMENT = 0x02, + FID_LBA_RANGE_TYPE = 0x03, + FID_TEMPERATURE_THRESHOLD = 0x04, + FID_ERROR_RECOVERY = 0x05, + FID_VOLATILE_WRITE_CACHE = 0x06, + FID_NUMBER_OF_QUEUES = 0x07, + FID_INTERRUPT_COALESCING = 0x08, + FID_INTERRUPT_VECTOR_CONFIGURATION = 0x09, + FID_WRITE_ATOMICITY = 0x0A, + FID_ASYNCHRONOUS_EVENT_CONFIGURATION = 0x0B, + FID_AUTONOMOUS_POWER_STATE_TRANSITION = 0x0C, +/*Below FID's are NVM Command Set Specific*/ + FID_SOFTWARE_PROGRESS_MARKER = 0x80, + FID_HOST_IDENTIFIER = 0x81, + FID_RESERVATION_NOTIFICATION_MASK = 0x82, + FID_RESERVATION_PERSISTENCE = 0x83 +} NVME_FEATURE_IDENTIFIERS; + +typedef enum +{ + WDC_DE_TYPE_IDENTIFY = 0x1, + WDC_DE_TYPE_SMARTATTRIBUTEDUMP = 0x2, + WDC_DE_TYPE_EVENTLOG = 0x4, + WDC_DE_TYPE_DUMPTRACE = 0x8, + WDC_DE_TYPE_DUMPSNAPSHOT = 0x10, + WDC_DE_TYPE_ATA_LOGS = 0x20, + WDC_DE_TYPE_SMART_LOGS = 0x40, + WDC_DE_TYPE_SCSI_LOGS = 0x80, + WDC_DE_TYPE_SCSI_MODE_PAGES = 0x100, + WDC_DE_TYPE_NVMe_FEATURES = 0x200, + WDC_DE_TYPE_DUMPSMARTERRORLOG3 = 0x400, + WDC_DE_TYPE_DUMPLOG3E = 0x800, + WDC_DE_TYPE_DUMPSCRAM = 0x1000, + WDC_DE_TYPE_PCU_LOG = 0x2000, + WDC_DE_TYPE_DUMP_ERROR_LOGS = 0x4000, + WDC_DE_TYPE_FW_SLOT_LOGS = 0x8000, + WDC_DE_TYPE_MEDIA_SETTINGS = 0x10000, + WDC_DE_TYPE_SMART_DATA = 0x20000, + WDC_DE_TYPE_NVME_SETTINGS = 0x40000, + WDC_DE_TYPE_NVME_ERROR_LOGS = 0x80000, + WDC_DE_TYPE_NVME_LOGS = 0x100000, + WDC_DE_TYPE_UART_LOGS = 0x200000, + WDC_DE_TYPE_DLOGS_SPI = 0x400000, + WDC_DE_TYPE_DLOGS_RAM = 0x800000, + WDC_DE_TYPE_NVME_MANF_INFO = 0x2000000, + WDC_DE_TYPE_NONE = 0x1000000, + WDC_DE_TYPE_ALL = 0xFFFFFFF, +} WDC_DRIVE_ESSENTIAL_TYPE; + +#define WDC_C0_GUID_LENGTH 16 +#define WDC_SCA_V1_NAND_STATS 0x1 +#define WDC_SCA_V1_ALL 0xF +typedef 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 */ +} SMART_CLOUD_ATTRIBUTE_OFFSETS_V1; + +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 }; + +typedef struct __attribute__((__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 */ +} wdc_nvme_ext_smart_log; + +typedef 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 */ +} SMART_CLOUD_ATTRIBUTE_OFFSETS_V3; + +static __u8 scao_guid[WDC_C0_GUID_LENGTH] = { 0xC5, 0xAF, 0x10, 0x28, 0xEA, 0xBF, 0xF2, 0xA4, + 0x9C, 0x4F, 0x6F, 0x7C, 0xC9, 0x14, 0xD5, 0xAF }; + +typedef 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 */ +} EOL_LOG_PAGE_C0_OFFSETS; + +#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 + +typedef struct __attribute__((__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 */ +} wdc_nvme_hw_rev_log; + +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 }; + +typedef struct __attribute__((__packed__)) _WDC_DE_VU_FILE_META_DATA +{ + __u8 fileName[WDC_DE_FILE_NAME_SIZE]; + __u16 fileID; + __u64 fileSize; +} WDC_DE_VU_FILE_META_DATA, *PWDC_DE_VU_FILE_META_DATA; + +typedef struct _WDC_DRIVE_ESSENTIALS +{ + WDC_DE_VU_FILE_META_DATA metaData; + WDC_DRIVE_ESSENTIAL_TYPE essentialType; +} WDC_DRIVE_ESSENTIALS; + +typedef struct _WDC_DE_VU_LOG_DIRECTORY +{ + WDC_DRIVE_ESSENTIALS *logEntry; /* Caller to allocate memory */ + __u32 maxNumLogEntries; /* Caller to input memory allocated */ + __u32 numOfValidLogEntries; /* API will output this value */ +} WDC_DE_VU_LOG_DIRECTORY,*PWDC_DE_VU_LOG_DIRECTORY; + +typedef struct _WDC_DE_CSA_FEATURE_ID_LIST +{ + NVME_FEATURE_IDENTIFIERS featureId; + __u8 featureName[WDC_DE_GENERIC_BUFFER_SIZE]; +} WDC_DE_CSA_FEATURE_ID_LIST; + +typedef struct tarfile_metadata { + char fileName[MAX_PATH_LEN]; + int8_t bufferFolderPath[MAX_PATH_LEN]; + char bufferFolderName[MAX_PATH_LEN]; + char tarFileName[MAX_PATH_LEN]; + char tarFiles[MAX_PATH_LEN]; + char tarCmd[MAX_PATH_LEN+MAX_PATH_LEN]; + char currDir[MAX_PATH_LEN]; + UtilsTimeInfo timeInfo; + uint8_t* timeString[MAX_PATH_LEN]; +} tarfile_metadata; + +static WDC_DE_CSA_FEATURE_ID_LIST deFeatureIdList[] = +{ + {0x00 , "Dummy Placeholder"}, + {FID_ARBITRATION , "Arbitration"}, + {FID_POWER_MANAGEMENT , "PowerMgmnt"}, + {FID_LBA_RANGE_TYPE , "LbaRangeType"}, + {FID_TEMPERATURE_THRESHOLD , "TempThreshold"}, + {FID_ERROR_RECOVERY , "ErrorRecovery"}, + {FID_VOLATILE_WRITE_CACHE , "VolatileWriteCache"}, + {FID_NUMBER_OF_QUEUES , "NumOfQueues"}, + {FID_INTERRUPT_COALESCING , "InterruptCoalesing"}, + {FID_INTERRUPT_VECTOR_CONFIGURATION , "InterruptVectorConfig"}, + {FID_WRITE_ATOMICITY , "WriteAtomicity"}, + {FID_ASYNCHRONOUS_EVENT_CONFIGURATION , "AsynEventConfig"}, + {FID_AUTONOMOUS_POWER_STATE_TRANSITION , "AutonomousPowerState"}, +}; + +typedef enum _NVME_VU_DE_LOGPAGE_NAMES +{ + NVME_DE_LOGPAGE_E3 = 0x01, + NVME_DE_LOGPAGE_C0 = 0x02 +} NVME_VU_DE_LOGPAGE_NAMES; +typedef struct _NVME_VU_DE_LOGPAGE_LIST +{ + NVME_VU_DE_LOGPAGE_NAMES logPageName; + __u32 logPageId; + __u32 logPageLen; + char logPageIdStr[5]; +} NVME_VU_DE_LOGPAGE_LIST, *PNVME_VU_DE_LOGPAGE_LIST; + +typedef struct _WDC_NVME_DE_VU_LOGPAGES +{ + NVME_VU_DE_LOGPAGE_NAMES vuLogPageReqd; + __u32 numOfVULogPages; +} WDC_NVME_DE_VU_LOGPAGES, *PWDC_NVME_DE_VU_LOGPAGES; + +static NVME_VU_DE_LOGPAGE_LIST deVULogPagesList[] = +{ + { NVME_DE_LOGPAGE_E3, 0xE3, 1072, "0xe3"}, + { NVME_DE_LOGPAGE_C0, 0xC0, 512, "0xc0"} +}; + +static int wdc_get_serial_name(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 data_area_id; + __le32 section_size; +}; + +/* DUI log header V2 */ +struct __attribute__((__packed__)) wdc_dui_log_section_v2 { + __le16 section_type; + __le16 data_area_id; + __le64 section_size; +}; + +/* 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 __attribute__((__packed__)) wdc_dui_log_hdr_v2 { + __u8 telemetry_hdr[512]; + __u8 hdr_version; + __u8 product_id; + __le16 section_count; + __le64 log_size; + struct wdc_dui_log_section_v2 log_section[WDC_NVME_DUI_MAX_SECTION_V2]; + __u8 log_data[40]; +}; + +struct __attribute__((__packed__)) wdc_dui_log_hdr_v3 { + __u8 telemetry_hdr[512]; + __u8 hdr_version; + __u8 product_id; + __le16 section_count; + __le64 log_size; + struct wdc_dui_log_section_v2 log_section[WDC_NVME_DUI_MAX_SECTION_V3]; + __u8 securityNonce[36]; + __u8 log_data[40]; +}; + +struct __attribute__((__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 __attribute__((__packed__)) wdc_bd_ca_log_format { + __u8 field_id; + __u8 reserved1[2]; + __u8 normalized_value; + __u8 raw_value[8]; +}; + +#define READ 0 +#define WRITE 1 +#define TRIM 2 +#define RESERVED 3 + +struct __attribute__((__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 __attribute__((__packed__)) wdc_ssd_ca_perf_stats { + __le64 nand_bytes_wr_lo; /* 0x00 - NAND Bytes Written lo */ + __le64 nand_bytes_wr_hi; /* 0x08 - NAND Bytes Written hi */ + __le64 nand_bytes_rd_lo; /* 0x10 - NAND Bytes Read lo */ + __le64 nand_bytes_rd_hi; /* 0x18 - NAND Bytes Read hi */ + __le64 nand_bad_block; /* 0x20 - NAND Bad Block Count */ + __le64 uncorr_read_count; /* 0x28 - Uncorrectable Read Count */ + __le64 ecc_error_count; /* 0x30 - Soft ECC Error Count */ + __le32 ssd_detect_count; /* 0x38 - SSD End to End Detection Count */ + __le32 ssd_correct_count; /* 0x3C - SSD End to End Correction Count */ + __u8 data_percent_used; /* 0x40 - System Data Percent Used */ + __le32 data_erase_max; /* 0x41 - User Data Erase Counts */ + __le32 data_erase_min; /* 0x45 - User Data Erase Counts */ + __le64 refresh_count; /* 0x49 - Refresh Count */ + __le64 program_fail; /* 0x51 - Program Fail Count */ + __le64 user_erase_fail; /* 0x59 - User Data Erase Fail Count */ + __le64 system_erase_fail; /* 0x61 - System Area Erase Fail Count */ + __u8 thermal_throttle_status; /* 0x69 - Thermal Throttling Status */ + __u8 thermal_throttle_count; /* 0x6A - Thermal Throttling Count */ + __le64 pcie_corr_error; /* 0x6B - pcie Correctable Error Count */ + __le32 incomplete_shutdown_count; /* 0x73 - Incomplete Shutdown Count */ + __u8 percent_free_blocks; /* 0x77 - Percent Free Blocks */ + __u8 rsvd[392]; /* 0x78 - Reserved bytes 120-511 */ +}; + +struct __attribute__((__packed__)) wdc_ssd_d0_smart_log { + __le32 smart_log_page_header; /* 0x00 - Smart Log Page Header */ + __le32 lifetime_realloc_erase_block_count; /* 0x04 - Lifetime reallocated erase block count */ + __le32 lifetime_power_on_hours; /* 0x08 - Lifetime power on hours */ + __le32 lifetime_uecc_count; /* 0x0C - Lifetime UECC count */ + __le32 lifetime_wrt_amp_factor; /* 0x10 - Lifetime write amplification factor */ + __le32 trailing_hr_wrt_amp_factor; /* 0x14 - Trailing hour write amplification factor */ + __le32 reserve_erase_block_count; /* 0x18 - Reserve erase block count */ + __le32 lifetime_program_fail_count; /* 0x1C - Lifetime program fail count */ + __le32 lifetime_block_erase_fail_count; /* 0x20 - Lifetime block erase fail count */ + __le32 lifetime_die_failure_count; /* 0x24 - Lifetime die failure count */ + __le32 lifetime_link_rate_downgrade_count; /* 0x28 - Lifetime link rate downgrade count */ + __le32 lifetime_clean_shutdown_count; /* 0x2C - Lifetime clean shutdown count on power loss */ + __le32 lifetime_unclean_shutdown_count; /* 0x30 - Lifetime unclean shutdowns on power loss */ + __le32 current_temp; /* 0x34 - Current temperature */ + __le32 max_recorded_temp; /* 0x38 - Max recorded temperature */ + __le32 lifetime_retired_block_count; /* 0x3C - Lifetime retired block count */ + __le32 lifetime_read_disturb_realloc_events; /* 0x40 - Lifetime read disturb reallocation events */ + __le64 lifetime_nand_writes; /* 0x44 - Lifetime NAND write Lpages */ + __le32 capacitor_health; /* 0x4C - Capacitor health */ + __le64 lifetime_user_writes; /* 0x50 - Lifetime user writes */ + __le64 lifetime_user_reads; /* 0x58 - Lifetime user reads */ + __le32 lifetime_thermal_throttle_act; /* 0x60 - Lifetime thermal throttle activations */ + __le32 percentage_pe_cycles_remaining; /* 0x64 - Percentage of P/E cycles remaining */ + __u8 rsvd[408]; /* 0x68 - 408 Reserved bytes */ +}; + +#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 __attribute__((__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 __attribute__((__packed__)) wdc_nand_stats { + __u8 nand_write_tlc[16]; + __u8 nand_write_slc[16]; + __le32 nand_prog_failure; + __le32 nand_erase_failure; + __le32 bad_block_count; + __le64 nand_rec_trigger_event; + __le64 e2e_error_counter; + __le64 successful_ns_resize_event; + __u8 rsvd[442]; + __u16 log_page_version; +}; + +struct __attribute__((__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 __attribute__((__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 __attribute__((__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 __attribute__((__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 Commmand 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 __attribute__((__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" + + +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_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) != NULL) + 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: + /* FALLTHRU */ + case WDC_NVME_SN630_DEV_ID_1: + capabilities = (WDC_DRIVE_CAP_CAP_DIAG | WDC_DRIVE_CAP_INTERNAL_LOG | + WDC_DRIVE_CAP_DRIVE_STATUS | WDC_DRIVE_CAP_CLEAR_ASSERT | + WDC_DRIVE_CAP_RESIZE | WDC_DRIVE_CAP_CLEAR_PCIE); + /* verify the 0xCA log page is supported */ + if (wdc_nvme_check_supported_log_page(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) + == true) + capabilities |= WDC_DRIVE_CAP_D0_LOG_PAGE; + break; + case WDC_NVME_SN640_DEV_ID: + /* FALLTHRU */ + case WDC_NVME_SN640_DEV_ID_1: + /* FALLTHRU */ + case WDC_NVME_SN640_DEV_ID_2: + /* FALLTHRU */ + case WDC_NVME_SN640_DEV_ID_3: + /* FALLTHRU */ + case WDC_NVME_SN560_DEV_ID_1: + /* FALLTHRU */ + case WDC_NVME_SN560_DEV_ID_2: + /* FALLTHRU */ + case WDC_NVME_SN560_DEV_ID_3: + /* FALLTHRU */ + 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 Requirments) 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: + /* FALLTHRU */ + case WDC_NVME_SN840_DEV_ID_1: + /* FALLTHRU */ + 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; + /* FALLTHRU */ + case WDC_NVME_ZN540_DEV_ID: + /* FALLTHRU */ + case WDC_NVME_SN540_DEV_ID: + /* FALLTHRU */ + 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: + 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_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 Requirments) 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; + default: + capabilities = 0; + } + break; + case WDC_NVME_SNDK_VID: + switch (read_device_id) { + case WDC_NVME_SXSLCL_DEV_ID: + capabilities = WDC_DRIVE_CAP_DRIVE_ESSENTIALS; + break; + case WDC_NVME_SN520_DEV_ID: + /* FALLTHRU */ + case WDC_NVME_SN520_DEV_ID_1: + /* FALLTHRU */ + case WDC_NVME_SN520_DEV_ID_2: + case WDC_NVME_SN530_DEV_ID: + 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_SN730A_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_SN740_DEV_ID: + case WDC_NVME_SN740_DEV_ID_1: + case WDC_NVME_SN740_DEV_ID_2: + case WDC_NVME_SN740_DEV_ID_3: + case WDC_NVME_SN340_DEV_ID: + capabilities = WDC_DRIVE_CAP_DUI; + break; + case WDC_NVME_ZN350_DEV_ID: + /* FALLTHRU */ + 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 == 0) { + fprintf(stderr, "ERROR : WDC: invalid log file length\n"); + return -1; + } + + fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) { + fprintf(stderr, "ERROR : WDC: open : %s\n", strerror(errno)); + return -1; + } + + while (drive_log_length > WRITE_SIZE) { + ret = write(fd, drive_log_data, WRITE_SIZE); + if (ret < 0) { + fprintf (stderr, "ERROR : WDC: write : %s\n", strerror(errno)); + 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 == NULL) { + fprintf(stderr, "ERROR : WDC - wdc_get_dev_mng_log_entry: No ppLogEntry pointer.\n"); + 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 - wdc_get_dev_mng_log_entry: \ + Buffer is not large enough for the common header. BufSize: 0x%x HdrSize: %"PRIxPTR"\n", + 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 == 0) || + (log_entry_size > remaining_len)) { + fprintf(stderr, "ERROR : WDC: wdc_get_dev_mng_log_entry: \ + Detected unaligned end of the data. Data Offset: 0x%x \ + Entry Size: 0x%x, Remaining Log Length: 0x%x Entry Id: 0x%x\n", + current_data_offset, log_entry_size, remaining_len, p_next_log_entry->entry_id); + + /* Force the loop to end */ + remaining_len = 0; + } else if ((p_next_log_entry->entry_id == 0) || + (p_next_log_entry->entry_id > 200)) { + /* Invalid entry - fail the search */ + fprintf(stderr, "ERROR : WDC: wdc_get_dev_mng_log_entry: \ + Invalid entry found at offset: 0x%x Entry Size: 0x%x, \ + Remaining Log Length: 0x%x Entry Id: 0x%x\n", + current_data_offset, log_entry_size, remaining_len, p_next_log_entry->entry_id); + + /* Force the loop to end */ + remaining_len = 0; + valid_log = false; + + /* The struture 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_mgment_cbs_data(nvme_root_t r, struct nvme_dev *dev, + __u8 log_id, void **cbs_data) +{ + int ret = -1; + void* data; + struct wdc_c2_log_page_header *hdr_ptr; + struct wdc_c2_log_subpage_header *sph; + __u32 length = 0; + bool found = false; + __u8 uuid_ix = 1; + __u8 lid = 0; + *cbs_data = NULL; + __u32 device_id, read_vendor_id; + + ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id); + if(device_id == WDC_NVME_ZN350_DEV_ID || device_id == WDC_NVME_ZN350_DEV_ID_1) { + lid = WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE_C8; + uuid_ix = 0; + } else + lid = WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE; + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_C2_LOG_BUF_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return false; + } + memset(data, 0, sizeof (__u8) * WDC_C2_LOG_BUF_LEN); + + /* get the log page length */ + 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, ret = 0x%x\n", lid, 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 == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return false; + } + } + + /* get the log page data */ + 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, ret = 0x%x\n", lid, 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 == NULL) { + fprintf(stderr, "ERROR : WDC : calloc : %s\n", strerror(errno)); + goto end; + } + memcpy((void *)*cbs_data, (void *)&sph->data, le32_to_cpu(sph->length)); + } else { + /* not found with uuid = 1 try with uuid = 0 */ + uuid_ix = 0; + /* get the log page data */ + struct nvme_get_log_args args = { + .args_size = sizeof(args), + .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 = le32_to_cpu(hdr_ptr->length), + .log = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + ret = nvme_get_log(&args); + + 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 == NULL) { + fprintf(stderr, "ERROR : WDC : calloc : %s\n", strerror(errno)); + goto end; + } + memcpy((void *)*cbs_data, (void *)&sph->data, le32_to_cpu(sph->length)); + + } else { + /* WD version not found */ + fprintf(stderr, "ERROR : WDC : Unable to find correct version of page 0x%x, entry id = %d\n", lid, log_id); + } + } +end: + free(data); + 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 != NULL) { + for (i = 0; i < le32_to_cpu(cbs_data->length); i++) { + if (log_id == cbs_data->data[i]) { + found = true; + break; + } + } + +#ifdef WDC_NVME_CLI_DEBUG + if (!found) { + fprintf(stderr, "ERROR : WDC : Log Page 0x%x not supported\n", log_id); + fprintf(stderr, "WDC : Supported Log Pages:\n"); + /* print the supported pages */ + d((__u8 *)cbs_data->data, le32_to_cpu(cbs_data->length), 16, 1); + } +#endif + 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 != NULL) { + 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 != 0) { + 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 != 0) { + 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 != 0) { + 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 != 0) { + 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 != 0) { + 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 == NULL) { + fprintf(stderr, "%s: ERROR : malloc : %s\n", __func__, strerror(errno)); + return -1; + } + memset(dump_data, 0, sizeof (__u8) * dump_length); + memset(&admin_cmd, 0, sizeof (struct nvme_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 != 0) { + 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, (long unsigned int)admin_cmd.addr); + break; + } + + if ((curr_data_offset + xfer_size) <= data_len) + curr_data_len = xfer_size; + else + curr_data_len = data_len - curr_data_offset; /* last transfer */ + + curr_data_offset += curr_data_len; + admin_cmd.addr = (__u64)(uintptr_t)dump_data + (__u64)curr_data_offset; + admin_cmd.data_len = curr_data_len; + admin_cmd.cdw10 = curr_data_len >> 2; + admin_cmd.cdw13 = curr_data_offset >> 2; + i++; + } + + if (ret == 0) { + 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; + + dump_data = (__u8 *) malloc(sizeof (__u8) * data_len); + + if (dump_data == NULL) { + fprintf(stderr, "%s: ERROR : malloc : %s\n", __func__, strerror(errno)); + return -1; + } + memset(dump_data, 0, sizeof (__u8) * data_len); + memset(&admin_cmd, 0, sizeof (struct nvme_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; + + log_size = data_len; + while (log_size > 0) { + xfer_size = min(xfer_size, log_size); + + admin_cmd.addr = (__u64)(uintptr_t)dump_data + (__u64)curr_data_offset; + admin_cmd.data_len = xfer_size; + admin_cmd.cdw10 = xfer_size >> 2; + admin_cmd.cdw13 = curr_data_offset >> 2; + + ret = nvme_submit_admin_passthru(fd, &admin_cmd, NULL); + if (ret != 0) { + 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, (long unsigned int)admin_cmd.addr); + break; + } + + log_size -= xfer_size; + curr_data_offset += xfer_size; + i++; + } + + if (ret == 0) { + fprintf(stderr, "%s: ", __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 == 0) { + if (result == 0) { + /* 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 == NULL) { + fprintf(stderr, "%s: ERROR : malloc : %s\n", __func__, strerror(errno)); + ret = -1; + goto out; + } + memset(log_hdr, 0, e6_log_hdr_size); + + if (type == WDC_TELEMETRY_TYPE_NONE) { + ret = wdc_dump_length_e6(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 == 0) { + 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(int fd, char *file, __u32 xfer_size, int data_area, int verbose, __u64 file_size, __u64 offset) +{ + int ret = 0; + __u32 dui_log_hdr_size = WDC_NVME_CAP_DUI_HEADER_SIZE; + struct wdc_dui_log_hdr *log_hdr; + struct wdc_dui_log_hdr_v3 *log_hdr_v3; + __u32 cap_dui_length; + __u64 cap_dui_length_v3; + __u64 cap_dui_length_v4; + __u8 *dump_data = NULL; + __u8 *buffer_addr; + __s64 total_size = 0; + int i; + int j; + bool last_xfer = false; + int err = 0, output = 0; + + log_hdr = (struct wdc_dui_log_hdr *) malloc(dui_log_hdr_size); + if (log_hdr == NULL) { + fprintf(stderr, "%s: ERROR : log header malloc failed : status %s, size 0x%x\n", + __func__, strerror(errno), dui_log_hdr_size); + return -1; + } + memset(log_hdr, 0, dui_log_hdr_size); + + /* get the dui telemetry and log headers */ + ret = wdc_dump_dui_data(fd, WDC_NVME_CAP_DUI_HEADER_SIZE, 0x00, (__u8 *)log_hdr, last_xfer); + if (ret != 0) { + fprintf(stderr, "%s: ERROR : WDC : Get DUI headers failed\n", __func__); + fprintf(stderr, "%s: ERROR : WDC : ", __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) { + __s32 log_size = 0; + __u32 curr_data_offset = 0; + + cap_dui_length = le32_to_cpu(log_hdr->log_size); + + if (verbose) { + fprintf(stderr, "INFO : WDC : Capture V1 Device Unit Info log, data area = %d\n", data_area); + fprintf(stderr, "INFO : WDC : DUI Header Version = 0x%x\n", log_hdr->hdr_version); + } + + if (cap_dui_length == 0) { + fprintf(stderr, "INFO : WDC : Capture V1 Device Unit Info log is empty\n"); + } else { + /* parse log header for all sections up to specified data area inclusively */ + if (data_area != WDC_NVME_DUI_MAX_DATA_AREA) { + for(j = 0; j < WDC_NVME_DUI_MAX_SECTION; j++) { + if (log_hdr->log_section[j].data_area_id <= data_area && + log_hdr->log_section[j].data_area_id != 0) { + log_size += log_hdr->log_section[j].section_size; + if (verbose) + fprintf(stderr, "%s: Data area ID %d : section size 0x%x, total size = 0x%x\n", + __func__, log_hdr->log_section[j].data_area_id, (unsigned int)log_hdr->log_section[j].section_size, (unsigned int)log_size); + + } + else { + if (verbose) + fprintf(stderr, "%s: break, total size = 0x%x\n", __func__, (unsigned int)log_size); + break; + } + } + } else + log_size = cap_dui_length; + + total_size = log_size; + + dump_data = (__u8 *) malloc(sizeof (__u8) * xfer_size); + if (dump_data == NULL) { + fprintf(stderr, "%s: ERROR : dump data V1 malloc failed : status %s, size = 0x%x\n", + __func__, strerror(errno), (unsigned int)xfer_size); + ret = -1; + goto out; + } + memset(dump_data, 0, sizeof (__u8) * xfer_size); + + output = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (output < 0) { + fprintf(stderr, "%s: Failed to open output file %s: %s!\n", + __func__, file, strerror(errno)); + ret = output; + goto free_mem; + } + + /* write the telemetry and log headers into the dump_file */ + err = write(output, (void *)log_hdr, WDC_NVME_CAP_DUI_HEADER_SIZE); + if (err != WDC_NVME_CAP_DUI_HEADER_SIZE) { + fprintf(stderr, "%s: Failed to flush header data to file!\n", __func__); + goto free_mem; + } + + log_size -= WDC_NVME_CAP_DUI_HEADER_SIZE; + curr_data_offset = WDC_NVME_CAP_DUI_HEADER_SIZE; + i = 0; + buffer_addr = dump_data; + + for(; log_size > 0; log_size -= xfer_size) { + xfer_size = min(xfer_size, log_size); + + if (log_size <= xfer_size) + last_xfer = true; + + ret = wdc_dump_dui_data(fd, xfer_size, curr_data_offset, buffer_addr, last_xfer); + if (ret != 0) { + fprintf(stderr, "%s: ERROR : WDC : Get chunk %d, size = 0x%"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); + goto free_mem; + } + + curr_data_offset += xfer_size; + i++; + } + } + } + else if (((log_hdr->hdr_version & 0xFF) == 0x02) || + ((log_hdr->hdr_version & 0xFF) == 0x03)) { /* Process Version 2 or 3 header */ + __s64 log_size = 0; + __u64 curr_data_offset = 0; + __u64 xfer_size_long = (__u64)xfer_size; + + log_hdr_v3 = (struct wdc_dui_log_hdr_v3 *)log_hdr; + + cap_dui_length_v3 = le64_to_cpu(log_hdr_v3->log_size); + + if (verbose) { + fprintf(stderr, "INFO : WDC : Capture V2 or V3 Device Unit Info log, data area = %d\n", data_area); + + fprintf(stderr, "INFO : WDC : DUI Header Version = 0x%x\n", log_hdr_v3->hdr_version); + if ((log_hdr->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 == 0) { + fprintf(stderr, "INFO : WDC : Capture V2 or V3 Device Unit Info log is empty\n"); + } else { + /* parse log header for all sections up to specified data area inclusively */ + if (data_area != WDC_NVME_DUI_MAX_DATA_AREA) { + for(j = 0; j < WDC_NVME_DUI_MAX_SECTION_V3; j++) { + if (log_hdr_v3->log_section[j].data_area_id <= data_area && + log_hdr_v3->log_section[j].data_area_id != 0) { + log_size += log_hdr_v3->log_section[j].section_size; + if (verbose) + fprintf(stderr, "%s: Data area ID %d : section size 0x%x, total size = 0x%"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); + goto out; + } + + dump_data = (__u8 *) malloc(sizeof (__u8) * xfer_size_long); + if (dump_data == NULL) { + fprintf(stderr, "%s: ERROR : dump data v3 malloc failed : status %s, size = 0x%"PRIx64"\n", + __func__, strerror(errno), (uint64_t)xfer_size_long); + ret = -1; + goto out; + } + memset(dump_data, 0, sizeof (__u8) * xfer_size_long); + + output = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (output < 0) { + fprintf(stderr, "%s: Failed to open output file %s: %s!\n", + __func__, file, strerror(errno)); + ret = output; + goto free_mem; + } + + curr_data_offset = 0; + + if (file_size != 0) { + /* Write the DUI data based on the passed in file size */ + if ((offset + file_size) > total_size) + log_size = min((total_size - offset), file_size); + else + log_size = min(total_size, file_size); + + if (verbose) + fprintf(stderr, "%s: INFO : WDC : Offset 0x%"PRIx64", file size 0x%"PRIx64", total size 0x%"PRIx64", log size 0x%"PRIx64"\n", + __func__, (uint64_t)offset, (uint64_t)file_size, (uint64_t)total_size, (uint64_t)log_size); + + curr_data_offset = offset; + + } + + i = 0; + buffer_addr = dump_data; + + for(; log_size > 0; log_size -= xfer_size_long) { + xfer_size_long = min(xfer_size_long, log_size); + + if (log_size <= xfer_size_long) + last_xfer = true; + + ret = wdc_dump_dui_data_v2(fd, (__u32)xfer_size_long, curr_data_offset, buffer_addr, last_xfer); + if (ret != 0) { + fprintf(stderr, "%s: ERROR : WDC : Get chunk %d, size = 0x%"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); + goto free_mem; + } + + curr_data_offset += xfer_size_long; + i++; + } + } + } + else if ((log_hdr->hdr_version & 0xFF) == 0x04) { + __s64 log_size = 0; + __u64 curr_data_offset = 0; + struct wdc_dui_log_hdr_v4 *log_hdr_v4; + log_hdr_v4 = (struct wdc_dui_log_hdr_v4 *)log_hdr; + __s64 xfer_size_long = (__s64)xfer_size; + __s64 section_size_bytes = 0; + + 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 == 0) { + fprintf(stderr, "INFO : WDC : Capture V4 Device Unit Info log is empty\n"); + } else { + /* parse log header for all sections up to specified data area inclusively */ + if (data_area != WDC_NVME_DUI_MAX_DATA_AREA) { + for(j = 0; j < WDC_NVME_DUI_MAX_SECTION; j++) { + if (log_hdr_v4->log_section[j].data_area_id <= data_area && + log_hdr_v4->log_section[j].data_area_id != 0) { + 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); + goto out; + } + + dump_data = (__u8 *) malloc(sizeof (__u8) * xfer_size_long); + if (dump_data == NULL) { + fprintf(stderr, "%s: ERROR : dump data V4 malloc failed : status %s, size = 0x%x\n", + __func__, strerror(errno), (unsigned int)xfer_size_long); + ret = -1; + goto out; + } + memset(dump_data, 0, sizeof (__u8) * xfer_size_long); + + output = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (output < 0) { + fprintf(stderr, "%s: Failed to open output file %s: %s!\n", + __func__, file, strerror(errno)); + ret = output; + goto free_mem; + } + + curr_data_offset = 0; + + if (file_size != 0) { + /* Write the DUI data based on the passed in file size */ + if ((offset + file_size) > total_size) + log_size = min((total_size - offset), file_size); + else + log_size = min(total_size, file_size); + + if (verbose) + fprintf(stderr, "%s: INFO : WDC : Offset 0x%"PRIx64", file size 0x%"PRIx64", total size 0x%"PRIx64", log size 0x%"PRIx64"\n", + __func__, (uint64_t)offset, (uint64_t)file_size, (uint64_t)total_size, (uint64_t)log_size); + + curr_data_offset = offset; + + } + + i = 0; + buffer_addr = dump_data; + + for(; log_size > 0; log_size -= xfer_size_long) { + xfer_size_long = min(xfer_size_long, log_size); + + if (log_size <= xfer_size_long) + last_xfer = true; + + ret = wdc_dump_dui_data_v2(fd, (__u32)xfer_size_long, curr_data_offset, buffer_addr, last_xfer); + if (ret != 0) { + fprintf(stderr, "%s: ERROR : WDC : Get chunk %d, size = 0x%"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); + goto free_mem; + } + + curr_data_offset += xfer_size_long; + i++; + } + } + } + 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); + + free_mem: + close(output); + free(dump_data); + + out: + free(log_hdr); + return ret; +} + +static int wdc_cap_diag(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + 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 != NULL) + strncpy(f, cfg.file, PATH_MAX - 1); + if (cfg.xfer_size != 0) + 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 == NULL) { + 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; + + if ((output = (uint32_t*)malloc(sizeof(uint32_t))) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + memset(output, 0, sizeof (uint32_t)); + memset(&admin_cmd, 0, sizeof (struct nvme_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 == 0) + *len_buf = *output; + free(output); + return ret; +} + +static int wdc_do_get_sn730_log(int fd, void * log_buf, uint32_t offset, uint32_t subopcode) +{ + int ret; + uint8_t *output = NULL; + struct nvme_passthru_cmd admin_cmd; + + if ((output = (uint8_t*)calloc(SN730_LOG_CHUNK_SIZE, sizeof(uint8_t))) == NULL) { + fprintf(stderr, "ERROR : WDC : calloc : %s\n", strerror(errno)); + return -1; + } + memset(&admin_cmd, 0, sizeof (struct nvme_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; + + if ((chunk_buf = (uint8_t*) malloc(sizeof (uint8_t) * SN730_LOG_CHUNK_SIZE)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + ret = -1; + goto out; + } + + while (remaining > 0) { + memset(chunk_buf, 0, SN730_LOG_CHUNK_SIZE); + ret = wdc_do_get_sn730_log(fd, chunk_buf, curr_offset, subopcode); + if (!ret) { + if (remaining >= SN730_LOG_CHUNK_SIZE) { + memcpy(log_buf + (curr_offset * SN730_LOG_CHUNK_SIZE), + chunk_buf, SN730_LOG_CHUNK_SIZE); + } else { + memcpy(log_buf + (curr_offset * SN730_LOG_CHUNK_SIZE), + chunk_buf, remaining); + } + remaining -= SN730_LOG_CHUNK_SIZE; + curr_offset += 1; + } else + goto out; + } +out: + free(chunk_buf); + return ret; +} + +static int wdc_do_sn730_get_and_tar(int fd, char * outputName) +{ + int ret = 0; + void *retPtr; + uint8_t* full_log_buf = NULL; + uint8_t* key_log_buf = NULL; + uint8_t* core_dump_log_buf = NULL; + uint8_t* extended_log_buf = NULL; + uint32_t full_log_len = 0; + uint32_t key_log_len = 0; + uint32_t core_dump_log_len = 0; + uint32_t extended_log_len = 0; + tarfile_metadata* tarInfo = NULL; + + tarInfo = (struct tarfile_metadata*) malloc(sizeof(tarfile_metadata)); + if (tarInfo == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + ret = -1; + goto free_buf; + } + memset(tarInfo, 0, sizeof(tarfile_metadata)); + + /* Create Logs directory */ + wdc_UtilsGetTime(&tarInfo->timeInfo); + memset(tarInfo->timeString, 0, sizeof(tarInfo->timeString)); + wdc_UtilsSnprintf((char*)tarInfo->timeString, MAX_PATH_LEN, "%02u%02u%02u_%02u%02u%02u", + tarInfo->timeInfo.year, tarInfo->timeInfo.month, tarInfo->timeInfo.dayOfMonth, + tarInfo->timeInfo.hour, tarInfo->timeInfo.minute, tarInfo->timeInfo.second); + + wdc_UtilsSnprintf((char*)tarInfo->bufferFolderName, MAX_PATH_LEN, "%s", + (char*)outputName); + + retPtr = getcwd((char*)tarInfo->currDir, MAX_PATH_LEN); + if (retPtr != NULL) + wdc_UtilsSnprintf((char*)tarInfo->bufferFolderPath, MAX_PATH_LEN, "%s%s%s", + (char *)tarInfo->currDir, WDC_DE_PATH_SEPARATOR, (char *)tarInfo->bufferFolderName); + else { + fprintf(stderr, "ERROR : WDC : get current working directory failed\n"); + goto free_buf; + } + + ret = wdc_UtilsCreateDir((char*)tarInfo->bufferFolderPath); + if (ret) + { + fprintf(stderr, "ERROR : WDC : create directory failed, ret = %d, dir = %s\n", ret, tarInfo->bufferFolderPath); + goto free_buf; + } else { + fprintf(stderr, "Stored log files in directory: %s\n", tarInfo->bufferFolderPath); + } + + ret = wdc_do_get_sn730_log_len(fd, &full_log_len, SN730_GET_FULL_LOG_LENGTH); + if (ret) { + 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 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 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; + 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 != 0) + xfer_size = cfg.xfer_size; + else { + fprintf(stderr, "ERROR : WDC : Invalid length\n"); + goto out; + } + + if (cfg.file != NULL) { + int verify_file; + + /* verify the passed in file name and path is valid before getting the dump data */ + verify_file = open(cfg.file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (verify_file < 0) { + fprintf(stderr, "ERROR : WDC: open : %s\n", strerror(errno)); + 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 == NULL) { + 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 == NULL) || + (!strcmp(cfg.type, "NONE")) || + (!strcmp(cfg.type, "none"))) { + telemetry_type = WDC_TELEMETRY_TYPE_NONE; + data_area = 0; + } else if ((!strcmp(cfg.type, "HOST")) || + (!strcmp(cfg.type, "host"))) { + telemetry_type = WDC_TELEMETRY_TYPE_HOST; + telemetry_data_area = cfg.data_area; + } else if ((!strcmp(cfg.type, "CONTROLLER")) || + (!strcmp(cfg.type, "controller"))) { + telemetry_type = WDC_TELEMETRY_TYPE_CONTROLLER; + telemetry_data_area = cfg.data_area; + } else { + fprintf(stderr, "ERROR : WDC: Invalid type - Must be NONE, HOST or CONTROLLER\n"); + ret = -1; + goto out; + } + + capabilities = wdc_get_drive_capabilities(r, dev); + if ((capabilities & WDC_DRIVE_CAP_INTERNAL_LOG) == WDC_DRIVE_CAP_INTERNAL_LOG) { + if (telemetry_data_area == 0) + telemetry_data_area = 3; /* Set the default DA to 3 if not specified */ + + ret = wdc_do_cap_diag(r, dev, f, xfer_size, + telemetry_type, telemetry_data_area); + 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 == 0) + 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 == 0) + 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 == 0) + 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 == 0) { + if (type == WDC_NVME_PFAIL_DUMP_TYPE) + fprintf(stderr, "INFO : WDC: Pfail dump is empty\n"); + else + fprintf(stderr, "INFO : WDC: Crash dump is empty\n"); + } else { + ret = wdc_do_dump(dev, + opcode, + crash_dump_length, + cdw12, + file, + crash_dump_length); + + if (ret == 0) + 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 != NULL) { + 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 == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + + memset(drive_log_data, 0, sizeof (__u8) * drive_log_length); + memset(&admin_cmd, 0, sizeof (struct nvme_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 == 0) { + ret = wdc_create_log_file(file, drive_log_data, drive_log_length); + } + free(drive_log_data); + return ret; +} + +static int wdc_drive_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Capture Drive Log."; + const char *file = "Output file pathname."; + char f[PATH_MAX] = {0}; + 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) == 0) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + } else { + if (cfg.file != NULL) { + strncpy(f, cfg.file, PATH_MAX - 1); + } + 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) == 0) { + 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 != 0) { + 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) == 0) { + 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 != 0) { + 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\nor reset interruption. Other commands may be " + "rejected until\nPurge Execute is issued and " + "completed."; + break; + default: + str = "Unknown."; + } + return str; +} + +static int wdc_purge(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + const char *desc = "Send a Purge command."; + 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) == 0) { + 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) == 0) { + 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 == 0) { + mon = (struct wdc_nvme_purge_monitor_data *) output; + printf("Purge state = 0x%0x\n", admin_cmd.result); + printf("%s\n", wdc_purge_mon_status_to_string(admin_cmd.result)); + if (admin_cmd.result == WDC_NVME_PURGE_STATE_BUSY) { + progress_percent = + ((double)le32_to_cpu(mon->entire_progress_current) * 100) / + le32_to_cpu(mon->entire_progress_total); + printf("Purge Progress = %f%%\n", progress_percent); + } + } + + 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; + + 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_convert_ts(time_t time, char *ts_buf) +{ + struct tm gmTimeInfo; + time_t time_Human, time_ms; + char buf[80]; + + time_Human = time/1000; + time_ms = time % 1000; + + gmtime_r((const time_t *)&time_Human, &gmTimeInfo); + + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &gmTimeInfo); + sprintf(ts_buf, "%s.%03ld GMT", buf, time_ms); + + 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][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 <= 3; i++) { + printf(" Active Measured Latency: Bucket %d %27d ms %27d ms %27d ms \n", + i, le16_to_cpu(log_data->active_measured_latency[i][READ]), le16_to_cpu(log_data->active_measured_latency[i][WRITE]), + le16_to_cpu(log_data->active_measured_latency[i][TRIM])); + } + + for (i = 0; i <= 3; i++) { + printf(" Active Latency Time Stamp: Bucket %d ", i); + for (j = 0; j <= 2; j++) { + if (le64_to_cpu(log_data->active_latency_timestamp[i][j]) == -1) + printf(" N/A "); + else { + wdc_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][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 <= 3; i++) { + printf(" Static Measured Latency: Bucket %d %27d ms %27d ms %27d ms \n", + i, le16_to_cpu(log_data->static_measured_latency[i][READ]), le16_to_cpu(log_data->static_measured_latency[i][WRITE]), + le16_to_cpu(log_data->static_measured_latency[i][TRIM])); + } + + for (i = 0; i <= 3; i++) { + printf(" Static Latency Time Stamp: Bucket %d ", i); + for (j = 0; j <= 2; j++) { + if (le64_to_cpu(log_data->static_latency_timestamp[i][j]) == -1) + printf(" N/A "); + else { + wdc_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; + 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 = 0; j <= 2; j++) { + sprintf(buf, "Active Bucket Counter: Bucket %d %s", i, operation[j]); + json_object_add_value_int(root, buf, le32_to_cpu(log_data->active_bucket_counter[i][j])); + } + } + for (i = 0; i <= 3; i++) { + for (j = 0; j <= 2; j++) { + sprintf(buf, "Active Measured Latency: Bucket %d %s", i, operation[j]); + json_object_add_value_int(root, buf, le16_to_cpu(log_data->active_measured_latency[i][j])); + } + } + for (i = 0; i <= 3; i++) { + for (j = 0; j <= 2; j++) { + sprintf(buf, "Active Latency Time Stamp: Bucket %d %s", i, operation[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 = 0; j <= 2; j++) { + sprintf(buf, "Static Bucket Counter: Bucket %d %s", i, operation[j]); + json_object_add_value_int(root, buf, le32_to_cpu(log_data->static_bucket_counter[i][j])); + } + } + for (i = 0; i <= 3; i++) { + for (j = 0; j <= 2; j++) { + sprintf(buf, "Static Measured Latency: Bucket %d %s", i, operation[j]); + json_object_add_value_int(root, buf, le16_to_cpu(log_data->static_measured_latency[i][j])); + } + } + for (i = 0; i <= 3; i++) { + for (j = 0; j <= 2; j++) { + sprintf(buf, "Static Latency Time Stamp: Bucket %d %s", i, operation[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; + 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 Desriptor : 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; + 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; + 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; + uint64_t converted = 0; + + root = json_create_object(); + json_object_add_value_int(root, "NAND Bytes Written Hi", le64_to_cpu(perf->nand_bytes_wr_hi)); + json_object_add_value_int(root, "NAND Bytes Written Lo", le64_to_cpu(perf->nand_bytes_wr_lo)); + json_object_add_value_int(root, "NAND Bytes Read Hi", le64_to_cpu(perf->nand_bytes_rd_hi)); + json_object_add_value_int(root, "NAND Bytes Read Lo", le64_to_cpu(perf->nand_bytes_rd_lo)); + + converted = le64_to_cpu(perf->nand_bad_block); + json_object_add_value_int(root, "NAND Bad Block Count (Normalized)", + converted & 0xFFFF); + json_object_add_value_int(root, "NAND Bad Block Count (Raw)", + converted >> 16); + + json_object_add_value_int(root, "Uncorrectable Read Count", le64_to_cpu(perf->uncorr_read_count)); + json_object_add_value_int(root, "Soft ECC Error Count", le64_to_cpu(perf->ecc_error_count)); + json_object_add_value_int(root, "SSD End to End Detected Correction Count", + le32_to_cpu(perf->ssd_detect_count)); + json_object_add_value_int(root, "SSD End to End Corrected Correction Count", + le32_to_cpu(perf->ssd_correct_count)); + json_object_add_value_int(root, "System Data Percent Used", + perf->data_percent_used); + json_object_add_value_int(root, "User Data Erase Counts Max", + le32_to_cpu(perf->data_erase_max)); + json_object_add_value_int(root, "User Data Erase Counts Min", + le32_to_cpu(perf->data_erase_min)); + json_object_add_value_int(root, "Refresh Count", le64_to_cpu(perf->refresh_count)); + + converted = le64_to_cpu(perf->program_fail); + json_object_add_value_int(root, "Program Fail Count (Normalized)", + converted & 0xFFFF); + json_object_add_value_int(root, "Program Fail Count (Raw)", + converted >> 16); + + converted = le64_to_cpu(perf->user_erase_fail); + json_object_add_value_int(root, "User Data Erase Fail Count (Normalized)", + converted & 0xFFFF); + json_object_add_value_int(root, "User Data Erase Fail Count (Raw)", + converted >> 16); + + converted = le64_to_cpu(perf->system_erase_fail); + json_object_add_value_int(root, "System Area Erase Fail Count (Normalized)", + converted & 0xFFFF); + json_object_add_value_int(root, "System Area Erase Fail Count (Raw)", + converted >> 16); + + json_object_add_value_int(root, "Thermal Throttling Status", + perf->thermal_throttle_status); + json_object_add_value_int(root, "Thermal Throttling Count", + perf->thermal_throttle_count); + json_object_add_value_int(root, "PCIe Correctable Error", le64_to_cpu(perf->pcie_corr_error)); + json_object_add_value_int(root, "Incomplete Shutdown Counte", le32_to_cpu(perf->incomplete_shutdown_count)); + json_object_add_value_int(root, "Percent Free Blocks", perf->percent_free_blocks); + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); +} + +static void wdc_print_bd_ca_log_normal(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; + + 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; + + 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) +{ + 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) { + 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 { + 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) == 0) + 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) == 0) + 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) +{ + struct json_object *root; + int i, j; + char previous_fw[9]; + char new_fw[9]; + char commit_action_bin[8]; + char fail_str[32]; + char time_str[11]; + 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 *)fail_str, 0, 11); + char *null_fw = "--------"; + __u16 oldestEntryIdx = 0, entryIdx = 0; + + root = json_create_object(); + + 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 { + 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) == 0) + 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) == 0) + 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; + + if ((log_ptr = (__u8*) malloc(sizeof (__u8) * WDC_NVME_SMART_CLOUD_ATTR_LEN)) == NULL) { + 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 == 0) { + + /* 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; + wdc_nvme_hw_rev_log *log_ptr = NULL; + + if ((log_ptr = (wdc_nvme_hw_rev_log *)malloc(sizeof (__u8) * WDC_NVME_HW_REV_LOG_PAGE_LEN)) == NULL) { + 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 == 0) { + + /* 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; + wdc_nvme_hw_rev_log *log_data = (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) +{ + wdc_nvme_hw_rev_log *log_data = (wdc_nvme_hw_rev_log *)data; + struct json_object *root; + char json_data[80]; + + root = json_create_object(); + 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; + wdc_nvme_ext_smart_log *ext_smart_log_ptr = (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) +{ + wdc_nvme_ext_smart_log *ext_smart_log_ptr = (wdc_nvme_ext_smart_log *)data; + struct json_object *root; + + 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; + uint16_t smart_log_ver = 0; + + root = json_create_object(); + 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; + + 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(nvme_root_t r, struct nvme_dev *dev, char *format, + int uuid_index, __u32 namespace_id) +{ + int ret = 0; + int fmt = -1; + int i = 0; + __u8 *data; + __u32 cust_id; + uint32_t device_id, read_vendor_id; + + if (!wdc_check_device(r, dev)) + return -1; + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id); + + switch (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_SN840_DEV_ID: + case WDC_NVME_SN840_DEV_ID_1: + case WDC_NVME_SN860_DEV_ID: + 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: + 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)) + { + if (uuid_index == 0) + { + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_NVME_SMART_CLOUD_ATTR_LEN)) == NULL) { + 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 == 0) { + + /* 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 == 0) { + + /* 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) { + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_NVME_EOL_STATUS_LOG_LEN)) == NULL) { + 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 == 0) { + /* 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; + } + } + else { + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_NVME_EOL_STATUS_LOG_LEN)) == NULL) { + 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 == 0) { + /* 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); + } + break; + + case WDC_NVME_ZN350_DEV_ID: + case WDC_NVME_ZN350_DEV_ID_1: + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_NVME_SMART_CLOUD_ATTR_LEN)) == NULL) { + 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 == 0) { + /* 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 == 0) { + /* 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) +{ + 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); + break; + case JSON: + wdc_print_fw_act_history_log_json(data, num_entries, cust_id, vendor_id); + break; + } + return 0; +} + +static int wdc_get_ca_log_page(nvme_root_t r, struct nvme_dev *dev, char *format) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + struct wdc_ssd_ca_perf_stats *perf; + uint32_t read_device_id, read_vendor_id; + __u32 cust_id; + + if (!wdc_check_device(r, dev)) + return -1; + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + /* verify the 0xCA log page is supported */ + if (wdc_nvme_check_supported_log_page(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) { + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_FB_CA_LOG_BUF_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + + memset(data, 0, sizeof (__u8) * WDC_FB_CA_LOG_BUF_LEN); + + ret = nvme_get_log_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 == 0) { + /* parse the data */ + perf = (struct wdc_ssd_ca_perf_stats *)(data); + ret = wdc_print_fb_ca_log(perf, fmt); + } else { + fprintf(stderr, "ERROR : WDC : Unable to read CA Log Page data\n"); + ret = -1; + } + } else { + + fprintf(stderr, "ERROR : WDC : Unsupported Customer id, id = 0x%x\n", cust_id); + return -1; + } + break; + + case WDC_NVME_SN640_DEV_ID: + case WDC_NVME_SN640_DEV_ID_1: + case WDC_NVME_SN640_DEV_ID_2: + case WDC_NVME_SN640_DEV_ID_3: + case WDC_NVME_SN840_DEV_ID: + case WDC_NVME_SN840_DEV_ID_1: + case WDC_NVME_SN860_DEV_ID: + if (cust_id == WDC_CUSTOMER_ID_0x1005) { + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_FB_CA_LOG_BUF_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + + memset(data, 0, sizeof (__u8) * WDC_FB_CA_LOG_BUF_LEN); + + ret = nvme_get_log_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 == 0) { + /* parse the data */ + perf = (struct wdc_ssd_ca_perf_stats *)(data); + ret = wdc_print_fb_ca_log(perf, fmt); + } else { + fprintf(stderr, "ERROR : WDC : Unable to read CA Log Page data\n"); + ret = -1; + } + } else if ((cust_id == WDC_CUSTOMER_ID_GN) || (cust_id == WDC_CUSTOMER_ID_GD) || + (cust_id == WDC_CUSTOMER_ID_BD)) { + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_BD_CA_LOG_BUF_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + + memset(data, 0, sizeof (__u8) * WDC_BD_CA_LOG_BUF_LEN); + ret = nvme_get_log_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 == 0) { + /* 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; + } + + break; + } 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; + break; + } + + free(data); + return ret; +} + +static int wdc_get_c1_log_page(nvme_root_t r, struct nvme_dev *dev, + char *format, uint8_t interval) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + __u8 *p; + int i; + int skip_cnt = 4; + int total_subpages; + struct wdc_log_page_header *l; + struct wdc_log_page_subpage_header *sph; + struct wdc_ssd_perf_stats *perf; + + if (!wdc_check_device(r, dev)) + return -1; + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + if (interval < 1 || interval > 15) { + fprintf(stderr, "ERROR : WDC : interval out of range [1-15]\n"); + return -1; + } + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_ADD_LOG_BUF_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + memset(data, 0, sizeof (__u8) * WDC_ADD_LOG_BUF_LEN); + + ret = nvme_get_log_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 == 0) { + l = (struct wdc_log_page_header*)data; + total_subpages = l->num_subpages + WDC_NVME_GET_STAT_PERF_INTERVAL_LIFETIME - 1; + for (i = 0, p = data + skip_cnt; i < total_subpages; i++, p += skip_cnt) { + sph = (struct wdc_log_page_subpage_header *) p; + if (sph->spcode == WDC_GET_LOG_PAGE_SSD_PERFORMANCE) { + if (sph->pcset == interval) { + perf = (struct wdc_ssd_perf_stats *) (p + 4); + ret = wdc_print_log(perf, fmt); + break; + } + } + skip_cnt = le16_to_cpu(sph->subpage_length) + 4; + } + if (ret) { + fprintf(stderr, "ERROR : WDC : Unable to read data from buffer\n"); + } + } + free(data); + return ret; +} + +static int wdc_get_c3_log_page(nvme_root_t r, struct nvme_dev *dev, char *format) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + int i; + struct wdc_ssd_latency_monitor_log *log_data; + + if (!wdc_check_device(r, dev)) + return -1; + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + if ((data = (__u8 *) malloc(sizeof(__u8) * WDC_LATENCY_MON_LOG_BUF_LEN)) == NULL) { + 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 == 0) { + 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) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + int i; + struct wdc_ocp_c1_error_recovery_log *log_data; + + if (!wdc_check_device(r, dev)) + return -1; + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + if ((data = (__u8 *) malloc(sizeof(__u8) * WDC_ERROR_REC_LOG_BUF_LEN)) == NULL) { + 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 == 0) { + 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) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + int i; + struct wdc_ocp_C4_dev_cap_log *log_data; + + if (!wdc_check_device(r, dev)) + return -1; + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + if ((data = (__u8 *) malloc(sizeof(__u8) * WDC_DEV_CAP_LOG_BUF_LEN)) == NULL) { + 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 == 0) { + 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) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + int i; + struct wdc_ocp_C5_unsupported_reqs *log_data; + + if (!wdc_check_device(r, dev)) + return -1; + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + if ((data = (__u8 *) malloc(sizeof(__u8) * WDC_UNSUPPORTED_REQS_LOG_BUF_LEN)) == NULL) { + 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 == 0) { + 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) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + struct wdc_ssd_d0_smart_log *perf; + + if (!wdc_check_device(r, dev)) + return -1; + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + /* verify the 0xD0 log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, WDC_NVME_GET_VU_SMART_LOG_OPCODE) == false) { + fprintf(stderr, "ERROR : WDC : 0xD0 Log Page not supported\n"); + return -1; + } + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_NVME_VU_SMART_LOG_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + memset(data, 0, sizeof (__u8) * WDC_NVME_VU_SMART_LOG_LEN); + + ret = nvme_get_log_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 == 0) { + /* parse the data */ + perf = (struct wdc_ssd_d0_smart_log *)(data); + ret = wdc_print_d0_log(perf, fmt); + } else { + fprintf(stderr, "ERROR : WDC : Unable to read D0 Log Page data\n"); + ret = -1; + } + + free(data); + return ret; +} + +static int wdc_vs_smart_add_log(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve additional performance statistics."; + const char *interval = "Interval to read the statistics from [1, 15]."; + 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"; + 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; + + 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: normal|json"), + 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 == 0) { + 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 == 0) + { + 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 == 0) + fprintf(stderr, "ERROR : WDC: Unknown log page mask - %s\n", cfg.log_page_mask); + + + capabilities = wdc_get_drive_capabilities(r, dev); + + if ((capabilities & WDC_DRIVE_CAP_SMART_LOG_MASK) == 0) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + goto out; + } + + if (((capabilities & WDC_DRIVE_CAP_C0_LOG_PAGE) == WDC_DRIVE_CAP_C0_LOG_PAGE) && + (page_mask & WDC_C0_PAGE_MASK)) { + /* Get 0xC0 log page if possible. */ + ret = wdc_get_c0_log_page(r, dev, cfg.output_format, + uuid_index, cfg.namespace_id); + if (ret) + fprintf(stderr, "ERROR : WDC : Failure reading the C0 Log Page, ret = %d\n", ret); + } + if (((capabilities & (WDC_DRIVE_CAP_CA_LOG_PAGE)) == (WDC_DRIVE_CAP_CA_LOG_PAGE)) && + (page_mask & WDC_CA_PAGE_MASK)) { + /* 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"; + __u64 capabilities = 0; + struct nvme_dev *dev; + int ret, fmt = -1; + nvme_root_t r; + __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) == 0) { + 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 == 0) { + fmt = validate_output_format(cfg.output_format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC %s: invalid output format\n", __func__); + ret = fmt; + } + + /* 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"; + __u64 capabilities = 0; + struct nvme_dev *dev; + int ret, fmt = -1; + __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) == 0) { + 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 == 0) { + fmt = validate_output_format(cfg.output_format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC %s: invalid output format\n", __func__); + ret = fmt; + 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; + } + } 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; + struct nvme_dev *dev; + __u8 *data; + nvme_root_t r; + int ret = 0; + int fmt = -1; + __u64 capabilities = 0; + 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) == 0) { + 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 == 0) { + ext_smart_log_ptr = (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); + + fmt = validate_output_format(cfg.output_format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC %s: invalid output format\n", __func__); + ret = fmt; + goto out; + } + + if (data_units_written == 0) { + 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) == 0) { + 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) == 0) { + 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) == 0) { + 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) == 0) { + 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) == 0) { + 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_OPCODE) == false) { + fprintf(stderr, "ERROR : WDC : 0xC2 Log Page not supported\n"); + ret = -1; + goto out; + } + + /* Get the assert dump present status */ + if (!wdc_nvme_get_dev_status_log_data(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) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + struct wdc_fw_act_history_log_hdr *fw_act_history_hdr; + + if (!wdc_check_device(r, dev)) + return -1; + + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + /* verify the FW Activate History log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID) == false) { + fprintf(stderr, "ERROR : WDC : %d Log Page not supported\n", WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID); + return -1; + } + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_FW_ACT_HISTORY_LOG_BUF_LEN)) == NULL) { + fprintf(stderr, "ERROR : WDC : malloc : %s\n", strerror(errno)); + return -1; + } + + memset(data, 0, sizeof (__u8) * WDC_FW_ACT_HISTORY_LOG_BUF_LEN); + + ret = nvme_get_log_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 == 0) { + /* 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); + else if (fw_act_history_hdr->num_entries == 0) { + 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) +{ + int ret = 0; + int fmt = -1; + __u8 *data; + __u32 cust_id; + 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; + + if (!wdc_check_device(r, dev)) + return -1; + + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + return fmt; + } + + ret = wdc_get_pci_ids(r, dev, &device_id, &vendor_id); + + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_FW_ACT_HISTORY_C2_LOG_BUF_LEN)) == NULL) { + 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 == 0) { + /* 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 */ + 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); + } 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) == 0) { + 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 */ + /* to retrieve fw activate history data */ + if ((data = (__u8*) malloc(sizeof (__u8) * WDC_NVME_SMART_CLOUD_ATTR_LEN)) == NULL) { + 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 == 0) { + /* 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) == 0) { + 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 == 0) { + if (result) + fprintf(stderr, "Controller Option Telemetry Log Page State: Disabled\n"); + else + fprintf(stderr, "Controller Option Telemetry Log Page State: Enabled\n"); + } else { + 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 */ + if (WDC_STATUS_SUCCESS != (ret = wdc_de_VU_read_size(dev, 0, 5, (__u32*)&headerPayloadSize))) + { + 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 */ + if (WDC_STATUS_SUCCESS != (ret = wdc_de_VU_read_buffer(dev, 0, 5, 0, fileIdOffsetsBuffer, &fileIdOffsetsBufferSize))) + { + fprintf(stderr, "ERROR : WDC : %s: Failed to get fileIdOffsets from file directory 0x%x\n", + __func__, ret); + goto end; + } + /* 3.Determine valid entries */ + for (fileNum = 0; fileNum < (headerPayloadSize - WDC_DE_FILE_HEADER_SIZE) / WDC_DE_FILE_OFFSET_SIZE; fileNum++) + { + fileOffset = (fileIdOffsetsBuffer[WDC_DE_FILE_HEADER_SIZE + (fileNum * WDC_DE_FILE_OFFSET_SIZE)] << 8) + + fileIdOffsetsBuffer[WDC_DE_FILE_HEADER_SIZE + (fileNum * WDC_DE_FILE_OFFSET_SIZE) + 1]; + if (!fileOffset) + continue; + (*maxNumOfEntries)++; + } +end: + free(fileIdOffsetsBuffer); + return ret; +} + +static WDC_DRIVE_ESSENTIAL_TYPE wdc_get_essential_type(__u8 fileName[]) +{ + WDC_DRIVE_ESSENTIAL_TYPE essentialType = WDC_DE_TYPE_NONE; + + if (wdc_UtilsStrCompare((char*)fileName, WDC_DE_CORE_DUMP_FILE_NAME) == 0) + { + essentialType = WDC_DE_TYPE_DUMPSNAPSHOT; + } + else if (wdc_UtilsStrCompare((char*)fileName, WDC_DE_EVENT_LOG_FILE_NAME) == 0) + { + essentialType = WDC_DE_TYPE_EVENTLOG; + } + else if (wdc_UtilsStrCompare((char*)fileName, WDC_DE_MANUFACTURING_INFO_PAGE_FILE_NAME) == 0) + { + essentialType = WDC_DE_TYPE_NVME_MANF_INFO; + } + + return essentialType; +} + +static int wdc_fetch_log_directory(struct nvme_dev *dev, PWDC_DE_VU_LOG_DIRECTORY directory) +{ + int ret = WDC_STATUS_FAILURE; + __u8 *fileOffset = NULL; + __u8 *fileDirectory = NULL; + __u32 headerSize = 0; + __u32 fileNum = 0, startIdx = 0; + __u16 fileOffsetTemp = 0; + __u32 entryId = 0; + __u32 fileDirectorySize = 0; + + if (!dev || !directory) { + ret = WDC_STATUS_INVALID_PARAMETER; + goto end; + } + + ret = wdc_de_VU_read_size(dev, 0, 5, &fileDirectorySize); + if (WDC_STATUS_SUCCESS != ret) { + fprintf(stderr, + "ERROR : WDC : %s: Failed to get filesystem directory size, ret = %d\n", + __func__, ret); + goto end; + } + + fileDirectory = (__u8*)calloc(1, fileDirectorySize); + ret = wdc_de_VU_read_buffer(dev, 0, 5, 0, fileDirectory, &fileDirectorySize); + if (WDC_STATUS_SUCCESS != ret) { + fprintf(stderr, + "ERROR : WDC : %s: Failed to get filesystem directory, ret = %d\n", + __func__, ret); + goto end; + } + + /* First four bytes of header directory is headerSize */ + memcpy(&headerSize, fileDirectory, WDC_DE_FILE_HEADER_SIZE); + + /* minimum buffer for 1 entry is required */ + if (directory->maxNumLogEntries == 0) { + ret = WDC_STATUS_INVALID_PARAMETER; + goto end; + } + + for (fileNum = 0; + fileNum < (headerSize - WDC_DE_FILE_HEADER_SIZE) / WDC_DE_FILE_OFFSET_SIZE; + fileNum++) { + if (entryId >= directory->maxNumLogEntries) + break; + + startIdx = WDC_DE_FILE_HEADER_SIZE + (fileNum * WDC_DE_FILE_OFFSET_SIZE); + memcpy(&fileOffsetTemp, fileDirectory + startIdx, sizeof(fileOffsetTemp)); + fileOffset = fileDirectory + fileOffsetTemp; + + if (0 == fileOffsetTemp) + continue; + + memset(&directory->logEntry[entryId], 0, sizeof(WDC_DRIVE_ESSENTIALS)); + memcpy(&directory->logEntry[entryId].metaData, fileOffset, sizeof(WDC_DE_VU_FILE_META_DATA)); + directory->logEntry[entryId].metaData.fileName[WDC_DE_FILE_NAME_SIZE - 1] = '\0'; + wdc_UtilsDeleteCharFromString((char*)directory->logEntry[entryId].metaData.fileName, + WDC_DE_FILE_NAME_SIZE, ' '); + if (0 == directory->logEntry[entryId].metaData.fileID) + continue; + + directory->logEntry[entryId].essentialType = wdc_get_essential_type(directory->logEntry[entryId].metaData.fileName); + /*fprintf(stderr, "WDC : %s: NVMe VU Log Entry %d, fileName = %s, fileSize = 0x%lx, fileId = 0x%x\n", + __func__, entryId, directory->logEntry[entryId].metaData.fileName, + (long unsigned int)directory->logEntry[entryId].metaData.fileSize, directory->logEntry[entryId].metaData.fileID); + */ + entryId++; + } + + directory->numOfValidLogEntries = entryId; +end: + if (fileDirectory != NULL) + free(fileDirectory); + return ret; +} + +static int wdc_fetch_log_file_from_device(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, (long unsigned int)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, (long unsigned int)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 == NULL) + { + fprintf(stderr, "ERROR : WDC : %s: readBuffer calloc failed\n", __func__); + ret = WDC_STATUS_INSUFFICIENT_MEMORY; + break; + } + + for (i = 0; i < chunks; i++) + { + offset = ((i*chunkSize) / 4); + + /* Last loop call, Assign readBufferLen to read only left over bytes */ + if (i == (chunks - 1)) + { + readBufferLen = lastPktReadBufferLen; + } + + ret = wdc_de_VU_read_buffer(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; +} + +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; + __u32 maxNumOfVUFiles = 0; + struct nvme_id_ctrl ctrl; + struct nvme_id_ns ns; + struct nvme_error_log_page *elogBuffer; + struct nvme_smart_log smart_log; + struct nvme_firmware_slot fw_log; + PWDC_NVME_DE_VU_LOGPAGES vuLogInput = NULL; + WDC_DE_VU_LOG_DIRECTORY deEssentialsList; + + memset(bufferFolderPath,0,sizeof(bufferFolderPath)); + memset(bufferFolderName,0,sizeof(bufferFolderName)); + memset(tarFileName,0,sizeof(tarFileName)); + memset(tarFiles,0,sizeof(tarFiles)); + memset(tarCmd,0,sizeof(tarCmd)); + memset(&timeInfo,0,sizeof(timeInfo)); + + 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; + } else { + fprintf(stderr, "Get Drive Essentials Data for device serial #: %s and fw revision: %s\n", + idSerialNo, idFwRev); + } + + /* Create Drive Essentials directory */ + wdc_UtilsGetTime(&timeInfo); + memset(timeString, 0, sizeof(timeString)); + wdc_UtilsSnprintf((char*)timeString, MAX_PATH_LEN, "%02u%02u%02u_%02u%02u%02u", + timeInfo.year, timeInfo.month, timeInfo.dayOfMonth, + timeInfo.hour, timeInfo.minute, timeInfo.second); + + wdc_UtilsSnprintf((char*)serialNo,WDC_SERIAL_NO_LEN,(char*)idSerialNo); + /* Remove any space form serialNo */ + wdc_UtilsDeleteCharFromString((char*)serialNo, WDC_SERIAL_NO_LEN, ' '); + + memset(firmwareRevision, 0, sizeof(firmwareRevision)); + wdc_UtilsSnprintf((char*)firmwareRevision, WDC_NVME_FIRMWARE_REV_LEN, (char*)idFwRev); + /* Remove any space form FirmwareRevision */ + wdc_UtilsDeleteCharFromString((char*)firmwareRevision, WDC_NVME_FIRMWARE_REV_LEN, ' '); + + wdc_UtilsSnprintf((char*)bufferFolderName, MAX_PATH_LEN, "%s_%s_%s_%s", + "DRIVE_ESSENTIALS", (char*)serialNo, (char*)firmwareRevision, (char*)timeString); + + if (dir != NULL) { + wdc_UtilsSnprintf((char*)bufferFolderPath, MAX_PATH_LEN, "%s%s%s", + (char *)dir, WDC_DE_PATH_SEPARATOR, (char *)bufferFolderName); + } else { + retPtr = getcwd((char*)currDir, MAX_PATH_LEN); + if (retPtr != NULL) + wdc_UtilsSnprintf((char*)bufferFolderPath, MAX_PATH_LEN, "%s%s%s", + (char *)currDir, WDC_DE_PATH_SEPARATOR, (char *)bufferFolderName); + else { + fprintf(stderr, "ERROR : WDC : get current working directory failed\n"); + return -1; + } + } + + ret = wdc_UtilsCreateDir((char*)bufferFolderPath); + if (ret != 0) + { + fprintf(stderr, "ERROR : WDC : create directory failed, ret = %d, dir = %s\n", ret, bufferFolderPath); + return -1; + } else { + fprintf(stderr, "Store Drive Essentials bin files in directory: %s\n", bufferFolderPath); + } + + /* Get Identify Controller Data */ + memset(&ctrl, 0, sizeof (struct nvme_id_ctrl)); + ret = nvme_identify_ctrl(dev_fd(dev), &ctrl); + if (ret) { + fprintf(stderr, "ERROR : WDC : nvme_identify_ctrl() failed, ret = %d\n", ret); + return -1; + } else { + wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", (char*)bufferFolderPath, WDC_DE_PATH_SEPARATOR, + "IdentifyController", (char*)serialNo, (char*)timeString); + wdc_WriteToFile(fileName, (char*)&ctrl, sizeof (struct nvme_id_ctrl)); + } + + memset(&ns, 0, sizeof (struct nvme_id_ns)); + ret = nvme_identify_ns(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 = (PWDC_NVME_DE_VU_LOGPAGES)calloc(1, sizeof(WDC_NVME_DE_VU_LOGPAGES)); + vuLogInput->numOfVULogPages = sizeof(deVULogPagesList) / sizeof(deVULogPagesList[0]); + + for (vuLogIdx = 0; vuLogIdx < vuLogInput->numOfVULogPages; vuLogIdx++) + { + dataBufferSize = deVULogPagesList[vuLogIdx].logPageLen; + dataBuffer = calloc(1, dataBufferSize); + memset(dataBuffer, 0, dataBufferSize); + + ret = nvme_get_log_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 < (sizeof(deFeatureIdList) / sizeof(deFeatureIdList[0])); listIdx++) + { + memset(featureIdBuff, 0, sizeof(featureIdBuff)); + /* skipping LbaRangeType as it is an optional nvme command and not supported */ + if (deFeatureIdList[listIdx].featureId == FID_LBA_RANGE_TYPE) + continue; + ret = nvme_get_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)); + } + } + + /* Read Debug Directory */ + ret = wdc_get_log_dir_max_entries(dev, &maxNumOfVUFiles); + if (ret == WDC_STATUS_SUCCESS) + { + memset(&deEssentialsList, 0, sizeof(deEssentialsList)); + deEssentialsList.logEntry = (WDC_DRIVE_ESSENTIALS*)calloc(1, sizeof(WDC_DRIVE_ESSENTIALS)*maxNumOfVUFiles); + deEssentialsList.maxNumLogEntries = maxNumOfVUFiles; + + /* Fetch VU File Directory */ + ret = wdc_fetch_log_directory(dev, &deEssentialsList); + if (ret == WDC_STATUS_SUCCESS) + { + /* Get Debug Data Files */ + for (listIdx = 0; listIdx < deEssentialsList.numOfValidLogEntries; listIdx++) + { + if (0 == deEssentialsList.logEntry[listIdx].metaData.fileSize) + { + fprintf(stderr, "ERROR : WDC : File Size for %s is 0\n", + deEssentialsList.logEntry[listIdx].metaData.fileName); + ret = WDC_STATUS_FILE_SIZE_ZERO; + } else { + /* Fetch Log File Data */ + dataBuffer = (char *)calloc(1, (size_t)deEssentialsList.logEntry[listIdx].metaData.fileSize); + ret = wdc_fetch_log_file_from_device(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); + dataBuffer = NULL; + } + } + } else { + fprintf(stderr, "WDC : wdc_fetch_log_directory failed, ret = %d\n", ret); + } + + free(deEssentialsList.logEntry); + deEssentialsList.logEntry = NULL; + } else { + fprintf(stderr, "WDC : wdc_get_log_dir_max_entries failed, ret = %d\n", ret); + } + + /* Get Dump Trace Data */ + wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", (char*)bufferFolderPath, WDC_DE_PATH_SEPARATOR, "dumptrace", serialNo, timeString); + if (WDC_STATUS_SUCCESS != (ret = wdc_de_get_dump_trace(dev, (char*)bufferFolderPath, 0, fileName))) + { + fprintf(stderr, "ERROR : WDC : wdc_de_get_dump_trace failed, ret = %d\n", ret); + } + + /* Tar the Drive Essentials directory */ + wdc_UtilsSnprintf(tarFileName, sizeof(tarFileName), "%s%s", (char*)bufferFolderPath, WDC_DE_TAR_FILE_EXTN); + if (dir != NULL) { + wdc_UtilsSnprintf(tarFiles, sizeof(tarFiles), "%s%s%s%s%s", + (char*)dir, WDC_DE_PATH_SEPARATOR, (char*)bufferFolderName, WDC_DE_PATH_SEPARATOR, WDC_DE_TAR_FILES); + } else { + wdc_UtilsSnprintf(tarFiles, sizeof(tarFiles), "%s%s%s", (char*)bufferFolderName, WDC_DE_PATH_SEPARATOR, WDC_DE_TAR_FILES); + } + wdc_UtilsSnprintf(tarCmd, sizeof(tarCmd), "%s %s %s", WDC_DE_TAR_CMD, (char*)tarFileName, (char*)tarFiles); + + ret = system(tarCmd); + + if (ret) { + fprintf(stderr, "ERROR : WDC : Tar of Drive Essentials data failed, ret = %d\n", ret); + } + + fprintf(stderr, "Get of Drive Essentials data successful\n"); + 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 != NULL) { + 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 != 0) + printf("ERROR : WDC: Namespace Resize of namespace id 0x%x, op option 0x%x failed\n", cfg.namespace_id, cfg.op_option); + } else { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + } + + 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_fd; + } + + if (cfg.file != NULL) { + int verify_file; + + /* verify the passed in file name and path is valid before getting the dump data */ + verify_file = open(cfg.file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (verify_file < 0) { + fprintf(stderr, "ERROR : WDC: open : %s\n", strerror(errno)); + ret = -1; + goto close_fd; + } + close(verify_file); + strncpy(f, cfg.file, PATH_MAX - 1); + } else { + wdc_UtilsGetTime(&timeInfo); + memset(timeStamp, 0, sizeof(timeStamp)); + wdc_UtilsSnprintf((char*)timeStamp, MAX_PATH_LEN, "%02u%02u%02u_%02u%02u%02u", + timeInfo.year, timeInfo.month, timeInfo.dayOfMonth, + timeInfo.hour, timeInfo.minute, timeInfo.second); + if (cfg.log_id == NVME_LOG_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_fd; + } + if (strlen(f) > PATH_MAX - 5) { + fprintf(stderr, "ERROR : WDC: file name overflow\n"); + ret = -1; + goto close_fd; + } + 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_fd: + 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 int wdc_log_page_directory(int argc, char **argv, struct command *command, + struct plugin *plugin) +{ + const char *desc = "Retrieve Log Page Directory."; + 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; + + 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); + if (ret < 0) { + fprintf(stderr, "%s: ERROR : WDC : invalid output format\n", __func__); + dev_close(dev); + return ret; + } + ret = 0; + + r = nvme_scan(NULL); + capabilities = wdc_get_drive_capabilities(r, dev); + + if ((capabilities & WDC_DRIVE_CAP_LOG_PAGE_DIR) == 0) { + fprintf(stderr, "ERROR : WDC: unsupported device for this command\n"); + ret = -1; + } else { + ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id); + log_id = (device_id == WDC_NVME_ZN350_DEV_ID || device_id == WDC_NVME_ZN350_DEV_ID_1) ? + WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE_C8 : WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_OPCODE; + /* verify the 0xC2 Device Manageability log page is supported */ + if (wdc_nvme_check_supported_log_page(r, dev, log_id) == false) { + fprintf(stderr, "%s: ERROR : WDC : 0x%x Log Page not supported\n", __func__, log_id); + ret = -1; + goto out; + } + + if (get_dev_mgment_cbs_data(r, dev, WDC_C2_LOG_PAGES_SUPPORTED_ID, (void *)&cbs_data)) { + if (cbs_data != NULL) { + printf("Log Page Directory\n"); + /* print the supported pages */ + if (!strcmp(cfg.output_format, "normal")) { + for (i = 0; i < le32_to_cpu(cbs_data->length); i++) { + printf("0x%x - %s\n", cbs_data->data[i], + nvme_log_id_to_string(cbs_data->data[i])); + } + } else if (!strcmp(cfg.output_format, "binary")) { + d((__u8 *)cbs_data->data, le32_to_cpu(cbs_data->length), 16, 1); + } else if (!strcmp(cfg.output_format, "json")) { + struct json_object *root; + root = json_create_object(); + + for (i = 0; i < le32_to_cpu(cbs_data->length); i++) { + json_object_add_value_int(root, nvme_log_id_to_string(cbs_data->data[i]), + cbs_data->data[i]); + } + + json_print_object(root, NULL); + printf("\n"); + json_free_object(root); + } else + fprintf(stderr, "%s: ERROR : WDC : Invalid format, format = %s\n", __func__, cfg.output_format); + + free(cbs_data); + } else + fprintf(stderr, "%s: ERROR : WDC : NULL_data ptr\n", __func__); + } else + fprintf(stderr, "%s: ERROR : WDC : 0xC2 Log Page entry ID 0x%x not found\n", __func__, WDC_C2_LOG_PAGES_SUPPORTED_ID); + + + } + + out: + 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 == NULL) { + fprintf(stderr, "%s: ERROR : malloc failed, size : 0x%x, status : %s\n", __func__, log_hdr_size, strerror(errno)); + ret = -1; + goto out; + } + memset(log_hdr, 0, log_hdr_size); + + ret = wdc_dump_telemetry_hdr(dev, log_id, log_hdr); + if (ret != 0) { + fprintf(stderr, "%s: ERROR : get telemetry header failed, ret : %d\n", __func__, ret); + ret = -1; + goto out; + } + + reason_id_size = sizeof(log_hdr->rsnident); + + if (log_id == NVME_LOG_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; + 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; + 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) +{ + int ret; + int fmt = -1; + uint8_t *data = NULL; + + 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 retreive NAND stats\n", __func__); + goto out; + } else { + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : %s : invalid output format\n", __func__); + ret = fmt; + 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; + } + } + +out: + if (data) + free(data); + return ret; +} + +static int wdc_do_vs_nand_stats(struct nvme_dev *dev, char *format) +{ + int ret; + int fmt = -1; + uint8_t *output = NULL; + __u16 version = 0; + + if ((output = (uint8_t*)calloc(WDC_NVME_NAND_STATS_SIZE, sizeof(uint8_t))) == NULL) { + fprintf(stderr, "ERROR : WDC : calloc : %s\n", strerror(errno)); + ret = -1; + goto out; + } + + ret = nvme_get_log_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 retreive NAND stats\n", __func__); + goto out; + } else { + fmt = validate_output_format(format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + ret = fmt; + goto out; + } + + 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; + } + } + +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) == 0) { + 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."; + struct nvme_dev *dev; + int ret = 0; + nvme_root_t r; + __u64 capabilities = 0; + int fmt = -1; + struct wdc_vs_pcie_stats *pcieStatsPtr = NULL; + int pcie_stats_size = sizeof(struct wdc_vs_pcie_stats); + bool huge; + + 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); + fmt = validate_output_format(cfg.output_format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + ret = fmt; + goto out; + } + + pcieStatsPtr = nvme_alloc(pcie_stats_size, &huge); + if (pcieStatsPtr == NULL) { + 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) == 0) { + 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; + } + } + } + + nvme_free(pcieStatsPtr, huge); + +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."; + 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; + int fmt = -1; + struct json_object *root = NULL; + char formatter[41] = { 0 }; + char rev_str[16] = { 0 }; + uint32_t read_device_id = -1, read_vendor_id = -1; + wdc_nvme_ext_smart_log *ext_smart_log_ptr = NULL; + + 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; + + fmt = validate_output_format(cfg.output_format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC %s invalid output format\n", __func__); + dev_close(dev); + return fmt; + } + + /* 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 Revison: %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 Revison", 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_SN730A_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 == 0) { + wdc_nvme_hw_rev_log *log_data = (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) == 0) { + 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 == 0) { + ext_smart_log_ptr = (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; + 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; + struct nvme_dev *dev; + nvme_root_t r; + uint64_t capabilities = 0; + __u32 hctm_tmt; + int temperature, temp_tmt1, temp_tmt2; + int ret, fmt = -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: normal|json"), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + fmt = validate_output_format(cfg.output_format); + if (fmt < 0) { + fprintf(stderr, "ERROR : WDC : invalid output format\n"); + ret = fmt; + 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 != 0) + goto out; + ret = nvme_get_log_smart(dev_fd(dev), NVME_NSID_ALL, false, + &smart_log); + if (ret != 0) + goto out; + + /* convert from Kelvin 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("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; + 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 = (wdc_nvme_ext_smart_log *)data; + if (ret == 0) { + 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 != 0) { + 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==NULL?0:strlen(cfg.file); + if (len > 0) { + output_fd = fopen(cfg.file,"wb"); + if (output_fd == 0) { + 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 == NULL) { + 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" \ + "opcode: 0x%02x, flags: 0x%02x, rsvd: 0x%04x, nsid: 0x%08x, cdw2: 0x%08x, cdw3: 0x%08x, " \ + "metadata_len: 0x%08x, data_len: 0x%08x, cdw10: 0x%08x, cdw11: 0x%08x, cdw12: 0x%08x, " \ + "cdw13: 0x%08x, cdw14: 0x%08x, cdw15: 0x%08x, timeout_ms: 0x%08x, result: 0x%08x, " \ + "metadata: %s, " \ + "data: %s\n", \ + nvme_cmd.opcode, nvme_cmd.flags, nvme_cmd.rsvd1, nvme_cmd.nsid, nvme_cmd.cdw2, nvme_cmd.cdw3, \ + nvme_cmd.metadata_len, nvme_cmd.data_len, nvme_cmd.cdw10, nvme_cmd.cdw11, nvme_cmd.cdw12, \ + nvme_cmd.cdw13, nvme_cmd.cdw14, nvme_cmd.cdw15, 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 != 0) { + 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 != 0) { + 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 == NULL) { + fprintf(stderr, "%s: ERROR : malloc : %s\n",__func__, strerror(errno)); + return -1; + } + memset(dump_data, 0, sizeof (__u8) * dump_length); + memset(&admin_cmd, 0, sizeof (struct nvme_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 != 0) { + 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, (long unsigned int)admin_cmd.addr); + break; + } + + if ((curr_data_offset + xfer_size) <= data_len) + curr_data_len = xfer_size; + else + curr_data_len = data_len - curr_data_offset; /* last transfer */ + + curr_data_offset += curr_data_len; + 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; +} diff --git a/plugins/wdc/wdc-nvme.h b/plugins/wdc/wdc-nvme.h new file mode 100644 index 0000000..242cf9a --- /dev/null +++ b/plugins/wdc/wdc-nvme.h @@ -0,0 +1,53 @@ +// 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.1.2" +#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) + ) +); + +#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..38e61ed --- /dev/null +++ b/plugins/wdc/wdc-utils.c @@ -0,0 +1,166 @@ +// 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 , + */ + +#include +#include +#include +#include +#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 comapring. + * @param pcDst Points to a null terminated string for comapring. + * + * @returns zero if the string matches or + * 1 if the pcSrc string is lexically higher than pcDst or + * -1 if the pcSrc string is lexically lower than pcDst. + */ +int wdc_UtilsStrCompare(char *pcSrc, char *pcDst) +{ + while ((toupper(*pcSrc) == toupper(*pcDst)) && (*pcSrc != '\0')) { + pcSrc++; + pcDst++; + } + return *pcSrc - *pcDst; +} + +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--; + } +} + diff --git a/plugins/wdc/wdc-utils.h b/plugins/wdc/wdc-utils.h new file mode 100644 index 0000000..83b208e --- /dev/null +++ b/plugins/wdc/wdc-utils.h @@ -0,0 +1,79 @@ +/* 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 , + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* 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); + diff --git a/plugins/ymtc/ymtc-nvme.c b/plugins/ymtc/ymtc-nvme.c new file mode 100644 index 0000000..d04481c --- /dev/null +++ b/plugins/ymtc/ymtc-nvme.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include + +#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); + /* Clumn 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 +#include +#include +#include + +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..39f79cb --- /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 + +/* Addtional 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..f8809ba --- /dev/null +++ b/plugins/zns/zns.c @@ -0,0 +1,1290 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "nvme.h" +#include "libnvme.h" +#include "nvme-print.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 an ZNS specific Identify Controller command to "\ + "the given device and report information about the specified "\ + "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 = flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + + 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_fd: + dev_close(dev); + return err; +} + +static int id_ns(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Send an ZNS specific Identify Namespace command to "\ + "the given device and report information about the specified "\ + "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; + + flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + 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_fd; + } + } + + err = nvme_identify_ns(dev_fd(dev), cfg.namespace_id, &id_ns); + if (err) { + nvme_show_status(err); + goto close_fd; + } + + 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_fd: + 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 extention 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 extention\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; + + flags = validate_output_format(cfg.output_format); + if (flags < 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; + bool huge = false; + struct nvme_zone_report *report, *buff; + + unsigned int nr_zones_chunks = 1024, /* 1024 entries * 64 bytes per entry = 64k byte transfer */ + nr_zones_retrieved = 0, + nr_zones, + offset, + log_len; + 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 = 0; + + 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; + + flags = validate_output_format(cfg.output_format); + if (flags < 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(report_size, &huge); + if (!report) { + perror("alloc"); + err = -ENOMEM; + goto close_dev; + } + + offset = cfg.zslba; + if (flags & JSON) + zone_list = json_create_array(); + else + printf("nr_zones: %"PRIu64"\n", (uint64_t)le64_to_cpu(total_nr_zones)); + + 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, flags, zone_list); + + nr_zones_retrieved += nr_zones_chunks; + offset = le64_to_cpu(report->entries[nr_zones_chunks-1].zslba) + zsze; + } + + if (flags & JSON) + json_nvme_finish_zone_list(total_nr_zones, zone_list); + + nvme_free(report, huge); + +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 "\ + "using the slba of the zone, and the write will be appended from the "\ + "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; + + flags = validate_output_format(cfg.output_format); + if (flags < 0) + goto close_fd; + + if (!cfg.namespace_id) { + err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id); + if (err < 0) { + perror("get-namespace-id"); + goto close_fd; + } + } + + 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_fd: + dev_close(dev); + return err; +} diff --git a/plugins/zns/zns.h b/plugins/zns/zns.h new file mode 100644 index 0000000..77bfdd6 --- /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", "Finishe 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" -- cgit v1.2.3